mirror of
https://github.com/docmost/docmost.git
synced 2026-08-30 02:25:01 +08:00
Tiptap3 migration - WIP
This commit is contained in:
@@ -1,9 +1,5 @@
|
|||||||
import {
|
import { BubbleMenu, BubbleMenuProps } from "@tiptap/react/menus";
|
||||||
BubbleMenu,
|
import { isNodeSelection, useEditor } from "@tiptap/react";
|
||||||
BubbleMenuProps,
|
|
||||||
isNodeSelection,
|
|
||||||
useEditor,
|
|
||||||
} from "@tiptap/react";
|
|
||||||
import { FC, useEffect, useRef, useState } from "react";
|
import { FC, useEffect, useRef, useState } from "react";
|
||||||
import {
|
import {
|
||||||
IconBold,
|
IconBold,
|
||||||
@@ -114,14 +110,9 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props) => {
|
|||||||
}
|
}
|
||||||
return isTextSelected(editor);
|
return isTextSelected(editor);
|
||||||
},
|
},
|
||||||
tippyOptions: {
|
options: {
|
||||||
moveTransition: "transform 0.15s ease-out",
|
placement: "top",
|
||||||
onCreate: (instance) => {
|
offset: 8,
|
||||||
instance.popper.firstChild?.addEventListener("blur", (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopImmediatePropagation();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onHide: () => {
|
onHide: () => {
|
||||||
setIsNodeSelectorOpen(false);
|
setIsNodeSelectorOpen(false);
|
||||||
setIsTextAlignmentOpen(false);
|
setIsTextAlignmentOpen(false);
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import {
|
import { BubbleMenu as BaseBubbleMenu } from "@tiptap/react/menus";
|
||||||
BubbleMenu as BaseBubbleMenu,
|
import { findParentNode, posToDOMRect } from "@tiptap/react";
|
||||||
findParentNode,
|
|
||||||
posToDOMRect,
|
|
||||||
} from "@tiptap/react";
|
|
||||||
import React, { useCallback } from "react";
|
import React, { useCallback } from "react";
|
||||||
import { Node as PMNode } from "prosemirror-model";
|
import { Node as PMNode } from "prosemirror-model";
|
||||||
import {
|
import {
|
||||||
@@ -55,20 +52,17 @@ export function CalloutMenu({ editor }: EditorMenuProps) {
|
|||||||
},
|
},
|
||||||
[editor],
|
[editor],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseBubbleMenu
|
<BaseBubbleMenu
|
||||||
editor={editor}
|
editor={editor}
|
||||||
pluginKey={`callout-menu}`}
|
pluginKey={`callout-menu}`}
|
||||||
updateDelay={0}
|
updateDelay={0}
|
||||||
tippyOptions={{
|
options={{
|
||||||
getReferenceClientRect,
|
// getReferenceClientRect,
|
||||||
offset: [0, 10],
|
placement: "right-end",
|
||||||
placement: "bottom",
|
// offset: 233,
|
||||||
zIndex: 99,
|
// zIndex: 99,
|
||||||
popperOptions: {
|
flip: false,
|
||||||
modifiers: [{ name: "flip", enabled: false }],
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
shouldShow={shouldShow}
|
shouldShow={shouldShow}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ export default function CodeBlockView(props: NodeViewProps) {
|
|||||||
node.textContent.length > 0
|
node.textContent.length > 0
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
{/* @ts-ignore */}
|
||||||
<NodeViewContent as="code" className={`language-${language}`} />
|
<NodeViewContent as="code" className={`language-${language}`} />
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
import {
|
import { BubbleMenu as BaseBubbleMenu } from "@tiptap/react/menus";
|
||||||
BubbleMenu as BaseBubbleMenu,
|
import { findParentNode, posToDOMRect } from "@tiptap/react";
|
||||||
findParentNode,
|
import { useCallback } from "react";
|
||||||
posToDOMRect,
|
import { Node as PMNode } from "prosemirror-model";
|
||||||
} from '@tiptap/react';
|
|
||||||
import { useCallback } from 'react';
|
|
||||||
import { sticky } from 'tippy.js';
|
|
||||||
import { Node as PMNode } from 'prosemirror-model';
|
|
||||||
import {
|
import {
|
||||||
EditorMenuProps,
|
EditorMenuProps,
|
||||||
ShouldShowProps,
|
ShouldShowProps,
|
||||||
} from '@/features/editor/components/table/types/types.ts';
|
} from "@/features/editor/components/table/types/types.ts";
|
||||||
import { NodeWidthResize } from '@/features/editor/components/common/node-width-resize.tsx';
|
import { NodeWidthResize } from "@/features/editor/components/common/node-width-resize.tsx";
|
||||||
|
|
||||||
export function DrawioMenu({ editor }: EditorMenuProps) {
|
export function DrawioMenu({ editor }: EditorMenuProps) {
|
||||||
const shouldShow = useCallback(
|
const shouldShow = useCallback(
|
||||||
@@ -19,14 +15,14 @@ export function DrawioMenu({ editor }: EditorMenuProps) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return editor.isActive('drawio') && editor.getAttributes('drawio')?.src;
|
return editor.isActive("drawio") && editor.getAttributes("drawio")?.src;
|
||||||
},
|
},
|
||||||
[editor]
|
[editor],
|
||||||
);
|
);
|
||||||
|
|
||||||
const getReferenceClientRect = useCallback(() => {
|
const getReferenceClientRect = useCallback(() => {
|
||||||
const { selection } = editor.state;
|
const { selection } = editor.state;
|
||||||
const predicate = (node: PMNode) => node.type.name === 'drawio';
|
const predicate = (node: PMNode) => node.type.name === "drawio";
|
||||||
const parent = findParentNode(predicate)(selection);
|
const parent = findParentNode(predicate)(selection);
|
||||||
|
|
||||||
if (parent) {
|
if (parent) {
|
||||||
@@ -39,9 +35,9 @@ export function DrawioMenu({ editor }: EditorMenuProps) {
|
|||||||
|
|
||||||
const onWidthChange = useCallback(
|
const onWidthChange = useCallback(
|
||||||
(value: number) => {
|
(value: number) => {
|
||||||
editor.commands.updateAttributes('drawio', { width: `${value}%` });
|
editor.commands.updateAttributes("drawio", { width: `${value}%` });
|
||||||
},
|
},
|
||||||
[editor]
|
[editor],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -49,29 +45,26 @@ export function DrawioMenu({ editor }: EditorMenuProps) {
|
|||||||
editor={editor}
|
editor={editor}
|
||||||
pluginKey={`drawio-menu}`}
|
pluginKey={`drawio-menu}`}
|
||||||
updateDelay={0}
|
updateDelay={0}
|
||||||
tippyOptions={{
|
options={{
|
||||||
getReferenceClientRect,
|
//getReferenceClientRect,
|
||||||
offset: [0, 8],
|
placement: "bottom",
|
||||||
zIndex: 99,
|
offset: 8,
|
||||||
popperOptions: {
|
// zIndex: 99,
|
||||||
modifiers: [{ name: 'flip', enabled: false }],
|
flip: false,
|
||||||
},
|
|
||||||
plugins: [sticky],
|
|
||||||
sticky: 'popper',
|
|
||||||
}}
|
}}
|
||||||
shouldShow={shouldShow}
|
shouldShow={shouldShow}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
flexDirection: 'column',
|
flexDirection: "column",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{editor.getAttributes('drawio')?.width && (
|
{editor.getAttributes("drawio")?.width && (
|
||||||
<NodeWidthResize
|
<NodeWidthResize
|
||||||
onChange={onWidthChange}
|
onChange={onWidthChange}
|
||||||
value={parseInt(editor.getAttributes('drawio').width)}
|
value={parseInt(editor.getAttributes("drawio").width)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ export default function DrawioView(props: NodeViewProps) {
|
|||||||
const fileName = "diagram.drawio.svg";
|
const fileName = "diagram.drawio.svg";
|
||||||
const drawioSVGFile = await svgStringToFile(svgString, fileName);
|
const drawioSVGFile = await svgStringToFile(svgString, fileName);
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
const pageId = editor.storage?.pageId;
|
const pageId = editor.storage?.pageId;
|
||||||
|
|
||||||
let attachment: IAttachment = null;
|
let attachment: IAttachment = null;
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
import {
|
import { BubbleMenu as BaseBubbleMenu } from "@tiptap/react/menus";
|
||||||
BubbleMenu as BaseBubbleMenu,
|
import { findParentNode, posToDOMRect } from "@tiptap/react";
|
||||||
findParentNode,
|
import { useCallback } from "react";
|
||||||
posToDOMRect,
|
import { Node as PMNode } from "prosemirror-model";
|
||||||
} from '@tiptap/react';
|
|
||||||
import { useCallback } from 'react';
|
|
||||||
import { sticky } from 'tippy.js';
|
|
||||||
import { Node as PMNode } from 'prosemirror-model';
|
|
||||||
import {
|
import {
|
||||||
EditorMenuProps,
|
EditorMenuProps,
|
||||||
ShouldShowProps,
|
ShouldShowProps,
|
||||||
} from '@/features/editor/components/table/types/types.ts';
|
} from "@/features/editor/components/table/types/types.ts";
|
||||||
import { NodeWidthResize } from '@/features/editor/components/common/node-width-resize.tsx';
|
import { NodeWidthResize } from "@/features/editor/components/common/node-width-resize.tsx";
|
||||||
|
|
||||||
export function ExcalidrawMenu({ editor }: EditorMenuProps) {
|
export function ExcalidrawMenu({ editor }: EditorMenuProps) {
|
||||||
const shouldShow = useCallback(
|
const shouldShow = useCallback(
|
||||||
@@ -19,14 +15,16 @@ export function ExcalidrawMenu({ editor }: EditorMenuProps) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return editor.isActive('excalidraw') && editor.getAttributes('excalidraw')?.src;
|
return (
|
||||||
|
editor.isActive("excalidraw") && editor.getAttributes("excalidraw")?.src
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[editor]
|
[editor],
|
||||||
);
|
);
|
||||||
|
|
||||||
const getReferenceClientRect = useCallback(() => {
|
const getReferenceClientRect = useCallback(() => {
|
||||||
const { selection } = editor.state;
|
const { selection } = editor.state;
|
||||||
const predicate = (node: PMNode) => node.type.name === 'excalidraw';
|
const predicate = (node: PMNode) => node.type.name === "excalidraw";
|
||||||
const parent = findParentNode(predicate)(selection);
|
const parent = findParentNode(predicate)(selection);
|
||||||
|
|
||||||
if (parent) {
|
if (parent) {
|
||||||
@@ -39,9 +37,9 @@ export function ExcalidrawMenu({ editor }: EditorMenuProps) {
|
|||||||
|
|
||||||
const onWidthChange = useCallback(
|
const onWidthChange = useCallback(
|
||||||
(value: number) => {
|
(value: number) => {
|
||||||
editor.commands.updateAttributes('excalidraw', { width: `${value}%` });
|
editor.commands.updateAttributes("excalidraw", { width: `${value}%` });
|
||||||
},
|
},
|
||||||
[editor]
|
[editor],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -49,29 +47,26 @@ export function ExcalidrawMenu({ editor }: EditorMenuProps) {
|
|||||||
editor={editor}
|
editor={editor}
|
||||||
pluginKey={`excalidraw-menu}`}
|
pluginKey={`excalidraw-menu}`}
|
||||||
updateDelay={0}
|
updateDelay={0}
|
||||||
tippyOptions={{
|
options={{
|
||||||
getReferenceClientRect,
|
//getReferenceClientRect,
|
||||||
offset: [0, 8],
|
placement: "bottom",
|
||||||
zIndex: 99,
|
offset: 8,
|
||||||
popperOptions: {
|
// zIndex: 99,
|
||||||
modifiers: [{ name: 'flip', enabled: false }],
|
flip: false,
|
||||||
},
|
|
||||||
plugins: [sticky],
|
|
||||||
sticky: 'popper',
|
|
||||||
}}
|
}}
|
||||||
shouldShow={shouldShow}
|
shouldShow={shouldShow}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
flexDirection: 'column',
|
flexDirection: "column",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{editor.getAttributes('excalidraw')?.width && (
|
{editor.getAttributes("excalidraw")?.width && (
|
||||||
<NodeWidthResize
|
<NodeWidthResize
|
||||||
onChange={onWidthChange}
|
onChange={onWidthChange}
|
||||||
value={parseInt(editor.getAttributes('excalidraw').width)}
|
value={parseInt(editor.getAttributes("excalidraw").width)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ export default function ExcalidrawView(props: NodeViewProps) {
|
|||||||
const fileName = "diagram.excalidraw.svg";
|
const fileName = "diagram.excalidraw.svg";
|
||||||
const excalidrawSvgFile = await svgStringToFile(svgString, fileName);
|
const excalidrawSvgFile = await svgStringToFile(svgString, fileName);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
const pageId = editor.storage?.pageId;
|
const pageId = editor.storage?.pageId;
|
||||||
|
|
||||||
let attachment: IAttachment = null;
|
let attachment: IAttachment = null;
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import {
|
import { BubbleMenu as BaseBubbleMenu } from "@tiptap/react/menus";
|
||||||
BubbleMenu as BaseBubbleMenu,
|
import { findParentNode, posToDOMRect } from "@tiptap/react";
|
||||||
findParentNode,
|
|
||||||
posToDOMRect,
|
|
||||||
} from "@tiptap/react";
|
|
||||||
import React, { useCallback } from "react";
|
import React, { useCallback } from "react";
|
||||||
import { sticky } from "tippy.js";
|
|
||||||
import { Node as PMNode } from "prosemirror-model";
|
import { Node as PMNode } from "prosemirror-model";
|
||||||
import {
|
import {
|
||||||
EditorMenuProps,
|
EditorMenuProps,
|
||||||
@@ -85,15 +81,12 @@ export function ImageMenu({ editor }: EditorMenuProps) {
|
|||||||
editor={editor}
|
editor={editor}
|
||||||
pluginKey={`image-menu}`}
|
pluginKey={`image-menu}`}
|
||||||
updateDelay={0}
|
updateDelay={0}
|
||||||
tippyOptions={{
|
options={{
|
||||||
getReferenceClientRect,
|
// getReferenceClientRect,
|
||||||
offset: [0, 8],
|
placement: "bottom",
|
||||||
zIndex: 99,
|
offset: 8,
|
||||||
popperOptions: {
|
//zIndex: 99,
|
||||||
modifiers: [{ name: "flip", enabled: false }],
|
flip: false,
|
||||||
},
|
|
||||||
plugins: [sticky],
|
|
||||||
sticky: "popper",
|
|
||||||
}}
|
}}
|
||||||
shouldShow={shouldShow}
|
shouldShow={shouldShow}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { BubbleMenu as BaseBubbleMenu } from "@tiptap/react";
|
import { BubbleMenu as BaseBubbleMenu } from "@tiptap/react/menus";
|
||||||
|
import { offset } from "@floating-ui/dom";
|
||||||
import React, { useCallback, useState } from "react";
|
import React, { useCallback, useState } from "react";
|
||||||
import { EditorMenuProps } from "@/features/editor/components/table/types/types.ts";
|
import { EditorMenuProps } from "@/features/editor/components/table/types/types.ts";
|
||||||
import { LinkEditorPanel } from "@/features/editor/components/link/link-editor-panel.tsx";
|
import { LinkEditorPanel } from "@/features/editor/components/link/link-editor-panel.tsx";
|
||||||
@@ -50,16 +51,13 @@ export function LinkMenu({ editor, appendTo }: EditorMenuProps) {
|
|||||||
editor={editor}
|
editor={editor}
|
||||||
pluginKey={`link-menu}`}
|
pluginKey={`link-menu}`}
|
||||||
updateDelay={0}
|
updateDelay={0}
|
||||||
tippyOptions={{
|
options={{
|
||||||
appendTo: () => {
|
onHide: () => {
|
||||||
return appendTo?.current;
|
|
||||||
},
|
|
||||||
onHidden: () => {
|
|
||||||
setShowEdit(false);
|
setShowEdit(false);
|
||||||
},
|
},
|
||||||
placement: "bottom",
|
placement: "bottom",
|
||||||
offset: [0, 5],
|
offset: 5,
|
||||||
zIndex: 101,
|
// zIndex: 101,
|
||||||
}}
|
}}
|
||||||
shouldShow={shouldShow}
|
shouldShow={shouldShow}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ const MentionList = forwardRef<any, MentionListProps>((props, ref) => {
|
|||||||
|
|
||||||
setRenderItems(items);
|
setRenderItems(items);
|
||||||
// update editor storage
|
// update editor storage
|
||||||
|
//@ts-ignore
|
||||||
props.editor.storage.mentionItems = items;
|
props.editor.storage.mentionItems = items;
|
||||||
}
|
}
|
||||||
}, [suggestion, isLoading]);
|
}, [suggestion, isLoading]);
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ const mentionRenderItems = () => {
|
|||||||
// destroy component if space is greater 3 without a match
|
// destroy component if space is greater 3 without a match
|
||||||
if (
|
if (
|
||||||
whitespaceCount > 3 &&
|
whitespaceCount > 3 &&
|
||||||
|
//@ts-ignore
|
||||||
props.editor.storage.mentionItems.length === 0
|
props.editor.storage.mentionItems.length === 0
|
||||||
) {
|
) {
|
||||||
popup?.[0]?.destroy();
|
popup?.[0]?.destroy();
|
||||||
|
|||||||
+2
@@ -73,6 +73,8 @@ function SearchAndReplaceDialog({ editor, editable = true }: PageFindDialogDialo
|
|||||||
if (!editor) return;
|
if (!editor) return;
|
||||||
|
|
||||||
const { results, resultIndex } = editor.storage.searchAndReplace;
|
const { results, resultIndex } = editor.storage.searchAndReplace;
|
||||||
|
//TODO: check type error
|
||||||
|
//@ts-ignore
|
||||||
const position: Range = results[resultIndex];
|
const position: Range = results[resultIndex];
|
||||||
|
|
||||||
if (!position) return;
|
if (!position) return;
|
||||||
|
|||||||
@@ -159,6 +159,7 @@ const CommandGroups: SlashMenuGroupedItemsType = {
|
|||||||
command: ({ editor, range }) => {
|
command: ({ editor, range }) => {
|
||||||
editor.chain().focus().deleteRange(range).run();
|
editor.chain().focus().deleteRange(range).run();
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
const pageId = editor.storage?.pageId;
|
const pageId = editor.storage?.pageId;
|
||||||
if (!pageId) return;
|
if (!pageId) return;
|
||||||
|
|
||||||
@@ -186,6 +187,7 @@ const CommandGroups: SlashMenuGroupedItemsType = {
|
|||||||
command: ({ editor, range }) => {
|
command: ({ editor, range }) => {
|
||||||
editor.chain().focus().deleteRange(range).run();
|
editor.chain().focus().deleteRange(range).run();
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
const pageId = editor.storage?.pageId;
|
const pageId = editor.storage?.pageId;
|
||||||
if (!pageId) return;
|
if (!pageId) return;
|
||||||
|
|
||||||
@@ -211,6 +213,7 @@ const CommandGroups: SlashMenuGroupedItemsType = {
|
|||||||
command: ({ editor, range }) => {
|
command: ({ editor, range }) => {
|
||||||
editor.chain().focus().deleteRange(range).run();
|
editor.chain().focus().deleteRange(range).run();
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
const pageId = editor.storage?.pageId;
|
const pageId = editor.storage?.pageId;
|
||||||
if (!pageId) return;
|
if (!pageId) return;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { BubbleMenu as BaseBubbleMenu } from "@tiptap/react";
|
import { BubbleMenu as BaseBubbleMenu } from "@tiptap/react/menus";
|
||||||
import React, { useCallback } from "react";
|
import React, { useCallback } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -57,12 +57,13 @@ export const TableCellMenu = React.memo(
|
|||||||
editor={editor}
|
editor={editor}
|
||||||
pluginKey="table-cell-menu"
|
pluginKey="table-cell-menu"
|
||||||
updateDelay={0}
|
updateDelay={0}
|
||||||
tippyOptions={{
|
options={{
|
||||||
appendTo: () => {
|
//appendTo: () => {
|
||||||
return appendTo?.current;
|
// return appendTo?.current;
|
||||||
},
|
// },
|
||||||
offset: [0, 15],
|
placement: "bottom",
|
||||||
zIndex: 99,
|
offset: 15,
|
||||||
|
//zIndex: 99,
|
||||||
}}
|
}}
|
||||||
shouldShow={shouldShow}
|
shouldShow={shouldShow}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import {
|
import { BubbleMenu as BaseBubbleMenu } from "@tiptap/react/menus";
|
||||||
BubbleMenu as BaseBubbleMenu,
|
import { posToDOMRect, findParentNode } from "@tiptap/react";
|
||||||
posToDOMRect,
|
|
||||||
findParentNode,
|
|
||||||
} from "@tiptap/react";
|
|
||||||
import { Node as PMNode } from "@tiptap/pm/model";
|
import { Node as PMNode } from "@tiptap/pm/model";
|
||||||
import React, { useCallback } from "react";
|
import React, { useCallback } from "react";
|
||||||
|
|
||||||
@@ -17,9 +14,11 @@ import {
|
|||||||
IconColumnRemove,
|
IconColumnRemove,
|
||||||
IconRowInsertBottom,
|
IconRowInsertBottom,
|
||||||
IconRowInsertTop,
|
IconRowInsertTop,
|
||||||
IconRowRemove, IconTableColumn, IconTableRow,
|
IconRowRemove,
|
||||||
|
IconTableColumn,
|
||||||
|
IconTableRow,
|
||||||
IconTrashX,
|
IconTrashX,
|
||||||
} from '@tabler/icons-react';
|
} from "@tabler/icons-react";
|
||||||
import { isCellSelection } from "@docmost/editor-ext";
|
import { isCellSelection } from "@docmost/editor-ext";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
@@ -91,38 +90,15 @@ export const TableMenu = React.memo(
|
|||||||
editor={editor}
|
editor={editor}
|
||||||
pluginKey="table-menu"
|
pluginKey="table-menu"
|
||||||
updateDelay={0}
|
updateDelay={0}
|
||||||
tippyOptions={{
|
options={{
|
||||||
getReferenceClientRect: getReferenceClientRect,
|
placement: "bottom",
|
||||||
offset: [0, 15],
|
offset: 15,
|
||||||
zIndex: 99,
|
//zIndex: 99,
|
||||||
popperOptions: {
|
|
||||||
modifiers: [
|
|
||||||
{
|
|
||||||
name: "preventOverflow",
|
|
||||||
enabled: true,
|
|
||||||
options: {
|
|
||||||
altAxis: true,
|
|
||||||
boundary: "clippingParents",
|
|
||||||
padding: 8,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "flip",
|
|
||||||
enabled: true,
|
|
||||||
options: {
|
|
||||||
boundary: editor.options.element,
|
|
||||||
fallbackPlacements: ["top", "bottom"],
|
|
||||||
padding: { top: 35, left: 8, right: 8, bottom: -Infinity },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
shouldShow={shouldShow}
|
shouldShow={shouldShow}
|
||||||
>
|
>
|
||||||
<ActionIcon.Group>
|
<ActionIcon.Group>
|
||||||
<Tooltip position="top" label={t("Add left column")}
|
<Tooltip position="top" label={t("Add left column")}>
|
||||||
>
|
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={addColumnLeft}
|
onClick={addColumnLeft}
|
||||||
variant="default"
|
variant="default"
|
||||||
@@ -188,8 +164,7 @@ export const TableMenu = React.memo(
|
|||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Tooltip position="top" label={t("Toggle header row")}
|
<Tooltip position="top" label={t("Toggle header row")}>
|
||||||
>
|
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={toggleHeaderRow}
|
onClick={toggleHeaderRow}
|
||||||
variant="default"
|
variant="default"
|
||||||
@@ -200,8 +175,7 @@ export const TableMenu = React.memo(
|
|||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Tooltip position="top" label={t("Toggle header column")}
|
<Tooltip position="top" label={t("Toggle header column")}>
|
||||||
>
|
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={toggleHeaderColumn}
|
onClick={toggleHeaderColumn}
|
||||||
variant="default"
|
variant="default"
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import {
|
import { BubbleMenu as BaseBubbleMenu } from "@tiptap/react/menus";
|
||||||
BubbleMenu as BaseBubbleMenu,
|
import { findParentNode, posToDOMRect } from "@tiptap/react";
|
||||||
findParentNode,
|
|
||||||
posToDOMRect,
|
|
||||||
} from "@tiptap/react";
|
|
||||||
import React, { useCallback } from "react";
|
import React, { useCallback } from "react";
|
||||||
import { sticky } from "tippy.js";
|
|
||||||
import { Node as PMNode } from "prosemirror-model";
|
import { Node as PMNode } from "prosemirror-model";
|
||||||
import {
|
import {
|
||||||
EditorMenuProps,
|
EditorMenuProps,
|
||||||
@@ -85,15 +81,12 @@ export function VideoMenu({ editor }: EditorMenuProps) {
|
|||||||
editor={editor}
|
editor={editor}
|
||||||
pluginKey={`video-menu}`}
|
pluginKey={`video-menu}`}
|
||||||
updateDelay={0}
|
updateDelay={0}
|
||||||
tippyOptions={{
|
options={{
|
||||||
getReferenceClientRect,
|
//getReferenceClientRect,
|
||||||
offset: [0, 8],
|
placement: "bottom",
|
||||||
zIndex: 99,
|
offset: 8,
|
||||||
popperOptions: {
|
//zIndex: 99,
|
||||||
modifiers: [{ name: "flip", enabled: false }],
|
flip: false,
|
||||||
},
|
|
||||||
plugins: [sticky],
|
|
||||||
sticky: "popper",
|
|
||||||
}}
|
}}
|
||||||
shouldShow={shouldShow}
|
shouldShow={shouldShow}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
import { StarterKit } from "@tiptap/starter-kit";
|
import { StarterKit } from "@tiptap/starter-kit";
|
||||||
import { Placeholder } from "@tiptap/extension-placeholder";
|
|
||||||
import { TextAlign } from "@tiptap/extension-text-align";
|
import { TextAlign } from "@tiptap/extension-text-align";
|
||||||
import { TaskList } from "@tiptap/extension-task-list";
|
import {
|
||||||
import { TaskItem } from "@tiptap/extension-task-item";
|
TaskList,
|
||||||
import { Underline } from "@tiptap/extension-underline";
|
TaskItem,
|
||||||
|
} from "@tiptap/extension-list";
|
||||||
|
import {
|
||||||
|
Placeholder,
|
||||||
|
CharacterCount,
|
||||||
|
} from "@tiptap/extensions";
|
||||||
import { Superscript } from "@tiptap/extension-superscript";
|
import { Superscript } from "@tiptap/extension-superscript";
|
||||||
import SubScript from "@tiptap/extension-subscript";
|
import SubScript from "@tiptap/extension-subscript";
|
||||||
import { Highlight } from "@tiptap/extension-highlight";
|
import { Highlight } from "@tiptap/extension-highlight";
|
||||||
@@ -12,7 +16,7 @@ import { TextStyle } from "@tiptap/extension-text-style";
|
|||||||
import { Color } from "@tiptap/extension-color";
|
import { Color } from "@tiptap/extension-color";
|
||||||
import SlashCommand from "@/features/editor/extensions/slash-command";
|
import SlashCommand from "@/features/editor/extensions/slash-command";
|
||||||
import { Collaboration } from "@tiptap/extension-collaboration";
|
import { Collaboration } from "@tiptap/extension-collaboration";
|
||||||
import { CollaborationCursor } from "@tiptap/extension-collaboration-cursor";
|
import { CollaborationCaret } from "@tiptap/extension-collaboration-caret";
|
||||||
import { HocuspocusProvider } from "@hocuspocus/provider";
|
import { HocuspocusProvider } from "@hocuspocus/provider";
|
||||||
import {
|
import {
|
||||||
Comment,
|
Comment,
|
||||||
@@ -73,7 +77,6 @@ import MentionView from "@/features/editor/components/mention/mention-view.tsx";
|
|||||||
import i18n from "@/i18n.ts";
|
import i18n from "@/i18n.ts";
|
||||||
import { MarkdownClipboard } from "@/features/editor/extensions/markdown-clipboard.ts";
|
import { MarkdownClipboard } from "@/features/editor/extensions/markdown-clipboard.ts";
|
||||||
import EmojiCommand from "./emoji-command";
|
import EmojiCommand from "./emoji-command";
|
||||||
import { CharacterCount } from "@tiptap/extension-character-count";
|
|
||||||
import { countWords } from "alfaaz";
|
import { countWords } from "alfaaz";
|
||||||
|
|
||||||
const lowlight = createLowlight(common);
|
const lowlight = createLowlight(common);
|
||||||
@@ -90,7 +93,7 @@ lowlight.register("scala", scala);
|
|||||||
|
|
||||||
export const mainExtensions = [
|
export const mainExtensions = [
|
||||||
StarterKit.configure({
|
StarterKit.configure({
|
||||||
history: false,
|
undoRedo: false,
|
||||||
dropcursor: {
|
dropcursor: {
|
||||||
width: 3,
|
width: 3,
|
||||||
color: "#70CFF8",
|
color: "#70CFF8",
|
||||||
@@ -101,6 +104,8 @@ export const mainExtensions = [
|
|||||||
spellcheck: false,
|
spellcheck: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
link: false,
|
||||||
|
trailingNode: false,
|
||||||
}),
|
}),
|
||||||
Placeholder.configure({
|
Placeholder.configure({
|
||||||
placeholder: ({ node }) => {
|
placeholder: ({ node }) => {
|
||||||
@@ -122,7 +127,6 @@ export const mainExtensions = [
|
|||||||
TaskItem.configure({
|
TaskItem.configure({
|
||||||
nested: true,
|
nested: true,
|
||||||
}),
|
}),
|
||||||
Underline,
|
|
||||||
LinkExtension.configure({
|
LinkExtension.configure({
|
||||||
openOnClick: false,
|
openOnClick: false,
|
||||||
}),
|
}),
|
||||||
@@ -221,17 +225,17 @@ export const mainExtensions = [
|
|||||||
SearchAndReplace.extend({
|
SearchAndReplace.extend({
|
||||||
addKeyboardShortcuts() {
|
addKeyboardShortcuts() {
|
||||||
return {
|
return {
|
||||||
'Mod-f': () => {
|
"Mod-f": () => {
|
||||||
const event = new CustomEvent("openFindDialogFromEditor", {});
|
const event = new CustomEvent("openFindDialogFromEditor", {});
|
||||||
document.dispatchEvent(event);
|
document.dispatchEvent(event);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
'Escape': () => {
|
Escape: () => {
|
||||||
const event = new CustomEvent("closeFindDialogFromEditor", {});
|
const event = new CustomEvent("closeFindDialogFromEditor", {});
|
||||||
document.dispatchEvent(event);
|
document.dispatchEvent(event);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
}).configure(),
|
}).configure(),
|
||||||
] as any;
|
] as any;
|
||||||
@@ -242,7 +246,7 @@ export const collabExtensions: CollabExtensions = (provider, user) => [
|
|||||||
Collaboration.configure({
|
Collaboration.configure({
|
||||||
document: provider.document,
|
document: provider.document,
|
||||||
}),
|
}),
|
||||||
CollaborationCursor.configure({
|
CollaborationCaret.configure({
|
||||||
provider,
|
provider,
|
||||||
user: {
|
user: {
|
||||||
name: user.name,
|
name: user.name,
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export default function PageEditor({
|
|||||||
const [isLocalSynced, setLocalSynced] = useState(false);
|
const [isLocalSynced, setLocalSynced] = useState(false);
|
||||||
const [isRemoteSynced, setRemoteSynced] = useState(false);
|
const [isRemoteSynced, setRemoteSynced] = useState(false);
|
||||||
const [yjsConnectionStatus, setYjsConnectionStatus] = useAtom(
|
const [yjsConnectionStatus, setYjsConnectionStatus] = useAtom(
|
||||||
yjsConnectionStatusAtom
|
yjsConnectionStatusAtom,
|
||||||
);
|
);
|
||||||
const menuContainerRef = useRef(null);
|
const menuContainerRef = useRef(null);
|
||||||
const documentName = `page.${pageId}`;
|
const documentName = `page.${pageId}`;
|
||||||
@@ -100,6 +100,7 @@ export default function PageEditor({
|
|||||||
|
|
||||||
// Track when collaborative provider is ready and synced
|
// Track when collaborative provider is ready and synced
|
||||||
const [collabReady, setCollabReady] = useState(false);
|
const [collabReady, setCollabReady] = useState(false);
|
||||||
|
/*
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
remoteProvider?.status === WebSocketStatus.Connected &&
|
remoteProvider?.status === WebSocketStatus.Connected &&
|
||||||
@@ -109,6 +110,7 @@ export default function PageEditor({
|
|||||||
setCollabReady(true);
|
setCollabReady(true);
|
||||||
}
|
}
|
||||||
}, [remoteProvider?.status, isLocalSynced, isRemoteSynced]);
|
}, [remoteProvider?.status, isLocalSynced, isRemoteSynced]);
|
||||||
|
*/
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!providersRef.current) {
|
if (!providersRef.current) {
|
||||||
@@ -119,8 +121,8 @@ export default function PageEditor({
|
|||||||
url: collaborationURL,
|
url: collaborationURL,
|
||||||
document: ydoc,
|
document: ydoc,
|
||||||
token: collabQuery?.token,
|
token: collabQuery?.token,
|
||||||
connect: true,
|
//connect: true,
|
||||||
preserveConnection: false,
|
//preserveConnection: false,
|
||||||
onAuthenticationFailed: (auth: onAuthenticationFailedParameters) => {
|
onAuthenticationFailed: (auth: onAuthenticationFailedParameters) => {
|
||||||
const payload = jwtDecode(collabQuery?.token);
|
const payload = jwtDecode(collabQuery?.token);
|
||||||
const now = Date.now().valueOf() / 1000;
|
const now = Date.now().valueOf() / 1000;
|
||||||
@@ -137,11 +139,11 @@ export default function PageEditor({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onStatus: (status) => {
|
//onStatus: (status) => {
|
||||||
if (status.status === "connected") {
|
// if (status.status === "connected") {
|
||||||
setYjsConnectionStatus(status.status);
|
// setYjsConnectionStatus(status.status);
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
});
|
});
|
||||||
remote.on("synced", () => setRemoteSynced(true));
|
remote.on("synced", () => setRemoteSynced(true));
|
||||||
remote.on("disconnect", () => {
|
remote.on("disconnect", () => {
|
||||||
@@ -176,13 +178,14 @@ export default function PageEditor({
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Only connect/disconnect on tab/idle, not destroy
|
// Only connect/disconnect on tab/idle, not destroy
|
||||||
|
/*
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!providersReady || !providersRef.current) return;
|
if (!providersReady || !providersRef.current) return;
|
||||||
const remoteProvider = providersRef.current.remote;
|
const remoteProvider = providersRef.current.remote;
|
||||||
if (
|
if (
|
||||||
isIdle &&
|
isIdle &&
|
||||||
documentState === "hidden" &&
|
documentState === "hidden" &&
|
||||||
remoteProvider.status === WebSocketStatus.Connected
|
remoteProvider === WebSocketStatus.Connected
|
||||||
) {
|
) {
|
||||||
remoteProvider.disconnect();
|
remoteProvider.disconnect();
|
||||||
setIsCollabReady(false);
|
setIsCollabReady(false);
|
||||||
@@ -197,6 +200,7 @@ export default function PageEditor({
|
|||||||
setTimeout(() => setIsCollabReady(true), 500);
|
setTimeout(() => setIsCollabReady(true), 500);
|
||||||
}
|
}
|
||||||
}, [isIdle, documentState, providersReady, resetIdle]);
|
}, [isIdle, documentState, providersReady, resetIdle]);
|
||||||
|
*/
|
||||||
|
|
||||||
const extensions = useMemo(() => {
|
const extensions = useMemo(() => {
|
||||||
if (!remoteProvider || !currentUser?.user) return mainExtensions;
|
if (!remoteProvider || !currentUser?.user) return mainExtensions;
|
||||||
@@ -217,7 +221,7 @@ export default function PageEditor({
|
|||||||
scrollMargin: 80,
|
scrollMargin: 80,
|
||||||
handleDOMEvents: {
|
handleDOMEvents: {
|
||||||
keydown: (_view, event) => {
|
keydown: (_view, event) => {
|
||||||
if ((event.ctrlKey || event.metaKey) && event.code === 'KeyS') {
|
if ((event.ctrlKey || event.metaKey) && event.code === "KeyS") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -252,6 +256,7 @@ export default function PageEditor({
|
|||||||
if (editor) {
|
if (editor) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
setEditor(editor);
|
setEditor(editor);
|
||||||
|
// @ts-ignore
|
||||||
editor.storage.pageId = pageId;
|
editor.storage.pageId = pageId;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -262,7 +267,7 @@ export default function PageEditor({
|
|||||||
debouncedUpdateContent(editorJson);
|
debouncedUpdateContent(editorJson);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[pageId, editable, remoteProvider]
|
[pageId, editable, remoteProvider],
|
||||||
);
|
);
|
||||||
|
|
||||||
const debouncedUpdateContent = useDebouncedCallback((newContent: any) => {
|
const debouncedUpdateContent = useDebouncedCallback((newContent: any) => {
|
||||||
@@ -300,7 +305,7 @@ export default function PageEditor({
|
|||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener(
|
document.removeEventListener(
|
||||||
"ACTIVE_COMMENT_EVENT",
|
"ACTIVE_COMMENT_EVENT",
|
||||||
handleActiveCommentEvent
|
handleActiveCommentEvent,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
@@ -311,6 +316,7 @@ export default function PageEditor({
|
|||||||
setAsideState({ tab: "", isAsideOpen: false });
|
setAsideState({ tab: "", isAsideOpen: false });
|
||||||
}, [pageId]);
|
}, [pageId]);
|
||||||
|
|
||||||
|
/*
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (remoteProvider?.status === WebSocketStatus.Connecting) {
|
if (remoteProvider?.status === WebSocketStatus.Connecting) {
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
@@ -319,9 +325,10 @@ export default function PageEditor({
|
|||||||
return () => clearTimeout(timeout);
|
return () => clearTimeout(timeout);
|
||||||
}
|
}
|
||||||
}, [remoteProvider?.status]);
|
}, [remoteProvider?.status]);
|
||||||
|
*/
|
||||||
const isSynced = isLocalSynced && isRemoteSynced;
|
const isSynced = isLocalSynced && isRemoteSynced;
|
||||||
|
|
||||||
|
/*
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const collabReadyTimeout = setTimeout(() => {
|
const collabReadyTimeout = setTimeout(() => {
|
||||||
if (
|
if (
|
||||||
@@ -334,6 +341,7 @@ export default function PageEditor({
|
|||||||
}, 500);
|
}, 500);
|
||||||
return () => clearTimeout(collabReadyTimeout);
|
return () => clearTimeout(collabReadyTimeout);
|
||||||
}, [isRemoteSynced, isLocalSynced, remoteProvider?.status]);
|
}, [isRemoteSynced, isLocalSynced, remoteProvider?.status]);
|
||||||
|
*/
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Only honor user default page edit mode preference and permissions
|
// Only honor user default page edit mode preference and permissions
|
||||||
@@ -351,8 +359,9 @@ export default function PageEditor({
|
|||||||
}, [userPageEditMode, editor, editable]);
|
}, [userPageEditMode, editor, editable]);
|
||||||
|
|
||||||
const hasConnectedOnceRef = useRef(false);
|
const hasConnectedOnceRef = useRef(false);
|
||||||
const [showStatic, setShowStatic] = useState(true);
|
const [showStatic, setShowStatic] = useState(false);
|
||||||
|
|
||||||
|
/*
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
!hasConnectedOnceRef.current &&
|
!hasConnectedOnceRef.current &&
|
||||||
@@ -361,7 +370,7 @@ export default function PageEditor({
|
|||||||
hasConnectedOnceRef.current = true;
|
hasConnectedOnceRef.current = true;
|
||||||
setShowStatic(false);
|
setShowStatic(false);
|
||||||
}
|
}
|
||||||
}, [remoteProvider?.status]);
|
}, [remoteProvider?.status]);*/
|
||||||
|
|
||||||
if (showStatic) {
|
if (showStatic) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ function HistoryList({ pageId }: Props) {
|
|||||||
mainEditorTitle
|
mainEditorTitle
|
||||||
.chain()
|
.chain()
|
||||||
.clearContent()
|
.clearContent()
|
||||||
.setContent(activeHistoryData.title, true)
|
.setContent(activeHistoryData.title, { emitUpdate: true })
|
||||||
.run();
|
.run();
|
||||||
mainEditor
|
mainEditor
|
||||||
.chain()
|
.chain()
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export class CollaborationGateway {
|
|||||||
) {
|
) {
|
||||||
this.redisConfig = parseRedisUrl(this.environmentService.getRedisUrl());
|
this.redisConfig = parseRedisUrl(this.environmentService.getRedisUrl());
|
||||||
|
|
||||||
this.hocuspocus = HocuspocusServer.configure({
|
this.hocuspocus = new Hocuspocus({
|
||||||
debounce: 10000,
|
debounce: 10000,
|
||||||
maxDebounce: 45000,
|
maxDebounce: 45000,
|
||||||
unloadImmediately: false,
|
unloadImmediately: false,
|
||||||
@@ -65,6 +65,6 @@ export class CollaborationGateway {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async destroy(): Promise<void> {
|
async destroy(): Promise<void> {
|
||||||
await this.hocuspocus.destroy();
|
//await this.hocuspocus.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { StarterKit } from '@tiptap/starter-kit';
|
import { StarterKit } from '@tiptap/starter-kit';
|
||||||
import { TextAlign } from '@tiptap/extension-text-align';
|
import { TextAlign } from '@tiptap/extension-text-align';
|
||||||
import { TaskList } from '@tiptap/extension-task-list';
|
|
||||||
import { TaskItem } from '@tiptap/extension-task-item';
|
|
||||||
import { Underline } from '@tiptap/extension-underline';
|
|
||||||
import { Superscript } from '@tiptap/extension-superscript';
|
import { Superscript } from '@tiptap/extension-superscript';
|
||||||
import SubScript from '@tiptap/extension-subscript';
|
import SubScript from '@tiptap/extension-subscript';
|
||||||
import { Highlight } from '@tiptap/extension-highlight';
|
import { Highlight } from '@tiptap/extension-highlight';
|
||||||
@@ -10,6 +7,7 @@ import { Typography } from '@tiptap/extension-typography';
|
|||||||
import { TextStyle } from '@tiptap/extension-text-style';
|
import { TextStyle } from '@tiptap/extension-text-style';
|
||||||
import { Color } from '@tiptap/extension-color';
|
import { Color } from '@tiptap/extension-color';
|
||||||
import { Youtube } from '@tiptap/extension-youtube';
|
import { Youtube } from '@tiptap/extension-youtube';
|
||||||
|
import { TaskList, TaskItem } from '@tiptap/extension-list';
|
||||||
import {
|
import {
|
||||||
Callout,
|
Callout,
|
||||||
Comment,
|
Comment,
|
||||||
@@ -51,7 +49,6 @@ export const tiptapExtensions = [
|
|||||||
TaskItem.configure({
|
TaskItem.configure({
|
||||||
nested: true,
|
nested: true,
|
||||||
}),
|
}),
|
||||||
Underline,
|
|
||||||
LinkExtension,
|
LinkExtension,
|
||||||
Superscript,
|
Superscript,
|
||||||
SubScript,
|
SubScript,
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export class AuthenticationExtension implements Extension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (userSpaceRole === SpaceRole.READER) {
|
if (userSpaceRole === SpaceRole.READER) {
|
||||||
data.connection.readOnly = true;
|
data.connectionConfig.readOnly = true;
|
||||||
this.logger.debug(`User granted readonly access to page: ${pageId}`);
|
this.logger.debug(`User granted readonly access to page: ${pageId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+35
-38
@@ -20,53 +20,50 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@braintree/sanitize-url": "^7.1.0",
|
"@braintree/sanitize-url": "^7.1.0",
|
||||||
"@docmost/editor-ext": "workspace:*",
|
"@docmost/editor-ext": "workspace:*",
|
||||||
"@hocuspocus/extension-redis": "^2.15.2",
|
"@floating-ui/dom": "^1.7.3",
|
||||||
"@hocuspocus/provider": "^2.15.2",
|
"@hocuspocus/extension-redis": "^3.2.2",
|
||||||
"@hocuspocus/server": "^2.15.2",
|
"@hocuspocus/provider": "^3.2.2",
|
||||||
"@hocuspocus/transformer": "^2.15.2",
|
"@hocuspocus/server": "^3.2.2",
|
||||||
|
"@hocuspocus/transformer": "^3.2.2",
|
||||||
"@joplin/turndown": "^4.0.74",
|
"@joplin/turndown": "^4.0.74",
|
||||||
"@joplin/turndown-plugin-gfm": "^1.0.56",
|
"@joplin/turndown-plugin-gfm": "^1.0.56",
|
||||||
"@sindresorhus/slugify": "1.1.0",
|
"@sindresorhus/slugify": "1.1.0",
|
||||||
"@tiptap/core": "^2.10.3",
|
"@tiptap/core": "^3.0.9",
|
||||||
"@tiptap/extension-code-block": "^2.10.3",
|
"@tiptap/extension-code-block": "^3.0.9",
|
||||||
"@tiptap/extension-code-block-lowlight": "^2.10.3",
|
"@tiptap/extension-collaboration": "^3.0.9",
|
||||||
"@tiptap/extension-collaboration": "^2.10.3",
|
"@tiptap/extension-collaboration-caret": "^3.0.9",
|
||||||
"@tiptap/extension-collaboration-cursor": "^2.10.3",
|
"@tiptap/extension-color": "^3.0.9",
|
||||||
"@tiptap/extension-color": "^2.10.3",
|
"@tiptap/extension-document": "^3.0.9",
|
||||||
"@tiptap/extension-document": "^2.10.3",
|
"@tiptap/extension-heading": "^3.0.9",
|
||||||
"@tiptap/extension-heading": "^2.10.3",
|
"@tiptap/extension-highlight": "^3.0.9",
|
||||||
"@tiptap/extension-highlight": "^2.10.3",
|
"@tiptap/extension-history": "^3.0.9",
|
||||||
"@tiptap/extension-history": "^2.10.3",
|
"@tiptap/extension-image": "^3.0.9",
|
||||||
"@tiptap/extension-image": "^2.10.3",
|
"@tiptap/extension-link": "^3.0.9",
|
||||||
"@tiptap/extension-link": "^2.10.3",
|
"@tiptap/extension-list": "^3.0.9",
|
||||||
"@tiptap/extension-list-item": "^2.10.3",
|
"@tiptap/extension-list-item": "^3.0.9",
|
||||||
"@tiptap/extension-list-keymap": "^2.10.3",
|
"@tiptap/extension-list-keymap": "^3.0.9",
|
||||||
"@tiptap/extension-placeholder": "^2.10.3",
|
"@tiptap/extension-placeholder": "^3.0.9",
|
||||||
"@tiptap/extension-subscript": "^2.10.3",
|
"@tiptap/extension-subscript": "^3.0.9",
|
||||||
"@tiptap/extension-superscript": "^2.10.3",
|
"@tiptap/extension-superscript": "^3.0.9",
|
||||||
"@tiptap/extension-table": "^2.10.3",
|
"@tiptap/extension-table": "^3.0.9",
|
||||||
"@tiptap/extension-table-cell": "^2.10.3",
|
"@tiptap/extension-text": "^3.0.9",
|
||||||
"@tiptap/extension-table-header": "^2.10.3",
|
"@tiptap/extension-text-align": "^3.0.9",
|
||||||
"@tiptap/extension-table-row": "^2.10.3",
|
"@tiptap/extension-text-style": "^3.0.9",
|
||||||
"@tiptap/extension-task-item": "^2.10.3",
|
"@tiptap/extension-typography": "^3.0.9",
|
||||||
"@tiptap/extension-task-list": "^2.10.3",
|
"@tiptap/extension-youtube": "^3.0.9",
|
||||||
"@tiptap/extension-text": "^2.10.3",
|
"@tiptap/html": "^3.0.9",
|
||||||
"@tiptap/extension-text-align": "^2.10.3",
|
"@tiptap/pm": "^3.0.9",
|
||||||
"@tiptap/extension-text-style": "^2.10.3",
|
"@tiptap/react": "^3.0.9",
|
||||||
"@tiptap/extension-typography": "^2.10.3",
|
"@tiptap/starter-kit": "^3.0.9",
|
||||||
"@tiptap/extension-underline": "^2.10.3",
|
"@tiptap/suggestion": "^3.0.9",
|
||||||
"@tiptap/extension-youtube": "^2.10.3",
|
|
||||||
"@tiptap/html": "^2.10.3",
|
|
||||||
"@tiptap/pm": "^2.10.3",
|
|
||||||
"@tiptap/react": "^2.10.3",
|
|
||||||
"@tiptap/starter-kit": "^2.10.3",
|
|
||||||
"@tiptap/suggestion": "^2.10.3",
|
|
||||||
"@types/qrcode": "^1.5.5",
|
"@types/qrcode": "^1.5.5",
|
||||||
"bytes": "^3.1.2",
|
"bytes": "^3.1.2",
|
||||||
|
"core": "link:highlight.js/lib/core",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"dompurify": "^3.2.6",
|
"dompurify": "^3.2.6",
|
||||||
"fractional-indexing-jittered": "^1.0.0",
|
"fractional-indexing-jittered": "^1.0.0",
|
||||||
|
"highlight.js": "^11.11.1",
|
||||||
"ioredis": "^5.4.1",
|
"ioredis": "^5.4.1",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"linkifyjs": "^4.3.2",
|
"linkifyjs": "^4.3.2",
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
import CodeBlockLowlight, {
|
|
||||||
CodeBlockLowlightOptions,
|
|
||||||
} from "@tiptap/extension-code-block-lowlight";
|
|
||||||
import { ReactNodeViewRenderer } from "@tiptap/react";
|
|
||||||
|
|
||||||
export interface CustomCodeBlockOptions extends CodeBlockLowlightOptions {
|
|
||||||
view: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TAB_CHAR = "\u00A0\u00A0";
|
|
||||||
|
|
||||||
export const CustomCodeBlock = CodeBlockLowlight.extend<CustomCodeBlockOptions>(
|
|
||||||
{
|
|
||||||
selectable: true,
|
|
||||||
|
|
||||||
addOptions() {
|
|
||||||
return {
|
|
||||||
...this.parent?.(),
|
|
||||||
view: null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
addKeyboardShortcuts() {
|
|
||||||
return {
|
|
||||||
...this.parent?.(),
|
|
||||||
Tab: () => {
|
|
||||||
if (this.editor.isActive("codeBlock")) {
|
|
||||||
this.editor
|
|
||||||
.chain()
|
|
||||||
.command(({ tr }) => {
|
|
||||||
tr.insertText(TAB_CHAR);
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
.run();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Mod-a": () => {
|
|
||||||
if (this.editor.isActive("codeBlock")) {
|
|
||||||
const { state } = this.editor;
|
|
||||||
const { $from } = state.selection;
|
|
||||||
|
|
||||||
let codeBlockNode = null;
|
|
||||||
let codeBlockPos = null;
|
|
||||||
let depth = 0;
|
|
||||||
|
|
||||||
for (depth = $from.depth; depth > 0; depth--) {
|
|
||||||
const node = $from.node(depth);
|
|
||||||
if (node.type.name === "codeBlock") {
|
|
||||||
codeBlockNode = node;
|
|
||||||
codeBlockPos = $from.start(depth) - 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (codeBlockNode && codeBlockPos !== null) {
|
|
||||||
const codeBlockStart = codeBlockPos;
|
|
||||||
const codeBlockEnd = codeBlockPos + codeBlockNode.nodeSize;
|
|
||||||
|
|
||||||
const contentStart = codeBlockStart + 1;
|
|
||||||
const contentEnd = codeBlockEnd - 1;
|
|
||||||
|
|
||||||
this.editor.commands.setTextSelection({
|
|
||||||
from: contentStart,
|
|
||||||
to: contentEnd,
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
addNodeView() {
|
|
||||||
return ReactNodeViewRenderer(this.options.view);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import type { CodeBlockOptions } from '@tiptap/extension-code-block'
|
||||||
|
import CodeBlock from '@tiptap/extension-code-block'
|
||||||
|
|
||||||
|
import { LowlightPlugin } from './lowlight-plugin.js'
|
||||||
|
import { ReactNodeViewRenderer } from '@tiptap/react';
|
||||||
|
|
||||||
|
export interface CodeBlockLowlightOptions extends CodeBlockOptions {
|
||||||
|
/**
|
||||||
|
* The lowlight instance.
|
||||||
|
*/
|
||||||
|
lowlight: any,
|
||||||
|
view: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TAB_CHAR = "\u00A0\u00A0";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This extension allows you to highlight code blocks with lowlight.
|
||||||
|
* @see https://tiptap.dev/api/nodes/code-block-lowlight
|
||||||
|
*/
|
||||||
|
export const CustomCodeBlock = CodeBlock.extend<CodeBlockLowlightOptions>({
|
||||||
|
selectable: true,
|
||||||
|
|
||||||
|
addOptions() {
|
||||||
|
return {
|
||||||
|
...this.parent?.(),
|
||||||
|
lowlight: {},
|
||||||
|
languageClassPrefix: 'language-',
|
||||||
|
exitOnTripleEnter: true,
|
||||||
|
exitOnArrowDown: true,
|
||||||
|
defaultLanguage: null,
|
||||||
|
HTMLAttributes: {},
|
||||||
|
view: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
addKeyboardShortcuts() {
|
||||||
|
return {
|
||||||
|
...this.parent?.(),
|
||||||
|
Tab: () => {
|
||||||
|
if (this.editor.isActive("codeBlock")) {
|
||||||
|
this.editor
|
||||||
|
.chain()
|
||||||
|
.command(({ tr }) => {
|
||||||
|
tr.insertText(TAB_CHAR);
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.run();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Mod-a": () => {
|
||||||
|
if (this.editor.isActive("codeBlock")) {
|
||||||
|
const { state } = this.editor;
|
||||||
|
const { $from } = state.selection;
|
||||||
|
|
||||||
|
let codeBlockNode = null;
|
||||||
|
let codeBlockPos = null;
|
||||||
|
let depth = 0;
|
||||||
|
|
||||||
|
for (depth = $from.depth; depth > 0; depth--) {
|
||||||
|
const node = $from.node(depth);
|
||||||
|
if (node.type.name === "codeBlock") {
|
||||||
|
codeBlockNode = node;
|
||||||
|
codeBlockPos = $from.start(depth) - 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codeBlockNode && codeBlockPos !== null) {
|
||||||
|
const codeBlockStart = codeBlockPos;
|
||||||
|
const codeBlockEnd = codeBlockPos + codeBlockNode.nodeSize;
|
||||||
|
|
||||||
|
const contentStart = codeBlockStart + 1;
|
||||||
|
const contentEnd = codeBlockEnd - 1;
|
||||||
|
|
||||||
|
this.editor.commands.setTextSelection({
|
||||||
|
from: contentStart,
|
||||||
|
to: contentEnd,
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
addNodeView() {
|
||||||
|
return ReactNodeViewRenderer(this.options.view);
|
||||||
|
},
|
||||||
|
|
||||||
|
addProseMirrorPlugins() {
|
||||||
|
return [
|
||||||
|
...(this.parent?.() || []),
|
||||||
|
LowlightPlugin({
|
||||||
|
name: this.name,
|
||||||
|
lowlight: this.options.lowlight,
|
||||||
|
defaultLanguage: this.options.defaultLanguage,
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { CustomCodeBlock } from "./custom-code-block";
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
import { findChildren } from '@tiptap/core'
|
||||||
|
import type { Node as ProsemirrorNode } from '@tiptap/pm/model'
|
||||||
|
import { Plugin, PluginKey } from '@tiptap/pm/state'
|
||||||
|
import { Decoration, DecorationSet } from '@tiptap/pm/view'
|
||||||
|
// @ts-ignore
|
||||||
|
import highlight from 'highlight.js/lib/core'
|
||||||
|
|
||||||
|
function parseNodes(nodes: any[], className: string[] = []): { text: string; classes: string[] }[] {
|
||||||
|
return nodes
|
||||||
|
.map(node => {
|
||||||
|
const classes = [...className, ...(node.properties ? node.properties.className : [])]
|
||||||
|
|
||||||
|
if (node.children) {
|
||||||
|
return parseNodes(node.children, classes)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
text: node.value,
|
||||||
|
classes,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.flat()
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHighlightNodes(result: any) {
|
||||||
|
// `.value` for lowlight v1, `.children` for lowlight v2
|
||||||
|
return result.value || result.children || []
|
||||||
|
}
|
||||||
|
|
||||||
|
function registered(aliasOrLanguage: string) {
|
||||||
|
return Boolean(highlight.getLanguage(aliasOrLanguage))
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDecorations({
|
||||||
|
doc,
|
||||||
|
name,
|
||||||
|
lowlight,
|
||||||
|
defaultLanguage,
|
||||||
|
}: {
|
||||||
|
doc: ProsemirrorNode
|
||||||
|
name: string
|
||||||
|
lowlight: any
|
||||||
|
defaultLanguage: string | null | undefined
|
||||||
|
}) {
|
||||||
|
const decorations: Decoration[] = []
|
||||||
|
|
||||||
|
findChildren(doc, node => node.type.name === name).forEach(block => {
|
||||||
|
let from = block.pos + 1
|
||||||
|
const language = block.node.attrs.language || defaultLanguage
|
||||||
|
const languages = lowlight.listLanguages()
|
||||||
|
|
||||||
|
const nodes =
|
||||||
|
language && (languages.includes(language) || registered(language) || lowlight.registered?.(language))
|
||||||
|
? getHighlightNodes(lowlight.highlight(language, block.node.textContent))
|
||||||
|
: getHighlightNodes(lowlight.highlightAuto(block.node.textContent))
|
||||||
|
|
||||||
|
parseNodes(nodes).forEach(node => {
|
||||||
|
const to = from + node.text.length
|
||||||
|
|
||||||
|
if (node.classes.length) {
|
||||||
|
const decoration = Decoration.inline(from, to, {
|
||||||
|
class: node.classes.join(' '),
|
||||||
|
})
|
||||||
|
|
||||||
|
decorations.push(decoration)
|
||||||
|
}
|
||||||
|
|
||||||
|
from = to
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return DecorationSet.create(doc, decorations)
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||||
|
function isFunction(param: any): param is Function {
|
||||||
|
return typeof param === 'function'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LowlightPlugin({
|
||||||
|
name,
|
||||||
|
lowlight,
|
||||||
|
defaultLanguage,
|
||||||
|
}: {
|
||||||
|
name: string
|
||||||
|
lowlight: any
|
||||||
|
defaultLanguage: string | null | undefined
|
||||||
|
}) {
|
||||||
|
if (!['highlight', 'highlightAuto', 'listLanguages'].every(api => isFunction(lowlight[api]))) {
|
||||||
|
throw Error('You should provide an instance of lowlight to use the code-block-lowlight extension')
|
||||||
|
}
|
||||||
|
|
||||||
|
const lowlightPlugin: Plugin<any> = new Plugin({
|
||||||
|
key: new PluginKey('lowlight'),
|
||||||
|
|
||||||
|
state: {
|
||||||
|
init: (_, { doc }) =>
|
||||||
|
getDecorations({
|
||||||
|
doc,
|
||||||
|
name,
|
||||||
|
lowlight,
|
||||||
|
defaultLanguage,
|
||||||
|
}),
|
||||||
|
apply: (transaction, decorationSet, oldState, newState) => {
|
||||||
|
const oldNodeName = oldState.selection.$head.parent.type.name
|
||||||
|
const newNodeName = newState.selection.$head.parent.type.name
|
||||||
|
const oldNodes = findChildren(oldState.doc, node => node.type.name === name)
|
||||||
|
const newNodes = findChildren(newState.doc, node => node.type.name === name)
|
||||||
|
|
||||||
|
if (
|
||||||
|
transaction.docChanged &&
|
||||||
|
// Apply decorations if:
|
||||||
|
// selection includes named node,
|
||||||
|
([oldNodeName, newNodeName].includes(name) ||
|
||||||
|
// OR transaction adds/removes named node,
|
||||||
|
newNodes.length !== oldNodes.length ||
|
||||||
|
// OR transaction has changes that completely encapsulte a node
|
||||||
|
// (for example, a transaction that affects the entire document).
|
||||||
|
// Such transactions can happen during collab syncing via y-prosemirror, for example.
|
||||||
|
transaction.steps.some(step => {
|
||||||
|
// @ts-ignore
|
||||||
|
return (
|
||||||
|
// @ts-ignore
|
||||||
|
step.from !== undefined &&
|
||||||
|
// @ts-ignore
|
||||||
|
step.to !== undefined &&
|
||||||
|
oldNodes.some(node => {
|
||||||
|
// @ts-ignore
|
||||||
|
return (
|
||||||
|
// @ts-ignore
|
||||||
|
node.pos >= step.from &&
|
||||||
|
// @ts-ignore
|
||||||
|
node.pos + node.node.nodeSize <= step.to
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
) {
|
||||||
|
return getDecorations({
|
||||||
|
doc: transaction.doc,
|
||||||
|
name,
|
||||||
|
lowlight,
|
||||||
|
defaultLanguage,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return decorationSet.map(transaction.mapping, transaction.doc)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
decorations(state) {
|
||||||
|
return lowlightPlugin.getState(state)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return lowlightPlugin
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ export const Details = Node.create<DetailsOptions>({
|
|||||||
content: "detailsSummary detailsContent",
|
content: "detailsSummary detailsContent",
|
||||||
defining: true,
|
defining: true,
|
||||||
isolating: true,
|
isolating: true,
|
||||||
|
// @ts-ignore
|
||||||
allowGapCursor: false,
|
allowGapCursor: false,
|
||||||
addOptions() {
|
addOptions() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ import {
|
|||||||
import { Node as PMNode, Mark } from "@tiptap/pm/model";
|
import { Node as PMNode, Mark } from "@tiptap/pm/model";
|
||||||
|
|
||||||
declare module "@tiptap/core" {
|
declare module "@tiptap/core" {
|
||||||
|
interface Storage {
|
||||||
|
searchAndReplace: SearchAndReplaceStorage;
|
||||||
|
}
|
||||||
interface Commands<ReturnType> {
|
interface Commands<ReturnType> {
|
||||||
search: {
|
search: {
|
||||||
/**
|
/**
|
||||||
@@ -189,7 +192,7 @@ const replace = (
|
|||||||
const marksSet = new Set<Mark>();
|
const marksSet = new Set<Mark>();
|
||||||
state.doc.nodesBetween(from, to, (node) => {
|
state.doc.nodesBetween(from, to, (node) => {
|
||||||
if (node.isText && node.marks) {
|
if (node.isText && node.marks) {
|
||||||
node.marks.forEach(mark => marksSet.add(mark));
|
node.marks.forEach((mark) => marksSet.add(mark));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -220,7 +223,7 @@ const replaceAll = (
|
|||||||
const marksSet = new Set<Mark>();
|
const marksSet = new Set<Mark>();
|
||||||
tr.doc.nodesBetween(from, to, (node) => {
|
tr.doc.nodesBetween(from, to, (node) => {
|
||||||
if (node.isText && node.marks) {
|
if (node.isText && node.marks) {
|
||||||
node.marks.forEach(mark => marksSet.add(mark));
|
node.marks.forEach((mark) => marksSet.add(mark));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -352,10 +355,17 @@ export const SearchAndReplace = Extension.create<
|
|||||||
// The results will be recalculated by the plugin, but we need to ensure
|
// The results will be recalculated by the plugin, but we need to ensure
|
||||||
// the index doesn't exceed the new bounds
|
// the index doesn't exceed the new bounds
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const newResultsLength = editor.storage.searchAndReplace.results.length;
|
const newResultsLength =
|
||||||
if (newResultsLength > 0 && editor.storage.searchAndReplace.resultIndex >= newResultsLength) {
|
editor.storage.searchAndReplace.results.length;
|
||||||
|
if (
|
||||||
|
newResultsLength > 0 &&
|
||||||
|
editor.storage.searchAndReplace.resultIndex >= newResultsLength
|
||||||
|
) {
|
||||||
// Keep the same position if possible, otherwise go to the last result
|
// Keep the same position if possible, otherwise go to the last result
|
||||||
editor.storage.searchAndReplace.resultIndex = Math.min(resultIndex, newResultsLength - 1);
|
editor.storage.searchAndReplace.resultIndex = Math.min(
|
||||||
|
resultIndex,
|
||||||
|
newResultsLength - 1,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { TableCell as TiptapTableCell } from "@tiptap/extension-table-cell";
|
import { TableCell as TiptapTableCell } from "@tiptap/extension-table";
|
||||||
|
|
||||||
export const TableCell = TiptapTableCell.extend({
|
export const TableCell = TiptapTableCell.extend({
|
||||||
name: "tableCell",
|
name: "tableCell",
|
||||||
content: "(paragraph | heading | bulletList | orderedList | taskList | blockquote | callout | image | video | attachment | mathBlock | details | codeBlock)+",
|
content:
|
||||||
|
"(paragraph | heading | bulletList | orderedList | taskList | blockquote | callout | image | video | attachment | mathBlock | details | codeBlock)+",
|
||||||
|
|
||||||
addAttributes() {
|
addAttributes() {
|
||||||
return {
|
return {
|
||||||
@@ -16,19 +17,21 @@ export const TableCell = TiptapTableCell.extend({
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
style: `background-color: ${attributes.backgroundColor}`,
|
style: `background-color: ${attributes.backgroundColor}`,
|
||||||
'data-background-color': attributes.backgroundColor,
|
"data-background-color": attributes.backgroundColor,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
backgroundColorName: {
|
backgroundColorName: {
|
||||||
default: null,
|
default: null,
|
||||||
parseHTML: (element) => element.getAttribute('data-background-color-name') || null,
|
parseHTML: (element) =>
|
||||||
|
element.getAttribute("data-background-color-name") || null,
|
||||||
renderHTML: (attributes) => {
|
renderHTML: (attributes) => {
|
||||||
if (!attributes.backgroundColorName) {
|
if (!attributes.backgroundColorName) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
'data-background-color-name': attributes.backgroundColorName.toLowerCase(),
|
"data-background-color-name":
|
||||||
|
attributes.backgroundColorName.toLowerCase(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { TableHeader as TiptapTableHeader } from "@tiptap/extension-table-header";
|
import { TableHeader as TiptapTableHeader } from "@tiptap/extension-table";
|
||||||
|
|
||||||
export const TableHeader = TiptapTableHeader.extend({
|
export const TableHeader = TiptapTableHeader.extend({
|
||||||
name: "tableHeader",
|
name: "tableHeader",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import TiptapTableRow from "@tiptap/extension-table-row";
|
import { TableRow as TiptapTableRow } from "@tiptap/extension-table";
|
||||||
|
|
||||||
export const TableRow = TiptapTableRow.extend({
|
export const TableRow = TiptapTableRow.extend({
|
||||||
allowGapCursor: false,
|
allowGapCursor: false,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import Table from "@tiptap/extension-table";
|
import { Table } from "@tiptap/extension-table";
|
||||||
import { Editor } from "@tiptap/core";
|
import { Editor } from "@tiptap/core";
|
||||||
|
|
||||||
const LIST_TYPES = ["bulletList", "orderedList", "taskList"];
|
const LIST_TYPES = ["bulletList", "orderedList", "taskList"];
|
||||||
@@ -17,13 +17,17 @@ function isInList(editor: Editor): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleListIndent(editor: Editor): boolean {
|
function handleListIndent(editor: Editor): boolean {
|
||||||
return editor.commands.sinkListItem("listItem") ||
|
return (
|
||||||
editor.commands.sinkListItem("taskItem");
|
editor.commands.sinkListItem("listItem") ||
|
||||||
|
editor.commands.sinkListItem("taskItem")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleListOutdent(editor: Editor): boolean {
|
function handleListOutdent(editor: Editor): boolean {
|
||||||
return editor.commands.liftListItem("listItem") ||
|
return (
|
||||||
editor.commands.liftListItem("taskItem");
|
editor.commands.liftListItem("listItem") ||
|
||||||
|
editor.commands.liftListItem("taskItem")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CustomTable = Table.extend({
|
export const CustomTable = Table.extend({
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import { Editor, findParentNode, isTextSelection } from "@tiptap/core";
|
import { Editor, findParentNode, isTextSelection } from "@tiptap/core";
|
||||||
import { Selection, Transaction } from "@tiptap/pm/state";
|
import { EditorState, Selection, Transaction } from '@tiptap/pm/state';
|
||||||
import { CellSelection, TableMap } from "@tiptap/pm/tables";
|
import { CellSelection, TableMap } from "@tiptap/pm/tables";
|
||||||
import { Node, ResolvedPos } from "@tiptap/pm/model";
|
import { Node, ResolvedPos } from "@tiptap/pm/model";
|
||||||
import Table from "@tiptap/extension-table";
|
import { Table } from "@tiptap/extension-table";
|
||||||
import { sanitizeUrl as braintreeSanitizeUrl } from "@braintree/sanitize-url";
|
import { sanitizeUrl as braintreeSanitizeUrl } from "@braintree/sanitize-url";
|
||||||
|
import { EditorView } from '@tiptap/pm/view';
|
||||||
|
|
||||||
export const isRectSelected = (rect: any) => (selection: CellSelection) => {
|
export const isRectSelected = (rect: any) => (selection: CellSelection) => {
|
||||||
const map = TableMap.get(selection.$anchorCell.node(-1));
|
const map = TableMap.get(selection.$anchorCell.node(-1));
|
||||||
|
|||||||
Generated
+475
-527
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user