diff --git a/frontend/appflowy_web_app/deploy/server.ts b/frontend/appflowy_web_app/deploy/server.ts index 46d81102bae1..35d5e09570a6 100644 --- a/frontend/appflowy_web_app/deploy/server.ts +++ b/frontend/appflowy_web_app/deploy/server.ts @@ -43,7 +43,13 @@ const logRequestTimer = (req: Request) => { }; }; -const fetchMetaData = async (url: string) => { +const fetchMetaData = async (namespace: string, publishName?: string) => { + let url = `${baseURL}/api/workspace/published/${namespace}`; + + if (publishName) { + url += `/${publishName}`; + } + logger.info(`Fetching meta data from ${url}`); try { const response = await fetch(url, { @@ -108,7 +114,7 @@ const createServer = async (req: Request) => { logger.info(`Namespace: ${namespace}, Publish Name: ${publishName}`); if (req.method === 'GET') { - if (namespace === '' || !publishName) { + if (namespace === '') { timer(); return new Response(null, { status: 302, @@ -121,7 +127,27 @@ const createServer = async (req: Request) => { let metaData; try { - metaData = await fetchMetaData(`${baseURL}/api/workspace/published/${namespace}/${publishName}`); + const data = await fetchMetaData(namespace, publishName); + + if (publishName) { + metaData = data; + } else { + + const publishInfo = data?.data?.info; + + if (publishInfo) { + const newURL = `/${publishInfo.namespace}/${publishInfo.publish_name}`; + + logger.info('Redirecting to default page: ', newURL); + timer(); + return new Response(null, { + status: 302, + headers: { + Location: newURL, + }, + }); + } + } } catch (error) { logger.error(`Error fetching meta data: ${error}`); } diff --git a/frontend/appflowy_web_app/public/og-image.png b/frontend/appflowy_web_app/public/og-image.png index 8b5996608ae3..a43f64a46eb9 100644 Binary files a/frontend/appflowy_web_app/public/og-image.png and b/frontend/appflowy_web_app/public/og-image.png differ diff --git a/frontend/appflowy_web_app/src/application/database-yjs/context.ts b/frontend/appflowy_web_app/src/application/database-yjs/context.ts index a5c873c92ddf..41ca2374931f 100644 --- a/frontend/appflowy_web_app/src/application/database-yjs/context.ts +++ b/frontend/appflowy_web_app/src/application/database-yjs/context.ts @@ -21,7 +21,7 @@ export interface DatabaseContextState { loadView?: LoadView; createRowDoc?: CreateRowDoc; loadViewMeta?: LoadViewMeta; - navigateToView?: (viewId: string) => Promise; + navigateToView?: (viewId: string, blockId?: string) => Promise; } export const DatabaseContext = createContext(null); diff --git a/frontend/appflowy_web_app/src/application/publish/context.tsx b/frontend/appflowy_web_app/src/application/publish/context.tsx index b80dccb2f346..a3c05f629ef8 100644 --- a/frontend/appflowy_web_app/src/application/publish/context.tsx +++ b/frontend/appflowy_web_app/src/application/publish/context.tsx @@ -21,7 +21,7 @@ export interface PublishContextType { isTemplate?: boolean; isTemplateThumb?: boolean; viewMeta?: ViewMeta; - toView: (viewId: string) => Promise; + toView: (viewId: string, blockId?: string) => Promise; loadViewMeta: LoadViewMeta; createRowDoc?: CreateRowDoc; loadView: LoadView; @@ -176,7 +176,7 @@ export const PublishProvider = ({ }, [service, publishName]); const navigate = useNavigate(); const toView = useCallback( - async (viewId: string) => { + async (viewId: string, blockId?: string) => { try { const res = await service?.getPublishInfo(viewId); @@ -187,7 +187,23 @@ export const PublishProvider = ({ const { namespace: viewNamespace, publishName } = res; prevViewMeta.current = undefined; - navigate(`/${viewNamespace}/${publishName}${isTemplate ? '?template=true' : ''}`, { + const searchParams = new URLSearchParams(''); + + if (blockId) { + searchParams.set('blockId', blockId); + } + + if (isTemplate) { + searchParams.set('template', 'true'); + } + + let url = `/${viewNamespace}/${publishName}`; + + if (searchParams.toString()) { + url += `?${searchParams.toString()}`; + } + + navigate(url, { replace: true, }); return; diff --git a/frontend/appflowy_web_app/src/application/services/js-services/http/http_api.ts b/frontend/appflowy_web_app/src/application/services/js-services/http/http_api.ts index bf51b34b1f52..a7276fdbd412 100644 --- a/frontend/appflowy_web_app/src/application/services/js-services/http/http_api.ts +++ b/frontend/appflowy_web_app/src/application/services/js-services/http/http_api.ts @@ -14,7 +14,7 @@ import { Subscriptions, SubscriptionPlan, SubscriptionInterval, - RequestAccessInfoStatus, + RequestAccessInfoStatus, ViewInfo, } from '@/application/types'; import { GlobalComment, Reaction } from '@/application/comment.type'; import { initGrantService, refreshToken } from '@/application/services/js-services/http/gotrue'; @@ -274,10 +274,22 @@ export async function getUserWorkspaceInfo (): Promise<{ } export async function getPublishViewMeta (namespace: string, publishName: string) { - const url = `/api/workspace/published/${namespace}/${publishName}`; - const response = await axiosInstance?.get(url); + const url = `/api/workspace/v1/published/${namespace}/${publishName}`; + const response = await axiosInstance?.get<{ + code: number; + data: { + view: ViewInfo; + child_views: ViewInfo[]; + ancestor_views: ViewInfo[]; + }; + message: string; + }>(url); - return response?.data; + if (response?.data.code !== 0) { + return Promise.reject(response?.data); + } + + return response?.data.data; } export async function getPublishViewBlob (namespace: string, publishName: string) { @@ -289,7 +301,7 @@ export async function getPublishViewBlob (namespace: string, publishName: string return blobToBytes(response?.data); } -export async function updateCollab (workspaceId: string, objectId: string, 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`; @@ -298,6 +310,7 @@ export async function updateCollab (workspaceId: string, objectId: string, docSt message: string; }>(url, { doc_state: Array.from(docState), + collab_type: collabType, }); if (response?.data.code !== 0) { @@ -1220,3 +1233,4 @@ export async function importFile (file: File, onProgress: (progress: number) => return Promise.reject(response?.data); } + diff --git a/frontend/appflowy_web_app/src/application/services/js-services/index.ts b/frontend/appflowy_web_app/src/application/services/js-services/index.ts index 0d04333289e8..87317c345831 100644 --- a/frontend/appflowy_web_app/src/application/services/js-services/index.ts +++ b/frontend/appflowy_web_app/src/application/services/js-services/index.ts @@ -459,7 +459,9 @@ export class AFClientService implements AFService { return APIService.getActiveSubscription(workspaceId); } - registerDocUpdate (doc: Y.Doc, workspaceId: string, objectId: string) { + registerDocUpdate (doc: Y.Doc, context: { + workspaceId: string, objectId: string, collabType: Types + }) { const token = getTokenParsed(); const userId = token?.user.id; @@ -467,7 +469,7 @@ export class AFClientService implements AFService { throw new Error('User not found'); } - const sync = new SyncManager(doc, userId, workspaceId, objectId); + const sync = new SyncManager(doc, { userId, ...context }); sync.initialize(); } diff --git a/frontend/appflowy_web_app/src/application/services/js-services/sync.ts b/frontend/appflowy_web_app/src/application/services/js-services/sync.ts index d39d2640fbcf..231d560581c9 100644 --- a/frontend/appflowy_web_app/src/application/services/js-services/sync.ts +++ b/frontend/appflowy_web_app/src/application/services/js-services/sync.ts @@ -1,5 +1,5 @@ import { updateCollab } from '@/application/services/js-services/http/http_api'; -import { CollabOrigin } from '@/application/types'; +import { CollabOrigin, Types } from '@/application/types'; import { debounce } from 'lodash-es'; import * as Y from 'yjs'; @@ -17,7 +17,9 @@ export class SyncManager { private isSending = false; - constructor (private doc: Y.Doc, private uuid: string, private workspaceId: string, private objectId: string) { + constructor (private doc: Y.Doc, private context: { + userId: string, workspaceId: string, objectId: string, collabType: Types + }) { this.versionVector = this.loadVersionVector(); this.hasUnsyncedChanges = this.loadUnsyncedFlag(); this.lastSyncedAt = this.loadLastSyncedAt(); @@ -33,7 +35,7 @@ export class SyncManager { } private getStorageKey (baseKey: string): string { - return `${this.uuid}_${baseKey}_${this.workspaceId}_${this.objectId}`; + return `${this.context.userId}_${baseKey}_${this.context.workspaceId}_${this.context.objectId}`; } private loadVersionVector (): number { @@ -81,7 +83,7 @@ export class SyncManager { const update = Y.encodeStateAsUpdate(this.doc); const context = { version_vector: this.versionVector }; - const response = await updateCollab(this.workspaceId, this.objectId, update, context); + const response = await updateCollab(this.context.workspaceId, this.context.objectId, this.context.collabType, update, context); if (response) { console.log(`Update sent successfully. Server version: ${response.version_vector}`); diff --git a/frontend/appflowy_web_app/src/application/services/services.type.ts b/frontend/appflowy_web_app/src/application/services/services.type.ts index 282915d84dd3..b4933bd87ee3 100644 --- a/frontend/appflowy_web_app/src/application/services/services.type.ts +++ b/frontend/appflowy_web_app/src/application/services/services.type.ts @@ -6,7 +6,7 @@ import { UserWorkspaceInfo, View, Workspace, - YDoc, DatabaseRelations, GetRequestAccessInfoResponse, Subscriptions, SubscriptionPlan, SubscriptionInterval, + YDoc, DatabaseRelations, GetRequestAccessInfoResponse, Subscriptions, SubscriptionPlan, SubscriptionInterval, Types, } from '@/application/types'; import { GlobalComment, Reaction } from '@/application/comment.type'; import { ViewMeta } from '@/application/db/tables/view_metas'; @@ -64,7 +64,9 @@ export interface AppService { getSubscriptionLink: (workspaceId: string, plan: SubscriptionPlan, interval: SubscriptionInterval) => Promise; getSubscriptions: () => Promise; getActiveSubscription: (workspaceId: string) => Promise; - registerDocUpdate: (doc: YDoc, workspaceId: string, objectId: string) => void; + registerDocUpdate: (doc: YDoc, context: { + workspaceId: string, objectId: string, collabType: Types + }) => void; importFile: (file: File, onProgress: (progress: number) => void) => Promise; } diff --git a/frontend/appflowy_web_app/src/application/services/tauri-services/index.ts b/frontend/appflowy_web_app/src/application/services/tauri-services/index.ts index c0a4d8e2205c..740a25eef75c 100644 --- a/frontend/appflowy_web_app/src/application/services/tauri-services/index.ts +++ b/frontend/appflowy_web_app/src/application/services/tauri-services/index.ts @@ -258,7 +258,7 @@ export class AFClientService implements AFService { return Promise.reject('Method not implemented'); } - registerDocUpdate (_doc: YDoc, _workspaceId: string, _objectId: string): void { + registerDocUpdate (): void { throw new Error('Method not implemented.'); } diff --git a/frontend/appflowy_web_app/src/application/slate-yjs/__tests__/convert.test.ts b/frontend/appflowy_web_app/src/application/slate-yjs/__tests__/convert.test.ts index 1d80680ec3df..21c43a1c5f9e 100644 --- a/frontend/appflowy_web_app/src/application/slate-yjs/__tests__/convert.test.ts +++ b/frontend/appflowy_web_app/src/application/slate-yjs/__tests__/convert.test.ts @@ -1,3 +1,4 @@ +import { YjsEditorKey, YSharedRoot } from '@/application/types'; import { generateId, getTestingDocData, insertBlock, withTestingYDoc } from './withTestingYjsEditor'; import { yDocToSlateContent, deltaInsertToSlateNode, yDataToSlateContent } from '@/application/slate-yjs/utils/convert'; import { expect } from '@jest/globals'; @@ -9,15 +10,16 @@ describe('convert yjs data to slate content', () => { it('should return undefined if root block is not exist', () => { const doc = new Y.Doc(); + const sharedRoot = doc.getMap(YjsEditorKey.data_section) as YSharedRoot; expect(yDocToSlateContent(doc)).toBeUndefined(); const doc2 = withTestingYDoc('1'); - const { blocks, childrenMap, textMap, pageId } = getTestingDocData(doc2); - expect(yDataToSlateContent({ blocks, rootId: '2', childrenMap, textMap })).toBeUndefined(); + const { blocks, pageId } = getTestingDocData(doc2); + expect(yDataToSlateContent(sharedRoot)).toBeUndefined(); blocks.delete(pageId); - expect(yDataToSlateContent({ blocks, rootId: pageId, childrenMap, textMap })).toBeUndefined(); + expect(yDataToSlateContent(sharedRoot)).toBeUndefined(); }); it('should match empty array', () => { const doc = withTestingYDoc('1'); diff --git a/frontend/appflowy_web_app/src/application/slate-yjs/command/index.ts b/frontend/appflowy_web_app/src/application/slate-yjs/command/index.ts index 2c62b80e54e9..ea7e8c07d81b 100644 --- a/frontend/appflowy_web_app/src/application/slate-yjs/command/index.ts +++ b/frontend/appflowy_web_app/src/application/slate-yjs/command/index.ts @@ -33,8 +33,21 @@ import { BasePoint, BaseRange, Editor, Element, Node, NodeEntry, Path, Range, Te import { ReactEditor } from 'slate-react'; export const CustomEditor = { + // find entry from blockId + getBlockEntry (editor: YjsEditor, blockId: string): NodeEntry | undefined { + const [entry] = editor.nodes({ + at: [], + match: (n) => !Editor.isEditor(n) && Element.isElement(n) && n.blockId === blockId, + }); + + if (!entry) { + return; + } + + return entry as NodeEntry; + }, // Get the text content of a block node, including the text content of its children and formula nodes - getBlockTextContent (node: Node): string { + getBlockTextContent (node: Node, depth: number = Infinity): string { if (Text.isText(node)) { if (node.formula) { return node.formula; @@ -56,7 +69,13 @@ export const CustomEditor = { return node.text || ''; } - return node.children.map((n) => CustomEditor.getBlockTextContent(n)).join(''); + if (depth <= 0) { + return ''; // Prevent infinite recursion + } + + return node.children + .map((n) => CustomEditor.getBlockTextContent(n, depth - 1)) + .join(''); }, setBlockData (editor: YjsEditor, blockId: string, updateData: T, select?: boolean) { @@ -75,10 +94,7 @@ export const CustomEditor = { const newProperties = { data: newData, } as Partial; - const [entry] = editor.nodes({ - at: [], - match: (n) => !Editor.isEditor(n) && Element.isElement(n) && n.blockId === blockId, - }); + const entry = CustomEditor.getBlockEntry(editor, blockId); if (!entry) { console.error('Block not found'); diff --git a/frontend/appflowy_web_app/src/application/slate-yjs/utils/convert.ts b/frontend/appflowy_web_app/src/application/slate-yjs/utils/convert.ts index b92ac70fd250..11b63f36bb76 100644 --- a/frontend/appflowy_web_app/src/application/slate-yjs/utils/convert.ts +++ b/frontend/appflowy_web_app/src/application/slate-yjs/utils/convert.ts @@ -5,115 +5,112 @@ import { getBlock, getText, updateBlockParent, + getTextMap, + getChildrenMap, + getBlocks, getPageId, } from '@/application/slate-yjs/utils/yjsOperations'; import { BlockData, BlockType, - YBlocks, - YChildrenMap, YDoc, YjsEditorKey, - YMeta, YSharedRoot, - YTextMap, } from '@/application/types'; import { TableCellNode } from '@/components/editor/editor.type'; import { Element, Text, Node } from 'slate'; -export function yDataToSlateContent ({ - blocks, - rootId, - childrenMap, - textMap, -}: { - blocks: YBlocks; - childrenMap: YChildrenMap; - textMap: YTextMap; - rootId: string; -}): Element | undefined { - function traverse (id: string) { - const block = blocks.get(id)?.toJSON() as BlockJson; - - if (!block) { - console.error('Block not found', id); - return; - } +export function traverseBlock (id: string, sharedRoot: YSharedRoot): Element | undefined { + const textMap = getTextMap(sharedRoot); + const childrenMap = getChildrenMap(sharedRoot); + const blocks = getBlocks(sharedRoot); + const block = blocks.get(id)?.toJSON() as BlockJson; - const childrenId = block.children as string; + if (!block) { + console.error('Block not found', id); + return; + } - const children = (childrenMap.get(childrenId)?.toJSON() ?? []).map(traverse).filter(Boolean) as (Element | Text)[]; + const childrenId = block.children as string; - const slateNode = blockToSlateNode(block); + const children = (childrenMap.get(childrenId)?.toJSON() ?? []).map((childId: string) => { + return traverseBlock(childId, sharedRoot); + }).filter(Boolean) as (Element | Text)[]; - if (slateNode.type === BlockType.TableBlock) { - slateNode.children = sortTableCells(children as TableCellNode[]); - } else if (slateNode.type === BlockType.TableCell) { - slateNode.children = children.slice(0, 1); - } else { - slateNode.children = children; - } + const slateNode = blockToSlateNode(block); - if (slateNode.type === BlockType.Page) { - return slateNode; - } + if (slateNode.type === BlockType.TableBlock) { + slateNode.children = sortTableCells(children as TableCellNode[]); + } else if (slateNode.type === BlockType.TableCell) { + slateNode.children = children.slice(0, 1); + } else { + slateNode.children = children; + } - let textId = block.external_id as string; + if (slateNode.type === BlockType.Page) { + return slateNode; + } - let delta; + let textId = block.external_id as string; - const yText = textId ? textMap.get(textId) : undefined; + let delta; - if (!yText) { + const yText = textId ? textMap.get(textId) : undefined; - if (children.length === 0) { - children.push({ - text: '', - }); - } + if (!yText) { - // Compatible data - // The old version of delta data is fully covered through the data field - if (slateNode.data) { - const data = slateNode.data as BlockData; - - if (YjsEditorKey.delta in data) { - textId = block.id; - delta = data.delta; - } else { - return slateNode; - } - } - } else { - delta = yText.toDelta(); + if (children.length === 0) { + children.push({ + text: '', + }); } - try { - const slateDelta = delta.flatMap(deltaInsertToSlateNode); + // Compatible data + // The old version of delta data is fully covered through the data field + if (slateNode.data) { + const data = slateNode.data as BlockData; - if (slateDelta.length === 0) { - slateDelta.push({ - text: '', - }); + if (YjsEditorKey.delta in data) { + textId = block.id; + delta = data.delta; + } else { + return slateNode; } + } + } else { + delta = yText.toDelta(); + } - const textNode: Element = { - textId, - type: YjsEditorKey.text, - children: slateDelta, - }; + try { + const slateDelta = delta.flatMap(deltaInsertToSlateNode); - children.unshift(textNode); - return slateNode; - } catch (e) { - return; + if (slateDelta.length === 0) { + slateDelta.push({ + text: '', + }); } + + const textNode: Element = { + textId, + type: YjsEditorKey.text, + children: slateDelta, + }; + + children.unshift(textNode); + return slateNode; + } catch (e) { + return; } +} + +export function yDataToSlateContent (sharedRoot: YSharedRoot): Element | undefined { + const rootId = getPageId(sharedRoot); + const blocks = getBlocks(sharedRoot); const root = blocks.get(rootId); if (!root) return; - const result = traverse(rootId); + const result = traverseBlock(rootId, sharedRoot); if (!result) return; @@ -124,20 +121,7 @@ export function yDocToSlateContent (doc: YDoc): Element | undefined { const sharedRoot = doc.getMap(YjsEditorKey.data_section) as YSharedRoot; if (!sharedRoot || sharedRoot.size === 0) return; - const document = sharedRoot.get(YjsEditorKey.document); - const pageId = document.get(YjsEditorKey.page_id) as string; - const blocks = document.get(YjsEditorKey.blocks) as YBlocks; - - const meta = document.get(YjsEditorKey.meta) as YMeta; - const childrenMap = meta.get(YjsEditorKey.children_map) as YChildrenMap; - const textMap = meta.get(YjsEditorKey.text_map) as YTextMap; - - return yDataToSlateContent({ - blocks, - rootId: pageId, - childrenMap, - textMap, - }); + return yDataToSlateContent(sharedRoot); } export function blockToSlateNode (block: BlockJson): Element { diff --git a/frontend/appflowy_web_app/src/application/types.ts b/frontend/appflowy_web_app/src/application/types.ts index 352721304997..8a07bc494e2c 100644 --- a/frontend/appflowy_web_app/src/application/types.ts +++ b/frontend/appflowy_web_app/src/application/types.ts @@ -154,11 +154,13 @@ export interface DatabaseNodeData extends BlockData { export enum MentionType { PageRef = 'page', Date = 'date', + childPage = 'childPage' } export interface Mention { // inline page ref id page_id?: string; + block_id?: string; // reminder date ref id date?: string; reminder_id?: string; @@ -294,6 +296,7 @@ export enum YjsDatabaseKey { show_week_numbers = 'show_week_numbers', show_weekends = 'show_weekends', layout_ty = 'layout_ty', + icon = 'icon', } export interface YDoc extends Y.Doc { @@ -596,6 +599,9 @@ export interface YDatabaseField extends Y.Map { get (key: YjsDatabaseKey.id): FieldId; + // eslint-disable-next-line @typescript-eslint/unified-signatures + get (key: YjsDatabaseKey.icon): string; + // eslint-disable-next-line @typescript-eslint/unified-signatures get (key: YjsDatabaseKey.type): string; @@ -644,7 +650,7 @@ export enum CollabOrigin { Local = 'local', // from remote changes and never sync to remote. Remote = 'remote', - + } export const layoutMap = { diff --git a/frontend/appflowy_web_app/src/assets/debug.svg b/frontend/appflowy_web_app/src/assets/debug.svg index 643edfbd23c3..5d3c014583a8 100644 --- a/frontend/appflowy_web_app/src/assets/debug.svg +++ b/frontend/appflowy_web_app/src/assets/debug.svg @@ -1,20 +1,22 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/frontend/appflowy_web_app/src/assets/message_support.svg b/frontend/appflowy_web_app/src/assets/message_support.svg index 03e72384d7f7..15a2871eaf58 100644 --- a/frontend/appflowy_web_app/src/assets/message_support.svg +++ b/frontend/appflowy_web_app/src/assets/message_support.svg @@ -1,12 +1,14 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/frontend/appflowy_web_app/src/assets/north_east.svg b/frontend/appflowy_web_app/src/assets/north_east.svg new file mode 100644 index 000000000000..c41ad9387861 --- /dev/null +++ b/frontend/appflowy_web_app/src/assets/north_east.svg @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/frontend/appflowy_web_app/src/assets/paragraph_mark.svg b/frontend/appflowy_web_app/src/assets/paragraph_mark.svg new file mode 100644 index 000000000000..f239fac17955 --- /dev/null +++ b/frontend/appflowy_web_app/src/assets/paragraph_mark.svg @@ -0,0 +1,4 @@ + + + diff --git a/frontend/appflowy_web_app/src/assets/star.svg b/frontend/appflowy_web_app/src/assets/star.svg index f4d6f7ae3cec..532abf0e2669 100644 --- a/frontend/appflowy_web_app/src/assets/star.svg +++ b/frontend/appflowy_web_app/src/assets/star.svg @@ -1,10 +1,11 @@ - - - - - - - - + + + + + + + + diff --git a/frontend/appflowy_web_app/src/components/_shared/view-icon/ViewIcon.tsx b/frontend/appflowy_web_app/src/components/_shared/view-icon/ViewIcon.tsx index 58a8bbdab9c7..c1360d9b7660 100644 --- a/frontend/appflowy_web_app/src/components/_shared/view-icon/ViewIcon.tsx +++ b/frontend/appflowy_web_app/src/components/_shared/view-icon/ViewIcon.tsx @@ -6,9 +6,10 @@ import { ReactComponent as DocumentSvg } from '@/assets/document.svg'; import { ReactComponent as GridSvg } from '@/assets/grid.svg'; import { ReactComponent as ChatSvg } from '@/assets/chat_ai.svg'; -export function ViewIcon ({ layout, size }: { +export function ViewIcon ({ layout, size, className }: { layout: ViewLayout; - size: number | 'small' | 'medium' | 'large' | 'unset' + size: number | 'small' | 'medium' | 'large' | 'unset', + className?: string; }) { const iconSize = useMemo(() => { if (size === 'small') { @@ -30,17 +31,21 @@ export function ViewIcon ({ layout, size }: { return `h-${size} w-${size}`; }, [size]); + const iconClassName = useMemo(() => { + return `${iconSize} ${className || ''}`; + }, [iconSize, className]); + switch (layout) { case ViewLayout.AIChat: - return ; + return ; case ViewLayout.Grid: - return ; + return ; case ViewLayout.Board: - return ; + return ; case ViewLayout.Calendar: - return ; + return ; case ViewLayout.Document: - return ; + return ; default: return null; } diff --git a/frontend/appflowy_web_app/src/components/app/DatabaseView.tsx b/frontend/appflowy_web_app/src/components/app/DatabaseView.tsx index 46d2ab7f95a9..d32f9c5106c8 100644 --- a/frontend/appflowy_web_app/src/components/app/DatabaseView.tsx +++ b/frontend/appflowy_web_app/src/components/app/DatabaseView.tsx @@ -22,7 +22,7 @@ import { useSearchParams } from 'react-router-dom'; function DatabaseView ({ viewMeta, ...props }: { doc: YDoc; - navigateToView?: (viewId: string) => Promise; + navigateToView?: (viewId: string, blockId?: string) => Promise; loadViewMeta?: LoadViewMeta; createRowDoc?: CreateRowDoc; loadView?: LoadView; diff --git a/frontend/appflowy_web_app/src/components/app/app.hooks.tsx b/frontend/appflowy_web_app/src/components/app/app.hooks.tsx index 0de618f4911a..e043ffae360b 100644 --- a/frontend/appflowy_web_app/src/components/app/app.hooks.tsx +++ b/frontend/appflowy_web_app/src/components/app/app.hooks.tsx @@ -4,7 +4,7 @@ import { CreateRowDoc, DatabaseRelations, LoadView, - LoadViewMeta, + LoadViewMeta, Types, UserWorkspaceInfo, View, ViewLayout, YjsDatabaseKey, YjsEditorKey, YSharedRoot, @@ -18,7 +18,7 @@ import { useNavigate, useParams } from 'react-router-dom'; import { validate as uuidValidate } from 'uuid'; export interface AppContextType { - toView: (viewId: string) => Promise; + toView: (viewId: string, blockId?: string, keepSearch?: boolean) => Promise; loadViewMeta: LoadViewMeta; createRowDoc?: CreateRowDoc; loadView: LoadView; @@ -135,11 +135,17 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { }); }, []); - const toView = useCallback(async (viewId: string, keepSearch?: boolean) => { + const toView = useCallback(async (viewId: string, blockId?: string, keepSearch?: boolean) => { let url = `/app/${currentWorkspaceId}/${viewId}`; - if (keepSearch) { - url += window.location.search; + const searchParams = new URLSearchParams(keepSearch ? window.location.search : undefined); + + if (blockId) { + searchParams.set('blockId', blockId); + } + + if (searchParams.toString()) { + url += `?${searchParams.toString()}`; } navigate(url); @@ -188,14 +194,20 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { const sharedRoot = res.get(YjsEditorKey.data_section) as YSharedRoot; let objectId = id; + let collabType = Types.Document; if (sharedRoot.has(YjsEditorKey.database)) { const database = sharedRoot.get(YjsEditorKey.database); objectId = database?.get(YjsDatabaseKey.id); + collabType = Types.Database; } - service.registerDocUpdate(res, currentWorkspaceId, objectId); + service.registerDocUpdate(res, { + workspaceId: currentWorkspaceId, + objectId, + collabType, + }); return res; // eslint-disable-next-line @@ -219,7 +231,11 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { throw new Error('Failed to create row doc'); } - service.registerDocUpdate(doc, currentWorkspaceId, rowKey); + service.registerDocUpdate(doc, { + workspaceId: currentWorkspaceId, + objectId: rowKey, + collabType: Types.DatabaseRow, + }); createdRowKeys.current.push(rowKey); return doc; diff --git a/frontend/appflowy_web_app/src/components/database/Database.tsx b/frontend/appflowy_web_app/src/components/database/Database.tsx index 7eef83b80337..b5c443a5b883 100644 --- a/frontend/appflowy_web_app/src/components/database/Database.tsx +++ b/frontend/appflowy_web_app/src/components/database/Database.tsx @@ -20,7 +20,7 @@ export interface Database2Props { doc: YDoc; createRowDoc?: CreateRowDoc; loadView?: LoadView; - navigateToView?: (viewId: string) => Promise; + navigateToView?: (viewId: string, blockId?: string) => Promise; loadViewMeta?: LoadViewMeta; viewId: string; iidName: string; @@ -108,7 +108,6 @@ function Database ({ useEffect(() => { onRendered?.(); }, [onRendered]); - if (!rowDocMap || !viewId) { return null; } @@ -129,7 +128,10 @@ function Database ({ createRowDoc={createRowDoc} > {rowId ? ( - + ) : (
{ switch (layout) { case DatabaseViewLayout.Grid: - return ; + return ; case DatabaseViewLayout.Board: - return ; + return ; case DatabaseViewLayout.Calendar: - return ; + return ; default: return null; } diff --git a/frontend/appflowy_web_app/src/components/database/components/database-row/DatabaseRowSubDocument.tsx b/frontend/appflowy_web_app/src/components/database/components/database-row/DatabaseRowSubDocument.tsx index c5d662ec7a80..2d4ef3a41698 100644 --- a/frontend/appflowy_web_app/src/components/database/components/database-row/DatabaseRowSubDocument.tsx +++ b/frontend/appflowy_web_app/src/components/database/components/database-row/DatabaseRowSubDocument.tsx @@ -39,10 +39,10 @@ export function DatabaseRowSubDocument ({ rowId }: { rowId: string }) { ); } - if (!doc) return null; + if (!doc || !documentId) return null; return ( - (null); + + const isDark = useContext(ThemeModeContext)?.isDark; const name = field?.get(YjsDatabaseKey.name); const type = useMemo(() => { const type = field?.get(YjsDatabaseKey.type); @@ -16,11 +21,24 @@ export function GridColumn ({ column, index }: { column: Column; index: number } return parseInt(type) as FieldType; }, [field]); + const icon = field?.get(YjsDatabaseKey.icon); + + useEffect(() => { + if (icon) { + void getIconSvgEncodedContent(icon, isDark ? 'white' : 'black').then((res) => { + setIconEncodeContent(res); + }); + } + }, [icon, isDark]); const isAIField = [FieldType.AISummaries, FieldType.AITranslations].includes(type); return ( - +
- + {iconEncodeContent ? {icon} : } +
{name}
{isAIField && } diff --git a/frontend/appflowy_web_app/src/components/database/components/grid/grid-header/GridHeader.tsx b/frontend/appflowy_web_app/src/components/database/components/grid/grid-header/GridHeader.tsx index d08b060d2d01..875249dd9ac7 100644 --- a/frontend/appflowy_web_app/src/components/database/components/grid/grid-header/GridHeader.tsx +++ b/frontend/appflowy_web_app/src/components/database/components/grid/grid-header/GridHeader.tsx @@ -20,12 +20,18 @@ const Cell = memo(({ columnIndex, style, data }: GridChildComponentProps) => { if (column.type === GridColumnType.Field) { return (
- +
); } - return
; + return
; }, areEqual); export const GridHeader = ({ scrollLeft, onScrollLeft, columnWidth, columns }: GridHeaderProps) => { diff --git a/frontend/appflowy_web_app/src/components/database/grid/Grid.tsx b/frontend/appflowy_web_app/src/components/database/grid/Grid.tsx index 2f7b0dc56c74..94cdedef92f3 100644 --- a/frontend/appflowy_web_app/src/components/database/grid/Grid.tsx +++ b/frontend/appflowy_web_app/src/components/database/grid/Grid.tsx @@ -24,7 +24,12 @@ export function Grid () { return (
- +
Promise; + navigateToView?: (viewId: string, blockId?: string) => Promise; loadViewMeta?: LoadViewMeta; loadView?: LoadView; createRowDoc?: CreateRowDoc; @@ -42,18 +42,20 @@ export const Document = ({ }, [setSearch]); const document = doc?.getMap(YjsEditorKey.data_section)?.get(YjsEditorKey.document); - if (!document) return null; + if (!document || !viewMeta.viewId) return null; return (
}>
; addCodeGrammars?: (blockId: string, grammar: string) => void; - navigateToView?: (viewId: string) => Promise; + navigateToView?: (viewId: string, blockId?: string) => Promise; loadViewMeta?: LoadViewMeta; loadView?: LoadView; createRowDoc?: CreateRowDoc; @@ -53,6 +54,7 @@ export const EditorContext = createContext({ readOnly: true, layoutStyle: defaultLayoutStyle, codeGrammars: {}, + viewId: '', }); export const EditorContextProvider = ({ children, ...props }: EditorContextState & { children: React.ReactNode }) => { diff --git a/frontend/appflowy_web_app/src/components/editor/__tests__/mount.tsx b/frontend/appflowy_web_app/src/components/editor/__tests__/mount.tsx index dac04098ca7e..6ebe9030adad 100644 --- a/frontend/appflowy_web_app/src/components/editor/__tests__/mount.tsx +++ b/frontend/appflowy_web_app/src/components/editor/__tests__/mount.tsx @@ -58,7 +58,7 @@ export const initialEditorTest = () => { const initializeEditor = (data: FromBlockJSON[]) => { documentTest = new DocumentTest(); documentTest.fromJSON(data); - mountEditor({ readOnly: false, doc: documentTest.doc }); + mountEditor({ readOnly: false, doc: documentTest.doc, viewId: 'test' }); cy.get('[role="textbox"]').should('exist'); }; diff --git a/frontend/appflowy_web_app/src/components/editor/components/blocks/code/Code.tsx b/frontend/appflowy_web_app/src/components/editor/components/blocks/code/Code.tsx index b26d735db55a..d74cc5c8f886 100644 --- a/frontend/appflowy_web_app/src/components/editor/components/blocks/code/Code.tsx +++ b/frontend/appflowy_web_app/src/components/editor/components/blocks/code/Code.tsx @@ -23,13 +23,22 @@ export const CodeBlock = memo( }} onMouseLeave={() => setShowToolbar(false)} > -
- +
+
-
+
             {children}
           
@@ -55,6 +64,6 @@ export const CodeBlock = memo(
); }), - (prevProps, nextProps) => JSON.stringify(prevProps.node) === JSON.stringify(nextProps.node) + (prevProps, nextProps) => JSON.stringify(prevProps.node) === JSON.stringify(nextProps.node), ); export default CodeBlock; diff --git a/frontend/appflowy_web_app/src/components/editor/components/element/Element.tsx b/frontend/appflowy_web_app/src/components/editor/components/element/Element.tsx index 83db4a296cae..0632c0612d07 100644 --- a/frontend/appflowy_web_app/src/components/editor/components/element/Element.tsx +++ b/frontend/appflowy_web_app/src/components/editor/components/element/Element.tsx @@ -42,6 +42,7 @@ export const Element = ({ } = useEditorContext(); const editor = useSlateStatic(); + const highlightTimeoutRef = React.useRef(); useEffect(() => { if (!jumpBlockId) return; @@ -57,10 +58,23 @@ export const Element = ({ behavior: 'smooth', scrollMode: 'if-needed', }); + element.className += ' highlight-block'; + highlightTimeoutRef.current = setTimeout(() => { + element.className = element.className.replace('highlight-block', ''); + }, 5000); + onJumpedBlockId?.(); })(); }, [editor, jumpBlockId, node, onJumpedBlockId]); + + useEffect(() => { + return () => { + if (highlightTimeoutRef.current) { + clearTimeout(highlightTimeoutRef.current); + } + }; + }, []); const Component = useMemo(() => { switch (node.type) { case BlockType.HeadingBlock: @@ -114,7 +128,7 @@ export const Element = ({ const data = (node.data as BlockData) || {}; const align = data.align; - return `block-element relative flex rounded ${align ? `block-align-${align}` : ''}`; + return `block-element relative flex rounded-[8px] ${align ? `block-align-${align}` : ''}`; }, [node.data]); const style = useMemo(() => { @@ -136,8 +150,14 @@ export const Element = ({ return ( -
- +
+ {children}
diff --git a/frontend/appflowy_web_app/src/components/editor/components/leaf/Leaf.tsx b/frontend/appflowy_web_app/src/components/editor/components/leaf/Leaf.tsx index c430a9575acd..165fb43a4f3c 100644 --- a/frontend/appflowy_web_app/src/components/editor/components/leaf/Leaf.tsx +++ b/frontend/appflowy_web_app/src/components/editor/components/leaf/Leaf.tsx @@ -55,12 +55,15 @@ export function Leaf ({ attributes, children, leaf, text }: RenderLeafProps) { const node = leaf.mention ? : leaf.formula ? : null; + /> : leaf.formula ? : null; newChildren = <> {node} {newChildren} @@ -68,7 +71,9 @@ export function Leaf ({ attributes, children, leaf, text }: RenderLeafProps) { } return ( - + {newChildren} ); diff --git a/frontend/appflowy_web_app/src/components/editor/components/leaf/mention/MentionLeaf.tsx b/frontend/appflowy_web_app/src/components/editor/components/leaf/mention/MentionLeaf.tsx index 2e07a4eadb15..efe7d225a32c 100644 --- a/frontend/appflowy_web_app/src/components/editor/components/leaf/mention/MentionLeaf.tsx +++ b/frontend/appflowy_web_app/src/components/editor/components/leaf/mention/MentionLeaf.tsx @@ -11,26 +11,33 @@ export function MentionLeaf ({ mention, text }: { text: Text; }) { const readonly = useReadOnly(); - const { type, date, page_id, reminder_id, reminder_option } = mention; + const { type, date, page_id, reminder_id, reminder_option, block_id } = mention; const reminder = useMemo(() => { return reminder_id ? { id: reminder_id ?? '', option: reminder_option ?? '' } : undefined; }, [reminder_id, reminder_option]); const content = useMemo(() => { - if (type === MentionType.PageRef && page_id) { - return ; + if ([MentionType.PageRef, MentionType.childPage].includes(type) && page_id) { + return ; } if (type === MentionType.Date && date) { - return ; + return ; } - }, [date, page_id, reminder, type]); + }, [date, page_id, reminder, type, block_id]); // check if the mention is selected const { isSelected, select, isCursorAfter, isCursorBefore } = useLeafSelected(text); const className = useMemo(() => { - const classList = ['w-fit mention', 'relative', 'rounded', 'p-0.5']; + const classList = ['w-fit mention', 'relative', 'rounded', 'py-0.5']; if (readonly) classList.push('cursor-default'); else classList.push('cursor-pointer'); @@ -43,7 +50,8 @@ export function MentionLeaf ({ mention, text }: { return {content} ; diff --git a/frontend/appflowy_web_app/src/components/editor/components/leaf/mention/MentionPage.tsx b/frontend/appflowy_web_app/src/components/editor/components/leaf/mention/MentionPage.tsx index 52ea13350244..f2de4b817c16 100644 --- a/frontend/appflowy_web_app/src/components/editor/components/leaf/mention/MentionPage.tsx +++ b/frontend/appflowy_web_app/src/components/editor/components/leaf/mention/MentionPage.tsx @@ -1,15 +1,25 @@ -import { ViewLayout, View } from '@/application/types'; +import { YjsEditor } from '@/application/slate-yjs'; +import { CustomEditor } from '@/application/slate-yjs/command'; +import { traverseBlock } from '@/application/slate-yjs/utils/convert'; +import { MentionType, View, ViewLayout, YjsEditorKey, YSharedRoot } from '@/application/types'; +import { ReactComponent as NorthEast } from '@/assets/north_east.svg'; +import { ReactComponent as MarkIcon } from '@/assets/paragraph_mark.svg'; + import { ViewIcon } from '@/components/_shared/view-icon'; import { useEditorContext } from '@/components/editor/EditorContext'; import { isFlagEmoji } from '@/utils/emoji'; import React, { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { useSlateStatic } from 'slate-react'; -function MentionPage ({ pageId }: { pageId: string }) { +function MentionPage ({ pageId, blockId, type }: { pageId: string; blockId?: string; type: MentionType }) { const context = useEditorContext(); - const { navigateToView, loadViewMeta } = context; + const editor = useSlateStatic(); + const currentViewId = context.viewId; + const { navigateToView, loadViewMeta, loadView } = context; const [noAccess, setNoAccess] = useState(false); const [meta, setMeta] = useState(null); + const [content, setContent] = useState(''); useEffect(() => { void (async () => { @@ -36,12 +46,74 @@ function MentionPage ({ pageId }: { pageId: string }) { return icon ? isFlagEmoji(icon.value) : false; }, [icon]); + useEffect(() => { + void ( + async () => { + const pageName = meta?.name || t('menuAppHeader.defaultNewPageName'); + + if (blockId) { + if (currentViewId === pageId) { + const entry = CustomEditor.getBlockEntry(editor as YjsEditor, blockId); + + if (entry) { + const [node] = entry; + + setContent(CustomEditor.getBlockTextContent(node, 2)); + return; + } + + } else { + try { + const otherDoc = await loadView?.(pageId); + + if (!otherDoc) return; + + const sharedRoot = otherDoc.getMap(YjsEditorKey.data_section) as YSharedRoot; + + const node = traverseBlock(blockId, sharedRoot); + + if (!node) return; + + setContent(`${pageName} - ${CustomEditor.getBlockTextContent(node, 2)}`); + return; + + } catch (e) { + // do nothing + } + } + + } + + setContent(pageName); + } + )(); + }, [blockId, currentViewId, editor, loadView, meta?.name, pageId, t]); + + const mentionIcon = useMemo(() => { + if (pageId === currentViewId && blockId) { + return ; + } + + return <> + {icon?.value || } + {type === MentionType.PageRef && + + + + } + ; + }, [blockId, currentViewId, icon?.value, meta?.layout, pageId, type]); + return ( { - void navigateToView?.(pageId); + void navigateToView?.(pageId, blockId); }} - className={`mention-inline cursor-pointer px-1 underline`} + className={`mention-inline cursor-pointer pr-1 underline`} contentEditable={false} data-mention-id={pageId} > @@ -50,13 +122,12 @@ function MentionPage ({ pageId }: { pageId: string }) { ) : ( <> - {icon?.value || } + {mentionIcon} - {meta?.name || t('menuAppHeader.defaultNewPageName')} + + {content} + )} diff --git a/frontend/appflowy_web_app/src/components/editor/editor.scss b/frontend/appflowy_web_app/src/components/editor/editor.scss index f505b5104d4e..69de74b9ab18 100644 --- a/frontend/appflowy_web_app/src/components/editor/editor.scss +++ b/frontend/appflowy_web_app/src/components/editor/editor.scss @@ -246,30 +246,13 @@ span[data-slate-placeholder="true"]:not(.inline-block-content) { @apply bg-transparent; } - //&:hover { - // .container-bg { - // background: var(--fill-list-hover) !important; - // } - //} } -[data-block-type='heading'] { - .mention-inline .mention-content { - @apply ml-7; - } - - .level-1, .level-2 { - .mention-inline .mention-content { - @apply ml-8; - } - } - -} .mention-inline { height: inherit; overflow: hidden; - @apply inline-flex gap-1 relative; + @apply inline-flex gap-1 relative truncate max-w-full; .mention-icon { @apply absolute top-1/2 transform -translate-y-1/2; @@ -277,7 +260,7 @@ span[data-slate-placeholder="true"]:not(.inline-block-content) { } .mention-content { - @apply ml-7; + @apply ml-[1.5em] truncate max-w-full flex-1; } } @@ -347,4 +330,18 @@ span[data-slate-placeholder="true"]:not(.inline-block-content) { 50% { opacity: 1; } +} + +.highlight-block { + animation: blink-bg 1s ease-in-out infinite; + +} + +@keyframes blink-bg { + from, to { + background-color: unset; + } + 50% { + background-color: var(--content-blue-100); + } } \ No newline at end of file diff --git a/frontend/appflowy_web_app/src/components/main/AppConfig.tsx b/frontend/appflowy_web_app/src/components/main/AppConfig.tsx index b87ede698863..541c2d4176e9 100644 --- a/frontend/appflowy_web_app/src/components/main/AppConfig.tsx +++ b/frontend/appflowy_web_app/src/components/main/AppConfig.tsx @@ -131,6 +131,7 @@ function AppConfig ({ children }: { children: React.ReactNode }) { isAuthenticated, currentUser, openLoginModal, + }} > {children} diff --git a/frontend/appflowy_web_app/src/components/publish/CollabView.tsx b/frontend/appflowy_web_app/src/components/publish/CollabView.tsx index d6a9a8fbdb7c..d0fa2fc639ec 100644 --- a/frontend/appflowy_web_app/src/components/publish/CollabView.tsx +++ b/frontend/appflowy_web_app/src/components/publish/CollabView.tsx @@ -40,7 +40,7 @@ function CollabView ({ doc }: CollabViewProps) { }, [layout]) as React.FC<{ doc: YDoc; readOnly: boolean; - navigateToView?: (viewId: string) => Promise; + navigateToView?: (viewId: string, blockId?: string) => Promise; loadViewMeta?: LoadViewMeta; createRowDoc?: CreateRowDoc; loadView?: LoadView; @@ -98,7 +98,10 @@ function CollabView ({ doc }: CollabViewProps) { return ( <> {rendered && - + }
Promise; + navigateToView?: (viewId: string, blockId?: string) => Promise; loadViewMeta?: LoadViewMeta; viewMeta: ViewMetaProps; appendBreadcrumb?: AppendBreadcrumb; diff --git a/frontend/appflowy_web_app/src/pages/AppPage.tsx b/frontend/appflowy_web_app/src/pages/AppPage.tsx index 33b45d3a0b75..0bc32ee30421 100644 --- a/frontend/appflowy_web_app/src/pages/AppPage.tsx +++ b/frontend/appflowy_web_app/src/pages/AppPage.tsx @@ -77,7 +77,7 @@ function AppPage () { }, [view?.layout]) as React.FC<{ doc: YDoc; readOnly: boolean; - navigateToView?: (viewId: string) => Promise; + navigateToView?: (viewId: string, blockId?: string) => Promise; loadViewMeta?: LoadViewMeta; createRowDoc?: CreateRowDoc; loadView?: LoadView; diff --git a/frontend/appflowy_web_app/src/styles/variables/dark.variables.css b/frontend/appflowy_web_app/src/styles/variables/dark.variables.css index 54191a737600..67ac29242106 100644 --- a/frontend/appflowy_web_app/src/styles/variables/dark.variables.css +++ b/frontend/appflowy_web_app/src/styles/variables/dark.variables.css @@ -25,6 +25,9 @@ --content-blue-600: #009fd1; --content-blue-100: #005174; --content-blue-50: #024562; + --content-blue-700: #0079a5; + --content-blue-800: #00597a; + --content-blue-900: #003d4d; --content-on-fill: #1a202c; --content-on-tag: #99a6b8; diff --git a/frontend/appflowy_web_app/src/styles/variables/light.variables.css b/frontend/appflowy_web_app/src/styles/variables/light.variables.css index 81ada54cf540..3d1bc63969ef 100644 --- a/frontend/appflowy_web_app/src/styles/variables/light.variables.css +++ b/frontend/appflowy_web_app/src/styles/variables/light.variables.css @@ -25,6 +25,9 @@ --content-blue-400: #00bcf0; --content-blue-300: #52d1f4; --content-blue-600: #009fd1; + --content-blue-700: #0079a5; + --content-blue-800: #00597a; + --content-blue-900: #003d4d; --content-blue-100: #e0f8ff; --content-blue-50: #f2fcff; --content-on-fill-hover: #00bcf0; diff --git a/frontend/appflowy_web_app/tailwind/box-shadow.cjs b/frontend/appflowy_web_app/tailwind/box-shadow.cjs index 89d02e180c9c..13984c1d1208 100644 --- a/frontend/appflowy_web_app/tailwind/box-shadow.cjs +++ b/frontend/appflowy_web_app/tailwind/box-shadow.cjs @@ -1,7 +1,7 @@ /** * Do not edit directly -* Generated on Mon, 16 Sep 2024 06:19:26 GMT +* Generated on Tue, 22 Oct 2024 09:59:50 GMT * Generated from $pnpm css:variables */ diff --git a/frontend/appflowy_web_app/tailwind/colors.cjs b/frontend/appflowy_web_app/tailwind/colors.cjs index aece3df56d01..15c4fbe9a5ab 100644 --- a/frontend/appflowy_web_app/tailwind/colors.cjs +++ b/frontend/appflowy_web_app/tailwind/colors.cjs @@ -1,7 +1,7 @@ /** * Do not edit directly -* Generated on Mon, 16 Sep 2024 06:19:26 GMT +* Generated on Tue, 22 Oct 2024 09:59:50 GMT * Generated from $pnpm css:variables */ @@ -41,6 +41,9 @@ module.exports = { "blue-400": "var(--content-blue-400)", "blue-300": "var(--content-blue-300)", "blue-600": "var(--content-blue-600)", + "blue-700": "var(--content-blue-700)", + "blue-800": "var(--content-blue-800)", + "blue-900": "var(--content-blue-900)", "blue-100": "var(--content-blue-100)", "blue-50": "var(--content-blue-50)", "on-fill-hover": "var(--content-on-fill-hover)",