@lowdefy/blocks-tiptap 5.5.1 → 5.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -27,9 +27,70 @@ import suggestion from './suggestion.js';
|
|
|
27
27
|
import useGroupMembersPopover from './useGroupMembersPopover.js';
|
|
28
28
|
import useTiptapMentionState from './useTiptapMentionState.js';
|
|
29
29
|
import './style.module.css';
|
|
30
|
+
// A mention node's `id` attribute holds whatever option the user picked — a
|
|
31
|
+
// plain string, or the option object ({ label, value, tag }) — so the label
|
|
32
|
+
// and the DOM identifier are both derived from it. Content loaded back from
|
|
33
|
+
// saved html arrives with a STRING id (tiptap's parseHTML reads data-id) and
|
|
34
|
+
// the label in `data-label`, which is why both render paths take the node's
|
|
35
|
+
// attributes rather than the id alone.
|
|
30
36
|
function mentionLabel(id) {
|
|
31
37
|
return id?.tag?.title ?? id?.label ?? id;
|
|
32
38
|
}
|
|
39
|
+
// The string that goes into the DOM as data-id. Option objects are commonly
|
|
40
|
+
// { label, value } with the identity inside `value` (a string, or an object
|
|
41
|
+
// with _id / id); fall back to the object's own _id / id. Anything without a
|
|
42
|
+
// scalar identity yields undefined and data-id is omitted — never the
|
|
43
|
+
// "[object Object]" tiptap's stock attribute renderer produces when it
|
|
44
|
+
// stringifies the whole option.
|
|
45
|
+
function scalarId(candidate) {
|
|
46
|
+
if (type.isString(candidate) || type.isNumber(candidate)) return String(candidate);
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
function mentionId(id) {
|
|
50
|
+
if (type.isNone(id)) return undefined;
|
|
51
|
+
if (type.isString(id) || type.isNumber(id)) return String(id);
|
|
52
|
+
if (!type.isObject(id)) return undefined;
|
|
53
|
+
const value = id.value;
|
|
54
|
+
if (type.isObject(value)) {
|
|
55
|
+
return scalarId(value._id) ?? scalarId(value.id) ?? scalarId(value.value);
|
|
56
|
+
}
|
|
57
|
+
return scalarId(value) ?? scalarId(id._id) ?? scalarId(id.id);
|
|
58
|
+
}
|
|
59
|
+
function nodeLabel(attrs) {
|
|
60
|
+
const label = type.isString(attrs.label) && attrs.label !== '' ? attrs.label : undefined;
|
|
61
|
+
const derived = mentionLabel(attrs.id);
|
|
62
|
+
return label ?? (type.isString(derived) || type.isNumber(derived) ? derived : undefined);
|
|
63
|
+
}
|
|
64
|
+
// Mention with its `id` and `label` attributes rendered through the helpers
|
|
65
|
+
// above: data-id is always a scalar identifier (or absent), and data-label
|
|
66
|
+
// always carries the display label so saved html round-trips with a readable
|
|
67
|
+
// mention even though the id comes back as a string.
|
|
68
|
+
const LowdefyMention = Mention.extend({
|
|
69
|
+
addAttributes () {
|
|
70
|
+
const parent = this.parent?.() ?? {};
|
|
71
|
+
return {
|
|
72
|
+
...parent,
|
|
73
|
+
id: {
|
|
74
|
+
...parent.id,
|
|
75
|
+
renderHTML: (attributes)=>{
|
|
76
|
+
const id = mentionId(attributes.id);
|
|
77
|
+
return type.isNone(id) ? {} : {
|
|
78
|
+
'data-id': id
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
label: {
|
|
83
|
+
...parent.label,
|
|
84
|
+
renderHTML: (attributes)=>{
|
|
85
|
+
const label = nodeLabel(attributes);
|
|
86
|
+
return type.isNone(label) ? {} : {
|
|
87
|
+
'data-label': label
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
});
|
|
33
94
|
const TiptapMentionInput = ({ blockId, components: { Icon, Link }, events, loading, methods, properties, required, validation, value })=>{
|
|
34
95
|
const { emit, insertImage } = useTiptapMentionState({
|
|
35
96
|
value,
|
|
@@ -40,13 +101,13 @@ const TiptapMentionInput = ({ blockId, components: { Icon, Link }, events, loadi
|
|
|
40
101
|
const char = properties.mentions?.char ?? '@';
|
|
41
102
|
const allowSpaces = properties.mentions?.allowSpaces !== false;
|
|
42
103
|
const limit = properties.mentions?.limit ?? 5;
|
|
43
|
-
const mentionExtension =
|
|
104
|
+
const mentionExtension = LowdefyMention.configure({
|
|
44
105
|
HTMLAttributes: {
|
|
45
106
|
class: 'tiptap-mention'
|
|
46
107
|
},
|
|
47
108
|
renderHTML ({ options, node }) {
|
|
48
109
|
const id = node.attrs.id;
|
|
49
|
-
const label =
|
|
110
|
+
const label = nodeLabel(node.attrs) ?? '';
|
|
50
111
|
const group = id?.tag?.group;
|
|
51
112
|
const modifier = {};
|
|
52
113
|
if (!type.isNone(group)) {
|
|
@@ -62,8 +123,7 @@ const TiptapMentionInput = ({ blockId, components: { Icon, Link }, events, loadi
|
|
|
62
123
|
return [
|
|
63
124
|
'a',
|
|
64
125
|
mergeAttributes({
|
|
65
|
-
href
|
|
66
|
-
'data-id': id?._id
|
|
126
|
+
href
|
|
67
127
|
}, options.HTMLAttributes, modifier),
|
|
68
128
|
`${char}${label}`
|
|
69
129
|
];
|
|
@@ -75,7 +135,7 @@ const TiptapMentionInput = ({ blockId, components: { Icon, Link }, events, loadi
|
|
|
75
135
|
];
|
|
76
136
|
},
|
|
77
137
|
renderText ({ node }) {
|
|
78
|
-
return `${char}${
|
|
138
|
+
return `${char}${nodeLabel(node.attrs) ?? ''}`;
|
|
79
139
|
},
|
|
80
140
|
suggestion: suggestion({
|
|
81
141
|
methods,
|
|
@@ -43,7 +43,9 @@ function useTiptapMentionState({ value, methods }) {
|
|
|
43
43
|
const mentionsMap = new Map();
|
|
44
44
|
(json.content ?? []).filter((c)=>c.type === 'paragraph').forEach((c)=>{
|
|
45
45
|
c.content?.forEach((cc)=>{
|
|
46
|
-
|
|
46
|
+
// Only freshly picked mentions carry the option object; a node parsed
|
|
47
|
+
// back from saved html has a string id and no `value` to report.
|
|
48
|
+
if (cc.type === 'mention' && cc.attrs.id?.value !== undefined) {
|
|
47
49
|
mentionsMap.set(JSON.stringify(cc.attrs.id.value), cc.attrs.id.value);
|
|
48
50
|
}
|
|
49
51
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lowdefy/blocks-tiptap",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.6.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Lowdefy TipTap rich-text editor blocks.",
|
|
6
6
|
"homepage": "https://lowdefy.com",
|
|
@@ -46,9 +46,9 @@
|
|
|
46
46
|
"dist/*"
|
|
47
47
|
],
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@lowdefy/block-utils": "5.
|
|
50
|
-
"@lowdefy/blocks-antd": "5.
|
|
51
|
-
"@lowdefy/helpers": "5.
|
|
49
|
+
"@lowdefy/block-utils": "5.6.0",
|
|
50
|
+
"@lowdefy/blocks-antd": "5.6.0",
|
|
51
|
+
"@lowdefy/helpers": "5.6.0",
|
|
52
52
|
"@tiptap/core": "2.27.2",
|
|
53
53
|
"@tiptap/extension-bubble-menu": "2.27.2",
|
|
54
54
|
"@tiptap/extension-file-handler": "2.27.2",
|
|
@@ -73,8 +73,8 @@
|
|
|
73
73
|
"react-dom": ">=18"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
|
-
"@lowdefy/block-dev-e2e": "5.
|
|
77
|
-
"@lowdefy/e2e-utils": "5.
|
|
76
|
+
"@lowdefy/block-dev-e2e": "5.6.0",
|
|
77
|
+
"@lowdefy/e2e-utils": "5.6.0",
|
|
78
78
|
"@playwright/test": "1.50.1",
|
|
79
79
|
"@swc/cli": "0.8.0",
|
|
80
80
|
"@swc/core": "1.15.18",
|