From 4cd16cb7e1c4644e6a3fd941d257c6c55f0914c6 Mon Sep 17 00:00:00 2001 From: Matt Roberts Date: Wed, 29 Jul 2026 15:37:45 +0100 Subject: [PATCH 1/3] build(deps): rewrite markdown renderer for marked 4 -> 17 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First step of unifying the app's two markdown engines onto marked. This upgrades the display renderer (public/services/markdown.ts) off the ancient marked 4 (held by a dependabot ignore) to marked 17 — the version @tiptap/markdown peers, so the later editor migration will dedupe to a single marked copy. marked 5+ is ESM-only and replaced the positional Renderer API with token objects registered via .use(). Changes: - markdown.ts: two Marked instances (full + plain-text) with RendererObject overrides; custom escape keeps apostrophes literal (marked now emits '); fider-image + @[mention] syntaxes preserved byte-identically. - package.json: marked ^4.0.15 -> ^17.0.6; remove @types/marked (marked ships its own types); jest transform now includes .js + transformIgnorePatterns allows transforming marked's ESM. - .babelrc -> babel.config.json: a root babel config is required for jest to transform ESM node_modules (a file-scoped .babelrc does not apply to them). Webpack's babel-loader only processes public/ + locale/, so the build is unaffected. - dependabot.yml: marked now held on 17.x (was 4.x) pending @tiptap/markdown supporting marked 18. Validated byte-identical: markdown.spec.ts (32/32), plus make lint-ui, build-ui, test-ui all green. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/dependabot.yml | 8 +-- .babelrc => babel.config.json | 6 +- package-lock.json | 18 ++--- package.json | 10 +-- public/services/markdown.ts | 124 ++++++++++++++++++++++------------ 5 files changed, 94 insertions(+), 72 deletions(-) rename .babelrc => babel.config.json (71%) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f6c08d8d2c..fde0fdc2fa 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -47,13 +47,11 @@ updates: update-types: ["version-update:semver-major"] - dependency-name: "babel-loader" update-types: ["version-update:semver-major"] - # marked: 5+ is ESM-only and rewrote the Renderer API. 4 -> 18 needs a rewrite of - # public/services/markdown.ts (custom marked.Renderer() + synchronous marked()). - # Held on the 4.x line until that migration is done. + # marked: now on 17 (renderer rewritten to the token-based API in markdown.ts). + # Held on 17.x because @tiptap/markdown peers marked ^17; unblock 18 once the + # editor's @tiptap/markdown supports it. - dependency-name: "marked" update-types: ["version-update:semver-major"] - - dependency-name: "@types/marked" - update-types: ["version-update:semver-major"] # Go modules - monthly, all grouped together - package-ecosystem: "gomod" diff --git a/.babelrc b/babel.config.json similarity index 71% rename from .babelrc rename to babel.config.json index 21f13c77f7..6e72449924 100644 --- a/.babelrc +++ b/babel.config.json @@ -2,11 +2,7 @@ "presets": ["@babel/env", "@babel/react", "@babel/preset-typescript"], "plugins": [ "@babel/plugin-proposal-class-properties", - ["macros", { - "lingui": { - "version": 5 - } - }], + ["macros", { "lingui": { "version": 5 } }], "@lingui/babel-plugin-lingui-macro" ] } diff --git a/package-lock.json b/package-lock.json index 01df461043..dba0de5163 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "@tiptap/suggestion": "^2.11.5", "dompurify": "^3.4.12", "inter-ui": "^4.1.1", - "marked": "^4.0.15", + "marked": "^17.0.6", "prosemirror-markdown": "^1.13.5", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -46,7 +46,6 @@ "@types/debug": "4.1.13", "@types/dompurify": "3.2.0", "@types/jsdom": "16.2.14", - "@types/marked": "4.0.3", "@types/node": "17.0.31", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", @@ -5771,13 +5770,6 @@ "@types/mdurl": "^2" } }, - "node_modules/@types/marked": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.3.tgz", - "integrity": "sha512-HnMWQkLJEf/PnxZIfbm0yGJRRZYYMhb++O9M36UCTA9z53uPvVoSlAwJr3XOpDEryb7Hwl1qAx/MV6YIW1RXxg==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", @@ -14697,15 +14689,15 @@ } }, "node_modules/marked": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", - "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "version": "17.0.6", + "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.6.tgz", + "integrity": "sha512-gB0gkNafnonOw0obSTEGZTT86IuhILt2Wfx0mWH/1Au83kybTayroZ/V6nS25mN7u8ASy+5fMhgB3XPNrOZdmA==", "license": "MIT", "bin": { "marked": "bin/marked.js" }, "engines": { - "node": ">= 12" + "node": ">= 20" } }, "node_modules/math-intrinsics": { diff --git a/package.json b/package.json index 562bdfbf90..6d4460c7ab 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "@tiptap/suggestion": "^2.11.5", "dompurify": "^3.4.12", "inter-ui": "^4.1.1", - "marked": "^4.0.15", + "marked": "^17.0.6", "prosemirror-markdown": "^1.13.5", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -42,7 +42,6 @@ "@types/debug": "4.1.13", "@types/dompurify": "3.2.0", "@types/jsdom": "16.2.14", - "@types/marked": "4.0.3", "@types/node": "17.0.31", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", @@ -82,7 +81,7 @@ "jest": { "testEnvironment": "jsdom", "transform": { - ".+\\.(tsx|ts)?$": "babel-jest" + ".+\\.(t|j)sx?$": "babel-jest" }, "setupFilesAfterEnv": [ "/public/jest.setup.tsx" @@ -100,7 +99,10 @@ ".*\\.(png|scss|svg)$": "/public/jest.assets.ts", "@fider/(.*)": "/public/$1", "@locale/(.*)": "/locale/$1" - } + }, + "transformIgnorePatterns": [ + "/node_modules/(?!(marked)/)" + ] }, "engines": { "npm": "10.x || 11.x", diff --git a/public/services/markdown.ts b/public/services/markdown.ts index 0a57149621..6ffd7ac1cd 100644 --- a/public/services/markdown.ts +++ b/public/services/markdown.ts @@ -1,15 +1,7 @@ -import { marked } from "marked" +import { Marked, RendererObject } from "marked" import DOMPurify from "dompurify" import { fiderAllowedSchemes } from "@fider/hooks" -marked.setOptions({ - headerIds: false, - xhtml: true, - smartLists: true, - gfm: true, - breaks: true, -}) - if (DOMPurify.isSupported) { DOMPurify.setConfig({ USE_PROFILES: { @@ -34,43 +26,85 @@ if (DOMPurify.isSupported) { }) } -const link = (href: string, title: string, text: string) => { - const titleAttr = title ? ` title=${title}` : "" - return `${text}` -} +// marked 5+ escapes text (including ' -> '). We keep apostrophes literal to match the +// historical output, while preserving existing entities and escaping & < > ". +const escapeText = (s: string): string => + s + .replace(/&(?![#\w]+;)/g, "&") + .replace(//g, ">") + .replace(/"/g, """) -const fullRenderer = new marked.Renderer() -const originalImage = fullRenderer.image.bind(fullRenderer) -fullRenderer.image = (href, title, alt) => { - // Check if this is our special fider-image syntax - if (href && href.startsWith("fider-image:")) { - const bkey = href.substring("fider-image:".length) - return `${alt || ` - } - return originalImage(href, title, alt) -} -fullRenderer.link = link -fullRenderer.text = (text: string) => { - // Handling mention links (they're in the format @[name]) - return text.replace(/@\[(.*?)\]/g, (match, name) => { - return `@${name}` - }) +// Expand Fider's proprietary mention syntax @[name] into a styled span. +const renderMentions = (html: string): string => html.replace(/@\[(.*?)\]/g, (_match, name) => `@${name}`) + +// marked 5+ replaced the positional Renderer API with token objects and requires renderer +// overrides to be registered via .use(). Typing as RendererObject binds `this` to the +// renderer (exposing this.parser) and infers the exact token types. Unoverridden methods +// keep marked's defaults. +const fullRenderer: RendererObject = { + image({ href, text }) { + // Fider's proprietary inline-image syntax: ![](fider-image:) + if (href && href.startsWith("fider-image:")) { + const bkey = href.substring("fider-image:".length) + return `${text || ` + } + return false // fall back to marked's default image renderer + }, + link({ href, title, tokens }) { + const text = this.parser.parseInline(tokens) + const titleAttr = title ? ` title=${title}` : "" + return `${text}` + }, + text(token) { + const rendered = "tokens" in token && token.tokens ? this.parser.parseInline(token.tokens) : escapeText(token.text) + return renderMentions(rendered) + }, } +const markedFull = new Marked({ gfm: true, breaks: true }) +markedFull.use({ renderer: fullRenderer }) -const plainTextRenderer = new marked.Renderer() -plainTextRenderer.link = (_href, _title, text) => text -plainTextRenderer.image = () => "" -plainTextRenderer.br = () => " " -plainTextRenderer.strong = (text) => text -plainTextRenderer.list = (body) => body -plainTextRenderer.listitem = (text) => `${text} ` -plainTextRenderer.heading = (text) => text -plainTextRenderer.paragraph = (text) => ` ${text} ` -plainTextRenderer.code = (code) => code -plainTextRenderer.codespan = (code) => code -plainTextRenderer.html = (html) => html -plainTextRenderer.del = (text) => text -plainTextRenderer.blockquote = (quote) => quote +// Plain-text renderer: strip all formatting down to readable text. +const plainTextRenderer: RendererObject = { + link({ tokens }) { + return this.parser.parseInline(tokens) + }, + image() { + return "" + }, + br() { + return " " + }, + strong({ tokens }) { + return this.parser.parseInline(tokens) + }, + del({ tokens }) { + return this.parser.parseInline(tokens) + }, + heading({ tokens }) { + return this.parser.parseInline(tokens) + }, + paragraph({ tokens }) { + return ` ${this.parser.parseInline(tokens)} ` + }, + code({ text }) { + return text + }, + codespan({ text }) { + return text + }, + html({ text }) { + return text + }, + blockquote({ tokens }) { + return this.parser.parse(tokens) + }, + list(token) { + return token.items.map((item) => `${this.parser.parse(item.tokens).trim()} `).join("") + }, +} +const markedPlainText = new Marked({ gfm: true, breaks: true }) +markedPlainText.use({ renderer: plainTextRenderer }) const encodeHTML = (s: string) => s.replace(/ input.replace(/<[^>]*>/g, "") @@ -91,10 +125,10 @@ const decodeHtmlEntities = (text: string): string => { } export const full = (input: string): string => { - return sanitize(marked(encodeHTML(input), { renderer: fullRenderer }).trim()) + return sanitize((markedFull.parse(encodeHTML(input)) as string).trim()) } export const plainText = (input: string): string => { - const text = sanitize(marked(input, { renderer: plainTextRenderer }).trim()) + const text = sanitize((markedPlainText.parse(input) as string).trim()) return decodeHtmlEntities(text).trim() } From 0685bb88f3448f6bdf9fe0636c085f44decb663b Mon Sep 17 00:00:00 2001 From: Matt Roberts Date: Wed, 29 Jul 2026 18:35:23 +0100 Subject: [PATCH 2/3] build(deps): upgrade tiptap v2 -> v3 (interim: keeps tiptap-markdown 0.9) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the whole @tiptap/* stack to 3.29.2 (exact-pinned — tiptap enforces identical versions across its packages), tiptap-markdown -> 0.9.0, moves Placeholder to @tiptap/extensions, disables StarterKit's now-bundled Link + Underline, and sets setContent emitUpdate:false. Adds a byte-identical markdown round-trip test suite (13 cases incl. fider-image + @mention) as the gate for the later @tiptap/markdown engine swap. jest transformIgnorePatterns extended for @tiptap/tiptap-markdown/prosemirror ESM. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/dependabot.yml | 3 - package-lock.json | 641 +++++++++--------- package.json | 20 +- .../form/CommentEditor.markdown.spec.ts | 52 ++ .../components/common/form/CommentEditor.tsx | 21 +- 5 files changed, 382 insertions(+), 355 deletions(-) create mode 100644 public/components/common/form/CommentEditor.markdown.spec.ts diff --git a/.github/dependabot.yml b/.github/dependabot.yml index fde0fdc2fa..cbcdb29852 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -23,9 +23,6 @@ updates: # Cucumber e2e-ui steps. Held on the 1.48 line. - dependency-name: "@playwright/test" update-types: ["version-update:semver-minor", "version-update:semver-major"] - # tiptap-markdown: 0.9+ requires @tiptap/core v3; project is on the tiptap v2 line. - - dependency-name: "tiptap-markdown" - update-types: ["version-update:semver-minor", "version-update:semver-major"] # @lingui/*: 5.9 breaks the build — return type is ReactNode, not a # valid JSX element under the current @types/react. Needs a @types/react # upgrade first. Held on the 5.1 line. diff --git a/package-lock.json b/package-lock.json index dba0de5163..b18fd59f4a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,14 +10,14 @@ "dependencies": { "@lingui/format-json": "^5.1.2", "@lingui/react": "^5.1.2", - "@tiptap/extension-image": "^2.22.3", - "@tiptap/extension-link": "^2.11.5", - "@tiptap/extension-mention": "^2.11.5", - "@tiptap/extension-placeholder": "^2.11.5", - "@tiptap/pm": "^2.11.5", - "@tiptap/react": "^2.11.5", - "@tiptap/starter-kit": "^2.11.5", - "@tiptap/suggestion": "^2.11.5", + "@tiptap/extension-image": "3.29.2", + "@tiptap/extension-link": "3.29.2", + "@tiptap/extension-mention": "3.29.2", + "@tiptap/extensions": "3.29.2", + "@tiptap/pm": "3.29.2", + "@tiptap/react": "3.29.2", + "@tiptap/starter-kit": "3.29.2", + "@tiptap/suggestion": "3.29.2", "dompurify": "^3.4.12", "inter-ui": "^4.1.1", "marked": "^17.0.6", @@ -26,7 +26,7 @@ "react-dom": "^18.3.1", "react-textarea-autosize": "^8.5.9", "react-toastify": "^9.0.1", - "tiptap-markdown": "^0.8.10" + "tiptap-markdown": "^0.9.0" }, "devDependencies": { "@babel/cli": "^7.28.3", @@ -2891,6 +2891,31 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@floating-ui/core": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", + "license": "MIT" + }, "node_modules/@google-cloud/common": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-6.0.0.tgz", @@ -4818,16 +4843,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@popperjs/core": { - "version": "2.11.8", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", - "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -4902,12 +4917,6 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/@remirror/core-constants": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz", - "integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==", - "license": "MIT" - }, "node_modules/@sinclair/typebox": { "version": "0.24.51", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", @@ -5045,387 +5054,394 @@ } }, "node_modules/@tiptap/core": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.11.5.tgz", - "integrity": "sha512-jb0KTdUJaJY53JaN7ooY3XAxHQNoMYti/H6ANo707PsLXVeEqJ9o8+eBup1JU5CuwzrgnDc2dECt2WIGX9f8Jw==", + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.29.2.tgz", + "integrity": "sha512-oKUkiPUB7noilVYxI9lNzUD4rX17sHub+PYjMfHMWHG9A3nvIy+FdePIVIIhThKWF7ijhr3eIqHY51Bn+GAFtw==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/pm": "^2.7.0" + "@tiptap/pm": "3.29.2" } }, "node_modules/@tiptap/extension-blockquote": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.11.5.tgz", - "integrity": "sha512-MZfcRIzKRD8/J1hkt/eYv49060GTL6qGR3NY/oTDuw2wYzbQXXLEbjk8hxAtjwNn7G+pWQv3L+PKFzZDxibLuA==", + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.29.2.tgz", + "integrity": "sha512-ca4OzKDh0yaxg2+Z56bC2QnWsNsFp2YMRfVig1PDXyMVFMNJpLcnhxgq/9btn+xYAlYrj8RymOeCTYREOR6Zjg==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.7.0" + "@tiptap/core": "3.29.2", + "@tiptap/pm": "3.29.2" } }, "node_modules/@tiptap/extension-bold": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.11.5.tgz", - "integrity": "sha512-OAq03MHEbl7MtYCUzGuwb0VpOPnM0k5ekMbEaRILFU5ZC7cEAQ36XmPIw1dQayrcuE8GZL35BKub2qtRxyC9iA==", + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.29.2.tgz", + "integrity": "sha512-elYbGxJsYnBb4leqrcjdIJuiG380BcOgN+UUzvOv+qEjfGVzHodFOMBl3qnmD6urYHNu5/qQK2S0qSSRXKCLNQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.7.0" + "@tiptap/core": "3.29.2" } }, "node_modules/@tiptap/extension-bubble-menu": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.11.5.tgz", - "integrity": "sha512-rx+rMd7EEdht5EHLWldpkzJ56SWYA9799b33ustePqhXd6linnokJCzBqY13AfZ9+xp3RsR6C0ZHI9GGea0tIA==", + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.29.2.tgz", + "integrity": "sha512-kzcWarcpr031rZu+R3Hzut5uxb3Qj/xxMDEYZIQ3uiq1yb5vEMXj8/SIyWautk6Nu8Rr1leV3rGdR4NzhzyFAA==", "license": "MIT", + "optional": true, "dependencies": { - "tippy.js": "^6.3.7" + "@floating-ui/dom": "^1.0.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.7.0", - "@tiptap/pm": "^2.7.0" + "@tiptap/core": "3.29.2", + "@tiptap/pm": "3.29.2" } }, "node_modules/@tiptap/extension-bullet-list": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.11.5.tgz", - "integrity": "sha512-VXwHlX6A/T6FAspnyjbKDO0TQ+oetXuat6RY1/JxbXphH42nLuBaGWJ6pgy6xMl6XY8/9oPkTNrfJw/8/eeRwA==", + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.29.2.tgz", + "integrity": "sha512-3bWcCUPbCHv0XttlMdnAtXLNYWx2pblByMgxmGsaP9FU0QnslGXty6A6gHCqI33ygRg1vrA6U5Wtpwbi5aKu5g==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.7.0" + "@tiptap/extension-list": "3.29.2" } }, "node_modules/@tiptap/extension-code": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.11.5.tgz", - "integrity": "sha512-xOvHevNIQIcCCVn9tpvXa1wBp0wHN/2umbAZGTVzS+AQtM7BTo0tz8IyzwxkcZJaImONcUVYLOLzt2AgW1LltA==", + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.29.2.tgz", + "integrity": "sha512-c6W5UGuB7WNLpYocsgRzpO2OOTI4QjaI9jjHRMuty9z+s9DtaYM/HrRLNwVh6MopkHb+i/89Wkv8gCS34fftig==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.7.0" + "@tiptap/core": "3.29.2" } }, "node_modules/@tiptap/extension-code-block": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.11.5.tgz", - "integrity": "sha512-ksxMMvqLDlC+ftcQLynqZMdlJT1iHYZorXsXw/n+wuRd7YElkRkd6YWUX/Pq/njFY6lDjKiqFLEXBJB8nrzzBA==", + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.29.2.tgz", + "integrity": "sha512-w153ct8g6dLiPTdXQ6SOIMxX4SEo5Q50AmjdEEEcJ7ZcYUcde/ScSskLHfOYmyt5ZFAiyEwr121+pux+p3/oAQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.7.0", - "@tiptap/pm": "^2.7.0" + "@tiptap/core": "3.29.2", + "@tiptap/pm": "3.29.2" } }, "node_modules/@tiptap/extension-document": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.11.5.tgz", - "integrity": "sha512-7I4BRTpIux2a0O2qS3BDmyZ5LGp3pszKbix32CmeVh7lN9dV7W5reDqtJJ9FCZEEF+pZ6e1/DQA362dflwZw2g==", + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.29.2.tgz", + "integrity": "sha512-YUamvefLnsqu6124GavVTI7nqcFlQJ12ROB0oSwG69eSBZYNjg1tIs05LFrBxkwf4Xgqd6YzfJ9+FeG428RvzQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.7.0" + "@tiptap/core": "3.29.2" } }, "node_modules/@tiptap/extension-dropcursor": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.11.5.tgz", - "integrity": "sha512-uIN7L3FU0904ec7FFFbndO7RQE/yiON4VzAMhNn587LFMyWO8US139HXIL4O8dpZeYwYL3d1FnDTflZl6CwLlg==", + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-3.29.2.tgz", + "integrity": "sha512-KKno7cU9r1HdR48CRrsDu69/1UjZdoslq/UcE+Kx+tdhAv/aljXMkRSNzGMrBNOBDmHRgS1+58zm21WQWdQzwA==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.7.0", - "@tiptap/pm": "^2.7.0" + "@tiptap/extensions": "3.29.2" } }, "node_modules/@tiptap/extension-floating-menu": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.11.5.tgz", - "integrity": "sha512-HsMI0hV5Lwzm530Z5tBeyNCBNG38eJ3qjfdV2OHlfSf3+KOEfn6a5AUdoNaZO02LF79/8+7BaYU2drafag9cxQ==", + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-3.29.2.tgz", + "integrity": "sha512-CBTq4Xs5aGWr65uFCIkKBms796OhmirWf/ax//iJ4fl9IfwqjwFuc2vQs9PmuwpKn9ctDHo2sMTtvyeCvIt5LQ==", "license": "MIT", - "dependencies": { - "tippy.js": "^6.3.7" - }, + "optional": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.7.0", - "@tiptap/pm": "^2.7.0" + "@floating-ui/dom": "^1.0.0", + "@tiptap/core": "3.29.2", + "@tiptap/pm": "3.29.2" } }, "node_modules/@tiptap/extension-gapcursor": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.11.5.tgz", - "integrity": "sha512-kcWa+Xq9cb6lBdiICvLReuDtz/rLjFKHWpW3jTTF3FiP3wx4H8Rs6bzVtty7uOVTfwupxZRiKICAMEU6iT0xrQ==", + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-3.29.2.tgz", + "integrity": "sha512-8Q39UR4/Tit759IeW9xZIe3NMwN11GsuA3FLheDyyGn7RrW02HD3HhUDlazE54Ki4HoosjFmChPlN4Ik2ubdRQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.7.0", - "@tiptap/pm": "^2.7.0" + "@tiptap/extensions": "3.29.2" } }, "node_modules/@tiptap/extension-hard-break": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.11.5.tgz", - "integrity": "sha512-q9doeN+Yg9F5QNTG8pZGYfNye3tmntOwch683v0CCVCI4ldKaLZ0jG3NbBTq+mosHYdgOH2rNbIORlRRsQ+iYQ==", + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.29.2.tgz", + "integrity": "sha512-eUW3LN3fq8rXnjEUeI3D2QONYdLsU3yYQm4jxlErs2h4cfwrFjgf19VSUFVmm6LrFbbQ0OnDVPeVLL6iOwDw2w==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.7.0" + "@tiptap/core": "3.29.2" } }, "node_modules/@tiptap/extension-heading": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.11.5.tgz", - "integrity": "sha512-x/MV53psJ9baRcZ4k4WjnCUBMt8zCX7mPlKVT+9C/o+DEs/j/qxPLs95nHeQv70chZpSwCQCt93xMmuF0kPoAg==", + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.29.2.tgz", + "integrity": "sha512-6W4aIy70Mh7BNlbG9zZ5FBLhJhU2UUEzgZJ/jwYSCcB30o8McLxJSEjhtoHiX8R78Ah2/JzBGvIe5olZlbeE4A==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.7.0" + "@tiptap/core": "3.29.2" } }, - "node_modules/@tiptap/extension-history": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.11.5.tgz", - "integrity": "sha512-b+wOS33Dz1azw6F1i9LFTEIJ/gUui0Jwz5ZvmVDpL2ZHBhq1Ui0/spTT+tuZOXq7Y/uCbKL8Liu4WoedIvhboQ==", + "node_modules/@tiptap/extension-horizontal-rule": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.29.2.tgz", + "integrity": "sha512-8/ZPzbB9X85Mc9/7xVLZupQKBr2UVcQTGr512xtqMW+XkCQRHCph46tRo828YE13IMWI5fWn/FaNCqXG9cULSw==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.7.0", - "@tiptap/pm": "^2.7.0" + "@tiptap/core": "3.29.2", + "@tiptap/pm": "3.29.2" } }, - "node_modules/@tiptap/extension-horizontal-rule": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.11.5.tgz", - "integrity": "sha512-3up2r1Du8/5/4ZYzTC0DjTwhgPI3dn8jhOCLu73m5F3OGvK/9whcXoeWoX103hYMnGDxBlfOje71yQuN35FL4A==", + "node_modules/@tiptap/extension-image": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-3.29.2.tgz", + "integrity": "sha512-F+S4iru247mNVZdkNwNDZHeb281DkjsBJinaOGsOyJTvXY+KU5Uq7vY1LQTn493dTfU48Z0yMBUXwJffCT92Mg==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.7.0", - "@tiptap/pm": "^2.7.0" + "@tiptap/core": "3.29.2" } }, - "node_modules/@tiptap/extension-image": { - "version": "2.22.3", - "resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-2.22.3.tgz", - "integrity": "sha512-JO8n5YOqOs+bckPZZ3qJFFLpRbYlu4N52n/7Do0XmxEMWaa3fLcR0Rsa1v3X4dGH2T5cKQ475dWSpJQRc+x07w==", + "node_modules/@tiptap/extension-italic": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.29.2.tgz", + "integrity": "sha512-iH63V/5wsaMnY4Jz0+meaAGhaec4AiOzOduzl6ZZr5IyGhZ1kthyW84ELt0dyLI3hNceAUhaNWc+I7+vX0aoXA==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.7.0" + "@tiptap/core": "3.29.2" } }, - "node_modules/@tiptap/extension-italic": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.11.5.tgz", - "integrity": "sha512-9VGfb2/LfPhQ6TjzDwuYLRvw0A6VGbaIp3F+5Mql8XVdTBHb2+rhELbyhNGiGVR78CaB/EiKb6dO9xu/tBWSYA==", + "node_modules/@tiptap/extension-link": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.29.2.tgz", + "integrity": "sha512-DcVer5SqrexKCEP6Ip1UPxJUMvcRCCItSv0wxoGytanrimBh2smvcg6X0DWnjlsi5H0updhyl+atYCmmQXUIXA==", "license": "MIT", + "dependencies": { + "linkifyjs": "^4.3.3" + }, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.7.0" + "@tiptap/core": "3.29.2", + "@tiptap/pm": "3.29.2" } }, - "node_modules/@tiptap/extension-link": { - "version": "2.26.2", - "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.26.2.tgz", - "integrity": "sha512-rzYxx5wI1551ubPfW2pJ3V957cX/WAmbUI3q8Un+LlOsSmbddl+5BjlF5t/vl/pwaOv7FJAz9e29n877zkGOVQ==", + "node_modules/@tiptap/extension-list": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.29.2.tgz", + "integrity": "sha512-WPZ9BHAPT6QeIm1vdVkuoOWvy9a8/EZeJwV2VhU8LXyTAttvzyj4rsbbHyJWvYWlUSTt/QF2AZ2zhKo7u1w3/A==", "license": "MIT", - "dependencies": { - "linkifyjs": "^4.2.0" - }, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.7.0", - "@tiptap/pm": "^2.7.0" + "@tiptap/core": "3.29.2", + "@tiptap/pm": "3.29.2" } }, "node_modules/@tiptap/extension-list-item": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.11.5.tgz", - "integrity": "sha512-Mp5RD/pbkfW1vdc6xMVxXYcta73FOwLmblQlFNn/l/E5/X1DUSA4iGhgDDH4EWO3swbs03x2f7Zka/Xoj3+WLg==", + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-3.29.2.tgz", + "integrity": "sha512-s8vBVHHFT0Qpu7CzAZ7S1kYmSiVaDvUNvMNcZUWnxj6VPfiwmx0eXd9FsjePrRCMoM5tFmPnhFTHDxY3D/eZeQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-list": "3.29.2" + } + }, + "node_modules/@tiptap/extension-list-keymap": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-keymap/-/extension-list-keymap-3.29.2.tgz", + "integrity": "sha512-R+3k8OLnxdCH7Xy9ieOwUt5m2Je74u8mikothGmsYVO2Zyq48fIbmZ+X6RBPCu7DBOI2FIUhHEFbKQeDWvDNmA==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.7.0" + "@tiptap/extension-list": "3.29.2" } }, "node_modules/@tiptap/extension-mention": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-mention/-/extension-mention-2.11.5.tgz", - "integrity": "sha512-xj0/P4WSQWiDHzQLSIqdPUEu8LlC+ptSYA+y9IDChG51j1jVqcmolnS4sxpyrfr/t0ug0smNmJ4PDjQtXaG63A==", + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-mention/-/extension-mention-3.29.2.tgz", + "integrity": "sha512-WF8ugDa2IRbgyRW+PffPYg8ZI+Qp9azvIsNoyuf+VSg5Vp7ze2TuJXUTObSckyiN5Ann8bDNM9Hbu3TdoI0DFQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.7.0", - "@tiptap/pm": "^2.7.0", - "@tiptap/suggestion": "^2.7.0" + "@tiptap/core": "3.29.2", + "@tiptap/pm": "3.29.2", + "@tiptap/suggestion": "3.29.2" } }, "node_modules/@tiptap/extension-ordered-list": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.11.5.tgz", - "integrity": "sha512-Cu8KwruBNWAaEfshRQR0yOSaUKAeEwxW7UgbvF9cN/zZuKgK5uZosPCPTehIFCcRe+TBpRtZQh+06f/gNYpYYg==", + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.29.2.tgz", + "integrity": "sha512-ndCunC+UsYOpkOtL7vGnDz21UNa45WUlcO9wMT1fbuYow2QnRhsuMlCWENXI52YPPARWuQ0RDgN7q6TaxPERBg==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.7.0" + "@tiptap/extension-list": "3.29.2" } }, "node_modules/@tiptap/extension-paragraph": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.11.5.tgz", - "integrity": "sha512-YFBWeg7xu/sBnsDIF/+nh9Arf7R0h07VZMd0id5Ydd2Qe3c1uIZwXxeINVtH0SZozuPIQFAT8ICe9M0RxmE+TA==", + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.29.2.tgz", + "integrity": "sha512-7qJj5YTr11vvjNgjDN1ypOfwTovc0QOCYcit/rskeuVgnmQZOZQzC/BbyKLLG7UGnpRLemU/mEGbW9pAqjAXkQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.7.0" + "@tiptap/core": "3.29.2" } }, - "node_modules/@tiptap/extension-placeholder": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-2.11.5.tgz", - "integrity": "sha512-Pr+0Ju/l2ZvXMd9VQxtaoSZbs0BBp1jbBDqwms88ctpyvQFRfLSfSkqudQcSHyw2ROOz2E31p/7I7fpI8Y0CLA==", + "node_modules/@tiptap/extension-strike": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.29.2.tgz", + "integrity": "sha512-aEvLAbddUQZ+FukCreV3q4G2HfNI+odE7E9U+wbq6XsSWKyo8/pDu1muz+TFKNre4blSMOQ3JQmw5UeHDKy+fg==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.7.0", - "@tiptap/pm": "^2.7.0" + "@tiptap/core": "3.29.2" } }, - "node_modules/@tiptap/extension-strike": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.11.5.tgz", - "integrity": "sha512-PVfUiCqrjvsLpbIoVlegSY8RlkR64F1Rr2RYmiybQfGbg+AkSZXDeO0eIrc03//4gua7D9DfIozHmAKv1KN3ow==", + "node_modules/@tiptap/extension-text": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.29.2.tgz", + "integrity": "sha512-Ubko45JWWHe8glBt2PiGNF8hcbys/JNalFhiR7Y1X4iOOtAxAKJJxh3+eq+//NTlGuBPdWGp7zw8EEUp7anjKA==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.7.0" + "@tiptap/core": "3.29.2" } }, - "node_modules/@tiptap/extension-text": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.11.5.tgz", - "integrity": "sha512-Gq1WwyhFpCbEDrLPIHt5A8aLSlf8bfz4jm417c8F/JyU0J5dtYdmx0RAxjnLw1i7ZHE7LRyqqAoS0sl7JHDNSQ==", + "node_modules/@tiptap/extension-underline": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.29.2.tgz", + "integrity": "sha512-K7XwH/xS/5AIREWQ00VTEf/W5U0olp7j6wwit7cdd/8nHv6h6AGr1+iEApHKoLXWQZLfGQKzJlT9W61LAl+fHA==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.7.0" + "@tiptap/core": "3.29.2" } }, - "node_modules/@tiptap/extension-text-style": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-2.11.5.tgz", - "integrity": "sha512-YUmYl0gILSd/u/ZkOmNxjNXVw+mu8fpC2f8G4I4tLODm0zCx09j9DDEJXSrM5XX72nxJQqtSQsCpNKnL0hfeEQ==", + "node_modules/@tiptap/extensions": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.29.2.tgz", + "integrity": "sha512-BCz+FCAChSYtUe4BFj97HEO+nSK+J7GxbJgZG4Hg7DT/gI+hRyeNndU8efiQAx3WGdzsFi3UxRpcF1tTQM7iMQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.7.0" + "@tiptap/core": "3.29.2", + "@tiptap/pm": "3.29.2" } }, "node_modules/@tiptap/pm": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.11.5.tgz", - "integrity": "sha512-z9JFtqc5ZOsdQLd9vRnXfTCQ8v5ADAfRt9Nm7SqP6FUHII8E1hs38ACzf5xursmth/VonJYb5+73Pqxk1hGIPw==", - "license": "MIT", - "dependencies": { - "prosemirror-changeset": "^2.2.1", - "prosemirror-collab": "^1.3.1", - "prosemirror-commands": "^1.6.2", - "prosemirror-dropcursor": "^1.8.1", - "prosemirror-gapcursor": "^1.3.2", - "prosemirror-history": "^1.4.1", - "prosemirror-inputrules": "^1.4.0", - "prosemirror-keymap": "^1.2.2", - "prosemirror-markdown": "^1.13.1", - "prosemirror-menu": "^1.2.4", - "prosemirror-model": "^1.23.0", - "prosemirror-schema-basic": "^1.2.3", - "prosemirror-schema-list": "^1.4.1", - "prosemirror-state": "^1.4.3", - "prosemirror-tables": "^1.6.3", - "prosemirror-trailing-node": "^3.0.0", - "prosemirror-transform": "^1.10.2", - "prosemirror-view": "^1.37.0" + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.29.2.tgz", + "integrity": "sha512-GCOme7xHaS+DSoaA4CDcAD3l6JyBlvZhvCyfsy2Vp6j8tEoBkZWio7soYVosmlyn7zq8/64VeFZP5s47yfG7fQ==", + "license": "MIT", + "dependencies": { + "prosemirror-changeset": "^2.4.1", + "prosemirror-commands": "^1.7.1", + "prosemirror-dropcursor": "^1.8.2", + "prosemirror-gapcursor": "^1.4.1", + "prosemirror-history": "^1.5.0", + "prosemirror-inputrules": "^1.5.1", + "prosemirror-keymap": "^1.2.3", + "prosemirror-model": "^1.25.11", + "prosemirror-schema-list": "^1.5.1", + "prosemirror-state": "^1.4.4", + "prosemirror-tables": "^1.8.5", + "prosemirror-transform": "^1.12.0", + "prosemirror-view": "^1.41.9" }, "funding": { "type": "github", @@ -5433,55 +5449,62 @@ } }, "node_modules/@tiptap/react": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-2.11.5.tgz", - "integrity": "sha512-Dp8eHL1G+R/C4+QzAczyb3t1ovexEIZx9ln7SGEM+cT1KHKAw9XGPRgsp92+NQaYI+EdEb/YqoBOSzQcd18/OQ==", + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-3.29.2.tgz", + "integrity": "sha512-ZMKgteYmZXnq7C22U9fbCTC6jAF8XlQM5n2K2FLWCdYAlP4FNV3XeQ9hY2a13KSQ0Q3yltWXZlcWBpt5ClxScg==", "license": "MIT", "dependencies": { - "@tiptap/extension-bubble-menu": "^2.11.5", - "@tiptap/extension-floating-menu": "^2.11.5", "@types/use-sync-external-store": "^0.0.6", - "fast-deep-equal": "^3", - "use-sync-external-store": "^1" + "fast-equals": "^5.3.3", + "use-sync-external-store": "^1.4.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, + "optionalDependencies": { + "@tiptap/extension-bubble-menu": "^3.29.2", + "@tiptap/extension-floating-menu": "^3.29.2" + }, "peerDependencies": { - "@tiptap/core": "^2.7.0", - "@tiptap/pm": "^2.7.0", + "@tiptap/core": "3.29.2", + "@tiptap/pm": "3.29.2", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "@types/react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/@tiptap/starter-kit": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.11.5.tgz", - "integrity": "sha512-SLI7Aj2ruU1t//6Mk8f+fqW+18uTqpdfLUJYgwu0CkqBckrkRZYZh6GVLk/02k3H2ki7QkFxiFbZrdbZdng0JA==", - "license": "MIT", - "dependencies": { - "@tiptap/core": "^2.11.5", - "@tiptap/extension-blockquote": "^2.11.5", - "@tiptap/extension-bold": "^2.11.5", - "@tiptap/extension-bullet-list": "^2.11.5", - "@tiptap/extension-code": "^2.11.5", - "@tiptap/extension-code-block": "^2.11.5", - "@tiptap/extension-document": "^2.11.5", - "@tiptap/extension-dropcursor": "^2.11.5", - "@tiptap/extension-gapcursor": "^2.11.5", - "@tiptap/extension-hard-break": "^2.11.5", - "@tiptap/extension-heading": "^2.11.5", - "@tiptap/extension-history": "^2.11.5", - "@tiptap/extension-horizontal-rule": "^2.11.5", - "@tiptap/extension-italic": "^2.11.5", - "@tiptap/extension-list-item": "^2.11.5", - "@tiptap/extension-ordered-list": "^2.11.5", - "@tiptap/extension-paragraph": "^2.11.5", - "@tiptap/extension-strike": "^2.11.5", - "@tiptap/extension-text": "^2.11.5", - "@tiptap/extension-text-style": "^2.11.5", - "@tiptap/pm": "^2.11.5" + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-3.29.2.tgz", + "integrity": "sha512-oTu0tysiqk4zgjEtxRHjAQgxUKaAevZwueOWwSWubHdokqp7SpcbE5n9USJv89HKuTUDm3GjnQH6q8HNn/2DsA==", + "license": "MIT", + "dependencies": { + "@tiptap/core": "^3.29.2", + "@tiptap/extension-blockquote": "^3.29.2", + "@tiptap/extension-bold": "^3.29.2", + "@tiptap/extension-bullet-list": "^3.29.2", + "@tiptap/extension-code": "^3.29.2", + "@tiptap/extension-code-block": "^3.29.2", + "@tiptap/extension-document": "^3.29.2", + "@tiptap/extension-dropcursor": "^3.29.2", + "@tiptap/extension-gapcursor": "^3.29.2", + "@tiptap/extension-hard-break": "^3.29.2", + "@tiptap/extension-heading": "^3.29.2", + "@tiptap/extension-horizontal-rule": "^3.29.2", + "@tiptap/extension-italic": "^3.29.2", + "@tiptap/extension-link": "^3.29.2", + "@tiptap/extension-list": "^3.29.2", + "@tiptap/extension-list-item": "^3.29.2", + "@tiptap/extension-list-keymap": "^3.29.2", + "@tiptap/extension-ordered-list": "^3.29.2", + "@tiptap/extension-paragraph": "^3.29.2", + "@tiptap/extension-strike": "^3.29.2", + "@tiptap/extension-text": "^3.29.2", + "@tiptap/extension-underline": "^3.29.2", + "@tiptap/extensions": "^3.29.2", + "@tiptap/pm": "^3.29.2" }, "funding": { "type": "github", @@ -5489,17 +5512,18 @@ } }, "node_modules/@tiptap/suggestion": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/@tiptap/suggestion/-/suggestion-2.11.5.tgz", - "integrity": "sha512-uafwGgB5YuKX/xLRjnt2H5eA21I8HcNXpdbH4Du2gg3KM71RpUbkyjaV7KEMA/5qwCEo+sddlpuErj4wBycZ5Q==", + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/suggestion/-/suggestion-3.29.2.tgz", + "integrity": "sha512-ZEhRm0gnRCwCScR9IrnIBhm9sr6U8vpR9oeznYk3hiedZ48zsgohqvgoIYpWcwYWrT2Pb0NrfhCT8IuSzdJHzQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.7.0", - "@tiptap/pm": "^2.7.0" + "@floating-ui/dom": "^1.0.0", + "@tiptap/core": "3.29.2", + "@tiptap/pm": "3.29.2" } }, "node_modules/@tootallnate/once": { @@ -5814,14 +5838,12 @@ "version": "15.7.13", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", - "dev": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.12", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", - "dev": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -5832,7 +5854,6 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/react": "*" @@ -7920,12 +7941,6 @@ "dev": true, "license": "MIT" }, - "node_modules/crelt": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", - "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", - "license": "MIT" - }, "node_modules/cross-spawn": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz", @@ -8170,7 +8185,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, "license": "MIT" }, "node_modules/d": { @@ -9472,6 +9486,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -10189,6 +10204,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, "license": "MIT" }, "node_modules/fast-diff": { @@ -10198,6 +10214,15 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/fast-equals": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.1.tgz", + "integrity": "sha512-DjlFSM5Pk9cGcL0q5QXl66eGzx0N6szNgaswwc5ZphlBohjTVJSnGgI+rJVOgOi65qUoQnDZN4nDqi33udtydQ==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -14426,9 +14451,9 @@ } }, "node_modules/linkifyjs": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.2.tgz", - "integrity": "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.3.tgz", + "integrity": "sha512-P8aEP5U/D1/IlTY2OeYsErdwh9bGuLE30NcXtKEjgdHcahveQoQwM2yZNsioQHsWFz0P7KKudisbrzCgR0sDHg==", "license": "MIT" }, "node_modules/loader-utils": { @@ -16412,27 +16437,18 @@ "license": "MIT" }, "node_modules/prosemirror-changeset": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.2.1.tgz", - "integrity": "sha512-J7msc6wbxB4ekDFj+n9gTW/jav/p53kdlivvuppHsrZXCaQdVgRghoZbSS3kwrRyAstRVQ4/+u5k7YfLgkkQvQ==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.1.tgz", + "integrity": "sha512-96WBLhOaYhJ+kPhLg3uW359Tz6I/MfcrQfL4EGv4SrcqKEMC1gmoGrXHecPE8eOwTVCJ4IwgfzM8fFad25wNfw==", "license": "MIT", "dependencies": { "prosemirror-transform": "^1.0.0" } }, - "node_modules/prosemirror-collab": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz", - "integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==", - "license": "MIT", - "dependencies": { - "prosemirror-state": "^1.0.0" - } - }, "node_modules/prosemirror-commands": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.0.tgz", - "integrity": "sha512-6toodS4R/Aah5pdsrIwnTYPEjW70SlO5a66oo5Kk+CIrgJz3ukOoS+FYDGqvQlAX5PxoGWDX1oD++tn5X3pyRA==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz", + "integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==", "license": "MIT", "dependencies": { "prosemirror-model": "^1.0.0", @@ -16441,9 +16457,9 @@ } }, "node_modules/prosemirror-dropcursor": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.1.tgz", - "integrity": "sha512-M30WJdJZLyXHi3N8vxN6Zh5O8ZBbQCz0gURTfPmTIBNQ5pxrdU7A58QkNqfa98YEjSAL1HUyyU34f6Pm5xBSGw==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.3.tgz", + "integrity": "sha512-FoYbsJR8gK+DGlqhNoE29Loa38eIZPzQRIb1VMaDNBoo4OLP6vVof/jR8qFY/6XvUd6Dhug8MDCHl2a/h8RTfQ==", "license": "MIT", "dependencies": { "prosemirror-state": "^1.0.0", @@ -16452,9 +16468,9 @@ } }, "node_modules/prosemirror-gapcursor": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.3.2.tgz", - "integrity": "sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.1.tgz", + "integrity": "sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==", "license": "MIT", "dependencies": { "prosemirror-keymap": "^1.0.0", @@ -16464,9 +16480,9 @@ } }, "node_modules/prosemirror-history": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.4.1.tgz", - "integrity": "sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz", + "integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==", "license": "MIT", "dependencies": { "prosemirror-state": "^1.2.2", @@ -16476,9 +16492,9 @@ } }, "node_modules/prosemirror-inputrules": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.4.0.tgz", - "integrity": "sha512-6ygpPRuTJ2lcOXs9JkefieMst63wVJBgHZGl5QOytN7oSZs3Co/BYbc3Yx9zm9H37Bxw8kVzCnDsihsVsL4yEg==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz", + "integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==", "license": "MIT", "dependencies": { "prosemirror-state": "^1.0.0", @@ -16486,9 +16502,9 @@ } }, "node_modules/prosemirror-keymap": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.2.tgz", - "integrity": "sha512-EAlXoksqC6Vbocqc0GtzCruZEzYgrn+iiGnNjsJsH4mrnIGex4qbLdWWNza3AW5W36ZRrlBID0eM6bdKH4OStQ==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz", + "integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==", "license": "MIT", "dependencies": { "prosemirror-state": "^1.0.0", @@ -16506,40 +16522,19 @@ "prosemirror-model": "^1.25.0" } }, - "node_modules/prosemirror-menu": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.2.4.tgz", - "integrity": "sha512-S/bXlc0ODQup6aiBbWVsX/eM+xJgCTAfMq/nLqaO5ID/am4wS0tTCIkzwytmao7ypEtjj39i7YbJjAgO20mIqA==", - "license": "MIT", - "dependencies": { - "crelt": "^1.0.0", - "prosemirror-commands": "^1.0.0", - "prosemirror-history": "^1.0.0", - "prosemirror-state": "^1.0.0" - } - }, "node_modules/prosemirror-model": { - "version": "1.25.9", - "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.9.tgz", - "integrity": "sha512-pRTklkDDMMRopyoAcrr9wV/8g/RYgrLHBuJAb5hlEuYZRdm5yqmPjWId83fpBwPpSFqEdja0H7Dfd7z1X/npcA==", + "version": "1.25.11", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.11.tgz", + "integrity": "sha512-QWg9RhnpLlogAmp3p96uEFrE5txQpFynd4vhBAELkwgOCWQs/X0yCzB3/hrHqiPwf91RG5KyWq6553zs9JqIOQ==", "license": "MIT", "dependencies": { "orderedmap": "^2.0.0" } }, - "node_modules/prosemirror-schema-basic": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.3.tgz", - "integrity": "sha512-h+H0OQwZVqMon1PNn0AG9cTfx513zgIG2DY00eJ00Yvgb3UD+GQ/VlWW5rcaxacpCGT1Yx8nuhwXk4+QbXUfJA==", - "license": "MIT", - "dependencies": { - "prosemirror-model": "^1.19.0" - } - }, "node_modules/prosemirror-schema-list": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.0.tgz", - "integrity": "sha512-gg1tAfH1sqpECdhIHOA/aLg2VH3ROKBWQ4m8Qp9mBKrOxQRW61zc+gMCI8nh22gnBzd1t2u1/NPLmO3nAa3ssg==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz", + "integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==", "license": "MIT", "dependencies": { "prosemirror-model": "^1.0.0", @@ -16548,9 +16543,9 @@ } }, "node_modules/prosemirror-state": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.3.tgz", - "integrity": "sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz", + "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==", "license": "MIT", "dependencies": { "prosemirror-model": "^1.0.0", @@ -16559,49 +16554,34 @@ } }, "node_modules/prosemirror-tables": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.6.4.tgz", - "integrity": "sha512-TkDY3Gw52gRFRfRn2f4wJv5WOgAOXLJA2CQJYIJ5+kdFbfj3acR4JUW6LX2e1hiEBiUwvEhzH5a3cZ5YSztpIA==", + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.8.5.tgz", + "integrity": "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==", "license": "MIT", "dependencies": { - "prosemirror-keymap": "^1.2.2", - "prosemirror-model": "^1.24.1", - "prosemirror-state": "^1.4.3", - "prosemirror-transform": "^1.10.2", - "prosemirror-view": "^1.37.2" - } - }, - "node_modules/prosemirror-trailing-node": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz", - "integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==", - "license": "MIT", - "dependencies": { - "@remirror/core-constants": "3.0.0", - "escape-string-regexp": "^4.0.0" - }, - "peerDependencies": { - "prosemirror-model": "^1.22.1", - "prosemirror-state": "^1.4.2", - "prosemirror-view": "^1.33.8" + "prosemirror-keymap": "^1.2.3", + "prosemirror-model": "^1.25.4", + "prosemirror-state": "^1.4.4", + "prosemirror-transform": "^1.10.5", + "prosemirror-view": "^1.41.4" } }, "node_modules/prosemirror-transform": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.2.tgz", - "integrity": "sha512-2iUq0wv2iRoJO/zj5mv8uDUriOHWzXRnOTVgCzSXnktS/2iQRa3UUQwVlkBlYZFtygw6Nh1+X4mGqoYBINn5KQ==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.12.0.tgz", + "integrity": "sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w==", "license": "MIT", "dependencies": { "prosemirror-model": "^1.21.0" } }, "node_modules/prosemirror-view": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.38.0.tgz", - "integrity": "sha512-O45kxXQTaP9wPdXhp8TKqCR+/unS/gnfg9Q93svQcB3j0mlp2XSPAmsPefxHADwzC+fbNS404jqRxm3UQaGvgw==", + "version": "1.42.2", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.42.2.tgz", + "integrity": "sha512-Pdg0l5kXm8aLDquFAnQFTCITg0q44sLqBlHlpsVLD9segdOao8TOfQdAhCrCXyVgPSRr6UDDROOIWA3bIrN9YQ==", "license": "MIT", "dependencies": { - "prosemirror-model": "^1.20.0", + "prosemirror-model": "^1.25.8", "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.1.0" } @@ -19132,19 +19112,10 @@ "dev": true, "license": "MIT" }, - "node_modules/tippy.js": { - "version": "6.3.7", - "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", - "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==", - "license": "MIT", - "dependencies": { - "@popperjs/core": "^2.9.0" - } - }, "node_modules/tiptap-markdown": { - "version": "0.8.10", - "resolved": "https://registry.npmjs.org/tiptap-markdown/-/tiptap-markdown-0.8.10.tgz", - "integrity": "sha512-iDVkR2BjAqkTDtFX0h94yVvE2AihCXlF0Q7RIXSJPRSR5I0PA1TMuAg6FHFpmqTn4tPxJ0by0CK7PUMlnFLGEQ==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/tiptap-markdown/-/tiptap-markdown-0.9.0.tgz", + "integrity": "sha512-dKLQ9iiuGNgrlGVjrNauF/UBzWu4LYOx5pkD0jNkmQt/GOwfCJsBuzZTsf1jZ204ANHOm572mZ9PYvGh1S7tpQ==", "license": "MIT", "workspaces": [ "example" @@ -19156,7 +19127,7 @@ "prosemirror-markdown": "^1.11.1" }, "peerDependencies": { - "@tiptap/core": "^2.0.3" + "@tiptap/core": "^3.0.1" } }, "node_modules/tiptap-markdown/node_modules/@types/linkify-it": { diff --git a/package.json b/package.json index 6d4460c7ab..6e3804be5d 100644 --- a/package.json +++ b/package.json @@ -6,14 +6,14 @@ "dependencies": { "@lingui/format-json": "^5.1.2", "@lingui/react": "^5.1.2", - "@tiptap/extension-image": "^2.22.3", - "@tiptap/extension-link": "^2.11.5", - "@tiptap/extension-mention": "^2.11.5", - "@tiptap/extension-placeholder": "^2.11.5", - "@tiptap/pm": "^2.11.5", - "@tiptap/react": "^2.11.5", - "@tiptap/starter-kit": "^2.11.5", - "@tiptap/suggestion": "^2.11.5", + "@tiptap/extension-image": "3.29.2", + "@tiptap/extension-link": "3.29.2", + "@tiptap/extension-mention": "3.29.2", + "@tiptap/extensions": "3.29.2", + "@tiptap/pm": "3.29.2", + "@tiptap/react": "3.29.2", + "@tiptap/starter-kit": "3.29.2", + "@tiptap/suggestion": "3.29.2", "dompurify": "^3.4.12", "inter-ui": "^4.1.1", "marked": "^17.0.6", @@ -22,7 +22,7 @@ "react-dom": "^18.3.1", "react-textarea-autosize": "^8.5.9", "react-toastify": "^9.0.1", - "tiptap-markdown": "^0.8.10" + "tiptap-markdown": "^0.9.0" }, "devDependencies": { "@babel/cli": "^7.28.3", @@ -101,7 +101,7 @@ "@locale/(.*)": "/locale/$1" }, "transformIgnorePatterns": [ - "/node_modules/(?!(marked)/)" + "/node_modules/(?!(marked|@tiptap|tiptap-markdown|prosemirror-.*)/)" ] }, "engines": { diff --git a/public/components/common/form/CommentEditor.markdown.spec.ts b/public/components/common/form/CommentEditor.markdown.spec.ts new file mode 100644 index 0000000000..c23d426b0f --- /dev/null +++ b/public/components/common/form/CommentEditor.markdown.spec.ts @@ -0,0 +1,52 @@ +import { Editor } from "@tiptap/core" +import StarterKit from "@tiptap/starter-kit" +import Link from "@tiptap/extension-link" +import { Markdown, MarkdownStorage } from "tiptap-markdown" +import { CustomImage } from "./CustomImage" +import { CustomMention } from "./CustomMention" + +// Headless editor with the same extension set CommentEditor uses (minus UI-only bits). +// This locks the markdown round-trip (markdown -> doc -> markdown) so the later engine +// swap to @tiptap/markdown can be verified byte-for-byte against it. +const makeEditor = () => + new Editor({ + extensions: [ + StarterKit.configure({ link: false, underline: false }), + Link.configure({ openOnClick: true, autolink: true, defaultProtocol: "https" }), + Markdown.configure({ html: false, breaks: true }), + CustomMention.configure({ HTMLAttributes: { class: "mention" } }), + CustomImage.configure({ allowBase64: true }), + ], + }) + +const roundTrip = (md: string): string => { + const editor = makeEditor() + editor.commands.setContent(md, { emitUpdate: false }) + const out = (editor.storage as unknown as { markdown: MarkdownStorage }).markdown.getMarkdown() + editor.destroy() + return out +} + +describe("CommentEditor markdown round-trip (tiptap-markdown 0.9)", () => { + const cases: Array<[string, string]> = [ + ["mention", "@[Jane Doe]"], + ["fider image", "![](fider-image:attachments/zy0hBtqrjQki7M56p26AuAXljRoaNUSwZO6MOky5gnYm2nW1rsMmrp3dwhjGk7ok-aden.jpeg)"], + ["plain image", "![](http://demo.dev.fider.io:3000/images/100/28)"], + ["bold", "**bold**"], + ["italic", "*italic*"], + ["strike", "~~struck~~"], + ["inline code", "`code`"], + ["h2", "## Heading two"], + ["h3", "### Heading three"], + ["bullet list", "- one\n- two"], + ["ordered list", "1. one\n2. two"], + ["blockquote", "> quoted"], + ["link", "[GitHub](https://github.com)"], + ] + + cases.forEach(([name, md]) => { + test(`${name} round-trips byte-identically: ${md}`, () => { + expect(roundTrip(md)).toEqual(md) + }) + }) +}) diff --git a/public/components/common/form/CommentEditor.tsx b/public/components/common/form/CommentEditor.tsx index 2ea0b0cad5..b28edc3c02 100644 --- a/public/components/common/form/CommentEditor.tsx +++ b/public/components/common/form/CommentEditor.tsx @@ -3,8 +3,8 @@ import StarterKit from "@tiptap/starter-kit" import Link from "@tiptap/extension-link" import React, { useState, useRef, useEffect } from "react" import { EditorContent, useEditor } from "@tiptap/react" -import { Markdown } from "tiptap-markdown" -import Placeholder from "@tiptap/extension-placeholder" +import { Markdown, MarkdownStorage } from "tiptap-markdown" +import { Placeholder } from "@tiptap/extensions" import { i18n } from "@lingui/core" import { useAllowedProtocols } from "@fider/hooks" @@ -35,6 +35,10 @@ import LinkInsertModal from "./LinkInsertModal" import { Trans } from "@lingui/react/macro" import { classSet } from "@fider/services" +// tiptap-markdown 0.9 exposes getMarkdown() on editor.storage.markdown but (unlike v2) no +// longer augments tiptap's Storage type, so reach it through the exported MarkdownStorage. +const getMarkdownStorage = (editor: Editor): MarkdownStorage => (editor.storage as unknown as { markdown: MarkdownStorage }).markdown + const MenuBar = ({ editor, isMarkdownMode, @@ -264,15 +268,16 @@ const Tiptap: React.FunctionComponent = (props) => { const toggleMarkdownMode = () => { if (isRawMarkdownMode) { // Switching FROM markdown TO rich text - // Load the edited markdown back into the existing editor instance + // Load the edited markdown back into the existing editor instance. + // tiptap v3 emits an update on setContent by default; keep v2 semantics (no onChange). if (editor) { - editor.commands.setContent(markdownText) + editor.commands.setContent(markdownText, { emitUpdate: false }) } } else { // Switching FROM rich text TO markdown // Get the markdown from the editor and store it if (editor) { - const markdown = editor.storage.markdown.getMarkdown() + const markdown = getMarkdownStorage(editor).getMarkdown() setMarkdownText(markdown) } } @@ -334,7 +339,7 @@ const Tiptap: React.FunctionComponent = (props) => { const updated = ({ editor }: { editor: Editor; transaction: any }): void => { // Get the current markdown content - const markdown = isRawMarkdownMode ? editor.getText() : editor.storage.markdown.getMarkdown() + const markdown = isRawMarkdownMode ? editor.getText() : getMarkdownStorage(editor).getMarkdown() setContentLength(markdown.length) @@ -487,7 +492,9 @@ const Tiptap: React.FunctionComponent = (props) => { // Only use extensions for rich text mode const extensions = [ - StarterKit, + // tiptap v3 StarterKit bundles Link (custom Link.configure below owns the mark) and + // Underline (no markdown representation with the marked renderer); disable both. + StarterKit.configure({ link: false, underline: false }), Link.configure({ openOnClick: true, autolink: true, From 4ec748a7f07bd4fe54ae00abe30da42d8435ec4a Mon Sep 17 00:00:00 2001 From: Matt Roberts Date: Thu, 30 Jul 2026 14:40:36 +0100 Subject: [PATCH 3/3] build(deps): swap editor to @tiptap/markdown, drop markdown-it (unify on marked) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completes the unify-on-marked migration: the editor now uses first-party @tiptap/markdown (marked engine) instead of tiptap-markdown (markdown-it). markdown-it, tiptap-markdown and prosemirror-markdown are removed entirely — the app now runs a SINGLE markdown engine (marked 17), shared by the editor and the display renderer. Custom-node rewrite (markdown-it rules -> @tiptap/markdown hooks): - CustomImage: markdownTokenName "image" + parseMarkdown/renderMarkdown; detects ![](fider-image:). Made the node INLINE — marked tokenizes images as inline tokens, so a block node was dropped during parse (matches the fider-inline-image intent). - CustomMention: a custom marked markdownTokenizer for the non-standard @[name] syntax + parseMarkdown/renderMarkdown. - Note: `this` inside parseMarkdown is NOT the extension (no this.name/this.options), so node types are literals and image src uses the static path (the same fallback used before; live base64 uploads still flow through the setImage command). - CommentEditor: Markdown.configure({ markedOptions: { breaks, gfm } }), content/setContent use contentType:"markdown", getMarkdown().trim() (v3 appends a trailing empty paragraph). Validation: new 21-case byte-identical round-trip suite (mentions/images in text, mixed inline, multi-paragraph, hard breaks, links+mentions) + markdown.spec 32/32 (renderer contract unchanged) + make lint-ui/build-ui/test-ui all green. Hard breaks normalize to the standard two-space form, which the renderer emits as identical
HTML, so existing stored content is unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) --- package-lock.json | 186 ++---------------- package.json | 5 +- .../form/CommentEditor.markdown.spec.ts | 21 +- .../components/common/form/CommentEditor.tsx | 18 +- public/components/common/form/CustomImage.ts | 86 ++++---- .../components/common/form/CustomMention.ts | 65 +++--- 6 files changed, 106 insertions(+), 275 deletions(-) diff --git a/package-lock.json b/package-lock.json index b18fd59f4a..588934b59a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@tiptap/extension-link": "3.29.2", "@tiptap/extension-mention": "3.29.2", "@tiptap/extensions": "3.29.2", + "@tiptap/markdown": "3.29.2", "@tiptap/pm": "3.29.2", "@tiptap/react": "3.29.2", "@tiptap/starter-kit": "3.29.2", @@ -21,12 +22,10 @@ "dompurify": "^3.4.12", "inter-ui": "^4.1.1", "marked": "^17.0.6", - "prosemirror-markdown": "^1.13.5", "react": "^18.3.1", "react-dom": "^18.3.1", "react-textarea-autosize": "^8.5.9", - "react-toastify": "^9.0.1", - "tiptap-markdown": "^0.9.0" + "react-toastify": "^9.0.1" }, "devDependencies": { "@babel/cli": "^7.28.3", @@ -5423,6 +5422,23 @@ "@tiptap/pm": "3.29.2" } }, + "node_modules/@tiptap/markdown": { + "version": "3.29.2", + "resolved": "https://registry.npmjs.org/@tiptap/markdown/-/markdown-3.29.2.tgz", + "integrity": "sha512-Knf9YqG3fxOyFuS3pe3eapDz0ya9eqPGzmyMSd5PuHNmR9/Ir6i5qk5+Md6bCPgDNjYskztNm7DoBXPfbXbO/w==", + "license": "MIT", + "dependencies": { + "marked": "^17.0.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.29.2", + "@tiptap/pm": "3.29.2" + } + }, "node_modules/@tiptap/pm": { "version": "3.29.2", "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.29.2.tgz", @@ -5771,12 +5787,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", - "license": "MIT" - }, "node_modules/@types/lodash": { "version": "4.17.13", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz", @@ -5784,22 +5794,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/markdown-it": { - "version": "14.1.2", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", - "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", - "license": "MIT", - "dependencies": { - "@types/linkify-it": "^5", - "@types/mdurl": "^2" - } - }, - "node_modules/@types/mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", - "license": "MIT" - }, "node_modules/@types/ms": { "version": "0.7.34", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", @@ -14431,25 +14425,6 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "license": "MIT" }, - "node_modules/linkify-it": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.2.tgz", - "integrity": "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/markdown-it" - } - ], - "license": "MIT", - "dependencies": { - "uc.micro": "^2.0.0" - } - }, "node_modules/linkifyjs": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.3.tgz", @@ -14662,57 +14637,6 @@ "node": ">=0.10.0" } }, - "node_modules/markdown-it": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.2.0.tgz", - "integrity": "sha512-1TGiQiJVRQ3NPmZH6sx5Cfnmg6GQm9jvC1ch4TK511NjSJvjzKLzn5pPfZRNZkRPZP0HqCioSndqH8v2nRaWVQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/markdown-it" - } - ], - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1", - "entities": "^4.4.0", - "linkify-it": "^5.0.1", - "mdurl": "^2.0.0", - "punycode.js": "^2.3.1", - "uc.micro": "^2.1.0" - }, - "bin": { - "markdown-it": "bin/markdown-it.mjs" - } - }, - "node_modules/markdown-it-task-lists": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/markdown-it-task-lists/-/markdown-it-task-lists-2.1.1.tgz", - "integrity": "sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==", - "license": "ISC" - }, - "node_modules/markdown-it/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, - "node_modules/markdown-it/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/marked": { "version": "17.0.6", "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.6.tgz", @@ -14742,12 +14666,6 @@ "dev": true, "license": "CC0-1.0" }, - "node_modules/mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", - "license": "MIT" - }, "node_modules/memfs": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", @@ -16511,17 +16429,6 @@ "w3c-keyname": "^2.2.0" } }, - "node_modules/prosemirror-markdown": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.5.tgz", - "integrity": "sha512-ac8trNQ01ybKDRTcfUc56LZufG3oYyU4N25qSXgp8dS0U4JtzzCj7oQlKu5v09VSmS5IseYoQ2yDkTbo7f7D8Q==", - "license": "MIT", - "dependencies": { - "@types/markdown-it": "^14.0.0", - "markdown-it": "^14.0.0", - "prosemirror-model": "^1.25.0" - } - }, "node_modules/prosemirror-model": { "version": "1.25.11", "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.11.tgz", @@ -16670,15 +16577,6 @@ "node": ">=6" } }, - "node_modules/punycode.js": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", - "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/purgecss": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/purgecss/-/purgecss-8.0.0.tgz", @@ -19112,46 +19010,6 @@ "dev": true, "license": "MIT" }, - "node_modules/tiptap-markdown": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/tiptap-markdown/-/tiptap-markdown-0.9.0.tgz", - "integrity": "sha512-dKLQ9iiuGNgrlGVjrNauF/UBzWu4LYOx5pkD0jNkmQt/GOwfCJsBuzZTsf1jZ204ANHOm572mZ9PYvGh1S7tpQ==", - "license": "MIT", - "workspaces": [ - "example" - ], - "dependencies": { - "@types/markdown-it": "^13.0.7", - "markdown-it": "^14.1.0", - "markdown-it-task-lists": "^2.1.1", - "prosemirror-markdown": "^1.11.1" - }, - "peerDependencies": { - "@tiptap/core": "^3.0.1" - } - }, - "node_modules/tiptap-markdown/node_modules/@types/linkify-it": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.5.tgz", - "integrity": "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==", - "license": "MIT" - }, - "node_modules/tiptap-markdown/node_modules/@types/markdown-it": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-13.0.9.tgz", - "integrity": "sha512-1XPwR0+MgXLWfTn9gCsZ55AHOKW1WN+P9vr0PaQh5aerR9LLQXUbjfEAFhjmEmyoYFWAyuN2Mqkn40MZ4ukjBw==", - "license": "MIT", - "dependencies": { - "@types/linkify-it": "^3", - "@types/mdurl": "^1" - } - }, - "node_modules/tiptap-markdown/node_modules/@types/mdurl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz", - "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==", - "license": "MIT" - }, "node_modules/tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", @@ -19592,12 +19450,6 @@ "node": ">=4.2.0" } }, - "node_modules/uc.micro": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", - "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", - "license": "MIT" - }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", diff --git a/package.json b/package.json index 6e3804be5d..474d108e5d 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "@tiptap/extension-link": "3.29.2", "@tiptap/extension-mention": "3.29.2", "@tiptap/extensions": "3.29.2", + "@tiptap/markdown": "3.29.2", "@tiptap/pm": "3.29.2", "@tiptap/react": "3.29.2", "@tiptap/starter-kit": "3.29.2", @@ -17,12 +18,10 @@ "dompurify": "^3.4.12", "inter-ui": "^4.1.1", "marked": "^17.0.6", - "prosemirror-markdown": "^1.13.5", "react": "^18.3.1", "react-dom": "^18.3.1", "react-textarea-autosize": "^8.5.9", - "react-toastify": "^9.0.1", - "tiptap-markdown": "^0.9.0" + "react-toastify": "^9.0.1" }, "devDependencies": { "@babel/cli": "^7.28.3", diff --git a/public/components/common/form/CommentEditor.markdown.spec.ts b/public/components/common/form/CommentEditor.markdown.spec.ts index c23d426b0f..d0f076c4e5 100644 --- a/public/components/common/form/CommentEditor.markdown.spec.ts +++ b/public/components/common/form/CommentEditor.markdown.spec.ts @@ -1,7 +1,7 @@ import { Editor } from "@tiptap/core" import StarterKit from "@tiptap/starter-kit" import Link from "@tiptap/extension-link" -import { Markdown, MarkdownStorage } from "tiptap-markdown" +import { Markdown } from "@tiptap/markdown" import { CustomImage } from "./CustomImage" import { CustomMention } from "./CustomMention" @@ -13,7 +13,7 @@ const makeEditor = () => extensions: [ StarterKit.configure({ link: false, underline: false }), Link.configure({ openOnClick: true, autolink: true, defaultProtocol: "https" }), - Markdown.configure({ html: false, breaks: true }), + Markdown.configure({ markedOptions: { breaks: true, gfm: true } }), CustomMention.configure({ HTMLAttributes: { class: "mention" } }), CustomImage.configure({ allowBase64: true }), ], @@ -21,27 +21,38 @@ const makeEditor = () => const roundTrip = (md: string): string => { const editor = makeEditor() - editor.commands.setContent(md, { emitUpdate: false }) - const out = (editor.storage as unknown as { markdown: MarkdownStorage }).markdown.getMarkdown() + editor.commands.setContent(md, { emitUpdate: false, contentType: "markdown" }) + const out = editor.getMarkdown().trim() editor.destroy() return out } -describe("CommentEditor markdown round-trip (tiptap-markdown 0.9)", () => { +describe("CommentEditor markdown round-trip (@tiptap/markdown)", () => { const cases: Array<[string, string]> = [ ["mention", "@[Jane Doe]"], + ["mention in text", "Hey @[Jane Doe], welcome aboard"], + ["two mentions", "@[Jane Doe] and @[John Smith]"], ["fider image", "![](fider-image:attachments/zy0hBtqrjQki7M56p26AuAXljRoaNUSwZO6MOky5gnYm2nW1rsMmrp3dwhjGk7ok-aden.jpeg)"], + ["fider image in text", "look ![](fider-image:attachments/abc-x.jpeg) here"], ["plain image", "![](http://demo.dev.fider.io:3000/images/100/28)"], ["bold", "**bold**"], ["italic", "*italic*"], ["strike", "~~struck~~"], ["inline code", "`code`"], + ["mixed inline", "a **bold** and *italic* and `code` and @[Jane Doe]"], ["h2", "## Heading two"], ["h3", "### Heading three"], ["bullet list", "- one\n- two"], ["ordered list", "1. one\n2. two"], ["blockquote", "> quoted"], ["link", "[GitHub](https://github.com)"], + ["link with mention", "see [GitHub](https://github.com) cc @[Jane Doe]"], + ["multi paragraph", "First paragraph.\n\nSecond paragraph."], + // Hard breaks serialize to the standard two-space form. This is render-equivalent: the + // marked renderer emits identical HTML (
) for bare "\n" and " \n" under breaks:true, + // so existing stored content (bare \n) is unaffected. + ["hard breaks", "line one \nline two \nline three"], + ["heading then text", "## Title\n\nSome body text."], ] cases.forEach(([name, md]) => { diff --git a/public/components/common/form/CommentEditor.tsx b/public/components/common/form/CommentEditor.tsx index b28edc3c02..70a9e77e16 100644 --- a/public/components/common/form/CommentEditor.tsx +++ b/public/components/common/form/CommentEditor.tsx @@ -3,7 +3,7 @@ import StarterKit from "@tiptap/starter-kit" import Link from "@tiptap/extension-link" import React, { useState, useRef, useEffect } from "react" import { EditorContent, useEditor } from "@tiptap/react" -import { Markdown, MarkdownStorage } from "tiptap-markdown" +import { Markdown } from "@tiptap/markdown" import { Placeholder } from "@tiptap/extensions" import { i18n } from "@lingui/core" import { useAllowedProtocols } from "@fider/hooks" @@ -35,10 +35,6 @@ import LinkInsertModal from "./LinkInsertModal" import { Trans } from "@lingui/react/macro" import { classSet } from "@fider/services" -// tiptap-markdown 0.9 exposes getMarkdown() on editor.storage.markdown but (unlike v2) no -// longer augments tiptap's Storage type, so reach it through the exported MarkdownStorage. -const getMarkdownStorage = (editor: Editor): MarkdownStorage => (editor.storage as unknown as { markdown: MarkdownStorage }).markdown - const MenuBar = ({ editor, isMarkdownMode, @@ -271,13 +267,13 @@ const Tiptap: React.FunctionComponent = (props) => { // Load the edited markdown back into the existing editor instance. // tiptap v3 emits an update on setContent by default; keep v2 semantics (no onChange). if (editor) { - editor.commands.setContent(markdownText, { emitUpdate: false }) + editor.commands.setContent(markdownText, { emitUpdate: false, contentType: "markdown" }) } } else { // Switching FROM rich text TO markdown // Get the markdown from the editor and store it if (editor) { - const markdown = getMarkdownStorage(editor).getMarkdown() + const markdown = editor.getMarkdown().trim() setMarkdownText(markdown) } } @@ -339,7 +335,7 @@ const Tiptap: React.FunctionComponent = (props) => { const updated = ({ editor }: { editor: Editor; transaction: any }): void => { // Get the current markdown content - const markdown = isRawMarkdownMode ? editor.getText() : getMarkdownStorage(editor).getMarkdown() + const markdown = isRawMarkdownMode ? editor.getText() : editor.getMarkdown().trim() setContentLength(markdown.length) @@ -506,10 +502,7 @@ const Tiptap: React.FunctionComponent = (props) => { rel: "noopener nofollow", }, }), - Markdown.configure({ - html: false, - breaks: true, - }), + Markdown.configure({ markedOptions: { breaks: true, gfm: true } }), CustomMention.configure({ HTMLAttributes: { class: "mention", @@ -549,6 +542,7 @@ const Tiptap: React.FunctionComponent = (props) => { { extensions, content: initialContentRef.current, + contentType: "markdown", onUpdate: updated, onFocus: () => { if (props.onFocus) { diff --git a/public/components/common/form/CustomImage.ts b/public/components/common/form/CustomImage.ts index 99916f0b5e..f8b1918d37 100644 --- a/public/components/common/form/CustomImage.ts +++ b/public/components/common/form/CustomImage.ts @@ -1,7 +1,6 @@ import { ImageUpload } from "@fider/models" import Image from "@tiptap/extension-image" -import * as MarkdownIt from "markdown-it" -import { defaultMarkdownSerializer } from "prosemirror-markdown" +import { JSONContent } from "@tiptap/core" export interface CustomImageOptions { HTMLAttributes?: Record @@ -11,9 +10,18 @@ export interface CustomImageOptions { onGetImageSrc?: (bkey: string) => string } +// marked image token shape (the parts we read) +type ImageToken = { href?: string; text?: string; title?: string | null } + export const CustomImage = Image.extend({ name: "customImage", + // marked tokenizes images as INLINE tokens, so the node must be inline to sit inside + // paragraph content (a block image would be dropped during markdown parse). This also + // matches Fider's `fider-inline-image` rendering. + inline: true, + group: "inline", + addOptions() { return { ...this.parent?.(), @@ -55,56 +63,36 @@ export const CustomImage = Image.extend({ } }, - addStorage() { + // --- @tiptap/markdown integration (marked engine) --- + // Handle the standard marked "image" token, detecting Fider's ![](fider-image:) syntax. + markdownTokenName: "image", + + parseMarkdown(token: ImageToken): JSONContent { + // Note: `this` inside parseMarkdown is NOT the extension (no this.name / this.options), + // so use a literal node type. Parsed content is already-stored images, which resolve + // via the static path (the same fallback the editor used before); live base64 uploads + // arrive through the setImage command, not markdown parsing. + const href = token.href || "" + if (href.startsWith("fider-image:")) { + const imageId = href.substring("fider-image:".length) + return { + type: "customImage", + attrs: { src: `/static/images/${imageId}`, alt: "", id: imageId, bkey: imageId }, + } + } return { - images: {}, - markdown: { - serialize: (state: any, node: any) => { - if (!node.attrs.bkey && !node.attrs.id) { - // Call the default image serializer - return defaultMarkdownSerializer.nodes.image(state, node, node.parent, node.index) - } - - // When serializing to markdown, we use a special syntax: ![](fider-image:bkey) - // Use bkey if available, otherwise fall back to id - const imageId = node.attrs.bkey || node.attrs.id || "" - state.write(`![](fider-image:${imageId})`) - }, - parse: { - setup: (markdownit: MarkdownIt) => { - // Custom rule to parse our special image syntax - markdownit.inline.ruler.before("image", "fider-image", (state: MarkdownIt.StateInline, silent: boolean) => { - const match = state.src.slice(state.pos).match(/^!\[\]\(fider-image:([a-zA-Z0-9_/.-]+)\)/) - if (!match) return false - - if (!silent) { - const imageId = match[1] - const token = state.push("image", "img", 0) - - // Initialize attrs as an empty array first - token.attrs = [] - - let imageSrc = this.options.onGetImageSrc ? this.options.onGetImageSrc(imageId) : "" - if (imageSrc.length === 0) { - imageSrc = `/static/images/${imageId}` - } - - token.attrSet("src", imageSrc) - token.attrSet("alt", "") - token.attrSet("data-id", imageId) - token.attrSet("data-bkey", imageId) - - token.children = [] - token.content = "" - } + type: "customImage", + attrs: { src: href, alt: token.text || "", id: null, bkey: null }, + } + }, - state.pos += match[0].length - return true - }) - }, - }, - }, + renderMarkdown(node: JSONContent): string { + const attrs = node.attrs || {} + if (attrs.bkey || attrs.id) { + // Fider inline-image syntax; use bkey if available, otherwise id. + return `![](fider-image:${attrs.bkey || attrs.id})` } + return `![${attrs.alt || ""}](${attrs.src || ""})` }, // Override the addImage command to include our custom attributes and handle uploads diff --git a/public/components/common/form/CustomMention.ts b/public/components/common/form/CustomMention.ts index 66019cea8e..cf89e31b18 100644 --- a/public/components/common/form/CustomMention.ts +++ b/public/components/common/form/CustomMention.ts @@ -1,49 +1,36 @@ import Mention from "@tiptap/extension-mention" -import * as MarkdownIt from "markdown-it" +import { JSONContent, MarkdownToken } from "@tiptap/core" export const CustomMention = Mention.extend({ name: "mention", - addStorage() { - return { - markdown: { - serialize: (state: any, node: { attrs: { id: string; label: string } }) => { - state.write(`@[${node.attrs.label}]`) - }, - parse: { - setup(markdownit: MarkdownIt) { - markdownit.renderer.rules.mention_open = (tokens, idx) => { - const token = tokens[idx] - const id = token.attrGet("id") - const label = token.attrGet("label") - return `` - } - markdownit.renderer.rules.mention_close = () => { - return "" - } - - markdownit.inline.ruler.before("text", "mention", (state: MarkdownIt.StateInline, silent: boolean) => { - const match = state.src.slice(state.pos).match(/^@\[(.+?)\]/) - if (!match) return false - if (!silent) { - const label = match[1] - const token = state.push("mention_open", "span", 1) - token.attrs = [["label", label]] + // --- @tiptap/markdown integration (marked engine) --- + // @[name] is non-standard markdown, so register a custom marked tokenizer for it. + markdownTokenizer: { + name: "mention", + level: "inline" as const, + start(src: string) { + return src.indexOf("@[") + }, + tokenize(src: string): MarkdownToken | undefined { + const match = /^@\[(.+?)\]/.exec(src) + if (!match) { + return undefined + } + return { type: "mention", raw: match[0], label: match[1] } + }, + }, - // Add the text content token - const contentToken = state.push("text", "", 0) - contentToken.content = `@${label}` + markdownTokenName: "mention", - // Close the mention span - state.push("mention_close", "span", -1) - } + parseMarkdown(token: MarkdownToken): JSONContent { + // Note: `this` inside parseMarkdown is NOT the extension (this.name is undefined), + // so the node type must be a literal. + const label = (token.label as string) || "" + return { type: "mention", attrs: { id: label, label } } + }, - state.pos += match[0].length - return true - }) - }, - }, - }, - } + renderMarkdown(node: JSONContent): string { + return `@[${node.attrs?.label ?? ""}]` }, })