mirror of
https://github.com/AppFlowy-IO/AppFlowy-Web.git
synced 2026-03-13 10:00:26 +08:00
chore: fix update issues (#31)
This commit is contained in:
@@ -21,6 +21,7 @@ import {
|
||||
YjsEditorKey,
|
||||
YSharedRoot,
|
||||
} from '@/application/types';
|
||||
import EventEmitter from 'events';
|
||||
|
||||
export interface DatabaseContextState {
|
||||
readOnly: boolean;
|
||||
@@ -54,6 +55,7 @@ export interface DatabaseContextState {
|
||||
testDatabasePromptConfig?: TestDatabasePromptConfig;
|
||||
requestInstance?: AxiosInstance | null;
|
||||
checkIfRowDocumentExists?: (documentId: string) => Promise<void>;
|
||||
eventEmitter?: EventEmitter;
|
||||
}
|
||||
|
||||
export const DatabaseContext = createContext<DatabaseContextState | null>(null);
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
getDateFormat,
|
||||
getTimeFormat,
|
||||
getTypeOptions,
|
||||
parseRelationTypeOption,
|
||||
parseSelectOptionTypeOptions,
|
||||
SelectOption,
|
||||
TimeFormat,
|
||||
@@ -290,6 +291,30 @@ export function useFieldSelector(fieldId: string) {
|
||||
};
|
||||
}
|
||||
|
||||
export function useDatabaseIdFromField(fieldId: string) {
|
||||
const database = useDatabase();
|
||||
const field = database?.get(YjsDatabaseKey.fields)?.get(fieldId);
|
||||
const [databaseId, setDatabaseId] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!field) return;
|
||||
|
||||
const observerEvent = () => {
|
||||
setDatabaseId(parseRelationTypeOption(field)?.database_id);
|
||||
};
|
||||
|
||||
observerEvent();
|
||||
|
||||
field.observe(observerEvent);
|
||||
|
||||
return () => {
|
||||
field.unobserve(observerEvent);
|
||||
};
|
||||
}, [database, field, fieldId]);
|
||||
|
||||
return databaseId;
|
||||
}
|
||||
|
||||
export function useFiltersSelector() {
|
||||
const database = useDatabase();
|
||||
const viewId = useDatabaseViewId();
|
||||
@@ -725,11 +750,14 @@ export function useRowOrdersSelector() {
|
||||
sorts?.observeDeep(throttleChange);
|
||||
filters?.observeDeep(throttleChange);
|
||||
fields?.observeDeep(throttleChange);
|
||||
Object.values(rows || {}).forEach((rowDoc) => {
|
||||
const rowSharedRoot = rowDoc.getMap(YjsEditorKey.data_section);
|
||||
const databaseRow = rowSharedRoot.get(YjsEditorKey.database_row) as YDatabaseRow;
|
||||
const debouncedConditionsChange = debounce(onConditionsChange, 150);
|
||||
|
||||
databaseRow?.get(YjsDatabaseKey.cells)?.observeDeep(throttleChange);
|
||||
const observerRowsEvent = () => {
|
||||
debouncedConditionsChange();
|
||||
};
|
||||
|
||||
Object.values(rows || {}).forEach((row) => {
|
||||
row.getMap(YjsEditorKey.data_section).observeDeep(observerRowsEvent);
|
||||
});
|
||||
|
||||
return () => {
|
||||
@@ -737,12 +765,8 @@ export function useRowOrdersSelector() {
|
||||
sorts?.unobserveDeep(throttleChange);
|
||||
filters?.unobserveDeep(throttleChange);
|
||||
fields?.unobserveDeep(throttleChange);
|
||||
|
||||
Object.values(rows || {}).forEach((rowDoc) => {
|
||||
const rowSharedRoot = rowDoc.getMap(YjsEditorKey.data_section);
|
||||
const databaseRow = rowSharedRoot.get(YjsEditorKey.database_row) as YDatabaseRow;
|
||||
|
||||
databaseRow?.get(YjsDatabaseKey.cells)?.unobserveDeep(throttleChange);
|
||||
Object.values(rows || {}).forEach((row) => {
|
||||
row.getMap(YjsEditorKey.data_section).unobserveDeep(observerRowsEvent);
|
||||
});
|
||||
};
|
||||
}, [onConditionsChange, view, fields, filters, sorts, rows]);
|
||||
|
||||
@@ -50,6 +50,7 @@ import {
|
||||
UploadPublishNamespacePayload,
|
||||
User,
|
||||
View,
|
||||
ViewIconType,
|
||||
ViewId,
|
||||
ViewInfo,
|
||||
ViewLayout,
|
||||
@@ -1548,6 +1549,37 @@ export async function updatePage(workspaceId: string, viewId: string, data: Upda
|
||||
return Promise.reject(res?.data);
|
||||
}
|
||||
|
||||
export async function updatePageIcon(workspaceId: string, viewId: string, icon: {
|
||||
ty: ViewIconType;
|
||||
value: string;
|
||||
}): Promise<void> {
|
||||
const url = `/api/workspace/${workspaceId}/page-view/${viewId}/update-icon`;
|
||||
const response = await axiosInstance?.post<{
|
||||
code: number;
|
||||
message: string;
|
||||
}>(url, { icon });
|
||||
|
||||
if (response?.data.code === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return Promise.reject(response?.data);
|
||||
}
|
||||
|
||||
export async function updatePageName(workspaceId: string, viewId: string, name: string): Promise<void> {
|
||||
const url = `/api/workspace/${workspaceId}/page-view/${viewId}/update-name`;
|
||||
const response = await axiosInstance?.post<{
|
||||
code: number;
|
||||
message: string;
|
||||
}>(url, { name });
|
||||
|
||||
if (response?.data.code === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return Promise.reject(response?.data);
|
||||
}
|
||||
|
||||
export async function deleteTrash(workspaceId: string, viewId?: string) {
|
||||
if (viewId) {
|
||||
const url = `/api/workspace/${workspaceId}/trash/${viewId}`;
|
||||
@@ -2155,7 +2187,7 @@ export async function addRecentPages(workspaceId: string, viewIds: string[]) {
|
||||
}
|
||||
|
||||
export async function checkIfCollabExists(workspaceId: string, objectId: string) {
|
||||
const url = `/api/workspace/${workspaceId}/collab/${objectId}/row-document-collab-exists`;
|
||||
const url = `/api/workspace/${workspaceId}/collab/${objectId}/collab-exists`;
|
||||
|
||||
const response = await axiosInstance?.get<{
|
||||
code: number;
|
||||
|
||||
@@ -54,6 +54,7 @@ import {
|
||||
UploadPublishNamespacePayload,
|
||||
WorkspaceMember,
|
||||
YjsEditorKey,
|
||||
ViewIconType
|
||||
} from '@/application/types';
|
||||
import { applyYDoc } from '@/application/ydoc/apply';
|
||||
|
||||
@@ -703,4 +704,16 @@ export class AFClientService implements AFService {
|
||||
async addRecentPages(workspaceId: string, viewIds: string[]) {
|
||||
return APIService.addRecentPages(workspaceId, viewIds);
|
||||
}
|
||||
|
||||
async updateAppPageIcon(
|
||||
workspaceId: string,
|
||||
viewId: string,
|
||||
icon: { ty: ViewIconType; value: string }
|
||||
): Promise<void> {
|
||||
return APIService.updatePageIcon(workspaceId, viewId, icon);
|
||||
}
|
||||
|
||||
async updateAppPageName(workspaceId: string, viewId: string, name: string): Promise<void> {
|
||||
return APIService.updatePageName(workspaceId, viewId, name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
User,
|
||||
UserWorkspaceInfo,
|
||||
View,
|
||||
ViewIconType,
|
||||
Workspace,
|
||||
WorkspaceMember,
|
||||
YDoc,
|
||||
@@ -132,6 +133,8 @@ export interface AppService {
|
||||
addAppPage: (workspaceId: string, parentViewId: string, payload: CreatePagePayload) => Promise<string>;
|
||||
createFolderView: (workspaceId: string, payload: CreateFolderViewPayload) => Promise<string>;
|
||||
updateAppPage: (workspaceId: string, viewId: string, data: UpdatePagePayload) => Promise<void>;
|
||||
updateAppPageIcon: (workspaceId: string, viewId: string, icon: { ty: ViewIconType; value: string }) => Promise<void>;
|
||||
updateAppPageName: (workspaceId: string, viewId: string, name: string) => Promise<void>;
|
||||
deleteTrash: (workspaceId: string, viewId?: string) => Promise<void>;
|
||||
moveToTrash: (workspaceId: string, viewId: string) => Promise<void>;
|
||||
restoreFromTrash: (workspaceId: string, viewId?: string) => Promise<void>;
|
||||
|
||||
@@ -1040,6 +1040,8 @@ export interface ViewMetaProps {
|
||||
readOnly?: boolean;
|
||||
updatePage?: (viewId: string, data: UpdatePagePayload) => Promise<void>;
|
||||
uploadFile?: (file: File) => Promise<string>;
|
||||
updatePageIcon?: (viewId: string, icon: { ty: ViewIconType; value: string }) => Promise<void>;
|
||||
updatePageName?: (viewId: string, name: string) => Promise<void>;
|
||||
onEnter?: (text: string) => void;
|
||||
maxWidth?: number;
|
||||
onFocus?: () => void;
|
||||
@@ -1082,6 +1084,8 @@ export interface ViewComponentProps {
|
||||
config: PromptDatabaseConfiguration;
|
||||
fields: DatabasePromptField[];
|
||||
}>;
|
||||
updatePageIcon?: (viewId: string, icon: { ty: ViewIconType; value: string }) => Promise<void>;
|
||||
updatePageName?: (viewId: string, name: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export interface CreatePagePayload {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { openUrl } from '@/utils/url';
|
||||
import { CircularProgress } from '@mui/material';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import Typography from '@mui/material/Typography';
|
||||
@@ -7,6 +6,8 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { createApi } from 'unsplash-js';
|
||||
|
||||
import { openUrl } from '@/utils/url';
|
||||
|
||||
const unsplash = createApi({
|
||||
accessKey: '1WxD1JpMOUX86lZKKob4Ca0LMZPyO2rUmAgjpWm9Ids',
|
||||
});
|
||||
@@ -129,7 +130,7 @@ export function Unsplash({ onDone, onEscape }: { onDone?: (value: string) => voi
|
||||
<>
|
||||
<div className={`grid w-full grid-cols-4 gap-4 max-sm:grid-cols-3`}>
|
||||
{photos.map((photo) => (
|
||||
<div key={photo.id} className={'flex flex-col gap-2'}>
|
||||
<div key={photo.id + photo.full} className={'flex flex-col gap-2'}>
|
||||
<div className={'relative pt-[56.25%]'}>
|
||||
<img
|
||||
onClick={() => {
|
||||
|
||||
@@ -82,7 +82,14 @@ function DatabaseView(props: ViewComponentProps) {
|
||||
className={'relative flex h-full w-full flex-col'}
|
||||
>
|
||||
{rowId ? null : (
|
||||
<ViewMetaPreview {...viewMeta} readOnly={props.readOnly} updatePage={props.updatePage} uploadFile={uploadFile} />
|
||||
<ViewMetaPreview
|
||||
{...viewMeta}
|
||||
readOnly={props.readOnly}
|
||||
updatePage={props.updatePage}
|
||||
updatePageIcon={props.updatePageIcon}
|
||||
updatePageName={props.updatePageName}
|
||||
uploadFile={uploadFile}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Suspense fallback={skeleton}>
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
UpdateSpacePayload,
|
||||
UserWorkspaceInfo,
|
||||
View,
|
||||
ViewIconType,
|
||||
ViewLayout,
|
||||
YDatabase,
|
||||
YDoc,
|
||||
@@ -80,6 +81,8 @@ export interface AppContextType {
|
||||
addPage?: (parentId: string, payload: CreatePagePayload) => Promise<string>;
|
||||
deletePage?: (viewId: string) => Promise<void>;
|
||||
updatePage?: (viewId: string, payload: UpdatePagePayload) => Promise<void>;
|
||||
updatePageIcon?: (viewId: string, icon: { ty: ViewIconType; value: string }) => Promise<void>;
|
||||
updatePageName?: (viewId: string, name: string) => Promise<void>;
|
||||
deleteTrash?: (viewId?: string) => Promise<void>;
|
||||
restorePage?: (viewId?: string) => Promise<void>;
|
||||
movePage?: (viewId: string, parentId: string, prevViewId?: string) => Promise<void>;
|
||||
@@ -744,6 +747,40 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
[currentWorkspaceId, service, loadOutline]
|
||||
);
|
||||
|
||||
const updatePageIcon = useCallback(
|
||||
async (viewId: string, icon: { ty: ViewIconType; value: string }) => {
|
||||
if (!currentWorkspaceId || !service) {
|
||||
throw new Error('No workspace or service found');
|
||||
}
|
||||
|
||||
try {
|
||||
await service.updateAppPageIcon(currentWorkspaceId, viewId, icon);
|
||||
|
||||
return;
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
},
|
||||
[currentWorkspaceId, service]
|
||||
);
|
||||
|
||||
const updatePageName = useCallback(
|
||||
async (viewId: string, name: string) => {
|
||||
if (!currentWorkspaceId || !service) {
|
||||
throw new Error('No workspace or service found');
|
||||
}
|
||||
|
||||
try {
|
||||
await service.updateAppPageName(currentWorkspaceId, viewId, name);
|
||||
|
||||
return;
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
},
|
||||
[currentWorkspaceId, service]
|
||||
);
|
||||
|
||||
const movePage = useCallback(
|
||||
async (viewId: string, parentId: string, prevViewId?: string) => {
|
||||
if (!currentWorkspaceId || !service) {
|
||||
@@ -1284,6 +1321,8 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
getMentionUser,
|
||||
awarenessMap,
|
||||
checkIfRowDocumentExists,
|
||||
updatePageIcon,
|
||||
updatePageName,
|
||||
}}
|
||||
>
|
||||
<AIChatProvider>
|
||||
@@ -1459,6 +1498,8 @@ export function useAppHandlers() {
|
||||
getMentionUser: context.getMentionUser,
|
||||
awarenessMap: context.awarenessMap,
|
||||
checkIfRowDocumentExists: context.checkIfRowDocumentExists,
|
||||
updatePageIcon: context.updatePageIcon,
|
||||
updatePageName: context.updatePageName,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -153,7 +153,7 @@ export function ApproveConversion() {
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return <ErrorPage onRetry={loadConversion} />;
|
||||
return <ErrorPage onRetry={handleApprove} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -135,7 +135,7 @@ function ApproveRequestPage() {
|
||||
);
|
||||
|
||||
if (isError) {
|
||||
return <ErrorPage onRetry={loadRequestInfo} />;
|
||||
return <ErrorPage onRetry={handleApprove} />;
|
||||
}
|
||||
|
||||
if (notInvitee) {
|
||||
|
||||
@@ -105,7 +105,7 @@ function InviteCode() {
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return <ErrorPage onRetry={loadWorkspaceInfo} />;
|
||||
return <ErrorPage onRetry={handleJoin} />;
|
||||
}
|
||||
|
||||
if (hasJoined) {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import { ReactComponent as NoAccessLogo } from '@/assets/icons/no_access.svg';
|
||||
import { ReactComponent as SuccessLogo } from '@/assets/icons/success_logo.svg';
|
||||
import { useAppViewId, useCurrentWorkspaceId } from '@/components/app/app.hooks';
|
||||
import { useService } from '@/components/main/app.hooks';
|
||||
import { useCurrentUser, useService } from '@/components/main/app.hooks';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { ErrorPage } from '@/components/_shared/landing-page/ErrorPage';
|
||||
import LandingPage from '@/components/_shared/landing-page/LandingPage';
|
||||
@@ -17,10 +18,12 @@ function RequestAccess() {
|
||||
const service = useService();
|
||||
const currentWorkspaceId = useCurrentWorkspaceId();
|
||||
const viewId = useAppViewId();
|
||||
const [searchParams] = useSearchParams();
|
||||
const isGuest = searchParams.get('is_guest') === 'true';
|
||||
const [hasSend, setHasSend] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [isError, setIsError] = useState(false);
|
||||
|
||||
const currentUser = useCurrentUser();
|
||||
const handleSendRequest = async () => {
|
||||
try {
|
||||
if (!service) return;
|
||||
@@ -47,6 +50,21 @@ function RequestAccess() {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isGuest && currentUser) {
|
||||
window.open(
|
||||
`appflowy-flutter://open-page?workspace_id=${currentWorkspaceId}&view_id=${viewId}&email=${currentUser.email}`,
|
||||
'_self'
|
||||
);
|
||||
}
|
||||
}, [isGuest, currentWorkspaceId, viewId, currentUser]);
|
||||
|
||||
const description = isGuest
|
||||
? `${t(
|
||||
'landingPage.noAccess.description'
|
||||
)}\n\n Guests invited to this page can access it via the desktop or mobile app.`
|
||||
: t('landingPage.noAccess.description');
|
||||
|
||||
if (hasSend) {
|
||||
return (
|
||||
<LandingPage
|
||||
@@ -69,7 +87,7 @@ function RequestAccess() {
|
||||
<LandingPage
|
||||
Logo={NoAccessLogo}
|
||||
title={t('landingPage.noAccess.title')}
|
||||
description={t('landingPage.noAccess.description')}
|
||||
description={description}
|
||||
primaryAction={{
|
||||
onClick: handleSendRequest,
|
||||
loading,
|
||||
|
||||
@@ -152,9 +152,6 @@ function Database(props: Database2Props) {
|
||||
|
||||
setOpenModalViewId(viewId);
|
||||
setOpenModalRowDatabaseDoc(viewDoc);
|
||||
const database = viewDoc.getMap(YjsEditorKey.data_section)?.get(YjsEditorKey.database) as YDatabase;
|
||||
|
||||
console.log('======', database.toJSON());
|
||||
|
||||
const rowDoc = await createRowDoc?.(`${viewDoc.guid}_rows_${rowId}`);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { GroupColumn, Row, useBoardLayoutSettings, useGetBoardHiddenGroup } from '@/application/database-yjs';
|
||||
import { useReorderGroupColumnDispatch } from '@/application/database-yjs/dispatch';
|
||||
@@ -6,7 +6,7 @@ import HiddenColumnItem from '@/components/database/components/board/column/Hidd
|
||||
import HiddenGroupColumnHeader from '@/components/database/components/board/column/HiddenGroupColumnHeader';
|
||||
import { DragContext, useDragContextValue } from '@/components/database/components/drag-and-drop/useDragContext';
|
||||
|
||||
function HiddenGroupColumn ({
|
||||
function HiddenGroupColumn({
|
||||
groupId,
|
||||
fieldId,
|
||||
getRows,
|
||||
@@ -15,37 +15,38 @@ function HiddenGroupColumn ({
|
||||
fieldId: string;
|
||||
getRows: (id: string) => Row[];
|
||||
}) {
|
||||
const {
|
||||
hiddenColumns,
|
||||
} = useGetBoardHiddenGroup(groupId);
|
||||
const { hiddenColumns } = useGetBoardHiddenGroup(groupId);
|
||||
const { isCollapsed } = useBoardLayoutSettings();
|
||||
const reorderColumn = useReorderGroupColumnDispatch(groupId);
|
||||
const onReorder = ({
|
||||
oldData,
|
||||
newData,
|
||||
startIndex,
|
||||
finishIndex,
|
||||
}: {
|
||||
oldData: GroupColumn[]
|
||||
newData: GroupColumn[]
|
||||
startIndex: number
|
||||
finishIndex: number
|
||||
}) => {
|
||||
const columnId = oldData[startIndex].id;
|
||||
const onReorder = useCallback(
|
||||
({
|
||||
oldData,
|
||||
newData,
|
||||
startIndex,
|
||||
finishIndex,
|
||||
}: {
|
||||
oldData: GroupColumn[];
|
||||
newData: GroupColumn[];
|
||||
startIndex: number;
|
||||
finishIndex: number;
|
||||
}) => {
|
||||
const columnId = oldData[startIndex].id;
|
||||
|
||||
if (!columnId) {
|
||||
throw new Error('No columnId provided');
|
||||
}
|
||||
if (!columnId) {
|
||||
throw new Error('No columnId provided');
|
||||
}
|
||||
|
||||
const beforeId = newData[finishIndex - 1]?.id;
|
||||
const beforeId = newData[finishIndex - 1]?.id;
|
||||
|
||||
reorderColumn(columnId, beforeId);
|
||||
};
|
||||
reorderColumn(columnId, beforeId);
|
||||
},
|
||||
[reorderColumn]
|
||||
);
|
||||
|
||||
const [container, setContainer] = useState<HTMLDivElement | null>(null);
|
||||
const contextValue = useDragContextValue({
|
||||
data: hiddenColumns,
|
||||
enabled: true,
|
||||
enabled: !isCollapsed,
|
||||
reorderAction: onReorder,
|
||||
container,
|
||||
});
|
||||
@@ -56,26 +57,20 @@ function HiddenGroupColumn ({
|
||||
style={{
|
||||
width: isCollapsed ? '32px' : '240px',
|
||||
}}
|
||||
className={'flex transform transition-all flex-col overflow-hidden gap-2'}
|
||||
className={'flex transform flex-col gap-2 overflow-hidden transition-all'}
|
||||
>
|
||||
<HiddenGroupColumnHeader />
|
||||
{!isCollapsed && <DragContext.Provider value={contextValue}>
|
||||
<div className={'flex flex-col justify-start'}>
|
||||
{hiddenColumns.map((column) => (
|
||||
<HiddenColumnItem
|
||||
key={column.id}
|
||||
fieldId={fieldId}
|
||||
id={column.id}
|
||||
getRows={getRows}
|
||||
groupId={groupId}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</DragContext.Provider>}
|
||||
|
||||
|
||||
{!isCollapsed && (
|
||||
<DragContext.Provider value={contextValue}>
|
||||
<div className={'flex flex-col justify-start'}>
|
||||
{hiddenColumns.map((column) => (
|
||||
<HiddenColumnItem key={column.id} fieldId={fieldId} id={column.id} getRows={getRows} groupId={groupId} />
|
||||
))}
|
||||
</div>
|
||||
</DragContext.Provider>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default HiddenGroupColumn;
|
||||
export default HiddenGroupColumn;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { CellProps, RelationCell as RelationCellType } from '@/application/database-yjs/cell.type';
|
||||
import RelationCellMenu from '@/components/database/components/cell/relation/RelationCellMenu';
|
||||
import RelationItems from '@/components/database/components/cell/relation/RelationItems';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export function RelationCell({
|
||||
cell,
|
||||
|
||||
@@ -111,7 +111,7 @@ function RelationCellMenuContent({
|
||||
|
||||
const rowSharedRoot = rowDoc.getMap(YjsEditorKey.data_section);
|
||||
const row = rowSharedRoot?.get(YjsEditorKey.database_row) as YDatabaseRow;
|
||||
const cell = row.get(YjsDatabaseKey.cells)?.get(primaryFieldId);
|
||||
const cell = row?.get(YjsDatabaseKey.cells)?.get(primaryFieldId);
|
||||
|
||||
if (!cell) return '';
|
||||
const cellValue = parseYDatabaseCellToCell(cell);
|
||||
|
||||
@@ -4,12 +4,11 @@ import * as Y from 'yjs';
|
||||
import {
|
||||
DatabaseContextState,
|
||||
getPrimaryFieldId,
|
||||
parseRelationTypeOption,
|
||||
useDatabaseContext,
|
||||
useFieldSelector,
|
||||
useDatabaseIdFromField,
|
||||
} from '@/application/database-yjs';
|
||||
import { RelationCell, RelationCellData } from '@/application/database-yjs/cell.type';
|
||||
import { View, YDatabase, YDoc, YjsEditorKey } from '@/application/types';
|
||||
import { YDoc, YjsEditorKey } from '@/application/types';
|
||||
import { RelationPrimaryValue } from '@/components/database/components/cell/relation/RelationPrimaryValue';
|
||||
import { notify } from '@/components/_shared/notify';
|
||||
import { cn } from '@/lib/utils';
|
||||
@@ -27,13 +26,12 @@ function RelationItems({
|
||||
}) {
|
||||
const context = useDatabaseContext();
|
||||
const viewId = context.iidIndex;
|
||||
const { field } = useFieldSelector(fieldId);
|
||||
const relatedDatabaseId = field ? parseRelationTypeOption(field)?.database_id : null;
|
||||
const relatedDatabaseId = useDatabaseIdFromField(fieldId);
|
||||
|
||||
const createRowDoc = context.createRowDoc;
|
||||
const loadViewMeta = context.loadViewMeta;
|
||||
const loadView = context.loadView;
|
||||
const navigateToRow = context.navigateToRow;
|
||||
const loadDatabaseRelations = context.loadDatabaseRelations;
|
||||
|
||||
const [noAccess, setNoAccess] = useState(false);
|
||||
const [relations, setRelations] = useState<Record<string, string> | null>();
|
||||
@@ -41,25 +39,25 @@ function RelationItems({
|
||||
const [relatedFieldId, setRelatedFieldId] = useState<string | undefined>();
|
||||
const relatedViewId = relatedDatabaseId ? relations?.[relatedDatabaseId] : null;
|
||||
const [docGuid, setDocGuid] = useState<string | null>(null);
|
||||
const [databaseDoc, setDatabaseDoc] = useState<YDoc | null>(null);
|
||||
|
||||
const [rowIds, setRowIds] = useState([] as string[]);
|
||||
|
||||
const navigateToView = context.navigateToView;
|
||||
|
||||
useEffect(() => {
|
||||
if (!viewId) return;
|
||||
const loadRelations = async () => {
|
||||
const relations = await loadDatabaseRelations?.();
|
||||
|
||||
const update = (meta: View | null) => {
|
||||
if (!meta) return;
|
||||
setRelations(meta.database_relations);
|
||||
setRelations(relations);
|
||||
};
|
||||
|
||||
try {
|
||||
void loadViewMeta?.(viewId, update);
|
||||
void loadRelations();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}, [loadViewMeta, viewId]);
|
||||
}, [loadDatabaseRelations]);
|
||||
|
||||
const handleUpdateRowIds = useCallback(() => {
|
||||
const data = cell?.data;
|
||||
@@ -109,11 +107,8 @@ function RelationItems({
|
||||
}
|
||||
|
||||
setDocGuid(viewDoc.guid);
|
||||
const database = viewDoc.getMap(YjsEditorKey.data_section).get(YjsEditorKey.database) as YDatabase;
|
||||
const fieldId = getPrimaryFieldId(database);
|
||||
|
||||
setNoAccess(!fieldId);
|
||||
setRelatedFieldId(fieldId);
|
||||
setDatabaseDoc(viewDoc);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
setNoAccess(true);
|
||||
@@ -121,6 +116,27 @@ function RelationItems({
|
||||
})();
|
||||
}, [loadView, relatedViewId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!databaseDoc) return;
|
||||
const sharedRoot = databaseDoc.getMap(YjsEditorKey.data_section);
|
||||
|
||||
const observerEvent = () => {
|
||||
const database = sharedRoot.get(YjsEditorKey.database);
|
||||
|
||||
const fieldId = getPrimaryFieldId(database);
|
||||
|
||||
setRelatedFieldId(fieldId);
|
||||
setNoAccess(!fieldId);
|
||||
};
|
||||
|
||||
observerEvent();
|
||||
|
||||
sharedRoot.observe(observerEvent);
|
||||
return () => {
|
||||
sharedRoot.unobserve(observerEvent);
|
||||
};
|
||||
}, [databaseDoc]);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={style}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import {
|
||||
@@ -13,7 +13,16 @@ import {
|
||||
} from '@/application/database-yjs';
|
||||
import { getCellDataText } from '@/application/database-yjs/cell.parse';
|
||||
import { useUpdateRowMetaDispatch } from '@/application/database-yjs/dispatch';
|
||||
import { YDatabaseCell, YDatabaseField, YDatabaseRow, YDoc, YjsDatabaseKey, YjsEditorKey } from '@/application/types';
|
||||
import { YjsEditor } from '@/application/slate-yjs';
|
||||
import {
|
||||
BlockType,
|
||||
YDatabaseCell,
|
||||
YDatabaseField,
|
||||
YDatabaseRow,
|
||||
YDoc,
|
||||
YjsDatabaseKey,
|
||||
YjsEditorKey,
|
||||
} from '@/application/types';
|
||||
import { Editor } from '@/components/editor';
|
||||
import { EditorSkeleton } from '@/components/_shared/skeleton/EditorSkeleton';
|
||||
|
||||
@@ -79,25 +88,6 @@ export const DatabaseRowSubDocument = memo(({ rowId }: { rowId: string }) => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [doc, setDoc] = useState<YDoc | null>(null);
|
||||
|
||||
const handleCreateDocument = useCallback(
|
||||
async (documentId: string) => {
|
||||
if (!createOrphanedView || !documentId) return;
|
||||
try {
|
||||
setDoc(null);
|
||||
await createOrphanedView({ document_id: documentId });
|
||||
|
||||
const doc = await loadView?.(documentId, true, true);
|
||||
|
||||
if (doc) {
|
||||
setDoc(doc);
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
} catch (e: any) {
|
||||
toast.error(e.message);
|
||||
}
|
||||
},
|
||||
[createOrphanedView, loadView]
|
||||
);
|
||||
const document = doc?.getMap(YjsEditorKey.data_section)?.get(YjsEditorKey.document);
|
||||
|
||||
const handleOpenDocument = useCallback(
|
||||
@@ -119,6 +109,24 @@ export const DatabaseRowSubDocument = memo(({ rowId }: { rowId: string }) => {
|
||||
},
|
||||
[loadView]
|
||||
);
|
||||
const handleCreateDocument = useCallback(
|
||||
async (documentId: string) => {
|
||||
if (!createOrphanedView || !documentId) return;
|
||||
setLoading(true);
|
||||
try {
|
||||
setDoc(null);
|
||||
await createOrphanedView({ document_id: documentId });
|
||||
|
||||
await handleOpenDocument(documentId);
|
||||
// eslint-disable-next-line
|
||||
} catch (e: any) {
|
||||
toast.error(e.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[createOrphanedView, handleOpenDocument]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!documentId) return;
|
||||
@@ -137,6 +145,57 @@ export const DatabaseRowSubDocument = memo(({ rowId }: { rowId: string }) => {
|
||||
return JSON.stringify(properties);
|
||||
}, [properties]);
|
||||
|
||||
const editorRef = useRef<YjsEditor | null>(null);
|
||||
|
||||
const isDocumentEmpty = useCallback((editor: YjsEditor) => {
|
||||
const children = editor.children;
|
||||
|
||||
if (children.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (children.length === 1) {
|
||||
const firstChildBlockType = 'type' in children[0] ? (children[0].type as BlockType) : BlockType.Paragraph;
|
||||
|
||||
if (firstChildBlockType !== BlockType.Paragraph) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}, []);
|
||||
|
||||
const handleEditorConnected = useCallback(
|
||||
(editor: YjsEditor) => {
|
||||
editorRef.current = editor;
|
||||
if (readOnly) return;
|
||||
|
||||
if (!isDocumentEmpty(editor)) {
|
||||
updateRowMeta(RowMetaKey.IsDocumentEmpty, false);
|
||||
return;
|
||||
}
|
||||
},
|
||||
[isDocumentEmpty, updateRowMeta, readOnly]
|
||||
);
|
||||
|
||||
const handleWordCountChange = useCallback(
|
||||
(_: string, { characters }: { characters: number }) => {
|
||||
if (characters > 0) {
|
||||
updateRowMeta(RowMetaKey.IsDocumentEmpty, false);
|
||||
return;
|
||||
}
|
||||
|
||||
const editor = editorRef.current;
|
||||
|
||||
if (!editor) return;
|
||||
|
||||
updateRowMeta(RowMetaKey.IsDocumentEmpty, isDocumentEmpty(editor));
|
||||
},
|
||||
[isDocumentEmpty, updateRowMeta]
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
return <EditorSkeleton />;
|
||||
}
|
||||
@@ -150,9 +209,8 @@ export const DatabaseRowSubDocument = memo(({ rowId }: { rowId: string }) => {
|
||||
doc={doc}
|
||||
readOnly={readOnly}
|
||||
getMoreAIContext={getMoreAIContext}
|
||||
onWordCountChange={(_, { characters }) => {
|
||||
updateRowMeta(RowMetaKey.IsDocumentEmpty, characters <= 0);
|
||||
}}
|
||||
onEditorConnected={handleEditorConnected}
|
||||
onWordCountChange={handleWordCountChange}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -78,9 +78,9 @@ export function useDragContextValue<
|
||||
stableData.current = data;
|
||||
}, [data]);
|
||||
|
||||
const getData = () => {
|
||||
const getData = useCallback(() => {
|
||||
return stableData.current;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const reorderItem = useCallback(
|
||||
({ startIndex, indexOfTarget, closestEdgeOfTarget }: ReorderPayload) => {
|
||||
@@ -163,9 +163,9 @@ export function useDragContextValue<
|
||||
reorderItem,
|
||||
registerItem: registry.register,
|
||||
instanceId,
|
||||
enabled
|
||||
enabled,
|
||||
}),
|
||||
[reorderItem, registry.register, instanceId, enabled]
|
||||
[getData, reorderItem, registry.register, instanceId, enabled]
|
||||
);
|
||||
|
||||
return contextValue;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { forwardRef, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import { APP_EVENTS } from '@/application/constants';
|
||||
import { useDatabase, useDatabaseContext } from '@/application/database-yjs';
|
||||
import { useAddDatabaseView, useUpdateDatabaseView } from '@/application/database-yjs/dispatch';
|
||||
import { DatabaseViewLayout, View, ViewLayout, YDatabaseView, YjsDatabaseKey } from '@/application/types';
|
||||
@@ -17,6 +18,7 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigge
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { TabLabel, Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import { findView } from '@/components/_shared/outline/utils';
|
||||
import { AFScroller } from '@/components/_shared/scroller';
|
||||
import { ViewIcon } from '@/components/_shared/view-icon';
|
||||
import PageIcon from '@/components/_shared/view-icon/PageIcon';
|
||||
@@ -36,7 +38,7 @@ export const DatabaseTabs = forwardRef<HTMLDivElement, DatabaseTabBarProps>(
|
||||
const views = useDatabase().get(YjsDatabaseKey.views);
|
||||
const context = useDatabaseContext();
|
||||
const onAddView = useAddDatabaseView();
|
||||
const { loadViewMeta, readOnly, showActions = true } = context;
|
||||
const { loadViewMeta, readOnly, showActions = true, eventEmitter } = context;
|
||||
const updatePage = useUpdateDatabaseView();
|
||||
const [meta, setMeta] = useState<View | null>(null);
|
||||
const scrollLeft = context.paddingStart;
|
||||
@@ -118,6 +120,26 @@ export const DatabaseTabs = forwardRef<HTMLDivElement, DatabaseTabBarProps>(
|
||||
}
|
||||
}, [iidIndex, loadViewMeta]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleOutlineLoaded = (outline: View[]) => {
|
||||
const view = findView(outline, iidIndex);
|
||||
|
||||
if (view) {
|
||||
setMeta(view);
|
||||
}
|
||||
};
|
||||
|
||||
if (eventEmitter) {
|
||||
eventEmitter.on(APP_EVENTS.OUTLINE_LOADED, handleOutlineLoaded);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (eventEmitter) {
|
||||
eventEmitter.off(APP_EVENTS.OUTLINE_LOADED, handleOutlineLoaded);
|
||||
}
|
||||
};
|
||||
}, [iidIndex, eventEmitter, reloadView]);
|
||||
|
||||
const renameView = useMemo(() => {
|
||||
if (renameViewId === iidIndex) return meta;
|
||||
return meta?.children.find((v) => v.view_id === renameViewId);
|
||||
|
||||
@@ -22,7 +22,7 @@ export type DocumentProps = ViewComponentProps & {
|
||||
|
||||
export const Document = (props: DocumentProps) => {
|
||||
const [search] = useSearchParams();
|
||||
const { doc, readOnly, viewMeta, isTemplateThumb, updatePage, onRendered, onEditorConnected, uploadFile } = props;
|
||||
const { doc, readOnly, viewMeta, isTemplateThumb, updatePage, onRendered, onEditorConnected, uploadFile, updatePageIcon, updatePageName } = props;
|
||||
const blockId = search.get('blockId') || undefined;
|
||||
|
||||
const awareness = useAppAwareness(viewMeta.viewId);
|
||||
@@ -141,6 +141,8 @@ export const Document = (props: DocumentProps) => {
|
||||
{...viewMeta}
|
||||
readOnly={readOnly}
|
||||
updatePage={updatePage}
|
||||
updatePageIcon={updatePageIcon}
|
||||
updatePageName={updatePageName}
|
||||
onEnter={readOnly ? undefined : handleEnter}
|
||||
maxWidth={952}
|
||||
uploadFile={uploadFile}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { debounce } from 'lodash-es';
|
||||
import { memo, useEffect, useMemo, useRef } from 'react';
|
||||
import { memo, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const isCursorAtEnd = (el: HTMLDivElement) => {
|
||||
@@ -23,6 +23,22 @@ const getCursorOffset = () => {
|
||||
return range.startOffset;
|
||||
};
|
||||
|
||||
const setCursorPosition = (element: HTMLDivElement, position: number) => {
|
||||
const range = document.createRange();
|
||||
const selection = window.getSelection();
|
||||
|
||||
if (!element.firstChild) return;
|
||||
|
||||
const textNode = element.firstChild;
|
||||
const maxPosition = textNode.textContent?.length || 0;
|
||||
const safePosition = Math.min(position, maxPosition);
|
||||
|
||||
range.setStart(textNode, safePosition);
|
||||
range.collapse(true);
|
||||
selection?.removeAllRanges();
|
||||
selection?.addRange(range);
|
||||
};
|
||||
|
||||
function TitleEditable({
|
||||
viewId,
|
||||
name,
|
||||
@@ -40,33 +56,29 @@ function TitleEditable({
|
||||
const debounceUpdateName = useMemo(() => {
|
||||
return debounce(onUpdateName, 200);
|
||||
}, [onUpdateName]);
|
||||
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const timestampsRef = useRef<{
|
||||
local: number;
|
||||
remote: number;
|
||||
}>({
|
||||
local: 0,
|
||||
remote: 0,
|
||||
});
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const cursorPositionRef = useRef<number>(0);
|
||||
const initialEditValueRef = useRef<string>('');
|
||||
|
||||
// Only update the content when not editing
|
||||
useEffect(() => {
|
||||
timestampsRef.current.remote = Date.now();
|
||||
if (contentRef.current && timestampsRef.current.local < timestampsRef.current.remote) {
|
||||
if (!isEditing && contentRef.current && contentRef.current.textContent !== name) {
|
||||
const cursorPosition = cursorPositionRef.current;
|
||||
|
||||
contentRef.current.textContent = name;
|
||||
}
|
||||
}, [name]);
|
||||
|
||||
useEffect(() => {
|
||||
if (contentRef.current) {
|
||||
const activeElement = document.activeElement;
|
||||
|
||||
if (activeElement === contentRef.current) {
|
||||
return;
|
||||
// If the element has focus, restore the cursor position
|
||||
if (document.activeElement === contentRef.current) {
|
||||
setTimeout(() => {
|
||||
if (contentRef.current) {
|
||||
setCursorPosition(contentRef.current, cursorPosition);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
contentRef.current.textContent = name;
|
||||
}
|
||||
}, [name]);
|
||||
}, [name, isEditing]);
|
||||
|
||||
const focusedTextbox = () => {
|
||||
const contentBox = contentRef.current;
|
||||
@@ -78,19 +90,22 @@ function TitleEditable({
|
||||
textbox?.focus();
|
||||
};
|
||||
|
||||
// Set focus and cursor position when initializing
|
||||
useEffect(() => {
|
||||
const contentBox = contentRef.current;
|
||||
|
||||
if (!contentBox) return;
|
||||
contentBox.focus();
|
||||
if (contentBox.textContent !== '') {
|
||||
const range = document.createRange();
|
||||
const sel = window.getSelection();
|
||||
|
||||
range.setStart(contentBox.childNodes[0], contentBox.textContent?.length || 0);
|
||||
range.collapse(true);
|
||||
sel?.removeAllRanges();
|
||||
sel?.addRange(range);
|
||||
// Record the initial value (for subsequent editing comparison)
|
||||
initialEditValueRef.current = contentBox.textContent || '';
|
||||
|
||||
contentBox.focus();
|
||||
|
||||
// Move the cursor to the end
|
||||
if (contentBox.textContent !== '') {
|
||||
setTimeout(() => {
|
||||
setCursorPosition(contentBox, contentBox.textContent?.length || 0);
|
||||
}, 0);
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -110,18 +125,52 @@ function TitleEditable({
|
||||
aria-readonly={false}
|
||||
autoFocus={true}
|
||||
onFocus={() => {
|
||||
// Record the initial value when starting to edit
|
||||
if (contentRef.current) {
|
||||
initialEditValueRef.current = contentRef.current.textContent || '';
|
||||
}
|
||||
|
||||
setIsEditing(true);
|
||||
onFocus?.();
|
||||
}}
|
||||
onBlur={() => {
|
||||
// Immediately save the user's latest input to avoid content loss due to debounce
|
||||
if (contentRef.current) {
|
||||
const currentText = contentRef.current.textContent || '';
|
||||
const initialValue = initialEditValueRef.current;
|
||||
|
||||
// Cancel debounce, update immediately (but only when the user really modified the content)
|
||||
debounceUpdateName.cancel();
|
||||
if (currentText !== initialValue) {
|
||||
onUpdateName(currentText);
|
||||
}
|
||||
}
|
||||
|
||||
// Delay a bit before setting the editing state to avoid issues with rapid focus switching
|
||||
setTimeout(() => {
|
||||
setIsEditing(false);
|
||||
}, 100);
|
||||
}}
|
||||
onInput={() => {
|
||||
if (!contentRef.current) return;
|
||||
timestampsRef.current.local = Date.now();
|
||||
debounceUpdateName(contentRef.current.textContent || '');
|
||||
|
||||
// Save the current cursor position
|
||||
cursorPositionRef.current = getCursorOffset();
|
||||
|
||||
// Clean up the automatically inserted <br> tags by the browser
|
||||
if (contentRef.current.innerHTML === '<br>') {
|
||||
contentRef.current.innerHTML = '';
|
||||
}
|
||||
|
||||
// Debounce update remote data
|
||||
debounceUpdateName(contentRef.current.textContent || '');
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (!contentRef.current) return;
|
||||
|
||||
// Save the current cursor position
|
||||
cursorPositionRef.current = getCursorOffset();
|
||||
|
||||
if (e.key === 'Enter' || e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
if (e.key === 'Enter') {
|
||||
@@ -130,7 +179,7 @@ function TitleEditable({
|
||||
const afterText = contentRef.current.textContent?.slice(offset) || '';
|
||||
|
||||
contentRef.current.textContent = beforeText;
|
||||
timestampsRef.current.remote = Date.now();
|
||||
setIsEditing(false);
|
||||
onUpdateName(beforeText);
|
||||
onEnter?.(afterText);
|
||||
|
||||
@@ -138,7 +187,8 @@ function TitleEditable({
|
||||
focusedTextbox();
|
||||
}, 0);
|
||||
} else {
|
||||
timestampsRef.current.remote = Date.now();
|
||||
// Escape key: complete editing and save the current content
|
||||
setIsEditing(false);
|
||||
onUpdateName(contentRef.current.textContent || '');
|
||||
}
|
||||
} else if (e.key === 'ArrowDown' || (e.key === 'ArrowRight' && isCursorAtEnd(contentRef.current))) {
|
||||
|
||||
@@ -23,6 +23,8 @@ export function ViewMetaPreview({
|
||||
uploadFile,
|
||||
layout,
|
||||
onFocus,
|
||||
updatePageIcon,
|
||||
updatePageName,
|
||||
}: ViewMetaProps) {
|
||||
const [cover, setCover] = React.useState<ViewMetaCover | null>(coverProp || null);
|
||||
const [icon, setIcon] = React.useState<ViewMetaIcon | null>(iconProp || null);
|
||||
@@ -69,41 +71,30 @@ export function ViewMetaPreview({
|
||||
|
||||
const handleUpdateIcon = React.useCallback(
|
||||
async (icon: { ty: ViewIconType; value: string }) => {
|
||||
if (!updatePage || !viewId) return;
|
||||
if (!updatePageIcon || !viewId) return;
|
||||
setIcon(icon);
|
||||
try {
|
||||
await updatePage(viewId, {
|
||||
icon,
|
||||
name: name || '',
|
||||
extra: extra || {},
|
||||
});
|
||||
await updatePageIcon(viewId, icon);
|
||||
// eslint-disable-next-line
|
||||
} catch (e: any) {
|
||||
notify.error(e.message);
|
||||
}
|
||||
},
|
||||
[updatePage, viewId, name, extra]
|
||||
[updatePageIcon, viewId]
|
||||
);
|
||||
|
||||
const handleUpdateName = React.useCallback(
|
||||
async (newName: string) => {
|
||||
if (!updatePage || !viewId) return;
|
||||
if (!updatePageName || !viewId) return;
|
||||
try {
|
||||
if (name === newName) return;
|
||||
await updatePage(viewId, {
|
||||
icon: icon || {
|
||||
ty: ViewIconType.Emoji,
|
||||
value: '',
|
||||
},
|
||||
name: newName,
|
||||
extra: extra || {},
|
||||
});
|
||||
await updatePageName(viewId, newName);
|
||||
// eslint-disable-next-line
|
||||
} catch (e: any) {
|
||||
notify.error(e.message);
|
||||
}
|
||||
},
|
||||
[name, updatePage, viewId, icon, extra]
|
||||
[name, updatePageName, viewId]
|
||||
);
|
||||
|
||||
const handleUpdateCover = React.useCallback(
|
||||
@@ -253,7 +244,7 @@ export function ViewMetaPreview({
|
||||
icon,
|
||||
layout: ViewLayout.Document,
|
||||
}}
|
||||
className={'flex h-[90%] w-[90%] items-center justify-center'}
|
||||
className={'flex h-[90%] w-[90%] min-w-[36px] items-center justify-center'}
|
||||
/>
|
||||
</div>
|
||||
</CustomIconPopover>
|
||||
|
||||
@@ -134,7 +134,7 @@ function AcceptInvitationPage() {
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return <ErrorPage onRetry={loadInvitation} />;
|
||||
return <ErrorPage onRetry={handleJoinWorkspace} />;
|
||||
}
|
||||
|
||||
if (hasJoined) {
|
||||
|
||||
Reference in New Issue
Block a user