update #1
@@ -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,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"
|
||||||
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,
|
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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
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 {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: () => {
|
||||||
|
editor.update(() => {
|
||||||
const selection = $getSelection();
|
const selection = $getSelection();
|
||||||
if ($isRangeSelection(selection)) {
|
if ($isRangeSelection(selection)) {
|
||||||
const currentNode = selection.anchor.getNode();
|
const currentNode = selection.anchor.getNode();
|
||||||
const ancestorNodeWithRootAsParent = currentNode
|
const ancestorNodeWithRootAsParent = currentNode
|
||||||
.getParents()
|
.getParents()
|
||||||
.at(-2);
|
.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,16 +25,9 @@ export default function NoteEditor() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Breadcrumb
|
<Breadcrumb
|
||||||
style={{ marginBottom: '24px' }}
|
style={{ marginBottom: '16px', paddingTop: '10px' }}
|
||||||
routes={['CyyNote', '笔记标题', currentTitle]}
|
routes={['CyyNote', '笔记标题', currentTitle]}
|
||||||
/>
|
/>
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
borderRadius: '10px',
|
|
||||||
border: '1px solid var(--semi-color-border)',
|
|
||||||
padding: '15px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Skeleton
|
<Skeleton
|
||||||
placeholder={<Skeleton.Paragraph rows={2} />}
|
placeholder={<Skeleton.Paragraph rows={2} />}
|
||||||
loading={isNoteLoading}
|
loading={isNoteLoading}
|
||||||
@@ -44,9 +37,15 @@ export default function NoteEditor() {
|
|||||||
size="large"
|
size="large"
|
||||||
value={currentTitle}
|
value={currentTitle}
|
||||||
onChange={handleTitleChange}
|
onChange={handleTitleChange}
|
||||||
|
style={{ marginBottom: '12px' }}
|
||||||
/>
|
/>
|
||||||
<br />
|
<div
|
||||||
<br />
|
style={{
|
||||||
|
borderRadius: '10px',
|
||||||
|
border: '1px solid var(--semi-color-border)',
|
||||||
|
overflow: 'clip',
|
||||||
|
}}
|
||||||
|
>
|
||||||
{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']}
|
||||||
|
|||||||
@@ -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>,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -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={{
|
||||||
|
|||||||
@@ -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/*"]
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user