mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2026-02-06 09:55:47 +00:00
fix: use @tiptap/markdown API for Hashtag and ItemMention extensions
- Replace addStorage() with markdownTokenizer/parseMarkdown/renderMarkdown - Remove tiptap-markdown from rollup commonjs config - These changes fix serialization of hashtags and mentions to markdown
This commit is contained in:
parent
237c84528b
commit
0ef35df335
@ -44,8 +44,6 @@ export default [
|
||||
commonjs({
|
||||
include: [
|
||||
/node_modules\/attr-accept/,
|
||||
/node_modules\/tiptap-markdown/,
|
||||
/node_modules\/markdown-it-task-lists/,
|
||||
/node_modules\/classnames/,
|
||||
/node_modules\/react-qr-code/,
|
||||
/node_modules\/use-sync-external-store/,
|
||||
|
||||
@ -40,6 +40,45 @@ export const Hashtag = Node.create<HashtagOptions>({
|
||||
}
|
||||
},
|
||||
|
||||
// Markdown tokenizer for @tiptap/markdown - recognizes #hashtag syntax
|
||||
markdownTokenizer: {
|
||||
name: 'hashtag',
|
||||
level: 'inline',
|
||||
// Fast hint for the lexer - where might a hashtag start?
|
||||
start: (src: string) => {
|
||||
// Look for # followed by word characters (but not inside links)
|
||||
const match = /(?<!\[)#[a-zA-Z0-9À-ÖØ-öø-ʸ_-]/.exec(src)
|
||||
return match ? match.index : -1
|
||||
},
|
||||
tokenize: (src: string) => {
|
||||
// Match hashtag: #tagname (not preceded by [)
|
||||
const match = /^#([a-zA-Z0-9À-ÖØ-öø-ʸ_-]+)/.exec(src)
|
||||
if (match) {
|
||||
return {
|
||||
type: 'hashtag',
|
||||
raw: match[0],
|
||||
label: match[1],
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
},
|
||||
},
|
||||
|
||||
// Parse Markdown token to Tiptap JSON
|
||||
parseMarkdown(token: { label: string }) {
|
||||
return {
|
||||
type: 'hashtag',
|
||||
attrs: {
|
||||
label: token.label,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
// Serialize Tiptap node to Markdown
|
||||
renderMarkdown(node: { attrs: { label: string } }) {
|
||||
return `#${node.attrs.label}`
|
||||
},
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
id: {
|
||||
@ -89,20 +128,6 @@ export const Hashtag = Node.create<HashtagOptions>({
|
||||
}
|
||||
},
|
||||
|
||||
addStorage() {
|
||||
return {
|
||||
markdown: {
|
||||
serialize(state: { write: (text: string) => void }, node: { attrs: { label: string } }) {
|
||||
// Write as plain hashtag
|
||||
state.write(`#${node.attrs.label}`)
|
||||
},
|
||||
parse: {
|
||||
// Parsing is handled by preprocessHashtags
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
addNodeView() {
|
||||
return ReactNodeViewRenderer(HashtagComponent)
|
||||
},
|
||||
|
||||
@ -39,6 +39,46 @@ export const ItemMention = Node.create<ItemMentionOptions>({
|
||||
}
|
||||
},
|
||||
|
||||
// Markdown tokenizer for @tiptap/markdown - recognizes [@Label](/item/id) syntax
|
||||
markdownTokenizer: {
|
||||
name: 'itemMention',
|
||||
level: 'inline',
|
||||
// Fast hint for the lexer - where might an item mention start?
|
||||
start: (src: string) => src.indexOf('[@'),
|
||||
tokenize: (src: string) => {
|
||||
// Match [@Label](/item/id) or [@Label](/item/layer/id)
|
||||
// UUID pattern: hex characters (case-insensitive) with dashes
|
||||
// eslint-disable-next-line security/detect-unsafe-regex
|
||||
const match = /^\[@([^\]]+?)\]\(\/item\/(?:[^/]+\/)?([a-fA-F0-9-]+)\)/.exec(src)
|
||||
if (match) {
|
||||
return {
|
||||
type: 'itemMention',
|
||||
raw: match[0],
|
||||
label: match[1],
|
||||
id: match[2],
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
},
|
||||
},
|
||||
|
||||
// Parse Markdown token to Tiptap JSON
|
||||
parseMarkdown(token: { label: string; id: string }) {
|
||||
return {
|
||||
type: 'itemMention',
|
||||
attrs: {
|
||||
label: token.label,
|
||||
id: token.id,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
// Serialize Tiptap node to Markdown
|
||||
renderMarkdown(node: { attrs: { label: string; id: string } }) {
|
||||
const { label, id } = node.attrs
|
||||
return `[@${label}](/item/${id})`
|
||||
},
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
id: {
|
||||
@ -88,24 +128,6 @@ export const ItemMention = Node.create<ItemMentionOptions>({
|
||||
}
|
||||
},
|
||||
|
||||
addStorage() {
|
||||
return {
|
||||
markdown: {
|
||||
serialize(
|
||||
state: { write: (text: string) => void },
|
||||
node: { attrs: { id: string; label: string } },
|
||||
) {
|
||||
// Write as markdown link: [@Label](/item/id)
|
||||
const { id, label } = node.attrs
|
||||
state.write(`[@${label}](/item/${id})`)
|
||||
},
|
||||
parse: {
|
||||
// Parsing is handled by preprocessItemMentions
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
addNodeView() {
|
||||
return ReactNodeViewRenderer(ItemMentionComponent)
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user