@livepreso/react-plugin-textfield 0.1.2 → 0.2.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.
@@ -0,0 +1,93 @@
1
+ import React, {
2
+ forwardRef,
3
+ useEffect,
4
+ useImperativeHandle,
5
+ useState,
6
+ } from "react";
7
+ import classNames from "classnames";
8
+ import style from "./TokenList.module.scss";
9
+ import buttonStyle from "../../editor-toolbars/SimpleButton.module.scss";
10
+ import InsertTokenDialog from "../../InsertTokenDialog";
11
+ import { Separator } from "radix-ui";
12
+
13
+ // the UI associated with the suggestion dropdown
14
+ export const TokenList = forwardRef(
15
+ ({ items, choices, command }, forwardedRef) => {
16
+ const [selectedIndex, setSelectedIndex] = useState(0);
17
+
18
+ const selectItem = (index) => {
19
+ const item = items[index];
20
+
21
+ if (item) {
22
+ command({ id: item.id, label: item.label });
23
+ }
24
+ };
25
+
26
+ const upHandler = () => {
27
+ setSelectedIndex((selectedIndex + items.length - 1) % items.length);
28
+ };
29
+
30
+ const downHandler = () => {
31
+ setSelectedIndex((selectedIndex + 1) % items.length);
32
+ };
33
+
34
+ const enterHandler = () => {
35
+ selectItem(selectedIndex);
36
+ };
37
+
38
+ useEffect(() => setSelectedIndex(0), [items]);
39
+
40
+ useImperativeHandle(forwardedRef, () => ({
41
+ onKeyDown: ({ event }) => {
42
+ if (event.key === "ArrowUp") {
43
+ upHandler();
44
+ return true;
45
+ }
46
+
47
+ if (event.key === "ArrowDown") {
48
+ downHandler();
49
+ return true;
50
+ }
51
+
52
+ if (event.key === "Enter") {
53
+ enterHandler();
54
+ return true;
55
+ }
56
+ return false;
57
+ },
58
+ }));
59
+
60
+ return (
61
+ <div
62
+ className={classNames(style.root, "interactive")}
63
+ data-companywide-interactive
64
+ ref={forwardedRef}
65
+ >
66
+ <div className={style.tokenPopup}>
67
+ <div className={style.dropdownMenu}>
68
+ {items.length ? (
69
+ items.map(({ id, label }, index) => (
70
+ <button
71
+ className={classNames(buttonStyle.button, style.suggestion, {
72
+ [style.isSelected]: index === selectedIndex,
73
+ })}
74
+ key={id}
75
+ onClick={() => selectItem(index)}
76
+ >
77
+ {label}
78
+ </button>
79
+ ))
80
+ ) : (
81
+ <div className={style.item}>No result</div>
82
+ )}
83
+ </div>
84
+ <Separator.Root className={style.separator} />
85
+ <InsertTokenDialog
86
+ tokens={choices}
87
+ onChange={(token) => command(token)}
88
+ />
89
+ </div>
90
+ </div>
91
+ );
92
+ },
93
+ );
@@ -0,0 +1,58 @@
1
+ @import "../../style.module.scss";
2
+
3
+ :global(.sp-presenter-overlay) {
4
+ .root {
5
+ @extend %root;
6
+
7
+ .token-popup {
8
+ z-index: 1000;
9
+ background: var(--white);
10
+ display: flex;
11
+ flex-direction: column;
12
+ }
13
+
14
+ /* Dropdown menu */
15
+ .dropdown-menu {
16
+ display: flex;
17
+ flex-direction: column;
18
+ overflow: auto;
19
+ position: relative;
20
+ }
21
+
22
+ .separator {
23
+ @extend %separator-horizontal;
24
+ flex: none;
25
+ }
26
+
27
+ .button {
28
+ @extend %item-vertical;
29
+ align-items: center;
30
+ background-color: transparent;
31
+ display: flex;
32
+ text-align: left;
33
+ width: 100%;
34
+ border-radius: none;
35
+
36
+ &:first-child {
37
+ padding-top: 8px;
38
+ }
39
+
40
+ &:last-child {
41
+ padding-bottom: 8px;
42
+ }
43
+ }
44
+
45
+ .suggestion {
46
+ padding-left: 12px;
47
+
48
+ &:hover,
49
+ &:hover.is-selected {
50
+ background-color: var(--sp-toolbar-hover-color);
51
+ }
52
+
53
+ &.is-selected {
54
+ background-color: var(--sp-toolbar-hover-color);
55
+ }
56
+ }
57
+ }
58
+ }
@@ -0,0 +1,92 @@
1
+ // modified from http://github.com/ueberdosis/tiptap/blob/main/demos/src/Nodes/Mention/React/suggestion.js
2
+ // the output of the function is a configuration object for the [Suggestion engine](https://tiptap.dev/docs/editor/api/utilities/suggestion)
3
+ // that Tiptap uses to drive the mention / Token plugin's output
4
+
5
+ import { computePosition, flip, shift } from "@floating-ui/react-dom";
6
+ import { posToDOMRect, ReactRenderer } from "@tiptap/react";
7
+ import { TokenList } from "./TokenList";
8
+
9
+ export const TOKEN_TRIGGER_CHAR = "@";
10
+
11
+ const updatePosition = (editor, element) => {
12
+ const virtualElement = {
13
+ getBoundingClientRect: () =>
14
+ posToDOMRect(
15
+ editor.view,
16
+ editor.state.selection.from,
17
+ editor.state.selection.to,
18
+ ),
19
+ };
20
+
21
+ computePosition(virtualElement, element, {
22
+ placement: "bottom-start",
23
+ strategy: "absolute",
24
+ middleware: [shift(), flip()],
25
+ }).then(({ x, y, strategy }) => {
26
+ element.style.width = "max-content";
27
+ element.style.position = strategy;
28
+ element.style.left = `${x}px`;
29
+ element.style.top = `${y}px`;
30
+ });
31
+ };
32
+
33
+ // TODO wrap this in a useMemo hook or similar to avoid unnecessary instantiations of Fuse
34
+ export function generateSuggestionConfiguration({ tokens = [], fuse }) {
35
+ return {
36
+ char: TOKEN_TRIGGER_CHAR,
37
+ items: ({ query }) => {
38
+ if (query === "") return tokens.slice(0, 5);
39
+ return fuse.search(query, { limit: 5 }).map((r) => r.item);
40
+ },
41
+ // this is a cached value for use in
42
+ choices: tokens,
43
+ // Disable UI if editor is not in editable mode or another condition is met
44
+ allow: ({ editor, state, range }) => editor.isEditable && tokens.length > 0,
45
+ render: () => {
46
+ let component;
47
+ return {
48
+ onStart: (props) => {
49
+ component = new ReactRenderer(TokenList, {
50
+ props: { ...props, choices: tokens },
51
+ editor: props.editor,
52
+ });
53
+
54
+ if (!props.clientRect) {
55
+ return;
56
+ }
57
+
58
+ component.element.style.position = "absolute";
59
+
60
+ window.Bridge.UI.overlay.appendChild(component.element);
61
+
62
+ updatePosition(props.editor, component.element);
63
+ },
64
+
65
+ onUpdate(props) {
66
+ component.updateProps(props);
67
+
68
+ if (!props.clientRect) {
69
+ return;
70
+ }
71
+
72
+ updatePosition(props.editor, component.element);
73
+ },
74
+
75
+ onKeyDown(props) {
76
+ if (props.event.key === "Escape") {
77
+ component.destroy();
78
+
79
+ return true;
80
+ }
81
+
82
+ return component.ref?.onKeyDown(props);
83
+ },
84
+
85
+ onExit() {
86
+ component.element.remove();
87
+ component.destroy();
88
+ },
89
+ };
90
+ },
91
+ };
92
+ }
@@ -4,13 +4,23 @@ import {
4
4
  TABLE_TOOLBAR_IDS,
5
5
  } from "../constants";
6
6
 
7
- const generateGroup = (id, { label = null, items = [], options = {} } = {}) => {
7
+ const generateGroup = (
8
+ id,
9
+ { label = null, items: itemsConfig = [], options = {} } = {},
10
+ ) => {
11
+ // a given generator can return null
12
+ // if the item will not funciton with the supplied options
13
+ const items = itemsConfig
14
+ .map((generator) => generator(options))
15
+ .filter(Boolean);
16
+
17
+ // if, having run the generators, the group has no items, return null
8
18
  if (items.length === 0) return null;
9
19
 
10
20
  return {
11
21
  id,
12
22
  label,
13
- items: items.map((generator) => generator(options)),
23
+ items,
14
24
  };
15
25
  };
16
26
 
@@ -75,10 +85,12 @@ export const generateToolbarConfiguration = ({
75
85
  itemGenerators = {},
76
86
  textStyles = [],
77
87
  colors = [],
88
+ tokens = [],
78
89
  } = {}) => {
79
90
  const options = {
80
91
  textStyles,
81
92
  colors,
93
+ tokens,
82
94
  };
83
95
 
84
96
  const generateItem = (item) => {
@@ -1,6 +1,6 @@
1
1
  import { tableItemGenerators } from "./table-toolbar-configuration";
2
2
  import { generateToolbarConfiguration } from "./generate-toolbar-configuration";
3
- import { TABLE, TOOLBAR_SCOPES } from "../constants";
3
+ import { TABLE, TOKEN, TOOLBAR_SCOPES } from "../constants";
4
4
  import { primaryItemGenerators } from "./toolbar-configuration";
5
5
 
6
6
  const testUniqueIds = (options) => {
@@ -26,9 +26,11 @@ export const generateToolbarOptions = ({
26
26
  tableToolbarConfig = [],
27
27
  textStyles = [],
28
28
  colors = [],
29
+ tokens = [],
29
30
  } = {}) => {
30
31
  const includeTableEditor = toolbarConfig.includes(TABLE);
31
- const options = { textStyles, colors };
32
+ const includeToken = toolbarConfig.includes(TOKEN) && tokens.length > 0;
33
+ const options = { textStyles, colors, tokens: includeToken ? tokens : [] };
32
34
 
33
35
  const primaryOptions = generateToolbarConfiguration({
34
36
  toolbar: toolbarConfig,
@@ -23,6 +23,7 @@ import LinkEditDialog from "../components/LinkEditDialog";
23
23
  import { Gapcursor } from "@tiptap/extensions";
24
24
  import { ColorPickerCombo } from "../components/color-picker/ColorPickerCombo";
25
25
  import { TableCreatorCombo } from "../components/table-creator/TableCreatorCombo";
26
+ import { TOKEN_TRIGGER_CHAR } from "../components/tiptap/token/utils";
26
27
 
27
28
  function generateTextStyleTest(textStyles) {
28
29
  return (editor) => {
@@ -283,6 +284,19 @@ const removeLinkButton = () => ({
283
284
  enabled: (editor) => editor.isActive("link"),
284
285
  icon: <icons.LinkOff />,
285
286
  });
287
+ const insertTokenButton = ({ tokens = [] }) =>
288
+ tokens.length
289
+ ? {
290
+ id: PRIMARY_TOOLBAR_IDS.INSERT_TOKEN,
291
+ label: "Insert token",
292
+ type: TOOLBAR_COMPONENT_TYPES.BUTTON,
293
+ icon: <icons.WandStars />,
294
+ // Token extension is always enabled, so nothing to load here
295
+ extensions: [],
296
+ command: (editor) =>
297
+ editor.chain().focus().insertContent(TOKEN_TRIGGER_CHAR).run(),
298
+ }
299
+ : null;
286
300
 
287
301
  const clearFormattingButton = () => ({
288
302
  id: PRIMARY_TOOLBAR_IDS.CLEAR_FORMATTING,
@@ -333,6 +347,7 @@ export const primaryItemGenerators = {
333
347
  [PRIMARY_TOOLBAR_IDS.ORDERED_LIST]: numberedListButton,
334
348
  [PRIMARY_TOOLBAR_IDS.INSERT_LINK]: addLinkButton,
335
349
  [PRIMARY_TOOLBAR_IDS.REMOVE_LINK]: removeLinkButton,
350
+ [PRIMARY_TOOLBAR_IDS.INSERT_TOKEN]: insertTokenButton,
336
351
  [PRIMARY_TOOLBAR_IDS.CLEAR_FORMATTING]: clearFormattingButton,
337
352
  [PRIMARY_TOOLBAR_IDS.INSERT_TABLE]: insertTableButton,
338
353
  };
package/constants.js CHANGED
@@ -30,6 +30,7 @@ export const BLOCK = "block";
30
30
  export const TEXT_STYLE = "text-style";
31
31
  export const ALIGNMENT = "alignment";
32
32
  export const LINK = "link";
33
+ export const TOKEN = "token";
33
34
  export const LIST = "list";
34
35
  export const CLEAR_FORMATTING = "clear-formatting";
35
36
  export const TABLE = "table";
@@ -48,6 +49,7 @@ export const PRIMARY_TOOLBAR_IDS = Object.freeze({
48
49
  TEXT_ALIGN: "textAlign",
49
50
  INSERT_LINK: "insertLink",
50
51
  REMOVE_LINK: "unlink",
52
+ INSERT_TOKEN: "insertToken",
51
53
  ORDERED_LIST: "orderedList",
52
54
  UNORDERED_LIST: "unorderedList",
53
55
  INSERT_TABLE: "insertTable",
@@ -107,6 +109,10 @@ const PRIMARY_TOOLBAR_GROUPS = Object.freeze({
107
109
  label: "Links",
108
110
  items: [PRIMARY_TOOLBAR_IDS.INSERT_LINK, PRIMARY_TOOLBAR_IDS.REMOVE_LINK],
109
111
  },
112
+ [TOKEN]: {
113
+ label: "Tokens",
114
+ items: [PRIMARY_TOOLBAR_IDS.INSERT_TOKEN],
115
+ },
110
116
  [TABLE]: { label: "Insert table", items: [PRIMARY_TOOLBAR_IDS.INSERT_TABLE] },
111
117
  [CLEAR_FORMATTING]: {
112
118
  label: "Clear formatting",
@@ -170,6 +176,7 @@ export const COMPLETE_TOOLBAR_CONFIG = [
170
176
  TEXT_STYLE,
171
177
  ALIGNMENT,
172
178
  LINK,
179
+ TOKEN,
173
180
  TABLE,
174
181
  CLEAR_FORMATTING,
175
182
  ];
@@ -185,7 +192,12 @@ export const DEFAULT_TABLE_TOOLBAR_CONFIG = [...COMPLETE_TABLE_TOOLBAR_CONFIG];
185
192
 
186
193
  // Alternative toolbar configurations
187
194
  export const MINIMAL_TOOLBAR_CONFIG = [UNDO_REDO];
188
- export const SIMPLE_TOOLBAR_CONFIG = [UNDO_REDO, TEXT_STYLE, CLEAR_FORMATTING];
195
+ export const SIMPLE_TOOLBAR_CONFIG = [
196
+ UNDO_REDO,
197
+ TEXT_STYLE,
198
+ CLEAR_FORMATTING,
199
+ TOKEN,
200
+ ];
189
201
 
190
202
  export const SIMPLE_TABLE_TOOLBAR_CONFIG = [
191
203
  DELETE_TABLE,
@@ -196,3 +208,9 @@ export const SIMPLE_TABLE_TOOLBAR_CONFIG = [
196
208
  TABLE_TOOLBAR_IDS.REMOVE_ROW,
197
209
  ],
198
210
  ];
211
+
212
+ // these are passed to the editable so it knows what mode it is in -
213
+ // useful for plugins that behave differently in different modes
214
+ export const MODE_CMS = "cms";
215
+ export const MODE_PREP = "prep";
216
+ export const MODE_UNMANAGED = "unmanaged";
@@ -0,0 +1,13 @@
1
+ import * as React from "react";
2
+ const HelpCenter = (props) => (
3
+ <svg
4
+ xmlns="http://www.w3.org/2000/svg"
5
+ height="24px"
6
+ viewBox="0 -960 960 960"
7
+ width="24px"
8
+ {...props}
9
+ >
10
+ <path d="M480.77-257.31q17.15 0 28.96-11.81 11.81-11.8 11.81-28.96 0-17.15-11.81-28.96-11.81-11.81-28.96-11.81t-28.96 11.81Q440-315.23 440-298.08q0 17.16 11.81 28.96 11.81 11.81 28.96 11.81Zm-28.31-143.23h56.31q.77-31.38 8.96-47.81 8.19-16.42 33.04-41.27 31.92-31.92 45.65-54.26 13.73-22.35 13.73-50.81 0-49.92-34.07-80.5-34.08-30.58-87.77-30.58-48.85 0-83.5 24.89-34.66 24.88-49.27 64.8l51.38 20.62q8.54-25.46 28.39-41.58 19.84-16.11 49.77-16.11 32.38 0 50.57 17.57 18.2 17.58 18.2 42.73 0 18.54-11 36.39t-33.93 38.54q-32.61 29.38-44.53 54.54-11.93 25.15-11.93 62.84ZM140-140v-680h680v680H140Zm60-60h560v-560H200v560Zm0 0v-560 560Z" />
11
+ </svg>
12
+ );
13
+ export default HelpCenter;
@@ -0,0 +1,13 @@
1
+ import * as React from "react";
2
+ const MoreVert = (props) => (
3
+ <svg
4
+ xmlns="http://www.w3.org/2000/svg"
5
+ height="24px"
6
+ viewBox="0 -960 960 960"
7
+ width="24px"
8
+ {...props}
9
+ >
10
+ <path d="M480-189.23q-24.75 0-42.37-17.63Q420-224.48 420-249.23q0-24.75 17.63-42.38 17.62-17.62 42.37-17.62 24.75 0 42.37 17.62Q540-273.98 540-249.23q0 24.75-17.63 42.37-17.62 17.63-42.37 17.63ZM480-420q-24.75 0-42.37-17.63Q420-455.25 420-480q0-24.75 17.63-42.37Q455.25-540 480-540q24.75 0 42.37 17.63Q540-504.75 540-480q0 24.75-17.63 42.37Q504.75-420 480-420Zm0-230.77q-24.75 0-42.37-17.62Q420-686.02 420-710.77q0-24.75 17.63-42.37 17.62-17.63 42.37-17.63 24.75 0 42.37 17.63Q540-735.52 540-710.77q0 24.75-17.63 42.38-17.62 17.62-42.37 17.62Z" />
11
+ </svg>
12
+ );
13
+ export default MoreVert;
@@ -0,0 +1,13 @@
1
+ import * as React from "react";
2
+ const WandStars = (props) => (
3
+ <svg
4
+ xmlns="http://www.w3.org/2000/svg"
5
+ height="24px"
6
+ viewBox="0 -960 960 960"
7
+ width="24px"
8
+ {...props}
9
+ >
10
+ <path d="M182.15-140 140-182.15 445.23-488l-174.84-43.08L453-644.46 437.16-860l164.76 139.08L801-802l-80.08 199.69L860-438.16 644.46-453l-114 182.61L487-445.23 182.15-140Zm23.62-523.85L140-729.62l65.77-65.76 65.77 65.76-65.77 65.77ZM555-422.61l57.23-93.24 109.54 8.16L651-591.77l41.16-101.39L590.77-652l-84.08-70.16 8.16 108.93L422-555.38l106.15 26.23L555-422.61ZM729.62-140l-65.77-65.77 65.77-65.77 65.76 65.77L729.62-140ZM572.08-572.69Z" />
11
+ </svg>
12
+ );
13
+ export default WandStars;
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480.77-257.31q17.15 0 28.96-11.81 11.81-11.8 11.81-28.96 0-17.15-11.81-28.96-11.81-11.81-28.96-11.81t-28.96 11.81Q440-315.23 440-298.08q0 17.16 11.81 28.96 11.81 11.81 28.96 11.81Zm-28.31-143.23h56.31q.77-31.38 8.96-47.81 8.19-16.42 33.04-41.27 31.92-31.92 45.65-54.26 13.73-22.35 13.73-50.81 0-49.92-34.07-80.5-34.08-30.58-87.77-30.58-48.85 0-83.5 24.89-34.66 24.88-49.27 64.8l51.38 20.62q8.54-25.46 28.39-41.58 19.84-16.11 49.77-16.11 32.38 0 50.57 17.57 18.2 17.58 18.2 42.73 0 18.54-11 36.39t-33.93 38.54q-32.61 29.38-44.53 54.54-11.93 25.15-11.93 62.84ZM140-140v-680h680v680H140Zm60-60h560v-560H200v560Zm0 0v-560 560Z"/></svg>
package/icons/index.js CHANGED
@@ -41,12 +41,14 @@ import FormatListBulleted from "./FormatListBulleted.js";
41
41
  import FormatListNumbered from "./FormatListNumbered.js";
42
42
  import FormatStrikethrough from "./FormatStrikethrough.js";
43
43
  import FormatUnderlined from "./FormatUnderlined.js";
44
+ import HelpCenter from "./HelpCenter.js";
44
45
  import HorizontalRule from "./HorizontalRule.js";
45
46
  import Italic from "./Italic.js";
46
47
  import ItalicIcon from "./ItalicIcon.js";
47
48
  import Link from "./Link.js";
48
49
  import LinkOff from "./LinkOff.js";
49
50
  import MergeCells from "./MergeCells.js";
51
+ import MoreVert from "./MoreVert.js";
50
52
  import Redo from "./Redo.js";
51
53
  import RemoveColumnOutline from "./RemoveColumnOutline.js";
52
54
  import RemoveRowOutline from "./RemoveRowOutline.js";
@@ -59,6 +61,7 @@ import Undo from "./Undo.js";
59
61
  import VerticalAlignBottom from "./VerticalAlignBottom.js";
60
62
  import VerticalAlignCenter from "./VerticalAlignCenter.js";
61
63
  import VerticalAlignTop from "./VerticalAlignTop.js";
64
+ import WandStars from "./WandStars.js";
62
65
 
63
66
  export const MATERIAL_SVG_PARAMS = {
64
67
  style: "sharp",
@@ -101,19 +104,20 @@ export {
101
104
  FormatBold,
102
105
  FormatClear,
103
106
  FormatColorFill,
104
- FormatColorText,
105
107
  FormatItalic,
106
108
  FormatLineSpacing,
107
109
  FormatListBulleted,
108
110
  FormatListNumbered,
109
111
  FormatStrikethrough,
110
112
  FormatUnderlined,
113
+ HelpCenter,
111
114
  HorizontalRule,
112
115
  Italic,
113
116
  ItalicIcon,
114
117
  Link,
115
118
  LinkOff,
116
119
  MergeCells,
120
+ MoreVert,
117
121
  Redo,
118
122
  RemoveColumnOutline,
119
123
  RemoveRowOutline,
@@ -126,6 +130,7 @@ export {
126
130
  VerticalAlignBottom,
127
131
  VerticalAlignCenter,
128
132
  VerticalAlignTop,
133
+ WandStars,
129
134
  };
130
135
 
131
136
  export default {
@@ -170,12 +175,14 @@ export default {
170
175
  FormatListNumbered,
171
176
  FormatStrikethrough,
172
177
  FormatUnderlined,
178
+ HelpCenter,
173
179
  HorizontalRule,
174
180
  Italic,
175
181
  ItalicIcon,
176
182
  Link,
177
183
  LinkOff,
178
184
  MergeCells,
185
+ MoreVert,
179
186
  Redo,
180
187
  RemoveColumnOutline,
181
188
  RemoveRowOutline,
@@ -188,4 +195,5 @@ export default {
188
195
  VerticalAlignBottom,
189
196
  VerticalAlignCenter,
190
197
  VerticalAlignTop,
198
+ WandStars,
191
199
  };
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480-189.23q-24.75 0-42.37-17.63Q420-224.48 420-249.23q0-24.75 17.63-42.38 17.62-17.62 42.37-17.62 24.75 0 42.37 17.62Q540-273.98 540-249.23q0 24.75-17.63 42.37-17.62 17.63-42.37 17.63ZM480-420q-24.75 0-42.37-17.63Q420-455.25 420-480q0-24.75 17.63-42.37Q455.25-540 480-540q24.75 0 42.37 17.63Q540-504.75 540-480q0 24.75-17.63 42.37Q504.75-420 480-420Zm0-230.77q-24.75 0-42.37-17.62Q420-686.02 420-710.77q0-24.75 17.63-42.37 17.62-17.63 42.37-17.63 24.75 0 42.37 17.63Q540-735.52 540-710.77q0 24.75-17.63 42.38-17.62 17.62-42.37 17.62Z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M182.15-140 140-182.15 445.23-488l-174.84-43.08L453-644.46 437.16-860l164.76 139.08L801-802l-80.08 199.69L860-438.16 644.46-453l-114 182.61L487-445.23 182.15-140Zm23.62-523.85L140-729.62l65.77-65.76 65.77 65.76-65.77 65.77ZM555-422.61l57.23-93.24 109.54 8.16L651-591.77l41.16-101.39L590.77-652l-84.08-70.16 8.16 108.93L422-555.38l106.15 26.23L555-422.61ZM729.62-140l-65.77-65.77 65.77-65.77 65.76 65.77L729.62-140ZM572.08-572.69Z"/></svg>
package/index.js CHANGED
@@ -33,11 +33,16 @@ import {
33
33
  DEFAULT_TOOLBAR_CONFIG,
34
34
  DEFAULT_TABLE_TOOLBAR_CONFIG,
35
35
  EDITABLE_OUTER_SELECTOR,
36
+ MODE_PREP,
37
+ MODE_CMS,
38
+ MODE_UNMANAGED,
36
39
  } from "./constants.js";
37
40
  import { TableCellMenu } from "./components/TableCellMenu.js";
38
41
  import style from "./index.module.scss";
39
42
  import { generateExtensionsFromTag } from "./utils/generateCustomExtensions.js";
40
43
  import debounce from "lodash.debounce";
44
+ import { generateTokenInstance } from "./components/tiptap/token/Token.js";
45
+ import Fuse from "fuse.js";
41
46
 
42
47
  function EditableTextField({
43
48
  value,
@@ -46,6 +51,7 @@ function EditableTextField({
46
51
  isEditable,
47
52
  textStyles,
48
53
  colors,
54
+ tokens,
49
55
  toolbarConfig = DEFAULT_TOOLBAR_CONFIG,
50
56
  tableToolbarConfig = DEFAULT_TABLE_TOOLBAR_CONFIG,
51
57
  maskSelector,
@@ -53,14 +59,20 @@ function EditableTextField({
53
59
  tag,
54
60
  autofocus = false,
55
61
  disableSpellcheck = false,
62
+ mode,
63
+ tokenConfig = {},
56
64
  // TODO implement an equivalent to the EditableText handler
57
65
  // for editables inside tab handlers / accordion headers etc
58
66
  // stopPropagation = false,
59
67
  ...other
60
68
  }) {
69
+ const { isPresomanager } = useModes();
70
+ const { isUserTemplateLibrary } = useSlide();
61
71
  const [textColor, setTextColor] = useState(null);
62
72
  const initialValue = useRef(value);
63
73
 
74
+ const isReallyPresoManager = isPresomanager && !isUserTemplateLibrary;
75
+
64
76
  // a custom tag can result in a custom Document extension
65
77
  // or you can pass custom extensions
66
78
  const additionalExtensions = {
@@ -68,7 +80,26 @@ function EditableTextField({
68
80
  ...customExtensions,
69
81
  };
70
82
 
71
- const baseExtensions = [Text, Paragraph, HardBreak];
83
+ // include Token here, because we need it for rendering even if its toolbar button is disabled
84
+ // elsewhere, we can kill its suggestion engine to disable its UI
85
+
86
+ const fuseInstance = useMemo(() => {
87
+ return new Fuse(tokens, { keys: ["label"] });
88
+ }, [tokens]);
89
+
90
+ const baseExtensions = [
91
+ Text,
92
+ Paragraph,
93
+ HardBreak,
94
+ generateTokenInstance({
95
+ tokens,
96
+ mode,
97
+ isEditable,
98
+ usePlaceholder: isReallyPresoManager,
99
+ fuse: fuseInstance,
100
+ ...tokenConfig,
101
+ }),
102
+ ].filter(Boolean);
72
103
 
73
104
  // it is your responsibility not to double-specify any extensions you need here
74
105
  // vs those derived from the toolbar config
@@ -89,8 +120,9 @@ function EditableTextField({
89
120
  tableToolbarConfig,
90
121
  textStyles,
91
122
  colors,
123
+ tokens,
92
124
  }),
93
- [textStyles, colors, toolbarConfig, tableToolbarConfig],
125
+ [textStyles, colors, tokens, toolbarConfig, tableToolbarConfig],
94
126
  );
95
127
 
96
128
  const groupedOptions = [
@@ -112,6 +144,7 @@ function EditableTextField({
112
144
  onUpdate: debounce(({ editor }) => {
113
145
  onChange?.(editor.getHTML());
114
146
  }, 100),
147
+ parseOptions: { preserveWhitespace: "full" },
115
148
  });
116
149
 
117
150
  const { refs } = useFloating({
@@ -219,8 +252,16 @@ function EditableTextField({
219
252
  {isEditable && (
220
253
  <>
221
254
  <PrimaryToolbar configuration={primaryOptions} {...toolbarOptions} />
222
- <TableCellMenu configuration={tableCellOptions} {...toolbarOptions} />
223
- <TableToolbar configuration={tableOptions} {...toolbarOptions} />
255
+
256
+ {tableOptions.length > 0 && (
257
+ <>
258
+ <TableCellMenu
259
+ configuration={tableCellOptions}
260
+ {...toolbarOptions}
261
+ />
262
+ <TableToolbar configuration={tableOptions} {...toolbarOptions} />
263
+ </>
264
+ )}
224
265
  </>
225
266
  )}
226
267
  <div
@@ -257,6 +298,7 @@ function PrepEditableField({ id, prepId = id, children, isCompany, ...props }) {
257
298
  value={value ?? renderToString(children)}
258
299
  onChange={setPrepValue}
259
300
  isEditable={isEditable}
301
+ mode={MODE_PREP}
260
302
  />
261
303
  );
262
304
  }
@@ -286,6 +328,7 @@ function CMSValField({ id, children, ...props }) {
286
328
  value={value ?? renderToString(children)}
287
329
  onChange={setCMSValue}
288
330
  isEditable={isEditable}
331
+ mode={MODE_CMS}
289
332
  />
290
333
  );
291
334
  }
@@ -298,6 +341,7 @@ export function TextField(props) {
298
341
  value={value ?? renderToString(children)}
299
342
  onChange={onChange}
300
343
  isEditable={!isReadOnly}
344
+ mode={MODE_UNMANAGED}
301
345
  {...other}
302
346
  />
303
347
  );