From ee5124b6799223c8ebcb9ca51480bb4b8832dc0c Mon Sep 17 00:00:00 2001 From: "Kilu.He" <108015703+qinluhe@users.noreply.github.com> Date: Mon, 28 Jul 2025 17:37:56 +0800 Subject: [PATCH] chore: support display mention person on document (#25) * chore: support display mention person on document * chore: add recent pages on open view --- src/@types/translations/en.json | 3 +- .../services/js-services/http/http_api.ts | 432 ++++++++++-------- src/application/services/js-services/index.ts | 8 + src/application/services/services.type.ts | 3 + src/application/types.ts | 271 ++++++----- .../gallery-preview/GalleryPreview.tsx | 18 +- src/components/app/app.hooks.tsx | 62 ++- .../cell/file-media/FileMediaCell.tsx | 4 +- .../cell/file-media/PreviewImage.tsx | 17 +- .../cell/file-media/UnPreviewFile.tsx | 29 +- .../database-row/file-media/FileMediaCell.tsx | 4 +- .../database-row/file-media/FileMediaItem.tsx | 26 +- src/components/editor/EditorContext.tsx | 2 + .../components/blocks/file/FileBlock.tsx | 45 +- .../blocks/gallery/GalleryBlock.tsx | 23 +- .../components/blocks/image/ImageBlock.tsx | 54 ++- .../components/blocks/image/ImageToolbar.tsx | 15 +- .../components/leaf/mention/MentionLeaf.tsx | 75 ++- .../components/leaf/mention/MentionPerson.tsx | 37 ++ 19 files changed, 704 insertions(+), 424 deletions(-) create mode 100644 src/components/editor/components/leaf/mention/MentionPerson.tsx diff --git a/src/@types/translations/en.json b/src/@types/translations/en.json index 95e6ea50..c0b0c4f7 100644 --- a/src/@types/translations/en.json +++ b/src/@types/translations/en.json @@ -3249,5 +3249,6 @@ "membersCount_zero": "{{count}} members", "membersCount_one": "{{count}} member", "membersCount_many": "{{count}} members", - "membersCount_other": "{{count}} members" + "membersCount_other": "{{count}} members", + "deleted": "deleted" } diff --git a/src/application/services/js-services/http/http_api.ts b/src/application/services/js-services/http/http_api.ts index 91ffa725..5db0dd48 100644 --- a/src/application/services/js-services/http/http_api.ts +++ b/src/application/services/js-services/http/http_api.ts @@ -13,58 +13,60 @@ import { Template, TemplateCategory, TemplateCategoryFormValues, - TemplateCreator, TemplateCreatorFormValues, TemplateSummary, + TemplateCreator, + TemplateCreatorFormValues, + TemplateSummary, UploadTemplatePayload, } from '@/application/template.type'; import { + AFWebUser, + CreateFolderViewPayload, + CreatePagePayload, + CreateSpacePayload, + CreateWorkspacePayload, DatabaseId, FolderView, + GenerateAISummaryRowPayload, + GenerateAITranslateRowPayload, + GetRequestAccessInfoResponse, + GuestConversionCodeInfo, + GuestInvitation, + Invitation, + MentionablePerson, + PublishViewPayload, + QuickNote, + QuickNoteEditorData, + RequestAccessInfoStatus, + Role, RowId, + SubscriptionInterval, + SubscriptionPlan, + Subscriptions, + Types, + UpdatePagePayload, + UpdatePublishConfigPayload, + UpdateSpacePayload, + UpdateWorkspacePayload, + UploadPublishNamespacePayload, User, View, ViewId, + ViewInfo, ViewLayout, Workspace, - Invitation, - Types, - AFWebUser, - GetRequestAccessInfoResponse, - Subscriptions, - SubscriptionPlan, - SubscriptionInterval, - RequestAccessInfoStatus, - ViewInfo, - UpdatePagePayload, - CreatePagePayload, - CreateSpacePayload, - UpdateSpacePayload, - Role, WorkspaceMember, - QuickNote, - QuickNoteEditorData, - CreateWorkspacePayload, - UpdateWorkspacePayload, - PublishViewPayload, - UploadPublishNamespacePayload, - UpdatePublishConfigPayload, - CreateFolderViewPayload, - GenerateAITranslateRowPayload, - GenerateAISummaryRowPayload, - GuestInvitation, - GuestConversionCodeInfo, } from '@/application/types'; import { notify } from '@/components/_shared/notify'; - export * from './gotrue'; let axiosInstance: AxiosInstance | null = null; -export function getAxiosInstance () { +export function getAxiosInstance() { return axiosInstance; } -export function initAPIService (config: AFCloudConfig) { +export function initAPIService(config: AFCloudConfig) { if (axiosInstance) { return; } @@ -112,7 +114,7 @@ export function initAPIService (config: AFCloudConfig) { }, (error) => { return Promise.reject(error); - }, + } ); axiosInstance.interceptors.response.use(async (response) => { @@ -139,7 +141,7 @@ export function initAPIService (config: AFCloudConfig) { }); } -export async function signInWithUrl (url: string) { +export async function signInWithUrl(url: string) { const hash = new URL(url).hash; if (!hash) { @@ -176,7 +178,7 @@ export async function signInWithUrl (url: string) { } } -export async function verifyToken (accessToken: string) { +export async function verifyToken(accessToken: string) { const url = `/api/user/verify/${accessToken}`; const response = await axiosInstance?.get<{ code: number; @@ -195,7 +197,7 @@ export async function verifyToken (accessToken: string) { return Promise.reject(data); } -export async function getCurrentUser (): Promise { +export async function getCurrentUser(): Promise { const url = '/api/user/profile'; const response = await axiosInstance?.get<{ code: number; @@ -233,17 +235,17 @@ export async function getCurrentUser (): Promise { } interface AFWorkspace { - workspace_id: string, - owner_uid: number, - owner_name: string, - workspace_name: string, - icon: string, - created_at: string, - member_count: number, - database_storage_id: string, + workspace_id: string; + owner_uid: number; + owner_name: string; + workspace_name: string; + icon: string; + created_at: string; + member_count: number; + database_storage_id: string; } -function afWorkspace2Workspace (workspace: AFWorkspace): Workspace { +function afWorkspace2Workspace(workspace: AFWorkspace): Workspace { return { id: workspace.workspace_id, owner: { @@ -258,7 +260,7 @@ function afWorkspace2Workspace (workspace: AFWorkspace): Workspace { }; } -export async function openWorkspace (workspaceId: string) { +export async function openWorkspace(workspaceId: string) { const url = `/api/workspace/${workspaceId}/open`; const response = await axiosInstance?.put<{ code: number; @@ -272,7 +274,7 @@ export async function openWorkspace (workspaceId: string) { return Promise.reject(response?.data); } -export async function updateWorkspace (workspaceId: string, payload: UpdateWorkspacePayload) { +export async function updateWorkspace(workspaceId: string, payload: UpdateWorkspacePayload) { const url = `/api/workspace`; const response = await axiosInstance?.patch<{ code: number; @@ -294,7 +296,7 @@ export async function updateWorkspace (workspaceId: string, payload: UpdateWorks return Promise.reject(data); } -export async function createWorkspace (payload: CreateWorkspacePayload) { +export async function createWorkspace(payload: CreateWorkspacePayload) { const url = '/api/workspace'; const response = await axiosInstance?.post<{ code: number; @@ -313,23 +315,22 @@ export async function createWorkspace (payload: CreateWorkspacePayload) { return Promise.reject(data); } -export async function getUserWorkspaceInfo (): Promise<{ +export async function getUserWorkspaceInfo(): Promise<{ user_id: string; selected_workspace: Workspace; workspaces: Workspace[]; }> { const url = '/api/user/workspace'; const response = await axiosInstance?.get<{ - code: number, - message: string, + code: number; + message: string; data: { user_profile: { uuid: string; - }, - visiting_workspace: AFWorkspace, - workspaces: AFWorkspace[] - } - + }; + visiting_workspace: AFWorkspace; + workspaces: AFWorkspace[]; + }; }>(url); const data = response?.data; @@ -347,7 +348,7 @@ export async function getUserWorkspaceInfo (): Promise<{ return Promise.reject(data); } -export async function publishView (workspaceId: string, viewId: string, payload?: PublishViewPayload) { +export async function publishView(workspaceId: string, viewId: string, payload?: PublishViewPayload) { const url = `/api/workspace/${workspaceId}/page-view/${viewId}/publish`; const response = await axiosInstance?.post<{ code: number; @@ -361,7 +362,7 @@ export async function publishView (workspaceId: string, viewId: string, payload? return Promise.reject(response?.data); } -export async function unpublishView (workspaceId: string, viewId: string) { +export async function unpublishView(workspaceId: string, viewId: string) { const url = `/api/workspace/${workspaceId}/page-view/${viewId}/unpublish`; const response = await axiosInstance?.post<{ code: number; @@ -375,7 +376,7 @@ export async function unpublishView (workspaceId: string, viewId: string) { return Promise.reject(response?.data); } -export async function updatePublishNamespace (workspaceId: string, payload: UploadPublishNamespacePayload) { +export async function updatePublishNamespace(workspaceId: string, payload: UploadPublishNamespacePayload) { const url = `/api/workspace/${workspaceId}/publish-namespace`; const response = await axiosInstance?.put<{ code: number; @@ -389,7 +390,7 @@ export async function updatePublishNamespace (workspaceId: string, payload: Uplo return Promise.reject(response?.data); } -export async function getPublishViewMeta (namespace: string, publishName: string) { +export async function getPublishViewMeta(namespace: string, publishName: string) { const url = `/api/workspace/v1/published/${namespace}/${publishName}`; const response = await axiosInstance?.get<{ code: number; @@ -408,7 +409,7 @@ export async function getPublishViewMeta (namespace: string, publishName: string return response?.data.data; } -export async function getPublishViewBlob (namespace: string, publishName: string) { +export async function getPublishViewBlob(namespace: string, publishName: string) { const url = `/api/workspace/published/${namespace}/${publishName}/blob`; const response = await axiosInstance?.get(url, { responseType: 'blob', @@ -417,9 +418,15 @@ export async function getPublishViewBlob (namespace: string, publishName: string return blobToBytes(response?.data); } -export async function updateCollab (workspaceId: string, objectId: string, collabType: Types, docState: Uint8Array, context: { - version_vector: number; -}) { +export async function updateCollab( + workspaceId: string, + objectId: string, + collabType: Types, + docState: Uint8Array, + context: { + version_vector: number; + } +) { const url = `/api/workspace/v1/${workspaceId}/collab/${objectId}/web-update`; let deviceId = localStorage.getItem('x-device-id'); @@ -431,15 +438,19 @@ export async function updateCollab (workspaceId: string, objectId: string, colla const response = await axiosInstance?.post<{ code: number; message: string; - }>(url, { - doc_state: Array.from(docState), - collab_type: collabType, - }, { - headers: { - 'client-version': 'web', - 'device-id': deviceId, + }>( + url, + { + doc_state: Array.from(docState), + collab_type: collabType, }, - }); + { + headers: { + 'client-version': 'web', + 'device-id': deviceId, + }, + } + ); if (response?.data.code !== 0) { return Promise.reject(response?.data); @@ -448,7 +459,7 @@ export async function updateCollab (workspaceId: string, objectId: string, colla return context; } -export async function getCollab (workspaceId: string, objectId: string, collabType: Types) { +export async function getCollab(workspaceId: string, objectId: string, collabType: Types) { const url = `/api/workspace/v1/${workspaceId}/collab/${objectId}`; const response = await axiosInstance?.get<{ code: number; @@ -474,7 +485,7 @@ export async function getCollab (workspaceId: string, objectId: string, collabTy }; } -export async function getPageCollab (workspaceId: string, viewId: string) { +export async function getPageCollab(workspaceId: string, viewId: string) { const url = `/api/workspace/${workspaceId}/page-view/${viewId}`; const response = await axiosInstance?.get<{ code: number; @@ -485,7 +496,7 @@ export async function getPageCollab (workspaceId: string, viewId: string) { row_data: Record; owner?: User; last_editor?: User; - } + }; }; message: string; }>(url); @@ -508,7 +519,7 @@ export async function getPageCollab (workspaceId: string, viewId: string) { }; } -export async function getPublishView (publishNamespace: string, publishName: string) { +export async function getPublishView(publishNamespace: string, publishName: string) { const meta = await getPublishViewMeta(publishNamespace, publishName); const blob = await getPublishViewBlob(publishNamespace, publishName); @@ -545,7 +556,7 @@ export async function getPublishView (publishNamespace: string, publishName: str } } -export async function updatePublishConfig (workspaceId: string, payload: UpdatePublishConfigPayload) { +export async function updatePublishConfig(workspaceId: string, payload: UpdatePublishConfigPayload) { const url = `/api/workspace/${workspaceId}/publish`; const response = await axiosInstance?.patch<{ code: number; @@ -559,7 +570,7 @@ export async function updatePublishConfig (workspaceId: string, payload: UpdateP return Promise.reject(response?.data); } -export async function getPublishInfoWithViewId (viewId: string) { +export async function getPublishInfoWithViewId(viewId: string) { const url = `/api/workspace/v1/published-info/${viewId}`; const response = await axiosInstance?.get<{ code: number; @@ -584,12 +595,12 @@ export async function getPublishInfoWithViewId (viewId: string) { return Promise.reject(data); } -export async function getAppFavorites (workspaceId: string) { +export async function getAppFavorites(workspaceId: string) { const url = `/api/workspace/${workspaceId}/favorite`; const response = await axiosInstance?.get<{ code: number; data?: { - views: View[] + views: View[]; }; message: string; }>(url); @@ -603,12 +614,12 @@ export async function getAppFavorites (workspaceId: string) { return Promise.reject(data); } -export async function getAppTrash (workspaceId: string) { +export async function getAppTrash(workspaceId: string) { const url = `/api/workspace/${workspaceId}/trash`; const response = await axiosInstance?.get<{ code: number; data?: { - views: View[] + views: View[]; }; message: string; }>(url); @@ -622,12 +633,12 @@ export async function getAppTrash (workspaceId: string) { return Promise.reject(data); } -export async function getAppRecent (workspaceId: string) { +export async function getAppRecent(workspaceId: string) { const url = `/api/workspace/${workspaceId}/recent`; const response = await axiosInstance?.get<{ code: number; data?: { - views: View[] + views: View[]; }; message: string; }>(url); @@ -641,7 +652,7 @@ export async function getAppRecent (workspaceId: string) { return Promise.reject(data); } -export async function getAppOutline (workspaceId: string) { +export async function getAppOutline(workspaceId: string) { const url = `/api/workspace/${workspaceId}/folder?depth=10`; const response = await axiosInstance?.get<{ @@ -659,7 +670,7 @@ export async function getAppOutline (workspaceId: string) { return Promise.reject(data); } -export async function getView (workspaceId: string, viewId: string, depth: number = 1) { +export async function getView(workspaceId: string, viewId: string, depth: number = 1) { const url = `/api/workspace/${workspaceId}/folder?depth=${depth}&root_view_id=${viewId}`; const response = await axiosInstance?.get<{ code: number; @@ -676,7 +687,7 @@ export async function getView (workspaceId: string, viewId: string, depth: numbe return Promise.reject(data); } -export async function getPublishNamespace (workspaceId: string) { +export async function getPublishNamespace(workspaceId: string) { const url = `/api/workspace/${workspaceId}/publish-namespace`; const response = await axiosInstance?.get<{ code: number; @@ -693,7 +704,7 @@ export async function getPublishNamespace (workspaceId: string) { return Promise.reject(data); } -export async function getPublishHomepage (workspaceId: string) { +export async function getPublishHomepage(workspaceId: string) { const url = `/api/workspace/${workspaceId}/publish-default`; const response = await axiosInstance?.get<{ code: number; @@ -715,7 +726,7 @@ export async function getPublishHomepage (workspaceId: string) { return Promise.reject(data); } -export async function updatePublishHomepage (workspaceId: string, viewId: string) { +export async function updatePublishHomepage(workspaceId: string, viewId: string) { const url = `/api/workspace/${workspaceId}/publish-default`; const response = await axiosInstance?.put<{ code: number; @@ -731,7 +742,7 @@ export async function updatePublishHomepage (workspaceId: string, viewId: string return Promise.reject(response?.data); } -export async function removePublishHomepage (workspaceId: string) { +export async function removePublishHomepage(workspaceId: string) { const url = `/api/workspace/${workspaceId}/publish-default`; const response = await axiosInstance?.delete<{ code: number; @@ -745,7 +756,7 @@ export async function removePublishHomepage (workspaceId: string) { return Promise.reject(response?.data); } -export async function getPublishOutline (publishNamespace: string) { +export async function getPublishOutline(publishNamespace: string) { const url = `/api/workspace/published-outline/${publishNamespace}`; const response = await axiosInstance?.get<{ code: number; @@ -762,7 +773,7 @@ export async function getPublishOutline (publishNamespace: string) { return Promise.reject(data); } -export async function getPublishViewComments (viewId: string): Promise { +export async function getPublishViewComments(viewId: string): Promise { const url = `/api/workspace/published-info/${viewId}/comment`; const response = await axiosInstance?.get<{ code: number; @@ -811,7 +822,7 @@ export async function getPublishViewComments (viewId: string): Promise> { +export async function getReactions(viewId: string, commentId?: string): Promise> { let url = `/api/workspace/published-info/${viewId}/reaction`; if (commentId) { @@ -862,7 +873,7 @@ export async function getReactions (viewId: string, commentId?: string): Promise return Promise.reject(data); } -export async function createGlobalCommentOnPublishView (viewId: string, content: string, replyCommentId?: string) { +export async function createGlobalCommentOnPublishView(viewId: string, content: string, replyCommentId?: string) { const url = `/api/workspace/published-info/${viewId}/comment`; const response = await axiosInstance?.post<{ code: number; message: string }>(url, { content, @@ -876,7 +887,7 @@ export async function createGlobalCommentOnPublishView (viewId: string, content: return Promise.reject(response?.data.message); } -export async function deleteGlobalCommentOnPublishView (viewId: string, commentId: string) { +export async function deleteGlobalCommentOnPublishView(viewId: string, commentId: string) { const url = `/api/workspace/published-info/${viewId}/comment`; const response = await axiosInstance?.delete<{ code: number; message: string }>(url, { data: { @@ -891,7 +902,7 @@ export async function deleteGlobalCommentOnPublishView (viewId: string, commentI return Promise.reject(response?.data.message); } -export async function addReaction (viewId: string, commentId: string, reactionType: string) { +export async function addReaction(viewId: string, commentId: string, reactionType: string) { const url = `/api/workspace/published-info/${viewId}/reaction`; const response = await axiosInstance?.post<{ code: number; message: string }>(url, { comment_id: commentId, @@ -905,7 +916,7 @@ export async function addReaction (viewId: string, commentId: string, reactionTy return Promise.reject(response?.data.message); } -export async function removeReaction (viewId: string, commentId: string, reactionType: string) { +export async function removeReaction(viewId: string, commentId: string, reactionType: string) { const url = `/api/workspace/published-info/${viewId}/reaction`; const response = await axiosInstance?.delete<{ code: number; message: string }>(url, { data: { @@ -921,7 +932,7 @@ export async function removeReaction (viewId: string, commentId: string, reactio return Promise.reject(response?.data.message); } -export async function getWorkspaces (): Promise { +export async function getWorkspaces(): Promise { const query = new URLSearchParams({ include_member_count: 'true', }); @@ -959,7 +970,7 @@ export interface WorkspaceFolder { children: WorkspaceFolder[]; } -function iterateFolder (folder: WorkspaceFolder): FolderView { +function iterateFolder(folder: WorkspaceFolder): FolderView { return { id: folder.view_id, name: folder.name, @@ -973,7 +984,7 @@ function iterateFolder (folder: WorkspaceFolder): FolderView { }; } -export async function getWorkspaceFolder (workspaceId: string): Promise { +export async function getWorkspaceFolder(workspaceId: string): Promise { const url = `/api/workspace/${workspaceId}/folder`; const response = await axiosInstance?.get<{ code: number; @@ -996,7 +1007,7 @@ export interface DuplicatePublishViewPayload { dest_view_id: string; } -export async function duplicatePublishView (workspaceId: string, payload: DuplicatePublishViewPayload) { +export async function duplicatePublishView(workspaceId: string, payload: DuplicatePublishViewPayload) { const url = `/api/workspace/${workspaceId}/published-duplicate`; const res = await axiosInstance?.post<{ @@ -1014,7 +1025,7 @@ export async function duplicatePublishView (workspaceId: string, payload: Duplic return Promise.reject(res?.data.message); } -export async function createTemplate (template: UploadTemplatePayload) { +export async function createTemplate(template: UploadTemplatePayload) { const url = '/api/template-center/template'; const response = await axiosInstance?.post<{ code: number; @@ -1028,7 +1039,7 @@ export async function createTemplate (template: UploadTemplatePayload) { return Promise.reject(response?.data.message); } -export async function updateTemplate (viewId: string, template: UploadTemplatePayload) { +export async function updateTemplate(viewId: string, template: UploadTemplatePayload) { const url = `/api/template-center/template/${viewId}`; const response = await axiosInstance?.put<{ code: number; @@ -1042,13 +1053,7 @@ export async function updateTemplate (viewId: string, template: UploadTemplatePa return Promise.reject(response?.data.message); } -export async function getTemplates ({ - categoryId, - nameContains, -}: { - categoryId?: string; - nameContains?: string; -}) { +export async function getTemplates({ categoryId, nameContains }: { categoryId?: string; nameContains?: string }) { const url = `/api/template-center/template`; const response = await axiosInstance?.get<{ @@ -1073,7 +1078,7 @@ export async function getTemplates ({ return Promise.reject(data); } -export async function getTemplateById (viewId: string) { +export async function getTemplateById(viewId: string) { const url = `/api/template-center/template/${viewId}`; const response = await axiosInstance?.get<{ code: number; @@ -1090,7 +1095,7 @@ export async function getTemplateById (viewId: string) { return Promise.reject(data); } -export async function deleteTemplate (viewId: string) { +export async function deleteTemplate(viewId: string) { const url = `/api/template-center/template/${viewId}`; const response = await axiosInstance?.delete<{ code: number; @@ -1104,13 +1109,12 @@ export async function deleteTemplate (viewId: string) { return Promise.reject(response?.data.message); } -export async function getTemplateCategories () { +export async function getTemplateCategories() { const url = '/api/template-center/category'; const response = await axiosInstance?.get<{ code: number; data?: { - categories: TemplateCategory[] - + categories: TemplateCategory[]; }; message: string; }>(url); @@ -1124,7 +1128,7 @@ export async function getTemplateCategories () { return Promise.reject(data); } -export async function addTemplateCategory (category: TemplateCategoryFormValues) { +export async function addTemplateCategory(category: TemplateCategoryFormValues) { const url = '/api/template-center/category'; const response = await axiosInstance?.post<{ code: number; @@ -1138,7 +1142,7 @@ export async function addTemplateCategory (category: TemplateCategoryFormValues) return Promise.reject(response?.data.message); } -export async function updateTemplateCategory (id: string, category: TemplateCategoryFormValues) { +export async function updateTemplateCategory(id: string, category: TemplateCategoryFormValues) { const url = `/api/template-center/category/${id}`; const response = await axiosInstance?.put<{ code: number; @@ -1152,7 +1156,7 @@ export async function updateTemplateCategory (id: string, category: TemplateCate return Promise.reject(response?.data.message); } -export async function deleteTemplateCategory (categoryId: string) { +export async function deleteTemplateCategory(categoryId: string) { const url = `/api/template-center/category/${categoryId}`; const response = await axiosInstance?.delete<{ code: number; @@ -1166,7 +1170,7 @@ export async function deleteTemplateCategory (categoryId: string) { return Promise.reject(response?.data.message); } -export async function getTemplateCreators () { +export async function getTemplateCreators() { const url = '/api/template-center/creator'; const response = await axiosInstance?.get<{ code: number; @@ -1185,7 +1189,7 @@ export async function getTemplateCreators () { return Promise.reject(data); } -export async function createTemplateCreator (creator: TemplateCreatorFormValues) { +export async function createTemplateCreator(creator: TemplateCreatorFormValues) { const url = '/api/template-center/creator'; const response = await axiosInstance?.post<{ code: number; @@ -1199,7 +1203,7 @@ export async function createTemplateCreator (creator: TemplateCreatorFormValues) return Promise.reject(response?.data.message); } -export async function updateTemplateCreator (creatorId: string, creator: TemplateCreatorFormValues) { +export async function updateTemplateCreator(creatorId: string, creator: TemplateCreatorFormValues) { const url = `/api/template-center/creator/${creatorId}`; const response = await axiosInstance?.put<{ code: number; @@ -1213,7 +1217,7 @@ export async function updateTemplateCreator (creatorId: string, creator: Templat return Promise.reject(response?.data.message); } -export async function deleteTemplateCreator (creatorId: string) { +export async function deleteTemplateCreator(creatorId: string) { const url = `/api/template-center/creator/${creatorId}`; const response = await axiosInstance?.delete<{ code: number; @@ -1227,7 +1231,7 @@ export async function deleteTemplateCreator (creatorId: string) { return Promise.reject(response?.data.message); } -export async function uploadTemplateAvatar (file: File) { +export async function uploadTemplateAvatar(file: File) { const url = '/api/template-center/avatar'; const formData = new FormData(); @@ -1257,7 +1261,7 @@ export async function uploadTemplateAvatar (file: File) { return Promise.reject(data); } -export async function getInvitation (invitationId: string) { +export async function getInvitation(invitationId: string) { const url = `/api/workspace/invite/${invitationId}`; const response = await axiosInstance?.get<{ code: number; @@ -1274,7 +1278,7 @@ export async function getInvitation (invitationId: string) { return Promise.reject(data); } -export async function acceptInvitation (invitationId: string) { +export async function acceptInvitation(invitationId: string) { const url = `/api/workspace/accept-invite/${invitationId}`; const response = await axiosInstance?.post<{ code: number; @@ -1288,7 +1292,7 @@ export async function acceptInvitation (invitationId: string) { return Promise.reject(response?.data.message); } -export async function getRequestAccessInfo (requestId: string): Promise { +export async function getRequestAccessInfo(requestId: string): Promise { const url = `/api/access-request/${requestId}`; const response = await axiosInstance?.get<{ code: number; @@ -1318,7 +1322,7 @@ export async function getRequestAccessInfo (requestId: string): Promise plans?.includes(subscription.plan)); - } catch (e) { return Promise.reject(e); } } -export async function getActiveSubscription (workspaceId: string) { +export async function getActiveSubscription(workspaceId: string) { const url = `/billing/api/v1/active-subscription/${workspaceId}`; const response = await axiosInstance?.get<{ @@ -1419,7 +1421,7 @@ export async function getActiveSubscription (workspaceId: string) { return Promise.reject(response?.data); } -export async function createImportTask (file: File) { +export async function createImportTask(file: File) { const url = `/api/import/create`; const fileName = file.name.split('.').slice(0, -1).join('.') || crypto.randomUUID(); @@ -1445,7 +1447,7 @@ export async function createImportTask (file: File) { return Promise.reject(res?.data); } -export async function uploadImportFile (presignedUrl: string, file: File, onProgress: (progress: number) => void) { +export async function uploadImportFile(presignedUrl: string, file: File, onProgress: (progress: number) => void) { const response = await axios.put(presignedUrl, file, { onUploadProgress: (progressEvent) => { const { progress = 0 } = progressEvent; @@ -1468,7 +1470,7 @@ export async function uploadImportFile (presignedUrl: string, file: File, onProg }); } -export async function createFolderView (workspaceId: string, payload: CreateFolderViewPayload) { +export async function createFolderView(workspaceId: string, payload: CreateFolderViewPayload) { const url = `/api/workspace/${workspaceId}/folder-view`; const res = await axiosInstance?.post<{ @@ -1492,10 +1494,7 @@ export async function createFolderView (workspaceId: string, payload: CreateFold return Promise.reject(res?.data); } -export async function addAppPage (workspaceId: string, parentViewId: string, { - layout, - name, -}: CreatePagePayload) { +export async function addAppPage(workspaceId: string, parentViewId: string, { layout, name }: CreatePagePayload) { const url = `/api/workspace/${workspaceId}/page-view`; const response = await axiosInstance?.post<{ code: number; @@ -1516,7 +1515,7 @@ export async function addAppPage (workspaceId: string, parentViewId: string, { return Promise.reject(response?.data); } -export async function updatePage (workspaceId: string, viewId: string, data: UpdatePagePayload) { +export async function updatePage(workspaceId: string, viewId: string, data: UpdatePagePayload) { const url = `/api/workspace/${workspaceId}/page-view/${viewId}`; const res = await axiosInstance?.patch<{ @@ -1531,7 +1530,7 @@ export async function updatePage (workspaceId: string, viewId: string, data: Upd return Promise.reject(res?.data); } -export async function deleteTrash (workspaceId: string, viewId?: string) { +export async function deleteTrash(workspaceId: string, viewId?: string) { if (viewId) { const url = `/api/workspace/${workspaceId}/trash/${viewId}`; const response = await axiosInstance?.delete<{ @@ -1557,10 +1556,9 @@ export async function deleteTrash (workspaceId: string, viewId?: string) { return Promise.reject(response?.data); } - } -export async function moveToTrash (workspaceId: string, viewId: string) { +export async function moveToTrash(workspaceId: string, viewId: string) { const url = `/api/workspace/${workspaceId}/page-view/${viewId}/move-to-trash`; const response = await axiosInstance?.post<{ code: number; @@ -1574,7 +1572,7 @@ export async function moveToTrash (workspaceId: string, viewId: string) { return Promise.reject(response?.data); } -export async function movePageTo (workspaceId: string, viewId: string, parentViewId: string, prevViewId?: string) { +export async function movePageTo(workspaceId: string, viewId: string, parentViewId: string, prevViewId?: string) { const url = `/api/workspace/${workspaceId}/page-view/${viewId}/move`; const response = await axiosInstance?.post<{ code: number; @@ -1591,8 +1589,10 @@ export async function movePageTo (workspaceId: string, viewId: string, parentVie return Promise.reject(response?.data); } -export async function restorePage (workspaceId: string, viewId?: string) { - const url = viewId ? `/api/workspace/${workspaceId}/page-view/${viewId}/restore-from-trash` : `/api/workspace/${workspaceId}/restore-all-pages-from-trash`; +export async function restorePage(workspaceId: string, viewId?: string) { + const url = viewId + ? `/api/workspace/${workspaceId}/page-view/${viewId}/restore-from-trash` + : `/api/workspace/${workspaceId}/restore-all-pages-from-trash`; const response = await axiosInstance?.post<{ code: number; message: string; @@ -1605,7 +1605,7 @@ export async function restorePage (workspaceId: string, viewId?: string) { return Promise.reject(response?.data); } -export async function createSpace (workspaceId: string, payload: CreateSpacePayload) { +export async function createSpace(workspaceId: string, payload: CreateSpacePayload) { const url = `/api/workspace/${workspaceId}/space`; const response = await axiosInstance?.post<{ code: number; @@ -1622,7 +1622,7 @@ export async function createSpace (workspaceId: string, payload: CreateSpacePayl return Promise.reject(response?.data); } -export async function updateSpace (workspaceId: string, payload: UpdateSpacePayload) { +export async function updateSpace(workspaceId: string, payload: UpdateSpacePayload) { const url = `/api/workspace/${workspaceId}/space/${payload.view_id}`; const data = omit(payload, ['view_id']); const response = await axiosInstance?.patch<{ @@ -1637,7 +1637,12 @@ export async function updateSpace (workspaceId: string, payload: UpdateSpacePayl return Promise.reject(response?.data); } -export async function uploadFile (workspaceId: string, viewId: string, file: File, onProgress?: (progress: number) => void) { +export async function uploadFile( + workspaceId: string, + viewId: string, + file: File, + onProgress?: (progress: number) => void +) { const url = `/api/file_storage/${workspaceId}/v1/blob/${viewId}`; // Check file size, if over 7MB, check subscription plan @@ -1660,7 +1665,7 @@ export async function uploadFile (workspaceId: string, viewId: string, file: Fil message: string; data: { file_id: string; - } + }; }>(url, file, { onUploadProgress: (progressEvent) => { const { progress = 0 } = progressEvent; @@ -1682,7 +1687,6 @@ export async function uploadFile (workspaceId: string, viewId: string, file: Fil return Promise.reject(response?.data); // eslint-disable-next-line } catch (e: any) { - if (e.response.status === 413) { return Promise.reject({ code: 413, @@ -1695,13 +1699,12 @@ export async function uploadFile (workspaceId: string, viewId: string, file: Fil code: -1, message: 'Upload file failed.', }); - } -export async function inviteMembers (workspaceId: string, emails: string[]) { +export async function inviteMembers(workspaceId: string, emails: string[]) { const url = `/api/workspace/${workspaceId}/invite`; - const payload = emails.map(e => ({ + const payload = emails.map((e) => ({ email: e, role: Role.Member, })); @@ -1718,7 +1721,7 @@ export async function inviteMembers (workspaceId: string, emails: string[]) { return Promise.reject(res?.data); } -export async function getMembers (workspaceId: string) { +export async function getMembers(workspaceId: string) { const url = `/api/workspace/${workspaceId}/member`; const res = await axiosInstance?.get<{ code: number; @@ -1733,7 +1736,7 @@ export async function getMembers (workspaceId: string) { return Promise.reject(res?.data); } -export async function leaveWorkspace (workspaceId: string) { +export async function leaveWorkspace(workspaceId: string) { const url = `/api/workspace/${workspaceId}/leave`; const res = await axiosInstance?.post<{ code: number; @@ -1747,7 +1750,7 @@ export async function leaveWorkspace (workspaceId: string) { return Promise.reject(res?.data); } -export async function deleteWorkspace (workspaceId: string) { +export async function deleteWorkspace(workspaceId: string) { const url = `/api/workspace/${workspaceId}`; const res = await axiosInstance?.delete<{ code: number; @@ -1761,11 +1764,14 @@ export async function deleteWorkspace (workspaceId: string) { return Promise.reject(res?.data); } -export async function getQuickNoteList (workspaceId: string, params: { - offset?: number; - limit?: number; - searchTerm?: string; -}) { +export async function getQuickNoteList( + workspaceId: string, + params: { + offset?: number; + limit?: number; + searchTerm?: string; + } +) { const url = `/api/workspace/${workspaceId}/quick-note`; const res = await axiosInstance?.get<{ code: number; @@ -1792,7 +1798,7 @@ export async function getQuickNoteList (workspaceId: string, params: { return Promise.reject(res?.data); } -export async function createQuickNote (workspaceId: string, payload: QuickNoteEditorData[]): Promise { +export async function createQuickNote(workspaceId: string, payload: QuickNoteEditorData[]): Promise { const url = `/api/workspace/${workspaceId}/quick-note`; const res = await axiosInstance?.post<{ code: number; @@ -1809,7 +1815,7 @@ export async function createQuickNote (workspaceId: string, payload: QuickNoteEd return Promise.reject(res?.data); } -export async function updateQuickNote (workspaceId: string, noteId: string, payload: QuickNoteEditorData[]) { +export async function updateQuickNote(workspaceId: string, noteId: string, payload: QuickNoteEditorData[]) { const url = `/api/workspace/${workspaceId}/quick-note/${noteId}`; const res = await axiosInstance?.put<{ code: number; @@ -1825,7 +1831,7 @@ export async function updateQuickNote (workspaceId: string, noteId: string, payl return Promise.reject(res?.data); } -export async function deleteQuickNote (workspaceId: string, noteId: string) { +export async function deleteQuickNote(workspaceId: string, noteId: string) { const url = `/api/workspace/${workspaceId}/quick-note/${noteId}`; const res = await axiosInstance?.delete<{ code: number; @@ -1839,7 +1845,7 @@ export async function deleteQuickNote (workspaceId: string, noteId: string) { return Promise.reject(res?.data); } -export async function cancelSubscription (workspaceId: string, plan: SubscriptionPlan, reason?: string) { +export async function cancelSubscription(workspaceId: string, plan: SubscriptionPlan, reason?: string) { const url = `/billing/api/v1/cancel-subscription`; const res = await axiosInstance?.post<{ code: number; @@ -1858,12 +1864,12 @@ export async function cancelSubscription (workspaceId: string, plan: Subscriptio return Promise.reject(res?.data); } -export async function searchWorkspace (workspaceId: string, query: string) { +export async function searchWorkspace(workspaceId: string, query: string) { const url = `/api/search/${workspaceId}`; const res = await axiosInstance?.get<{ code: number; data: { - object_id: string + object_id: string; }[]; message: string; }>(url, { @@ -1873,13 +1879,13 @@ export async function searchWorkspace (workspaceId: string, query: string) { }); if (res?.data.code === 0) { - return res?.data.data.map(item => item.object_id); + return res?.data.data.map((item) => item.object_id); } return Promise.reject(res?.data); } -export async function getChatMessages (workspaceId: string, chatId: string, limit?: number | undefined) { +export async function getChatMessages(workspaceId: string, chatId: string, limit?: number | undefined) { const url = `/api/chat/${workspaceId}/${chatId}/message`; const response = await axiosInstance?.get<{ @@ -1899,7 +1905,7 @@ export async function getChatMessages (workspaceId: string, chatId: string, limi return Promise.reject(data); } -export async function duplicatePage (workspaceId: string, viewId: string) { +export async function duplicatePage(workspaceId: string, viewId: string) { const url = `/api/workspace/${workspaceId}/page-view/${viewId}/duplicate`; const response = await axiosInstance?.post<{ code: number; @@ -1913,16 +1919,14 @@ export async function duplicatePage (workspaceId: string, viewId: string) { return Promise.reject(response?.data); } -export async function joinWorkspaceByInvitationCode ( - code: string, -) { +export async function joinWorkspaceByInvitationCode(code: string) { const url = `/api/workspace/join-by-invite-code`; const response = await axiosInstance?.post<{ code: number; message: string; data: { - workspace_id: string - } + workspace_id: string; + }; }>(url, { code, }); @@ -1934,7 +1938,7 @@ export async function joinWorkspaceByInvitationCode ( return Promise.reject(response?.data); } -export async function getWorkspaceInfoByInvitationCode (code: string) { +export async function getWorkspaceInfoByInvitationCode(code: string) { const url = `/api/invite-code-info`; const response = await axiosInstance?.get<{ @@ -1948,7 +1952,7 @@ export async function getWorkspaceInfoByInvitationCode (code: string) { owner_avatar: string; is_member: boolean; member_count: number; - } + }; }>(url, { params: { code, @@ -1964,14 +1968,14 @@ export async function getWorkspaceInfoByInvitationCode (code: string) { return Promise.reject(data); } -export async function generateAISummaryForRow (workspaceId: string, payload: GenerateAISummaryRowPayload) { +export async function generateAISummaryForRow(workspaceId: string, payload: GenerateAISummaryRowPayload) { const url = `/api/ai/${workspaceId}/summarize_row`; const response = await axiosInstance?.post<{ code: number; message: string; data: { text: string; - } + }; }>(url, { workspace_id: workspaceId, data: payload, @@ -1984,7 +1988,7 @@ export async function generateAISummaryForRow (workspaceId: string, payload: Gen return Promise.reject(response?.data); } -export async function generateAITranslateForRow (workspaceId: string, payload: GenerateAITranslateRowPayload) { +export async function generateAITranslateForRow(workspaceId: string, payload: GenerateAITranslateRowPayload) { const url = `/api/ai/${workspaceId}/translate_row`; const response = await axiosInstance?.post<{ code: number; @@ -1992,26 +1996,30 @@ export async function generateAITranslateForRow (workspaceId: string, payload: G data: { items: { [key: string]: string; - }[] - } + }[]; + }; }>(url, { workspace_id: workspaceId, data: payload, }); if (response?.data.code === 0) { - return response?.data.data.items.map((item) => { - return Object.entries(item).map(([key, value]) => { - if (!value) return ''; - return `${key}: ${value}`; - }).join(', '); - }).join('\n'); + return response?.data.data.items + .map((item) => { + return Object.entries(item) + .map(([key, value]) => { + if (!value) return ''; + return `${key}: ${value}`; + }) + .join(', '); + }) + .join('\n'); } return Promise.reject(response?.data); } -export async function createOrphanedView (workspaceId: string, payload: { document_id: string }) { +export async function createOrphanedView(workspaceId: string, payload: { document_id: string }) { const url = `/api/workspace/${workspaceId}/orphaned-view`; const response = await axiosInstance?.post<{ code: number; @@ -2025,7 +2033,7 @@ export async function createOrphanedView (workspaceId: string, payload: { docume return Promise.reject(response?.data); } -export async function getGuestInvitation (workspaceId: string, code: string) { +export async function getGuestInvitation(workspaceId: string, code: string) { const url = `/api/sharing/workspace/${workspaceId}/guest-invite-code-info`; const response = await axiosInstance?.get<{ code: number; @@ -2039,13 +2047,12 @@ export async function getGuestInvitation (workspaceId: string, code: string) { if (response?.data.code === 0) { return response?.data.data; - } return Promise.reject(response?.data); } -export async function acceptGuestInvitation (workspaceId: string, code: string) { +export async function acceptGuestInvitation(workspaceId: string, code: string) { const url = `/api/sharing/workspace/${workspaceId}/join-by-guest-invite-code`; const response = await axiosInstance?.post<{ code: number; @@ -2061,13 +2068,13 @@ export async function acceptGuestInvitation (workspaceId: string, code: string) return Promise.reject(response?.data); } -export async function approveTurnGuestToMember (workspaceId: string, code: string) { +export async function approveTurnGuestToMember(workspaceId: string, code: string) { const url = `/api/sharing/workspace/${workspaceId}/approve-guest-conversion`; const response = await axiosInstance?.post<{ code: number; message: string; }>(url, { - code + code, }); if (response?.data.code === 0) { @@ -2077,7 +2084,7 @@ export async function approveTurnGuestToMember (workspaceId: string, code: strin return Promise.reject(response?.data); } -export async function getGuestToMemberConversionInfo (workspaceId: string, code: string) { +export async function getGuestToMemberConversionInfo(workspaceId: string, code: string) { const url = `/api/sharing/workspace/${workspaceId}/guest-conversion-code-info`; const response = await axiosInstance?.get<{ code: number; @@ -2095,3 +2102,36 @@ export async function getGuestToMemberConversionInfo (workspaceId: string, code: return Promise.reject(response?.data); } + +export async function getMentionableUsers(workspaceId: string) { + const url = `/api/workspace/${workspaceId}/mentionable-person`; + const response = await axiosInstance?.get<{ + code: number; + message: string; + data: { + persons: MentionablePerson[]; + }; + }>(url); + + if (response?.data.code === 0) { + return response?.data.data.persons; + } + + return Promise.reject(response?.data); +} + +export async function addRecentPages(workspaceId: string, viewIds: string[]) { + const url = `/api/workspace/${workspaceId}/add-recent-pages`; + const response = await axiosInstance?.post<{ + code: number; + message: string; + }>(url, { + recent_view_ids: viewIds, + }); + + if (response?.data.code === 0) { + return; + } + + return Promise.reject(response?.data); +} diff --git a/src/application/services/js-services/index.ts b/src/application/services/js-services/index.ts index f7a74df8..1e6405c5 100644 --- a/src/application/services/js-services/index.ts +++ b/src/application/services/js-services/index.ts @@ -714,4 +714,12 @@ export class AFClientService implements AFService { async approveTurnGuestToMember(workspaceId: string, code: string) { return APIService.approveTurnGuestToMember(workspaceId, code); } + + async getMentionableUsers(workspaceId: string) { + return APIService.getMentionableUsers(workspaceId); + } + + async addRecentPages(workspaceId: string, viewIds: string[]) { + return APIService.addRecentPages(workspaceId, viewIds); + } } diff --git a/src/application/services/services.type.ts b/src/application/services/services.type.ts index e1d3c661..b8178ac7 100644 --- a/src/application/services/services.type.ts +++ b/src/application/services/services.type.ts @@ -31,6 +31,7 @@ import { GenerateAITranslateRowPayload, GuestInvitation, GuestConversionCodeInfo, + MentionablePerson, } from '@/application/types'; import { GlobalComment, Reaction } from '@/application/comment.type'; import { ViewMeta } from '@/application/db/tables/view_metas'; @@ -79,6 +80,8 @@ export interface WorkspaceService { acceptGuestInvitation: (workspaceId: string, code: string) => Promise; getGuestToMemberConversionInfo: (workspaceId: string, code: string) => Promise; approveTurnGuestToMember: (workspaceId: string, code: string) => Promise; + getMentionableUsers: (workspaceId: string) => Promise; + addRecentPages: (workspaceId: string, viewIds: string[]) => Promise; } export interface AppService { diff --git a/src/application/types.ts b/src/application/types.ts index d17dcbe1..93ea702c 100644 --- a/src/application/types.ts +++ b/src/application/types.ts @@ -90,7 +90,7 @@ export interface CodeBlockData extends BlockData { export interface CalloutBlockData extends BlockData { icon: string; - icon_type?: 'emoji' | 'icon', + icon_type?: 'emoji' | 'icon'; } export interface MathEquationBlockData extends BlockData { @@ -150,8 +150,8 @@ export enum GalleryLayout { export interface GalleryBlockData extends BlockData { images: { - type: ImageType, - url: string, + type: ImageType; + url: string; }[]; layout: GalleryLayout; } @@ -211,6 +211,7 @@ export enum MentionType { Date = 'date', childPage = 'childPage', externalLink = 'externalLink', + Person = 'person', } export interface Mention { @@ -225,6 +226,10 @@ export interface Mention { // external link url?: string; type: MentionType; + + // mention person + person_id?: string; + person_name?: string; } export interface FolderMeta { @@ -362,154 +367,153 @@ export enum YjsDatabaseKey { export interface YDoc extends Y.Doc { // eslint-disable-next-line @typescript-eslint/no-explicit-any - getMap (key: YjsEditorKey.data_section): YSharedRoot | any; + getMap(key: YjsEditorKey.data_section): YSharedRoot | any; } export interface YDatabaseRow extends Y.Map { - get (key: YjsDatabaseKey.id): RowId; + get(key: YjsDatabaseKey.id): RowId; - get (key: YjsDatabaseKey.database_id | YjsDatabaseKey.height): string; + get(key: YjsDatabaseKey.database_id | YjsDatabaseKey.height): string; - get (key: YjsDatabaseKey.visibility): boolean; + get(key: YjsDatabaseKey.visibility): boolean; - get (key: YjsDatabaseKey.cells): YDatabaseCells; + get(key: YjsDatabaseKey.cells): YDatabaseCells; - get (key: YjsDatabaseKey.created_at): CreatedAt; - - get (key: YjsDatabaseKey.last_modified): LastModified; + get(key: YjsDatabaseKey.created_at): CreatedAt; + get(key: YjsDatabaseKey.last_modified): LastModified; } export interface YDatabaseCells extends Y.Map { - get (key: FieldId): YDatabaseCell; + get(key: FieldId): YDatabaseCell; } export type EndTimestamp = string; export type ReminderId = string; export interface YDatabaseCell extends Y.Map { - get (key: YjsDatabaseKey.created_at): CreatedAt; + get(key: YjsDatabaseKey.created_at): CreatedAt; - get (key: YjsDatabaseKey.last_modified): LastModified; + get(key: YjsDatabaseKey.last_modified): LastModified; - get (key: YjsDatabaseKey.field_type): string; + get(key: YjsDatabaseKey.field_type): string; - get (key: YjsDatabaseKey.data): string | boolean | number | null | Y.Array | object; + get(key: YjsDatabaseKey.data): string | boolean | number | null | Y.Array | object; - get (key: YjsDatabaseKey.end_timestamp): EndTimestamp; + get(key: YjsDatabaseKey.end_timestamp): EndTimestamp; - get (key: YjsDatabaseKey.include_time): boolean; + get(key: YjsDatabaseKey.include_time): boolean; // eslint-disable-next-line @typescript-eslint/unified-signatures - get (key: YjsDatabaseKey.is_range): boolean; + get(key: YjsDatabaseKey.is_range): boolean; - get (key: YjsDatabaseKey.reminder_id): ReminderId; + get(key: YjsDatabaseKey.reminder_id): ReminderId; } export interface YSharedRoot extends Y.Map { - get (key: YjsEditorKey.document): YDocument; + get(key: YjsEditorKey.document): YDocument; - get (key: YjsEditorKey.folder): YFolder; + get(key: YjsEditorKey.folder): YFolder; - get (key: YjsEditorKey.database): YDatabase; + get(key: YjsEditorKey.database): YDatabase; - get (key: YjsEditorKey.database_row): YDatabaseRow; + get(key: YjsEditorKey.database_row): YDatabaseRow; - get (key: YjsEditorKey.meta): Y.Map; + get(key: YjsEditorKey.meta): Y.Map; } export interface YFolder extends Y.Map { - get (key: YjsFolderKey.views): YViews; + get(key: YjsFolderKey.views): YViews; // eslint-disable-next-line @typescript-eslint/unified-signatures - get (key: YjsFolderKey.meta): YFolderMeta; + get(key: YjsFolderKey.meta): YFolderMeta; // eslint-disable-next-line @typescript-eslint/unified-signatures - get (key: YjsFolderKey.relation): YFolderRelation; + get(key: YjsFolderKey.relation): YFolderRelation; // eslint-disable-next-line @typescript-eslint/unified-signatures - get (key: YjsFolderKey.section): YFolderSection; + get(key: YjsFolderKey.section): YFolderSection; } export interface YViews extends Y.Map { - get (key: ViewId): YView; + get(key: ViewId): YView; } export interface YView extends Y.Map { - get (key: YjsFolderKey.id): ViewId; + get(key: YjsFolderKey.id): ViewId; - get (key: YjsFolderKey.bid): string; + get(key: YjsFolderKey.bid): string; // eslint-disable-next-line @typescript-eslint/unified-signatures - get (key: YjsFolderKey.name): string; + get(key: YjsFolderKey.name): string; // eslint-disable-next-line @typescript-eslint/unified-signatures - get (key: YjsFolderKey.icon | YjsFolderKey.extra): string; + get(key: YjsFolderKey.icon | YjsFolderKey.extra): string; // eslint-disable-next-line @typescript-eslint/unified-signatures - get (key: YjsFolderKey.layout): string; + get(key: YjsFolderKey.layout): string; } export interface YFolderRelation extends Y.Map { - get (key: ViewId): Y.Array; + get(key: ViewId): Y.Array; } export interface YFolderMeta extends Y.Map { - get (key: YjsFolderKey.current_view | YjsFolderKey.current_workspace): string; + get(key: YjsFolderKey.current_view | YjsFolderKey.current_workspace): string; } export interface YFolderSection extends Y.Map { - get (key: YjsFolderKey.favorite | YjsFolderKey.private | YjsFolderKey.recent | YjsFolderKey.trash): YFolderSectionItem; + get(key: YjsFolderKey.favorite | YjsFolderKey.private | YjsFolderKey.recent | YjsFolderKey.trash): YFolderSectionItem; } export interface YFolderSectionItem extends Y.Map { - get (key: string): Y.Array; + get(key: string): Y.Array; } export interface YDocument extends Y.Map { - get (key: YjsEditorKey.blocks | YjsEditorKey.page_id | YjsEditorKey.meta): YBlocks | YMeta | string; + get(key: YjsEditorKey.blocks | YjsEditorKey.page_id | YjsEditorKey.meta): YBlocks | YMeta | string; } export interface YBlocks extends Y.Map { - get (key: BlockId): YBlock; + get(key: BlockId): YBlock; } export interface YBlock extends Y.Map { - get (key: YjsEditorKey.block_id | YjsEditorKey.block_parent): BlockId; + get(key: YjsEditorKey.block_id | YjsEditorKey.block_parent): BlockId; - get (key: YjsEditorKey.block_type): BlockType; + get(key: YjsEditorKey.block_type): BlockType; - get (key: YjsEditorKey.block_data): string; + get(key: YjsEditorKey.block_data): string; - get (key: YjsEditorKey.block_children): ChildrenId; + get(key: YjsEditorKey.block_children): ChildrenId; - get (key: YjsEditorKey.block_external_id): ExternalId; + get(key: YjsEditorKey.block_external_id): ExternalId; } export interface YMeta extends Y.Map { - get (key: YjsEditorKey.children_map | YjsEditorKey.text_map): YChildrenMap | YTextMap; + get(key: YjsEditorKey.children_map | YjsEditorKey.text_map): YChildrenMap | YTextMap; } export interface YChildrenMap extends Y.Map { - get (key: ChildrenId): Y.Array; + get(key: ChildrenId): Y.Array; } export interface YTextMap extends Y.Map { - get (key: ExternalId): Y.Text; + get(key: ExternalId): Y.Text; } export interface YDatabase extends Y.Map { - get (key: YjsDatabaseKey.views): YDatabaseViews; + get(key: YjsDatabaseKey.views): YDatabaseViews; - get (key: YjsDatabaseKey.metas): YDatabaseMetas; + get(key: YjsDatabaseKey.metas): YDatabaseMetas; - get (key: YjsDatabaseKey.fields): YDatabaseFields; + get(key: YjsDatabaseKey.fields): YDatabaseFields; - get (key: YjsDatabaseKey.id): string; + get(key: YjsDatabaseKey.id): string; } export interface YDatabaseViews extends Y.Map { - get (key: ViewId): YDatabaseView; + get(key: ViewId): YDatabaseView; } export type DatabaseId = string; @@ -525,39 +529,39 @@ export enum DatabaseViewLayout { } export interface YDatabaseView extends Y.Map { - get (key: YjsDatabaseKey.database_id): DatabaseId; + get(key: YjsDatabaseKey.database_id): DatabaseId; - get (key: YjsDatabaseKey.name): string; + get(key: YjsDatabaseKey.name): string; - get (key: YjsDatabaseKey.created_at): CreatedAt; + get(key: YjsDatabaseKey.created_at): CreatedAt; - get (key: YjsDatabaseKey.modified_at): ModifiedAt; + get(key: YjsDatabaseKey.modified_at): ModifiedAt; // eslint-disable-next-line @typescript-eslint/unified-signatures - get (key: YjsDatabaseKey.layout): string; + get(key: YjsDatabaseKey.layout): string; - get (key: YjsDatabaseKey.layout_settings): YDatabaseLayoutSettings; + get(key: YjsDatabaseKey.layout_settings): YDatabaseLayoutSettings; - get (key: YjsDatabaseKey.filters): YDatabaseFilters; + get(key: YjsDatabaseKey.filters): YDatabaseFilters; - get (key: YjsDatabaseKey.groups): YDatabaseGroups; + get(key: YjsDatabaseKey.groups): YDatabaseGroups; - get (key: YjsDatabaseKey.sorts): YDatabaseSorts; + get(key: YjsDatabaseKey.sorts): YDatabaseSorts; - get (key: YjsDatabaseKey.field_settings): YDatabaseFieldSettings; + get(key: YjsDatabaseKey.field_settings): YDatabaseFieldSettings; - get (key: YjsDatabaseKey.field_orders): YDatabaseFieldOrders; + get(key: YjsDatabaseKey.field_orders): YDatabaseFieldOrders; - get (key: YjsDatabaseKey.row_orders): YDatabaseRowOrders; + get(key: YjsDatabaseKey.row_orders): YDatabaseRowOrders; - get (key: YjsDatabaseKey.calculations): YDatabaseCalculations; + get(key: YjsDatabaseKey.calculations): YDatabaseCalculations; - get (key: YjsDatabaseKey.is_inline): boolean; + get(key: YjsDatabaseKey.is_inline): boolean; } export type YDatabaseFieldOrders = Y.Array<{ id: FieldId }>; // [ { id: FieldId } ] -export type YDatabaseRowOrders = Y.Array<{ id: RowId, height: number }>; // [ { id: RowId, height: number } ] +export type YDatabaseRowOrders = Y.Array<{ id: RowId; height: number }>; // [ { id: RowId, height: number } ] export type YDatabaseGroups = Y.Array; @@ -573,136 +577,136 @@ export type GroupId = string; export interface YDatabaseLayoutSettings extends Y.Map { // DatabaseViewLayout.Board - get (key: '1'): YDatabaseBoardLayoutSetting; + get(key: '1'): YDatabaseBoardLayoutSetting; // DatabaseViewLayout.Calendar - get (key: '2'): YDatabaseCalendarLayoutSetting; + get(key: '2'): YDatabaseCalendarLayoutSetting; } export interface YDatabaseBoardLayoutSetting extends Y.Map { - get (key: YjsDatabaseKey.hide_ungrouped_column | YjsDatabaseKey.collapse_hidden_groups): boolean; + get(key: YjsDatabaseKey.hide_ungrouped_column | YjsDatabaseKey.collapse_hidden_groups): boolean; } export interface YDatabaseCalendarLayoutSetting extends Y.Map { - get (key: YjsDatabaseKey.first_day_of_week | YjsDatabaseKey.field_id | YjsDatabaseKey.layout_ty): string; + get(key: YjsDatabaseKey.first_day_of_week | YjsDatabaseKey.field_id | YjsDatabaseKey.layout_ty): string; - get (key: YjsDatabaseKey.show_week_numbers | YjsDatabaseKey.show_weekends): boolean; + get(key: YjsDatabaseKey.show_week_numbers | YjsDatabaseKey.show_weekends): boolean; } export interface YDatabaseGroup extends Y.Map { - get (key: YjsDatabaseKey.id): GroupId; + get(key: YjsDatabaseKey.id): GroupId; - get (key: YjsDatabaseKey.field_id): FieldId; + get(key: YjsDatabaseKey.field_id): FieldId; // eslint-disable-next-line @typescript-eslint/unified-signatures - get (key: YjsDatabaseKey.content): string; // "{"hide_empty":false,"condition":2}" + get(key: YjsDatabaseKey.content): string; // "{"hide_empty":false,"condition":2}" - get (key: YjsDatabaseKey.groups): YDatabaseGroupColumns; + get(key: YjsDatabaseKey.groups): YDatabaseGroupColumns; } -export type YDatabaseGroupColumns = Y.Array<{ id: string, visible: boolean }>; +export type YDatabaseGroupColumns = Y.Array<{ id: string; visible: boolean }>; export interface YDatabaseGroupColumn extends Y.Map { - get (key: YjsDatabaseKey.id): string; + get(key: YjsDatabaseKey.id): string; - get (key: YjsDatabaseKey.visible): boolean; + get(key: YjsDatabaseKey.visible): boolean; } export interface YDatabaseSort extends Y.Map { - get (key: YjsDatabaseKey.id): SortId; + get(key: YjsDatabaseKey.id): SortId; - get (key: YjsDatabaseKey.field_id): FieldId; + get(key: YjsDatabaseKey.field_id): FieldId; - get (key: YjsDatabaseKey.condition): string; + get(key: YjsDatabaseKey.condition): string; } export type FilterId = string; export interface YDatabaseFilter extends Y.Map { - get (key: YjsDatabaseKey.id): FilterId; + get(key: YjsDatabaseKey.id): FilterId; - get (key: YjsDatabaseKey.field_id): FieldId; + get(key: YjsDatabaseKey.field_id): FieldId; - get (key: YjsDatabaseKey.type | YjsDatabaseKey.condition | YjsDatabaseKey.content | YjsDatabaseKey.filter_type): string; + get(key: YjsDatabaseKey.type | YjsDatabaseKey.condition | YjsDatabaseKey.content | YjsDatabaseKey.filter_type): string; } export interface YDatabaseCalculation extends Y.Map { - get (key: YjsDatabaseKey.field_id): FieldId; + get(key: YjsDatabaseKey.field_id): FieldId; - get (key: YjsDatabaseKey.id | YjsDatabaseKey.type | YjsDatabaseKey.calculation_value): string; + get(key: YjsDatabaseKey.id | YjsDatabaseKey.type | YjsDatabaseKey.calculation_value): string; } export interface YDatabaseFieldSettings extends Y.Map { - get (key: FieldId): YDatabaseFieldSetting; + get(key: FieldId): YDatabaseFieldSetting; } export interface YDatabaseFieldSetting extends Y.Map { - get (key: YjsDatabaseKey.visibility): string; + get(key: YjsDatabaseKey.visibility): string; - get (key: YjsDatabaseKey.wrap): boolean; + get(key: YjsDatabaseKey.wrap): boolean; // eslint-disable-next-line @typescript-eslint/unified-signatures - get (key: YjsDatabaseKey.width): string; + get(key: YjsDatabaseKey.width): string; } export interface YDatabaseMetas extends Y.Map { - get (key: YjsDatabaseKey.iid): string; + get(key: YjsDatabaseKey.iid): string; } export interface YDatabaseFields extends Y.Map { - get (key: FieldId): YDatabaseField; + get(key: FieldId): YDatabaseField; } export interface YDatabaseField extends Y.Map { - get (key: YjsDatabaseKey.name): string; + get(key: YjsDatabaseKey.name): string; - get (key: YjsDatabaseKey.id): FieldId; + get(key: YjsDatabaseKey.id): FieldId; // eslint-disable-next-line @typescript-eslint/unified-signatures - get (key: YjsDatabaseKey.icon): string; + get(key: YjsDatabaseKey.icon): string; // eslint-disable-next-line @typescript-eslint/unified-signatures - get (key: YjsDatabaseKey.type): string; + get(key: YjsDatabaseKey.type): string; - get (key: YjsDatabaseKey.type_option): YDatabaseFieldTypeOption; + get(key: YjsDatabaseKey.type_option): YDatabaseFieldTypeOption; - get (key: YjsDatabaseKey.is_primary): boolean; + get(key: YjsDatabaseKey.is_primary): boolean; - get (key: YjsDatabaseKey.last_modified): LastModified; + get(key: YjsDatabaseKey.last_modified): LastModified; } export interface YDatabaseFieldTypeOption extends Y.Map { // key is the field type - get (key: string): YMapFieldTypeOption; + get(key: string): YMapFieldTypeOption; } export interface YMapFieldTypeOption extends Y.Map { // single select, Multi select, File media - get (key: YjsDatabaseKey.content): string; + get(key: YjsDatabaseKey.content): string; // CreatedTime, LastEditedTime, DateTime // eslint-disable-next-line @typescript-eslint/unified-signatures - get (key: YjsDatabaseKey.time_format): string; + get(key: YjsDatabaseKey.time_format): string; // CreatedTime, LastEditedTime, DateTime // eslint-disable-next-line @typescript-eslint/unified-signatures - get (key: YjsDatabaseKey.date_format): string; + get(key: YjsDatabaseKey.date_format): string; // Relation - get (key: YjsDatabaseKey.database_id): DatabaseId; + get(key: YjsDatabaseKey.database_id): DatabaseId; // Number // eslint-disable-next-line @typescript-eslint/unified-signatures - get (key: YjsDatabaseKey.format): string; + get(key: YjsDatabaseKey.format): string; // LastModified and CreatedTime - get (key: YjsDatabaseKey.include_time): boolean; + get(key: YjsDatabaseKey.include_time): boolean; // AI Translate // eslint-disable-next-line @typescript-eslint/unified-signatures - get (key: YjsDatabaseKey.auto_fill): boolean; + get(key: YjsDatabaseKey.auto_fill): boolean; - get (key: YjsDatabaseKey.language): bigint; + get(key: YjsDatabaseKey.language): bigint; } export enum Types { @@ -720,7 +724,6 @@ export enum CollabOrigin { Local = 'local', // from remote changes and never sync to remote. Remote = 'remote', - } export interface PublishViewPayload { @@ -920,7 +923,7 @@ export interface GuestInvitation { is_existing_member: boolean; } -export interface GuestConversionCodeInfo { +export interface GuestConversionCodeInfo { workspace_name: string; requester_avatar?: string; requester_name: string; @@ -993,7 +996,7 @@ export enum SubscriptionPlan { Free = 'free', Pro = 'pro', Team = 'team', - AIMax = 'ai_max' + AIMax = 'ai_max', } export enum SubscriptionInterval { @@ -1013,8 +1016,8 @@ export type Subscriptions = Subscription[]; export interface UpdatePagePayload { name: string; icon?: { - ty: ViewIconType, - value: string, + ty: ViewIconType; + value: string; }; extra?: Partial; } @@ -1085,18 +1088,18 @@ export interface CreatePagePayload { } export interface CreateFolderViewPayload { - parentViewId: string, - layout: ViewLayout, - name?: string, - viewId?: string, - databaseId?: string, + parentViewId: string; + layout: ViewLayout; + name?: string; + viewId?: string; + databaseId?: string; } export interface CreateSpacePayload { name?: string; space_icon?: string; space_icon_color?: string; - space_permission?: SpacePermission, // 0 for public space, 1 for private space + space_permission?: SpacePermission; // 0 for public space, 1 for private space } export interface UpdateSpacePayload extends CreateSpacePayload { @@ -1105,7 +1108,7 @@ export interface UpdateSpacePayload extends CreateSpacePayload { export interface QuickNoteEditorData { type: string; - delta: { insert: string, attributes?: Record }[]; + delta: { insert: string; attributes?: Record }[]; data?: BlockData; children: QuickNoteEditorData[]; } @@ -1113,7 +1116,7 @@ export interface QuickNoteEditorData { export interface QuickNote { id: string; title: string; - data: QuickNoteEditorData[], + data: QuickNoteEditorData[]; created_at: string; last_updated_at: string; } @@ -1146,9 +1149,9 @@ export interface GenerateAITranslateRowPayload { title: string; // cell data content: string; - }[], - language: string, - include_header?: boolean + }[]; + language: string; + include_header?: boolean; } export type LoadDatabasePrompts = (config: PromptDatabaseConfiguration) => Promise<{ @@ -1183,3 +1186,17 @@ export interface DatabasePromptRow { [fieldId: string]: any; }; } + +export interface MentionablePerson { + avatar_url: string | null; + cover_image_url: string | null; + custom_image_url: string | null; + description: string | null; + email: string; + + name: string; + role: number; + uuid: string; + invited: boolean; + last_mentioned_at: string | null; +} diff --git a/src/components/_shared/gallery-preview/GalleryPreview.tsx b/src/components/_shared/gallery-preview/GalleryPreview.tsx index 56c8aff9..142c5be3 100644 --- a/src/components/_shared/gallery-preview/GalleryPreview.tsx +++ b/src/components/_shared/gallery-preview/GalleryPreview.tsx @@ -1,5 +1,5 @@ import { IconButton, Portal, Tooltip } from '@mui/material'; -import React, { memo, useCallback, useEffect, useRef, useState } from 'react'; +import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { ReactZoomPanPinchContentRef, TransformComponent, TransformWrapper } from 'react-zoom-pan-pinch'; @@ -12,6 +12,7 @@ import { ReactComponent as ReloadIcon } from '@/assets/icons/reset.svg'; import { ReactComponent as DownloadIcon } from '@/assets/icons/save_as.svg'; import { notify } from '@/components/_shared/notify'; import { copyTextToClipboard } from '@/utils/copy'; +import isURL from 'validator/lib/isURL'; export interface GalleryImage { src: string; @@ -22,11 +23,12 @@ export interface GalleryPreviewProps { open: boolean; onClose: () => void; previewIndex: number; + workspaceId: string; } const buttonClassName = 'p-1 hover:bg-transparent text-white hover:text-text-action p-0'; -function GalleryPreview({ images, open, onClose, previewIndex }: GalleryPreviewProps) { +function GalleryPreview({ images, open, onClose, previewIndex, workspaceId }: GalleryPreviewProps) { const { t } = useTranslation(); const [index, setIndex] = useState(previewIndex); const transformComponentRef = useRef(null); @@ -92,6 +94,16 @@ function GalleryPreview({ images, open, onClose, previewIndex }: GalleryPreviewP }; }, [handleKeydown]); + const imageUrl = useMemo(() => { + if (isURL(images[index].src)) { + return images[index].src; + } + + const fileId = images[index].src; + + return import.meta.env.AF_BASE_URL + '/api/file_storage/' + workspaceId + '/v1/blob/' + fileId; + }, [images, index, workspaceId]); + if (!open) { return null; } @@ -190,7 +202,7 @@ function GalleryPreview({ images, open, onClose, previewIndex }: GalleryPreviewP }} > {images[index].src} import('@/components/app/ViewModal')); @@ -96,6 +97,7 @@ export interface AppContextType { loadDatabasePrompts?: LoadDatabasePrompts; testDatabasePromptConfig?: TestDatabasePromptConfig; eventEmitter?: EventEmitter; + getMentionUser?: (uuid: string) => Promise; } const USER_NO_ACCESS_CODE = [1024, 1012]; @@ -299,6 +301,15 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { collabType, }); + // add recent pages when view is loaded + void (async () => { + try { + await service.addRecentPages(currentWorkspaceId, [id]); + } catch (e) { + console.error(e); + } + })(); + return res; // eslint-disable-next-line } catch (e: any) { @@ -1040,6 +1051,49 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { [getFields] ); + const mentionableUsersRef = useRef([]); + const loadMentionableUsers = useCallback(async () => { + if (!currentWorkspaceId || !service) { + throw new Error('No workspace or service found'); + } + + try { + const res = await service.getMentionableUsers(currentWorkspaceId); + + mentionableUsersRef.current = res; + + return res; + } catch (e) { + return Promise.reject(e); + } + }, [currentWorkspaceId, service]); + + useEffect(() => { + void loadMentionableUsers(); + }, [loadMentionableUsers]); + + const getMentionUser = useCallback( + async (uuid: string) => { + console.log('mentionableUsers', mentionableUsersRef.current); + if (mentionableUsersRef.current.length > 0) { + const user = mentionableUsersRef.current.find((user) => user.uuid === uuid); + + if (user) { + return user; + } + } + + try { + const res = await loadMentionableUsers(); + + return res.find((user) => user.uuid === uuid); + } catch (e) { + return Promise.reject(e); + } + }, + [loadMentionableUsers] + ); + return ( { loadDatabasePrompts, testDatabasePromptConfig, eventEmitter: eventEmitterRef.current, + getMentionUser, }} > @@ -1248,6 +1303,7 @@ export function useAppHandlers() { loadDatabasePrompts: context.loadDatabasePrompts, testDatabasePromptConfig: context.testDatabasePromptConfig, eventEmitter: context.eventEmitter, + getMentionUser: context.getMentionUser, }; } diff --git a/src/components/database/components/cell/file-media/FileMediaCell.tsx b/src/components/database/components/cell/file-media/FileMediaCell.tsx index a42acf0c..fd2c34d3 100644 --- a/src/components/database/components/cell/file-media/FileMediaCell.tsx +++ b/src/components/database/components/cell/file-media/FileMediaCell.tsx @@ -1,5 +1,6 @@ import React, { Suspense, useCallback, useMemo } from 'react'; +import { useDatabaseContext } from '@/application/database-yjs'; import { CellProps, FileMediaCell as FileMediaCellType, @@ -24,7 +25,7 @@ export function FileMediaCell({ readOnly, }: CellProps) { const value = cell?.data; - + const { workspaceId } = useDatabaseContext(); const [openPreview, setOpenPreview] = React.useState(false); const previewIndexRef = React.useRef(0); const photos = useMemo(() => { @@ -111,6 +112,7 @@ export function FileMediaCell({ {openPreview && ( void }) { + const { workspaceId } = useDatabaseContext(); + const thumb = useMemo(() => { - const url = new URL(file.url); + let fileUrl = file.url; + + if (!isURL(file.url)) { + fileUrl = import.meta.env.AF_BASE_URL + '/api/file_storage/' + workspaceId + '/v1/blob/' + file.url; + } + + const url = new URL(fileUrl); url.searchParams.set('auto', 'format'); url.searchParams.set('fit', 'crop'); return url.toString() + '&w=240&q=80'; - }, [file.url]); + }, [file.url, workspaceId]); return (
+