update #1

Merged
Chuyaoyuan merged 4 commits from v2 into v2-bak 2026-06-15 14:31:36 +08:00
55 changed files with 5754 additions and 5277 deletions
Showing only changes of commit 8a16b01e9e - Show all commits
+10
View File
@@ -97,6 +97,12 @@ test.db1:
```bash ```bash
cd cyynote-backend cd cyynote-backend
mvn clean package mvn clean package
mvn clean package -DskipTests
mvn solon:run
java -jar demo.jar
java -jar target/cyynote.jar java -jar target/cyynote.jar
``` ```
@@ -177,7 +183,11 @@ VITE_WS_URL=ws://localhost:1234
```bash ```bash
cd cyynote-frontend cd cyynote-frontend
若网络不好
pnpm config set registry https://registry.npmmirror.com/
pnpm install pnpm install
pnpm dev pnpm dev
``` ```
Binary file not shown.
+1 -1
View File
@@ -1,3 +1,3 @@
test.db1: test.db1:
jdbcUrl: "jdbc:sqlite:D:/cyynote.db" jdbcUrl: "jdbc:sqlite:./cyynote.db"
driverClassName: "org.sqlite.JDBC" driverClassName: "org.sqlite.JDBC"
+2 -1
View File
@@ -32,7 +32,8 @@ CREATE TABLE `history` (
conjson TEXT, conjson TEXT,
createtime TEXT, createtime TEXT,
userid INTEGER, userid INTEGER,
username TEXT username TEXT,
flag INTEGER DEFAULT 1
); );
CREATE TABLE appx ( CREATE TABLE appx (
app_id INTEGER PRIMARY KEY AUTOINCREMENT, app_id INTEGER PRIMARY KEY AUTOINCREMENT,
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,5 @@
#Generated by Maven #Generated by Maven
#Wed Apr 29 15:30:28 CST 2026 #Thu May 14 15:10:41 CST 2026
groupId=com.cyynote groupId=com.cyynote
artifactId=cyynote artifactId=cyynote
version=1.0.0 version=1.0.0
+2 -1
View File
@@ -1,4 +1,3 @@
{ {
"name": "cyynote", "name": "cyynote",
"private": true, "private": true,
@@ -42,6 +41,7 @@
"html-react-parser": "^5.2.0", "html-react-parser": "^5.2.0",
"katex": "^0.16.21", "katex": "^0.16.21",
"lexical": "0.44.0", "lexical": "0.44.0",
"lodash-es": "^4.18.1",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-hook-form": "^7.55.0", "react-hook-form": "^7.55.0",
@@ -52,6 +52,7 @@
"zustand": "^5.0.3" "zustand": "^5.0.3"
}, },
"devDependencies": { "devDependencies": {
"@types/lodash-es": "^4.17.12",
"@types/react": "^18.3.20", "@types/react": "^18.3.20",
"@types/react-dom": "^18.3.5", "@types/react-dom": "^18.3.5",
"@typescript-eslint/eslint-plugin": "^8.30.0", "@typescript-eslint/eslint-plugin": "^8.30.0",
+5537
View File
File diff suppressed because it is too large Load Diff
@@ -10,9 +10,9 @@ import {AutoFocusPlugin} from '@lexical/react/LexicalAutoFocusPlugin';
import {CharacterLimitPlugin} from '@lexical/react/LexicalCharacterLimitPlugin'; import {CharacterLimitPlugin} from '@lexical/react/LexicalCharacterLimitPlugin';
import {CheckListPlugin} from '@lexical/react/LexicalCheckListPlugin'; import {CheckListPlugin} from '@lexical/react/LexicalCheckListPlugin';
import {ClearEditorPlugin} from '@lexical/react/LexicalClearEditorPlugin'; import {ClearEditorPlugin} from '@lexical/react/LexicalClearEditorPlugin';
import LexicalClickableLinkPlugin from '@lexical/react/LexicalClickableLinkPlugin'; import {ClickableLinkPlugin} from '@lexical/react/LexicalClickableLinkPlugin';
import {CollaborationPlugin} from '@lexical/react/LexicalCollaborationPlugin'; import {CollaborationPlugin} from '@lexical/react/LexicalCollaborationPlugin';
import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary'; import {LexicalErrorBoundary} from '@lexical/react/LexicalErrorBoundary';
import {HashtagPlugin} from '@lexical/react/LexicalHashtagPlugin'; import {HashtagPlugin} from '@lexical/react/LexicalHashtagPlugin';
import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin'; import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin';
import {HorizontalRulePlugin} from '@lexical/react/LexicalHorizontalRulePlugin'; import {HorizontalRulePlugin} from '@lexical/react/LexicalHorizontalRulePlugin';
@@ -21,7 +21,7 @@ import {PlainTextPlugin} from '@lexical/react/LexicalPlainTextPlugin';
import {RichTextPlugin} from '@lexical/react/LexicalRichTextPlugin'; import {RichTextPlugin} from '@lexical/react/LexicalRichTextPlugin';
import {TabIndentationPlugin} from '@lexical/react/LexicalTabIndentationPlugin'; import {TabIndentationPlugin} from '@lexical/react/LexicalTabIndentationPlugin';
import {TablePlugin} from '@lexical/react/LexicalTablePlugin'; import {TablePlugin} from '@lexical/react/LexicalTablePlugin';
import useLexicalEditable from '@lexical/react/useLexicalEditable'; import {useLexicalEditable} from '@lexical/react/useLexicalEditable';
import {useEffect, useState,useLayoutEffect} from 'react'; import {useEffect, useState,useLayoutEffect} from 'react';
import {CAN_USE_DOM} from './shared/src/canUseDOM'; import {CAN_USE_DOM} from './shared/src/canUseDOM';
@@ -221,7 +221,7 @@ export default function Editor(props: { noteId: bigint; noteTitle: string; editD
<TwitterPlugin /> <TwitterPlugin />
<YouTubePlugin /> <YouTubePlugin />
<FigmaPlugin /> <FigmaPlugin />
{!isEditable && <LexicalClickableLinkPlugin />} {!isEditable && <ClickableLinkPlugin />}
<HorizontalRulePlugin /> <HorizontalRulePlugin />
<EquationsPlugin /> <EquationsPlugin />
<ExcalidrawPlugin /> <ExcalidrawPlugin />
@@ -9,6 +9,7 @@ import "./index.css";
import {$createLinkNode} from '@lexical/link'; import {$createLinkNode} from '@lexical/link';
import {$createListItemNode, $createListNode} from '@lexical/list'; import {$createListItemNode, $createListNode} from '@lexical/list';
import {LexicalComposer} from '@lexical/react/LexicalComposer'; import {LexicalComposer} from '@lexical/react/LexicalComposer';
import {LexicalCollaboration} from '@lexical/react/LexicalCollaborationContext';
import {$createHeadingNode, $createQuoteNode} from '@lexical/rich-text'; import {$createHeadingNode, $createQuoteNode} from '@lexical/rich-text';
import {$createParagraphNode, $createTextNode, $getRoot} from 'lexical'; import {$createParagraphNode, $createTextNode, $getRoot} from 'lexical';
@@ -121,13 +122,27 @@ function LexicalApp(props: { noteId: bigint; editData: object | null; noteTitle:
console.log(props.editData!==null) console.log(props.editData!==null)
console.log(prepopulatedRichText) console.log(prepopulatedRichText)
const resolveEditorState = (): string | null | undefined => {
if (props.editData === null) return undefined; // use prepopulatedRichText via function below
const raw = props.editData as unknown as string;
if (typeof raw === 'string') {
try {
JSON.parse(raw);
return raw;
} catch {
return null; // not valid Lexical JSON, start with empty editor
}
}
return undefined;
};
const editorState = resolveEditorState();
const resolvedEditorState = editorState === undefined && props.editData === null
? prepopulatedRichText
: editorState;
const initialConfig = { const initialConfig = {
// editorState: isCollab editorState: resolvedEditorState,
// ? null
// : emptyEditor
// ? undefined
// : prepopulatedRichText,
editorState: props.editData!==null?props.editData:prepopulatedRichText,
namespace: 'Playground', namespace: 'Playground',
nodes: [...PlaygroundNodes], nodes: [...PlaygroundNodes],
onError: (error: Error) => { onError: (error: Error) => {
@@ -137,6 +152,7 @@ function LexicalApp(props: { noteId: bigint; editData: object | null; noteTitle:
}; };
return ( return (
<LexicalCollaboration>
<LexicalComposer initialConfig={initialConfig}> <LexicalComposer initialConfig={initialConfig}>
<SharedHistoryContext> <SharedHistoryContext>
<TableContext> <TableContext>
@@ -159,6 +175,7 @@ function LexicalApp(props: { noteId: bigint; editData: object | null; noteTitle:
</TableContext> </TableContext>
</SharedHistoryContext> </SharedHistoryContext>
</LexicalComposer> </LexicalComposer>
</LexicalCollaboration>
); );
} }
@@ -46,9 +46,8 @@ header h1 {
} }
.editor-shell { .editor-shell {
margin: 20px auto; margin: 0;
border-radius: 2px; border-radius: 2px;
max-width: 1100px;
color: #000; color: #000;
position: relative; position: relative;
line-height: 1.7; line-height: 1.7;
@@ -61,6 +60,7 @@ header h1 {
display: block; display: block;
border-bottom-left-radius: 10px; border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px; border-bottom-right-radius: 10px;
padding: 8px 15px 15px;
} }
.editor-shell .editor-container.tree-view { .editor-shell .editor-container.tree-view {
@@ -1428,7 +1428,6 @@ button.action-button:disabled {
.toolbar { .toolbar {
display: flex; display: flex;
margin-bottom: 1px;
background: #fff; background: #fff;
padding: 4px; padding: 4px;
border-top-left-radius: 10px; border-top-left-radius: 10px;
@@ -1438,7 +1437,9 @@ button.action-button:disabled {
height: 36px; height: 36px;
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 2; z-index: 4;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
border-bottom: 1px solid #eee;
} }
button.toolbar-item { button.toolbar-item {
@@ -19,7 +19,7 @@ import {AutoFocusPlugin} from '@lexical/react/LexicalAutoFocusPlugin';
import {useCollaborationContext} from '@lexical/react/LexicalCollaborationContext'; import {useCollaborationContext} from '@lexical/react/LexicalCollaborationContext';
import {CollaborationPlugin} from '@lexical/react/LexicalCollaborationPlugin'; import {CollaborationPlugin} from '@lexical/react/LexicalCollaborationPlugin';
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary'; import {LexicalErrorBoundary} from '@lexical/react/LexicalErrorBoundary';
import {HashtagPlugin} from '@lexical/react/LexicalHashtagPlugin'; import {HashtagPlugin} from '@lexical/react/LexicalHashtagPlugin';
import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin'; import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin';
import {LexicalNestedComposer} from '@lexical/react/LexicalNestedComposer'; import {LexicalNestedComposer} from '@lexical/react/LexicalNestedComposer';
@@ -12,7 +12,7 @@ import './InlineImageNode.css';
import {AutoFocusPlugin} from '@lexical/react/LexicalAutoFocusPlugin'; import {AutoFocusPlugin} from '@lexical/react/LexicalAutoFocusPlugin';
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary'; import {LexicalErrorBoundary} from '@lexical/react/LexicalErrorBoundary';
import {LexicalNestedComposer} from '@lexical/react/LexicalNestedComposer'; import {LexicalNestedComposer} from '@lexical/react/LexicalNestedComposer';
import {RichTextPlugin} from '@lexical/react/LexicalRichTextPlugin'; import {RichTextPlugin} from '@lexical/react/LexicalRichTextPlugin';
import {useLexicalNodeSelection} from '@lexical/react/useLexicalNodeSelection'; import {useLexicalNodeSelection} from '@lexical/react/useLexicalNodeSelection';
@@ -13,7 +13,7 @@ import './StickyNode.css';
import {useCollaborationContext} from '@lexical/react/LexicalCollaborationContext'; import {useCollaborationContext} from '@lexical/react/LexicalCollaborationContext';
import {CollaborationPlugin} from '@lexical/react/LexicalCollaborationPlugin'; import {CollaborationPlugin} from '@lexical/react/LexicalCollaborationPlugin';
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary'; import {LexicalErrorBoundary} from '@lexical/react/LexicalErrorBoundary';
import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin'; import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin';
import {LexicalNestedComposer} from '@lexical/react/LexicalNestedComposer'; import {LexicalNestedComposer} from '@lexical/react/LexicalNestedComposer';
import {PlainTextPlugin} from '@lexical/react/LexicalPlainTextPlugin'; import {PlainTextPlugin} from '@lexical/react/LexicalPlainTextPlugin';
@@ -32,7 +32,7 @@ import {useCollaborationContext} from '@lexical/react/LexicalCollaborationContex
import {LexicalComposer} from '@lexical/react/LexicalComposer'; import {LexicalComposer} from '@lexical/react/LexicalComposer';
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import {EditorRefPlugin} from '@lexical/react/LexicalEditorRefPlugin'; import {EditorRefPlugin} from '@lexical/react/LexicalEditorRefPlugin';
import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary'; import {LexicalErrorBoundary} from '@lexical/react/LexicalErrorBoundary';
import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin'; import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin';
import {OnChangePlugin} from '@lexical/react/LexicalOnChangePlugin'; import {OnChangePlugin} from '@lexical/react/LexicalOnChangePlugin';
import {PlainTextPlugin} from '@lexical/react/LexicalPlainTextPlugin'; import {PlainTextPlugin} from '@lexical/react/LexicalPlainTextPlugin';
@@ -8,120 +8,40 @@
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import { import {
LexicalContextMenuPlugin, NodeContextMenuPlugin,
MenuOption, NodeContextMenuOption,
} from '@lexical/react/LexicalContextMenuPlugin'; } from '@lexical/react/LexicalNodeContextMenuPlugin';
import { import {
type LexicalNode,
$getSelection, $getSelection,
$isRangeSelection, $isRangeSelection,
COPY_COMMAND, COPY_COMMAND,
CUT_COMMAND, CUT_COMMAND,
PASTE_COMMAND, PASTE_COMMAND,
} from 'lexical'; } from 'lexical';
import {useCallback, useMemo} from 'react'; import {useMemo} from 'react';
import * as React from 'react'; import * as React from 'react';
import * as ReactDOM from 'react-dom';
function ContextMenuItem({
index,
isSelected,
onClick,
onMouseEnter,
option,
}: {
index: number;
isSelected: boolean;
onClick: () => void;
onMouseEnter: () => void;
option: ContextMenuOption;
}) {
let className = 'item';
if (isSelected) {
className += ' selected';
}
return (
<li
key={option.key}
tabIndex={-1}
className={className}
ref={option.setRefElement}
role="option"
aria-selected={isSelected}
id={'typeahead-item-' + index}
onMouseEnter={onMouseEnter}
onClick={onClick}>
<span className="text">{option.title}</span>
</li>
);
}
function ContextMenu({
options,
selectedItemIndex,
onOptionClick,
onOptionMouseEnter,
}: {
selectedItemIndex: number | null;
onOptionClick: (option: ContextMenuOption, index: number) => void;
onOptionMouseEnter: (index: number) => void;
options: Array<ContextMenuOption>;
}) {
return (
<div className="typeahead-popover">
<ul>
{options.map((option: ContextMenuOption, i: number) => (
<ContextMenuItem
index={i}
isSelected={selectedItemIndex === i}
onClick={() => onOptionClick(option, i)}
onMouseEnter={() => onOptionMouseEnter(i)}
key={option.key}
option={option}
/>
))}
</ul>
</div>
);
}
export class ContextMenuOption extends MenuOption {
title: string;
onSelect: (targetNode: LexicalNode | null) => void;
constructor(
title: string,
options: {
onSelect: (targetNode: LexicalNode | null) => void;
},
) {
super(title);
this.title = title;
this.onSelect = options.onSelect.bind(this);
}
}
export default function ContextMenuPlugin(): JSX.Element { export default function ContextMenuPlugin(): JSX.Element {
const [editor] = useLexicalComposerContext(); const [editor] = useLexicalComposerContext();
const options = useMemo(() => { const items = useMemo(() => {
return [ return [
new ContextMenuOption(`Copy`, { new NodeContextMenuOption('Copy', {
onSelect: (_node) => { $onSelect: () => {
editor.dispatchCommand(COPY_COMMAND, null); editor.dispatchCommand(COPY_COMMAND, null);
}, },
}), }),
new ContextMenuOption(`Cut`, { new NodeContextMenuOption('Cut', {
onSelect: (_node) => { $onSelect: () => {
editor.dispatchCommand(CUT_COMMAND, null); editor.dispatchCommand(CUT_COMMAND, null);
}, },
}), }),
new ContextMenuOption(`Paste`, { new NodeContextMenuOption('Paste', {
onSelect: (_node) => { $onSelect: () => {
navigator.clipboard.read().then(async (...args) => { navigator.clipboard.read().then(async () => {
const data = new DataTransfer(); const data = new DataTransfer();
const clipItems = await navigator.clipboard.read();
const items = await navigator.clipboard.read(); const item = clipItems[0];
const item = items[0];
const permission = await navigator.permissions.query({ const permission = await navigator.permissions.query({
// @ts-ignore These types are incorrect. // @ts-ignore These types are incorrect.
@@ -137,108 +57,48 @@ export default function ContextMenuPlugin(): JSX.Element {
data.setData(type, dataString); data.setData(type, dataString);
} }
const event = new ClipboardEvent('paste', { const event = new ClipboardEvent('paste', {clipboardData: data});
clipboardData: data,
});
editor.dispatchCommand(PASTE_COMMAND, event); editor.dispatchCommand(PASTE_COMMAND, event);
}); });
}, },
}), }),
new ContextMenuOption(`Paste as Plain Text`, { new NodeContextMenuOption('Paste as Plain Text', {
onSelect: (_node) => { $onSelect: () => {
navigator.clipboard.read().then(async (...args) => { navigator.clipboard.read().then(async () => {
const permission = await navigator.permissions.query({ const permission = await navigator.permissions.query({
// @ts-ignore These types are incorrect. // @ts-ignore These types are incorrect.
name: 'clipboard-read', name: 'clipboard-read',
}); });
if (permission.state === 'denied') { if (permission.state === 'denied') {
alert('Not allowed to paste from clipboard.'); alert('Not allowed to paste from clipboard.');
return; return;
} }
const data = new DataTransfer(); const data = new DataTransfer();
const items = await navigator.clipboard.readText(); const text = await navigator.clipboard.readText();
data.setData('text/plain', items); data.setData('text/plain', text);
const event = new ClipboardEvent('paste', { const event = new ClipboardEvent('paste', {clipboardData: data});
clipboardData: data,
});
editor.dispatchCommand(PASTE_COMMAND, event); editor.dispatchCommand(PASTE_COMMAND, event);
}); });
}, },
}), }),
new ContextMenuOption(`Delete Node`, { new NodeContextMenuOption('Delete Node', {
onSelect: (_node) => { $onSelect: () => {
const selection = $getSelection(); editor.update(() => {
if ($isRangeSelection(selection)) { const selection = $getSelection();
const currentNode = selection.anchor.getNode(); if ($isRangeSelection(selection)) {
const ancestorNodeWithRootAsParent = currentNode const currentNode = selection.anchor.getNode();
.getParents() const ancestorNodeWithRootAsParent = currentNode
.at(-2); .getParents()
.at(-2);
ancestorNodeWithRootAsParent?.remove(); ancestorNodeWithRootAsParent?.remove();
} }
});
}, },
}), }),
]; ];
}, [editor]); }, [editor]);
const onSelectOption = useCallback( return <NodeContextMenuPlugin items={items} />;
(
selectedOption: ContextMenuOption,
targetNode: LexicalNode | null,
closeMenu: () => void,
) => {
editor.update(() => {
selectedOption.onSelect(targetNode);
closeMenu();
});
},
[editor],
);
return (
<LexicalContextMenuPlugin
options={options}
onSelectOption={onSelectOption}
menuRenderFn={(
anchorElementRef,
{
selectedIndex,
options: _options,
selectOptionAndCleanUp,
setHighlightedIndex,
},
{setMenuRef},
) =>
anchorElementRef.current
? ReactDOM.createPortal(
<div
className="typeahead-popover auto-embed-menu"
style={{
marginLeft: anchorElementRef.current.style.width,
userSelect: 'none',
width: 200,
}}
ref={setMenuRef}>
<ContextMenu
options={options}
selectedItemIndex={selectedIndex}
onOptionClick={(option: ContextMenuOption, index: number) => {
setHighlightedIndex(index);
selectOptionAndCleanUp(option);
}}
onOptionMouseEnter={(index: number) => {
setHighlightedIndex(index);
}}
/>
</div>,
anchorElementRef.current,
)
: null
}
/>
);
} }
@@ -7,16 +7,16 @@
*/ */
import type { import type {
DEPRECATED_GridCellNode,
ElementNode, ElementNode,
LexicalEditor, LexicalEditor,
} from 'lexical'; } from 'lexical';
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import useLexicalEditable from '@lexical/react/useLexicalEditable'; import {useLexicalEditable} from '@lexical/react/useLexicalEditable';
import { import {
$deleteTableColumn__EXPERIMENTAL, $deleteTableColumn__EXPERIMENTAL,
$deleteTableRow__EXPERIMENTAL, $deleteTableRow__EXPERIMENTAL,
$getNodeTriplet,
$getTableCellNodeFromLexicalNode, $getTableCellNodeFromLexicalNode,
$getTableColumnIndexFromTableCellNode, $getTableColumnIndexFromTableCellNode,
$getTableNodeFromLexicalNodeOrThrow, $getTableNodeFromLexicalNodeOrThrow,
@@ -25,11 +25,13 @@ import {
$insertTableRow__EXPERIMENTAL, $insertTableRow__EXPERIMENTAL,
$isTableCellNode, $isTableCellNode,
$isTableRowNode, $isTableRowNode,
$isTableSelection,
$unmergeCell, $unmergeCell,
getTableSelectionFromTableElement, getTableObserverFromTableElement,
HTMLTableElementWithWithTableSelectionState, HTMLTableElementWithWithTableSelectionState,
TableCellHeaderStates, TableCellHeaderStates,
TableCellNode, TableCellNode,
TableSelection,
} from '@lexical/table'; } from '@lexical/table';
import { import {
$createParagraphNode, $createParagraphNode,
@@ -39,10 +41,6 @@ import {
$isParagraphNode, $isParagraphNode,
$isRangeSelection, $isRangeSelection,
$isTextNode, $isTextNode,
DEPRECATED_$getNodeTriplet,
DEPRECATED_$isGridCellNode,
DEPRECATED_$isGridSelection,
GridSelection,
} from 'lexical'; } from 'lexical';
import * as React from 'react'; import * as React from 'react';
import {ReactPortal, useCallback, useEffect, useRef, useState} from 'react'; import {ReactPortal, useCallback, useEffect, useRef, useState} from 'react';
@@ -52,7 +50,7 @@ import invariant from '../../shared/src/invariant';
import useModal from '../../hooks/useModal'; import useModal from '../../hooks/useModal';
import ColorPicker from '../../ui/ColorPicker'; import ColorPicker from '../../ui/ColorPicker';
function computeSelectionCount(selection: GridSelection): { function computeSelectionCount(selection: TableSelection): {
columns: number; columns: number;
rows: number; rows: number;
} { } {
@@ -65,7 +63,7 @@ function computeSelectionCount(selection: GridSelection): {
// This is important when merging cells as there is no good way to re-merge weird shapes (a result // This is important when merging cells as there is no good way to re-merge weird shapes (a result
// of selecting merged cells and non-merged) // of selecting merged cells and non-merged)
function isGridSelectionRectangular(selection: GridSelection): boolean { function isTableSelectionRectangular(selection: TableSelection): boolean {
const nodes = selection.getNodes(); const nodes = selection.getNodes();
const currentRows: Array<number> = []; const currentRows: Array<number> = [];
let currentRow = null; let currentRow = null;
@@ -109,17 +107,17 @@ function $canUnmerge(): boolean {
const selection = $getSelection(); const selection = $getSelection();
if ( if (
($isRangeSelection(selection) && !selection.isCollapsed()) || ($isRangeSelection(selection) && !selection.isCollapsed()) ||
(DEPRECATED_$isGridSelection(selection) && ($isTableSelection(selection) &&
!selection.anchor.is(selection.focus)) || !selection.anchor.is(selection.focus)) ||
(!$isRangeSelection(selection) && !DEPRECATED_$isGridSelection(selection)) (!$isRangeSelection(selection) && !$isTableSelection(selection))
) { ) {
return false; return false;
} }
const [cell] = DEPRECATED_$getNodeTriplet(selection.anchor); const [cell] = $getNodeTriplet(selection.anchor);
return cell.__colSpan > 1 || cell.__rowSpan > 1; return cell.__colSpan > 1 || cell.__rowSpan > 1;
} }
function $cellContainsEmptyParagraph(cell: DEPRECATED_GridCellNode): boolean { function $cellContainsEmptyParagraph(cell: TableCellNode): boolean {
if (cell.getChildrenSize() !== 1) { if (cell.getChildrenSize() !== 1) {
return false; return false;
} }
@@ -146,9 +144,9 @@ function currentCellBackgroundColor(editor: LexicalEditor): null | string {
const selection = $getSelection(); const selection = $getSelection();
if ( if (
$isRangeSelection(selection) || $isRangeSelection(selection) ||
DEPRECATED_$isGridSelection(selection) $isTableSelection(selection)
) { ) {
const [cell] = DEPRECATED_$getNodeTriplet(selection.anchor); const [cell] = $getNodeTriplet(selection.anchor);
if ($isTableCellNode(cell)) { if ($isTableCellNode(cell)) {
return cell.getBackgroundColor(); return cell.getBackgroundColor();
} }
@@ -208,11 +206,11 @@ function TableActionMenu({
editor.getEditorState().read(() => { editor.getEditorState().read(() => {
const selection = $getSelection(); const selection = $getSelection();
// Merge cells // Merge cells
if (DEPRECATED_$isGridSelection(selection)) { if ($isTableSelection(selection)) {
const currentSelectionCounts = computeSelectionCount(selection); const currentSelectionCounts = computeSelectionCount(selection);
updateSelectionCounts(computeSelectionCount(selection)); updateSelectionCounts(computeSelectionCount(selection));
setCanMergeCells( setCanMergeCells(
isGridSelectionRectangular(selection) && isTableSelectionRectangular(selection) &&
(currentSelectionCounts.columns > 1 || (currentSelectionCounts.columns > 1 ||
currentSelectionCounts.rows > 1), currentSelectionCounts.rows > 1),
); );
@@ -286,9 +284,9 @@ function TableActionMenu({
throw new Error('Expected to find tableElement in DOM'); throw new Error('Expected to find tableElement in DOM');
} }
const tableSelection = getTableSelectionFromTableElement(tableElement); const tableSelection = getTableObserverFromTableElement(tableElement);
if (tableSelection !== null) { if (tableSelection !== null) {
tableSelection.clearHighlight(); tableSelection.$clearHighlight();
} }
tableNode.markDirty(); tableNode.markDirty();
@@ -303,13 +301,13 @@ function TableActionMenu({
const mergeTableCellsAtSelection = () => { const mergeTableCellsAtSelection = () => {
editor.update(() => { editor.update(() => {
const selection = $getSelection(); const selection = $getSelection();
if (DEPRECATED_$isGridSelection(selection)) { if ($isTableSelection(selection)) {
const {columns, rows} = computeSelectionCount(selection); const {columns, rows} = computeSelectionCount(selection);
const nodes = selection.getNodes(); const nodes = selection.getNodes();
let firstCell: null | DEPRECATED_GridCellNode = null; let firstCell: null | TableCellNode = null;
for (let i = 0; i < nodes.length; i++) { for (let i = 0; i < nodes.length; i++) {
const node = nodes[i]; const node = nodes[i];
if (DEPRECATED_$isGridCellNode(node)) { if ($isTableCellNode(node)) {
if (firstCell === null) { if (firstCell === null) {
node.setColSpan(columns).setRowSpan(rows); node.setColSpan(columns).setRowSpan(rows);
firstCell = node; firstCell = node;
@@ -321,7 +319,7 @@ function TableActionMenu({
) { ) {
firstChild.remove(); firstChild.remove();
} }
} else if (DEPRECATED_$isGridCellNode(firstCell)) { } else if ($isTableCellNode(firstCell)) {
const isEmpty = $cellContainsEmptyParagraph(node); const isEmpty = $cellContainsEmptyParagraph(node);
if (!isEmpty) { if (!isEmpty) {
firstCell.append(...node.getChildren()); firstCell.append(...node.getChildren());
@@ -473,19 +471,19 @@ function TableActionMenu({
const selection = $getSelection(); const selection = $getSelection();
if ( if (
$isRangeSelection(selection) || $isRangeSelection(selection) ||
DEPRECATED_$isGridSelection(selection) $isTableSelection(selection)
) { ) {
const [cell] = DEPRECATED_$getNodeTriplet(selection.anchor); const [cell] = $getNodeTriplet(selection.anchor);
if ($isTableCellNode(cell)) { if ($isTableCellNode(cell)) {
cell.setBackgroundColor(value); cell.setBackgroundColor(value);
} }
if (DEPRECATED_$isGridSelection(selection)) { if ($isTableSelection(selection)) {
const nodes = selection.getNodes(); const nodes = selection.getNodes();
for (let i = 0; i < nodes.length; i++) { for (let i = 0; i < nodes.length; i++) {
const node = nodes[i]; const node = nodes[i];
if (DEPRECATED_$isGridCellNode(node)) { if ($isTableCellNode(node)) {
node.setBackgroundColor(value); node.setBackgroundColor(value);
} }
} }
@@ -5,26 +5,26 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
* *
*/ */
import type {Cell} from '@lexical/table'; import type {TableDOMCell} from '@lexical/table';
import type {LexicalEditor} from 'lexical'; import type {LexicalEditor} from 'lexical';
import './index.css'; import './index.css';
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import useLexicalEditable from '@lexical/react/useLexicalEditable'; import {useLexicalEditable} from '@lexical/react/useLexicalEditable';
import { import {
$getTableColumnIndexFromTableCellNode, $getTableColumnIndexFromTableCellNode,
$getTableNodeFromLexicalNodeOrThrow, $getTableNodeFromLexicalNodeOrThrow,
$getTableRowIndexFromTableCellNode, $getTableRowIndexFromTableCellNode,
$isTableCellNode, $isTableCellNode,
$isTableRowNode, $isTableRowNode,
getCellFromTarget, $isTableSelection,
getDOMCellFromTarget,
} from '@lexical/table'; } from '@lexical/table';
import { import {
$getNearestNodeFromDOMNode, $getNearestNodeFromDOMNode,
$getSelection, $getSelection,
COMMAND_PRIORITY_HIGH, COMMAND_PRIORITY_HIGH,
DEPRECATED_$isGridSelection,
SELECTION_CHANGE_COMMAND, SELECTION_CHANGE_COMMAND,
} from 'lexical'; } from 'lexical';
import * as React from 'react'; import * as React from 'react';
@@ -58,7 +58,7 @@ function TableCellResizer({editor}: {editor: LexicalEditor}): JSX.Element {
const [mouseCurrentPos, updateMouseCurrentPos] = const [mouseCurrentPos, updateMouseCurrentPos] =
useState<MousePosition | null>(null); useState<MousePosition | null>(null);
const [activeCell, updateActiveCell] = useState<Cell | null>(null); const [activeCell, updateActiveCell] = useState<TableDOMCell | null>(null);
const [isSelectingGrid, updateIsSelectingGrid] = useState<boolean>(false); const [isSelectingGrid, updateIsSelectingGrid] = useState<boolean>(false);
const [draggingDirection, updateDraggingDirection] = const [draggingDirection, updateDraggingDirection] =
useState<MouseDraggingDirection | null>(null); useState<MouseDraggingDirection | null>(null);
@@ -68,7 +68,7 @@ function TableCellResizer({editor}: {editor: LexicalEditor}): JSX.Element {
SELECTION_CHANGE_COMMAND, SELECTION_CHANGE_COMMAND,
(payload) => { (payload) => {
const selection = $getSelection(); const selection = $getSelection();
const isGridSelection = DEPRECATED_$isGridSelection(selection); const isGridSelection = $isTableSelection(selection);
if (isSelectingGrid !== isGridSelection) { if (isSelectingGrid !== isGridSelection) {
updateIsSelectingGrid(isGridSelection); updateIsSelectingGrid(isGridSelection);
@@ -107,7 +107,7 @@ function TableCellResizer({editor}: {editor: LexicalEditor}): JSX.Element {
if (targetRef.current !== target) { if (targetRef.current !== target) {
targetRef.current = target as HTMLElement; targetRef.current = target as HTMLElement;
const cell = getCellFromTarget(target as HTMLElement); const cell = getDOMCellFromTarget(target as HTMLElement);
if (cell && activeCell !== cell) { if (cell && activeCell !== cell) {
editor.update(() => { editor.update(() => {
@@ -5,14 +5,14 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
* *
*/ */
import type {TableOfContentsEntry} from '@lexical/react/LexicalTableOfContents'; import type {TableOfContentsEntry} from '@lexical/react/LexicalTableOfContentsPlugin';
import type {HeadingTagType} from '@lexical/rich-text'; import type {HeadingTagType} from '@lexical/rich-text';
import type {NodeKey} from 'lexical'; import type {NodeKey} from 'lexical';
import './index.css'; import './index.css';
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import LexicalTableOfContents from '@lexical/react/LexicalTableOfContents'; import {TableOfContentsPlugin} from '@lexical/react/LexicalTableOfContentsPlugin';
import {useEffect, useRef, useState} from 'react'; import {useEffect, useRef, useState} from 'react';
import { Collapsible, Button } from '@douyinfe/semi-ui'; import { Collapsible, Button } from '@douyinfe/semi-ui';
@@ -243,12 +243,12 @@ function TableOfContentsList({
); );
} }
export default function TableOfContentsPlugin() { export default function TableOfContentsPluginWrapper() {
return ( return (
<LexicalTableOfContents> <TableOfContentsPlugin>
{(tableOfContents) => { {(tableOfContents) => {
return <TableOfContentsList tableOfContents={tableOfContents} />; return <TableOfContentsList tableOfContents={tableOfContents} />;
}} }}
</LexicalTableOfContents> </TableOfContentsPlugin>
); );
} }
@@ -51,7 +51,6 @@ import {
$getNodeByKey, $getNodeByKey,
$getRoot, $getRoot,
$getSelection, $getSelection,
$INTERNAL_isPointSelection,
$isElementNode, $isElementNode,
$isRangeSelection, $isRangeSelection,
$isRootOrShadowRoot, $isRootOrShadowRoot,
@@ -213,7 +212,7 @@ function BlockFormatDropDown({
const formatParagraph = () => { const formatParagraph = () => {
editor.update(() => { editor.update(() => {
const selection = $getSelection(); const selection = $getSelection();
if ($INTERNAL_isPointSelection(selection)) { if ($isRangeSelection(selection)) {
$setBlocksType(selection, () => $createParagraphNode()); $setBlocksType(selection, () => $createParagraphNode());
} }
}); });
@@ -223,7 +222,7 @@ function BlockFormatDropDown({
if (blockType !== headingSize) { if (blockType !== headingSize) {
editor.update(() => { editor.update(() => {
const selection = $getSelection(); const selection = $getSelection();
if ($INTERNAL_isPointSelection(selection)) { if ($isRangeSelection(selection)) {
$setBlocksType(selection, () => $createHeadingNode(headingSize)); $setBlocksType(selection, () => $createHeadingNode(headingSize));
} }
}); });
@@ -258,7 +257,7 @@ function BlockFormatDropDown({
if (blockType !== 'quote') { if (blockType !== 'quote') {
editor.update(() => { editor.update(() => {
const selection = $getSelection(); const selection = $getSelection();
if ($INTERNAL_isPointSelection(selection)) { if ($isRangeSelection(selection)) {
$setBlocksType(selection, () => $createQuoteNode()); $setBlocksType(selection, () => $createQuoteNode());
} }
}); });
@@ -270,7 +269,7 @@ function BlockFormatDropDown({
editor.update(() => { editor.update(() => {
let selection = $getSelection(); let selection = $getSelection();
if ($INTERNAL_isPointSelection(selection)) { if ($isRangeSelection(selection)) {
if (selection.isCollapsed()) { if (selection.isCollapsed()) {
$setBlocksType(selection, () => $createCodeNode()); $setBlocksType(selection, () => $createCodeNode());
} else { } else {
@@ -370,7 +369,7 @@ function FontDropDown({
(option: string) => { (option: string) => {
editor.update(() => { editor.update(() => {
const selection = $getSelection(); const selection = $getSelection();
if ($INTERNAL_isPointSelection(selection)) { if ($isRangeSelection(selection)) {
$patchStyleText(selection, { $patchStyleText(selection, {
[style]: option, [style]: option,
}); });
@@ -677,7 +676,7 @@ export default function ToolbarPlugin({
activeEditor.registerUpdateListener(({editorState}) => { activeEditor.registerUpdateListener(({editorState}) => {
editorState.read(() => { editorState.read(() => {
$updateToolbar(); $updateToolbar();
}); }, {editor: activeEditor});
}), }),
activeEditor.registerCommand<boolean>( activeEditor.registerCommand<boolean>(
CAN_UNDO_COMMAND, CAN_UNDO_COMMAND,
@@ -727,7 +726,7 @@ export default function ToolbarPlugin({
(styles: Record<string, string>) => { (styles: Record<string, string>) => {
activeEditor.update(() => { activeEditor.update(() => {
const selection = $getSelection(); const selection = $getSelection();
if ($INTERNAL_isPointSelection(selection)) { if ($isRangeSelection(selection)) {
$patchStyleText(selection, styles); $patchStyleText(selection, styles);
} }
}); });
@@ -25,28 +25,27 @@ export default function NoteEditor() {
return ( return (
<> <>
<Breadcrumb <Breadcrumb
style={{ marginBottom: '24px' }} style={{ marginBottom: '16px', paddingTop: '10px' }}
routes={['CyyNote', '笔记标题', currentTitle]} routes={['CyyNote', '笔记标题', currentTitle]}
/> />
<div <Skeleton
style={{ placeholder={<Skeleton.Paragraph rows={2} />}
borderRadius: '10px', loading={isNoteLoading}
border: '1px solid var(--semi-color-border)',
padding: '15px',
}}
> >
<Skeleton <Input
placeholder={<Skeleton.Paragraph rows={2} />} placeholder="笔记标题"
loading={isNoteLoading} size="large"
value={currentTitle}
onChange={handleTitleChange}
style={{ marginBottom: '12px' }}
/>
<div
style={{
borderRadius: '10px',
border: '1px solid var(--semi-color-border)',
overflow: 'clip',
}}
> >
<Input
placeholder="笔记标题"
size="large"
value={currentTitle}
onChange={handleTitleChange}
/>
<br />
<br />
{currentNoteId !== null && currentContent !== null ? ( {currentNoteId !== null && currentContent !== null ? (
<PlaygroundApp <PlaygroundApp
noteId={currentNoteId} noteId={currentNoteId}
@@ -55,10 +54,10 @@ export default function NoteEditor() {
handleLawClick={handleRefreshTree} handleLawClick={handleRefreshTree}
/> />
) : ( ) : (
<div style={{ color: 'var(--semi-color-text-2)' }}></div> <div style={{ padding: '15px', color: 'var(--semi-color-text-2)' }}></div>
)} )}
</Skeleton> </div>
</div> </Skeleton>
</> </>
); );
} }
@@ -189,7 +189,7 @@ export default function NoteTree({ onRefreshTree, onNavItemClick, onSearch }: No
searchPlaceholder=" " searchPlaceholder=" "
showFilteredOnly showFilteredOnly
directory directory
virtualize virtualize={{ itemSize: 28 }}
value={currentNoteId === null ? undefined : String(currentNoteId)} value={currentNoteId === null ? undefined : String(currentNoteId)}
treeData={treeData as unknown as Parameters<typeof Tree>[0]['treeData']} treeData={treeData as unknown as Parameters<typeof Tree>[0]['treeData']}
renderLabel={renderLabel as Parameters<typeof Tree>[0]['renderLabel']} renderLabel={renderLabel as Parameters<typeof Tree>[0]['renderLabel']}
+3 -6
View File
@@ -1,4 +1,3 @@
import React from 'react';
import ReactDOM from 'react-dom/client'; import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router'; import { BrowserRouter } from 'react-router';
@@ -6,10 +5,8 @@ import './index.css';
import App from './App'; import App from './App';
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode> <BrowserRouter>
<BrowserRouter> <App />
<App /> </BrowserRouter>,
</BrowserRouter>
</React.StrictMode>,
); );
+8 -8
View File
@@ -131,8 +131,8 @@ export default function NotePage() {
const siderWidth = siderFlag ? '265px' : '0px'; const siderWidth = siderFlag ? '265px' : '0px';
return ( return (
<Layout style={{ border: '1px solid var(--semi-color-border)' }}> <Layout style={{ border: '1px solid var(--semi-color-border)', height: '100vh', overflow: 'hidden' }}>
<Header style={{ backgroundColor: 'var(--semi-color-bg-1)' }}> <Header style={{ backgroundColor: 'var(--semi-color-bg-1)', flexShrink: 0 }}>
<Nav mode="horizontal" defaultSelectedKeys={['Home']}> <Nav mode="horizontal" defaultSelectedKeys={['Home']}>
<Nav.Header> <Nav.Header>
<IconDescriptions style={{ height: '36px', fontSize: 36 }} /> <IconDescriptions style={{ height: '36px', fontSize: 36 }} />
@@ -181,9 +181,9 @@ export default function NotePage() {
</Nav> </Nav>
</Header> </Header>
<Layout> <Layout style={{ flex: 1, minHeight: 0 }}>
<Sider <Sider
style={{ width: siderWidth, backgroundColor: 'var(--semi-color-bg-1)' }} style={{ width: siderWidth, backgroundColor: 'var(--semi-color-bg-1)', overflowY: 'auto' }}
breakpoint={['md']} breakpoint={['md']}
onBreakpoint={handleBreakpoint} onBreakpoint={handleBreakpoint}
> >
@@ -207,13 +207,13 @@ export default function NotePage() {
{/* Main content area */} {/* Main content area */}
{activeView === 'note' && ( {activeView === 'note' && (
<Content style={{ padding: '10px', backgroundColor: 'var(--semi-color-bg-0)' }}> <Content style={{ padding: '0 10px 10px', backgroundColor: 'var(--semi-color-bg-0)', overflowY: 'auto' }}>
<NoteEditor /> <NoteEditor />
</Content> </Content>
)} )}
{activeView === 'home' && ( {activeView === 'home' && (
<Content style={{ padding: '10px', backgroundColor: 'var(--semi-color-bg-0)' }}> <Content style={{ padding: '10px', backgroundColor: 'var(--semi-color-bg-0)', overflowY: 'auto' }}>
<Breadcrumb style={{ marginBottom: 24 }} routes={['首页']} /> <Breadcrumb style={{ marginBottom: 24 }} routes={['首页']} />
<div <div
style={{ style={{
@@ -228,7 +228,7 @@ export default function NotePage() {
)} )}
{activeView === 'settings' && ( {activeView === 'settings' && (
<Content style={{ padding: '10px', backgroundColor: 'var(--semi-color-bg-0)' }}> <Content style={{ padding: '10px', backgroundColor: 'var(--semi-color-bg-0)', overflowY: 'auto' }}>
<Breadcrumb style={{ marginBottom: 24 }} routes={['设置']} /> <Breadcrumb style={{ marginBottom: 24 }} routes={['设置']} />
<div <div
style={{ style={{
@@ -284,7 +284,7 @@ export default function NotePage() {
)} )}
{activeView === 'search' && ( {activeView === 'search' && (
<Content style={{ padding: 24, backgroundColor: 'var(--semi-color-bg-0)' }}> <Content style={{ padding: 24, backgroundColor: 'var(--semi-color-bg-0)', overflowY: 'auto' }}>
<Breadcrumb style={{ marginBottom: 24 }} routes={['搜索结果']} /> <Breadcrumb style={{ marginBottom: 24 }} routes={['搜索结果']} />
<div <div
style={{ style={{
+1 -1
View File
@@ -16,7 +16,7 @@
"jsx": "react-jsx", "jsx": "react-jsx",
/* Path aliases */ /* Path aliases */
"ignoreDeprecations": "6.0", "ignoreDeprecations": "5.0",
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@/*": ["./src/*"] "@/*": ["./src/*"]
+3
View File
@@ -4,6 +4,9 @@ import react from '@vitejs/plugin-react';
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig(({ command }) => ({ export default defineConfig(({ command }) => ({
plugins: [react()], plugins: [react()],
define: {
'process.env': {},
},
resolve: { resolve: {
// new URL is pure ESM — no __dirname needed, no @types/node required // new URL is pure ESM — no __dirname needed, no @types/node required
alias: { '@': new URL('./src', import.meta.url).pathname }, alias: { '@': new URL('./src', import.meta.url).pathname },
File diff suppressed because it is too large Load Diff