Update
This commit is contained in:
@@ -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) => {
|
||||
const selection = $getSelection();
|
||||
if ($isRangeSelection(selection)) {
|
||||
const currentNode = selection.anchor.getNode();
|
||||
const ancestorNodeWithRootAsParent = currentNode
|
||||
.getParents()
|
||||
.at(-2);
|
||||
|
||||
ancestorNodeWithRootAsParent?.remove();
|
||||
}
|
||||
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,28 +25,27 @@ 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}
|
||||
>
|
||||
<Skeleton
|
||||
placeholder={<Skeleton.Paragraph rows={2} />}
|
||||
loading={isNoteLoading}
|
||||
<Input
|
||||
placeholder="笔记标题"
|
||||
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 ? (
|
||||
<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>
|
||||
</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>
|
||||
<App />
|
||||
</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