diff --git a/package.json b/package.json index eb00e937..4774a0af 100644 --- a/package.json +++ b/package.json @@ -185,5 +185,8 @@ "vite-plugin-svgr": "^3.2.0", "vite-plugin-terminal": "^1.2.0", "vite-plugin-total-bundle-size": "^1.0.7" + }, + "engines": { + "pnpm": "^8.0.0" } } diff --git a/src/@types/translations/en.json b/src/@types/translations/en.json index 9b71b6e6..179b8332 100644 --- a/src/@types/translations/en.json +++ b/src/@types/translations/en.json @@ -3061,5 +3061,7 @@ "downloadTip": "Don't have AppFlowy Apps installed? Download .", "here": "here", "openInBrowser": "Open in browser", - "openInApp": "Open in app" + "openInApp": "Open in app", + "comments": "Comments", + "duplicateAsTemplate": "Duplicate as template" } diff --git a/src/application/publish/context.tsx b/src/application/publish/context.tsx index bb33c38c..3a6f2bed 100644 --- a/src/application/publish/context.tsx +++ b/src/application/publish/context.tsx @@ -13,7 +13,7 @@ import { notify } from '@/components/_shared/notify'; import { findAncestors, findView } from '@/components/_shared/outline/utils'; import { useService } from '@/components/main/app.hooks'; import { useLiveQuery } from 'dexie-react-hooks'; -import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; +import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; export interface PublishContextType { @@ -31,6 +31,8 @@ export interface PublishContextType { breadcrumbs: View[]; rendered?: boolean; onRendered?: () => void; + commentEnabled?: boolean; + duplicateEnabled?: boolean; } export const PublishContext = createContext(null); @@ -60,12 +62,12 @@ export const PublishProvider = ({ }; }, []); - const viewMeta = useLiveQuery(async () => { + const viewMeta = useLiveQuery(async() => { const name = `${namespace}_${publishName}`; const view = await db.view_metas.get(name); - if (!view) return; + if(!view) return; return { ...view, @@ -73,18 +75,25 @@ export const PublishProvider = ({ }; }, [namespace, publishName, outline]); + const viewId = viewMeta?.view_id; + + const [publishInfo, setPublishInfo] = React.useState<{ + commentEnabled: boolean, + duplicateEnabled: boolean, + } | undefined>(); + const originalCrumbs = useMemo(() => { - if (!viewMeta || !outline) return []; + if(!viewMeta || !outline) return []; const ancestors = findAncestors(outline, viewMeta?.view_id); - if (ancestors) return ancestors; - if (!viewMeta?.ancestor_views) return []; + if(ancestors) return ancestors; + if(!viewMeta?.ancestor_views) return []; const parseToView = (ancestor: ViewInfo): View => { let extra = null; try { extra = ancestor.extra ? JSON.parse(ancestor.extra) : null; - } catch (e) { + } catch(e) { // do nothing } @@ -113,13 +122,13 @@ export const PublishProvider = ({ const appendBreadcrumb = useCallback((view?: View) => { setBreadcrumbs((prev) => { - if (!view) { + if(!view) { return prev.slice(0, -1); } const index = prev.findIndex((v) => v.view_id === view.view_id); - if (index === -1) { + if(index === -1) { return [...prev, view]; } @@ -165,24 +174,43 @@ export const PublishProvider = ({ createdRowKeys.current = []; - if (!rowKeys.length) return; + if(!rowKeys.length) return; rowKeys.forEach((rowKey) => { try { service?.deleteRowDoc(rowKey); - } catch (e) { + } catch(e) { console.error(e); } }); }, [service, publishName]); + + const loadPublishInfo = useCallback(async() => { + if(!service || !viewId) return; + try { + const res = await service.getPublishInfo(viewId); + + setPublishInfo(res); + + // eslint-disable-next-line + } catch(e: any) { + // do nothing + } + + }, [viewId, service]); + + useEffect(() => { + void loadPublishInfo(); + }, [loadPublishInfo]); + const navigate = useNavigate(); const loadViewMeta = useCallback( - async (viewId: string, callback?: (meta: View) => void) => { + async(viewId: string, callback?: (meta: View) => void) => { try { const info = await service?.getPublishInfo(viewId); - if (!info) { + if(!info) { throw new Error('View has not been published yet'); } @@ -192,7 +220,7 @@ export const PublishProvider = ({ const meta = await service?.getPublishViewMeta(namespace, publishName); - if (!meta) { + if(!meta) { return Promise.reject(new Error('View meta has not been published yet')); } @@ -214,7 +242,7 @@ export const PublishProvider = ({ callback?.(res); - if (callback) { + if(callback) { setSubscribers((prev) => { prev.set(name, (meta) => { return callback?.(parseMetaToView(meta)); @@ -225,7 +253,7 @@ export const PublishProvider = ({ } return res; - } catch (e) { + } catch(e) { return Promise.reject(e); } }, @@ -233,13 +261,13 @@ export const PublishProvider = ({ ); const toView = useCallback( - async (viewId: string, blockId?: string) => { + async(viewId: string, blockId?: string) => { try { const view = await loadViewMeta(viewId); const res = await service?.getPublishInfo(viewId); - if (!res) { + if(!res) { throw new Error('View has not been published yet'); } @@ -248,8 +276,8 @@ export const PublishProvider = ({ prevViewMeta.current = undefined; const searchParams = new URLSearchParams(''); - if (blockId) { - switch (view.layout) { + if(blockId) { + switch(view.layout) { case ViewLayout.Document: searchParams.set('blockId', blockId); break; @@ -263,13 +291,13 @@ export const PublishProvider = ({ } } - if (isTemplate) { + if(isTemplate) { searchParams.set('template', 'true'); } let url = `/${viewNamespace}/${publishName}`; - if (searchParams.toString()) { + if(searchParams.toString()) { url += `?${searchParams.toString()}`; } @@ -277,40 +305,40 @@ export const PublishProvider = ({ replace: true, }); return; - } catch (e) { + } catch(e) { return Promise.reject(e); } }, [loadViewMeta, service, isTemplate, navigate], ); - const loadOutline = useCallback(async () => { - if (!service || !namespace) return; + const loadOutline = useCallback(async() => { + if(!service || !namespace) return; try { const res = await service?.getPublishOutline(namespace); - if (!res) { + if(!res) { throw new Error('Publish outline not found'); } setOutline(res); - } catch (e) { + } catch(e) { notify.error('Publish outline not found'); } }, [namespace, service]); const createRowDoc = useCallback( - async (rowKey: string) => { + async(rowKey: string) => { try { const doc = await service?.createRowDoc(rowKey); - if (!doc) { + if(!doc) { throw new Error('Failed to create row doc'); } createdRowKeys.current.push(rowKey); return doc; - } catch (e) { + } catch(e) { return Promise.reject(e); } }, @@ -318,11 +346,11 @@ export const PublishProvider = ({ ); const loadView = useCallback( - async (viewId: string, isSubDocument?: boolean) => { - if (isSubDocument) { + async(viewId: string, isSubDocument?: boolean) => { + if(isSubDocument) { const data = await service?.getPublishRowDocument(viewId); - if (!data) { + if(!data) { return Promise.reject(new Error('View has not been published yet')); } @@ -332,7 +360,7 @@ export const PublishProvider = ({ try { const res = await service?.getPublishInfo(viewId); - if (!res) { + if(!res) { throw new Error('View has not been published yet'); } @@ -340,12 +368,12 @@ export const PublishProvider = ({ const data = service?.getPublishView(namespace, publishName); - if (!data) { + if(!data) { throw new Error('View has not been published yet'); } return data; - } catch (e) { + } catch(e) { return Promise.reject(e); } }, @@ -357,7 +385,7 @@ export const PublishProvider = ({ }, []); useEffect(() => { - if (!viewMeta && prevViewMeta.current) { + if(!viewMeta && prevViewMeta.current) { window.location.reload(); return; } @@ -385,6 +413,8 @@ export const PublishProvider = ({ appendBreadcrumb, onRendered, rendered, + commentEnabled: publishInfo?.commentEnabled, + duplicateEnabled: publishInfo?.duplicateEnabled, }} > {children} @@ -392,6 +422,6 @@ export const PublishProvider = ({ ); }; -export function usePublishContext () { +export function usePublishContext() { return useContext(PublishContext); } diff --git a/src/application/services/js-services/http/http_api.ts b/src/application/services/js-services/http/http_api.ts index 23350c9e..abd1b774 100644 --- a/src/application/services/js-services/http/http_api.ts +++ b/src/application/services/js-services/http/http_api.ts @@ -27,7 +27,7 @@ import { CreateWorkspacePayload, UpdateWorkspacePayload, PublishViewPayload, - UploadPublishNamespacePayload, + UploadPublishNamespacePayload, UpdatePublishConfigPayload, } from '@/application/types'; import { GlobalComment, Reaction } from '@/application/comment.type'; import { initGrantService, refreshToken } from '@/application/services/js-services/http/gotrue'; @@ -51,8 +51,8 @@ export * from './gotrue'; let axiosInstance: AxiosInstance | null = null; -export function initAPIService (config: AFCloudConfig) { - if (axiosInstance) { +export function initAPIService(config: AFCloudConfig) { + if(axiosInstance) { return; } @@ -66,10 +66,10 @@ export function initAPIService (config: AFCloudConfig) { initGrantService(config.gotrueURL); axiosInstance.interceptors.request.use( - async (config) => { + async(config) => { const token = getTokenParsed(); - if (!token) { + if(!token) { return config; } @@ -78,18 +78,18 @@ export function initAPIService (config: AFCloudConfig) { let access_token = token.access_token; const refresh_token = token.refresh_token; - if (isExpired) { + if(isExpired) { try { const newToken = await refreshToken(refresh_token); access_token = newToken?.access_token || ''; - } catch (e) { + } catch(e) { invalidToken(); return config; } } - if (access_token) { + if(access_token) { Object.assign(config.headers, { Authorization: `Bearer ${access_token}`, }); @@ -102,13 +102,13 @@ export function initAPIService (config: AFCloudConfig) { }, ); - axiosInstance.interceptors.response.use(async (response) => { + axiosInstance.interceptors.response.use(async(response) => { const status = response.status; - if (status === 401) { + if(status === 401) { const token = getTokenParsed(); - if (!token) { + if(!token) { invalidToken(); return response; } @@ -117,7 +117,7 @@ export function initAPIService (config: AFCloudConfig) { try { await refreshToken(refresh_token); - } catch (e) { + } catch(e) { invalidToken(); } } @@ -126,10 +126,10 @@ 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) { + if(!hash) { return Promise.reject('No hash found'); } @@ -137,7 +137,7 @@ export async function signInWithUrl (url: string) { const accessToken = params.get('access_token'); const refresh_token = params.get('refresh_token'); - if (!accessToken || !refresh_token) { + if(!accessToken || !refresh_token) { return Promise.reject({ code: -1, message: 'No access token or refresh token found', @@ -146,7 +146,7 @@ export async function signInWithUrl (url: string) { try { await verifyToken(accessToken); - } catch (e) { + } catch(e) { return Promise.reject({ code: -1, message: 'Verify token failed', @@ -155,7 +155,7 @@ export async function signInWithUrl (url: string) { try { await refreshToken(refresh_token); - } catch (e) { + } catch(e) { return Promise.reject({ code: -1, message: 'Refresh token failed', @@ -163,7 +163,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; @@ -175,14 +175,14 @@ export async function verifyToken (accessToken: string) { const data = response?.data; - if (data?.code === 0 && data.data) { + if(data?.code === 0 && data.data) { return data.data; } 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; @@ -203,7 +203,7 @@ export async function getCurrentUser (): Promise { const data = response?.data; - if (data?.code === 0 && data.data) { + if(data?.code === 0 && data.data) { const { uid, uuid, email, name, metadata } = data.data; return { @@ -230,7 +230,7 @@ interface AFWorkspace { database_storage_id: string, } -function afWorkspace2Workspace (workspace: AFWorkspace): Workspace { +function afWorkspace2Workspace(workspace: AFWorkspace): Workspace { return { id: workspace.workspace_id, owner: { @@ -245,21 +245,21 @@ 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; message: string; }>(url); - if (response?.data.code === 0) { + if(response?.data.code === 0) { return; } 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; @@ -274,14 +274,14 @@ export async function updateWorkspace (workspaceId: string, payload: UpdateWorks const data = response?.data; - if (data?.code === 0) { + if(data?.code === 0) { return; } 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; @@ -293,14 +293,14 @@ export async function createWorkspace (payload: CreateWorkspacePayload) { const data = response?.data; - if (data?.code === 0 && data.data) { + if(data?.code === 0 && data.data) { return data.data.workspace_id; } return Promise.reject(data); } -export async function getUserWorkspaceInfo (): Promise<{ +export async function getUserWorkspaceInfo(): Promise<{ user_id: string; selected_workspace: Workspace; workspaces: Workspace[]; @@ -321,7 +321,7 @@ export async function getUserWorkspaceInfo (): Promise<{ const data = response?.data; - if (data?.code === 0) { + if(data?.code === 0) { const { visiting_workspace, workspaces, user_profile } = data.data; return { @@ -334,49 +334,49 @@ 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; message: string; }>(url, payload); - if (response?.data.code === 0) { + if(response?.data.code === 0) { return; } 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; message: string; }>(url); - if (response?.data.code === 0) { + if(response?.data.code === 0) { return; } 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; message: string; }>(url, payload); - if (response?.data.code === 0) { + if(response?.data.code === 0) { return; } 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; @@ -388,14 +388,14 @@ export async function getPublishViewMeta (namespace: string, publishName: string message: string; }>(url); - if (response?.data.code !== 0) { + if(response?.data.code !== 0) { return Promise.reject(response?.data); } 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', @@ -404,13 +404,13 @@ 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: { +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'); - if (!deviceId) { + if(!deviceId) { deviceId = nanoid(8); localStorage.setItem('x-device-id', deviceId); } @@ -428,14 +428,14 @@ export async function updateCollab (workspaceId: string, objectId: string, colla }, }); - if (response?.data.code !== 0) { + if(response?.data.code !== 0) { return Promise.reject(response?.data); } 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; @@ -450,7 +450,7 @@ export async function getCollab (workspaceId: string, objectId: string, collabTy }, }); - if (response?.data.code !== 0) { + if(response?.data.code !== 0) { return Promise.reject(response?.data); } @@ -461,7 +461,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; @@ -477,11 +477,11 @@ export async function getPageCollab (workspaceId: string, viewId: string) { message: string; }>(url); - if (!response) { + if(!response) { return Promise.reject('No response'); } - if (response.data.code !== 0) { + if(response.data.code !== 0) { return Promise.reject(response?.data); } @@ -495,11 +495,11 @@ 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); - if (meta.view.layout === ViewLayout.Document) { + if(meta.view.layout === ViewLayout.Document) { return { data: blob, meta, @@ -527,12 +527,26 @@ export async function getPublishView (publishNamespace: string, publishName: str subDocuments: res.database_row_document_collabs, meta, }; - } catch (e) { + } catch(e) { return Promise.reject(e); } } -export async function getPublishInfoWithViewId (viewId: string) { +export async function updatePublishConfig(workspaceId: string, payload: UpdatePublishConfigPayload) { + const url = `/api/workspace/${workspaceId}/publish`; + const response = await axiosInstance?.patch<{ + code: number; + message: string; + }>(url, [payload]); + + if(response?.data.code === 0) { + return; + } + + return Promise.reject(response?.data); +} + +export async function getPublishInfoWithViewId(viewId: string) { const url = `/api/workspace/v1/published-info/${viewId}`; const response = await axiosInstance?.get<{ code: number; @@ -542,20 +556,22 @@ export async function getPublishInfoWithViewId (viewId: string) { publisher_email: string; view_id: string; publish_timestamp: string; + comments_enabled: boolean; + duplicate_enabled: boolean; }; message: string; }>(url); const data = response?.data; - if (data?.code === 0 && data.data) { + if(data?.code === 0 && data.data) { return data.data; } 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; @@ -567,14 +583,14 @@ export async function getAppFavorites (workspaceId: string) { const data = response?.data; - if (data?.code === 0 && data.data) { + if(data?.code === 0 && data.data) { return data.data.views; } 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; @@ -586,14 +602,14 @@ export async function getAppTrash (workspaceId: string) { const data = response?.data; - if (data?.code === 0 && data.data) { + if(data?.code === 0 && data.data) { return data.data.views; } 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; @@ -605,14 +621,14 @@ export async function getAppRecent (workspaceId: string) { const data = response?.data; - if (data?.code === 0 && data.data) { + if(data?.code === 0 && data.data) { return data.data.views; } 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<{ @@ -623,14 +639,14 @@ export async function getAppOutline (workspaceId: string) { const data = response?.data; - if (data?.code === 0 && data.data) { + if(data?.code === 0 && data.data) { return data.data.children; } 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; @@ -640,14 +656,14 @@ export async function getView (workspaceId: string, viewId: string, depth: numbe const data = response?.data; - if (data?.code === 0 && data.data) { + if(data?.code === 0 && data.data) { return data.data; } 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; @@ -657,14 +673,14 @@ export async function getPublishNamespace (workspaceId: string) { const data = response?.data; - if (data?.code === 0 && data.data) { + if(data?.code === 0 && data.data) { return data.data; } 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; @@ -679,14 +695,14 @@ export async function getPublishHomepage (workspaceId: string) { const data = response?.data; - if (data?.code === 0 && data.data) { + if(data?.code === 0 && data.data) { return data.data; } 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; @@ -695,28 +711,28 @@ export async function updatePublishHomepage (workspaceId: string, viewId: string view_id: viewId, }); - if (response?.data.code === 0) { + if(response?.data.code === 0) { return; } 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; message: string; }>(url); - if (response?.data.code === 0) { + if(response?.data.code === 0) { return; } 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; @@ -726,14 +742,14 @@ export async function getPublishOutline (publishNamespace: string) { const data = response?.data; - if (data?.code === 0 && data.data) { + if(data?.code === 0 && data.data) { return data.data.children; } 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; @@ -758,7 +774,7 @@ export async function getPublishViewComments (viewId: string): Promise { @@ -782,10 +798,10 @@ 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) { + if(commentId) { url += `?comment_id=${commentId}`; } @@ -807,12 +823,12 @@ export async function getReactions (viewId: string, commentId?: string): Promise const data = response?.data; - if (data?.code === 0 && data.data) { + if(data?.code === 0 && data.data) { const { reactions } = data.data; const reactionsMap: Record = {}; - for (const reaction of reactions) { - if (!reactionsMap[reaction.comment_id]) { + for(const reaction of reactions) { + if(!reactionsMap[reaction.comment_id]) { reactionsMap[reaction.comment_id] = []; } @@ -833,21 +849,21 @@ 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, reply_comment_id: replyCommentId, }); - if (response?.data.code === 0) { + if(response?.data.code === 0) { return; } 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: { @@ -855,28 +871,28 @@ export async function deleteGlobalCommentOnPublishView (viewId: string, commentI }, }); - if (response?.data.code === 0) { + if(response?.data.code === 0) { return; } 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, reaction_type: reactionType, }); - if (response?.data.code === 0) { + if(response?.data.code === 0) { return; } 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: { @@ -885,14 +901,14 @@ export async function removeReaction (viewId: string, commentId: string, reactio }, }); - if (response?.data.code === 0) { + if(response?.data.code === 0) { return; } return Promise.reject(response?.data.message); } -export async function getWorkspaces (): Promise { +export async function getWorkspaces(): Promise { const query = new URLSearchParams({ include_member_count: 'true', }); @@ -906,7 +922,7 @@ export async function getWorkspaces (): Promise { const data = response?.data; - if (data?.code === 0 && data.data) { + if(data?.code === 0 && data.data) { return data.data.map(afWorkspace2Workspace); } @@ -930,7 +946,7 @@ export interface WorkspaceFolder { children: WorkspaceFolder[]; } -function iterateFolder (folder: WorkspaceFolder): FolderView { +function iterateFolder(folder: WorkspaceFolder): FolderView { return { id: folder.view_id, name: folder.name, @@ -944,7 +960,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; @@ -954,7 +970,7 @@ export async function getWorkspaceFolder (workspaceId: string): Promise(url, payload); - if (res?.data.code === 0) { + if(res?.data.code === 0) { return res.data.data.view_id; } 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; message: string; }>(url, template); - if (response?.data.code === 0) { + if(response?.data.code === 0) { return; } 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; message: string; }>(url, template); - if (response?.data.code === 0) { + if(response?.data.code === 0) { return; } return Promise.reject(response?.data.message); } -export async function getTemplates ({ +export async function getTemplates({ categoryId, nameContains, }: { @@ -1037,14 +1053,14 @@ export async function getTemplates ({ const data = response?.data; - if (data?.code === 0 && data.data) { + if(data?.code === 0 && data.data) { return data.data.templates; } 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; @@ -1054,28 +1070,28 @@ export async function getTemplateById (viewId: string) { const data = response?.data; - if (data?.code === 0 && data.data) { + if(data?.code === 0 && data.data) { return data.data; } 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; message: string; }>(url); - if (response?.data.code === 0) { + if(response?.data.code === 0) { return; } 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; @@ -1088,56 +1104,56 @@ export async function getTemplateCategories () { const data = response?.data; - if (data?.code === 0 && data.data) { + if(data?.code === 0 && data.data) { return data.data.categories; } 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; message: string; }>(url, category); - if (response?.data.code === 0) { + if(response?.data.code === 0) { return; } 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; message: string; }>(url, category); - if (response?.data.code === 0) { + if(response?.data.code === 0) { return; } 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; message: string; }>(url); - if (response?.data.code === 0) { + if(response?.data.code === 0) { return; } 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; @@ -1149,56 +1165,56 @@ export async function getTemplateCreators () { const data = response?.data; - if (data?.code === 0 && data.data) { + if(data?.code === 0 && data.data) { return data.data.creators; } 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; message: string; }>(url, creator); - if (response?.data.code === 0) { + if(response?.data.code === 0) { return; } 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; message: string; }>(url, creator); - if (response?.data.code === 0) { + if(response?.data.code === 0) { return; } 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; message: string; }>(url); - if (response?.data.code === 0) { + if(response?.data.code === 0) { return; } 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(); @@ -1221,14 +1237,14 @@ export async function uploadTemplateAvatar (file: File) { const data = response?.data; - if (data?.code === 0 && data.data) { + if(data?.code === 0 && data.data) { return axiosInstance?.defaults.baseURL + '/api/template-center/avatar/' + data.data.file_id; } 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; @@ -1238,28 +1254,28 @@ export async function getInvitation (invitationId: string) { const data = response?.data; - if (data?.code === 0 && data.data) { + if(data?.code === 0 && data.data) { return data.data; } 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; message: string; }>(url); - if (response?.data.code === 0) { + if(response?.data.code === 0) { return; } 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; @@ -1277,7 +1293,7 @@ export async function getRequestAccessInfo (requestId: string): Promise(url); - if (response?.data.code === 0) { + if(response?.data.code === 0) { return response?.data.data; } @@ -1362,19 +1378,19 @@ export async function getSubscriptions () { } -export async function getWorkspaceSubscriptions (workspaceId: string) { +export async function getWorkspaceSubscriptions(workspaceId: string) { try { const plans = await getActiveSubscription(workspaceId); const subscriptions = await getSubscriptions(); return subscriptions?.filter((subscription) => plans?.includes(subscription.plan)); - } catch (e) { + } 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<{ @@ -1383,14 +1399,14 @@ export async function getActiveSubscription (workspaceId: string) { message: string; }>(url); - if (response?.data.code === 0) { + if(response?.data.code === 0) { return response?.data.data; } 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(); @@ -1406,7 +1422,7 @@ export async function createImportTask (file: File) { content_length: file.size, }); - if (res?.data.code === 0) { + if(res?.data.code === 0) { return { taskId: res?.data.data.task_id, presignedUrl: res?.data.data.presigned_url, @@ -1416,7 +1432,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; @@ -1429,7 +1445,7 @@ export async function uploadImportFile (presignedUrl: string, file: File, onProg }, }); - if (response.status === 200) { + if(response.status === 200) { return; } @@ -1439,7 +1455,7 @@ export async function uploadImportFile (presignedUrl: string, file: File, onProg }); } -export async function addAppPage (workspaceId: string, parentViewId: string, { +export async function addAppPage(workspaceId: string, parentViewId: string, { layout, name, }: CreatePagePayload) { @@ -1456,14 +1472,14 @@ export async function addAppPage (workspaceId: string, parentViewId: string, { name, }); - if (response?.data.code === 0) { + if(response?.data.code === 0) { return response?.data.data.view_id; } 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<{ @@ -1471,22 +1487,22 @@ export async function updatePage (workspaceId: string, viewId: string, data: Upd message: string; }>(url, data); - if (res?.data.code === 0) { + if(res?.data.code === 0) { return; } return Promise.reject(res?.data); } -export async function deleteTrash (workspaceId: string, viewId?: string) { - if (viewId) { +export async function deleteTrash(workspaceId: string, viewId?: string) { + if(viewId) { const url = `/api/workspace/${workspaceId}/trash/${viewId}`; const response = await axiosInstance?.delete<{ code: number; message: string; }>(url); - if (response?.data.code === 0) { + if(response?.data.code === 0) { return; } @@ -1498,7 +1514,7 @@ export async function deleteTrash (workspaceId: string, viewId?: string) { message: string; }>(url); - if (response?.data.code === 0) { + if(response?.data.code === 0) { return; } @@ -1507,21 +1523,21 @@ export async function deleteTrash (workspaceId: string, viewId?: string) { } -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; message: string; }>(url); - if (response?.data.code === 0) { + if(response?.data.code === 0) { return; } 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; @@ -1531,28 +1547,28 @@ export async function movePageTo (workspaceId: string, viewId: string, parentVie prev_view_id: prevViewId, }); - if (response?.data.code === 0) { + if(response?.data.code === 0) { return; } return Promise.reject(response?.data); } -export async function restorePage (workspaceId: string, viewId?: string) { +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; }>(url); - if (response?.data.code === 0) { + if(response?.data.code === 0) { return; } 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; @@ -1562,14 +1578,14 @@ export async function createSpace (workspaceId: string, payload: CreateSpacePayl message: string; }>(url, payload); - if (response?.data.code === 0) { + if(response?.data.code === 0) { return response?.data.data.view_id; } 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<{ @@ -1577,21 +1593,21 @@ export async function updateSpace (workspaceId: string, payload: UpdateSpacePayl message: string; }>(url, data); - if (response?.data.code === 0) { + if(response?.data.code === 0) { return; } 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 - if (file.size > 7 * 1024 * 1024) { + if(file.size > 7 * 1024 * 1024) { const plan = await getActiveSubscription(workspaceId); - if (plan?.length === 0 || plan?.[0] === SubscriptionPlan.Free) { + if(plan?.length === 0 || plan?.[0] === SubscriptionPlan.Free) { notify.error('Your file is over 7 MB limit of the Free plan. Upgrade for unlimited uploads.'); return Promise.reject({ @@ -1619,7 +1635,7 @@ export async function uploadFile (workspaceId: string, viewId: string, file: Fil }, }); - if (response?.data.code === 0) { + if(response?.data.code === 0) { const baseURL = axiosInstance?.defaults.baseURL; const url = `${baseURL}/api/file_storage/${workspaceId}/v1/blob/${viewId}/${response?.data.data.file_id}`; @@ -1628,9 +1644,9 @@ export async function uploadFile (workspaceId: string, viewId: string, file: Fil return Promise.reject(response?.data); // eslint-disable-next-line - } catch (e: any) { + } catch(e: any) { - if (e.response.status === 413) { + if(e.response.status === 413) { return Promise.reject({ code: 413, message: 'File size is too large. Please upgrade your plan for unlimited uploads.', @@ -1645,7 +1661,7 @@ export async function uploadFile (workspaceId: string, viewId: string, file: Fil } -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 => ({ @@ -1658,14 +1674,14 @@ export async function inviteMembers (workspaceId: string, emails: string[]) { message: string; }>(url, payload); - if (res?.data.code === 0) { + if(res?.data.code === 0) { return; } 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; @@ -1673,42 +1689,42 @@ export async function getMembers (workspaceId: string) { message: string; }>(url); - if (res?.data.code === 0) { + if(res?.data.code === 0) { return res?.data.data; } 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; message: string; }>(url); - if (res?.data.code === 0) { + if(res?.data.code === 0) { return; } 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; message: string; }>(url); - if (res?.data.code === 0) { + if(res?.data.code === 0) { return; } return Promise.reject(res?.data); } -export async function getQuickNoteList (workspaceId: string, params: { +export async function getQuickNoteList(workspaceId: string, params: { offset?: number; limit?: number; searchTerm?: string; @@ -1729,7 +1745,7 @@ export async function getQuickNoteList (workspaceId: string, params: { }, }); - if (res?.data.code === 0) { + if(res?.data.code === 0) { return { data: res?.data.data.quick_notes, has_more: res?.data.data.has_more, @@ -1739,7 +1755,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; @@ -1749,14 +1765,14 @@ export async function createQuickNote (workspaceId: string, payload: QuickNoteEd data: payload, }); - if (res?.data.code === 0) { + if(res?.data.code === 0) { return res?.data.data; } 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; @@ -1765,28 +1781,28 @@ export async function updateQuickNote (workspaceId: string, noteId: string, payl data: payload, }); - if (res?.data.code === 0) { + if(res?.data.code === 0) { return; } 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; message: string; }>(url); - if (res?.data.code === 0) { + if(res?.data.code === 0) { return; } 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; @@ -1798,14 +1814,14 @@ export async function cancelSubscription (workspaceId: string, plan: Subscriptio reason, }); - if (res?.data.code === 0) { + if(res?.data.code === 0) { return; } 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; @@ -1819,7 +1835,7 @@ export async function searchWorkspace (workspaceId: string, query: string) { }, }); - if (res?.data.code === 0) { + if(res?.data.code === 0) { return res?.data.data.map(item => item.object_id); } diff --git a/src/application/services/js-services/index.ts b/src/application/services/js-services/index.ts index a2560aa9..f08de032 100644 --- a/src/application/services/js-services/index.ts +++ b/src/application/services/js-services/index.ts @@ -29,11 +29,22 @@ import { UploadTemplatePayload, } from '@/application/template.type'; import { - CreatePagePayload, CreateSpacePayload, CreateWorkspacePayload, + CreatePagePayload, + CreateSpacePayload, + CreateWorkspacePayload, DatabaseRelations, - DuplicatePublishView, PublishViewPayload, QuickNoteEditorData, - SubscriptionInterval, SubscriptionPlan, - Types, UpdatePagePayload, UpdateSpacePayload, UpdateWorkspacePayload, UploadPublishNamespacePayload, WorkspaceMember, + DuplicatePublishView, + PublishViewPayload, + QuickNoteEditorData, + SubscriptionInterval, + SubscriptionPlan, + Types, + UpdatePagePayload, + UpdatePublishConfigPayload, + UpdateSpacePayload, + UpdateWorkspacePayload, + UploadPublishNamespacePayload, + WorkspaceMember, YjsEditorKey, } from '@/application/types'; import { applyYDoc } from '@/application/ydoc/apply'; @@ -205,6 +216,8 @@ export class AFClientService implements AFService { publisherEmail: string; viewId: string; publishedAt: string; + commentEnabled: boolean; + duplicateEnabled: boolean; }; } @@ -222,6 +235,8 @@ export class AFClientService implements AFService { publisherEmail: info.publisher_email, viewId: info.view_id, publishedAt: info.publish_timestamp, + commentEnabled: info.comments_enabled, + duplicateEnabled: info.duplicate_enabled, }; this.publishViewInfo.set(viewId, data); @@ -229,6 +244,10 @@ export class AFClientService implements AFService { return data; } + async updatePublishConfig (workspaceId: string, config: UpdatePublishConfigPayload) { + return APIService.updatePublishConfig(workspaceId, config); + } + async getPublishOutline (namespace: string) { return APIService.getPublishOutline(namespace); } diff --git a/src/application/services/services.type.ts b/src/application/services/services.type.ts index 25d9d30d..237d5608 100644 --- a/src/application/services/services.type.ts +++ b/src/application/services/services.type.ts @@ -24,7 +24,7 @@ import { CreateWorkspacePayload, UpdateWorkspacePayload, PublishViewPayload, - UploadPublishNamespacePayload, + UploadPublishNamespacePayload, UpdatePublishConfigPayload, } from '@/application/types'; import { GlobalComment, Reaction } from '@/application/comment.type'; import { ViewMeta } from '@/application/db/tables/view_metas'; @@ -153,8 +153,11 @@ export interface PublishService { namespace: string; publishName: string, publisherEmail: string, - publishedAt: string + publishedAt: string, + commentEnabled: boolean, + duplicateEnabled: boolean, }>; + updatePublishConfig: (workspaceId: string, payload: UpdatePublishConfigPayload) => Promise; getPublishNamespace: (namespace: string) => Promise; getPublishHomepage: (workspaceId: string) => Promise<{ view_id: string }>; updatePublishHomepage: (workspaceId: string, viewId: string) => Promise; diff --git a/src/application/types.ts b/src/application/types.ts index e86e8ccf..c1f899c8 100644 --- a/src/application/types.ts +++ b/src/application/types.ts @@ -852,6 +852,12 @@ export interface View { publish_timestamp?: string; } +export interface UpdatePublishConfigPayload { + comments_enabled?: boolean; + duplicate_enabled?: boolean; + view_id: string; +} + export interface Invitation { invite_id: string; workspace_id: string; diff --git a/src/assets/switch_checked.svg b/src/assets/switch_checked.svg new file mode 100644 index 00000000..538a5cd1 --- /dev/null +++ b/src/assets/switch_checked.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/assets/unswitch.svg b/src/assets/unswitch.svg new file mode 100644 index 00000000..be0ff903 --- /dev/null +++ b/src/assets/unswitch.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/components/_shared/switch/Switch.tsx b/src/components/_shared/switch/Switch.tsx new file mode 100644 index 00000000..1e3d83dd --- /dev/null +++ b/src/components/_shared/switch/Switch.tsx @@ -0,0 +1,65 @@ +import { styled, Switch as MUISwitch, SwitchProps } from '@mui/material'; + +export const Switch = styled((props: SwitchProps) => ( + +))(({ theme, size }) => ({ + width: size === 'small' ? 30 : 56, + height: size === 'small' ? 18 : 32, + padding: 0, + '& .MuiSwitch-switchBase': { + padding: 0, + margin: 2, + transitionDuration: '300ms', + '&.Mui-checked': { + transform: size === 'small' ? 'translateX(12px)' : 'translateX(16px)', + color: '#fff', + '& + .MuiSwitch-track': { + backgroundColor: 'var(--fill-default)', + opacity: 1, + border: 0, + ...theme.applyStyles('dark', { + backgroundColor: 'var(--fill-default)', + }), + }, + '&.Mui-disabled + .MuiSwitch-track': { + opacity: 0.5, + }, + }, + '&.Mui-focusVisible .MuiSwitch-thumb': { + color: 'var(--fill-default)', + border: '6px solid #fff', + }, + '&.Mui-disabled .MuiSwitch-thumb': { + color: theme.palette.grey[100], + ...theme.applyStyles('dark', { + color: theme.palette.grey[600], + }), + }, + '&.Mui-disabled + .MuiSwitch-track': { + opacity: 0.7, + ...theme.applyStyles('dark', { + opacity: 0.3, + }), + }, + }, + '& .MuiSwitch-thumb': { + boxSizing: 'border-box', + width: size === 'small' ? 14 : 20, + height: size === 'small' ? 14 : 20, + }, + '& .MuiSwitch-track': { + borderRadius: size === 'small' ? 9 : 16, + backgroundColor: '#E9E9EA', + opacity: 1, + transition: theme.transitions.create(['background-color'], { + duration: 500, + }), + ...theme.applyStyles('dark', { + backgroundColor: '#39393D', + }), + }, +})); \ No newline at end of file diff --git a/src/components/_shared/switch/index.ts b/src/components/_shared/switch/index.ts new file mode 100644 index 00000000..2e57f683 --- /dev/null +++ b/src/components/_shared/switch/index.ts @@ -0,0 +1 @@ +export * from './Switch'; \ No newline at end of file diff --git a/src/components/app/share/PublishPanel.tsx b/src/components/app/share/PublishPanel.tsx index 28ac6a5f..a2d20c61 100644 --- a/src/components/app/share/PublishPanel.tsx +++ b/src/components/app/share/PublishPanel.tsx @@ -10,6 +10,7 @@ import { useTranslation } from 'react-i18next'; import { ReactComponent as PublishIcon } from '@/assets/publish.svg'; import { ReactComponent as CheckboxCheckSvg } from '@/assets/check_filled.svg'; import { ReactComponent as CheckboxUncheckSvg } from '@/assets/uncheck.svg'; +import { Switch } from '@/components/_shared/switch'; function PublishPanel ({ viewId, opened, onClose }: { viewId: string; onClose: () => void; opened: boolean }) { const { t } = useTranslation(); @@ -25,10 +26,13 @@ function PublishPanel ({ viewId, opened, onClose }: { viewId: string; onClose: ( loading, isOwner, isPublisher, + updatePublishConfig, } = useLoadPublishInfo(viewId); const [unpublishLoading, setUnpublishLoading] = React.useState(false); const [publishLoading, setPublishLoading] = React.useState(false); const [visibleViewId, setVisibleViewId] = React.useState(undefined); + const [commentEnabled, setCommentEnabled] = React.useState(undefined); + const [duplicateEnabled, setDuplicateEnabled] = React.useState(undefined); useEffect(() => { if (opened) { @@ -36,6 +40,13 @@ function PublishPanel ({ viewId, opened, onClose }: { viewId: string; onClose: ( } }, [loadPublishInfo, opened]); + useEffect(() => { + if (opened && publishInfo) { + setCommentEnabled(publishInfo.commentEnabled); + setDuplicateEnabled(publishInfo.duplicateEnabled); + } + }, [opened, publishInfo]); + const handlePublish = useCallback(async (publishName?: string) => { if (!publish || !view) return; @@ -77,7 +88,7 @@ function PublishPanel ({ viewId, opened, onClose }: { viewId: string; onClose: ( const renderPublished = useCallback(() => { if (!publishInfo || !view) return null; - return
+ return
{t('shareAction.visitSite')}
+
+
+ {t('comments')} + { + setCommentEnabled(e.target.checked); + void updatePublishConfig({ comments_enabled: e.target.checked, view_id: viewId }); + }} + size={'small'} + /> +
+
+ {t('duplicateAsTemplate')} + { + setDuplicateEnabled(e.target.checked); + void updatePublishConfig({ duplicate_enabled: e.target.checked, view_id: viewId }); + }} + size={'small'} + /> + +
+
; - }, [handlePublish, handleUnpublish, isOwner, isPublisher, onClose, publishInfo, t, unpublishLoading, url, view]); + }, [publishInfo, view, url, handlePublish, handleUnpublish, isOwner, isPublisher, onClose, unpublishLoading, t, commentEnabled, duplicateEnabled, updatePublishConfig, viewId]); const layout = view?.layout; const isDatabase = layout !== undefined ? [ViewLayout.Grid, ViewLayout.Board, ViewLayout.Calendar].includes(layout) : false; @@ -212,7 +248,6 @@ function PublishPanel ({ viewId, opened, onClose }: { viewId: string; onClose: ( > {view?.is_published ? renderPublished() : renderUnpublished()} - ); } diff --git a/src/components/app/share/publish.hooks.ts b/src/components/app/share/publish.hooks.ts index 1f750bbc..55fc73a5 100644 --- a/src/components/app/share/publish.hooks.ts +++ b/src/components/app/share/publish.hooks.ts @@ -1,3 +1,5 @@ +import { UpdatePublishConfigPayload } from '@/application/types'; +import { notify } from '@/components/_shared/notify'; import { useAppView, useUserWorkspaceInfo } from '@/components/app/app.hooks'; import { useCurrentUser, useService } from '@/components/main/app.hooks'; import React, { useCallback, useEffect, useMemo } from 'react'; @@ -7,7 +9,9 @@ export function useLoadPublishInfo (viewId: string) { const [publishInfo, setPublishInfo] = React.useState<{ namespace: string, publishName: string, - publisherEmail: string + publisherEmail: string, + commentEnabled: boolean, + duplicateEnabled: boolean, }>(); const [loading, setLoading] = React.useState(false); const service = useService(); @@ -15,6 +19,7 @@ export function useLoadPublishInfo (viewId: string) { const userWorkspaceInfo = useUserWorkspaceInfo(); const currentUser = useCurrentUser(); const isOwner = userWorkspaceInfo?.selectedWorkspace?.owner?.uid.toString() === currentUser?.uid.toString(); + const workspaceId = userWorkspaceInfo?.selectedWorkspace?.id; const isPublisher = publishInfo?.publisherEmail === currentUser?.email; const loadPublishInfo = useCallback(async () => { @@ -37,9 +42,20 @@ export function useLoadPublishInfo (viewId: string) { void loadPublishInfo(); }, [loadPublishInfo]); + const updatePublishConfig = useCallback(async (payload: UpdatePublishConfigPayload) => { + if (!service || !workspaceId) return; + try { + await service.updatePublishConfig(workspaceId, payload); + // eslint-disable-next-line + } catch (e: any) { + notify.error(e.message); + } + + }, [service, workspaceId]); + const url = useMemo(() => { return `${window.origin}/${publishInfo?.namespace}/${publishInfo?.publishName}`; }, [publishInfo]); - return { publishInfo, url, loadPublishInfo, view, loading, isPublisher, isOwner }; + return { publishInfo, url, loadPublishInfo, view, loading, isPublisher, isOwner, updatePublishConfig }; } \ No newline at end of file diff --git a/src/components/publish/PublishMain.tsx b/src/components/publish/PublishMain.tsx index 6b92843b..4dd37fb4 100644 --- a/src/components/publish/PublishMain.tsx +++ b/src/components/publish/PublishMain.tsx @@ -1,3 +1,4 @@ +import { usePublishContext } from '@/application/publish'; import { YDoc } from '@/application/types'; import ComponentLoading from '@/components/_shared/progress/ComponentLoading'; import { GlobalCommentProvider } from '@/components/global-comment'; @@ -8,10 +9,12 @@ function PublishMain ({ doc, isTemplate }: { doc?: YDoc; isTemplate: boolean; }) { + const commentEnabled = usePublishContext()?.commentEnabled; + return ( <> - {doc && !isTemplate && ( + {doc && !isTemplate && commentEnabled && ( }> diff --git a/src/components/publish/PublishView.tsx b/src/components/publish/PublishView.tsx index 5364aa55..f97632b5 100644 --- a/src/components/publish/PublishView.tsx +++ b/src/components/publish/PublishView.tsx @@ -15,18 +15,18 @@ export interface PublishViewProps { publishName: string; } -export function PublishView ({ namespace, publishName }: PublishViewProps) { +export function PublishView({ namespace, publishName }: PublishViewProps) { const [doc, setDoc] = useState(); const [notFound, setNotFound] = useState(false); const service = useContext(AFConfigContext)?.service; - const openPublishView = useCallback(async () => { + const openPublishView = useCallback(async() => { let doc; setNotFound(false); setDoc(undefined); try { doc = await service?.getPublishView(namespace, publishName); - } catch (e) { + } catch(e) { setNotFound(true); return; } @@ -44,11 +44,12 @@ export function PublishView ({ namespace, publishName }: PublishViewProps) { const isTemplateThumb = isTemplate && search.get('thumbnail') === 'true'; useEffect(() => { - if (!isTemplateThumb) return; + if(!isTemplateThumb) return; + document.documentElement.setAttribute('thumbnail', 'true'); }, [isTemplateThumb]); - if (notFound && !doc) { + if(notFound && !doc) { return ; } diff --git a/src/components/publish/header/RightMenu.tsx b/src/components/publish/header/RightMenu.tsx index 66e51ac4..f6f5d746 100644 --- a/src/components/publish/header/RightMenu.tsx +++ b/src/components/publish/header/RightMenu.tsx @@ -5,15 +5,16 @@ import React, { useCallback, useContext } from 'react'; import { ReactComponent as Logo } from '@/assets/logo.svg'; import { Duplicate } from '@/components/publish/header/duplicate'; import { useTranslation } from 'react-i18next'; -import { PublishContext } from '@/application/publish'; +import { PublishContext, usePublishContext } from '@/application/publish'; import { useCurrentUser } from '@/components/main/app.hooks'; -import { ReactComponent as TemplateIcon } from '@/assets/template.svg'; +import { ReactComponent as TemplateIcon } from '@/assets/template.svg'; function RightMenu () { const { t } = useTranslation(); const viewMeta = useContext(PublishContext)?.viewMeta; const viewId = viewMeta?.view_id; const viewName = viewMeta?.name; + const duplicateEnabled = usePublishContext()?.duplicateEnabled; const handleTemplateClick = useCallback(() => { const url = `${window.origin}${window.location.pathname}`; @@ -28,10 +29,13 @@ function RightMenu () { return ( <> - + {duplicateEnabled && } {isAppFlowyUser && ( - +