import StarterKit from "@tiptap/starter-kit"; import HorizontalRule from "@tiptap/extension-horizontal-rule"; import TiptapLink from "@tiptap/extension-link"; import Placeholder from "@tiptap/extension-placeholder"; import TiptapUnderline from "@tiptap/extension-underline"; import TextStyle from "@tiptap/extension-text-style"; import { Color } from "@tiptap/extension-color"; import TaskItem from "@tiptap/extension-task-item"; import TaskList from "@tiptap/extension-task-list"; import { Markdown } from "tiptap-markdown"; import Highlight from "@tiptap/extension-highlight"; import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight"; import { lowlight } from "lowlight/lib/core"; import SlashCommand from "../slash-command"; import { InputRule } from "@tiptap/core"; import Gapcursor from "@tiptap/extension-gapcursor"; import ts from "highlight.js/lib/languages/typescript"; import "highlight.js/styles/github-dark.css"; import UpdatedImage from "./updated-image"; import isValidHttpUrl from "../bubble-menu/utils/link-validator"; import { CustomTableCell } from "./table/table-cell"; import { Table } from "./table/table"; import { TableHeader } from "./table/table-header"; import { TableRow } from "@tiptap/extension-table-row"; import { Mentions } from "../mentions"; import DragAndDrop from "./drag-and-drop"; import CustomKeymap from "./keymap"; lowlight.registerLanguage("ts", ts); export const TiptapExtensions = ( workspaceSlug: string, setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void ) => [ StarterKit.configure({ bulletList: { HTMLAttributes: { class: "list-disc list-outside leading-3 -mt-2", }, }, orderedList: { HTMLAttributes: { class: "list-decimal list-outside leading-3 -mt-2", }, }, listItem: { HTMLAttributes: { class: "leading-normal -mb-2", }, }, blockquote: { HTMLAttributes: { class: "border-l-4 border-custom-border-300", }, }, code: { HTMLAttributes: { class: "rounded-md bg-custom-primary-30 mx-1 px-1 py-1 font-mono font-medium text-custom-text-1000", spellcheck: "false", }, }, codeBlock: false, horizontalRule: false, dropcursor: { color: "rgba(var(--color-text-100))", width: 2, }, gapcursor: false, }), CodeBlockLowlight.configure({ lowlight, }), HorizontalRule.extend({ addInputRules() { return [ new InputRule({ find: /^(?:---|—-|___\s|\*\*\*\s)$/, handler: ({ state, range, commands }) => { commands.splitBlock(); const attributes = {}; const { tr } = state; const start = range.from; const end = range.to; // @ts-ignore tr.replaceWith(start - 1, end, this.type.create(attributes)); }, }), ]; }, }).configure({ HTMLAttributes: { class: "mb-6 border-t border-custom-border-300", }, }), Gapcursor, TiptapLink.configure({ protocols: ["http", "https"], validate: (url) => isValidHttpUrl(url), HTMLAttributes: { class: "text-custom-primary-300 underline underline-offset-[3px] hover:text-custom-primary-500 transition-colors cursor-pointer", }, }), UpdatedImage.configure({ HTMLAttributes: { class: "rounded-lg border border-custom-border-300", }, }), Placeholder.configure({ placeholder: ({ node }) => { if (node.type.name === "heading") { return `Heading ${node.attrs.level}`; } if (node.type.name === "image" || node.type.name === "table") { return ""; } return "Press '/' for commands..."; }, includeChildren: true, }), SlashCommand(workspaceSlug, setIsSubmitting), TiptapUnderline, TextStyle, Color, Highlight.configure({ multicolor: true, }), TaskList.configure({ HTMLAttributes: { class: "not-prose pl-2", }, }), TaskItem.configure({ HTMLAttributes: { class: "flex items-start my-4", }, nested: true, }), Markdown.configure({ html: true, transformCopiedText: true, }), Table, TableHeader, CustomTableCell, TableRow, ];