@lowdefy/blocks-tiptap 5.5.0 → 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 = Mention.configure({
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 = mentionLabel(id);
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}${mentionLabel(node.attrs.id)}`;
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
- if (cc.type === 'mention') {
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.5.0",
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.5.0",
50
- "@lowdefy/blocks-antd": "5.5.0",
51
- "@lowdefy/helpers": "5.5.0",
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.5.0",
77
- "@lowdefy/e2e-utils": "5.5.0",
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",