// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates // SPDX-License-Identifier: MIT import { AIHighlight, CharacterCount, CodeBlockLowlight, Color, CustomKeymap, GlobalDragHandle, HighlightExtension, HorizontalRule, Mathematics, Placeholder, StarterKit, TaskItem, TaskList, TextStyle, TiptapImage, TiptapLink, TiptapUnderline, Twitter, UpdatedImage, UploadImagesPlugin, Youtube, } from "novel"; import { Markdown } from "tiptap-markdown"; import { Table } from "@tiptap/extension-table"; import { TableHeader } from "@tiptap/extension-table-header"; import { TableRow } from "@tiptap/extension-table-row"; import { TableCell } from "@tiptap/extension-table-cell"; import { cx } from "class-variance-authority"; import { common, createLowlight } from "lowlight"; //TODO I am using cx here to get tailwind autocomplete working, idk if someone else can write a regex to just capture the class key in objects const aiHighlight = AIHighlight; //You can overwrite the placeholder with your own configuration const placeholder = Placeholder; const tiptapLink = TiptapLink.configure({ HTMLAttributes: { class: cx( "text-muted-foreground underline underline-offset-[3px] hover:text-primary transition-colors cursor-pointer", ), }, }); const tiptapImage = TiptapImage.extend({ addProseMirrorPlugins() { return [ UploadImagesPlugin({ imageClass: cx("opacity-40 rounded-lg border border-stone-200"), }), ]; }, }).configure({ allowBase64: true, HTMLAttributes: { class: cx("rounded-lg border border-muted"), }, }); const updatedImage = UpdatedImage.configure({ HTMLAttributes: { class: cx("rounded-lg border border-muted"), }, }); const taskList = TaskList.configure({ HTMLAttributes: { class: cx("not-prose pl-2 "), }, }); const taskItem = TaskItem.configure({ HTMLAttributes: { class: cx("flex gap-2 items-start my-4"), }, nested: true, }); const horizontalRule = HorizontalRule.configure({ HTMLAttributes: {}, }); const starterKit = StarterKit.configure({ bulletList: { HTMLAttributes: {}, }, orderedList: { HTMLAttributes: { class: cx("list-decimal list-outside leading-3 -mt-2"), }, }, listItem: { HTMLAttributes: {}, }, blockquote: { HTMLAttributes: { class: cx("border-l-4 border-primary"), }, }, codeBlock: false, code: { HTMLAttributes: { spellcheck: "false", }, }, horizontalRule: false, dropcursor: { color: "#DBEAFE", width: 4, }, gapcursor: false, }); const codeBlockLowlight = CodeBlockLowlight.configure({ // configure lowlight: common / all / use highlightJS in case there is a need to specify certain language grammars only // common: covers 37 language grammars which should be good enough in most cases lowlight: createLowlight(common), }); const youtube = Youtube.configure({ HTMLAttributes: { class: cx("rounded-lg border border-muted"), }, inline: false, }); const twitter = Twitter.configure({ HTMLAttributes: { class: cx("not-prose"), }, inline: false, }); const mathematics = Mathematics.configure({ HTMLAttributes: { class: cx("text-foreground rounded p-1 hover:bg-accent cursor-pointer"), }, katexOptions: { throwOnError: false, }, }); const characterCount = CharacterCount.configure(); const table = Table.configure(); const tableRow = TableRow.configure(); const tableCell = TableCell.configure(); const tableHeader = TableHeader.configure(); const markdownExtension = Markdown.configure({ html: true, tightLists: true, tightListClass: "tight", bulletListMarker: "-", linkify: false, breaks: false, transformPastedText: false, transformCopiedText: false, }); const globalDragHandle = GlobalDragHandle.configure({}); export const defaultExtensions = [ starterKit, placeholder, tiptapLink, updatedImage, taskList, taskItem, table, tableRow, tableCell, tableHeader, horizontalRule, aiHighlight, codeBlockLowlight, youtube, twitter, mathematics, characterCount, TiptapUnderline, markdownExtension, HighlightExtension, TextStyle, Color, CustomKeymap, globalDragHandle, ];