@livepreso/react-plugin-textfield 0.2.0 → 0.2.2

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.
package/CHANGELOG.json CHANGED
@@ -1,6 +1,33 @@
1
1
  {
2
2
  "name": "@livepreso/react-plugin-textfield",
3
3
  "entries": [
4
+ {
5
+ "version": "0.2.2",
6
+ "tag": "@livepreso/react-plugin-textfield_v0.2.2",
7
+ "date": "Tue, 25 Nov 2025 00:07:07 GMT",
8
+ "comments": {
9
+ "patch": [
10
+ {
11
+ "comment": "Change reserveWhitespace to \"true\" to keep token's trailing whitespace, but trim whitespace between paragraphs in Strategy Recs. Add contenteditable: false to Token when not burst. Add leading space to @ when using the token button (when required)."
12
+ }
13
+ ]
14
+ }
15
+ },
16
+ {
17
+ "version": "0.2.1",
18
+ "tag": "@livepreso/react-plugin-textfield_v0.2.1",
19
+ "date": "Sun, 23 Nov 2025 23:46:44 GMT",
20
+ "comments": {
21
+ "patch": [
22
+ {
23
+ "comment": "Burst all tokens when opening saved token state into a burst editable state."
24
+ },
25
+ {
26
+ "comment": "Render token option before clear_formatting in simple toolbar config spec"
27
+ }
28
+ ]
29
+ }
30
+ },
4
31
  {
5
32
  "version": "0.2.0",
6
33
  "tag": "@livepreso/react-plugin-textfield_v0.2.0",
package/CHANGELOG.md CHANGED
@@ -1,6 +1,21 @@
1
1
  # Change Log - @livepreso/react-plugin-textfield
2
2
 
3
- This log was last generated on Wed, 19 Nov 2025 03:51:23 GMT and should not be manually modified.
3
+ This log was last generated on Tue, 25 Nov 2025 00:07:07 GMT and should not be manually modified.
4
+
5
+ ## 0.2.2
6
+ Tue, 25 Nov 2025 00:07:07 GMT
7
+
8
+ ### Patches
9
+
10
+ - Change reserveWhitespace to "true" to keep token's trailing whitespace, but trim whitespace between paragraphs in Strategy Recs. Add contenteditable: false to Token when not burst. Add leading space to @ when using the token button (when required).
11
+
12
+ ## 0.2.1
13
+ Sun, 23 Nov 2025 23:46:44 GMT
14
+
15
+ ### Patches
16
+
17
+ - Burst all tokens when opening saved token state into a burst editable state.
18
+ - Render token option before clear_formatting in simple toolbar config spec
4
19
 
5
20
  ## 0.2.0
6
21
  Wed, 19 Nov 2025 03:51:23 GMT
@@ -3,8 +3,47 @@ import style from "./Token.module.scss";
3
3
  import classNames from "classnames";
4
4
  import { generateSuggestionConfiguration, TOKEN_TRIGGER_CHAR } from "./utils";
5
5
  import { mergeAttributes } from "@tiptap/core";
6
+ import { NODE_TYPES } from "../../../constants";
6
7
 
7
- const TYPE_TOKEN = "token";
8
+ export const convertTokensToTextNodes = (editor, options) => {
9
+ const nodesToReplace = [];
10
+
11
+ // Using transaction instead of run() so all changes
12
+ // are made before application. When I did everything one
13
+ // run() after the other, replacements got jumbled as the
14
+ // positions changed after each replacement.
15
+ let tr = editor.state.tr;
16
+
17
+ editor.state.doc.descendants((node, pos) => {
18
+ if (node.type.name === NODE_TYPES.TOKEN) {
19
+ nodesToReplace.push({ node, pos });
20
+ }
21
+ });
22
+
23
+ // Sort by position to avoid position shifts
24
+ nodesToReplace.sort((a, b) => b.pos - a.pos);
25
+
26
+ nodesToReplace.forEach(({ node, pos }) => {
27
+ const { render: tokenRender = () => "" } =
28
+ options.tokens.find((token) => token.id === node.attrs.id) || {};
29
+
30
+ const tokenSummary = `${TOKEN_TRIGGER_CHAR}${
31
+ node.attrs.label ?? node.attrs.id
32
+ }`;
33
+
34
+ const content =
35
+ options.isEditable && options.usePlaceholder
36
+ ? tokenSummary
37
+ : tokenRender();
38
+
39
+ const endPos = pos + editor.state.doc.nodeAt(pos).nodeSize;
40
+ const textNode = editor.state.schema.text(content);
41
+
42
+ tr.replaceWith(pos, endPos, textNode);
43
+ });
44
+
45
+ editor.view.dispatch(tr);
46
+ };
8
47
 
9
48
  const TokenBase = Mention.extend({
10
49
  name: "token",
@@ -62,7 +101,8 @@ const TokenBase = Mention.extend({
62
101
  : render();
63
102
 
64
103
  const editableNode = document.createElement("span");
65
- editableNode.setAttribute("contenteditable", true);
104
+ editableNode.setAttribute("contenteditable", options.fieldIsEditable);
105
+ tokenDOM.setAttribute("contenteditable", false);
66
106
 
67
107
  const dom = options.burstOnCreate ? editableNode : tokenDOM;
68
108
  const contentDOM = dom.appendChild(document.createTextNode(content));
@@ -79,7 +119,7 @@ const TokenBase = Mention.extend({
79
119
  .focus()
80
120
  .setNodeSelection(pos)
81
121
  .insertContentAt({ from: pos, to: endPos }, content)
82
- .toggleNode(TYPE_TOKEN, "text")
122
+ .toggleNode(NODE_TYPES.TOKEN, "text")
83
123
  .run();
84
124
  };
85
125
 
@@ -93,8 +133,11 @@ const TokenBase = Mention.extend({
93
133
 
94
134
  if (options.burstOnCreate) {
95
135
  // this will be run, once, after the node is created
96
- // after which, the Token will no longer exist
97
- setTimeout(() => convertToTextNode(), 50);
136
+ // after which, the Token will no longer exist.
137
+ // Only run if currently editing and adding tokens (ie. not on editor load)
138
+ if (editor.isFocused) {
139
+ setTimeout(() => convertToTextNode(), 50);
140
+ }
98
141
  }
99
142
  return {
100
143
  dom,
@@ -293,8 +293,27 @@ const insertTokenButton = ({ tokens = [] }) =>
293
293
  icon: <icons.WandStars />,
294
294
  // Token extension is always enabled, so nothing to load here
295
295
  extensions: [],
296
- command: (editor) =>
297
- editor.chain().focus().insertContent(TOKEN_TRIGGER_CHAR).run(),
296
+ command: (editor) => {
297
+ const { $from } = editor.view.state.selection;
298
+ const prevPos = $from.pos - 1;
299
+
300
+ let tokenTriggerStr = TOKEN_TRIGGER_CHAR;
301
+
302
+ // Add a space before the token trigger if necessary.
303
+ // Without a leading space the token functionality won't run.
304
+ if (prevPos && editor.view.state.doc.nodeAt(prevPos)) {
305
+ const charBefore = editor.view.state.doc.textBetween(
306
+ prevPos,
307
+ $from.pos,
308
+ );
309
+
310
+ if (charBefore !== " ") {
311
+ tokenTriggerStr = ` ${TOKEN_TRIGGER_CHAR}`;
312
+ }
313
+ }
314
+
315
+ return editor.chain().focus().insertContent(tokenTriggerStr).run();
316
+ },
298
317
  }
299
318
  : null;
300
319
 
package/constants.js CHANGED
@@ -9,6 +9,10 @@ export const TOOLBAR_COMPONENT_TYPES = Object.freeze({
9
9
  CUSTOM: "custom",
10
10
  });
11
11
 
12
+ export const NODE_TYPES = Object.freeze({
13
+ TOKEN: "token",
14
+ });
15
+
12
16
  /**
13
17
  * What a toolbar option can affect, and where they will appear in the editor.
14
18
  *
@@ -195,8 +199,8 @@ export const MINIMAL_TOOLBAR_CONFIG = [UNDO_REDO];
195
199
  export const SIMPLE_TOOLBAR_CONFIG = [
196
200
  UNDO_REDO,
197
201
  TEXT_STYLE,
198
- CLEAR_FORMATTING,
199
202
  TOKEN,
203
+ CLEAR_FORMATTING,
200
204
  ];
201
205
 
202
206
  export const SIMPLE_TABLE_TOOLBAR_CONFIG = [
package/index.js CHANGED
@@ -41,7 +41,10 @@ import { TableCellMenu } from "./components/TableCellMenu.js";
41
41
  import style from "./index.module.scss";
42
42
  import { generateExtensionsFromTag } from "./utils/generateCustomExtensions.js";
43
43
  import debounce from "lodash.debounce";
44
- import { generateTokenInstance } from "./components/tiptap/token/Token.js";
44
+ import {
45
+ convertTokensToTextNodes,
46
+ generateTokenInstance,
47
+ } from "./components/tiptap/token/Token.js";
45
48
  import Fuse from "fuse.js";
46
49
 
47
50
  function EditableTextField({
@@ -87,17 +90,21 @@ function EditableTextField({
87
90
  return new Fuse(tokens, { keys: ["label"] });
88
91
  }, [tokens]);
89
92
 
93
+ const tokenOptions = {
94
+ tokens,
95
+ mode,
96
+ isEditable,
97
+ usePlaceholder: isReallyPresoManager,
98
+ ...tokenConfig,
99
+ };
100
+
90
101
  const baseExtensions = [
91
102
  Text,
92
103
  Paragraph,
93
104
  HardBreak,
94
105
  generateTokenInstance({
95
- tokens,
96
- mode,
97
- isEditable,
98
- usePlaceholder: isReallyPresoManager,
106
+ ...tokenOptions,
99
107
  fuse: fuseInstance,
100
- ...tokenConfig,
101
108
  }),
102
109
  ].filter(Boolean);
103
110
 
@@ -141,10 +148,16 @@ function EditableTextField({
141
148
  ],
142
149
  editable: isEditable,
143
150
  autofocus: false,
151
+ onCreate: ({ editor }) => {
152
+ // Burst any saved tokens (eg. added in PresoManager, editable in present)
153
+ if (tokenOptions.burstOnCreate) {
154
+ convertTokensToTextNodes(editor, tokenOptions);
155
+ }
156
+ },
144
157
  onUpdate: debounce(({ editor }) => {
145
158
  onChange?.(editor.getHTML());
146
159
  }, 100),
147
- parseOptions: { preserveWhitespace: "full" },
160
+ parseOptions: { preserveWhitespace: true },
148
161
  });
149
162
 
150
163
  const { refs } = useFloating({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livepreso/react-plugin-textfield",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "publishConfig": {