From eee90f1a5c23a3474e752427e0012f4ce5120704 Mon Sep 17 00:00:00 2001 From: QiShaoXuan Date: Thu, 4 Aug 2022 19:18:45 +0800 Subject: [PATCH 01/44] feat: improved clipboard event determination conditions --- .../src/editor/clipboard/browser-clipboard.ts | 31 ++++++++++++------- .../src/editor/clipboard/clipboard-parse.ts | 6 ++-- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/libs/components/editor-core/src/editor/clipboard/browser-clipboard.ts b/libs/components/editor-core/src/editor/clipboard/browser-clipboard.ts index eefca42b0e..b3dab25ca3 100644 --- a/libs/components/editor-core/src/editor/clipboard/browser-clipboard.ts +++ b/libs/components/editor-core/src/editor/clipboard/browser-clipboard.ts @@ -17,6 +17,18 @@ import { MarkdownParser } from './markdown-parse'; // todo needs to be a switch const support_markdown_paste = true; +const filterNodes = ['INPUT', 'SELECT', 'TEXTAREA']; + +const shouldHandlerContinue = (event: Event, editor: Editor) => { + if (event.defaultPrevented) { + return false; + } + if (filterNodes.includes((event.target as HTMLElement)?.tagName)) { + return false; + } + + return editor.selectionManager.currentSelectInfo.type !== 'None'; +}; enum ClipboardAction { COPY = 'copy', @@ -72,8 +84,7 @@ class BrowserClipboard { } private handle_copy(e: Event) { - //@ts-ignore - if (e.defaultPrevented || e.target.nodeName === 'INPUT') { + if (!shouldHandlerContinue(e, this.editor)) { return; } @@ -84,33 +95,31 @@ class BrowserClipboard { } private handle_cut(e: Event) { - //@ts-ignore - if (e.defaultPrevented || e.target.nodeName === 'INPUT') { + if (!shouldHandlerContinue(e, this.editor)) { return; } + this.dispatch_clipboard_event(ClipboardAction.CUT, e as ClipboardEvent); } private handle_paste(e: Event) { - //@ts-ignore TODO should be handled more scientifically here, whether to trigger the paste time, also need some whitelist mechanism - if (e.defaultPrevented || e.target.nodeName === 'INPUT') { + if (!shouldHandlerContinue(e, this.editor)) { return; } + e.stopPropagation(); const clipboardData = (e as ClipboardEvent).clipboardData; const isPureFile = this.is_pure_file_in_clipboard(clipboardData); - if (!isPureFile) { - this.paste_content(clipboardData); - } else { + if (isPureFile) { this.paste_file(clipboardData); + } else { + this.paste_content(clipboardData); } // this.editor.selectionManager // .getSelectInfo() // .then(selectionInfo => console.log(selectionInfo)); - - e.stopPropagation(); } private paste_content(clipboardData: any) { diff --git a/libs/components/editor-core/src/editor/clipboard/clipboard-parse.ts b/libs/components/editor-core/src/editor/clipboard/clipboard-parse.ts index a4a59bd2ba..43b72ed6e7 100644 --- a/libs/components/editor-core/src/editor/clipboard/clipboard-parse.ts +++ b/libs/components/editor-core/src/editor/clipboard/clipboard-parse.ts @@ -115,10 +115,8 @@ export default class ClipboardParse { const block_utils = this.editor.getView( ClipboardParse.block_types[i] ); - const blocks = - block_utils && - block_utils.html2block && - block_utils.html2block(el, this.parse_dom); + const blocks = block_utils?.html2block?.(el, this.parse_dom); + if (blocks) { return blocks; } From 75083f1c75bebec7c670513ee9c7854533245d83 Mon Sep 17 00:00:00 2001 From: QiShaoXuan Date: Tue, 9 Aug 2022 14:54:59 +0800 Subject: [PATCH 02/44] style: update code style --- .../src/editor/clipboard/browser-clipboard.ts | 300 +++++++++--------- 1 file changed, 148 insertions(+), 152 deletions(-) diff --git a/libs/components/editor-core/src/editor/clipboard/browser-clipboard.ts b/libs/components/editor-core/src/editor/clipboard/browser-clipboard.ts index b3dab25ca3..6c6d4ad031 100644 --- a/libs/components/editor-core/src/editor/clipboard/browser-clipboard.ts +++ b/libs/components/editor-core/src/editor/clipboard/browser-clipboard.ts @@ -16,10 +16,11 @@ import { import { MarkdownParser } from './markdown-parse'; // todo needs to be a switch -const support_markdown_paste = true; -const filterNodes = ['INPUT', 'SELECT', 'TEXTAREA']; +const SUPPORT_MARKDOWN_PASTE = true; const shouldHandlerContinue = (event: Event, editor: Editor) => { + const filterNodes = ['INPUT', 'SELECT', 'TEXTAREA']; + if (event.defaultPrevented) { return false; } @@ -36,96 +37,94 @@ enum ClipboardAction { PASTE = 'paste', } class BrowserClipboard { - private event_target: Element; - private hooks: HooksRunner; - private editor: Editor; - private clipboard_parse: ClipboardParse; - private markdown_parse: MarkdownParser; + private _eventTarget: Element; + private _hooks: HooksRunner; + private _editor: Editor; + private _clipboardParse: ClipboardParse; + private _markdownParse: MarkdownParser; - private static optimal_mime_type: string[] = [ + private static _optimalMimeType: string[] = [ OFFICE_CLIPBOARD_MIMETYPE.DOCS_DOCUMENT_SLICE_CLIP_WRAPPED, OFFICE_CLIPBOARD_MIMETYPE.HTML, OFFICE_CLIPBOARD_MIMETYPE.TEXT, ]; constructor(eventTarget: Element, hooks: HooksRunner, editor: Editor) { - this.event_target = eventTarget; - this.hooks = hooks; - this.editor = editor; - this.clipboard_parse = new ClipboardParse(editor); - this.markdown_parse = new MarkdownParser(); - this.initialize(); + this._eventTarget = eventTarget; + this._hooks = hooks; + this._editor = editor; + this._clipboardParse = new ClipboardParse(editor); + this._markdownParse = new MarkdownParser(); + this._initialize(); } public getClipboardParse() { - return this.clipboard_parse; + return this._clipboardParse; } - private initialize() { - this.handle_copy = this.handle_copy.bind(this); - this.handle_cut = this.handle_cut.bind(this); - this.handle_paste = this.handle_paste.bind(this); + private _initialize() { + this._handleCopy = this._handleCopy.bind(this); + this._handleCut = this._handleCut.bind(this); + this._handlePaste = this._handlePaste.bind(this); - document.addEventListener(ClipboardAction.COPY, this.handle_copy); - document.addEventListener(ClipboardAction.CUT, this.handle_cut); - document.addEventListener(ClipboardAction.PASTE, this.handle_paste); - this.event_target.addEventListener( + document.addEventListener(ClipboardAction.COPY, this._handleCopy); + document.addEventListener(ClipboardAction.CUT, this._handleCut); + document.addEventListener(ClipboardAction.PASTE, this._handlePaste); + this._eventTarget.addEventListener( ClipboardAction.COPY, - this.handle_copy + this._handleCopy ); - this.event_target.addEventListener( + this._eventTarget.addEventListener( ClipboardAction.CUT, - this.handle_cut + this._handleCut ); - this.event_target.addEventListener( + this._eventTarget.addEventListener( ClipboardAction.PASTE, - this.handle_paste + this._handlePaste ); } - private handle_copy(e: Event) { - if (!shouldHandlerContinue(e, this.editor)) { + private _handleCopy(e: Event) { + if (!shouldHandlerContinue(e, this._editor)) { return; } - this.dispatch_clipboard_event( - ClipboardAction.COPY, - e as ClipboardEvent - ); + this._dispatchClipboardEvent(ClipboardAction.COPY, e as ClipboardEvent); } - private handle_cut(e: Event) { - if (!shouldHandlerContinue(e, this.editor)) { + private _handleCut(e: Event) { + if (!shouldHandlerContinue(e, this._editor)) { return; } - this.dispatch_clipboard_event(ClipboardAction.CUT, e as ClipboardEvent); + this._dispatchClipboardEvent(ClipboardAction.CUT, e as ClipboardEvent); } - private handle_paste(e: Event) { - if (!shouldHandlerContinue(e, this.editor)) { + private _handlePaste(e: Event) { + if (!shouldHandlerContinue(e, this._editor)) { return; } e.stopPropagation(); const clipboardData = (e as ClipboardEvent).clipboardData; - const isPureFile = this.is_pure_file_in_clipboard(clipboardData); + const isPureFile = this._isPureFileInClipboard(clipboardData); if (isPureFile) { - this.paste_file(clipboardData); + this._pasteFile(clipboardData); } else { - this.paste_content(clipboardData); + this._pasteContent(clipboardData); } - // this.editor.selectionManager + // this._editor.selectionManager // .getSelectInfo() // .then(selectionInfo => console.log(selectionInfo)); } - private paste_content(clipboardData: any) { + private _pasteContent(clipboardData: any) { const originClip: { data: any; type: any } = this.getOptimalClip( clipboardData ) as { data: any; type: any }; + const originTextClipData = clipboardData.getData( OFFICE_CLIPBOARD_MIMETYPE.TEXT ); @@ -133,19 +132,19 @@ class BrowserClipboard { let clipData = originClip['data']; if (originClip['type'] === OFFICE_CLIPBOARD_MIMETYPE.TEXT) { - clipData = this.excape_html(clipData); + clipData = this._excapeHtml(clipData); } switch (originClip['type']) { /** Protocol paste */ case OFFICE_CLIPBOARD_MIMETYPE.DOCS_DOCUMENT_SLICE_CLIP_WRAPPED: - this.fire_paste_edit_action(clipData); + this._firePasteEditAction(clipData); break; case OFFICE_CLIPBOARD_MIMETYPE.HTML: - this.paste_html(clipData, originTextClipData); + this._pasteHtml(clipData, originTextClipData); break; case OFFICE_CLIPBOARD_MIMETYPE.TEXT: - this.paste_text(clipData, originTextClipData); + this._pasteText(clipData, originTextClipData); break; default: @@ -153,14 +152,14 @@ class BrowserClipboard { } } - private paste_html(clipData: any, originTextClipData: any) { - if (support_markdown_paste) { - const has_markdown = - this.markdown_parse.checkIfTextContainsMd(originTextClipData); - if (has_markdown) { + private _pasteHtml(clipData: any, originTextClipData: any) { + if (SUPPORT_MARKDOWN_PASTE) { + const hasMarkdown = + this._markdownParse.checkIfTextContainsMd(originTextClipData); + if (hasMarkdown) { try { const convertedDataObj = - this.markdown_parse.md2Html(originTextClipData); + this._markdownParse.md2Html(originTextClipData); if (convertedDataObj.isConverted) { clipData = convertedDataObj.text; } @@ -171,23 +170,23 @@ class BrowserClipboard { } } - const blocks = this.clipboard_parse.html2blocks(clipData); + const blocks = this._clipboardParse.html2blocks(clipData); this.insert_blocks(blocks); } - private paste_text(clipData: any, originTextClipData: any) { - const blocks = this.clipboard_parse.text2blocks(clipData); + private _pasteText(clipData: any, originTextClipData: any) { + const blocks = this._clipboardParse.text2blocks(clipData); this.insert_blocks(blocks); } - private async paste_file(clipboardData: any) { - const file = this.get_image_file(clipboardData); + private async _pasteFile(clipboardData: any) { + const file = this._getImageFile(clipboardData); if (file) { const result = await services.api.file.create({ - workspace: this.editor.workspace, + workspace: this._editor.workspace, file: file, }); - const block_info: ClipBlockInfo = { + const blockInfo: ClipBlockInfo = { type: 'image', properties: { image: { @@ -199,11 +198,11 @@ class BrowserClipboard { }, children: [] as ClipBlockInfo[], }; - this.insert_blocks([block_info]); + this.insert_blocks([blockInfo]); } } - private get_image_file(clipboardData: any) { + private _getImageFile(clipboardData: any) { const files = clipboardData.files; if (files && files[0] && files[0].type.indexOf('image') > -1) { return files[0]; @@ -211,7 +210,7 @@ class BrowserClipboard { return; } - private excape_html(data: any, onlySpace?: any) { + private _excapeHtml(data: any, onlySpace?: any) { if (!onlySpace) { // TODO: // data = string.htmlEscape(data); @@ -224,7 +223,7 @@ class BrowserClipboard { } public getOptimalClip(clipboardData: any) { - const mimeTypeArr = BrowserClipboard.optimal_mime_type; + const mimeTypeArr = BrowserClipboard._optimalMimeType; for (let i = 0; i < mimeTypeArr.length; i++) { const data = @@ -242,24 +241,23 @@ class BrowserClipboard { return ''; } - private is_pure_file_in_clipboard(clipboardData: DataTransfer) { + private _isPureFileInClipboard(clipboardData: DataTransfer) { const types = clipboardData.types; - const res = + return ( (types.length === 1 && types[0] === 'Files') || (types.length === 2 && (types.includes('text/plain') || types.includes('text/html')) && - types.includes('Files')); - - return res; + types.includes('Files')) + ); } - private async fire_paste_edit_action(clipboardData: any) { - const clip_info: InnerClipInfo = JSON.parse(clipboardData); - clip_info && this.insert_blocks(clip_info.data, clip_info.select); + private async _firePasteEditAction(clipboardData: any) { + const clipInfo: InnerClipInfo = JSON.parse(clipboardData); + clipInfo && this.insert_blocks(clipInfo.data, clipInfo.select); } - private can_edit_text(type: BlockFlavorKeys) { + private _canEditText(type: BlockFlavorKeys) { return ( type === Protocol.Block.Type.page || type === Protocol.Block.Type.text || @@ -282,144 +280,142 @@ class BrowserClipboard { } const cur_select_info = - await this.editor.selectionManager.getSelectInfo(); + await this._editor.selectionManager.getSelectInfo(); if (cur_select_info.blocks.length === 0) { return; } - let begin_index = 0; - const cur_node_id = + let beginIndex = 0; + const curNodeId = cur_select_info.blocks[cur_select_info.blocks.length - 1].blockId; - let cur_block = await this.editor.getBlockById(cur_node_id); - const block_view = this.editor.getView(cur_block.type); + let curBlock = await this._editor.getBlockById(curNodeId); + const blockView = this._editor.getView(curBlock.type); if ( cur_select_info.type === 'Range' && - cur_block.type === 'text' && - block_view.isEmpty(cur_block) + curBlock.type === 'text' && + blockView.isEmpty(curBlock) ) { - cur_block.setType(blocks[0].type); - cur_block.setProperties(blocks[0].properties); - this.paste_children(cur_block, blocks[0].children); - begin_index = 1; + await curBlock.setType(blocks[0].type); + curBlock.setProperties(blocks[0].properties); + await this._pasteChildren(curBlock, blocks[0].children); + beginIndex = 1; } else if ( select?.type === 'Range' && cur_select_info.type === 'Range' && - this.can_edit_text(cur_block.type) && - this.can_edit_text(blocks[0].type) + this._canEditText(curBlock.type) && + this._canEditText(blocks[0].type) ) { if ( cur_select_info.blocks.length > 0 && cur_select_info.blocks[0].startInfo ) { - const start_info = cur_select_info.blocks[0].startInfo; - const end_info = cur_select_info.blocks[0].endInfo; - const cur_text_value = cur_block.getProperty('text').value; - const pre_cur_text_value = cur_text_value.slice( + const startInfo = cur_select_info.blocks[0].startInfo; + const endInfo = cur_select_info.blocks[0].endInfo; + const curTextValue = curBlock.getProperty('text').value; + const pre_curTextValue = curTextValue.slice( 0, - start_info.arrayIndex + startInfo.arrayIndex ); - const last_cur_text_value = cur_text_value.slice( - end_info.arrayIndex + 1 + const lastCurTextValue = curTextValue.slice( + endInfo.arrayIndex + 1 ); - const pre_text = cur_text_value[ - start_info.arrayIndex - ].text.substring(0, start_info.offset); - const last_text = cur_text_value[ - end_info.arrayIndex - ].text.substring(end_info.offset); + const preText = curTextValue[ + startInfo.arrayIndex + ].text.substring(0, startInfo.offset); + const lastText = curTextValue[ + endInfo.arrayIndex + ].text.substring(endInfo.offset); - let last_block: ClipBlockInfo = blocks[blocks.length - 1]; - if (!this.can_edit_text(last_block.type)) { - last_block = { type: 'text', children: [] }; - blocks.push(last_block); + let lastBlock: ClipBlockInfo = blocks[blocks.length - 1]; + if (!this._canEditText(lastBlock.type)) { + lastBlock = { type: 'text', children: [] }; + blocks.push(lastBlock); } - const last_values = last_block.properties?.text?.value; - last_text && last_values.push({ text: last_text }); - last_values.push(...last_cur_text_value); - last_block.properties = { - text: { value: last_values }, + const lastValues = lastBlock.properties?.text?.value; + lastText && lastValues.push({ text: lastText }); + lastValues.push(...lastCurTextValue); + lastBlock.properties = { + text: { value: lastValues }, }; - const insert_info = blocks[0].properties.text; - pre_text && pre_cur_text_value.push({ text: pre_text }); - pre_cur_text_value.push(...insert_info.value); - this.editor.blockHelper.setBlockBlur(cur_node_id); + const insertInfo = blocks[0].properties.text; + preText && pre_curTextValue.push({ text: preText }); + pre_curTextValue.push(...insertInfo.value); + this._editor.blockHelper.setBlockBlur(curNodeId); setTimeout(async () => { - const cur_block = await this.editor.getBlockById( - cur_node_id - ); - cur_block.setProperties({ - text: { value: pre_cur_text_value }, + const curBlock = await this._editor.getBlockById(curNodeId); + curBlock.setProperties({ + text: { value: pre_curTextValue }, }); - this.paste_children(cur_block, blocks[0].children); + await this._pasteChildren(curBlock, blocks[0].children); }, 0); - begin_index = 1; + beginIndex = 1; } } - for (let i = begin_index; i < blocks.length; i++) { - const next_block = await this.editor.createBlock(blocks[i].type); - next_block.setProperties(blocks[i].properties); - if (cur_block.type === 'page') { - cur_block.prepend(next_block); + for (let i = beginIndex; i < blocks.length; i++) { + const nextBlock = await this._editor.createBlock(blocks[i].type); + nextBlock.setProperties(blocks[i].properties); + if (curBlock.type === 'page') { + curBlock.prepend(nextBlock); } else { - cur_block.after(next_block); + curBlock.after(nextBlock); } - this.paste_children(next_block, blocks[i].children); - cur_block = next_block; + await this._pasteChildren(nextBlock, blocks[i].children); + curBlock = nextBlock; } } - private async paste_children(parent: AsyncBlock, children: any[]) { + private async _pasteChildren(parent: AsyncBlock, children: any[]) { for (let i = 0; i < children.length; i++) { - const next_block = await this.editor.createBlock(children[i].type); - next_block.setProperties(children[i].properties); - parent.append(next_block); - await this.paste_children(next_block, children[i].children); + const nextBlock = await this._editor.createBlock(children[i].type); + nextBlock.setProperties(children[i].properties); + await parent.append(nextBlock); + await this._pasteChildren(nextBlock, children[i].children); } } - private pre_copy_cut(action: ClipboardAction, e: ClipboardEvent) { + private _preCopyCut(action: ClipboardAction, e: ClipboardEvent) { switch (action) { case ClipboardAction.COPY: - this.hooks.beforeCopy(e); + this._hooks.beforeCopy(e); break; case ClipboardAction.CUT: - this.hooks.beforeCut(e); + this._hooks.beforeCut(e); break; } } - private dispatch_clipboard_event( + private _dispatchClipboardEvent( action: ClipboardAction, e: ClipboardEvent ) { - this.pre_copy_cut(action, e); + this._preCopyCut(action, e); } dispose() { - document.removeEventListener(ClipboardAction.COPY, this.handle_copy); - document.removeEventListener(ClipboardAction.CUT, this.handle_cut); - document.removeEventListener(ClipboardAction.PASTE, this.handle_paste); - this.event_target.removeEventListener( + document.removeEventListener(ClipboardAction.COPY, this._handleCopy); + document.removeEventListener(ClipboardAction.CUT, this._handleCut); + document.removeEventListener(ClipboardAction.PASTE, this._handlePaste); + this._eventTarget.removeEventListener( ClipboardAction.COPY, - this.handle_copy + this._handleCopy ); - this.event_target.removeEventListener( + this._eventTarget.removeEventListener( ClipboardAction.CUT, - this.handle_cut + this._handleCut ); - this.event_target.removeEventListener( + this._eventTarget.removeEventListener( ClipboardAction.PASTE, - this.handle_paste + this._handlePaste ); - this.clipboard_parse.dispose(); - this.clipboard_parse = null; - this.event_target = null; - this.hooks = null; - this.editor = null; + this._clipboardParse.dispose(); + this._clipboardParse = null; + this._eventTarget = null; + this._hooks = null; + this._editor = null; } } From 36ad39237b20da14f35ec904b56b33821ebd6b74 Mon Sep 17 00:00:00 2001 From: DiamondThree Date: Tue, 9 Aug 2022 18:41:08 +0800 Subject: [PATCH 03/44] feat: add selection function --- .../src/editor/selection/selection.ts | 49 +++++++++++++------ 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/libs/components/editor-core/src/editor/selection/selection.ts b/libs/components/editor-core/src/editor/selection/selection.ts index b66d1b5fd6..ad52fdd2d8 100644 --- a/libs/components/editor-core/src/editor/selection/selection.ts +++ b/libs/components/editor-core/src/editor/selection/selection.ts @@ -30,6 +30,7 @@ import { } from './types'; import { isLikeBlockListIds } from './utils'; import { Protocol } from '@toeverything/datasource/db-service'; +import { Editor } from 'slate'; // IMP: maybe merge active and select into single function export type SelectionInfo = InstanceType< @@ -321,16 +322,16 @@ export class SelectionManager implements VirgoSelection { if (selectionRect.isIntersect(domToRect(block.dom))) { const childrenBlocks = await block.children(); // should check directly in structured block - const structuredChildrenBlocks: Array = - childrenBlocks.filter(childBlock => { + const structuredChildrenBlocks: Array = childrenBlocks.filter( + childBlock => { return this._editor.getView(childBlock.type).layoutOnly; - }); + } + ); for await (const childBlock of structuredChildrenBlocks) { - const childSelectedNodes = - await this.calcRenderBlockIntersect( - selectionRect, - childBlock - ); + const childSelectedNodes = await this.calcRenderBlockIntersect( + selectionRect, + childBlock + ); selectedNodes.push(...childSelectedNodes); } const selectableChildren = childrenBlocks.filter(childBlock => { @@ -347,11 +348,10 @@ export class SelectionManager implements VirgoSelection { } // if just only has one selected maybe select the children if (selectedNodes.length === 1) { - const childrenSelectedNodes: Array = - await this.calcRenderBlockIntersect( - selectionRect, - selectedNodes[0] - ); + const childrenSelectedNodes: Array = await this.calcRenderBlockIntersect( + selectionRect, + selectedNodes[0] + ); if (childrenSelectedNodes.length) return childrenSelectedNodes; } @@ -584,8 +584,7 @@ export class SelectionManager implements VirgoSelection { } else { const new_node = await this._editor.getBlockById(newNodeId); if (new_node) { - const new_node_children_ids = - await new_node.childrenIds; + const new_node_children_ids = await new_node.childrenIds; let select_ids_new = this._selectedNodesIds; if ( new_node_children_ids && @@ -1050,4 +1049,24 @@ export class SelectionManager implements VirgoSelection { this._windowSelectionChangeHandler ); } + /** + * + * move active selection to the new position + * @param index:number + * @memberof SelectionManager + */ + public moveCursor(index: number, blockId: string): void { + const now_range = window.getSelection().getRangeAt(0); + let preRang = document.createRange(); + preRang.setStart( + now_range.startContainer, + now_range.startOffset + index + ); + preRang.setEnd(now_range.endContainer, now_range.endOffset + index); + let prePosition = preRang.getClientRects().item(0); + this.activeNodeByNodeId( + blockId, + new Point(prePosition.left, prePosition.bottom) + ); + } } From 1b337e97667478daf36f363f8b26e8ac4a6adfef Mon Sep 17 00:00:00 2001 From: DiamondThree Date: Tue, 9 Aug 2022 18:43:09 +0800 Subject: [PATCH 04/44] feat: add selection function --- libs/components/editor-core/src/editor/selection/selection.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/components/editor-core/src/editor/selection/selection.ts b/libs/components/editor-core/src/editor/selection/selection.ts index ad52fdd2d8..882d637b16 100644 --- a/libs/components/editor-core/src/editor/selection/selection.ts +++ b/libs/components/editor-core/src/editor/selection/selection.ts @@ -1052,7 +1052,8 @@ export class SelectionManager implements VirgoSelection { /** * * move active selection to the new position - * @param index:number + * @param {number} index + * @param {string} blockId * @memberof SelectionManager */ public moveCursor(index: number, blockId: string): void { From e795655bd94771696d41a2805219289d620bccc2 Mon Sep 17 00:00:00 2001 From: DiamondThree Date: Tue, 9 Aug 2022 18:57:41 +0800 Subject: [PATCH 05/44] fix:lint --- .../editor-core/src/editor/selection/selection.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/libs/components/editor-core/src/editor/selection/selection.ts b/libs/components/editor-core/src/editor/selection/selection.ts index 882d637b16..4b2da05cb6 100644 --- a/libs/components/editor-core/src/editor/selection/selection.ts +++ b/libs/components/editor-core/src/editor/selection/selection.ts @@ -1057,13 +1057,10 @@ export class SelectionManager implements VirgoSelection { * @memberof SelectionManager */ public moveCursor(index: number, blockId: string): void { - const now_range = window.getSelection().getRangeAt(0); + const nowRange = window.getSelection().getRangeAt(0); let preRang = document.createRange(); - preRang.setStart( - now_range.startContainer, - now_range.startOffset + index - ); - preRang.setEnd(now_range.endContainer, now_range.endOffset + index); + preRang.setStart(nowRange.startContainer, nowRange.startOffset + index); + preRang.setEnd(nowRange.endContainer, nowRange.endOffset + index); let prePosition = preRang.getClientRects().item(0); this.activeNodeByNodeId( blockId, From 0f49444c059cdd06c3af9be8db6287dd7f4c34b8 Mon Sep 17 00:00:00 2001 From: QiShaoXuan Date: Wed, 10 Aug 2022 13:32:29 +0800 Subject: [PATCH 06/44] style: update code style --- .../editor/clipboard/clipboard-populator.ts | 47 +++++++++---------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/libs/components/editor-core/src/editor/clipboard/clipboard-populator.ts b/libs/components/editor-core/src/editor/clipboard/clipboard-populator.ts index 4d28ce3cf8..2f05866ab9 100644 --- a/libs/components/editor-core/src/editor/clipboard/clipboard-populator.ts +++ b/libs/components/editor-core/src/editor/clipboard/clipboard-populator.ts @@ -46,7 +46,7 @@ class ClipboardPopulator { } // TODO: is not compatible with safari - const success = this.copy_to_cliboard_from_pc(clips); + const success = this._copyToClipboardFromPc(clips); if (!success) { // This way, not compatible with firefox const clipboardData = e.clipboardData; @@ -65,7 +65,7 @@ class ClipboardPopulator { } }; - private copy_to_cliboard_from_pc(clips: any[]) { + private _copyToClipboardFromPc(clips: any[]) { let success = false; const tempElem = document.createElement('textarea'); tempElem.value = 'temp'; @@ -96,56 +96,55 @@ class ClipboardPopulator { return success; } - private async get_clip_block_info(selBlock: SelectBlock) { + private async _getClipBlockInfo(selBlock: SelectBlock) { const block = await this._editor.getBlockById(selBlock.blockId); - const block_view = this._editor.getView(block.type); - assert(block_view); - const block_info: ClipBlockInfo = { + const blockView = this._editor.getView(block.type); + assert(blockView); + const blockInfo: ClipBlockInfo = { type: block.type, - properties: block_view.getSelProperties(block, selBlock), + properties: blockView.getSelProperties(block, selBlock), children: [] as any[], }; for (let i = 0; i < selBlock.children.length; i++) { - const child_info = await this.get_clip_block_info( + const childInfo = await this._getClipBlockInfo( selBlock.children[i] ); - block_info.children.push(child_info); + blockInfo.children.push(childInfo); } - return block_info; + return blockInfo; } - private async get_inner_clip(): Promise { + private async _getInnerClip(): Promise { const clips: ClipBlockInfo[] = []; - const select_info: SelectInfo = + const selectInfo: SelectInfo = await this._selectionManager.getSelectInfo(); - for (let i = 0; i < select_info.blocks.length; i++) { - const sel_block = select_info.blocks[i]; - const clip_block_info = await this.get_clip_block_info(sel_block); - clips.push(clip_block_info); + for (let i = 0; i < selectInfo.blocks.length; i++) { + const selBlock = selectInfo.blocks[i]; + const clipBlockInfo = await this._getClipBlockInfo(selBlock); + clips.push(clipBlockInfo); } - const clipInfo: InnerClipInfo = { - select: select_info, + return { + select: selectInfo, data: clips, }; - return clipInfo; } async getClips() { const clips: any[] = []; - const inner_clip = await this.get_inner_clip(); + const innerClip = await this._getInnerClip(); clips.push( new Clip( OFFICE_CLIPBOARD_MIMETYPE.DOCS_DOCUMENT_SLICE_CLIP_WRAPPED, - JSON.stringify(inner_clip) + JSON.stringify(innerClip) ) ); - const html_clip = await this._clipboardParse.generateHtml(); - html_clip && - clips.push(new Clip(OFFICE_CLIPBOARD_MIMETYPE.HTML, html_clip)); + const htmlClip = await this._clipboardParse.generateHtml(); + htmlClip && + clips.push(new Clip(OFFICE_CLIPBOARD_MIMETYPE.HTML, htmlClip)); return clips; } From 8183552011d04cb5221ad5a7d0acce441248d453 Mon Sep 17 00:00:00 2001 From: SaikaSakura Date: Wed, 10 Aug 2022 16:07:52 +0800 Subject: [PATCH 07/44] feat: fix backspeace --- .../src/blocks/text/TextView.tsx | 19 ++++++++++++------- .../src/menu/command-menu/Menu.tsx | 3 +++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/libs/components/editor-blocks/src/blocks/text/TextView.tsx b/libs/components/editor-blocks/src/blocks/text/TextView.tsx index cb408cb189..8d1726a9a9 100644 --- a/libs/components/editor-blocks/src/blocks/text/TextView.tsx +++ b/libs/components/editor-blocks/src/blocks/text/TextView.tsx @@ -112,13 +112,18 @@ export const TextView: FC = ({ block.id, 'end' ); - const value = [ - ...preNode.getProperty('text').value, - ...block.getProperty('text').value, - ]; - await preNode.setProperty('text', { - value, - }); + if ( + block.getProperty('text').value[0] && + block.getProperty('text').value[0]?.text !== '' + ) { + const value = [ + ...preNode.getProperty('text').value, + ...block.getProperty('text').value, + ]; + await preNode.setProperty('text', { + value, + }); + } await preNode.append(...children); await block.remove(); editor.suspend(false); diff --git a/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx b/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx index b6d174e513..fc5583a2ee 100644 --- a/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx +++ b/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx @@ -83,6 +83,9 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => { async (event: React.KeyboardEvent) => { const { type, anchorNode } = editor.selection.currentSelectInfo; // console.log(await editor.getBlockById(anchorNode.id)); + if (!anchorNode?.id) { + return; + } const activeBlock = await editor.getBlockById(anchorNode.id); if (activeBlock.type === Protocol.Block.Type.page) { return; From 9b0f608b6b2e713b5401f20983f1f60c83d40a98 Mon Sep 17 00:00:00 2001 From: DiamondThree Date: Thu, 11 Aug 2022 15:15:47 +0800 Subject: [PATCH 08/44] fix: add moveCursor function --- .../editor-core/src/editor/selection/selection.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/libs/components/editor-core/src/editor/selection/selection.ts b/libs/components/editor-core/src/editor/selection/selection.ts index 4b2da05cb6..99f648a930 100644 --- a/libs/components/editor-core/src/editor/selection/selection.ts +++ b/libs/components/editor-core/src/editor/selection/selection.ts @@ -1056,11 +1056,14 @@ export class SelectionManager implements VirgoSelection { * @param {string} blockId * @memberof SelectionManager */ - public moveCursor(index: number, blockId: string): void { - const nowRange = window.getSelection().getRangeAt(0); + public async moveCursor( + nowRange: any, + index: number, + blockId: string + ): Promise { let preRang = document.createRange(); - preRang.setStart(nowRange.startContainer, nowRange.startOffset + index); - preRang.setEnd(nowRange.endContainer, nowRange.endOffset + index); + preRang.setStart(nowRange.startContainer, index); + preRang.setEnd(nowRange.endContainer, index); let prePosition = preRang.getClientRects().item(0); this.activeNodeByNodeId( blockId, From 9ad7332b30eb31889016b4d829ae37967842bae7 Mon Sep 17 00:00:00 2001 From: DiamondThree Date: Thu, 11 Aug 2022 15:39:22 +0800 Subject: [PATCH 09/44] fix: lint --- .../src/components/text-manage/TextManage.tsx | 124 ++++++++---------- 1 file changed, 57 insertions(+), 67 deletions(-) diff --git a/libs/components/editor-blocks/src/components/text-manage/TextManage.tsx b/libs/components/editor-blocks/src/components/text-manage/TextManage.tsx index 2e146c7701..cc55e9b7b5 100644 --- a/libs/components/editor-blocks/src/components/text-manage/TextManage.tsx +++ b/libs/components/editor-blocks/src/components/text-manage/TextManage.tsx @@ -42,13 +42,13 @@ const TextBlockContainer = styled(Text)(({ theme }) => ({ })); const findSlice = (arr: string[], p: string, q: string) => { - let should_include = false; + let shouldInclude = false; return arr.filter(block => { if (block === p || block === q) { - should_include = !should_include; + shouldInclude = !shouldInclude; return true; } else { - return should_include; + return shouldInclude; } }); }; @@ -112,11 +112,8 @@ export const TextManage = forwardRef( (ref as MutableRefObject) || defaultRef; const properties = block.getProperties(); - // const [is_select, set_is_select] = useState(); - // useOnSelect(block.id, (is_select: boolean) => { - // set_is_select(is_select); - // }); - const on_text_view_set_selection = (selection: Range | Point) => { + + const onTextViewSetSelection = (selection: Range | Point) => { if (selection instanceof Point) { //do some thing } else { @@ -125,18 +122,18 @@ export const TextManage = forwardRef( }; // block = await editor.commands.blockCommands.createNextBlock(block.id,) - const on_text_view_active = useCallback( + const onTextViewActive = useCallback( (point: CursorTypes) => { // TODO code to be optimized if (textRef.current) { - const end_selection = textRef.current.getEndSelection(); - const start_selection = textRef.current.getStartSelection(); + const endSelection = textRef.current.getEndSelection(); + const startSelection = textRef.current.getStartSelection(); if (point === 'start') { - textRef.current.setSelection(start_selection); + textRef.current.setSelection(startSelection); return; } if (point === 'end') { - textRef.current.setSelection(end_selection); + textRef.current.setSelection(endSelection); return; } try { @@ -151,24 +148,24 @@ export const TextManage = forwardRef( } else { blockTop = blockDomStyle.top + 5; } - const end_position = ReactEditor.toDOMRange( + const endPosition = ReactEditor.toDOMRange( textRef.current.editor, - end_selection + endSelection ) .getClientRects() .item(0); - const start_position = ReactEditor.toDOMRange( + const startPosition = ReactEditor.toDOMRange( textRef.current.editor, - start_selection + startSelection ) .getClientRects() .item(0); - if (end_position.left <= point.x) { - textRef.current.setSelection(end_selection); + if (endPosition.left <= point.x) { + textRef.current.setSelection(endSelection); return; } - if (start_position.left >= point.x) { - textRef.current.setSelection(start_selection); + if (startPosition.left >= point.x) { + textRef.current.setSelection(startSelection); return; } let range: globalThis.Range; @@ -186,7 +183,7 @@ export const TextManage = forwardRef( range = document.createRange(); range.setStart(caret.offsetNode, caret.offset); } - const slate_rang = ReactEditor.toSlateRange( + const slateRang = ReactEditor.toSlateRange( textRef.current.editor, range, { @@ -194,19 +191,19 @@ export const TextManage = forwardRef( suppressThrow: true, } ); - textRef.current.setSelection(slate_rang); + textRef.current.setSelection(slateRang); } } catch (e) { console.log('e: ', e); - textRef.current.setSelection(end_selection); + textRef.current.setSelection(endSelection); } } }, [textRef] ); - useOnSelectActive(block.id, on_text_view_active); - useOnSelectSetSelection<'Range'>(block.id, on_text_view_set_selection); + useOnSelectActive(block.id, onTextViewActive); + useOnSelectSetSelection<'Range'>(block.id, onTextViewSetSelection); useEffect(() => { if (textRef.current) { @@ -232,17 +229,17 @@ export const TextManage = forwardRef( (block.id === lastSelectNodeId && type === 'Range') || (type === 'Range' && info) ) { - on_text_view_active('end'); + onTextViewActive('end'); } else { - on_text_view_active('start'); + onTextViewActive('start'); } } } catch (e) { console.warn('error occured in set active in initialization'); } - }, [block.id, editor.selectionManager, on_text_view_active, textRef]); + }, [block.id, editor.selectionManager, onTextViewActive, textRef]); - const on_text_change: TextProps['handleChange'] = async ( + const onTextChange: TextProps['handleChange'] = async ( value, textStyle ) => { @@ -263,39 +260,34 @@ export const TextManage = forwardRef( }); } }; - const get_now_and_pre_rang_position = () => { - window.getSelection().getRangeAt(0); - // const now_range = - // editor.selectionManager.currentSelectInfo?.browserSelection.getRangeAt( - // 0 - // ); - const now_range = window.getSelection().getRangeAt(0); - let pre_position = null; - const now_position = now_range.getClientRects().item(0); + const getNowAndPreRangPosition = () => { + const nowRange = window.getSelection().getRangeAt(0); + let prePosition = null; + const nowPosition = nowRange.getClientRects().item(0); try { - if (now_range.startOffset !== 0) { - const pre_rang = document.createRange(); - pre_rang.setStart( - now_range.startContainer, - now_range.startOffset + 1 + if (nowRange.startOffset !== 0) { + const preRang = document.createRange(); + preRang.setStart( + nowRange.startContainer, + nowRange.startOffset + 1 ); - pre_rang.setEnd( - now_range.endContainer, - now_range.endOffset + 1 + preRang.setEnd( + nowRange.endContainer, + nowRange.endOffset + 1 ); - pre_position = pre_rang.getClientRects().item(0); + prePosition = preRang.getClientRects().item(0); } } catch (e) { // console.log(e); } - return { nowPosition: now_position, prePosition: pre_position }; + return { nowPosition: nowPosition, prePosition: prePosition }; }; const onKeyboardUp = (event: React.KeyboardEvent) => { // if default event is prevented do noting // if U want to disable up/down/enter use capture event for preventing if (!event.isDefaultPrevented()) { - const positions = get_now_and_pre_rang_position(); + const positions = getNowAndPreRangPosition(); const prePosition = positions.prePosition; const nowPosition = positions.nowPosition; if (prePosition) { @@ -336,7 +328,7 @@ export const TextManage = forwardRef( // editor.selectionManager.activeNextNode(block.id, 'start'); // return; if (!event.isDefaultPrevented()) { - const positions = get_now_and_pre_rang_position(); + const positions = getNowAndPreRangPosition(); const prePosition = positions.prePosition; const nowPosition = positions.nowPosition; // Create the last element range of slate_editor @@ -392,7 +384,7 @@ export const TextManage = forwardRef( return false; } }; - const on_select_all = () => { + const onSelectAll = () => { const isSelectAll = textRef.current.isEmpty() || textRef.current.isSelectAll(); if (isSelectAll) { @@ -402,22 +394,20 @@ export const TextManage = forwardRef( return false; }; - const on_undo = () => { + const onUndo = () => { editor.undo(); }; - const on_redo = () => { + const onRedo = () => { editor.redo(); }; - const on_keyboard_esc = () => { + const onKeyboardEsc = () => { if (editor.selectionManager.getSelectedNodesIds().length === 0) { - const active_node_id = + const activeNodeId = editor.selectionManager.getActivatedNodeId(); - if (active_node_id) { - editor.selectionManager.setSelectedNodesIds([ - active_node_id, - ]); + if (activeNodeId) { + editor.selectionManager.setSelectedNodesIds([activeNodeId]); ReactEditor.blur(textRef.current.editor); } } else { @@ -425,7 +415,7 @@ export const TextManage = forwardRef( } }; - const on_shift_click = async (e: MouseEvent) => { + const onShiftClick = async (e: MouseEvent) => { if (e.shiftKey) { const activeId = editor.selectionManager.getActivatedNodeId(); if (activeId === block.id) { @@ -474,16 +464,16 @@ export const TextManage = forwardRef( className={`${otherOptions.className}`} currentValue={properties.text.value} textStyle={properties.textStyle} - handleChange={on_text_change} + handleChange={onTextChange} handleUp={onKeyboardUp} handleDown={onKeyboardDown} handleLeft={onKeyboardLeft} handleRight={onKeyboardRight} - handleSelectAll={on_select_all} - handleMouseDown={on_shift_click} - handleUndo={on_undo} - handleRedo={on_redo} - handleEsc={on_keyboard_esc} + handleSelectAll={onSelectAll} + handleMouseDown={onShiftClick} + handleUndo={onUndo} + handleRedo={onRedo} + handleEsc={onKeyboardEsc} {...otherOptions} /> ); From 0a265f9981533cfa6e2055dbd247613ab3964553 Mon Sep 17 00:00:00 2001 From: QiShaoXuan Date: Thu, 11 Aug 2022 17:06:16 +0800 Subject: [PATCH 10/44] refactor: refactor paste behavior (not support selection) --- .../clipboard/browser-clipboard-back.ts | 422 ++++++++++++++ .../src/editor/clipboard/browser-clipboard.ts | 322 +---------- .../editor-core/src/editor/clipboard/paste.ts | 535 ++++++++++++++++++ .../editor-core/src/editor/clipboard/utils.ts | 14 + 4 files changed, 991 insertions(+), 302 deletions(-) create mode 100644 libs/components/editor-core/src/editor/clipboard/browser-clipboard-back.ts create mode 100644 libs/components/editor-core/src/editor/clipboard/paste.ts create mode 100644 libs/components/editor-core/src/editor/clipboard/utils.ts diff --git a/libs/components/editor-core/src/editor/clipboard/browser-clipboard-back.ts b/libs/components/editor-core/src/editor/clipboard/browser-clipboard-back.ts new file mode 100644 index 0000000000..6c6d4ad031 --- /dev/null +++ b/libs/components/editor-core/src/editor/clipboard/browser-clipboard-back.ts @@ -0,0 +1,422 @@ +import { HooksRunner } from '../types'; +import { + OFFICE_CLIPBOARD_MIMETYPE, + InnerClipInfo, + ClipBlockInfo, +} from './types'; +import { Editor } from '../editor'; +import { AsyncBlock } from '../block'; +import ClipboardParse from './clipboard-parse'; +import { SelectInfo } from '../selection'; +import { + Protocol, + BlockFlavorKeys, + services, +} from '@toeverything/datasource/db-service'; +import { MarkdownParser } from './markdown-parse'; + +// todo needs to be a switch +const SUPPORT_MARKDOWN_PASTE = true; + +const shouldHandlerContinue = (event: Event, editor: Editor) => { + const filterNodes = ['INPUT', 'SELECT', 'TEXTAREA']; + + if (event.defaultPrevented) { + return false; + } + if (filterNodes.includes((event.target as HTMLElement)?.tagName)) { + return false; + } + + return editor.selectionManager.currentSelectInfo.type !== 'None'; +}; + +enum ClipboardAction { + COPY = 'copy', + CUT = 'cut', + PASTE = 'paste', +} +class BrowserClipboard { + private _eventTarget: Element; + private _hooks: HooksRunner; + private _editor: Editor; + private _clipboardParse: ClipboardParse; + private _markdownParse: MarkdownParser; + + private static _optimalMimeType: string[] = [ + OFFICE_CLIPBOARD_MIMETYPE.DOCS_DOCUMENT_SLICE_CLIP_WRAPPED, + OFFICE_CLIPBOARD_MIMETYPE.HTML, + OFFICE_CLIPBOARD_MIMETYPE.TEXT, + ]; + + constructor(eventTarget: Element, hooks: HooksRunner, editor: Editor) { + this._eventTarget = eventTarget; + this._hooks = hooks; + this._editor = editor; + this._clipboardParse = new ClipboardParse(editor); + this._markdownParse = new MarkdownParser(); + this._initialize(); + } + + public getClipboardParse() { + return this._clipboardParse; + } + + private _initialize() { + this._handleCopy = this._handleCopy.bind(this); + this._handleCut = this._handleCut.bind(this); + this._handlePaste = this._handlePaste.bind(this); + + document.addEventListener(ClipboardAction.COPY, this._handleCopy); + document.addEventListener(ClipboardAction.CUT, this._handleCut); + document.addEventListener(ClipboardAction.PASTE, this._handlePaste); + this._eventTarget.addEventListener( + ClipboardAction.COPY, + this._handleCopy + ); + this._eventTarget.addEventListener( + ClipboardAction.CUT, + this._handleCut + ); + this._eventTarget.addEventListener( + ClipboardAction.PASTE, + this._handlePaste + ); + } + + private _handleCopy(e: Event) { + if (!shouldHandlerContinue(e, this._editor)) { + return; + } + + this._dispatchClipboardEvent(ClipboardAction.COPY, e as ClipboardEvent); + } + + private _handleCut(e: Event) { + if (!shouldHandlerContinue(e, this._editor)) { + return; + } + + this._dispatchClipboardEvent(ClipboardAction.CUT, e as ClipboardEvent); + } + + private _handlePaste(e: Event) { + if (!shouldHandlerContinue(e, this._editor)) { + return; + } + e.stopPropagation(); + + const clipboardData = (e as ClipboardEvent).clipboardData; + + const isPureFile = this._isPureFileInClipboard(clipboardData); + + if (isPureFile) { + this._pasteFile(clipboardData); + } else { + this._pasteContent(clipboardData); + } + // this._editor.selectionManager + // .getSelectInfo() + // .then(selectionInfo => console.log(selectionInfo)); + } + + private _pasteContent(clipboardData: any) { + const originClip: { data: any; type: any } = this.getOptimalClip( + clipboardData + ) as { data: any; type: any }; + + const originTextClipData = clipboardData.getData( + OFFICE_CLIPBOARD_MIMETYPE.TEXT + ); + + let clipData = originClip['data']; + + if (originClip['type'] === OFFICE_CLIPBOARD_MIMETYPE.TEXT) { + clipData = this._excapeHtml(clipData); + } + + switch (originClip['type']) { + /** Protocol paste */ + case OFFICE_CLIPBOARD_MIMETYPE.DOCS_DOCUMENT_SLICE_CLIP_WRAPPED: + this._firePasteEditAction(clipData); + break; + case OFFICE_CLIPBOARD_MIMETYPE.HTML: + this._pasteHtml(clipData, originTextClipData); + break; + case OFFICE_CLIPBOARD_MIMETYPE.TEXT: + this._pasteText(clipData, originTextClipData); + break; + + default: + break; + } + } + + private _pasteHtml(clipData: any, originTextClipData: any) { + if (SUPPORT_MARKDOWN_PASTE) { + const hasMarkdown = + this._markdownParse.checkIfTextContainsMd(originTextClipData); + if (hasMarkdown) { + try { + const convertedDataObj = + this._markdownParse.md2Html(originTextClipData); + if (convertedDataObj.isConverted) { + clipData = convertedDataObj.text; + } + } catch (e) { + console.error(e); + clipData = originTextClipData; + } + } + } + + const blocks = this._clipboardParse.html2blocks(clipData); + this.insert_blocks(blocks); + } + + private _pasteText(clipData: any, originTextClipData: any) { + const blocks = this._clipboardParse.text2blocks(clipData); + this.insert_blocks(blocks); + } + + private async _pasteFile(clipboardData: any) { + const file = this._getImageFile(clipboardData); + if (file) { + const result = await services.api.file.create({ + workspace: this._editor.workspace, + file: file, + }); + const blockInfo: ClipBlockInfo = { + type: 'image', + properties: { + image: { + value: result.id, + name: file.name, + size: file.size, + type: file.type, + }, + }, + children: [] as ClipBlockInfo[], + }; + this.insert_blocks([blockInfo]); + } + } + + private _getImageFile(clipboardData: any) { + const files = clipboardData.files; + if (files && files[0] && files[0].type.indexOf('image') > -1) { + return files[0]; + } + return; + } + + private _excapeHtml(data: any, onlySpace?: any) { + if (!onlySpace) { + // TODO: + // data = string.htmlEscape(data); + // data = data.replace(/\n/g, '
'); + } + + // data = data.replace(/ /g, ' '); + // data = data.replace(/\t/g, '    '); + return data; + } + + public getOptimalClip(clipboardData: any) { + const mimeTypeArr = BrowserClipboard._optimalMimeType; + + for (let i = 0; i < mimeTypeArr.length; i++) { + const data = + clipboardData[mimeTypeArr[i]] || + clipboardData.getData(mimeTypeArr[i]); + + if (data) { + return { + type: mimeTypeArr[i], + data: data, + }; + } + } + + return ''; + } + + private _isPureFileInClipboard(clipboardData: DataTransfer) { + const types = clipboardData.types; + + return ( + (types.length === 1 && types[0] === 'Files') || + (types.length === 2 && + (types.includes('text/plain') || types.includes('text/html')) && + types.includes('Files')) + ); + } + + private async _firePasteEditAction(clipboardData: any) { + const clipInfo: InnerClipInfo = JSON.parse(clipboardData); + clipInfo && this.insert_blocks(clipInfo.data, clipInfo.select); + } + + private _canEditText(type: BlockFlavorKeys) { + return ( + type === Protocol.Block.Type.page || + type === Protocol.Block.Type.text || + type === Protocol.Block.Type.heading1 || + type === Protocol.Block.Type.heading2 || + type === Protocol.Block.Type.heading3 || + type === Protocol.Block.Type.quote || + type === Protocol.Block.Type.todo || + type === Protocol.Block.Type.code || + type === Protocol.Block.Type.callout || + type === Protocol.Block.Type.numbered || + type === Protocol.Block.Type.bullet + ); + } + + // TODO: cursor positioning problem + private async insert_blocks(blocks: ClipBlockInfo[], select?: SelectInfo) { + if (blocks.length === 0) { + return; + } + + const cur_select_info = + await this._editor.selectionManager.getSelectInfo(); + if (cur_select_info.blocks.length === 0) { + return; + } + + let beginIndex = 0; + const curNodeId = + cur_select_info.blocks[cur_select_info.blocks.length - 1].blockId; + let curBlock = await this._editor.getBlockById(curNodeId); + const blockView = this._editor.getView(curBlock.type); + if ( + cur_select_info.type === 'Range' && + curBlock.type === 'text' && + blockView.isEmpty(curBlock) + ) { + await curBlock.setType(blocks[0].type); + curBlock.setProperties(blocks[0].properties); + await this._pasteChildren(curBlock, blocks[0].children); + beginIndex = 1; + } else if ( + select?.type === 'Range' && + cur_select_info.type === 'Range' && + this._canEditText(curBlock.type) && + this._canEditText(blocks[0].type) + ) { + if ( + cur_select_info.blocks.length > 0 && + cur_select_info.blocks[0].startInfo + ) { + const startInfo = cur_select_info.blocks[0].startInfo; + const endInfo = cur_select_info.blocks[0].endInfo; + const curTextValue = curBlock.getProperty('text').value; + const pre_curTextValue = curTextValue.slice( + 0, + startInfo.arrayIndex + ); + const lastCurTextValue = curTextValue.slice( + endInfo.arrayIndex + 1 + ); + const preText = curTextValue[ + startInfo.arrayIndex + ].text.substring(0, startInfo.offset); + const lastText = curTextValue[ + endInfo.arrayIndex + ].text.substring(endInfo.offset); + + let lastBlock: ClipBlockInfo = blocks[blocks.length - 1]; + if (!this._canEditText(lastBlock.type)) { + lastBlock = { type: 'text', children: [] }; + blocks.push(lastBlock); + } + const lastValues = lastBlock.properties?.text?.value; + lastText && lastValues.push({ text: lastText }); + lastValues.push(...lastCurTextValue); + lastBlock.properties = { + text: { value: lastValues }, + }; + + const insertInfo = blocks[0].properties.text; + preText && pre_curTextValue.push({ text: preText }); + pre_curTextValue.push(...insertInfo.value); + this._editor.blockHelper.setBlockBlur(curNodeId); + setTimeout(async () => { + const curBlock = await this._editor.getBlockById(curNodeId); + curBlock.setProperties({ + text: { value: pre_curTextValue }, + }); + await this._pasteChildren(curBlock, blocks[0].children); + }, 0); + beginIndex = 1; + } + } + + for (let i = beginIndex; i < blocks.length; i++) { + const nextBlock = await this._editor.createBlock(blocks[i].type); + nextBlock.setProperties(blocks[i].properties); + if (curBlock.type === 'page') { + curBlock.prepend(nextBlock); + } else { + curBlock.after(nextBlock); + } + + await this._pasteChildren(nextBlock, blocks[i].children); + curBlock = nextBlock; + } + } + + private async _pasteChildren(parent: AsyncBlock, children: any[]) { + for (let i = 0; i < children.length; i++) { + const nextBlock = await this._editor.createBlock(children[i].type); + nextBlock.setProperties(children[i].properties); + await parent.append(nextBlock); + await this._pasteChildren(nextBlock, children[i].children); + } + } + + private _preCopyCut(action: ClipboardAction, e: ClipboardEvent) { + switch (action) { + case ClipboardAction.COPY: + this._hooks.beforeCopy(e); + break; + + case ClipboardAction.CUT: + this._hooks.beforeCut(e); + break; + } + } + + private _dispatchClipboardEvent( + action: ClipboardAction, + e: ClipboardEvent + ) { + this._preCopyCut(action, e); + } + + dispose() { + document.removeEventListener(ClipboardAction.COPY, this._handleCopy); + document.removeEventListener(ClipboardAction.CUT, this._handleCut); + document.removeEventListener(ClipboardAction.PASTE, this._handlePaste); + this._eventTarget.removeEventListener( + ClipboardAction.COPY, + this._handleCopy + ); + this._eventTarget.removeEventListener( + ClipboardAction.CUT, + this._handleCut + ); + this._eventTarget.removeEventListener( + ClipboardAction.PASTE, + this._handlePaste + ); + this._clipboardParse.dispose(); + this._clipboardParse = null; + this._eventTarget = null; + this._hooks = null; + this._editor = null; + } +} + +export { BrowserClipboard }; diff --git a/libs/components/editor-core/src/editor/clipboard/browser-clipboard.ts b/libs/components/editor-core/src/editor/clipboard/browser-clipboard.ts index 6c6d4ad031..64eecbc5ca 100644 --- a/libs/components/editor-core/src/editor/clipboard/browser-clipboard.ts +++ b/libs/components/editor-core/src/editor/clipboard/browser-clipboard.ts @@ -14,40 +14,24 @@ import { services, } from '@toeverything/datasource/db-service'; import { MarkdownParser } from './markdown-parse'; - +import { shouldHandlerContinue } from './utils'; +import { Paste } from './paste'; // todo needs to be a switch -const SUPPORT_MARKDOWN_PASTE = true; - -const shouldHandlerContinue = (event: Event, editor: Editor) => { - const filterNodes = ['INPUT', 'SELECT', 'TEXTAREA']; - - if (event.defaultPrevented) { - return false; - } - if (filterNodes.includes((event.target as HTMLElement)?.tagName)) { - return false; - } - - return editor.selectionManager.currentSelectInfo.type !== 'None'; -}; enum ClipboardAction { COPY = 'copy', CUT = 'cut', PASTE = 'paste', } + +//TODO: need to consider the cursor position after inserting the children class BrowserClipboard { private _eventTarget: Element; private _hooks: HooksRunner; private _editor: Editor; private _clipboardParse: ClipboardParse; private _markdownParse: MarkdownParser; - - private static _optimalMimeType: string[] = [ - OFFICE_CLIPBOARD_MIMETYPE.DOCS_DOCUMENT_SLICE_CLIP_WRAPPED, - OFFICE_CLIPBOARD_MIMETYPE.HTML, - OFFICE_CLIPBOARD_MIMETYPE.TEXT, - ]; + private _paste: Paste; constructor(eventTarget: Element, hooks: HooksRunner, editor: Editor) { this._eventTarget = eventTarget; @@ -55,6 +39,11 @@ class BrowserClipboard { this._editor = editor; this._clipboardParse = new ClipboardParse(editor); this._markdownParse = new MarkdownParser(); + this._paste = new Paste( + editor, + this._clipboardParse, + this._markdownParse + ); this._initialize(); } @@ -65,11 +54,13 @@ class BrowserClipboard { private _initialize() { this._handleCopy = this._handleCopy.bind(this); this._handleCut = this._handleCut.bind(this); - this._handlePaste = this._handlePaste.bind(this); document.addEventListener(ClipboardAction.COPY, this._handleCopy); document.addEventListener(ClipboardAction.CUT, this._handleCut); - document.addEventListener(ClipboardAction.PASTE, this._handlePaste); + document.addEventListener( + ClipboardAction.PASTE, + this._paste.handlePaste + ); this._eventTarget.addEventListener( ClipboardAction.COPY, this._handleCopy @@ -80,7 +71,7 @@ class BrowserClipboard { ); this._eventTarget.addEventListener( ClipboardAction.PASTE, - this._handlePaste + this._paste.handlePaste ); } @@ -100,282 +91,6 @@ class BrowserClipboard { this._dispatchClipboardEvent(ClipboardAction.CUT, e as ClipboardEvent); } - private _handlePaste(e: Event) { - if (!shouldHandlerContinue(e, this._editor)) { - return; - } - e.stopPropagation(); - - const clipboardData = (e as ClipboardEvent).clipboardData; - - const isPureFile = this._isPureFileInClipboard(clipboardData); - - if (isPureFile) { - this._pasteFile(clipboardData); - } else { - this._pasteContent(clipboardData); - } - // this._editor.selectionManager - // .getSelectInfo() - // .then(selectionInfo => console.log(selectionInfo)); - } - - private _pasteContent(clipboardData: any) { - const originClip: { data: any; type: any } = this.getOptimalClip( - clipboardData - ) as { data: any; type: any }; - - const originTextClipData = clipboardData.getData( - OFFICE_CLIPBOARD_MIMETYPE.TEXT - ); - - let clipData = originClip['data']; - - if (originClip['type'] === OFFICE_CLIPBOARD_MIMETYPE.TEXT) { - clipData = this._excapeHtml(clipData); - } - - switch (originClip['type']) { - /** Protocol paste */ - case OFFICE_CLIPBOARD_MIMETYPE.DOCS_DOCUMENT_SLICE_CLIP_WRAPPED: - this._firePasteEditAction(clipData); - break; - case OFFICE_CLIPBOARD_MIMETYPE.HTML: - this._pasteHtml(clipData, originTextClipData); - break; - case OFFICE_CLIPBOARD_MIMETYPE.TEXT: - this._pasteText(clipData, originTextClipData); - break; - - default: - break; - } - } - - private _pasteHtml(clipData: any, originTextClipData: any) { - if (SUPPORT_MARKDOWN_PASTE) { - const hasMarkdown = - this._markdownParse.checkIfTextContainsMd(originTextClipData); - if (hasMarkdown) { - try { - const convertedDataObj = - this._markdownParse.md2Html(originTextClipData); - if (convertedDataObj.isConverted) { - clipData = convertedDataObj.text; - } - } catch (e) { - console.error(e); - clipData = originTextClipData; - } - } - } - - const blocks = this._clipboardParse.html2blocks(clipData); - this.insert_blocks(blocks); - } - - private _pasteText(clipData: any, originTextClipData: any) { - const blocks = this._clipboardParse.text2blocks(clipData); - this.insert_blocks(blocks); - } - - private async _pasteFile(clipboardData: any) { - const file = this._getImageFile(clipboardData); - if (file) { - const result = await services.api.file.create({ - workspace: this._editor.workspace, - file: file, - }); - const blockInfo: ClipBlockInfo = { - type: 'image', - properties: { - image: { - value: result.id, - name: file.name, - size: file.size, - type: file.type, - }, - }, - children: [] as ClipBlockInfo[], - }; - this.insert_blocks([blockInfo]); - } - } - - private _getImageFile(clipboardData: any) { - const files = clipboardData.files; - if (files && files[0] && files[0].type.indexOf('image') > -1) { - return files[0]; - } - return; - } - - private _excapeHtml(data: any, onlySpace?: any) { - if (!onlySpace) { - // TODO: - // data = string.htmlEscape(data); - // data = data.replace(/\n/g, '
'); - } - - // data = data.replace(/ /g, ' '); - // data = data.replace(/\t/g, '    '); - return data; - } - - public getOptimalClip(clipboardData: any) { - const mimeTypeArr = BrowserClipboard._optimalMimeType; - - for (let i = 0; i < mimeTypeArr.length; i++) { - const data = - clipboardData[mimeTypeArr[i]] || - clipboardData.getData(mimeTypeArr[i]); - - if (data) { - return { - type: mimeTypeArr[i], - data: data, - }; - } - } - - return ''; - } - - private _isPureFileInClipboard(clipboardData: DataTransfer) { - const types = clipboardData.types; - - return ( - (types.length === 1 && types[0] === 'Files') || - (types.length === 2 && - (types.includes('text/plain') || types.includes('text/html')) && - types.includes('Files')) - ); - } - - private async _firePasteEditAction(clipboardData: any) { - const clipInfo: InnerClipInfo = JSON.parse(clipboardData); - clipInfo && this.insert_blocks(clipInfo.data, clipInfo.select); - } - - private _canEditText(type: BlockFlavorKeys) { - return ( - type === Protocol.Block.Type.page || - type === Protocol.Block.Type.text || - type === Protocol.Block.Type.heading1 || - type === Protocol.Block.Type.heading2 || - type === Protocol.Block.Type.heading3 || - type === Protocol.Block.Type.quote || - type === Protocol.Block.Type.todo || - type === Protocol.Block.Type.code || - type === Protocol.Block.Type.callout || - type === Protocol.Block.Type.numbered || - type === Protocol.Block.Type.bullet - ); - } - - // TODO: cursor positioning problem - private async insert_blocks(blocks: ClipBlockInfo[], select?: SelectInfo) { - if (blocks.length === 0) { - return; - } - - const cur_select_info = - await this._editor.selectionManager.getSelectInfo(); - if (cur_select_info.blocks.length === 0) { - return; - } - - let beginIndex = 0; - const curNodeId = - cur_select_info.blocks[cur_select_info.blocks.length - 1].blockId; - let curBlock = await this._editor.getBlockById(curNodeId); - const blockView = this._editor.getView(curBlock.type); - if ( - cur_select_info.type === 'Range' && - curBlock.type === 'text' && - blockView.isEmpty(curBlock) - ) { - await curBlock.setType(blocks[0].type); - curBlock.setProperties(blocks[0].properties); - await this._pasteChildren(curBlock, blocks[0].children); - beginIndex = 1; - } else if ( - select?.type === 'Range' && - cur_select_info.type === 'Range' && - this._canEditText(curBlock.type) && - this._canEditText(blocks[0].type) - ) { - if ( - cur_select_info.blocks.length > 0 && - cur_select_info.blocks[0].startInfo - ) { - const startInfo = cur_select_info.blocks[0].startInfo; - const endInfo = cur_select_info.blocks[0].endInfo; - const curTextValue = curBlock.getProperty('text').value; - const pre_curTextValue = curTextValue.slice( - 0, - startInfo.arrayIndex - ); - const lastCurTextValue = curTextValue.slice( - endInfo.arrayIndex + 1 - ); - const preText = curTextValue[ - startInfo.arrayIndex - ].text.substring(0, startInfo.offset); - const lastText = curTextValue[ - endInfo.arrayIndex - ].text.substring(endInfo.offset); - - let lastBlock: ClipBlockInfo = blocks[blocks.length - 1]; - if (!this._canEditText(lastBlock.type)) { - lastBlock = { type: 'text', children: [] }; - blocks.push(lastBlock); - } - const lastValues = lastBlock.properties?.text?.value; - lastText && lastValues.push({ text: lastText }); - lastValues.push(...lastCurTextValue); - lastBlock.properties = { - text: { value: lastValues }, - }; - - const insertInfo = blocks[0].properties.text; - preText && pre_curTextValue.push({ text: preText }); - pre_curTextValue.push(...insertInfo.value); - this._editor.blockHelper.setBlockBlur(curNodeId); - setTimeout(async () => { - const curBlock = await this._editor.getBlockById(curNodeId); - curBlock.setProperties({ - text: { value: pre_curTextValue }, - }); - await this._pasteChildren(curBlock, blocks[0].children); - }, 0); - beginIndex = 1; - } - } - - for (let i = beginIndex; i < blocks.length; i++) { - const nextBlock = await this._editor.createBlock(blocks[i].type); - nextBlock.setProperties(blocks[i].properties); - if (curBlock.type === 'page') { - curBlock.prepend(nextBlock); - } else { - curBlock.after(nextBlock); - } - - await this._pasteChildren(nextBlock, blocks[i].children); - curBlock = nextBlock; - } - } - - private async _pasteChildren(parent: AsyncBlock, children: any[]) { - for (let i = 0; i < children.length; i++) { - const nextBlock = await this._editor.createBlock(children[i].type); - nextBlock.setProperties(children[i].properties); - await parent.append(nextBlock); - await this._pasteChildren(nextBlock, children[i].children); - } - } - private _preCopyCut(action: ClipboardAction, e: ClipboardEvent) { switch (action) { case ClipboardAction.COPY: @@ -398,7 +113,10 @@ class BrowserClipboard { dispose() { document.removeEventListener(ClipboardAction.COPY, this._handleCopy); document.removeEventListener(ClipboardAction.CUT, this._handleCut); - document.removeEventListener(ClipboardAction.PASTE, this._handlePaste); + document.removeEventListener( + ClipboardAction.PASTE, + this._paste.handlePaste + ); this._eventTarget.removeEventListener( ClipboardAction.COPY, this._handleCopy @@ -409,7 +127,7 @@ class BrowserClipboard { ); this._eventTarget.removeEventListener( ClipboardAction.PASTE, - this._handlePaste + this._paste.handlePaste ); this._clipboardParse.dispose(); this._clipboardParse = null; diff --git a/libs/components/editor-core/src/editor/clipboard/paste.ts b/libs/components/editor-core/src/editor/clipboard/paste.ts new file mode 100644 index 0000000000..022b3de214 --- /dev/null +++ b/libs/components/editor-core/src/editor/clipboard/paste.ts @@ -0,0 +1,535 @@ +import { HooksRunner } from '../types'; +import { + OFFICE_CLIPBOARD_MIMETYPE, + InnerClipInfo, + ClipBlockInfo, +} from './types'; +import { Editor } from '../editor'; +import { AsyncBlock } from '../block'; +import ClipboardParse from './clipboard-parse'; +import { SelectInfo } from '../selection'; +import { + Protocol, + BlockFlavorKeys, + services, +} from '@toeverything/datasource/db-service'; +import { MarkdownParser } from './markdown-parse'; +import { shouldHandlerContinue } from './utils'; +const SUPPORT_MARKDOWN_PASTE = true; + +type TextValueItem = { + text: string; + [key: string]: any; +}; + +export class Paste { + private _editor: Editor; + private _markdownParse: MarkdownParser; + private _clipboardParse: ClipboardParse; + + constructor( + editor: Editor, + clipboardParse: ClipboardParse, + markdownParse: MarkdownParser + ) { + this._markdownParse = markdownParse; + this._clipboardParse = clipboardParse; + this._editor = editor; + this.handlePaste = this.handlePaste.bind(this); + } + private static _optimalMimeType: string[] = [ + OFFICE_CLIPBOARD_MIMETYPE.DOCS_DOCUMENT_SLICE_CLIP_WRAPPED, + OFFICE_CLIPBOARD_MIMETYPE.HTML, + OFFICE_CLIPBOARD_MIMETYPE.TEXT, + ]; + public handlePaste(e: Event) { + if (!shouldHandlerContinue(e, this._editor)) { + return; + } + e.stopPropagation(); + + const clipboardData = (e as ClipboardEvent).clipboardData; + + const isPureFile = Paste._isPureFileInClipboard(clipboardData); + if (isPureFile) { + this._pasteFile(clipboardData); + } else { + this._pasteContent(clipboardData); + } + } + public getOptimalClip(clipboardData: any) { + const mimeTypeArr = Paste._optimalMimeType; + + for (let i = 0; i < mimeTypeArr.length; i++) { + const data = + clipboardData[mimeTypeArr[i]] || + clipboardData.getData(mimeTypeArr[i]); + + if (data) { + return { + type: mimeTypeArr[i], + data: data, + }; + } + } + + return ''; + } + + private _pasteContent(clipboardData: any) { + const originClip: { data: any; type: any } = this.getOptimalClip( + clipboardData + ) as { data: any; type: any }; + + const originTextClipData = clipboardData.getData( + OFFICE_CLIPBOARD_MIMETYPE.TEXT + ); + + let clipData = originClip['data']; + + if (originClip['type'] === OFFICE_CLIPBOARD_MIMETYPE.TEXT) { + clipData = Paste._excapeHtml(clipData); + } + + switch (originClip['type']) { + /** Protocol paste */ + case OFFICE_CLIPBOARD_MIMETYPE.DOCS_DOCUMENT_SLICE_CLIP_WRAPPED: + this._firePasteEditAction(clipData); + break; + case OFFICE_CLIPBOARD_MIMETYPE.HTML: + this._pasteHtml(clipData, originTextClipData); + break; + case OFFICE_CLIPBOARD_MIMETYPE.TEXT: + this._pasteText(clipData, originTextClipData); + break; + + default: + break; + } + } + private async _firePasteEditAction(clipboardData: any) { + const clipInfo: InnerClipInfo = JSON.parse(clipboardData); + clipInfo && this._insertBlocks(clipInfo.data, clipInfo.select); + } + private async _pasteFile(clipboardData: any) { + const file = Paste._getImageFile(clipboardData); + if (file) { + const result = await services.api.file.create({ + workspace: this._editor.workspace, + file: file, + }); + const blockInfo: ClipBlockInfo = { + type: 'image', + properties: { + image: { + value: result.id, + name: file.name, + size: file.size, + type: file.type, + }, + }, + children: [] as ClipBlockInfo[], + }; + await this._insertBlocks([blockInfo]); + } + } + private static _isPureFileInClipboard(clipboardData: DataTransfer) { + const types = clipboardData.types; + + return ( + (types.length === 1 && types[0] === 'Files') || + (types.length === 2 && + (types.includes('text/plain') || types.includes('text/html')) && + types.includes('Files')) + ); + } + + private static _isTextEditBlock(type: BlockFlavorKeys) { + return ( + type === Protocol.Block.Type.page || + type === Protocol.Block.Type.text || + type === Protocol.Block.Type.heading1 || + type === Protocol.Block.Type.heading2 || + type === Protocol.Block.Type.heading3 || + type === Protocol.Block.Type.quote || + type === Protocol.Block.Type.todo || + type === Protocol.Block.Type.code || + type === Protocol.Block.Type.callout || + type === Protocol.Block.Type.numbered || + type === Protocol.Block.Type.bullet + ); + } + + private async _insertBlocks( + blocks: ClipBlockInfo[], + pasteSelect?: SelectInfo + ) { + if (blocks.length === 0) { + return; + } + const currentSelectInfo = + await this._editor.selectionManager.getSelectInfo(); + + // 当选区在某一个block中时 + // select?.type === 'Range' + if (currentSelectInfo.type === 'Range') { + // 当 currentSelectInfo.type === 'Range' 时,光标选中的block必然只有一个 + const selectedBlock = await this._editor.getBlockById( + currentSelectInfo.blocks[0].blockId + ); + const isSelectedBlockEdit = Paste._isTextEditBlock( + selectedBlock.type + ); + if (isSelectedBlockEdit) { + const shouldSplitBlock = + blocks.length > 1 || + !Paste._isTextEditBlock(blocks[0].type); + const pureText = !shouldSplitBlock + ? blocks[0].properties.text.value + : [{ text: '' }]; + this._editor.blockHelper.setBlockBlur( + currentSelectInfo.blocks[0].blockId + ); + + const { startInfo, endInfo } = currentSelectInfo.blocks[0]; + + // 选中的当前的可编辑block的文字信息 + const currentTextValue = + selectedBlock.getProperty('text').value; + // 当光标选区跨越不同样式文字时 + if (startInfo?.arrayIndex !== endInfo?.arrayIndex) { + if (shouldSplitBlock) { + const newTextValue = currentTextValue.reduce( + ( + newTextValue: TextValueItem[], + textStore: TextValueItem, + i: number + ) => { + if (i < startInfo?.arrayIndex) { + newTextValue.push(textStore); + } + const { text, ...props } = textStore; + + if (i === startInfo?.arrayIndex) { + newTextValue.push({ + text: text.slice(0, startInfo?.offset), + ...props, + }); + } + return newTextValue; + }, + [] + ); + const nextTextValue = currentTextValue.reduce( + ( + newTextValue: TextValueItem[], + textStore: TextValueItem, + i: number + ) => { + if (i > endInfo?.arrayIndex) { + newTextValue.push(textStore); + } + const { text, ...props } = textStore; + + if (i === endInfo?.arrayIndex) { + newTextValue.push({ + text: text.slice(endInfo?.offset), + ...props, + }); + } + return newTextValue; + }, + [] + ); + + selectedBlock.setProperties({ + text: { + value: newTextValue, + }, + }); + const pasteBlocks = await this._createBlocks(blocks); + pasteBlocks.forEach(block => { + selectedBlock.after(block); + }); + const nextBlock = await this._editor.createBlock( + selectedBlock?.type + ); + nextBlock.setProperties({ + text: { + value: nextTextValue, + }, + }); + pasteBlocks[pasteBlocks.length - 1].after(nextBlock); + + this._setEndSelectToBlock( + pasteBlocks[pasteBlocks.length - 1].id + ); + } else { + const newTextValue = currentTextValue.reduce( + ( + newTextValue: TextValueItem[], + textStore: TextValueItem, + i: number + ) => { + if ( + i < startInfo?.arrayIndex || + i > endInfo?.arrayIndex + ) { + newTextValue.push(textStore); + } + const { text, ...props } = textStore; + + if (i === startInfo?.arrayIndex) { + newTextValue.push({ + text: text.slice(0, startInfo?.offset), + ...props, + }); + } else if (i === endInfo?.arrayIndex) { + newTextValue.push({ + text: text.slice(endInfo?.offset), + ...props, + }); + } + return newTextValue; + }, + [] + ); + newTextValue.splice( + startInfo?.arrayIndex + 1, + 0, + ...pureText + ); + selectedBlock.setProperties({ + text: { + value: newTextValue, + }, + }); + } + } + // 当光标选区没有跨越不同样式文字时 + if (startInfo?.arrayIndex === endInfo?.arrayIndex) { + if (shouldSplitBlock) { + const newTextValue = currentTextValue.reduce( + ( + newTextValue: TextValueItem[], + textStore: TextValueItem, + i: number + ) => { + if (i < startInfo?.arrayIndex) { + newTextValue.push(textStore); + } + const { text, ...props } = textStore; + + if (i === startInfo?.arrayIndex) { + newTextValue.push({ + text: `${text.slice( + 0, + startInfo?.offset + )}`, + ...props, + }); + } + return newTextValue; + }, + [] + ); + + const nextTextValue = currentTextValue.reduce( + ( + nextTextValue: TextValueItem[], + textStore: TextValueItem, + i: number + ) => { + if (i > endInfo?.arrayIndex) { + nextTextValue.push(textStore); + } + const { text, ...props } = textStore; + + if (i === endInfo?.arrayIndex) { + nextTextValue.push({ + text: `${text.slice(endInfo?.offset)}`, + ...props, + }); + } + return nextTextValue; + }, + [] + ); + selectedBlock.setProperties({ + text: { + value: newTextValue, + }, + }); + const pasteBlocks = await this._createBlocks(blocks); + pasteBlocks.forEach((block: AsyncBlock) => { + selectedBlock.after(block); + }); + const nextBlock = await this._editor.createBlock( + selectedBlock?.type + ); + nextBlock.setProperties({ + text: { + value: nextTextValue, + }, + }); + pasteBlocks[pasteBlocks.length - 1].after(nextBlock); + + this._setEndSelectToBlock( + pasteBlocks[pasteBlocks.length - 1].id + ); + } else { + const newTextValue = currentTextValue.reduce( + ( + newTextValue: TextValueItem[], + textStore: TextValueItem, + i: number + ) => { + if (i !== startInfo?.arrayIndex) { + newTextValue.push(textStore); + } + const { text, ...props } = textStore; + + if (i === startInfo?.arrayIndex) { + newTextValue.push({ + text: `${text.slice( + 0, + startInfo?.offset + )}`, + ...props, + }); + newTextValue.push(...pureText); + + newTextValue.push({ + text: `${text.slice(endInfo?.offset)}`, + ...props, + }); + } + return newTextValue; + }, + [] + ); + selectedBlock.setProperties({ + text: { + value: newTextValue, + }, + }); + + const pastedTextLength = pureText.reduce( + (sumLength: number, textItem: TextValueItem) => { + sumLength += textItem.text.length; + return sumLength; + }, + 0 + ); + + // this._editor.selectionManager.moveCursor( + // window.getSelection().getRangeAt(0), + // pastedTextLength, + // selectedBlock.id + // ); + } + } + } else { + const pasteBlocks = await this._createBlocks(blocks); + pasteBlocks.forEach(block => { + selectedBlock.after(block); + }); + this._setEndSelectToBlock( + pasteBlocks[pasteBlocks.length - 1].id + ); + } + } + + if (currentSelectInfo.type === 'Block') { + const selectedBlock = await this._editor.getBlockById( + currentSelectInfo.blocks[currentSelectInfo.blocks.length - 1] + .blockId + ); + const pasteBlocks = await this._createBlocks(blocks); + + let groupBlock: AsyncBlock; + if ( + selectedBlock?.type === 'group' || + selectedBlock?.type === 'page' + ) { + groupBlock = await this._editor.createBlock('group'); + pasteBlocks.forEach(block => { + groupBlock.append(block); + }); + await selectedBlock.after(groupBlock); + } else { + pasteBlocks.forEach(block => { + selectedBlock.after(block); + }); + } + this._setEndSelectToBlock(pasteBlocks[pasteBlocks.length - 1].id); + } + } + + private _setEndSelectToBlock(blockId: string) { + setTimeout(() => { + this._editor.selectionManager.activeNodeByNodeId(blockId, 'end'); + }, 100); + } + + private async _createBlocks(blocks: ClipBlockInfo[], parentId?: string) { + return Promise.all( + blocks.map(async clipBlockInfo => { + const block = await this._editor.createBlock( + clipBlockInfo.type + ); + block?.setProperties(clipBlockInfo.properties); + await this._createBlocks(clipBlockInfo.children, block?.id); + return block; + }) + ); + } + + private async _pasteHtml(clipData: any, originTextClipData: any) { + if (SUPPORT_MARKDOWN_PASTE) { + const hasMarkdown = + this._markdownParse.checkIfTextContainsMd(originTextClipData); + if (hasMarkdown) { + try { + const convertedDataObj = + this._markdownParse.md2Html(originTextClipData); + if (convertedDataObj.isConverted) { + clipData = convertedDataObj.text; + } + } catch (e) { + console.error(e); + clipData = originTextClipData; + } + } + } + + const blocks = this._clipboardParse.html2blocks(clipData); + + await this._insertBlocks(blocks); + } + + private async _pasteText(clipData: any, originTextClipData: any) { + const blocks = this._clipboardParse.text2blocks(clipData); + await this._insertBlocks(blocks); + } + + private static _getImageFile(clipboardData: any) { + const files = clipboardData.files; + if (files && files[0] && files[0].type.indexOf('image') > -1) { + return files[0]; + } + return; + } + + private static _excapeHtml(data: any, onlySpace?: any) { + if (!onlySpace) { + // TODO: + // data = string.htmlEscape(data); + // data = data.replace(/\n/g, '
'); + } + + // data = data.replace(/ /g, ' '); + // data = data.replace(/\t/g, '    '); + return data; + } +} diff --git a/libs/components/editor-core/src/editor/clipboard/utils.ts b/libs/components/editor-core/src/editor/clipboard/utils.ts new file mode 100644 index 0000000000..7b4d420a3a --- /dev/null +++ b/libs/components/editor-core/src/editor/clipboard/utils.ts @@ -0,0 +1,14 @@ +import {Editor} from "../editor"; + +export const shouldHandlerContinue = (event: Event, editor: Editor) => { + const filterNodes = ['INPUT', 'SELECT', 'TEXTAREA']; + + if (event.defaultPrevented) { + return false; + } + if (filterNodes.includes((event.target as HTMLElement)?.tagName)) { + return false; + } + + return editor.selectionManager.currentSelectInfo.type !== 'None'; +}; From bb0219c5edaa9e298841e327cc8ded6ac86043c7 Mon Sep 17 00:00:00 2001 From: QiShaoXuan Date: Thu, 11 Aug 2022 21:43:51 +0800 Subject: [PATCH 11/44] feat: support keep block in view --- libs/components/editor-core/src/editor/selection/selection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/components/editor-core/src/editor/selection/selection.ts b/libs/components/editor-core/src/editor/selection/selection.ts index b66d1b5fd6..1c45060cd6 100644 --- a/libs/components/editor-core/src/editor/selection/selection.ts +++ b/libs/components/editor-core/src/editor/selection/selection.ts @@ -692,7 +692,7 @@ export class SelectionManager implements VirgoSelection { this.emit(nodeId, SelectEventTypes.active, this.lastPoint); // TODO: Optimize the related logic after implementing the scroll bar setTimeout(() => { - // this._editor.scrollManager.keepBlockInView(node); + this._editor.scrollManager.keepBlockInView(node); }, this._scrollDelay); } else { console.warn('Can not find node by this id'); From 322fb098a4288ffcf94dcc22b0a7234d35cbeddc Mon Sep 17 00:00:00 2001 From: SaikaSakura Date: Thu, 11 Aug 2022 22:06:50 +0800 Subject: [PATCH 12/44] feat: repair paste cursor --- .../common/src/lib/text/slate-utils.ts | 13 ++++ .../src/editor/block/block-helper.ts | 66 ++++++++++++++++++- .../editor-core/src/editor/clipboard/paste.ts | 26 +++++--- .../editor-core/src/editor/clipboard/utils.ts | 2 +- .../src/editor/selection/selection.ts | 28 ++++---- 5 files changed, 109 insertions(+), 26 deletions(-) diff --git a/libs/components/common/src/lib/text/slate-utils.ts b/libs/components/common/src/lib/text/slate-utils.ts index 3f5fb57697..1783d79cab 100644 --- a/libs/components/common/src/lib/text/slate-utils.ts +++ b/libs/components/common/src/lib/text/slate-utils.ts @@ -905,6 +905,19 @@ class SlateUtils { ); } + public insertNodes( + nodes: SlateNode | Array, + options?: Parameters[2] + ) { + Transforms.insertNodes(this.editor, nodes, { + ...options, + }); + } + + public getNodeByPath(path: Path) { + Editor.node(this.editor, path); + } + public getStartSelection() { return { anchor: this.getStart(), diff --git a/libs/components/editor-core/src/editor/block/block-helper.ts b/libs/components/editor-core/src/editor/block/block-helper.ts index 1c1ab37dbe..7bd0284ffc 100644 --- a/libs/components/editor-core/src/editor/block/block-helper.ts +++ b/libs/components/editor-core/src/editor/block/block-helper.ts @@ -3,7 +3,13 @@ import type { SlateUtils, TextAlignOptions, } from '@toeverything/components/common'; -import { Point, Selection as SlateSelection } from 'slate'; +import { + BaseRange, + Node, + Path, + Point, + Selection as SlateSelection, +} from 'slate'; import { Editor } from '../editor'; type TextUtilsFunctions = @@ -28,7 +34,10 @@ type TextUtilsFunctions = | 'removeSelection' | 'insertReference' | 'isCollapsed' - | 'blur'; + | 'blur' + | 'setSelection' + | 'insertNodes' + | 'getNodeByPath'; type ExtendedTextUtils = SlateUtils & { setLinkModalVisible: (visible: boolean) => void; @@ -193,6 +202,59 @@ export class BlockHelper { return ''; } + /** + * + * set selection of a text input + * @param {string} blockId + * @param {BaseRange} selection + * @return {*} + * @memberof BlockHelper + */ + public setSelection(blockId: string, selection: BaseRange) { + const text_utils = this._blockTextUtilsMap[blockId]; + if (text_utils) { + return text_utils.setSelection(selection); + } + console.warn('Could find the block text utils'); + } + + /** + * + * insert nodes in text + * @param {string} blockId + * @param {Array} nodes + * @param {Parameters[1]} options + * @return {*} + * @memberof BlockHelper + */ + public insertNodes( + blockId: string, + nodes: Array, + options?: Parameters[1] + ) { + const text_utils = this._blockTextUtilsMap[blockId]; + if (text_utils) { + return text_utils.insertNodes(nodes, options); + } + console.warn('Could find the block text utils'); + } + + /** + * + * get text(slate node) by path + * @param {string} blockId + * @param {Path} path + * @return {*} + * @memberof BlockHelper + */ + public getNodeByPath(blockId: string, path: Path) { + const text_utils = this._blockTextUtilsMap[blockId]; + if (text_utils) { + return text_utils.getNodeByPath(path); + } + console.warn('Could find the block text utils'); + } + public transformPoint( blockId: string, ...restArgs: Parameters diff --git a/libs/components/editor-core/src/editor/clipboard/paste.ts b/libs/components/editor-core/src/editor/clipboard/paste.ts index 022b3de214..6633b618e4 100644 --- a/libs/components/editor-core/src/editor/clipboard/paste.ts +++ b/libs/components/editor-core/src/editor/clipboard/paste.ts @@ -1,4 +1,4 @@ -import { HooksRunner } from '../types'; +/* eslint-disable max-lines */ import { OFFICE_CLIPBOARD_MIMETYPE, InnerClipInfo, @@ -187,10 +187,15 @@ export class Paste { const pureText = !shouldSplitBlock ? blocks[0].properties.text.value : [{ text: '' }]; - this._editor.blockHelper.setBlockBlur( - currentSelectInfo.blocks[0].blockId + this._editor.blockHelper.insertNodes( + selectedBlock.id, + pureText, + { select: true } ); + return; + //TODO repair the following logics + /** const { startInfo, endInfo } = currentSelectInfo.blocks[0]; // 选中的当前的可编辑block的文字信息 @@ -414,13 +419,13 @@ export class Paste { }, }); - const pastedTextLength = pureText.reduce( - (sumLength: number, textItem: TextValueItem) => { - sumLength += textItem.text.length; - return sumLength; - }, - 0 - ); + // const pastedTextLength = pureText.reduce( + // (sumLength: number, textItem: TextValueItem) => { + // sumLength += textItem.text.length; + // return sumLength; + // }, + // 0 + // ); // this._editor.selectionManager.moveCursor( // window.getSelection().getRangeAt(0), @@ -429,6 +434,7 @@ export class Paste { // ); } } + */ } else { const pasteBlocks = await this._createBlocks(blocks); pasteBlocks.forEach(block => { diff --git a/libs/components/editor-core/src/editor/clipboard/utils.ts b/libs/components/editor-core/src/editor/clipboard/utils.ts index 7b4d420a3a..cb5d10241e 100644 --- a/libs/components/editor-core/src/editor/clipboard/utils.ts +++ b/libs/components/editor-core/src/editor/clipboard/utils.ts @@ -1,4 +1,4 @@ -import {Editor} from "../editor"; +import { Editor } from '../editor'; export const shouldHandlerContinue = (event: Event, editor: Editor) => { const filterNodes = ['INPUT', 'SELECT', 'TEXTAREA']; diff --git a/libs/components/editor-core/src/editor/selection/selection.ts b/libs/components/editor-core/src/editor/selection/selection.ts index 99f648a930..40c069b941 100644 --- a/libs/components/editor-core/src/editor/selection/selection.ts +++ b/libs/components/editor-core/src/editor/selection/selection.ts @@ -322,16 +322,16 @@ export class SelectionManager implements VirgoSelection { if (selectionRect.isIntersect(domToRect(block.dom))) { const childrenBlocks = await block.children(); // should check directly in structured block - const structuredChildrenBlocks: Array = childrenBlocks.filter( - childBlock => { + const structuredChildrenBlocks: Array = + childrenBlocks.filter(childBlock => { return this._editor.getView(childBlock.type).layoutOnly; - } - ); + }); for await (const childBlock of structuredChildrenBlocks) { - const childSelectedNodes = await this.calcRenderBlockIntersect( - selectionRect, - childBlock - ); + const childSelectedNodes = + await this.calcRenderBlockIntersect( + selectionRect, + childBlock + ); selectedNodes.push(...childSelectedNodes); } const selectableChildren = childrenBlocks.filter(childBlock => { @@ -348,10 +348,11 @@ export class SelectionManager implements VirgoSelection { } // if just only has one selected maybe select the children if (selectedNodes.length === 1) { - const childrenSelectedNodes: Array = await this.calcRenderBlockIntersect( - selectionRect, - selectedNodes[0] - ); + const childrenSelectedNodes: Array = + await this.calcRenderBlockIntersect( + selectionRect, + selectedNodes[0] + ); if (childrenSelectedNodes.length) return childrenSelectedNodes; } @@ -584,7 +585,8 @@ export class SelectionManager implements VirgoSelection { } else { const new_node = await this._editor.getBlockById(newNodeId); if (new_node) { - const new_node_children_ids = await new_node.childrenIds; + const new_node_children_ids = + await new_node.childrenIds; let select_ids_new = this._selectedNodesIds; if ( new_node_children_ids && From 6ad94242c4a0117735e122a11f2502f4883f1902 Mon Sep 17 00:00:00 2001 From: QiShaoXuan Date: Thu, 11 Aug 2022 22:33:51 +0800 Subject: [PATCH 13/44] fix: insert correct selection after paste --- .../editor-core/src/editor/clipboard/paste.ts | 116 +++--------------- 1 file changed, 15 insertions(+), 101 deletions(-) diff --git a/libs/components/editor-core/src/editor/clipboard/paste.ts b/libs/components/editor-core/src/editor/clipboard/paste.ts index 6633b618e4..0c48eee56b 100644 --- a/libs/components/editor-core/src/editor/clipboard/paste.ts +++ b/libs/components/editor-core/src/editor/clipboard/paste.ts @@ -170,8 +170,8 @@ export class Paste { const currentSelectInfo = await this._editor.selectionManager.getSelectInfo(); - // 当选区在某一个block中时 - // select?.type === 'Range' + // When the selection is in one of the blocks, select?.type === 'Range' + // Currently the selection does not support cross-blocking, so this case is not considered if (currentSelectInfo.type === 'Range') { // 当 currentSelectInfo.type === 'Range' 时,光标选中的block必然只有一个 const selectedBlock = await this._editor.getBlockById( @@ -187,23 +187,15 @@ export class Paste { const pureText = !shouldSplitBlock ? blocks[0].properties.text.value : [{ text: '' }]; - this._editor.blockHelper.insertNodes( - selectedBlock.id, - pureText, - { select: true } - ); - return; - //TODO repair the following logics - - /** const { startInfo, endInfo } = currentSelectInfo.blocks[0]; - // 选中的当前的可编辑block的文字信息 + // Text content of the selected current editable block const currentTextValue = selectedBlock.getProperty('text').value; - // 当光标选区跨越不同样式文字时 + // When the cursor selection spans different styles of text if (startInfo?.arrayIndex !== endInfo?.arrayIndex) { if (shouldSplitBlock) { + // TODO: split block maybe should use slate method to support, like "this._editor.blockHelper.insertNodes" const newTextValue = currentTextValue.reduce( ( newTextValue: TextValueItem[], @@ -270,50 +262,17 @@ export class Paste { pasteBlocks[pasteBlocks.length - 1].id ); } else { - const newTextValue = currentTextValue.reduce( - ( - newTextValue: TextValueItem[], - textStore: TextValueItem, - i: number - ) => { - if ( - i < startInfo?.arrayIndex || - i > endInfo?.arrayIndex - ) { - newTextValue.push(textStore); - } - const { text, ...props } = textStore; - - if (i === startInfo?.arrayIndex) { - newTextValue.push({ - text: text.slice(0, startInfo?.offset), - ...props, - }); - } else if (i === endInfo?.arrayIndex) { - newTextValue.push({ - text: text.slice(endInfo?.offset), - ...props, - }); - } - return newTextValue; - }, - [] + this._editor.blockHelper.insertNodes( + selectedBlock.id, + pureText, + { select: true } ); - newTextValue.splice( - startInfo?.arrayIndex + 1, - 0, - ...pureText - ); - selectedBlock.setProperties({ - text: { - value: newTextValue, - }, - }); } } - // 当光标选区没有跨越不同样式文字时 + // When the cursor selection does not span different styles of text if (startInfo?.arrayIndex === endInfo?.arrayIndex) { if (shouldSplitBlock) { + // TODO: split block maybe should use slate method to support, like "this._editor.blockHelper.insertNodes" const newTextValue = currentTextValue.reduce( ( newTextValue: TextValueItem[], @@ -383,58 +342,13 @@ export class Paste { pasteBlocks[pasteBlocks.length - 1].id ); } else { - const newTextValue = currentTextValue.reduce( - ( - newTextValue: TextValueItem[], - textStore: TextValueItem, - i: number - ) => { - if (i !== startInfo?.arrayIndex) { - newTextValue.push(textStore); - } - const { text, ...props } = textStore; - - if (i === startInfo?.arrayIndex) { - newTextValue.push({ - text: `${text.slice( - 0, - startInfo?.offset - )}`, - ...props, - }); - newTextValue.push(...pureText); - - newTextValue.push({ - text: `${text.slice(endInfo?.offset)}`, - ...props, - }); - } - return newTextValue; - }, - [] + this._editor.blockHelper.insertNodes( + selectedBlock.id, + pureText, + { select: true } ); - selectedBlock.setProperties({ - text: { - value: newTextValue, - }, - }); - - // const pastedTextLength = pureText.reduce( - // (sumLength: number, textItem: TextValueItem) => { - // sumLength += textItem.text.length; - // return sumLength; - // }, - // 0 - // ); - - // this._editor.selectionManager.moveCursor( - // window.getSelection().getRangeAt(0), - // pastedTextLength, - // selectedBlock.id - // ); } } - */ } else { const pasteBlocks = await this._createBlocks(blocks); pasteBlocks.forEach(block => { From 0674a4885455ce08f5bfa28576a645580c9ffad1 Mon Sep 17 00:00:00 2001 From: QiShaoXuan Date: Thu, 11 Aug 2022 22:49:42 +0800 Subject: [PATCH 14/44] refactor: remove useless file --- .../clipboard/browser-clipboard-back.ts | 422 ------------------ 1 file changed, 422 deletions(-) delete mode 100644 libs/components/editor-core/src/editor/clipboard/browser-clipboard-back.ts diff --git a/libs/components/editor-core/src/editor/clipboard/browser-clipboard-back.ts b/libs/components/editor-core/src/editor/clipboard/browser-clipboard-back.ts deleted file mode 100644 index 6c6d4ad031..0000000000 --- a/libs/components/editor-core/src/editor/clipboard/browser-clipboard-back.ts +++ /dev/null @@ -1,422 +0,0 @@ -import { HooksRunner } from '../types'; -import { - OFFICE_CLIPBOARD_MIMETYPE, - InnerClipInfo, - ClipBlockInfo, -} from './types'; -import { Editor } from '../editor'; -import { AsyncBlock } from '../block'; -import ClipboardParse from './clipboard-parse'; -import { SelectInfo } from '../selection'; -import { - Protocol, - BlockFlavorKeys, - services, -} from '@toeverything/datasource/db-service'; -import { MarkdownParser } from './markdown-parse'; - -// todo needs to be a switch -const SUPPORT_MARKDOWN_PASTE = true; - -const shouldHandlerContinue = (event: Event, editor: Editor) => { - const filterNodes = ['INPUT', 'SELECT', 'TEXTAREA']; - - if (event.defaultPrevented) { - return false; - } - if (filterNodes.includes((event.target as HTMLElement)?.tagName)) { - return false; - } - - return editor.selectionManager.currentSelectInfo.type !== 'None'; -}; - -enum ClipboardAction { - COPY = 'copy', - CUT = 'cut', - PASTE = 'paste', -} -class BrowserClipboard { - private _eventTarget: Element; - private _hooks: HooksRunner; - private _editor: Editor; - private _clipboardParse: ClipboardParse; - private _markdownParse: MarkdownParser; - - private static _optimalMimeType: string[] = [ - OFFICE_CLIPBOARD_MIMETYPE.DOCS_DOCUMENT_SLICE_CLIP_WRAPPED, - OFFICE_CLIPBOARD_MIMETYPE.HTML, - OFFICE_CLIPBOARD_MIMETYPE.TEXT, - ]; - - constructor(eventTarget: Element, hooks: HooksRunner, editor: Editor) { - this._eventTarget = eventTarget; - this._hooks = hooks; - this._editor = editor; - this._clipboardParse = new ClipboardParse(editor); - this._markdownParse = new MarkdownParser(); - this._initialize(); - } - - public getClipboardParse() { - return this._clipboardParse; - } - - private _initialize() { - this._handleCopy = this._handleCopy.bind(this); - this._handleCut = this._handleCut.bind(this); - this._handlePaste = this._handlePaste.bind(this); - - document.addEventListener(ClipboardAction.COPY, this._handleCopy); - document.addEventListener(ClipboardAction.CUT, this._handleCut); - document.addEventListener(ClipboardAction.PASTE, this._handlePaste); - this._eventTarget.addEventListener( - ClipboardAction.COPY, - this._handleCopy - ); - this._eventTarget.addEventListener( - ClipboardAction.CUT, - this._handleCut - ); - this._eventTarget.addEventListener( - ClipboardAction.PASTE, - this._handlePaste - ); - } - - private _handleCopy(e: Event) { - if (!shouldHandlerContinue(e, this._editor)) { - return; - } - - this._dispatchClipboardEvent(ClipboardAction.COPY, e as ClipboardEvent); - } - - private _handleCut(e: Event) { - if (!shouldHandlerContinue(e, this._editor)) { - return; - } - - this._dispatchClipboardEvent(ClipboardAction.CUT, e as ClipboardEvent); - } - - private _handlePaste(e: Event) { - if (!shouldHandlerContinue(e, this._editor)) { - return; - } - e.stopPropagation(); - - const clipboardData = (e as ClipboardEvent).clipboardData; - - const isPureFile = this._isPureFileInClipboard(clipboardData); - - if (isPureFile) { - this._pasteFile(clipboardData); - } else { - this._pasteContent(clipboardData); - } - // this._editor.selectionManager - // .getSelectInfo() - // .then(selectionInfo => console.log(selectionInfo)); - } - - private _pasteContent(clipboardData: any) { - const originClip: { data: any; type: any } = this.getOptimalClip( - clipboardData - ) as { data: any; type: any }; - - const originTextClipData = clipboardData.getData( - OFFICE_CLIPBOARD_MIMETYPE.TEXT - ); - - let clipData = originClip['data']; - - if (originClip['type'] === OFFICE_CLIPBOARD_MIMETYPE.TEXT) { - clipData = this._excapeHtml(clipData); - } - - switch (originClip['type']) { - /** Protocol paste */ - case OFFICE_CLIPBOARD_MIMETYPE.DOCS_DOCUMENT_SLICE_CLIP_WRAPPED: - this._firePasteEditAction(clipData); - break; - case OFFICE_CLIPBOARD_MIMETYPE.HTML: - this._pasteHtml(clipData, originTextClipData); - break; - case OFFICE_CLIPBOARD_MIMETYPE.TEXT: - this._pasteText(clipData, originTextClipData); - break; - - default: - break; - } - } - - private _pasteHtml(clipData: any, originTextClipData: any) { - if (SUPPORT_MARKDOWN_PASTE) { - const hasMarkdown = - this._markdownParse.checkIfTextContainsMd(originTextClipData); - if (hasMarkdown) { - try { - const convertedDataObj = - this._markdownParse.md2Html(originTextClipData); - if (convertedDataObj.isConverted) { - clipData = convertedDataObj.text; - } - } catch (e) { - console.error(e); - clipData = originTextClipData; - } - } - } - - const blocks = this._clipboardParse.html2blocks(clipData); - this.insert_blocks(blocks); - } - - private _pasteText(clipData: any, originTextClipData: any) { - const blocks = this._clipboardParse.text2blocks(clipData); - this.insert_blocks(blocks); - } - - private async _pasteFile(clipboardData: any) { - const file = this._getImageFile(clipboardData); - if (file) { - const result = await services.api.file.create({ - workspace: this._editor.workspace, - file: file, - }); - const blockInfo: ClipBlockInfo = { - type: 'image', - properties: { - image: { - value: result.id, - name: file.name, - size: file.size, - type: file.type, - }, - }, - children: [] as ClipBlockInfo[], - }; - this.insert_blocks([blockInfo]); - } - } - - private _getImageFile(clipboardData: any) { - const files = clipboardData.files; - if (files && files[0] && files[0].type.indexOf('image') > -1) { - return files[0]; - } - return; - } - - private _excapeHtml(data: any, onlySpace?: any) { - if (!onlySpace) { - // TODO: - // data = string.htmlEscape(data); - // data = data.replace(/\n/g, '
'); - } - - // data = data.replace(/ /g, ' '); - // data = data.replace(/\t/g, '    '); - return data; - } - - public getOptimalClip(clipboardData: any) { - const mimeTypeArr = BrowserClipboard._optimalMimeType; - - for (let i = 0; i < mimeTypeArr.length; i++) { - const data = - clipboardData[mimeTypeArr[i]] || - clipboardData.getData(mimeTypeArr[i]); - - if (data) { - return { - type: mimeTypeArr[i], - data: data, - }; - } - } - - return ''; - } - - private _isPureFileInClipboard(clipboardData: DataTransfer) { - const types = clipboardData.types; - - return ( - (types.length === 1 && types[0] === 'Files') || - (types.length === 2 && - (types.includes('text/plain') || types.includes('text/html')) && - types.includes('Files')) - ); - } - - private async _firePasteEditAction(clipboardData: any) { - const clipInfo: InnerClipInfo = JSON.parse(clipboardData); - clipInfo && this.insert_blocks(clipInfo.data, clipInfo.select); - } - - private _canEditText(type: BlockFlavorKeys) { - return ( - type === Protocol.Block.Type.page || - type === Protocol.Block.Type.text || - type === Protocol.Block.Type.heading1 || - type === Protocol.Block.Type.heading2 || - type === Protocol.Block.Type.heading3 || - type === Protocol.Block.Type.quote || - type === Protocol.Block.Type.todo || - type === Protocol.Block.Type.code || - type === Protocol.Block.Type.callout || - type === Protocol.Block.Type.numbered || - type === Protocol.Block.Type.bullet - ); - } - - // TODO: cursor positioning problem - private async insert_blocks(blocks: ClipBlockInfo[], select?: SelectInfo) { - if (blocks.length === 0) { - return; - } - - const cur_select_info = - await this._editor.selectionManager.getSelectInfo(); - if (cur_select_info.blocks.length === 0) { - return; - } - - let beginIndex = 0; - const curNodeId = - cur_select_info.blocks[cur_select_info.blocks.length - 1].blockId; - let curBlock = await this._editor.getBlockById(curNodeId); - const blockView = this._editor.getView(curBlock.type); - if ( - cur_select_info.type === 'Range' && - curBlock.type === 'text' && - blockView.isEmpty(curBlock) - ) { - await curBlock.setType(blocks[0].type); - curBlock.setProperties(blocks[0].properties); - await this._pasteChildren(curBlock, blocks[0].children); - beginIndex = 1; - } else if ( - select?.type === 'Range' && - cur_select_info.type === 'Range' && - this._canEditText(curBlock.type) && - this._canEditText(blocks[0].type) - ) { - if ( - cur_select_info.blocks.length > 0 && - cur_select_info.blocks[0].startInfo - ) { - const startInfo = cur_select_info.blocks[0].startInfo; - const endInfo = cur_select_info.blocks[0].endInfo; - const curTextValue = curBlock.getProperty('text').value; - const pre_curTextValue = curTextValue.slice( - 0, - startInfo.arrayIndex - ); - const lastCurTextValue = curTextValue.slice( - endInfo.arrayIndex + 1 - ); - const preText = curTextValue[ - startInfo.arrayIndex - ].text.substring(0, startInfo.offset); - const lastText = curTextValue[ - endInfo.arrayIndex - ].text.substring(endInfo.offset); - - let lastBlock: ClipBlockInfo = blocks[blocks.length - 1]; - if (!this._canEditText(lastBlock.type)) { - lastBlock = { type: 'text', children: [] }; - blocks.push(lastBlock); - } - const lastValues = lastBlock.properties?.text?.value; - lastText && lastValues.push({ text: lastText }); - lastValues.push(...lastCurTextValue); - lastBlock.properties = { - text: { value: lastValues }, - }; - - const insertInfo = blocks[0].properties.text; - preText && pre_curTextValue.push({ text: preText }); - pre_curTextValue.push(...insertInfo.value); - this._editor.blockHelper.setBlockBlur(curNodeId); - setTimeout(async () => { - const curBlock = await this._editor.getBlockById(curNodeId); - curBlock.setProperties({ - text: { value: pre_curTextValue }, - }); - await this._pasteChildren(curBlock, blocks[0].children); - }, 0); - beginIndex = 1; - } - } - - for (let i = beginIndex; i < blocks.length; i++) { - const nextBlock = await this._editor.createBlock(blocks[i].type); - nextBlock.setProperties(blocks[i].properties); - if (curBlock.type === 'page') { - curBlock.prepend(nextBlock); - } else { - curBlock.after(nextBlock); - } - - await this._pasteChildren(nextBlock, blocks[i].children); - curBlock = nextBlock; - } - } - - private async _pasteChildren(parent: AsyncBlock, children: any[]) { - for (let i = 0; i < children.length; i++) { - const nextBlock = await this._editor.createBlock(children[i].type); - nextBlock.setProperties(children[i].properties); - await parent.append(nextBlock); - await this._pasteChildren(nextBlock, children[i].children); - } - } - - private _preCopyCut(action: ClipboardAction, e: ClipboardEvent) { - switch (action) { - case ClipboardAction.COPY: - this._hooks.beforeCopy(e); - break; - - case ClipboardAction.CUT: - this._hooks.beforeCut(e); - break; - } - } - - private _dispatchClipboardEvent( - action: ClipboardAction, - e: ClipboardEvent - ) { - this._preCopyCut(action, e); - } - - dispose() { - document.removeEventListener(ClipboardAction.COPY, this._handleCopy); - document.removeEventListener(ClipboardAction.CUT, this._handleCut); - document.removeEventListener(ClipboardAction.PASTE, this._handlePaste); - this._eventTarget.removeEventListener( - ClipboardAction.COPY, - this._handleCopy - ); - this._eventTarget.removeEventListener( - ClipboardAction.CUT, - this._handleCut - ); - this._eventTarget.removeEventListener( - ClipboardAction.PASTE, - this._handlePaste - ); - this._clipboardParse.dispose(); - this._clipboardParse = null; - this._eventTarget = null; - this._hooks = null; - this._editor = null; - } -} - -export { BrowserClipboard }; From b9e69ec8334d7cc2cc4c5ae3921816975efc01fd Mon Sep 17 00:00:00 2001 From: alt0 Date: Fri, 12 Aug 2022 11:44:04 +0800 Subject: [PATCH 15/44] fix: left menu color --- libs/components/editor-plugins/src/menu/group-menu/DragItem.tsx | 1 + .../editor-plugins/src/menu/left-menu/LeftMenuDraggable.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/libs/components/editor-plugins/src/menu/group-menu/DragItem.tsx b/libs/components/editor-plugins/src/menu/group-menu/DragItem.tsx index 73ab1fde1c..c6e5d6a9d4 100644 --- a/libs/components/editor-plugins/src/menu/group-menu/DragItem.tsx +++ b/libs/components/editor-plugins/src/menu/group-menu/DragItem.tsx @@ -54,4 +54,5 @@ const StyledButton = styled('div')({ backgroundColor: 'transparent', width: '100%', height: '100%', + color: '#B9CAD5', }); diff --git a/libs/components/editor-plugins/src/menu/left-menu/LeftMenuDraggable.tsx b/libs/components/editor-plugins/src/menu/left-menu/LeftMenuDraggable.tsx index f07380e0b9..f9d9e947d6 100644 --- a/libs/components/editor-plugins/src/menu/left-menu/LeftMenuDraggable.tsx +++ b/libs/components/editor-plugins/src/menu/left-menu/LeftMenuDraggable.tsx @@ -265,6 +265,7 @@ const Draggable = styled(Button)({ backgroundColor: 'transparent', width: '16px', height: '22px', + color: '#B9CAD5', '& svg': { fontSize: '20px', marginLeft: '-2px', From dbd16f33a971aa4f55f8244296958f5ecbcf4a7a Mon Sep 17 00:00:00 2001 From: MingLIang Wang Date: Fri, 12 Aug 2022 13:29:52 +0800 Subject: [PATCH 16/44] feat: repair grid border in selection (#206) --- libs/components/editor-blocks/src/blocks/grid/Grid.tsx | 2 +- libs/components/editor-core/src/editor/selection/selection.ts | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/libs/components/editor-blocks/src/blocks/grid/Grid.tsx b/libs/components/editor-blocks/src/blocks/grid/Grid.tsx index 79ab22d0ad..822270f495 100644 --- a/libs/components/editor-blocks/src/blocks/grid/Grid.tsx +++ b/libs/components/editor-blocks/src/blocks/grid/Grid.tsx @@ -261,7 +261,7 @@ const GridContainer = styled('div')<{ display: 'flex', alignItems: 'stretch', borderRadius: '10px', - border: '1px solid #FFF', + border: '1px solid transparent', minWidth: `${gridItemMinWidth}%`, [`&:hover .${GRID_ITEM_CONTENT_CLASS_NAME}`]: { borderColor: theme.affine.palette.borderColor, diff --git a/libs/components/editor-core/src/editor/selection/selection.ts b/libs/components/editor-core/src/editor/selection/selection.ts index b66d1b5fd6..30e7f4efdd 100644 --- a/libs/components/editor-core/src/editor/selection/selection.ts +++ b/libs/components/editor-core/src/editor/selection/selection.ts @@ -77,9 +77,6 @@ export class SelectionManager implements VirgoSelection { type: 'None', info: null, }; - // IMP: to delete - // @ts-ignore - window['selectionManager'] = this; this._initWindowSelectionChangeListen(); } From 41980188cff9533d9b8a3b0016bbafbba9248beb Mon Sep 17 00:00:00 2001 From: lawvs <18554747+lawvs@users.noreply.github.com> Date: Fri, 12 Aug 2022 13:53:51 +0800 Subject: [PATCH 17/44] chore: update warn message --- libs/components/layout/src/header/LayoutHeader.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/components/layout/src/header/LayoutHeader.tsx b/libs/components/layout/src/header/LayoutHeader.tsx index 604b6caa6d..beade0de0d 100644 --- a/libs/components/layout/src/header/LayoutHeader.tsx +++ b/libs/components/layout/src/header/LayoutHeader.tsx @@ -52,8 +52,8 @@ export const LayoutHeader = () => { - AFFiNE now under active development, the version is - UNSTABLE, please DO NOT store important data in this version + AFFiNE is currently under active development. This build is + UNSTABLE. Please DO NOT store important data. From 7024b7f11a1edd8c410c7528f28973aae6274652 Mon Sep 17 00:00:00 2001 From: lawvs <18554747+lawvs@users.noreply.github.com> Date: Fri, 12 Aug 2022 13:54:11 +0800 Subject: [PATCH 18/44] chore: clean page tree styles --- .../workspace-sidebar/page-tree/DndTree.tsx | 12 +- .../page-tree/tree-item/DndTreeItem.tsx | 6 +- .../page-tree/tree-item/TreeItem.tsx | 73 ++++++------- .../page-tree/tree-item/styles.ts | 103 +++--------------- 4 files changed, 53 insertions(+), 141 deletions(-) diff --git a/libs/components/layout/src/workspace-sidebar/page-tree/DndTree.tsx b/libs/components/layout/src/workspace-sidebar/page-tree/DndTree.tsx index 40991b930c..6db19e3e86 100755 --- a/libs/components/layout/src/workspace-sidebar/page-tree/DndTree.tsx +++ b/libs/components/layout/src/workspace-sidebar/page-tree/DndTree.tsx @@ -36,19 +36,13 @@ export type DndTreeProps = { indentationWidth?: number; collapsible?: boolean; removable?: boolean; - showDragIndicator?: boolean; }; /** * Currently does not support drag and drop using the keyboard. */ export function DndTree(props: DndTreeProps) { - const { - indentationWidth = 20, - collapsible, - removable, - showDragIndicator, - } = props; + const { indentationWidth = 20, collapsible, removable } = props; const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 8 } }) @@ -111,7 +105,6 @@ export function DndTree(props: DndTreeProps) { : depth } indentationWidth={indentationWidth} - indicator={showDragIndicator} childCount={children.length} onCollapse={ collapsible && children.length @@ -129,7 +122,8 @@ export function DndTree(props: DndTreeProps) { )} {activeId && activeItem ? ( ble-node; the ref of the inner dom is often used as draggable-node */ - wrapperRef?(node: HTMLLIElement): void; -} & HTMLAttributes; +} & HTMLAttributes; export const TreeItem = forwardRef( ( @@ -52,13 +48,10 @@ export const TreeItem = forwardRef( ghost, handleProps, indentationWidth, - indicator, collapsed, onCollapse, onRemove, - style, value, - wrapperRef, pageId, ...props }, @@ -71,8 +64,8 @@ export const TreeItem = forwardRef( ); return ( - ( active={pageId === page_id} {...props} > - - - {childCount !== 0 ? ( - collapsed ? ( - - ) : ( - - ) - ) : ( - - )} - + {childCount !== 0 ? ( + collapsed ? ( + + + + ) : ( + + + + ) + ) : ( + + )} - - - {value} - - {BooleanPageTreeItemMoreActions && ( - - )} + + + {value} + + {BooleanPageTreeItemMoreActions && ( + + )} - {/*{!clone && onRemove && }*/} - {clone && childCount && childCount > 1 ? ( - {childCount} - ) : null} - - - + {/*{!clone && onRemove && }*/} + {clone && childCount && childCount > 1 ? ( + {childCount} + ) : null} + + ); } ); diff --git a/libs/components/layout/src/workspace-sidebar/page-tree/tree-item/styles.ts b/libs/components/layout/src/workspace-sidebar/page-tree/tree-item/styles.ts index 0040e5bef7..442715229c 100644 --- a/libs/components/layout/src/workspace-sidebar/page-tree/tree-item/styles.ts +++ b/libs/components/layout/src/workspace-sidebar/page-tree/tree-item/styles.ts @@ -1,22 +1,18 @@ import { styled } from '@toeverything/components/ui'; import { Link } from 'react-router-dom'; -export const TreeItemContainer = styled('div')` - box-sizing: border-box; - display: flex; - align-items: center; - color: #4c6275; -`; - -export const Wrapper = styled('li')<{ +export const TreeItemContainer = styled('div')<{ spacing: string; clone?: boolean; ghost?: boolean; - indicator?: boolean; disableSelection?: boolean; disableInteraction?: boolean; active?: boolean; }>` + display: flex; + align-items: center; + color: #4c6275; + box-sizing: border-box; padding-left: ${({ spacing }) => spacing}; list-style: none; @@ -26,21 +22,13 @@ export const Wrapper = styled('li')<{ ${({ clone, disableSelection }) => (clone || disableSelection) && - `width: 100%; - .Text, - .Count { - user-select: none; - -webkit-user-select: none; - }`} - - ${({ indicator }) => - indicator && - `width: 100%; - .Text, - .Count { - user-select: none; - -webkit-user-select: none; - }`} + ` + width: 100%; + user-select: none; + opacity: 0.7; + background: transparent; + cursor: grab; + `} ${({ disableInteraction }) => disableInteraction && `pointer-events: none;`} @@ -48,64 +36,6 @@ export const Wrapper = styled('li')<{ background: #f5f7f8; border-radius: 5px; } - - &.clone { - display: inline-block; - padding: 0; - margin-left: 10px; - margin-top: 5px; - pointer-events: none; - - ${TreeItemContainer} { - padding-right: 20px; - border-radius: 4px; - box-shadow: 0px 15px 15px 0 rgba(34, 33, 81, 0.1); - } - } - - &.ghost { - &.indicator { - opacity: 1; - position: relative; - z-index: 1; - margin-bottom: -1px; - - ${TreeItemContainer} { - position: relative; - padding: 0; - height: 8px; - border-color: #2389ff; - background-color: #56a1f8; - - &:before { - position: absolute; - left: -8px; - top: -4px; - display: block; - content: ''; - width: 12px; - height: 12px; - border-radius: 50%; - border: 1px solid #2389ff; - } - - > * { - /* Items are hidden using height and opacity to retain focus */ - opacity: 0; - height: 0; - } - } - } - - &:not(.indicator) { - opacity: 0.5; - } - - ${TreeItemContainer} > * { - box-shadow: none; - background-color: transparent; - } - } `; export const Counter = styled('span')` @@ -138,7 +68,7 @@ export const ActionButton = styled('button')<{ border: none; outline: none; appearance: none; - background-color: transparent; + background: transparent; -webkit-tap-highlight-color: transparent; svg { @@ -151,8 +81,7 @@ export const ActionButton = styled('button')<{ } &:active { - background-color: ${({ background }) => - background ?? 'rgba(0, 0, 0, 0.05)'}; + background: ${({ background }) => background ?? 'rgba(0, 0, 0, 0.05)'}; svg { fill: ${({ fill }) => fill ?? '#788491'}; @@ -170,9 +99,7 @@ export const TreeItemMoreActions = styled('div')` visibility: hidden; `; -export const TextLink = styled(Link, { - shouldForwardProp: (prop: string) => !['active'].includes(prop), -})<{ active?: boolean }>` +export const TextLink = styled(Link)` display: flex; align-items: center; flex-grow: 1; From 7177e625176823265915419a1c76ac9cdb52af23 Mon Sep 17 00:00:00 2001 From: MingLIang Wang Date: Fri, 12 Aug 2022 14:58:11 +0800 Subject: [PATCH 19/44] feat: fix selection on scroll --- libs/components/editor-core/src/Selection.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/components/editor-core/src/Selection.tsx b/libs/components/editor-core/src/Selection.tsx index 98ad9ccb1f..6317c9ac0c 100644 --- a/libs/components/editor-core/src/Selection.tsx +++ b/libs/components/editor-core/src/Selection.tsx @@ -233,7 +233,8 @@ export const SelectionRect = forwardRef( startPointRef.current && endPointRef.current && scrollManager.scrollContainer && - scrollContainerRect.current + scrollContainerRect.current && + mouseType.current === 'down' ) { const xSign = DIRECTION_VALUE_MAP[direction[0]] || 0; const ySign = DIRECTION_VALUE_MAP[direction[1]] || 0; From 9be07352076244f0a6ef78f3ec19999ded142373 Mon Sep 17 00:00:00 2001 From: Whitewater Date: Fri, 12 Aug 2022 15:17:51 +0800 Subject: [PATCH 20/44] chore: align tree line (#211) --- .../src/utils/WithTreeViewChildren.tsx | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/libs/components/editor-blocks/src/utils/WithTreeViewChildren.tsx b/libs/components/editor-blocks/src/utils/WithTreeViewChildren.tsx index f2b2b3652d..f26690cbd4 100644 --- a/libs/components/editor-blocks/src/utils/WithTreeViewChildren.tsx +++ b/libs/components/editor-blocks/src/utils/WithTreeViewChildren.tsx @@ -27,7 +27,7 @@ const defaultConfig: WithChildrenConfig = { const TreeView = forwardRef< HTMLDivElement, { lastItem?: boolean } & ComponentPropsWithRef<'div'> ->(({ lastItem, children, onClick, ...restProps }, ref) => { +>(({ lastItem = false, children, onClick, ...restProps }, ref) => { return ( @@ -155,8 +155,10 @@ const Wrapper = styled('div')({ display: 'flex', flexDirection: 'column' }); const Children = Wrapper; const TREE_COLOR = '#D5DFE6'; -// TODO determine the position of the horizontal line by the type of the item -const ITEM_POINT_HEIGHT = '12.5px'; // '50%' +// adjust left and right margins of the the tree line +const TREE_LINE_LEFT_OFFSET = '-16px'; +// determine the position of the horizontal line by the type of the item +const TREE_LINE_TOP_OFFSET = '20px'; // '50%' const TreeWrapper = styled('div')({ position: 'relative', @@ -164,7 +166,7 @@ const TreeWrapper = styled('div')({ const StyledTreeView = styled('div')({ position: 'absolute', - left: '-21px', + left: TREE_LINE_LEFT_OFFSET, height: '100%', }); @@ -183,7 +185,7 @@ const Line = styled('div')({ const VerticalLine = styled(Line)<{ last: boolean }>(({ last }) => ({ width: '1px', - height: last ? ITEM_POINT_HEIGHT : '100%', + height: last ? TREE_LINE_TOP_OFFSET : '100%', paddingTop: 0, paddingBottom: 0, transform: 'translate(-50%, 0)', @@ -196,7 +198,7 @@ const HorizontalLine = styled(Line)<{ last: boolean }>(({ last }) => ({ height: '1px', paddingLeft: 0, paddingRight: 0, - top: ITEM_POINT_HEIGHT, + top: TREE_LINE_TOP_OFFSET, transform: 'translate(0, -50%)', opacity: last ? 0 : 'unset', })); @@ -204,7 +206,8 @@ const HorizontalLine = styled(Line)<{ last: boolean }>(({ last }) => ({ const Collapsed = styled('div')({ cursor: 'pointer', display: 'inline-block', - color: '#B9CAD5', + color: '#98ACBD', + padding: '8px', }); const LastItemRadius = styled('div')({ @@ -212,7 +215,7 @@ const LastItemRadius = styled('div')({ position: 'absolute', left: '-0.5px', top: 0, - height: ITEM_POINT_HEIGHT, + height: TREE_LINE_TOP_OFFSET, bottom: '50%', width: '16px', borderWidth: '1px', From ac84c16a4adaa41a465d1c8bd42f0cd0458344d2 Mon Sep 17 00:00:00 2001 From: QiShaoXuan Date: Fri, 12 Aug 2022 15:23:11 +0800 Subject: [PATCH 21/44] fix: fix color and text of status pendant --- libs/components/editor-core/src/block-pendant/config.ts | 4 ++-- libs/components/editor-core/src/block-pendant/utils.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/components/editor-core/src/block-pendant/config.ts b/libs/components/editor-core/src/block-pendant/config.ts index 077a795142..74471647d2 100644 --- a/libs/components/editor-core/src/block-pendant/config.ts +++ b/libs/components/editor-core/src/block-pendant/config.ts @@ -98,8 +98,8 @@ export const pendantConfig: { [key: string]: PendantConfig } = { }, [PendantTypes.Status]: { iconName: IconNames.STATUS, - background: ['#C5FBE0', '#FFF5AB', '#FFCECE', '#E3DEFF'], - color: ['#05683D', '#896406', '#AF1212', '#511AAB'], + background: ['#FFCECE', '#FFF5AB', '#C5FBE0', '#E3DEFF'], + color: ['#AF1212', '#896406', '#05683D', '#511AAB'], }, [PendantTypes.Select]: { iconName: IconNames.SINGLE_SELECT, diff --git a/libs/components/editor-core/src/block-pendant/utils.ts b/libs/components/editor-core/src/block-pendant/utils.ts index 63a3344a91..bebdef1925 100644 --- a/libs/components/editor-core/src/block-pendant/utils.ts +++ b/libs/components/editor-core/src/block-pendant/utils.ts @@ -88,7 +88,7 @@ export const generateInitialOptions = ( ) => { if (type === PendantTypes.Status) { return [ - generateBasicOption({ index: 0, iconConfig, name: 'No Started' }), + generateBasicOption({ index: 0, iconConfig, name: 'Not Started' }), generateBasicOption({ index: 1, iconConfig, From 551ddf5f94e438ea6443439a8147f2f105cceab9 Mon Sep 17 00:00:00 2001 From: DiamondThree Date: Fri, 12 Aug 2022 15:24:16 +0800 Subject: [PATCH 22/44] fix: code style (#208) * fix: code style * fix: add copy icon --- .../common/src/lib/text/EditableText.tsx | 10 +++++----- .../src/blocks/bullet/BulletView.tsx | 3 ++- .../editor-blocks/src/blocks/code/CodeView.tsx | 17 +++++++++++++++-- .../src/components/style-container/index.tsx | 1 + 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/libs/components/common/src/lib/text/EditableText.tsx b/libs/components/common/src/lib/text/EditableText.tsx index 8970f6a008..3c9ca6fe5c 100644 --- a/libs/components/common/src/lib/text/EditableText.tsx +++ b/libs/components/common/src/lib/text/EditableText.tsx @@ -799,11 +799,11 @@ const EditorLeaf = ({ attributes, children, leaf }: any) => { {customChildren} diff --git a/libs/components/editor-blocks/src/blocks/bullet/BulletView.tsx b/libs/components/editor-blocks/src/blocks/bullet/BulletView.tsx index a2ef05300c..2835a284cf 100644 --- a/libs/components/editor-blocks/src/blocks/bullet/BulletView.tsx +++ b/libs/components/editor-blocks/src/blocks/bullet/BulletView.tsx @@ -40,8 +40,9 @@ const todoIsEmpty = (contentValue: ContentColumnValue): boolean => { (todoValue.length === 1 && !todoValue[0]['text']) ); }; -const BulletLeft = styled('div')(() => ({ +const BulletLeft = styled('div')(({ theme }) => ({ height: '22px', + color: theme.affine.typography.body1.color, })); export const BulletView = ({ block, editor }: CreateView) => { // block.remove(); diff --git a/libs/components/editor-blocks/src/blocks/code/CodeView.tsx b/libs/components/editor-blocks/src/blocks/code/CodeView.tsx index 2d421296ee..680bbea84d 100644 --- a/libs/components/editor-blocks/src/blocks/code/CodeView.tsx +++ b/libs/components/editor-blocks/src/blocks/code/CodeView.tsx @@ -52,6 +52,8 @@ import { BlockPendantProvider, } from '@toeverything/components/editor-core'; import { copyToClipboard } from '@toeverything/utils'; +import { DuplicateIcon } from '@toeverything/components/icons'; + interface CreateCodeView extends CreateView { style9?: StyleWithAtRules; containerClassName?: string; @@ -117,10 +119,21 @@ const CodeBlock = styled('div')(({ theme }) => ({ justifyContent: 'space-between', }, '.copy-block': { - padding: '6px 10px', + padding: '0px 10px', backgroundColor: '#fff', + height: '32px', + display: 'flex', + width: '90px', + justifyContent: 'center', + alignItems: 'center', + color: '#4C6275', + fontSize: '14px', borderRadius: theme.affine.shape.borderRadius, cursor: 'pointer', + svg: { + marginRight: '4px', + display: 'block', + }, }, '.cm-focused': { outline: 'none !important', @@ -187,7 +200,7 @@ export const CodeView = ({ block, editor }: CreateCodeView) => {
- Copy + Copy
diff --git a/libs/components/editor-blocks/src/components/style-container/index.tsx b/libs/components/editor-blocks/src/components/style-container/index.tsx index 28ca5d6aea..b0dfd8dd2f 100644 --- a/libs/components/editor-blocks/src/components/style-container/index.tsx +++ b/libs/components/editor-blocks/src/components/style-container/index.tsx @@ -5,6 +5,7 @@ export const List = styled('div')(({ theme }) => ({ '.checkBoxContainer': { marginRight: '4px', lineHeight: theme.affine.typography.body1.lineHeight, + color: theme.affine.typography.body1.color, }, '.textContainer': { flex: 1, From 7e3df042ecf0312e89cdf2478ce1bdd46f22eb7f Mon Sep 17 00:00:00 2001 From: QiShaoXuan Date: Fri, 12 Aug 2022 16:10:05 +0800 Subject: [PATCH 23/44] feat: update pendant add button --- .../src/block-pendant/AddPendantPopover.tsx | 27 ++++++++++++++++--- .../pendant-popover/PendantPopover.tsx | 1 + 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/libs/components/editor-core/src/block-pendant/AddPendantPopover.tsx b/libs/components/editor-core/src/block-pendant/AddPendantPopover.tsx index bbb61be631..015f8a4120 100644 --- a/libs/components/editor-core/src/block-pendant/AddPendantPopover.tsx +++ b/libs/components/editor-core/src/block-pendant/AddPendantPopover.tsx @@ -4,7 +4,10 @@ import { Popover, type PopoverProps, PopperHandler, + Tag, } from '@toeverything/components/ui'; +import { TagsIcon } from '@toeverything/components/icons'; + import { CreatePendantPanel } from './pendant-operation-panel'; import { IconButton } from './StyledComponent'; import { AsyncBlock } from '../editor'; @@ -13,11 +16,13 @@ type Props = { block: AsyncBlock; onSure?: () => void; iconStyle?: CSSProperties; + useAddIcon?: boolean; } & Omit; export const AddPendantPopover = ({ block, onSure, iconStyle, + useAddIcon = true, ...popoverProps }: Props) => { const popoverHandlerRef = useRef(); @@ -38,9 +43,25 @@ export const AddPendantPopover = ({ style={{ padding: 0 }} {...popoverProps} > - - - + {useAddIcon ? ( + + + + ) : ( + + } + > + Tag App + + )} ); }; diff --git a/libs/components/editor-core/src/block-pendant/pendant-popover/PendantPopover.tsx b/libs/components/editor-core/src/block-pendant/pendant-popover/PendantPopover.tsx index d92a2ab8b4..36d785a181 100644 --- a/libs/components/editor-core/src/block-pendant/pendant-popover/PendantPopover.tsx +++ b/libs/components/editor-core/src/block-pendant/pendant-popover/PendantPopover.tsx @@ -33,6 +33,7 @@ export const PendantPopover = ( }} offset={[0, -30]} trigger="click" + useAddIcon={false} /> } onClose={() => { From 82c3d773d6fb782d05a4528ce9ef7605dbfe2ba9 Mon Sep 17 00:00:00 2001 From: QiShaoXuan Date: Fri, 12 Aug 2022 16:24:51 +0800 Subject: [PATCH 24/44] fix: move the pendant popover to the body layer --- .../src/block-pendant/pendant-popover/PendantPopover.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/components/editor-core/src/block-pendant/pendant-popover/PendantPopover.tsx b/libs/components/editor-core/src/block-pendant/pendant-popover/PendantPopover.tsx index 36d785a181..09ad317777 100644 --- a/libs/components/editor-core/src/block-pendant/pendant-popover/PendantPopover.tsx +++ b/libs/components/editor-core/src/block-pendant/pendant-popover/PendantPopover.tsx @@ -26,7 +26,6 @@ export const PendantPopover = ( block={block} endElement={ { popoverHandlerRef.current?.setVisible(false); From 29c2d8b1a21c90fd130509ae795ec6d15123af0d Mon Sep 17 00:00:00 2001 From: zuomeng wang Date: Fri, 12 Aug 2022 16:38:15 +0800 Subject: [PATCH 25/44] fix: add star badge & add link to contributor badge (#214) --- README.md | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 9e2ebc85ff..7f0c70e371 100644 --- a/README.md +++ b/README.md @@ -12,17 +12,8 @@ Planning, Sorting and Creating all Together. Open-source, Privacy-First, and Fre
- - - -[all-contributors-badge]: https://img.shields.io/badge/all_contributors-23-orange.svg?style=flat-square - - - -[![All Contributors][all-contributors-badge]](#contributors) +[![stars](https://img.shields.io/github/stars/toeverything/AFFiNE.svg?style=flat&logo=github&colorB=deeppink&label=stars)](https://github.com/toeverything/AFFiNE) +[![contributors](https://img.shields.io/github/contributors/toeverything/AFFiNE?color=%23fe7230)](https://github.com/toeverything/AFFiNE/graphs/contributors) [![Node](https://img.shields.io/badge/node->=16.0-success)](https://www.typescriptlang.org/) [![React](https://img.shields.io/badge/TypeScript-4.7-3178c6)](https://www.typescriptlang.org/) [![React](https://img.shields.io/badge/React-18-61dafb)](https://reactjs.org/) From e47268bb8b0a6559527436e7b9d8e810c58b46f4 Mon Sep 17 00:00:00 2001 From: xiaodong zuo Date: Fri, 12 Aug 2022 16:41:37 +0800 Subject: [PATCH 26/44] fix: link style issues --- libs/components/common/src/lib/text/plugins/link.tsx | 11 ++++++----- .../editor-blocks/src/blocks/page/PageView.tsx | 3 +++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/libs/components/common/src/lib/text/plugins/link.tsx b/libs/components/common/src/lib/text/plugins/link.tsx index 60193e38a0..5c1425460d 100644 --- a/libs/components/common/src/lib/text/plugins/link.tsx +++ b/libs/components/common/src/lib/text/plugins/link.tsx @@ -25,7 +25,6 @@ import { ReactEditor } from 'slate-react'; import OpenInNewIcon from '@mui/icons-material/OpenInNew'; import EditIcon from '@mui/icons-material/Edit'; import LinkOffIcon from '@mui/icons-material/LinkOff'; -import AttachmentIcon from '@mui/icons-material/Attachment'; import { MuiTooltip as Tooltip, styled, @@ -39,7 +38,7 @@ import { import { getRandomString } from '../utils'; import { colors } from '../../colors'; - +import { LinkIcon } from '@toeverything/components/icons'; export type LinkElement = { type: 'link'; url: string; @@ -124,7 +123,7 @@ const LinkStyledTooltip = styled(({ className, ...props }: MuiTooltipProps) => ( ))(({ theme }) => ({ [`& .${muiTooltipClasses.tooltip}`]: { backgroundColor: '#fff', - color: '#4C6275', + color: '#3E6FDB', boxShadow: theme.affine.shadows.shadow1, fontSize: '14px', }, @@ -419,8 +418,8 @@ export const LinkModal = memo((props: LinkModalProps) => { }} >
-
({ const styles = style9.create({ linkModalContainerIcon: { + display: 'flex', width: '16px', margin: '0 16px 0 4px', }, @@ -517,6 +517,7 @@ const styles = style9.create({ '::-webkit-input-placeholder': { color: '#98acbd', }, + color: '#4C6275', }, linkMask: { position: 'fixed', diff --git a/libs/components/editor-blocks/src/blocks/page/PageView.tsx b/libs/components/editor-blocks/src/blocks/page/PageView.tsx index 64ea3c9536..4d5de824cf 100644 --- a/libs/components/editor-blocks/src/blocks/page/PageView.tsx +++ b/libs/components/editor-blocks/src/blocks/page/PageView.tsx @@ -119,5 +119,8 @@ const PageTitleBlock = styled('div')(({ theme }) => { '.content': { outline: 'none', }, + a: { + color: '#3e6fdb', + }, }; }); From 174779bb28176b6b7ddca33c56a9329fdfd31334 Mon Sep 17 00:00:00 2001 From: xiaodong zuo Date: Fri, 12 Aug 2022 16:49:17 +0800 Subject: [PATCH 27/44] fix: link style issues --- libs/components/common/src/lib/text/plugins/link.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/components/common/src/lib/text/plugins/link.tsx b/libs/components/common/src/lib/text/plugins/link.tsx index 5c1425460d..619239c64d 100644 --- a/libs/components/common/src/lib/text/plugins/link.tsx +++ b/libs/components/common/src/lib/text/plugins/link.tsx @@ -507,6 +507,7 @@ const styles = style9.create({ display: 'flex', width: '16px', margin: '0 16px 0 4px', + color: '#4C6275', }, linkModalContainerInput: { flex: '1', From edfa95057b47af1fca828268df6bc55a8ec578a3 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Fri, 12 Aug 2022 16:54:47 +0800 Subject: [PATCH 28/44] feat: optional provider --- apps/ligo-virgo/src/pages/RoutePrivate.tsx | 2 +- libs/components/account/src/login/fs.tsx | 8 +- libs/components/account/src/login/index.tsx | 3 +- .../layout/src/header/FileSystem.tsx | 112 ++++++++++++++++++ .../layout/src/header/LayoutHeader.tsx | 6 +- .../db-service/src/services/database/index.ts | 13 +- libs/datasource/jwt/src/adapter/yjs/index.ts | 42 ++++--- .../jwt/src/adapter/yjs/provider.ts | 97 ++++++++------- libs/datasource/jwt/src/index.ts | 1 + libs/datasource/state/src/user.ts | 21 +++- pnpm-lock.yaml | 47 +++++++- 11 files changed, 267 insertions(+), 85 deletions(-) create mode 100644 libs/components/layout/src/header/FileSystem.tsx diff --git a/apps/ligo-virgo/src/pages/RoutePrivate.tsx b/apps/ligo-virgo/src/pages/RoutePrivate.tsx index 58dcbe3db5..a7cedac934 100644 --- a/apps/ligo-virgo/src/pages/RoutePrivate.tsx +++ b/apps/ligo-virgo/src/pages/RoutePrivate.tsx @@ -23,7 +23,7 @@ export function RoutePrivate({ return ; } - if (!user) { + if (!user || !pathname.startsWith(`/${user.id}`)) { return ( { }; export const FileSystem = (props: { onError: () => void }) => { - const onSelected = useLocalTrigger(); + const [, onSelected] = useLocalTrigger(); const apiSupported = useMemo(() => { try { @@ -56,12 +56,6 @@ export const FileSystem = (props: { onError: () => void }) => { } }, []); - useEffect(() => { - if (process.env['NX_E2E']) { - onSelected(); - } - }, []); - return ( - {' '} + new Promise((resolve, reject) => { + const req = indexedDB.deleteDatabase(workspace); + req.addEventListener('error', e => reject(e)); + req.addEventListener('blocked', e => reject(e)); + req.addEventListener('upgradeneeded', e => reject(e)); + req.addEventListener('success', e => resolve(e)); + }); + +const requestPermission = async (workspace: string) => { + await cleanupWorkspace(workspace); + const dirHandler = await window.showDirectoryPicker({ + id: 'AFFiNE_' + workspace, + mode: 'readwrite', + startIn: 'documents', + }); + + const fileHandle = await dirHandler.getFileHandle('affine.db', { + create: true, + }); + const file = await fileHandle.getFile(); + const initialData = new Uint8Array(await file.arrayBuffer()); + + const exporter = async (contents: Uint8Array) => { + try { + const writable = await fileHandle.createWritable(); + await writable.write(contents); + await writable.close(); + } catch (e) { + console.log(e); + } + }; + + await services.api.editorBlock.setupDataExporter( + workspace, + new Uint8Array(initialData), + exporter + ); +}; + +const StyledFileSystem = styled('div')<{ disabled?: boolean }>({ + padding: '10px 12px', + fontWeight: 600, + fontSize: '14px', + color: '#3E6FDB', + textTransform: 'uppercase', + cursor: 'pointer', + '&:hover': { + background: '#F5F7F8', + borderRadius: '5px', + }, +}); + +export const FileSystem = () => { + const [selected, onSelected] = useLocalTrigger(); + const [error, setError] = useState(false); + + const onError = useCallback(() => { + setError(true); + setTimeout(() => setError(false), 3000); + }, []); + + const apiSupported = useMemo(() => { + try { + return 'showOpenFilePicker' in window; + } catch (e) { + return false; + } + }, []); + + if (apiSupported && !selected) { + return ( + <> + setError(false)} + > + + + } + /> + { + try { + await requestPermission('AFFiNE'); + onSelected(); + } catch (e) { + onError(); + } + }} + > + Sync to Disk + + + ); + } + return null; +}; diff --git a/libs/components/layout/src/header/LayoutHeader.tsx b/libs/components/layout/src/header/LayoutHeader.tsx index beade0de0d..bc33736157 100644 --- a/libs/components/layout/src/header/LayoutHeader.tsx +++ b/libs/components/layout/src/header/LayoutHeader.tsx @@ -1,4 +1,4 @@ -import { IconButton, styled, MuiButton } from '@toeverything/components/ui'; +import { IconButton, styled } from '@toeverything/components/ui'; import { LogoIcon, SideBarViewIcon, @@ -7,8 +7,9 @@ import { } from '@toeverything/components/icons'; import { useShowSettingsSidebar } from '@toeverything/datasource/state'; -import { CurrentPageTitle } from './Title'; import { EditorBoardSwitcher } from './EditorBoardSwitcher'; +import { FileSystem } from './FileSystem'; +import { CurrentPageTitle } from './Title'; export const LayoutHeader = () => { const { toggleSettingsSidebar: toggleInfoSidebar, showSettingsSidebar } = @@ -25,6 +26,7 @@ export const LayoutHeader = () => { + Share
void; + type YjsProviders = { awareness: Awareness; - idb: IndexedDBProvider; - binariesIdb: IndexedDBProvider; + binaries: Doc; + doc: Doc; gatekeeper: GateKeeper; connListener: { listeners?: ConnectivityListener }; userId: string; remoteToken?: string; // remote storage token }; + const _yjsDatabaseInstance = new Map(); const _asyncInitLoading = new Set(); @@ -90,13 +90,9 @@ async function _initYjsDatabase( const { userId, token } = options; const doc = new Doc({ autoLoad: true, shouldLoad: true }); - const idb = await new IndexedDBProvider(workspace, doc).whenSynced; + // const idb = await new IndexedDBProvider(workspace, doc).whenSynced; const binaries = new Doc({ autoLoad: true, shouldLoad: true }); - const binariesIdb = await new IndexedDBProvider( - `${workspace}_binaries`, - binaries - ).whenSynced; const awareness = new Awareness(doc); @@ -114,16 +110,24 @@ async function _initYjsDatabase( const emitState = (c: Connectivity) => connListener.listeners?.(workspace, c); await Promise.all( - Object.entries(options.provider).map(async ([, p]) => - p({ awareness, doc, token, workspace, emitState }) - ) + Object.entries(options.provider).flatMap(async ([, p]) => [ + p({ awareness, doc, token, workspace, emitState }), + p({ + awareness, + doc: binaries, + token, + workspace: `${workspace}_binaries`, + emitState, + }), + ]) ); } const newInstance = { awareness, - idb, - binariesIdb, + binaries, + doc, gatekeeper, + connListener, userId, remoteToken: token, @@ -183,7 +187,7 @@ export class YjsAdapter implements AsyncDatabaseAdapter { private constructor(providers: YjsProviders) { this._provider = providers; - this._doc = providers.idb.doc; + this._doc = providers.doc; this._awareness = providers.awareness; this._gatekeeper = providers.gatekeeper; this._reload = () => { @@ -201,7 +205,7 @@ export class YjsAdapter implements AsyncDatabaseAdapter { }); // @ts-ignore this._binaries = new YjsRemoteBinaries( - providers.binariesIdb.doc.getMap(), + providers.binaries.getMap(), providers.remoteToken ); // @ts-ignore @@ -336,7 +340,7 @@ export class YjsAdapter implements AsyncDatabaseAdapter { }); const [file] = (await fromEvent(handles)) as File[]; const binary = await file.arrayBuffer(); - await this._provider.idb.clearData(); + // await this._provider.idb.clearData(); const doc = new Doc({ autoLoad: true, shouldLoad: true }); let updated = 0; let isUpdated = false; @@ -357,8 +361,8 @@ export class YjsAdapter implements AsyncDatabaseAdapter { }; check(); }); - await new IndexedDBProvider(this._provider.idb.name, doc) - .whenSynced; + // await new IndexedDBProvider(this._provider.idb.name, doc) + // .whenSynced; applyUpdate(doc, new Uint8Array(binary)); await update_check; console.log('load success'); diff --git a/libs/datasource/jwt/src/adapter/yjs/provider.ts b/libs/datasource/jwt/src/adapter/yjs/provider.ts index f7068288fd..e4cebd1f01 100644 --- a/libs/datasource/jwt/src/adapter/yjs/provider.ts +++ b/libs/datasource/jwt/src/adapter/yjs/provider.ts @@ -2,6 +2,7 @@ import { Doc } from 'yjs'; import { Awareness } from 'y-protocols/awareness.js'; import { + IndexedDBProvider, SQLiteProvider, WebsocketProvider, } from '@toeverything/datasource/jwt-rpc'; @@ -19,7 +20,10 @@ type YjsDefaultInstances = { export type YjsProvider = (instances: YjsDefaultInstances) => Promise; +type ProviderType = 'idb' | 'sqlite' | 'ws'; + export type YjsProviderOptions = { + enabled: ProviderType[]; backend: typeof BucketBackend[keyof typeof BucketBackend]; params?: Record; importData?: () => Promise | Uint8Array | undefined; @@ -30,53 +34,64 @@ export type YjsProviderOptions = { export const getYjsProviders = ( options: YjsProviderOptions ): Record => { + console.log('getYjsProviders', options); return { + indexeddb: async (instances: YjsDefaultInstances) => { + if (options.enabled.includes('idb')) { + await new IndexedDBProvider(instances.workspace, instances.doc) + .whenSynced; + } + }, sqlite: async (instances: YjsDefaultInstances) => { - const fsHandle = setInterval(async () => { - if (options.hasExporter?.()) { - clearInterval(fsHandle); - const fs = new SQLiteProvider( - instances.workspace, - instances.doc, - await options.importData?.() - ); - if (options.exportData) { - fs.registerExporter(options.exportData); + if (options.enabled.includes('sqlite')) { + const fsHandle = setInterval(async () => { + if (options.hasExporter?.()) { + clearInterval(fsHandle); + const fs = new SQLiteProvider( + instances.workspace, + instances.doc, + await options.importData?.() + ); + if (options.exportData) { + fs.registerExporter(options.exportData); + } + await fs.whenSynced; } - await fs.whenSynced; - } - }, 500); + }, 500); + } }, ws: async (instances: YjsDefaultInstances) => { - if (instances.token) { - const ws = new WebsocketProvider( - instances.token, - options.backend, - instances.workspace, - instances.doc, - { - awareness: instances.awareness, - params: options.params, - } - ) as any; // TODO: type is erased after cascading references + if (options.enabled.includes('ws')) { + if (instances.token) { + const ws = new WebsocketProvider( + instances.token, + options.backend, + instances.workspace, + instances.doc, + { + awareness: instances.awareness, + params: options.params, + } + ) as any; // TODO: type is erased after cascading references - // Wait for ws synchronization to complete, otherwise the data will be modified in reverse, which can be optimized later - return new Promise((resolve, reject) => { - // TODO: synced will also be triggered on reconnection after losing sync - // There needs to be an event mechanism to emit the synchronization state to the upper layer - ws.once('synced', () => resolve()); - ws.once('lost-connection', () => resolve()); - ws.once('connection-error', () => reject()); - ws.on('synced', () => instances.emitState('connected')); - ws.on('lost-connection', () => - instances.emitState('retry') - ); - ws.on('connection-error', () => - instances.emitState('retry') - ); - }); - } else { - return; + // Wait for ws synchronization to complete, otherwise the data will be modified in reverse, which can be optimized later + return new Promise((resolve, reject) => { + // TODO: synced will also be triggered on reconnection after losing sync + // There needs to be an event mechanism to emit the synchronization state to the upper layer + ws.once('synced', () => resolve()); + ws.once('lost-connection', () => resolve()); + ws.once('connection-error', () => reject()); + ws.on('synced', () => instances.emitState('connected')); + ws.on('lost-connection', () => + instances.emitState('retry') + ); + ws.on('connection-error', () => + instances.emitState('retry') + ); + }); + } else { + return; + } } }, }; diff --git a/libs/datasource/jwt/src/index.ts b/libs/datasource/jwt/src/index.ts index ecad7aebf9..38bc11396e 100644 --- a/libs/datasource/jwt/src/index.ts +++ b/libs/datasource/jwt/src/index.ts @@ -618,6 +618,7 @@ export class BlockClient< const instance = await YjsAdapter.init(workspace, { provider: getYjsProviders({ + enabled: [], backend: BucketBackend.YjsWebSocketAffine, importData, exportData, diff --git a/libs/datasource/state/src/user.ts b/libs/datasource/state/src/user.ts index fefb689f0a..d32fef2c56 100644 --- a/libs/datasource/state/src/user.ts +++ b/libs/datasource/state/src/user.ts @@ -1,3 +1,4 @@ +import { useNavigate } from 'react-router'; import { getAuth, onAuthStateChanged, @@ -62,11 +63,17 @@ const BRAND_ID = 'AFFiNE'; const _localTrigger = atom(false); const _useUserAndSpacesForFreeLogin = () => { + const navigate = useNavigate(); const [user, setUser] = useAtom(_userAtom); const [loading, setLoading] = useAtom(_loadingAtom); const [localTrigger] = useAtom(_localTrigger); - useEffect(() => setLoading(false), []); + useEffect(() => { + if (loading) { + navigate('/demo'); + setLoading(false); + } + }, []); useEffect(() => { if (localTrigger) { @@ -77,6 +84,14 @@ const _useUserAndSpacesForFreeLogin = () => { nickname: BRAND_ID, email: '', }); + } else { + setUser({ + photo: '', + id: 'demo', + username: 'demo', + nickname: 'demo', + email: '', + }); } }, [localTrigger, setLoading, setUser]); @@ -86,8 +101,8 @@ const _useUserAndSpacesForFreeLogin = () => { }; export const useLocalTrigger = () => { - const [, setTrigger] = useAtom(_localTrigger); - return () => setTrigger(true); + const [trigger, setTrigger] = useAtom(_localTrigger); + return [trigger, () => setTrigger(true)] as [boolean, () => void]; }; export const useUserAndSpaces = process.env['NX_LOCAL'] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dacfe065aa..a632fb99bd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -194,7 +194,7 @@ importers: yjs: ^13.5.41 dependencies: authing-js-sdk: 4.23.35 - firebase-admin: 11.0.1_@firebase+app-types@0.7.0 + firebase-admin: 11.0.1 lib0: 0.2.52 lru-cache: 7.13.2 nanoid: 4.0.0 @@ -571,6 +571,9 @@ importers: dependencies: ffc-js-client-side-sdk: 1.1.5 + libs/datasource/jwst/pkg: + specifiers: {} + libs/datasource/jwt: specifiers: '@types/debug': ^4.1.7 @@ -3291,6 +3294,15 @@ packages: - utf-8-validate dev: true + /@firebase/auth-interop-types/0.1.6_@firebase+util@1.6.3: + resolution: {integrity: sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==} + peerDependencies: + '@firebase/app-types': 0.x + '@firebase/util': 1.x + dependencies: + '@firebase/util': 1.6.3 + dev: false + /@firebase/auth-interop-types/0.1.6_pbfwexsq7uf6mrzcwnikj3g37m: resolution: {integrity: sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==} peerDependencies: @@ -3299,6 +3311,7 @@ packages: dependencies: '@firebase/app-types': 0.7.0 '@firebase/util': 1.6.3 + dev: true /@firebase/auth-types/0.11.0_pbfwexsq7uf6mrzcwnikj3g37m: resolution: {integrity: sha512-q7Bt6cx+ySj9elQHTsKulwk3+qDezhzRBFC9zlQ1BjgMueUOnGMcvqmU0zuKlQ4RhLSH7MNAdBV2znVaoN3Vxw==} @@ -3334,6 +3347,19 @@ packages: '@firebase/util': 1.6.3 tslib: 2.4.0 + /@firebase/database-compat/0.2.4: + resolution: {integrity: sha512-VtsGixO5mTjNMJn6PwxAJEAR70fj+3blCXIdQKel3q+eYGZAfdqxox1+tzZDnf9NWBJpaOgAHPk3JVDxEo9NFQ==} + dependencies: + '@firebase/component': 0.5.17 + '@firebase/database': 0.13.4 + '@firebase/database-types': 0.9.12 + '@firebase/logger': 0.3.3 + '@firebase/util': 1.6.3 + tslib: 2.4.0 + transitivePeerDependencies: + - '@firebase/app-types' + dev: false + /@firebase/database-compat/0.2.4_@firebase+app-types@0.7.0: resolution: {integrity: sha512-VtsGixO5mTjNMJn6PwxAJEAR70fj+3blCXIdQKel3q+eYGZAfdqxox1+tzZDnf9NWBJpaOgAHPk3JVDxEo9NFQ==} dependencies: @@ -3345,6 +3371,7 @@ packages: tslib: 2.4.0 transitivePeerDependencies: - '@firebase/app-types' + dev: true /@firebase/database-types/0.9.10: resolution: {integrity: sha512-2ji6nXRRsY+7hgU6zRhUtK0RmSjVWM71taI7Flgaw+BnopCo/lDF5HSwxp8z7LtiHlvQqeRA3Ozqx5VhlAbiKg==} @@ -3359,6 +3386,19 @@ packages: '@firebase/app-types': 0.7.0 '@firebase/util': 1.6.3 + /@firebase/database/0.13.4: + resolution: {integrity: sha512-NW7bOoiaC4sJCj6DY/m9xHoFNa0CK32YPMCh6FiMweLCDQbOZM8Ql/Kn6yyuxCb7K7ypz9eSbRlCWQJsJRQjhg==} + dependencies: + '@firebase/auth-interop-types': 0.1.6_@firebase+util@1.6.3 + '@firebase/component': 0.5.17 + '@firebase/logger': 0.3.3 + '@firebase/util': 1.6.3 + faye-websocket: 0.11.4 + tslib: 2.4.0 + transitivePeerDependencies: + - '@firebase/app-types' + dev: false + /@firebase/database/0.13.4_@firebase+app-types@0.7.0: resolution: {integrity: sha512-NW7bOoiaC4sJCj6DY/m9xHoFNa0CK32YPMCh6FiMweLCDQbOZM8Ql/Kn6yyuxCb7K7ypz9eSbRlCWQJsJRQjhg==} dependencies: @@ -3370,6 +3410,7 @@ packages: tslib: 2.4.0 transitivePeerDependencies: - '@firebase/app-types' + dev: true /@firebase/firestore-compat/0.1.23_53yvy43rwpg2c45kgeszsxtrca: resolution: {integrity: sha512-QfcuyMAavp//fQnjSfCEpnbWi7spIdKaXys1kOLu7395fLr+U6ykmto1HUMCSz8Yus9cEr/03Ujdi2SUl2GUAA==} @@ -10863,12 +10904,12 @@ packages: semver-regex: 2.0.0 dev: true - /firebase-admin/11.0.1_@firebase+app-types@0.7.0: + /firebase-admin/11.0.1: resolution: {integrity: sha512-rL3wlZbi2Kb/KJgcmj1YHlD4ZhfmhfgRO2YJialxAllm0tj1IQea878hHuBLGmv4DpbW9t9nLvX9kddNR2Y65Q==} engines: {node: '>=14'} dependencies: '@fastify/busboy': 1.1.0 - '@firebase/database-compat': 0.2.4_@firebase+app-types@0.7.0 + '@firebase/database-compat': 0.2.4 '@firebase/database-types': 0.9.10 '@types/node': 18.0.1 jsonwebtoken: 8.5.1 From ce0590449a89bcb4486ba5471e61ba7216841b9d Mon Sep 17 00:00:00 2001 From: DiamondThree Date: Fri, 12 Aug 2022 17:04:12 +0800 Subject: [PATCH 29/44] fix:setTimeout error (#215) --- .../editor-core/src/editor/selection/selection.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libs/components/editor-core/src/editor/selection/selection.ts b/libs/components/editor-core/src/editor/selection/selection.ts index 38d7f86b0f..406f9adecb 100644 --- a/libs/components/editor-core/src/editor/selection/selection.ts +++ b/libs/components/editor-core/src/editor/selection/selection.ts @@ -48,7 +48,7 @@ export class SelectionManager implements VirgoSelection { private _scrollDelay = 150; private _selectEndDelayTime = 500; private _hasEmitEndPending = false; - + private _scrollTimer: number | null = null; /** * * the selection info before current @@ -681,6 +681,9 @@ export class SelectionManager implements VirgoSelection { */ public async activeNodeByNodeId(nodeId: string, position?: CursorTypes) { try { + if (this._scrollTimer) { + clearTimeout(this._scrollTimer); + } const node = await this._editor.getBlockById(nodeId); if (node) { this._activatedNodeId = nodeId; @@ -689,7 +692,7 @@ export class SelectionManager implements VirgoSelection { } this.emit(nodeId, SelectEventTypes.active, this.lastPoint); // TODO: Optimize the related logic after implementing the scroll bar - setTimeout(() => { + this._scrollTimer = window.setTimeout(() => { this._editor.scrollManager.keepBlockInView(node); }, this._scrollDelay); } else { From 06d4dbc99b5ed75523c136028482e25e367968c6 Mon Sep 17 00:00:00 2001 From: xiaodong zuo Date: Fri, 12 Aug 2022 17:06:21 +0800 Subject: [PATCH 30/44] fix: link style issues --- libs/components/editor-blocks/src/blocks/page/PageView.tsx | 3 --- .../editor-blocks/src/components/text-manage/TextManage.tsx | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/components/editor-blocks/src/blocks/page/PageView.tsx b/libs/components/editor-blocks/src/blocks/page/PageView.tsx index 4d5de824cf..64ea3c9536 100644 --- a/libs/components/editor-blocks/src/blocks/page/PageView.tsx +++ b/libs/components/editor-blocks/src/blocks/page/PageView.tsx @@ -119,8 +119,5 @@ const PageTitleBlock = styled('div')(({ theme }) => { '.content': { outline: 'none', }, - a: { - color: '#3e6fdb', - }, }; }); diff --git a/libs/components/editor-blocks/src/components/text-manage/TextManage.tsx b/libs/components/editor-blocks/src/components/text-manage/TextManage.tsx index 99179220a9..f433c1fba9 100644 --- a/libs/components/editor-blocks/src/components/text-manage/TextManage.tsx +++ b/libs/components/editor-blocks/src/components/text-manage/TextManage.tsx @@ -42,6 +42,9 @@ const TextBlockContainer = styled(Text)(({ theme }) => ({ fontFamily: theme.affine.typography.body1.fontFamily, color: theme.affine.typography.body1.color, letterSpacing: '0.1px', + a: { + color: '#3e6fdb', + }, })); const findSlice = (arr: string[], p: string, q: string) => { From a8e1de079c50731eee6e95e96a202887d50bba8a Mon Sep 17 00:00:00 2001 From: alt0 Date: Fri, 12 Aug 2022 17:41:14 +0800 Subject: [PATCH 31/44] feat: update Get Started template --- apps/ligo-virgo/src/pages/workspace/Home.tsx | 4 +- .../templates/get-started-group0.json | 197 +++++++++++++++++ .../templates/get-started-group1.json | 200 ++++++++++++++++++ .../templates/template-factory.ts | 14 +- 4 files changed, 411 insertions(+), 4 deletions(-) create mode 100644 libs/datasource/db-service/src/services/editor-block/templates/get-started-group0.json create mode 100644 libs/datasource/db-service/src/services/editor-block/templates/get-started-group1.json diff --git a/apps/ligo-virgo/src/pages/workspace/Home.tsx b/apps/ligo-virgo/src/pages/workspace/Home.tsx index a424a2052d..0077fa69be 100644 --- a/apps/ligo-virgo/src/pages/workspace/Home.tsx +++ b/apps/ligo-virgo/src/pages/workspace/Home.tsx @@ -22,8 +22,8 @@ export function WorkspaceHome() { workspace_id, user_initial_page_id, TemplateFactory.generatePageTemplateByGroupKeys({ - name: null, - groupKeys: ['todolist'], + name: '👋 Get Started with AFFINE', + groupKeys: ['getStartedGroup0', 'getStartedGroup1'], }) ); } diff --git a/libs/datasource/db-service/src/services/editor-block/templates/get-started-group0.json b/libs/datasource/db-service/src/services/editor-block/templates/get-started-group0.json new file mode 100644 index 0000000000..1b58d6c2b3 --- /dev/null +++ b/libs/datasource/db-service/src/services/editor-block/templates/get-started-group0.json @@ -0,0 +1,197 @@ +{ + "type": "group", + "properties": { + "metaProps": [ + { + "id": "JaBsu39eY_tAWjJM", + "type": "information", + "name": "Information#XvIJ", + "emailOptions": [ + { + "id": "flJIydQpaSqr9Mvc", + "name": "Email", + "color": "#8f4400", + "background": "#ffcca7", + "iconName": "email" + } + ], + "phoneOptions": [ + { + "id": "DODf7wtotk-n_-sH", + "name": "Phone", + "color": "#263486", + "background": "#c3dbff", + "iconName": "phone" + } + ], + "locationOptions": [ + { + "id": "aYqpJse4RX2n2tuq", + "name": "Location", + "color": "#263486", + "background": "#c5f1f3", + "iconName": "location" + } + ] + }, + { + "id": "tgbUMhgz0yWEPfKj", + "name": "Select#kWWx", + "type": "select", + "options": [ + { + "id": "fIsk1MI2bCq5mtyE", + "name": "Single Select", + "color": "#896406", + "background": "#FFF5AB", + "iconName": "singleSelect" + } + ] + }, + { + "id": "xZld2XD__Cojniv5", + "name": "MultiSelect#tjdY", + "type": "multiSelect", + "options": [ + { + "id": "pXKGBw3_mPXxXIwV", + "name": "Multiple Select", + "color": "#511AAB", + "background": "#E3DEFF", + "iconName": "multiSelect" + } + ] + } + ] + }, + "blocks": [ + { + "type": "heading1", + "properties": { + "text": { + "value": [ + { + "text": "The Essentials. Check things off in Text view" + } + ] + }, + "textStyle": {} + }, + "blocks": [] + }, + { + "type": "todo", + "properties": { + "text": { + "value": [ + { + "text": "Click anywhere and just start typing. Hit `/` to see all the types of content you can add - images, videos, figma files, code blocks, and more." + } + ] + }, + "textStyle": {}, + "collapsed": { "value": false } + }, + "blocks": [] + }, + { + "type": "todo", + "properties": { + "text": { + "value": [ + { "text": "Do you see the " }, + { "text": "⋮", "inlinecode": true }, + { + "text": " when you hover over the left of this checkbox? Click and drag the " + }, + { "text": "⋮", "inlinecode": true }, + { + "text": " to move this block, and see more settings inside." + } + ] + }, + "textStyle": {}, + "collapsed": { "value": false } + }, + "blocks": [] + }, + { + "type": "todo", + "properties": { + "text": { + "value": [ + { "text": "Find the " }, + { "text": "⋮⋮", "inlinecode": true }, + { + "text": " button while hovering anywhere on this group. Click the " + }, + { + "text": "—— + Add New Group Here + ——", + "inlinecode": true + }, + { "text": " divider to add a new group." } + ] + }, + "textStyle": {}, + "collapsed": { "value": false } + }, + "blocks": [] + }, + { + "type": "todo", + "properties": { + "text": { + "value": [ + { "text": "Hover over the " }, + { "text": "——————", "inlinecode": true }, + { + "text": " button at the bottom of every block to evoke Tag App. Click on the " + }, + { "text": "+", "inlinecode": true }, + { + "text": " button following the tagged labels to see more options - status, date, collaborators, text, information, multiple/single select... and you can can expect more options soon!" + } + ] + }, + "textStyle": {}, + "collapsed": { "value": false }, + "recastValues": { + "JaBsu39eY_tAWjJM": { + "id": "JaBsu39eY_tAWjJM", + "type": "information", + "value": { + "email": ["flJIydQpaSqr9Mvc"], + "phone": ["DODf7wtotk-n_-sH"], + "location": ["aYqpJse4RX2n2tuq"] + } + }, + "tgbUMhgz0yWEPfKj": { + "id": "tgbUMhgz0yWEPfKj", + "type": "select", + "value": "fIsk1MI2bCq5mtyE" + }, + "xZld2XD__Cojniv5": { + "id": "xZld2XD__Cojniv5", + "type": "multiSelect", + "value": ["pXKGBw3_mPXxXIwV"] + } + } + }, + "blocks": [] + }, + { + "type": "todo", + "properties": { + "text": { + "value": [ + { + "text": "An incredible design! You can check your tasks in different views, switch between Text View and Kanban View on group menu. → try it out!" + } + ] + }, + "textStyle": {} + }, + "blocks": [] + } + ] +} diff --git a/libs/datasource/db-service/src/services/editor-block/templates/get-started-group1.json b/libs/datasource/db-service/src/services/editor-block/templates/get-started-group1.json new file mode 100644 index 0000000000..96c967a08e --- /dev/null +++ b/libs/datasource/db-service/src/services/editor-block/templates/get-started-group1.json @@ -0,0 +1,200 @@ +{ + "type": "group", + "properties": { + "recastCurrentViewId": "vwjI_O0J2CEsHyMW", + "metaProps": [ + { + "id": "bNjVwBq8YLxvmvr7", + "name": "Status#GVsG", + "type": "status", + "options": [ + { + "id": "wusZ8Qht8rsmFD1s", + "name": "No Started", + "color": "#05683D", + "background": "#C5FBE0", + "iconName": "status" + }, + { + "id": "qLJFK5bqYhW8NLtQ", + "name": "In Progress", + "color": "#896406", + "background": "#FFF5AB", + "iconName": "status" + }, + { + "id": "u0ZgY0sRCnqMkxzn", + "name": "Complete", + "color": "#AF1212", + "background": "#FFCECE", + "iconName": "status" + } + ] + }, + { + "id": "DfVMd-EXXO9-WZTO", + "type": "date", + "name": "Date#0neY", + "background": "#6880FF", + "color": "#fff", + "iconName": "date" + }, + { + "id": "UgQQiv9D-72nRyYy", + "type": "mention", + "name": "Mention#Recx", + "background": "#FFD568", + "color": "#FFFFFF", + "iconName": "collaborator" + }, + { + "id": "ICT1AlKCQkwYLjGs", + "type": "text", + "name": "Text#uObM", + "background": "#67dcaa", + "color": "#FFF", + "iconName": "text" + } + ], + "recastViews": [ + { + "id": "qJslh-HAKnf8gGV-", + "name": "Text", + "type": "page" + }, + { + "id": "vwjI_O0J2CEsHyMW", + "name": "Kanban", + "type": "kanban", + "groupBy": "bNjVwBq8YLxvmvr7" + } + ] + }, + "blocks": [ + { + "type": "text", + "properties": { + "text": { + "value": [ + { + "text": "You can customize a Kanban to fit your workflow. Such as by adding/removing columns or changing Tag App labels." + } + ] + }, + "textStyle": {} + }, + "blocks": [] + }, + { + "type": "text", + "properties": { + "text": { + "value": [{ "text": "Add a Due Date to set a reminder" }] + }, + "recastValues": { + "bNjVwBq8YLxvmvr7": { + "id": "bNjVwBq8YLxvmvr7", + "type": "status", + "value": "wusZ8Qht8rsmFD1s" + }, + "DfVMd-EXXO9-WZTO": { + "id": "DfVMd-EXXO9-WZTO", + "type": "date", + "value": 1660233600000 + } + }, + "textStyle": {} + }, + "blocks": [] + }, + { + "type": "text", + "properties": { + "text": { + "value": [{ "text": "Type @ to mention people" }] + }, + "recastValues": { + "bNjVwBq8YLxvmvr7": { + "id": "bNjVwBq8YLxvmvr7", + "type": "status", + "value": "wusZ8Qht8rsmFD1s" + }, + "UgQQiv9D-72nRyYy": { + "id": "UgQQiv9D-72nRyYy", + "type": "mention", + "value": "AFFiNE" + } + }, + "textStyle": {} + }, + "blocks": [] + }, + { + "type": "todo", + "properties": { + "text": { + "value": [ + { + "text": "Check the box to complete the task!" + } + ] + }, + "recastValues": { + "bNjVwBq8YLxvmvr7": { + "id": "bNjVwBq8YLxvmvr7", + "type": "status", + "value": "qLJFK5bqYhW8NLtQ" + } + }, + "textStyle": {}, + "collapsed": { "value": false } + }, + "blocks": [ + { + "type": "todo", + "properties": { + "text": { "value": [{ "text": "Check 01" }] }, + "textStyle": {}, + "collapsed": { "value": false }, + "checked": { "value": true } + }, + "blocks": [] + }, + { + "type": "todo", + "properties": { + "text": { "value": [{ "text": "Check 02" }] }, + "textStyle": {} + }, + "blocks": [] + } + ] + }, + { + "type": "text", + "properties": { + "text": { + "value": [ + { + "text": "The GroupBy button allows you to customise how you want your data grouped just utilize your preferred Tag App settings" + } + ] + }, + "recastValues": { + "bNjVwBq8YLxvmvr7": { + "id": "bNjVwBq8YLxvmvr7", + "type": "status", + "value": "u0ZgY0sRCnqMkxzn" + }, + "ICT1AlKCQkwYLjGs": { + "id": "ICT1AlKCQkwYLjGs", + "type": "text", + "value": "GroupBy others" + } + }, + "textStyle": {} + }, + "blocks": [] + } + ] +} diff --git a/libs/datasource/db-service/src/services/editor-block/templates/template-factory.ts b/libs/datasource/db-service/src/services/editor-block/templates/template-factory.ts index 88a8d08bbe..10af0eebb9 100644 --- a/libs/datasource/db-service/src/services/editor-block/templates/template-factory.ts +++ b/libs/datasource/db-service/src/services/editor-block/templates/template-factory.ts @@ -3,14 +3,24 @@ import blogTemplate from './blog.json'; import emptyTemplate from './empty.json'; import gridTemplate from './grid.json'; import todoTemplate from './todo.json'; +import getStartedGroup0 from './get-started-group0.json'; +import getStartedGroup1 from './get-started-group1.json'; -export type GroupTemplateKeys = 'todolist' | 'blog' | 'empty' | 'grid'; -type GroupTemplateMap = Record; +export type GroupTemplateKeys = + | 'todolist' + | 'blog' + | 'empty' + | 'grid' + | 'getStartedGroup0' + | 'getStartedGroup1'; +type GroupTemplateMap = Record; const groupTemplateMap = { empty: emptyTemplate, todolist: todoTemplate, blog: blogTemplate, grid: gridTemplate, + getStartedGroup0, + getStartedGroup1, } as GroupTemplateMap; const defaultTemplateList: Array = [ From c0c042dac0bcdcef23cc1341d82b2b3035e2d69e Mon Sep 17 00:00:00 2001 From: DarkSky Date: Fri, 12 Aug 2022 17:49:39 +0800 Subject: [PATCH 32/44] feat: warning tips --- apps/venus/src/app/index.tsx | 2 +- .../layout/src/header/FileSystem.tsx | 16 ++++++------ .../layout/src/header/LayoutHeader.tsx | 25 ++++++++++++++----- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/apps/venus/src/app/index.tsx b/apps/venus/src/app/index.tsx index e3b4a76c90..6939299dfd 100644 --- a/apps/venus/src/app/index.tsx +++ b/apps/venus/src/app/index.tsx @@ -27,7 +27,7 @@ const DiscordIcon = (props: any) => { viewBox="0 0 71 55" fill="currentcolor" > - + ({ }, }); +export const fsApiSupported = () => { + try { + return 'showOpenFilePicker' in window; + } catch (e) { + return false; + } +}; + export const FileSystem = () => { const [selected, onSelected] = useLocalTrigger(); const [error, setError] = useState(false); @@ -68,13 +76,7 @@ export const FileSystem = () => { setTimeout(() => setError(false), 3000); }, []); - const apiSupported = useMemo(() => { - try { - return 'showOpenFilePicker' in window; - } catch (e) { - return false; - } - }, []); + const apiSupported = useMemo(() => fsApiSupported(), []); if (apiSupported && !selected) { return ( diff --git a/libs/components/layout/src/header/LayoutHeader.tsx b/libs/components/layout/src/header/LayoutHeader.tsx index bc33736157..f933348407 100644 --- a/libs/components/layout/src/header/LayoutHeader.tsx +++ b/libs/components/layout/src/header/LayoutHeader.tsx @@ -1,3 +1,5 @@ +import { useMemo } from 'react'; + import { IconButton, styled } from '@toeverything/components/ui'; import { LogoIcon, @@ -5,16 +7,30 @@ import { SearchIcon, SideBarViewCloseIcon, } from '@toeverything/components/icons'; -import { useShowSettingsSidebar } from '@toeverything/datasource/state'; +import { + useShowSettingsSidebar, + useLocalTrigger, +} from '@toeverything/datasource/state'; import { EditorBoardSwitcher } from './EditorBoardSwitcher'; -import { FileSystem } from './FileSystem'; +import { fsApiSupported, FileSystem } from './FileSystem'; import { CurrentPageTitle } from './Title'; export const LayoutHeader = () => { + const [isLocalWorkspace] = useLocalTrigger(); const { toggleSettingsSidebar: toggleInfoSidebar, showSettingsSidebar } = useShowSettingsSidebar(); + const warningTips = useMemo(() => { + if (!fsApiSupported()) { + return 'Your browser does not support the local storage feature, please upgrade to the latest version of Chrome or Edge browser'; + } else if (isLocalWorkspace) { + return 'You are in DEMO mode. Changes will NOT be saved unless you SYNC TO DISK'; + } else { + return 'AFFiNE is under active development and the current version is UNSTABLE. Please DO NOT store information or data'; + } + }, [isLocalWorkspace]); + return ( @@ -53,10 +69,7 @@ export const LayoutHeader = () => { - - AFFiNE is currently under active development. This build is - UNSTABLE. Please DO NOT store important data. - + {warningTips} ); From dc98d4042befa122dbde0147c73b849a80103e5a Mon Sep 17 00:00:00 2001 From: DarkSky Date: Fri, 12 Aug 2022 17:57:40 +0800 Subject: [PATCH 33/44] fix: types --- libs/components/account/src/login/fs.tsx | 1 + libs/components/layout/src/header/FileSystem.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/libs/components/account/src/login/fs.tsx b/libs/components/account/src/login/fs.tsx index bdccdd47ee..2482e5187c 100644 --- a/libs/components/account/src/login/fs.tsx +++ b/libs/components/account/src/login/fs.tsx @@ -17,6 +17,7 @@ const cleanupWorkspace = (workspace: string) => const requestPermission = async (workspace: string) => { await cleanupWorkspace(workspace); + // @ts-ignore const dirHandler = await window.showDirectoryPicker({ id: 'AFFiNE_' + workspace, mode: 'readwrite', diff --git a/libs/components/layout/src/header/FileSystem.tsx b/libs/components/layout/src/header/FileSystem.tsx index a29ad9ea94..83c638c5b3 100644 --- a/libs/components/layout/src/header/FileSystem.tsx +++ b/libs/components/layout/src/header/FileSystem.tsx @@ -17,6 +17,7 @@ const cleanupWorkspace = (workspace: string) => const requestPermission = async (workspace: string) => { await cleanupWorkspace(workspace); + // @ts-ignore const dirHandler = await window.showDirectoryPicker({ id: 'AFFiNE_' + workspace, mode: 'readwrite', From feda6d390e4f523c4b4534cac68bbfc14821b4e8 Mon Sep 17 00:00:00 2001 From: alt0 Date: Fri, 12 Aug 2022 18:24:32 +0800 Subject: [PATCH 34/44] fix: update e2e --- apps/ligo-virgo-e2e/src/integration/app.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ligo-virgo-e2e/src/integration/app.spec.ts b/apps/ligo-virgo-e2e/src/integration/app.spec.ts index 651bf215f2..86404fe6c3 100644 --- a/apps/ligo-virgo-e2e/src/integration/app.spec.ts +++ b/apps/ligo-virgo-e2e/src/integration/app.spec.ts @@ -4,7 +4,7 @@ describe('ligo-virgo', () => { beforeEach(() => cy.visit('/')); it('basic load check', () => { - getTitle().contains('Get Started with AFFiNE'); + getTitle().contains('👋 Get Started with AFFINE'); cy.get('.block_container').contains('The Essentials'); From c67a033f8a8354848109501309214d2ffdf1d62a Mon Sep 17 00:00:00 2001 From: DarkSky Date: Fri, 12 Aug 2022 18:33:25 +0800 Subject: [PATCH 35/44] chore: adjust content --- apps/venus/src/app/index.tsx | 105 +++++++++++++++++++++++++---------- 1 file changed, 76 insertions(+), 29 deletions(-) diff --git a/apps/venus/src/app/index.tsx b/apps/venus/src/app/index.tsx index 6939299dfd..40250c0c6b 100644 --- a/apps/venus/src/app/index.tsx +++ b/apps/venus/src/app/index.tsx @@ -9,6 +9,7 @@ import { Box, Button, Container, Grid, SvgIcon, Typography } from '@mui/joy'; import GitHubIcon from '@mui/icons-material/GitHub'; import RedditIcon from '@mui/icons-material/Reddit'; import TelegramIcon from '@mui/icons-material/Telegram'; +import { LogoIcon } from '@toeverything/components/icons'; // eslint-disable-next-line no-restricted-imports import { useMediaQuery } from '@mui/material'; @@ -203,9 +204,7 @@ const GitHub = (props: { center?: boolean; flat?: boolean }) => { return ( + ); +}; + +const AFFiNEOnline = (props: { center?: boolean; flat?: boolean }) => { + const matches = useMediaQuery('(max-width: 1024px)'); + + return ( + ); }; @@ -258,7 +300,10 @@ export function App() {
diff --git a/libs/components/ui/src/popover/Popover.tsx b/libs/components/ui/src/popover/Popover.tsx index d03a65f322..c165b45197 100644 --- a/libs/components/ui/src/popover/Popover.tsx +++ b/libs/components/ui/src/popover/Popover.tsx @@ -1,6 +1,6 @@ import type { MuiPopperPlacementType as PopperPlacementType } from '../mui'; -import React, { forwardRef, type PropsWithChildren } from 'react'; -import { type PopperHandler, Popper } from '../popper'; +import React, { type PropsWithChildren } from 'react'; +import { Popper } from '../popper'; import { PopoverContainer } from './Container'; import type { PopoverProps, PopoverDirection } from './interface'; @@ -25,15 +25,11 @@ export const placementToContainerDirection: Record< 'auto-end': 'none', }; -export const Popover = forwardRef< - PopperHandler, - PropsWithChildren ->((props, ref) => { +export const Popover = (props: PropsWithChildren) => { const { popoverDirection, placement, content, children, style } = props; return ( ); -}); +}; diff --git a/libs/components/ui/src/popper/Popper.tsx b/libs/components/ui/src/popper/Popper.tsx index f74cf89980..12a6211015 100644 --- a/libs/components/ui/src/popper/Popper.tsx +++ b/libs/components/ui/src/popper/Popper.tsx @@ -1,184 +1,182 @@ import React, { - forwardRef, + CSSProperties, useEffect, useImperativeHandle, useMemo, useRef, useState, } from 'react'; -import { - MuiPopper, - MuiClickAwayListener as ClickAwayListener, - MuiGrow as Grow, -} from '../mui'; +/* eslint-disable no-restricted-imports */ +import PopperUnstyled from '@mui/base/PopperUnstyled'; +/* eslint-disable no-restricted-imports */ +import ClickAwayListener from '@mui/base/ClickAwayListener'; +/* eslint-disable no-restricted-imports */ +import Grow from '@mui/material/Grow'; + import { styled } from '../styled'; -import { PopperProps, PopperHandler, VirtualElement } from './interface'; +import { PopperProps, VirtualElement } from './interface'; import { useTheme } from '../theme'; import { PopperArrow } from './PopoverArrow'; -export const Popper = forwardRef( - ( - { - children, - content, - anchor: propsAnchor, - placement = 'top-start', - defaultVisible = false, - container, - keepMounted = false, - visible: propsVisible, - trigger = 'hover', - pointerEnterDelay = 100, - pointerLeaveDelay = 100, - onVisibleChange, - popoverStyle, - popoverClassName, - anchorStyle, - anchorClassName, - zIndex, - offset = [0, 5], - showArrow = false, - }, - ref - ) => { - const [anchorEl, setAnchorEl] = useState(null); - const [visible, setVisible] = useState(defaultVisible); - const [arrowRef, setArrowRef] = useState(null); +export const Popper = ({ + children, + content, + anchorEl: propsAnchorEl, + placement = 'top-start', + defaultVisible = false, + visible: propsVisible, + trigger = 'hover', + pointerEnterDelay = 100, + pointerLeaveDelay = 100, + onVisibleChange, + popoverStyle, + popoverClassName, + anchorStyle, + anchorClassName, + zIndex, + offset = [0, 5], + showArrow = false, + popperHandlerRef, + ...popperProps +}: PopperProps) => { + const [anchorEl, setAnchorEl] = useState(null); + const [visible, setVisible] = useState(defaultVisible); + const [arrowRef, setArrowRef] = useState(null); + const popperRef = useRef(); + const pointerLeaveTimer = useRef(); + const pointerEnterTimer = useRef(); - const pointerLeaveTimer = useRef(); - const pointerEnterTimer = useRef(); - - const visibleControlledByParent = typeof propsVisible !== 'undefined'; - const isAnchorCustom = typeof propsAnchor !== 'undefined'; - - const hasHoverTrigger = useMemo(() => { - return ( - trigger === 'hover' || - (Array.isArray(trigger) && trigger.includes('hover')) - ); - }, [trigger]); - - const hasClickTrigger = useMemo(() => { - return ( - trigger === 'click' || - (Array.isArray(trigger) && trigger.includes('click')) - ); - }, [trigger]); - - const theme = useTheme(); - - const onPointerEnterHandler = () => { - if (!hasHoverTrigger || visibleControlledByParent) { - return; - } - window.clearTimeout(pointerLeaveTimer.current); - - pointerEnterTimer.current = window.setTimeout(() => { - setVisible(true); - }, pointerEnterDelay); - }; - - const onPointerLeaveHandler = () => { - if (!hasHoverTrigger || visibleControlledByParent) { - return; - } - window.clearTimeout(pointerEnterTimer.current); - pointerLeaveTimer.current = window.setTimeout(() => { - setVisible(false); - }, pointerLeaveDelay); - }; - - useEffect(() => { - onVisibleChange?.(visible); - }, [visible, onVisibleChange]); - - useImperativeHandle(ref, () => { - return { - setVisible: (visible: boolean) => { - !visibleControlledByParent && setVisible(visible); - }, - }; - }); + const visibleControlledByParent = typeof propsVisible !== 'undefined'; + const isAnchorCustom = typeof propsAnchorEl !== 'undefined'; + const hasHoverTrigger = useMemo(() => { return ( - { - setVisible(false); - }} - > - - {isAnchorCustom ? null : ( -
setAnchorEl(dom)} - onClick={() => { - if ( - !hasClickTrigger || - visibleControlledByParent - ) { - return; - } - setVisible(!visible); - }} - onPointerEnter={onPointerEnterHandler} - onPointerLeave={onPointerLeaveHandler} - style={anchorStyle} - className={anchorClassName} - > - {children} -
- )} - - {({ TransitionProps }) => ( - -
- {showArrow && ( - - )} - {content} -
-
- )} -
-
-
+ trigger === 'hover' || + (Array.isArray(trigger) && trigger.includes('hover')) ); - } -); + }, [trigger]); + + const hasClickTrigger = useMemo(() => { + return ( + trigger === 'click' || + (Array.isArray(trigger) && trigger.includes('click')) + ); + }, [trigger]); + + const onPointerEnterHandler = () => { + if (!hasHoverTrigger || visibleControlledByParent) { + return; + } + window.clearTimeout(pointerLeaveTimer.current); + + pointerEnterTimer.current = window.setTimeout(() => { + setVisible(true); + }, pointerEnterDelay); + }; + + const onPointerLeaveHandler = () => { + if (!hasHoverTrigger || visibleControlledByParent) { + return; + } + window.clearTimeout(pointerEnterTimer.current); + pointerLeaveTimer.current = window.setTimeout(() => { + setVisible(false); + }, pointerLeaveDelay); + }; + + useEffect(() => { + onVisibleChange?.(visible); + }, [visible, onVisibleChange]); + + useImperativeHandle(popperHandlerRef, () => { + return { + setVisible: (visible: boolean) => { + !visibleControlledByParent && setVisible(visible); + }, + }; + }); + + return ( + { + setVisible(false); + }} + > + + {isAnchorCustom ? null : ( +
setAnchorEl(dom)} + onClick={() => { + if (!hasClickTrigger || visibleControlledByParent) { + return; + } + setVisible(!visible); + }} + onPointerEnter={onPointerEnterHandler} + onPointerLeave={onPointerLeaveHandler} + style={anchorStyle} + className={anchorClassName} + > + {children} +
+ )} + + {({ TransitionProps }) => ( + +
+ {showArrow && ( + + )} + {content} +
+
+ )} +
+
+
+ ); +}; // The children of ClickAwayListener must be a DOM Node to judge whether the click is outside, use node.contains const Container = styled('div')({ display: 'contents', }); + +const BasicStyledPopper = styled(PopperUnstyled)<{ + zIndex?: CSSProperties['zIndex']; +}>(({ zIndex, theme }) => { + return { + zIndex: zIndex || theme.affine.zIndex.popover, + }; +}); diff --git a/libs/components/ui/src/popper/interface.ts b/libs/components/ui/src/popper/interface.ts index 9f52cf551f..4930de15f1 100644 --- a/libs/components/ui/src/popper/interface.ts +++ b/libs/components/ui/src/popper/interface.ts @@ -1,6 +1,9 @@ -import type { CSSProperties, ReactNode } from 'react'; -import type { MuiPopperPlacementType as PopperPlacementType } from '../mui'; - +import type { CSSProperties, ReactNode, Ref } from 'react'; +/* eslint-disable no-restricted-imports */ +import { + type PopperUnstyledProps, + type PopperPlacementType, +} from '@mui/base/PopperUnstyled'; export type VirtualElement = { getBoundingClientRect: () => ClientRect | DOMRect; contextElement?: Element; @@ -21,26 +24,12 @@ export type PopperProps = { // Popover trigger children?: ReactNode; - // Position of Popover - placement?: PopperPlacementType; - - // The popover will pop up based on the anchor position - // And if this parameter is passed, children will not be rendered - anchor?: VirtualElement | (() => VirtualElement); - // Whether the default is implicit defaultVisible?: boolean; // Used to manually control the visibility of the Popover visible?: boolean; - // A HTML element or function that returns one. The container will have the portal children appended to it. - // By default, it uses the body of the top-level document object, so it's simply document.body most of the time. - container?: HTMLElement; - - // Always keep the children in the DOM. This prop can be useful in SEO situation or when you want to maximize the responsiveness of the Popper - keepMounted?: boolean; - // TODO: support focus trigger?: 'hover' | 'click' | 'focus' | ('click' | 'hover' | 'focus')[]; @@ -66,9 +55,11 @@ export type PopperProps = { anchorClassName?: string; // Popover z-index - zIndex?: number; + zIndex?: CSSProperties['zIndex']; offset?: [number, number]; showArrow?: boolean; -}; + + popperHandlerRef?: Ref; +} & Omit; diff --git a/libs/components/ui/src/tooltip/Tooltip.tsx b/libs/components/ui/src/tooltip/Tooltip.tsx index 2c896e5c0d..ff0b47262c 100644 --- a/libs/components/ui/src/tooltip/Tooltip.tsx +++ b/libs/components/ui/src/tooltip/Tooltip.tsx @@ -1,5 +1,5 @@ -import { forwardRef, type PropsWithChildren, type CSSProperties } from 'react'; -import { type PopperHandler, type PopperProps, Popper } from '../popper'; +import { type PropsWithChildren, type CSSProperties } from 'react'; +import { type PopperProps, Popper } from '../popper'; import { PopoverContainer, placementToContainerDirection } from '../popover'; import type { TooltipProps } from './interface'; import { useTheme } from '../theme'; @@ -14,17 +14,15 @@ const useTooltipStyle = (): CSSProperties => { }; }; -export const Tooltip = forwardRef< - PopperHandler, - PropsWithChildren ->((props, ref) => { +export const Tooltip = ( + props: PropsWithChildren +) => { const { content, placement = 'top-start' } = props; const style = useTooltipStyle(); // If there is no content, hide forever const visibleProp = content ? {} : { visible: false }; return ( ); -}); +}; From 4944887d4a22237d34e1b8be1e8a63bf69525a9f Mon Sep 17 00:00:00 2001 From: QiShaoXuan Date: Fri, 12 Aug 2022 18:51:56 +0800 Subject: [PATCH 39/44] fix: fix pendant popover obscured by screen --- .../editor-core/src/block-pendant/AddPendantPopover.tsx | 8 +++++++- .../src/block-pendant/pendant-modify-panel/types.ts | 1 + .../pendant-operation-panel/CreatePendantPanel.tsx | 6 ++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/libs/components/editor-core/src/block-pendant/AddPendantPopover.tsx b/libs/components/editor-core/src/block-pendant/AddPendantPopover.tsx index 015f8a4120..ce5dc6cdc9 100644 --- a/libs/components/editor-core/src/block-pendant/AddPendantPopover.tsx +++ b/libs/components/editor-core/src/block-pendant/AddPendantPopover.tsx @@ -5,6 +5,7 @@ import { type PopoverProps, PopperHandler, Tag, + type PopperProps, } from '@toeverything/components/ui'; import { TagsIcon } from '@toeverything/components/icons'; @@ -26,9 +27,11 @@ export const AddPendantPopover = ({ ...popoverProps }: Props) => { const popoverHandlerRef = useRef(); + const popperRef = useRef(); return ( { + popperRef.current?.update?.(); + }} /> } placement="bottom-start" diff --git a/libs/components/editor-core/src/block-pendant/pendant-modify-panel/types.ts b/libs/components/editor-core/src/block-pendant/pendant-modify-panel/types.ts index a7ed9e0198..5afbb29ad5 100644 --- a/libs/components/editor-core/src/block-pendant/pendant-modify-panel/types.ts +++ b/libs/components/editor-core/src/block-pendant/pendant-modify-panel/types.ts @@ -11,6 +11,7 @@ export type ModifyPanelProps = { iconConfig?: PendantConfig; isStatusSelect?: boolean; property?: RecastMetaProperty; + onTypeChange?: (type: PendantTypes) => void; }; export type ModifyPanelContentProps = { diff --git a/libs/components/editor-core/src/block-pendant/pendant-operation-panel/CreatePendantPanel.tsx b/libs/components/editor-core/src/block-pendant/pendant-operation-panel/CreatePendantPanel.tsx index 42e36e063f..88fd5b039f 100644 --- a/libs/components/editor-core/src/block-pendant/pendant-operation-panel/CreatePendantPanel.tsx +++ b/libs/components/editor-core/src/block-pendant/pendant-operation-panel/CreatePendantPanel.tsx @@ -24,9 +24,11 @@ import { useOnCreateSure } from './hooks'; export const CreatePendantPanel = ({ block, onSure, + onTypeChange, }: { block: AsyncBlock; onSure?: () => void; + onTypeChange?: (option: PendantOptions) => void; }) => { const [selectedOption, setSelectedOption] = useState(); const [fieldName, setFieldName] = useState(''); @@ -37,6 +39,10 @@ export const CreatePendantPanel = ({ setFieldName(generateRandomFieldName(selectedOption.type)); }, [selectedOption]); + useEffect(() => { + onTypeChange?.(selectedOption); + }, [selectedOption, onTypeChange]); + return ( Add Field From ea865eb0aa66b88102f3790a27dc170758d9ad51 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Fri, 12 Aug 2022 18:47:09 +0800 Subject: [PATCH 40/44] chore: adjust styles --- apps/venus/src/app/index.tsx | 2 +- libs/components/layout/src/header/LayoutHeader.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/venus/src/app/index.tsx b/apps/venus/src/app/index.tsx index 40250c0c6b..b40f3deee8 100644 --- a/apps/venus/src/app/index.tsx +++ b/apps/venus/src/app/index.tsx @@ -235,7 +235,7 @@ const GitHub = (props: { center?: boolean; flat?: boolean }) => { startIcon={} size="lg" > - Check GitHub + {props.center ? 'Check ' : ''}GitHub ); }; diff --git a/libs/components/layout/src/header/LayoutHeader.tsx b/libs/components/layout/src/header/LayoutHeader.tsx index f933348407..5118233907 100644 --- a/libs/components/layout/src/header/LayoutHeader.tsx +++ b/libs/components/layout/src/header/LayoutHeader.tsx @@ -24,7 +24,7 @@ export const LayoutHeader = () => { const warningTips = useMemo(() => { if (!fsApiSupported()) { return 'Your browser does not support the local storage feature, please upgrade to the latest version of Chrome or Edge browser'; - } else if (isLocalWorkspace) { + } else if (!isLocalWorkspace) { return 'You are in DEMO mode. Changes will NOT be saved unless you SYNC TO DISK'; } else { return 'AFFiNE is under active development and the current version is UNSTABLE. Please DO NOT store information or data'; From 6368485cf315aaa2e2614b42d92366c8b9f55f2f Mon Sep 17 00:00:00 2001 From: lawvs <18554747+lawvs@users.noreply.github.com> Date: Fri, 12 Aug 2022 19:01:20 +0800 Subject: [PATCH 41/44] chore: tweak styles --- .../editor-blocks/src/utils/WithTreeViewChildren.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libs/components/editor-blocks/src/utils/WithTreeViewChildren.tsx b/libs/components/editor-blocks/src/utils/WithTreeViewChildren.tsx index f26690cbd4..f4fdd29240 100644 --- a/libs/components/editor-blocks/src/utils/WithTreeViewChildren.tsx +++ b/libs/components/editor-blocks/src/utils/WithTreeViewChildren.tsx @@ -159,6 +159,7 @@ const TREE_COLOR = '#D5DFE6'; const TREE_LINE_LEFT_OFFSET = '-16px'; // determine the position of the horizontal line by the type of the item const TREE_LINE_TOP_OFFSET = '20px'; // '50%' +const TREE_LINE_WIDTH = '12px'; const TreeWrapper = styled('div')({ position: 'relative', @@ -194,7 +195,7 @@ const VerticalLine = styled(Line)<{ last: boolean }>(({ last }) => ({ })); const HorizontalLine = styled(Line)<{ last: boolean }>(({ last }) => ({ - width: '16px', + width: TREE_LINE_WIDTH, height: '1px', paddingLeft: 0, paddingRight: 0, @@ -217,7 +218,7 @@ const LastItemRadius = styled('div')({ top: 0, height: TREE_LINE_TOP_OFFSET, bottom: '50%', - width: '16px', + width: TREE_LINE_WIDTH, borderWidth: '1px', borderStyle: 'solid', borderLeftColor: TREE_COLOR, From c9a5fe3d25328fd669ff6a97ffb4431630920e9a Mon Sep 17 00:00:00 2001 From: QiShaoXuan Date: Fri, 12 Aug 2022 19:01:40 +0800 Subject: [PATCH 42/44] fix: Property zIndex does not exist on type CSSProperties --- libs/components/ui/src/popper/Popper.tsx | 4 +--- libs/components/ui/src/popper/interface.ts | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/libs/components/ui/src/popper/Popper.tsx b/libs/components/ui/src/popper/Popper.tsx index 12a6211015..4cf9f6a78b 100644 --- a/libs/components/ui/src/popper/Popper.tsx +++ b/libs/components/ui/src/popper/Popper.tsx @@ -1,5 +1,4 @@ import React, { - CSSProperties, useEffect, useImperativeHandle, useMemo, @@ -16,7 +15,6 @@ import Grow from '@mui/material/Grow'; import { styled } from '../styled'; import { PopperProps, VirtualElement } from './interface'; -import { useTheme } from '../theme'; import { PopperArrow } from './PopoverArrow'; export const Popper = ({ children, @@ -174,7 +172,7 @@ const Container = styled('div')({ }); const BasicStyledPopper = styled(PopperUnstyled)<{ - zIndex?: CSSProperties['zIndex']; + zIndex?: number; }>(({ zIndex, theme }) => { return { zIndex: zIndex || theme.affine.zIndex.popover, diff --git a/libs/components/ui/src/popper/interface.ts b/libs/components/ui/src/popper/interface.ts index 4930de15f1..d60fac29f7 100644 --- a/libs/components/ui/src/popper/interface.ts +++ b/libs/components/ui/src/popper/interface.ts @@ -55,7 +55,7 @@ export type PopperProps = { anchorClassName?: string; // Popover z-index - zIndex?: CSSProperties['zIndex']; + zIndex?: number; offset?: [number, number]; From 043389524ab5660a4d8943e10b7a754cd0a7ee46 Mon Sep 17 00:00:00 2001 From: HeJiachen-PM <79301703+HeJiachen-PM@users.noreply.github.com> Date: Fri, 12 Aug 2022 19:12:36 +0800 Subject: [PATCH 43/44] REALEASE: WEB LIVE DEMO --- README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7f0c70e371..0046a3d3d1 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,23 @@ Planning, Sorting and Creating all Together. Open-source, Privacy-First, and Fre ![952cd7a5-70fe-48ab-b74f-23981d94d2c5](https://user-images.githubusercontent.com/79301703/182365526-df074c64-cee4-45f6-b8e0-b912f17332c6.gif) # How to use +

+🥳🥳🥳 Our web live demo is out! 🥳🥳🥳 +

-If you have experience in front-end development, you may wish to refer to our [documentation](https://docs.affine.pro/affine/basic-documentation/contribute-to-affine) to learn more about deploying your own version or contributing further to development. For those intersting in trying our latest version, please bear with us as we are planning to launch a web version soon. +

+Start to try out AFFiNE web version on our landing page: +

+ +

+ check_live_demo +

+ +If you have experience in front-end development, you may wish to refer to our [documentation](https://docs.affine.pro/affine/basic-documentation/contribute-to-affine) to learn more about deploying your own version or contributing further to development. Also, thanks to Lee who has made a [desktop build with Tauri](https://github.com/m1911star/affine-client) for you to try out. Please notice that AFFiNE is still under Alpha stage and is not ready for production use. From ebed12dbecbf0b93fc49f07f27e5c914b790efc9 Mon Sep 17 00:00:00 2001 From: HeJiachen-PM <79301703+HeJiachen-PM@users.noreply.github.com> Date: Fri, 12 Aug 2022 19:16:17 +0800 Subject: [PATCH 44/44] fix:typo --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0046a3d3d1..b9fdf16e0b 100644 --- a/README.md +++ b/README.md @@ -37,11 +37,11 @@ Planning, Sorting and Creating all Together. Open-source, Privacy-First, and Fre # How to use

-🥳🥳🥳 Our web live demo is out! 🥳🥳🥳 +🥳🥳🥳 Our web live demo is ready! 🥳🥳🥳

-Start to try out AFFiNE web version on our landing page: +Start to play with AFFiNE web version on our landing page: