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