mirror of
https://github.com/docmost/docmost.git
synced 2026-05-07 06:23:06 +08:00
a062f7a165
* fix placeholder * min resize dimensions * fix media links * fix
377 lines
11 KiB
TypeScript
377 lines
11 KiB
TypeScript
import { markInputRule } from "@tiptap/core";
|
|
import { StarterKit } from "@tiptap/starter-kit";
|
|
import { Code } from "@tiptap/extension-code";
|
|
import { TextAlign } from "@tiptap/extension-text-align";
|
|
import { TaskList, TaskItem } from "@tiptap/extension-list";
|
|
import { Placeholder, CharacterCount } from "@tiptap/extensions";
|
|
import { Superscript } from "@tiptap/extension-superscript";
|
|
import SubScript from "@tiptap/extension-subscript";
|
|
import { Typography } from "@tiptap/extension-typography";
|
|
import { TextStyle } from "@tiptap/extension-text-style";
|
|
import { Color } from "@tiptap/extension-color";
|
|
import GlobalDragHandle from "tiptap-extension-global-drag-handle";
|
|
import { Youtube } from "@tiptap/extension-youtube";
|
|
import SlashCommand from "@/features/editor/extensions/slash-command";
|
|
import { Collaboration, isChangeOrigin } from "@tiptap/extension-collaboration";
|
|
import { CollaborationCaret } from "@tiptap/extension-collaboration-caret";
|
|
import { HocuspocusProvider } from "@hocuspocus/provider";
|
|
import {
|
|
Comment,
|
|
Details,
|
|
DetailsContent,
|
|
DetailsSummary,
|
|
MathBlock,
|
|
MathInline,
|
|
TableCell,
|
|
TableRow,
|
|
TableHeader,
|
|
CustomTable,
|
|
TrailingNode,
|
|
TiptapImage,
|
|
Callout,
|
|
TiptapVideo,
|
|
TiptapAudio,
|
|
LinkExtension,
|
|
Selection,
|
|
Attachment,
|
|
CustomCodeBlock,
|
|
Drawio,
|
|
Excalidraw,
|
|
Embed,
|
|
TiptapPdf,
|
|
SearchAndReplace,
|
|
Mention,
|
|
TableDndExtension,
|
|
Subpages,
|
|
Heading,
|
|
Highlight,
|
|
UniqueID,
|
|
SharedStorage,
|
|
Columns,
|
|
Column,
|
|
Status,
|
|
} from "@docmost/editor-ext";
|
|
import {
|
|
randomElement,
|
|
userColors,
|
|
} from "@/features/editor/extensions/utils.ts";
|
|
import { IUser } from "@/features/user/types/user.types.ts";
|
|
import {
|
|
createImageHandle,
|
|
imageResizeClasses,
|
|
} from "@/features/editor/components/image/image-resize-handles.ts";
|
|
import {
|
|
createResizeHandle,
|
|
buildResizeClasses,
|
|
} from "@/features/editor/components/common/node-resize-handles.ts";
|
|
import MathInlineView from "@/features/editor/components/math/math-inline.tsx";
|
|
import MathBlockView from "@/features/editor/components/math/math-block.tsx";
|
|
import ImageView from "@/features/editor/components/image/image-view.tsx";
|
|
import CalloutView from "@/features/editor/components/callout/callout-view.tsx";
|
|
import StatusView from "@/features/editor/components/status/status-view.tsx";
|
|
import VideoView from "@/features/editor/components/video/video-view.tsx";
|
|
import AudioView from "@/features/editor/components/audio/audio-view.tsx";
|
|
import AttachmentView from "@/features/editor/components/attachment/attachment-view.tsx";
|
|
import CodeBlockView from "@/features/editor/components/code-block/code-block-view.tsx";
|
|
import DrawioView from "../components/drawio/drawio-view";
|
|
import ExcalidrawView from "@/features/editor/components/excalidraw/excalidraw-view.tsx";
|
|
import EmbedView from "@/features/editor/components/embed/embed-view.tsx";
|
|
import PdfView from "@/features/editor/components/pdf/pdf-view.tsx";
|
|
import SubpagesView from "@/features/editor/components/subpages/subpages-view.tsx";
|
|
import { common, createLowlight } from "lowlight";
|
|
import plaintext from "highlight.js/lib/languages/plaintext";
|
|
import powershell from "highlight.js/lib/languages/powershell";
|
|
import abap from "highlightjs-sap-abap";
|
|
import elixir from "highlight.js/lib/languages/elixir";
|
|
import erlang from "highlight.js/lib/languages/erlang";
|
|
import dockerfile from "highlight.js/lib/languages/dockerfile";
|
|
import clojure from "highlight.js/lib/languages/clojure";
|
|
import fortran from "highlight.js/lib/languages/fortran";
|
|
import haskell from "highlight.js/lib/languages/haskell";
|
|
import scala from "highlight.js/lib/languages/scala";
|
|
import mentionRenderItems from "@/features/editor/components/mention/mention-suggestion.ts";
|
|
import { ReactNodeViewRenderer, ReactMarkViewRenderer } from "@tiptap/react";
|
|
import MentionView from "@/features/editor/components/mention/mention-view.tsx";
|
|
import LinkView from "@/features/editor/components/link/link-view.tsx";
|
|
import i18n from "@/i18n.ts";
|
|
import { MarkdownClipboard } from "@/features/editor/extensions/markdown-clipboard.ts";
|
|
import EmojiCommand from "./emoji-command";
|
|
import { countWords } from "alfaaz";
|
|
import AutoJoiner from "@/features/editor/extensions/autojoiner.ts";
|
|
|
|
const lowlight = createLowlight(common);
|
|
lowlight.register("mermaid", plaintext);
|
|
lowlight.register("powershell", powershell);
|
|
lowlight.register("abap", abap);
|
|
lowlight.register("erlang", erlang);
|
|
lowlight.register("elixir", elixir);
|
|
lowlight.register("dockerfile", dockerfile);
|
|
lowlight.register("clojure", clojure);
|
|
lowlight.register("fortran", fortran);
|
|
lowlight.register("haskell", haskell);
|
|
lowlight.register("scala", scala);
|
|
|
|
// @ts-ignore
|
|
export const mainExtensions = [
|
|
StarterKit.configure({
|
|
heading: false,
|
|
undoRedo: false,
|
|
link: false,
|
|
trailingNode: false,
|
|
dropcursor: {
|
|
width: 3,
|
|
color: "#70CFF8",
|
|
},
|
|
codeBlock: false,
|
|
code: false,
|
|
}),
|
|
// Override TipTap's Code extension to fix the inline code input rule.
|
|
// The upstream regex /(^|[^`])`([^`]+)`(?!`)$/ captures the character
|
|
// before the opening backtick as part of the match, causing markInputRule
|
|
// to delete it. Using a lookbehind avoids including it in the match.
|
|
Code.configure({
|
|
HTMLAttributes: {
|
|
spellcheck: false,
|
|
},
|
|
}).extend({
|
|
addInputRules() {
|
|
return [
|
|
markInputRule({
|
|
find: /(?:^|(?<=[^`]))`([^`]+)`(?!`)$/,
|
|
type: this.type,
|
|
}),
|
|
];
|
|
},
|
|
}),
|
|
SharedStorage,
|
|
Heading,
|
|
UniqueID.configure({
|
|
types: ["heading", "paragraph"],
|
|
filterTransaction: (transaction) => !isChangeOrigin(transaction),
|
|
}),
|
|
Placeholder.configure({
|
|
placeholder: ({ editor, node, pos }) => {
|
|
if (node.type.name === "heading") {
|
|
return i18n.t("Heading {{level}}", { level: node.attrs.level });
|
|
}
|
|
if (node.type.name === "detailsSummary") {
|
|
return i18n.t("Toggle title");
|
|
}
|
|
if (node.type.name === "paragraph") {
|
|
const $pos = editor.state.doc.resolve(pos);
|
|
const parentName = $pos.parent.type.name;
|
|
if (
|
|
parentName === "column" ||
|
|
parentName === "tableCell" ||
|
|
parentName === "tableHeader" ||
|
|
parentName === "callout" ||
|
|
parentName === "blockquote"
|
|
) {
|
|
return i18n.t("Write...");
|
|
}
|
|
return i18n.t('Write anything. Enter "/" for commands');
|
|
}
|
|
},
|
|
includeChildren: true,
|
|
showOnlyWhenEditable: true,
|
|
}),
|
|
TextAlign.configure({ types: ["heading", "paragraph"] }),
|
|
TaskList,
|
|
TaskItem.configure({
|
|
nested: true,
|
|
}),
|
|
LinkExtension.configure({
|
|
openOnClick: false,
|
|
}).extend({
|
|
addMarkView() {
|
|
return ReactMarkViewRenderer(LinkView);
|
|
},
|
|
}),
|
|
Superscript,
|
|
SubScript,
|
|
Highlight.configure({
|
|
multicolor: true,
|
|
}),
|
|
Typography,
|
|
TrailingNode,
|
|
GlobalDragHandle,
|
|
TextStyle,
|
|
Color,
|
|
SlashCommand,
|
|
EmojiCommand,
|
|
Comment.configure({
|
|
HTMLAttributes: {
|
|
class: "comment-mark",
|
|
},
|
|
}),
|
|
Mention.configure({
|
|
suggestion: {
|
|
allowSpaces: true,
|
|
items: () => {
|
|
return [];
|
|
},
|
|
// @ts-ignore
|
|
render: mentionRenderItems,
|
|
},
|
|
HTMLAttributes: {
|
|
class: "mention",
|
|
},
|
|
}).extend({
|
|
addNodeView() {
|
|
// Force the react node view to render immediately using flush sync (https://github.com/ueberdosis/tiptap/blob/b4db352f839e1d82f9add6ee7fb45561336286d8/packages/react/src/ReactRenderer.tsx#L183-L191)
|
|
this.editor.isInitialized = true;
|
|
|
|
return ReactNodeViewRenderer(MentionView);
|
|
},
|
|
}),
|
|
CustomTable.configure({
|
|
resizable: true,
|
|
lastColumnResizable: true,
|
|
allowTableNodeSelection: true,
|
|
}),
|
|
TableRow,
|
|
TableCell,
|
|
TableHeader,
|
|
TableDndExtension,
|
|
MathInline.configure({
|
|
view: MathInlineView,
|
|
}),
|
|
MathBlock.configure({
|
|
view: MathBlockView,
|
|
}),
|
|
Details,
|
|
DetailsSummary,
|
|
DetailsContent,
|
|
Youtube.configure({
|
|
addPasteHandler: false,
|
|
controls: true,
|
|
nocookie: true,
|
|
}),
|
|
TiptapImage.configure({
|
|
view: ImageView,
|
|
allowBase64: false,
|
|
resize: {
|
|
enabled: true,
|
|
directions: ["left", "right"],
|
|
minWidth: 24,
|
|
minHeight: 16,
|
|
alwaysPreserveAspectRatio: true,
|
|
//@ts-ignore
|
|
createCustomHandle: createImageHandle,
|
|
className: imageResizeClasses,
|
|
},
|
|
}),
|
|
TiptapVideo.configure({
|
|
view: VideoView,
|
|
resize: {
|
|
enabled: true,
|
|
directions: ["left", "right"],
|
|
minWidth: 24,
|
|
minHeight: 16,
|
|
alwaysPreserveAspectRatio: true,
|
|
//@ts-ignore
|
|
createCustomHandle: createResizeHandle,
|
|
className: buildResizeClasses("node-video"),
|
|
},
|
|
}),
|
|
TiptapAudio.configure({
|
|
view: AudioView,
|
|
}),
|
|
Callout.configure({
|
|
view: CalloutView,
|
|
}),
|
|
CustomCodeBlock.configure({
|
|
view: CodeBlockView,
|
|
//@ts-ignore
|
|
lowlight,
|
|
HTMLAttributes: {
|
|
spellcheck: false,
|
|
},
|
|
}),
|
|
Selection,
|
|
Attachment.configure({
|
|
view: AttachmentView,
|
|
}),
|
|
Drawio.configure({
|
|
view: DrawioView,
|
|
resize: {
|
|
enabled: true,
|
|
directions: ["left", "right"],
|
|
minWidth: 24,
|
|
minHeight: 16,
|
|
alwaysPreserveAspectRatio: true,
|
|
//@ts-ignore
|
|
createCustomHandle: createResizeHandle,
|
|
className: buildResizeClasses("node-drawio"),
|
|
},
|
|
}),
|
|
Excalidraw.configure({
|
|
view: ExcalidrawView,
|
|
resize: {
|
|
enabled: true,
|
|
directions: ["left", "right"],
|
|
minWidth: 24,
|
|
minHeight: 16,
|
|
alwaysPreserveAspectRatio: true,
|
|
//@ts-ignore
|
|
createCustomHandle: createResizeHandle,
|
|
className: buildResizeClasses("node-excalidraw"),
|
|
},
|
|
}),
|
|
Embed.configure({
|
|
view: EmbedView,
|
|
}),
|
|
TiptapPdf.configure({
|
|
view: PdfView,
|
|
}),
|
|
Subpages.configure({
|
|
view: SubpagesView,
|
|
}),
|
|
Status.configure({
|
|
view: StatusView,
|
|
}),
|
|
MarkdownClipboard.configure({
|
|
transformPastedText: true,
|
|
}),
|
|
CharacterCount.configure({
|
|
wordCounter: (text) => countWords(text),
|
|
}),
|
|
SearchAndReplace.extend({
|
|
addKeyboardShortcuts() {
|
|
return {
|
|
"Mod-f": () => {
|
|
const event = new CustomEvent("openFindDialogFromEditor", {});
|
|
document.dispatchEvent(event);
|
|
return true;
|
|
},
|
|
Escape: () => {
|
|
const event = new CustomEvent("closeFindDialogFromEditor", {});
|
|
document.dispatchEvent(event);
|
|
return false;
|
|
},
|
|
};
|
|
},
|
|
}).configure(),
|
|
Columns,
|
|
Column,
|
|
AutoJoiner.configure({
|
|
elementsToJoin: [],
|
|
}),
|
|
] as any;
|
|
|
|
type CollabExtensions = (provider: HocuspocusProvider, user: IUser) => any[];
|
|
|
|
export const collabExtensions: CollabExtensions = (provider, user) => [
|
|
Collaboration.configure({
|
|
document: provider.document,
|
|
provider,
|
|
}),
|
|
CollaborationCaret.configure({
|
|
provider,
|
|
user: {
|
|
name: user.name,
|
|
color: randomElement(userColors),
|
|
},
|
|
}),
|
|
];
|