@notum-cz/strapi-plugin-tiptap-editor 1.0.1 → 1.0.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.
Files changed (80) hide show
  1. package/README.md +85 -55
  2. package/dist/_chunks/{RichTextInput-BZQ2iVqa.mjs → RichTextInput-COGVRWOW.mjs} +786 -596
  3. package/dist/_chunks/RichTextInput-COGVRWOW.mjs.map +1 -0
  4. package/dist/_chunks/{RichTextInput-BlxoJMa2.js → RichTextInput-Wc2q__HY.js} +785 -595
  5. package/dist/_chunks/RichTextInput-Wc2q__HY.js.map +1 -0
  6. package/dist/admin/index.js +23 -1
  7. package/dist/admin/index.js.map +1 -1
  8. package/dist/admin/index.mjs +23 -1
  9. package/dist/admin/index.mjs.map +1 -1
  10. package/dist/admin/src/components/BaseTiptapInput.d.ts +1 -0
  11. package/dist/admin/src/components/EditorErrorBoundary.d.ts +23 -0
  12. package/dist/admin/src/components/FeatureGuard.d.ts +15 -0
  13. package/dist/admin/src/components/PresetSelect.d.ts +7 -0
  14. package/dist/admin/src/extensions/Heading.d.ts +1 -0
  15. package/dist/admin/src/fields/richTextField.d.ts +18 -0
  16. package/dist/admin/src/hooks/usePresetConfig.d.ts +6 -0
  17. package/dist/admin/src/utils/buildExtensions.d.ts +3 -0
  18. package/dist/server/index.js +154 -6
  19. package/dist/server/index.js.map +1 -1
  20. package/dist/server/index.mjs +153 -6
  21. package/dist/server/index.mjs.map +1 -1
  22. package/dist/server/src/config/index.d.ts +7 -4
  23. package/dist/server/src/controllers/index.d.ts +8 -1
  24. package/dist/server/src/controllers/preset.d.ts +8 -0
  25. package/dist/server/src/index.d.ts +43 -5
  26. package/dist/server/src/routes/index.d.ts +15 -1
  27. package/dist/server/src/services/index.d.ts +9 -1
  28. package/dist/server/src/services/preset.d.ts +10 -0
  29. package/dist/shared/types.d.ts +54 -0
  30. package/package.json +11 -3
  31. package/dist/_chunks/AccentCursive-CpAPpH9C.mjs +0 -3383
  32. package/dist/_chunks/AccentCursive-CpAPpH9C.mjs.map +0 -1
  33. package/dist/_chunks/AccentCursive-D6sTlhub.js +0 -3384
  34. package/dist/_chunks/AccentCursive-D6sTlhub.js.map +0 -1
  35. package/dist/_chunks/FormattedHeadingInput-DycgfIze.mjs +0 -101
  36. package/dist/_chunks/FormattedHeadingInput-DycgfIze.mjs.map +0 -1
  37. package/dist/_chunks/FormattedHeadingInput-FFjiRSEJ.js +0 -101
  38. package/dist/_chunks/FormattedHeadingInput-FFjiRSEJ.js.map +0 -1
  39. package/dist/_chunks/RichTextInput-BZQ2iVqa.mjs.map +0 -1
  40. package/dist/_chunks/RichTextInput-BbbQxPc-.js +0 -4414
  41. package/dist/_chunks/RichTextInput-BbbQxPc-.js.map +0 -1
  42. package/dist/_chunks/RichTextInput-BjLR2pi0.js +0 -4416
  43. package/dist/_chunks/RichTextInput-BjLR2pi0.js.map +0 -1
  44. package/dist/_chunks/RichTextInput-BlxoJMa2.js.map +0 -1
  45. package/dist/_chunks/RichTextInput-Bm3X8fR2.mjs +0 -4400
  46. package/dist/_chunks/RichTextInput-Bm3X8fR2.mjs.map +0 -1
  47. package/dist/_chunks/RichTextInput-Bms-gSvK.js +0 -4407
  48. package/dist/_chunks/RichTextInput-Bms-gSvK.js.map +0 -1
  49. package/dist/_chunks/RichTextInput-BtNjPJRN.mjs +0 -4400
  50. package/dist/_chunks/RichTextInput-BtNjPJRN.mjs.map +0 -1
  51. package/dist/_chunks/RichTextInput-Bw3tcXfp.js +0 -4407
  52. package/dist/_chunks/RichTextInput-Bw3tcXfp.js.map +0 -1
  53. package/dist/_chunks/RichTextInput-CsgNpoxq.mjs +0 -4409
  54. package/dist/_chunks/RichTextInput-CsgNpoxq.mjs.map +0 -1
  55. package/dist/_chunks/RichTextInput-CwTvEMda.js +0 -4407
  56. package/dist/_chunks/RichTextInput-CwTvEMda.js.map +0 -1
  57. package/dist/_chunks/RichTextInput-DG-36krM.js +0 -1181
  58. package/dist/_chunks/RichTextInput-DG-36krM.js.map +0 -1
  59. package/dist/_chunks/RichTextInput-DLac-zNQ.mjs +0 -4400
  60. package/dist/_chunks/RichTextInput-DLac-zNQ.mjs.map +0 -1
  61. package/dist/_chunks/RichTextInput-DSXttrvi.js +0 -4407
  62. package/dist/_chunks/RichTextInput-DSXttrvi.js.map +0 -1
  63. package/dist/_chunks/RichTextInput-DeJ6Exto.mjs +0 -4400
  64. package/dist/_chunks/RichTextInput-DeJ6Exto.mjs.map +0 -1
  65. package/dist/_chunks/RichTextInput-DgT88AkO.mjs +0 -1175
  66. package/dist/_chunks/RichTextInput-DgT88AkO.mjs.map +0 -1
  67. package/dist/_chunks/RichTextInput-DlMaDJQF.mjs +0 -4400
  68. package/dist/_chunks/RichTextInput-DlMaDJQF.mjs.map +0 -1
  69. package/dist/_chunks/RichTextInput-DtaYdjCs.mjs +0 -4400
  70. package/dist/_chunks/RichTextInput-DtaYdjCs.mjs.map +0 -1
  71. package/dist/_chunks/RichTextInput-YTKXo5oq.js +0 -4407
  72. package/dist/_chunks/RichTextInput-YTKXo5oq.js.map +0 -1
  73. package/dist/_chunks/RichTextInput-tmg-oMJk.mjs +0 -4407
  74. package/dist/_chunks/RichTextInput-tmg-oMJk.mjs.map +0 -1
  75. package/dist/_chunks/RichTextInput-umhMsI5o.js +0 -4407
  76. package/dist/_chunks/RichTextInput-umhMsI5o.js.map +0 -1
  77. package/dist/admin/src/components/FormattedHeadingInput.d.ts +0 -4
  78. package/dist/admin/src/extensions/AccentCursive.d.ts +0 -18
  79. package/dist/admin/src/fields/formattedHeadingField.d.ts +0 -20
  80. package/dist/admin/src/pluginId.d.ts +0 -1
@@ -1,12 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const jsxRuntime = require("react/jsx-runtime");
4
+ const React = require("react");
4
5
  const designSystem = require("@strapi/design-system");
5
6
  const react = require("@tiptap/react");
6
7
  const styled = require("styled-components");
7
- const React = require("react");
8
8
  const admin = require("@strapi/strapi/admin");
9
- const StarterKit = require("@tiptap/starter-kit");
10
9
  const icons = require("@strapi/icons");
11
10
  const transform = require("@tiptap/pm/transform");
12
11
  const commands = require("@tiptap/pm/commands");
@@ -15,16 +14,17 @@ const model = require("@tiptap/pm/model");
15
14
  const schemaList = require("@tiptap/pm/schema-list");
16
15
  const view = require("@tiptap/pm/view");
17
16
  require("@tiptap/pm/keymap");
17
+ const StarterKit = require("@tiptap/starter-kit");
18
18
  const Superscript = require("@tiptap/extension-superscript");
19
19
  const Subscript = require("@tiptap/extension-subscript");
20
20
  const extensionTable = require("@tiptap/extension-table");
21
+ const TextAlign = require("@tiptap/extension-text-align");
21
22
  const dropcursor = require("@tiptap/pm/dropcursor");
22
23
  const gapcursor = require("@tiptap/pm/gapcursor");
23
24
  const history = require("@tiptap/pm/history");
24
- const TextAlign = require("@tiptap/extension-text-align");
25
25
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
26
- const styled__default = /* @__PURE__ */ _interopDefault(styled);
27
26
  const React__default = /* @__PURE__ */ _interopDefault(React);
27
+ const styled__default = /* @__PURE__ */ _interopDefault(styled);
28
28
  const StarterKit__default = /* @__PURE__ */ _interopDefault(StarterKit);
29
29
  const Superscript__default = /* @__PURE__ */ _interopDefault(Superscript);
30
30
  const Subscript__default = /* @__PURE__ */ _interopDefault(Subscript);
@@ -216,7 +216,7 @@ const TiptapInputStyles = styled__default.default.div`
216
216
  }
217
217
  `;
218
218
  const BaseTiptapInput = React.forwardRef(
219
- ({ hint, disabled = false, labelAction, label, name, required = false, editor, field, children }, forwardedRef) => {
219
+ ({ hint, disabled = false, labelAction, label, name, required = false, editor, field, children, noPresetConfigured }, forwardedRef) => {
220
220
  const borderColor = field.error ? "danger600" : "neutral200";
221
221
  const background = disabled ? "neutral200" : "neutral100";
222
222
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { name, id: name, hint, error: field.error, required, children: [
@@ -233,6 +233,7 @@ const BaseTiptapInput = React.forwardRef(
233
233
  paddingLeft: 0,
234
234
  paddingRight: 0,
235
235
  children: [
236
+ noPresetConfigured && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingLeft: 2, paddingRight: 2, paddingTop: 2, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Status, { variant: "secondary", showBullet: false, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", children: "No editor preset configured — showing minimal editor" }) }) }),
236
237
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { className: "editor-toolbar", paddingLeft: 2, paddingRight: 2, paddingBottom: 2, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 1, wrap: "wrap", children }) }),
237
238
  /* @__PURE__ */ jsxRuntime.jsx(
238
239
  designSystem.Box,
@@ -254,6 +255,71 @@ const BaseTiptapInput = React.forwardRef(
254
255
  ] });
255
256
  }
256
257
  );
258
+ class EditorErrorBoundary extends React.Component {
259
+ state = { hasError: false, error: null };
260
+ static getDerivedStateFromError(error) {
261
+ return { hasError: true, error };
262
+ }
263
+ componentDidCatch(error, errorInfo) {
264
+ console.error("[TiptapEditor] Editor crashed:", error, errorInfo);
265
+ }
266
+ handleRetry = () => {
267
+ this.setState({ hasError: false, error: null });
268
+ };
269
+ render() {
270
+ if (this.state.hasError) {
271
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 4, background: "danger100", hasRadius: true, children: [
272
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "danger700", children: "The editor encountered an error and could not render." }),
273
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginTop: 2, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "secondary", size: "S", onClick: this.handleRetry, children: "Retry" }) })
274
+ ] });
275
+ }
276
+ return this.props.children;
277
+ }
278
+ }
279
+ const MINIMAL_PRESET_CONFIG = {
280
+ bold: true,
281
+ italic: true
282
+ };
283
+ const isPlainObject$1 = (value) => {
284
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
285
+ const prototype = Object.getPrototypeOf(value);
286
+ return prototype === Object.prototype || prototype === null;
287
+ };
288
+ const isFeatureEnabled = (value) => {
289
+ if (value === void 0) {
290
+ return true;
291
+ }
292
+ if (typeof value === "boolean") {
293
+ return value;
294
+ }
295
+ if (!isPlainObject$1(value)) {
296
+ return false;
297
+ }
298
+ const obj = value;
299
+ if (typeof obj.enabled === "boolean") {
300
+ return obj.enabled;
301
+ }
302
+ if (typeof obj.disabled === "boolean") {
303
+ return !obj.disabled;
304
+ }
305
+ return true;
306
+ };
307
+ const getFeatureOptions = (value, defaults) => {
308
+ if (value === false) {
309
+ return null;
310
+ }
311
+ if (!isPlainObject$1(value)) {
312
+ return defaults;
313
+ }
314
+ const { enabled: _e, disabled: _d, ...rest } = value;
315
+ return { ...defaults, ...rest };
316
+ };
317
+ function FeatureGuard({ featureValue, children }) {
318
+ if (!isFeatureEnabled(featureValue)) {
319
+ return null;
320
+ }
321
+ return children;
322
+ }
257
323
  function tiptapContent(text) {
258
324
  return {
259
325
  type: "doc",
@@ -284,10 +350,10 @@ function parseJSONContent(value, defaultValue) {
284
350
  `);
285
351
  }
286
352
  }
287
- function useTiptapEditor(name, defaultValue = "", extensions2 = []) {
353
+ function useTiptapEditor(name, defaultValue = "", extensions = []) {
288
354
  const field = admin.useField(name);
289
355
  const editor = react.useEditor({
290
- extensions: extensions2,
356
+ extensions,
291
357
  content: parseJSONContent(field.value, defaultValue),
292
358
  onUpdate: ({ editor: editor2 }) => {
293
359
  const json = editor2.getJSON();
@@ -1679,10 +1745,10 @@ function callOrReturn(value, context = void 0, ...props) {
1679
1745
  }
1680
1746
  return value;
1681
1747
  }
1682
- function splitExtensions(extensions2) {
1683
- const baseExtensions = extensions2.filter((extension) => extension.type === "extension");
1684
- const nodeExtensions = extensions2.filter((extension) => extension.type === "node");
1685
- const markExtensions = extensions2.filter((extension) => extension.type === "mark");
1748
+ function splitExtensions(extensions) {
1749
+ const baseExtensions = extensions.filter((extension) => extension.type === "extension");
1750
+ const nodeExtensions = extensions.filter((extension) => extension.type === "node");
1751
+ const markExtensions = extensions.filter((extension) => extension.type === "mark");
1686
1752
  return {
1687
1753
  baseExtensions,
1688
1754
  nodeExtensions,
@@ -1871,8 +1937,8 @@ function isMarkActive(state2, typeOrName, attributes = {}) {
1871
1937
  const range = matchedRange > 0 ? matchedRange + excludedRange : matchedRange;
1872
1938
  return range >= selectionRange;
1873
1939
  }
1874
- function isList(name, extensions2) {
1875
- const { nodeExtensions } = splitExtensions(extensions2);
1940
+ function isList(name, extensions) {
1941
+ const { nodeExtensions } = splitExtensions(extensions);
1876
1942
  const extension = nodeExtensions.find((item) => item.name === name);
1877
1943
  if (!extension) {
1878
1944
  return false;
@@ -2258,7 +2324,7 @@ var joinListForwards = (tr, listType) => {
2258
2324
  return true;
2259
2325
  };
2260
2326
  var toggleList = (listTypeOrName, itemTypeOrName, keepMarks, attributes = {}) => ({ editor, tr, state: state2, dispatch, chain, commands: commands2, can }) => {
2261
- const { extensions: extensions2, splittableMarks } = editor.extensionManager;
2327
+ const { extensions, splittableMarks } = editor.extensionManager;
2262
2328
  const listType = getNodeType(listTypeOrName, state2.schema);
2263
2329
  const itemType = getNodeType(itemTypeOrName, state2.schema);
2264
2330
  const { selection, storedMarks } = state2;
@@ -2268,12 +2334,12 @@ var toggleList = (listTypeOrName, itemTypeOrName, keepMarks, attributes = {}) =>
2268
2334
  if (!range) {
2269
2335
  return false;
2270
2336
  }
2271
- const parentList = findParentNode((node) => isList(node.type.name, extensions2))(selection);
2337
+ const parentList = findParentNode((node) => isList(node.type.name, extensions))(selection);
2272
2338
  if (range.depth >= 1 && parentList && range.depth - parentList.depth <= 1) {
2273
2339
  if (parentList.node.type === listType) {
2274
2340
  return commands2.liftListItem(itemType);
2275
2341
  }
2276
- if (isList(parentList.node.type.name, extensions2) && listType.validContent(parentList.node.content) && dispatch) {
2342
+ if (isList(parentList.node.type.name, extensions) && listType.validContent(parentList.node.content) && dispatch) {
2277
2343
  return chain().command(() => {
2278
2344
  tr.setNodeMarkup(parentList.pos, listType);
2279
2345
  return true;
@@ -3608,7 +3674,7 @@ var Heading = Node3.create({
3608
3674
  }
3609
3675
  });
3610
3676
  var index_default = Heading;
3611
- const HeadingWithSEOTag = index_default.extend({
3677
+ const BaseHeadingWithSEOTag = index_default.extend({
3612
3678
  addAttributes() {
3613
3679
  return {
3614
3680
  ...this.parent?.(),
@@ -3616,7 +3682,8 @@ const HeadingWithSEOTag = index_default.extend({
3616
3682
  tag: { default: null }
3617
3683
  };
3618
3684
  }
3619
- }).configure({ levels: [1, 2, 3, 4] });
3685
+ });
3686
+ BaseHeadingWithSEOTag.configure({ levels: [1, 2, 3, 4] });
3620
3687
  function useHeading(editor, props = { disabled: false }) {
3621
3688
  const editorState = react.useEditorState({
3622
3689
  editor,
@@ -3735,425 +3802,72 @@ function useScript(editor, props = { disabled: false }) {
3735
3802
  )
3736
3803
  };
3737
3804
  }
3738
- Extension.create({
3739
- name: "characterCount",
3740
- addOptions() {
3741
- return {
3742
- limit: null,
3743
- mode: "textSize",
3744
- textCounter: (text) => text.length,
3745
- wordCounter: (text) => text.split(" ").filter((word) => word !== "").length
3746
- };
3747
- },
3748
- addStorage() {
3749
- return {
3750
- characters: () => 0,
3751
- words: () => 0
3752
- };
3753
- },
3754
- onBeforeCreate() {
3755
- this.storage.characters = (options) => {
3756
- const node = (options == null ? void 0 : options.node) || this.editor.state.doc;
3757
- const mode = (options == null ? void 0 : options.mode) || this.options.mode;
3758
- if (mode === "textSize") {
3759
- const text = node.textBetween(0, node.content.size, void 0, " ");
3760
- return this.options.textCounter(text);
3761
- }
3762
- return node.nodeSize;
3763
- };
3764
- this.storage.words = (options) => {
3765
- const node = (options == null ? void 0 : options.node) || this.editor.state.doc;
3766
- const text = node.textBetween(0, node.content.size, " ", " ");
3767
- return this.options.wordCounter(text);
3768
- };
3769
- },
3770
- addProseMirrorPlugins() {
3771
- let initialEvaluationDone = false;
3772
- return [
3773
- new state.Plugin({
3774
- key: new state.PluginKey("characterCount"),
3775
- appendTransaction: (transactions, oldState, newState) => {
3776
- if (initialEvaluationDone) {
3777
- return;
3778
- }
3779
- const limit = this.options.limit;
3780
- if (limit === null || limit === void 0 || limit === 0) {
3781
- initialEvaluationDone = true;
3782
- return;
3783
- }
3784
- const initialContentSize = this.storage.characters({ node: newState.doc });
3785
- if (initialContentSize > limit) {
3786
- const over = initialContentSize - limit;
3787
- const from = 0;
3788
- const to = over;
3789
- console.warn(
3790
- `[CharacterCount] Initial content exceeded limit of ${limit} characters. Content was automatically trimmed.`
3791
- );
3792
- const tr = newState.tr.deleteRange(from, to);
3793
- initialEvaluationDone = true;
3794
- return tr;
3795
- }
3796
- initialEvaluationDone = true;
3797
- },
3798
- filterTransaction: (transaction, state2) => {
3799
- const limit = this.options.limit;
3800
- if (!transaction.docChanged || limit === 0 || limit === null || limit === void 0) {
3801
- return true;
3802
- }
3803
- const oldSize = this.storage.characters({ node: state2.doc });
3804
- const newSize = this.storage.characters({ node: transaction.doc });
3805
- if (newSize <= limit) {
3806
- return true;
3807
- }
3808
- if (oldSize > limit && newSize > limit && newSize <= oldSize) {
3809
- return true;
3810
- }
3811
- if (oldSize > limit && newSize > limit && newSize > oldSize) {
3812
- return false;
3813
- }
3814
- const isPaste = transaction.getMeta("paste");
3815
- if (!isPaste) {
3816
- return false;
3817
- }
3818
- const pos = transaction.selection.$head.pos;
3819
- const over = newSize - limit;
3820
- const from = pos - over;
3821
- const to = pos;
3822
- transaction.deleteRange(from, to);
3823
- const updatedSize = this.storage.characters({ node: transaction.doc });
3824
- if (updatedSize > limit) {
3825
- return false;
3826
- }
3827
- return true;
3828
- }
3829
- })
3830
- ];
3831
- }
3832
- });
3833
- Extension.create({
3834
- name: "dropCursor",
3835
- addOptions() {
3836
- return {
3837
- color: "currentColor",
3838
- width: 1,
3839
- class: void 0
3840
- };
3841
- },
3842
- addProseMirrorPlugins() {
3843
- return [dropcursor.dropCursor(this.options)];
3844
- }
3845
- });
3846
- Extension.create({
3847
- name: "focus",
3848
- addOptions() {
3849
- return {
3850
- className: "has-focus",
3851
- mode: "all"
3852
- };
3853
- },
3854
- addProseMirrorPlugins() {
3855
- return [
3856
- new state.Plugin({
3857
- key: new state.PluginKey("focus"),
3858
- props: {
3859
- decorations: ({ doc, selection }) => {
3860
- const { isEditable, isFocused } = this.editor;
3861
- const { anchor } = selection;
3862
- const decorations = [];
3863
- if (!isEditable || !isFocused) {
3864
- return view.DecorationSet.create(doc, []);
3865
- }
3866
- let maxLevels = 0;
3867
- if (this.options.mode === "deepest") {
3868
- doc.descendants((node, pos) => {
3869
- if (node.isText) {
3870
- return;
3871
- }
3872
- const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1;
3873
- if (!isCurrent) {
3874
- return false;
3875
- }
3876
- maxLevels += 1;
3877
- });
3878
- }
3879
- let currentLevel = 0;
3880
- doc.descendants((node, pos) => {
3881
- if (node.isText) {
3882
- return false;
3883
- }
3884
- const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1;
3885
- if (!isCurrent) {
3886
- return false;
3805
+ const clamp = (val, min, max) => Math.max(min, Math.min(max, val));
3806
+ const parseNum = (value, fallback) => {
3807
+ const n = Number(value);
3808
+ return Number.isFinite(n) ? n : fallback;
3809
+ };
3810
+ const TableSizeDialog = ({
3811
+ open,
3812
+ defaultRows = 3,
3813
+ defaultCols = 3,
3814
+ onClose,
3815
+ onSave
3816
+ }) => {
3817
+ const [rows, setRows] = React.useState(defaultRows);
3818
+ const [cols, setCols] = React.useState(defaultCols);
3819
+ React.useEffect(() => {
3820
+ if (open) {
3821
+ setRows(defaultRows);
3822
+ setCols(defaultCols);
3823
+ }
3824
+ }, [open, defaultRows, defaultCols]);
3825
+ const min = 1;
3826
+ const max = 10;
3827
+ const isValid = rows >= min && rows <= max && cols >= min && cols <= max;
3828
+ const handleSave = () => {
3829
+ if (!isValid) return;
3830
+ onSave(clamp(rows, min, max), clamp(cols, min, max));
3831
+ };
3832
+ return /* @__PURE__ */ jsxRuntime.jsx(
3833
+ designSystem.Dialog.Root,
3834
+ {
3835
+ open,
3836
+ onOpenChange: (v) => {
3837
+ if (!v) onClose();
3838
+ },
3839
+ children: open && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Dialog.Content, { children: [
3840
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Header, { children: "Insert table" }),
3841
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Body, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, alignItems: "flex-end", children: [
3842
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { width: "100%", children: [
3843
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Rows" }),
3844
+ /* @__PURE__ */ jsxRuntime.jsx(
3845
+ designSystem.TextInput,
3846
+ {
3847
+ name: "table-rows",
3848
+ type: "number",
3849
+ value: String(rows),
3850
+ onChange: (e) => setRows(clamp(parseNum(e.target.value, rows), min, max)),
3851
+ placeholder: String(defaultRows)
3887
3852
  }
3888
- currentLevel += 1;
3889
- const outOfScope = this.options.mode === "deepest" && maxLevels - currentLevel > 0 || this.options.mode === "shallowest" && currentLevel > 1;
3890
- if (outOfScope) {
3891
- return this.options.mode === "deepest";
3892
- }
3893
- decorations.push(
3894
- view.Decoration.node(pos, pos + node.nodeSize, {
3895
- class: this.options.className
3896
- })
3897
- );
3898
- });
3899
- return view.DecorationSet.create(doc, decorations);
3900
- }
3901
- }
3902
- })
3903
- ];
3904
- }
3905
- });
3906
- var Gapcursor = Extension.create({
3907
- name: "gapCursor",
3908
- addProseMirrorPlugins() {
3909
- return [gapcursor.gapCursor()];
3910
- },
3911
- extendNodeSchema(extension) {
3912
- var _a;
3913
- const context = {
3914
- name: extension.name,
3915
- options: extension.options,
3916
- storage: extension.storage
3917
- };
3918
- return {
3919
- allowGapCursor: (_a = callOrReturn(getExtensionField(extension, "allowGapCursor", context))) != null ? _a : null
3920
- };
3921
- }
3922
- });
3923
- var DEFAULT_DATA_ATTRIBUTE = "placeholder";
3924
- function preparePlaceholderAttribute(attr) {
3925
- return attr.replace(/\s+/g, "-").replace(/[^a-zA-Z0-9-]/g, "").replace(/^[0-9-]+/, "").replace(/^-+/, "").toLowerCase();
3926
- }
3927
- Extension.create({
3928
- name: "placeholder",
3929
- addOptions() {
3930
- return {
3931
- emptyEditorClass: "is-editor-empty",
3932
- emptyNodeClass: "is-empty",
3933
- dataAttribute: DEFAULT_DATA_ATTRIBUTE,
3934
- placeholder: "Write something …",
3935
- showOnlyWhenEditable: true,
3936
- showOnlyCurrent: true,
3937
- includeChildren: false
3938
- };
3939
- },
3940
- addProseMirrorPlugins() {
3941
- const dataAttribute = this.options.dataAttribute ? `data-${preparePlaceholderAttribute(this.options.dataAttribute)}` : `data-${DEFAULT_DATA_ATTRIBUTE}`;
3942
- return [
3943
- new state.Plugin({
3944
- key: new state.PluginKey("placeholder"),
3945
- props: {
3946
- decorations: ({ doc, selection }) => {
3947
- const active = this.editor.isEditable || !this.options.showOnlyWhenEditable;
3948
- const { anchor } = selection;
3949
- const decorations = [];
3950
- if (!active) {
3951
- return null;
3952
- }
3953
- const isEmptyDoc = this.editor.isEmpty;
3954
- doc.descendants((node, pos) => {
3955
- const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize;
3956
- const isEmpty = !node.isLeaf && isNodeEmpty(node);
3957
- if ((hasAnchor || !this.options.showOnlyCurrent) && isEmpty) {
3958
- const classes = [this.options.emptyNodeClass];
3959
- if (isEmptyDoc) {
3960
- classes.push(this.options.emptyEditorClass);
3961
- }
3962
- const decoration = view.Decoration.node(pos, pos + node.nodeSize, {
3963
- class: classes.join(" "),
3964
- [dataAttribute]: typeof this.options.placeholder === "function" ? this.options.placeholder({
3965
- editor: this.editor,
3966
- node,
3967
- pos,
3968
- hasAnchor
3969
- }) : this.options.placeholder
3970
- });
3971
- decorations.push(decoration);
3972
- }
3973
- return this.options.includeChildren;
3974
- });
3975
- return view.DecorationSet.create(doc, decorations);
3976
- }
3977
- }
3978
- })
3979
- ];
3980
- }
3981
- });
3982
- Extension.create({
3983
- name: "selection",
3984
- addOptions() {
3985
- return {
3986
- className: "selection"
3987
- };
3988
- },
3989
- addProseMirrorPlugins() {
3990
- const { editor, options } = this;
3991
- return [
3992
- new state.Plugin({
3993
- key: new state.PluginKey("selection"),
3994
- props: {
3995
- decorations(state2) {
3996
- if (state2.selection.empty || editor.isFocused || !editor.isEditable || isNodeSelection(state2.selection) || editor.view.dragging) {
3997
- return null;
3998
- }
3999
- return view.DecorationSet.create(state2.doc, [
4000
- view.Decoration.inline(state2.selection.from, state2.selection.to, {
4001
- class: options.className
4002
- })
4003
- ]);
4004
- }
4005
- }
4006
- })
4007
- ];
4008
- }
4009
- });
4010
- function nodeEqualsType({ types, node }) {
4011
- return node && Array.isArray(types) && types.includes(node.type) || (node == null ? void 0 : node.type) === types;
4012
- }
4013
- Extension.create({
4014
- name: "trailingNode",
4015
- addOptions() {
4016
- return {
4017
- node: void 0,
4018
- notAfter: []
4019
- };
4020
- },
4021
- addProseMirrorPlugins() {
4022
- var _a;
4023
- const plugin = new state.PluginKey(this.name);
4024
- const defaultNode = this.options.node || ((_a = this.editor.schema.topNodeType.contentMatch.defaultType) == null ? void 0 : _a.name) || "paragraph";
4025
- const disabledNodes = Object.entries(this.editor.schema.nodes).map(([, value]) => value).filter((node) => (this.options.notAfter || []).concat(defaultNode).includes(node.name));
4026
- return [
4027
- new state.Plugin({
4028
- key: plugin,
4029
- appendTransaction: (_, __, state2) => {
4030
- const { doc, tr, schema } = state2;
4031
- const shouldInsertNodeAtEnd = plugin.getState(state2);
4032
- const endPosition = doc.content.size;
4033
- const type = schema.nodes[defaultNode];
4034
- if (!shouldInsertNodeAtEnd) {
4035
- return;
4036
- }
4037
- return tr.insert(endPosition, type.create());
4038
- },
4039
- state: {
4040
- init: (_, state2) => {
4041
- const lastNode = state2.tr.doc.lastChild;
4042
- return !nodeEqualsType({ node: lastNode, types: disabledNodes });
4043
- },
4044
- apply: (tr, value) => {
4045
- if (!tr.docChanged) {
4046
- return value;
4047
- }
4048
- if (tr.getMeta("__uniqueIDTransaction")) {
4049
- return value;
4050
- }
4051
- const lastNode = tr.doc.lastChild;
4052
- return !nodeEqualsType({ node: lastNode, types: disabledNodes });
4053
- }
4054
- }
4055
- })
4056
- ];
4057
- }
4058
- });
4059
- Extension.create({
4060
- name: "undoRedo",
4061
- addOptions() {
4062
- return {
4063
- depth: 100,
4064
- newGroupDelay: 500
4065
- };
4066
- },
4067
- addCommands() {
4068
- return {
4069
- undo: () => ({ state: state2, dispatch }) => {
4070
- return history.undo(state2, dispatch);
4071
- },
4072
- redo: () => ({ state: state2, dispatch }) => {
4073
- return history.redo(state2, dispatch);
4074
- }
4075
- };
4076
- },
4077
- addProseMirrorPlugins() {
4078
- return [history.history(this.options)];
4079
- },
4080
- addKeyboardShortcuts() {
4081
- return {
4082
- "Mod-z": () => this.editor.commands.undo(),
4083
- "Shift-Mod-z": () => this.editor.commands.redo(),
4084
- "Mod-y": () => this.editor.commands.redo(),
4085
- // Russian keyboard layouts
4086
- "Mod-я": () => this.editor.commands.undo(),
4087
- "Shift-Mod-я": () => this.editor.commands.redo()
4088
- };
4089
- }
4090
- });
4091
- const clamp = (val, min, max) => Math.max(min, Math.min(max, val));
4092
- const parseNum = (value, fallback) => {
4093
- const n = Number(value);
4094
- return Number.isFinite(n) ? n : fallback;
4095
- };
4096
- const TableSizeDialog = ({
4097
- open,
4098
- defaultRows = 3,
4099
- defaultCols = 3,
4100
- onClose,
4101
- onSave
4102
- }) => {
4103
- const [rows, setRows] = React.useState(defaultRows);
4104
- const [cols, setCols] = React.useState(defaultCols);
4105
- React.useEffect(() => {
4106
- if (open) {
4107
- setRows(defaultRows);
4108
- setCols(defaultCols);
4109
- }
4110
- }, [open, defaultRows, defaultCols]);
4111
- const min = 1;
4112
- const max = 10;
4113
- const isValid = rows >= min && rows <= max && cols >= min && cols <= max;
4114
- const handleSave = () => {
4115
- if (!isValid) return;
4116
- onSave(clamp(rows, min, max), clamp(cols, min, max));
4117
- };
4118
- return /* @__PURE__ */ jsxRuntime.jsx(
4119
- designSystem.Dialog.Root,
4120
- {
4121
- open,
4122
- onOpenChange: (v) => {
4123
- if (!v) onClose();
4124
- },
4125
- children: open && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Dialog.Content, { children: [
4126
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Header, { children: "Insert table" }),
4127
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Body, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, alignItems: "flex-end", children: [
4128
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { width: "100%", children: [
4129
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Rows" }),
4130
- /* @__PURE__ */ jsxRuntime.jsx(
4131
- designSystem.TextInput,
4132
- {
4133
- name: "table-rows",
4134
- type: "number",
4135
- value: String(rows),
4136
- onChange: (e) => setRows(clamp(parseNum(e.target.value, rows), min, max)),
4137
- placeholder: String(defaultRows)
4138
- }
4139
- ),
4140
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Hint, { children: [
4141
- "Min ",
4142
- min,
4143
- ", max ",
4144
- max
4145
- ] })
4146
- ] }),
4147
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { width: "100%", children: [
4148
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Columns" }),
4149
- /* @__PURE__ */ jsxRuntime.jsx(
4150
- designSystem.TextInput,
4151
- {
4152
- name: "table-cols",
4153
- type: "number",
4154
- value: String(cols),
4155
- onChange: (e) => setCols(clamp(parseNum(e.target.value, cols), min, max)),
4156
- placeholder: String(defaultCols)
3853
+ ),
3854
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Hint, { children: [
3855
+ "Min ",
3856
+ min,
3857
+ ", max ",
3858
+ max
3859
+ ] })
3860
+ ] }),
3861
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { width: "100%", children: [
3862
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Columns" }),
3863
+ /* @__PURE__ */ jsxRuntime.jsx(
3864
+ designSystem.TextInput,
3865
+ {
3866
+ name: "table-cols",
3867
+ type: "number",
3868
+ value: String(cols),
3869
+ onChange: (e) => setCols(clamp(parseNum(e.target.value, cols), min, max)),
3870
+ placeholder: String(defaultCols)
4157
3871
  }
4158
3872
  ),
4159
3873
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Hint, { children: [
@@ -4311,140 +4025,597 @@ function TextAlignJustify(props) {
4311
4025
  "stroke-linecap": "round",
4312
4026
  "stroke-linejoin": "round"
4313
4027
  }
4314
- )
4315
- }
4316
- );
4317
- }
4318
- function TextAlignRight(props) {
4319
- return /* @__PURE__ */ jsxRuntime.jsx(
4320
- "svg",
4321
- {
4322
- width: "16",
4323
- height: "16",
4324
- viewBox: "0 0 24 24",
4325
- fill: "currentColor",
4326
- xmlns: "http://www.w3.org/2000/svg",
4327
- ...props,
4328
- children: /* @__PURE__ */ jsxRuntime.jsx(
4329
- "path",
4330
- {
4331
- d: "M8 10H21M3 14H21M8 18H21M3 6H21",
4332
- stroke: "currentColor",
4333
- "stroke-width": "2",
4334
- "stroke-linecap": "round",
4335
- "stroke-linejoin": "round"
4028
+ )
4029
+ }
4030
+ );
4031
+ }
4032
+ function TextAlignRight(props) {
4033
+ return /* @__PURE__ */ jsxRuntime.jsx(
4034
+ "svg",
4035
+ {
4036
+ width: "16",
4037
+ height: "16",
4038
+ viewBox: "0 0 24 24",
4039
+ fill: "currentColor",
4040
+ xmlns: "http://www.w3.org/2000/svg",
4041
+ ...props,
4042
+ children: /* @__PURE__ */ jsxRuntime.jsx(
4043
+ "path",
4044
+ {
4045
+ d: "M8 10H21M3 14H21M8 18H21M3 6H21",
4046
+ stroke: "currentColor",
4047
+ "stroke-width": "2",
4048
+ "stroke-linecap": "round",
4049
+ "stroke-linejoin": "round"
4050
+ }
4051
+ )
4052
+ }
4053
+ );
4054
+ }
4055
+ function TextAlignCenter(props) {
4056
+ return /* @__PURE__ */ jsxRuntime.jsx(
4057
+ "svg",
4058
+ {
4059
+ width: "16",
4060
+ height: "16",
4061
+ viewBox: "0 0 24 24",
4062
+ fill: "currentColor",
4063
+ xmlns: "http://www.w3.org/2000/svg",
4064
+ ...props,
4065
+ children: /* @__PURE__ */ jsxRuntime.jsx(
4066
+ "path",
4067
+ {
4068
+ d: "M3 6H21M3 14H21M17 10H7M17 18H7",
4069
+ stroke: "currentColor",
4070
+ "stroke-width": "2",
4071
+ "stroke-linecap": "round",
4072
+ "stroke-linejoin": "round"
4073
+ }
4074
+ )
4075
+ }
4076
+ );
4077
+ }
4078
+ function useTextAlign(editor, props = { disabled: false }) {
4079
+ const editorState = react.useEditorState({
4080
+ editor,
4081
+ selector: (ctx) => {
4082
+ return {
4083
+ isTextAlignLeft: ctx.editor.isActive({ textAlign: "left" }) ?? false,
4084
+ isTextAlignRight: ctx.editor.isActive({ textAlign: "right" }) ?? false,
4085
+ isTextAlignCenter: ctx.editor.isActive({ textAlign: "center" }) ?? false,
4086
+ isTextAlignJustify: ctx.editor.isActive({ textAlign: "justify" }) ?? false,
4087
+ canToggleAlign: ctx.editor.can().chain().setTextAlign("left").run() ?? false
4088
+ };
4089
+ }
4090
+ });
4091
+ const setTextAlign = (alignment) => {
4092
+ editor.chain().focus().setTextAlign(alignment).run();
4093
+ };
4094
+ return {
4095
+ textAlignLeftButton: /* @__PURE__ */ jsxRuntime.jsx(
4096
+ ToolbarButton,
4097
+ {
4098
+ onClick: () => setTextAlign("left"),
4099
+ icon: /* @__PURE__ */ jsxRuntime.jsx(TextAlignLeft, {}),
4100
+ active: editorState.isTextAlignLeft,
4101
+ disabled: props.disabled || !editor || !editorState.canToggleAlign,
4102
+ tooltip: "Text Align Left"
4103
+ },
4104
+ "text-align-left"
4105
+ ),
4106
+ textAlignCenterButton: /* @__PURE__ */ jsxRuntime.jsx(
4107
+ ToolbarButton,
4108
+ {
4109
+ onClick: () => setTextAlign("center"),
4110
+ icon: /* @__PURE__ */ jsxRuntime.jsx(TextAlignCenter, {}),
4111
+ active: editorState.isTextAlignCenter,
4112
+ disabled: props.disabled || !editor || !editorState.canToggleAlign,
4113
+ tooltip: "Text Align Center"
4114
+ },
4115
+ "text-align-center"
4116
+ ),
4117
+ textAlignRightButton: /* @__PURE__ */ jsxRuntime.jsx(
4118
+ ToolbarButton,
4119
+ {
4120
+ onClick: () => setTextAlign("right"),
4121
+ icon: /* @__PURE__ */ jsxRuntime.jsx(TextAlignRight, {}),
4122
+ active: editorState.isTextAlignRight,
4123
+ disabled: props.disabled || !editor || !editorState.canToggleAlign,
4124
+ tooltip: "Text Align Right"
4125
+ },
4126
+ "text-align-right"
4127
+ ),
4128
+ textAlignJustifyButton: /* @__PURE__ */ jsxRuntime.jsx(
4129
+ ToolbarButton,
4130
+ {
4131
+ onClick: () => setTextAlign("justify"),
4132
+ icon: /* @__PURE__ */ jsxRuntime.jsx(TextAlignJustify, {}),
4133
+ active: editorState.isTextAlignJustify,
4134
+ disabled: props.disabled || !editor || !editorState.canToggleAlign,
4135
+ tooltip: "Text Align Justify"
4136
+ },
4137
+ "text-align-justify"
4138
+ )
4139
+ };
4140
+ }
4141
+ function usePresetConfig(presetName) {
4142
+ const { get } = admin.useFetchClient();
4143
+ const normalizedPresetName = React.useMemo(
4144
+ () => presetName?.trim() || void 0,
4145
+ [presetName]
4146
+ );
4147
+ const [config, setConfig] = React.useState(
4148
+ normalizedPresetName ? null : MINIMAL_PRESET_CONFIG
4149
+ );
4150
+ const [isLoading, setIsLoading] = React.useState(Boolean(normalizedPresetName));
4151
+ React.useEffect(() => {
4152
+ let mounted = true;
4153
+ if (!normalizedPresetName) {
4154
+ setConfig(MINIMAL_PRESET_CONFIG);
4155
+ setIsLoading(false);
4156
+ return;
4157
+ }
4158
+ const fetchPreset = async () => {
4159
+ setIsLoading(true);
4160
+ try {
4161
+ const response = await get(
4162
+ `/api/tiptap-editor/presets/${normalizedPresetName}`
4163
+ );
4164
+ if (!mounted) return;
4165
+ setConfig(response.data || MINIMAL_PRESET_CONFIG);
4166
+ } catch (error) {
4167
+ console.warn(
4168
+ "[TiptapEditor] Failed to fetch preset config:",
4169
+ error
4170
+ );
4171
+ if (!mounted) return;
4172
+ setConfig(MINIMAL_PRESET_CONFIG);
4173
+ } finally {
4174
+ if (mounted) {
4175
+ setIsLoading(false);
4176
+ }
4177
+ }
4178
+ };
4179
+ fetchPreset();
4180
+ return () => {
4181
+ mounted = false;
4182
+ };
4183
+ }, [get, normalizedPresetName]);
4184
+ return { config, isLoading };
4185
+ }
4186
+ Extension.create({
4187
+ name: "characterCount",
4188
+ addOptions() {
4189
+ return {
4190
+ limit: null,
4191
+ mode: "textSize",
4192
+ textCounter: (text) => text.length,
4193
+ wordCounter: (text) => text.split(" ").filter((word) => word !== "").length
4194
+ };
4195
+ },
4196
+ addStorage() {
4197
+ return {
4198
+ characters: () => 0,
4199
+ words: () => 0
4200
+ };
4201
+ },
4202
+ onBeforeCreate() {
4203
+ this.storage.characters = (options) => {
4204
+ const node = (options == null ? void 0 : options.node) || this.editor.state.doc;
4205
+ const mode = (options == null ? void 0 : options.mode) || this.options.mode;
4206
+ if (mode === "textSize") {
4207
+ const text = node.textBetween(0, node.content.size, void 0, " ");
4208
+ return this.options.textCounter(text);
4209
+ }
4210
+ return node.nodeSize;
4211
+ };
4212
+ this.storage.words = (options) => {
4213
+ const node = (options == null ? void 0 : options.node) || this.editor.state.doc;
4214
+ const text = node.textBetween(0, node.content.size, " ", " ");
4215
+ return this.options.wordCounter(text);
4216
+ };
4217
+ },
4218
+ addProseMirrorPlugins() {
4219
+ let initialEvaluationDone = false;
4220
+ return [
4221
+ new state.Plugin({
4222
+ key: new state.PluginKey("characterCount"),
4223
+ appendTransaction: (transactions, oldState, newState) => {
4224
+ if (initialEvaluationDone) {
4225
+ return;
4226
+ }
4227
+ const limit = this.options.limit;
4228
+ if (limit === null || limit === void 0 || limit === 0) {
4229
+ initialEvaluationDone = true;
4230
+ return;
4231
+ }
4232
+ const initialContentSize = this.storage.characters({ node: newState.doc });
4233
+ if (initialContentSize > limit) {
4234
+ const over = initialContentSize - limit;
4235
+ const from = 0;
4236
+ const to = over;
4237
+ console.warn(
4238
+ `[CharacterCount] Initial content exceeded limit of ${limit} characters. Content was automatically trimmed.`
4239
+ );
4240
+ const tr = newState.tr.deleteRange(from, to);
4241
+ initialEvaluationDone = true;
4242
+ return tr;
4243
+ }
4244
+ initialEvaluationDone = true;
4245
+ },
4246
+ filterTransaction: (transaction, state2) => {
4247
+ const limit = this.options.limit;
4248
+ if (!transaction.docChanged || limit === 0 || limit === null || limit === void 0) {
4249
+ return true;
4250
+ }
4251
+ const oldSize = this.storage.characters({ node: state2.doc });
4252
+ const newSize = this.storage.characters({ node: transaction.doc });
4253
+ if (newSize <= limit) {
4254
+ return true;
4255
+ }
4256
+ if (oldSize > limit && newSize > limit && newSize <= oldSize) {
4257
+ return true;
4258
+ }
4259
+ if (oldSize > limit && newSize > limit && newSize > oldSize) {
4260
+ return false;
4261
+ }
4262
+ const isPaste = transaction.getMeta("paste");
4263
+ if (!isPaste) {
4264
+ return false;
4265
+ }
4266
+ const pos = transaction.selection.$head.pos;
4267
+ const over = newSize - limit;
4268
+ const from = pos - over;
4269
+ const to = pos;
4270
+ transaction.deleteRange(from, to);
4271
+ const updatedSize = this.storage.characters({ node: transaction.doc });
4272
+ if (updatedSize > limit) {
4273
+ return false;
4274
+ }
4275
+ return true;
4276
+ }
4277
+ })
4278
+ ];
4279
+ }
4280
+ });
4281
+ Extension.create({
4282
+ name: "dropCursor",
4283
+ addOptions() {
4284
+ return {
4285
+ color: "currentColor",
4286
+ width: 1,
4287
+ class: void 0
4288
+ };
4289
+ },
4290
+ addProseMirrorPlugins() {
4291
+ return [dropcursor.dropCursor(this.options)];
4292
+ }
4293
+ });
4294
+ Extension.create({
4295
+ name: "focus",
4296
+ addOptions() {
4297
+ return {
4298
+ className: "has-focus",
4299
+ mode: "all"
4300
+ };
4301
+ },
4302
+ addProseMirrorPlugins() {
4303
+ return [
4304
+ new state.Plugin({
4305
+ key: new state.PluginKey("focus"),
4306
+ props: {
4307
+ decorations: ({ doc, selection }) => {
4308
+ const { isEditable, isFocused } = this.editor;
4309
+ const { anchor } = selection;
4310
+ const decorations = [];
4311
+ if (!isEditable || !isFocused) {
4312
+ return view.DecorationSet.create(doc, []);
4313
+ }
4314
+ let maxLevels = 0;
4315
+ if (this.options.mode === "deepest") {
4316
+ doc.descendants((node, pos) => {
4317
+ if (node.isText) {
4318
+ return;
4319
+ }
4320
+ const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1;
4321
+ if (!isCurrent) {
4322
+ return false;
4323
+ }
4324
+ maxLevels += 1;
4325
+ });
4326
+ }
4327
+ let currentLevel = 0;
4328
+ doc.descendants((node, pos) => {
4329
+ if (node.isText) {
4330
+ return false;
4331
+ }
4332
+ const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1;
4333
+ if (!isCurrent) {
4334
+ return false;
4335
+ }
4336
+ currentLevel += 1;
4337
+ const outOfScope = this.options.mode === "deepest" && maxLevels - currentLevel > 0 || this.options.mode === "shallowest" && currentLevel > 1;
4338
+ if (outOfScope) {
4339
+ return this.options.mode === "deepest";
4340
+ }
4341
+ decorations.push(
4342
+ view.Decoration.node(pos, pos + node.nodeSize, {
4343
+ class: this.options.className
4344
+ })
4345
+ );
4346
+ });
4347
+ return view.DecorationSet.create(doc, decorations);
4348
+ }
4349
+ }
4350
+ })
4351
+ ];
4352
+ }
4353
+ });
4354
+ var Gapcursor = Extension.create({
4355
+ name: "gapCursor",
4356
+ addProseMirrorPlugins() {
4357
+ return [gapcursor.gapCursor()];
4358
+ },
4359
+ extendNodeSchema(extension) {
4360
+ var _a;
4361
+ const context = {
4362
+ name: extension.name,
4363
+ options: extension.options,
4364
+ storage: extension.storage
4365
+ };
4366
+ return {
4367
+ allowGapCursor: (_a = callOrReturn(getExtensionField(extension, "allowGapCursor", context))) != null ? _a : null
4368
+ };
4369
+ }
4370
+ });
4371
+ var DEFAULT_DATA_ATTRIBUTE = "placeholder";
4372
+ function preparePlaceholderAttribute(attr) {
4373
+ return attr.replace(/\s+/g, "-").replace(/[^a-zA-Z0-9-]/g, "").replace(/^[0-9-]+/, "").replace(/^-+/, "").toLowerCase();
4374
+ }
4375
+ Extension.create({
4376
+ name: "placeholder",
4377
+ addOptions() {
4378
+ return {
4379
+ emptyEditorClass: "is-editor-empty",
4380
+ emptyNodeClass: "is-empty",
4381
+ dataAttribute: DEFAULT_DATA_ATTRIBUTE,
4382
+ placeholder: "Write something …",
4383
+ showOnlyWhenEditable: true,
4384
+ showOnlyCurrent: true,
4385
+ includeChildren: false
4386
+ };
4387
+ },
4388
+ addProseMirrorPlugins() {
4389
+ const dataAttribute = this.options.dataAttribute ? `data-${preparePlaceholderAttribute(this.options.dataAttribute)}` : `data-${DEFAULT_DATA_ATTRIBUTE}`;
4390
+ return [
4391
+ new state.Plugin({
4392
+ key: new state.PluginKey("placeholder"),
4393
+ props: {
4394
+ decorations: ({ doc, selection }) => {
4395
+ const active = this.editor.isEditable || !this.options.showOnlyWhenEditable;
4396
+ const { anchor } = selection;
4397
+ const decorations = [];
4398
+ if (!active) {
4399
+ return null;
4400
+ }
4401
+ const isEmptyDoc = this.editor.isEmpty;
4402
+ doc.descendants((node, pos) => {
4403
+ const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize;
4404
+ const isEmpty = !node.isLeaf && isNodeEmpty(node);
4405
+ if ((hasAnchor || !this.options.showOnlyCurrent) && isEmpty) {
4406
+ const classes = [this.options.emptyNodeClass];
4407
+ if (isEmptyDoc) {
4408
+ classes.push(this.options.emptyEditorClass);
4409
+ }
4410
+ const decoration = view.Decoration.node(pos, pos + node.nodeSize, {
4411
+ class: classes.join(" "),
4412
+ [dataAttribute]: typeof this.options.placeholder === "function" ? this.options.placeholder({
4413
+ editor: this.editor,
4414
+ node,
4415
+ pos,
4416
+ hasAnchor
4417
+ }) : this.options.placeholder
4418
+ });
4419
+ decorations.push(decoration);
4420
+ }
4421
+ return this.options.includeChildren;
4422
+ });
4423
+ return view.DecorationSet.create(doc, decorations);
4424
+ }
4425
+ }
4426
+ })
4427
+ ];
4428
+ }
4429
+ });
4430
+ Extension.create({
4431
+ name: "selection",
4432
+ addOptions() {
4433
+ return {
4434
+ className: "selection"
4435
+ };
4436
+ },
4437
+ addProseMirrorPlugins() {
4438
+ const { editor, options } = this;
4439
+ return [
4440
+ new state.Plugin({
4441
+ key: new state.PluginKey("selection"),
4442
+ props: {
4443
+ decorations(state2) {
4444
+ if (state2.selection.empty || editor.isFocused || !editor.isEditable || isNodeSelection(state2.selection) || editor.view.dragging) {
4445
+ return null;
4446
+ }
4447
+ return view.DecorationSet.create(state2.doc, [
4448
+ view.Decoration.inline(state2.selection.from, state2.selection.to, {
4449
+ class: options.className
4450
+ })
4451
+ ]);
4452
+ }
4336
4453
  }
4337
- )
4338
- }
4339
- );
4454
+ })
4455
+ ];
4456
+ }
4457
+ });
4458
+ function nodeEqualsType({ types, node }) {
4459
+ return node && Array.isArray(types) && types.includes(node.type) || (node == null ? void 0 : node.type) === types;
4340
4460
  }
4341
- function TextAlignCenter(props) {
4342
- return /* @__PURE__ */ jsxRuntime.jsx(
4343
- "svg",
4344
- {
4345
- width: "16",
4346
- height: "16",
4347
- viewBox: "0 0 24 24",
4348
- fill: "currentColor",
4349
- xmlns: "http://www.w3.org/2000/svg",
4350
- ...props,
4351
- children: /* @__PURE__ */ jsxRuntime.jsx(
4352
- "path",
4353
- {
4354
- d: "M3 6H21M3 14H21M17 10H7M17 18H7",
4355
- stroke: "currentColor",
4356
- "stroke-width": "2",
4357
- "stroke-linecap": "round",
4358
- "stroke-linejoin": "round"
4461
+ Extension.create({
4462
+ name: "trailingNode",
4463
+ addOptions() {
4464
+ return {
4465
+ node: void 0,
4466
+ notAfter: []
4467
+ };
4468
+ },
4469
+ addProseMirrorPlugins() {
4470
+ var _a;
4471
+ const plugin = new state.PluginKey(this.name);
4472
+ const defaultNode = this.options.node || ((_a = this.editor.schema.topNodeType.contentMatch.defaultType) == null ? void 0 : _a.name) || "paragraph";
4473
+ const disabledNodes = Object.entries(this.editor.schema.nodes).map(([, value]) => value).filter((node) => (this.options.notAfter || []).concat(defaultNode).includes(node.name));
4474
+ return [
4475
+ new state.Plugin({
4476
+ key: plugin,
4477
+ appendTransaction: (_, __, state2) => {
4478
+ const { doc, tr, schema } = state2;
4479
+ const shouldInsertNodeAtEnd = plugin.getState(state2);
4480
+ const endPosition = doc.content.size;
4481
+ const type = schema.nodes[defaultNode];
4482
+ if (!shouldInsertNodeAtEnd) {
4483
+ return;
4484
+ }
4485
+ return tr.insert(endPosition, type.create());
4486
+ },
4487
+ state: {
4488
+ init: (_, state2) => {
4489
+ const lastNode = state2.tr.doc.lastChild;
4490
+ return !nodeEqualsType({ node: lastNode, types: disabledNodes });
4491
+ },
4492
+ apply: (tr, value) => {
4493
+ if (!tr.docChanged) {
4494
+ return value;
4495
+ }
4496
+ if (tr.getMeta("__uniqueIDTransaction")) {
4497
+ return value;
4498
+ }
4499
+ const lastNode = tr.doc.lastChild;
4500
+ return !nodeEqualsType({ node: lastNode, types: disabledNodes });
4501
+ }
4359
4502
  }
4360
- )
4361
- }
4362
- );
4363
- }
4364
- function useTextAlign(editor, props = { disabled: false }) {
4365
- const editorState = react.useEditorState({
4366
- editor,
4367
- selector: (ctx) => {
4368
- return {
4369
- isTextAlignLeft: ctx.editor.isActive({ textAlign: "left" }) ?? false,
4370
- isTextAlignRight: ctx.editor.isActive({ textAlign: "right" }) ?? false,
4371
- isTextAlignCenter: ctx.editor.isActive({ textAlign: "center" }) ?? false,
4372
- isTextAlignJustify: ctx.editor.isActive({ textAlign: "justify" }) ?? false,
4373
- canToggleAlign: ctx.editor.can().chain().setTextAlign("left").run() ?? false
4374
- };
4375
- }
4376
- });
4377
- const setTextAlign = (alignment) => {
4378
- editor.chain().focus().setTextAlign(alignment).run();
4379
- };
4380
- return {
4381
- textAlignLeftButton: /* @__PURE__ */ jsxRuntime.jsx(
4382
- ToolbarButton,
4383
- {
4384
- onClick: () => setTextAlign("left"),
4385
- icon: /* @__PURE__ */ jsxRuntime.jsx(TextAlignLeft, {}),
4386
- active: editorState.isTextAlignLeft,
4387
- disabled: props.disabled || !editor || !editorState.canToggleAlign,
4388
- tooltip: "Text Align Left"
4389
- },
4390
- "text-align-left"
4391
- ),
4392
- textAlignCenterButton: /* @__PURE__ */ jsxRuntime.jsx(
4393
- ToolbarButton,
4394
- {
4395
- onClick: () => setTextAlign("center"),
4396
- icon: /* @__PURE__ */ jsxRuntime.jsx(TextAlignCenter, {}),
4397
- active: editorState.isTextAlignCenter,
4398
- disabled: props.disabled || !editor || !editorState.canToggleAlign,
4399
- tooltip: "Text Align Center"
4400
- },
4401
- "text-align-center"
4402
- ),
4403
- textAlignRightButton: /* @__PURE__ */ jsxRuntime.jsx(
4404
- ToolbarButton,
4405
- {
4406
- onClick: () => setTextAlign("right"),
4407
- icon: /* @__PURE__ */ jsxRuntime.jsx(TextAlignRight, {}),
4408
- active: editorState.isTextAlignRight,
4409
- disabled: props.disabled || !editor || !editorState.canToggleAlign,
4410
- tooltip: "Text Align Right"
4411
- },
4412
- "text-align-right"
4413
- ),
4414
- textAlignJustifyButton: /* @__PURE__ */ jsxRuntime.jsx(
4415
- ToolbarButton,
4416
- {
4417
- onClick: () => setTextAlign("justify"),
4418
- icon: /* @__PURE__ */ jsxRuntime.jsx(TextAlignJustify, {}),
4419
- active: editorState.isTextAlignJustify,
4420
- disabled: props.disabled || !editor || !editorState.canToggleAlign,
4421
- tooltip: "Text Align Justify"
4503
+ })
4504
+ ];
4505
+ }
4506
+ });
4507
+ Extension.create({
4508
+ name: "undoRedo",
4509
+ addOptions() {
4510
+ return {
4511
+ depth: 100,
4512
+ newGroupDelay: 500
4513
+ };
4514
+ },
4515
+ addCommands() {
4516
+ return {
4517
+ undo: () => ({ state: state2, dispatch }) => {
4518
+ return history.undo(state2, dispatch);
4422
4519
  },
4423
- "text-align-justify"
4424
- )
4425
- };
4426
- }
4427
- const extensions = [
4428
- StarterKit__default.default.configure({
4520
+ redo: () => ({ state: state2, dispatch }) => {
4521
+ return history.redo(state2, dispatch);
4522
+ }
4523
+ };
4524
+ },
4525
+ addProseMirrorPlugins() {
4526
+ return [history.history(this.options)];
4527
+ },
4528
+ addKeyboardShortcuts() {
4529
+ return {
4530
+ "Mod-z": () => this.editor.commands.undo(),
4531
+ "Shift-Mod-z": () => this.editor.commands.redo(),
4532
+ "Mod-y": () => this.editor.commands.redo(),
4533
+ // Russian keyboard layouts
4534
+ "Mod-я": () => this.editor.commands.undo(),
4535
+ "Shift-Mod-я": () => this.editor.commands.redo()
4536
+ };
4537
+ }
4538
+ });
4539
+ const starterKitFeatureValue = (value) => {
4540
+ if (!isFeatureEnabled(value)) {
4541
+ return false;
4542
+ }
4543
+ return getFeatureOptions(value, {}) ?? {};
4544
+ };
4545
+ function buildExtensions(config) {
4546
+ const starterKitConfig = {
4429
4547
  heading: false,
4430
- // disable default so we can use our custom version
4431
- link: {
4432
- openOnClick: false
4548
+ // ALWAYS false heading handled separately via BaseHeadingWithSEOTag
4549
+ bold: starterKitFeatureValue(config.bold),
4550
+ italic: starterKitFeatureValue(config.italic),
4551
+ strike: starterKitFeatureValue(config.strike),
4552
+ code: starterKitFeatureValue(config.code),
4553
+ codeBlock: starterKitFeatureValue(config.codeBlock),
4554
+ blockquote: starterKitFeatureValue(config.blockquote),
4555
+ bulletList: starterKitFeatureValue(config.bulletList),
4556
+ orderedList: starterKitFeatureValue(config.orderedList),
4557
+ hardBreak: starterKitFeatureValue(config.hardBreak),
4558
+ horizontalRule: starterKitFeatureValue(config.horizontalRule),
4559
+ history: starterKitFeatureValue(config.history),
4560
+ link: !isFeatureEnabled(config.link) ? false : {
4561
+ openOnClick: false,
4562
+ ...getFeatureOptions(config.link, {})
4433
4563
  }
4434
- }),
4435
- HeadingWithSEOTag,
4436
- Superscript__default.default,
4437
- Subscript__default.default,
4438
- Gapcursor,
4439
- // cursor for resizing tables
4440
- extensionTable.TableKit.configure({
4441
- table: { resizable: true }
4442
- }),
4443
- TextAlign__default.default.configure({
4444
- types: ["heading", "paragraph"]
4445
- })
4446
- ];
4564
+ };
4565
+ const extensions = [
4566
+ StarterKit__default.default.configure(starterKitConfig)
4567
+ ];
4568
+ if (isFeatureEnabled(config.heading)) {
4569
+ const headingConfig = getFeatureOptions(config.heading, {
4570
+ levels: [1, 2, 3, 4]
4571
+ });
4572
+ const levels = headingConfig?.levels || [1, 2, 3, 4];
4573
+ extensions.push(BaseHeadingWithSEOTag.configure({ levels }));
4574
+ }
4575
+ if (isFeatureEnabled(config.superscript)) {
4576
+ extensions.push(Superscript__default.default);
4577
+ }
4578
+ if (isFeatureEnabled(config.subscript)) {
4579
+ extensions.push(Subscript__default.default);
4580
+ }
4581
+ if (isFeatureEnabled(config.table)) {
4582
+ extensions.push(
4583
+ extensionTable.TableKit.configure({
4584
+ table: {
4585
+ resizable: true,
4586
+ ...getFeatureOptions(config.table, {})
4587
+ }
4588
+ })
4589
+ );
4590
+ }
4591
+ if (isFeatureEnabled(config.textAlign)) {
4592
+ const textAlignConfig = getFeatureOptions(config.textAlign, {
4593
+ types: ["heading", "paragraph"],
4594
+ alignments: ["left", "center", "right", "justify"]
4595
+ });
4596
+ extensions.push(
4597
+ TextAlign__default.default.configure({
4598
+ types: textAlignConfig?.types || ["heading", "paragraph"],
4599
+ alignments: textAlignConfig?.alignments || [
4600
+ "left",
4601
+ "center",
4602
+ "right",
4603
+ "justify"
4604
+ ]
4605
+ })
4606
+ );
4607
+ }
4608
+ extensions.push(Gapcursor);
4609
+ return extensions;
4610
+ }
4447
4611
  const RichTextInput = React.forwardRef((props, forwardedRef) => {
4612
+ const attribute = props.attribute;
4613
+ const presetName = attribute?.options?.preset;
4614
+ const { config, isLoading } = usePresetConfig(presetName);
4615
+ const extensions = React.useMemo(() => {
4616
+ if (!config) return [];
4617
+ return buildExtensions(config);
4618
+ }, [presetName]);
4448
4619
  const { editor, field } = useTiptapEditor(props.name, "", extensions);
4449
4620
  const starterKit = useStarterKit(editor, { disabled: props.disabled });
4450
4621
  const heading = useHeading(editor, { disabled: props.disabled });
@@ -4452,37 +4623,56 @@ const RichTextInput = React.forwardRef((props, forwardedRef) => {
4452
4623
  const script = useScript(editor, { disabled: props.disabled });
4453
4624
  const table = useTable(editor, { disabled: props.disabled });
4454
4625
  const textAlign = useTextAlign(editor, { disabled: props.disabled });
4455
- return /* @__PURE__ */ jsxRuntime.jsxs(BaseTiptapInput, { editor, field, ...props, ref: forwardedRef, children: [
4456
- heading.headingSelect,
4457
- heading.headingTagSelect,
4458
- /* @__PURE__ */ jsxRuntime.jsx(Spacer, { width: 8 }),
4459
- starterKit.boldButton,
4460
- starterKit.italicButton,
4461
- starterKit.underlineButton,
4462
- starterKit.strikeButton,
4463
- script.superscriptButton,
4464
- script.subscriptButton,
4465
- /* @__PURE__ */ jsxRuntime.jsx(Spacer, { width: 8 }),
4466
- textAlign.textAlignLeftButton,
4467
- textAlign.textAlignCenterButton,
4468
- textAlign.textAlignRightButton,
4469
- textAlign.textAlignJustifyButton,
4470
- /* @__PURE__ */ jsxRuntime.jsx(Spacer, { width: 8 }),
4471
- starterKit.bulletButton,
4472
- starterKit.orderedButton,
4473
- /* @__PURE__ */ jsxRuntime.jsx(Spacer, { width: 8 }),
4474
- starterKit.codeButton,
4475
- starterKit.blockquoteButton,
4476
- link.linkButton,
4477
- link.linkDialog,
4478
- /* @__PURE__ */ jsxRuntime.jsx(Spacer, { width: 8 }),
4479
- table.tableButton,
4480
- table.addColumnButton,
4481
- table.removeColumnButton,
4482
- table.addRowButton,
4483
- table.removeRowButton,
4484
- table.tableDialog
4485
- ] });
4626
+ if (isLoading) {
4627
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 4, children: "Loading editor..." });
4628
+ }
4629
+ return /* @__PURE__ */ jsxRuntime.jsx(EditorErrorBoundary, { children: /* @__PURE__ */ jsxRuntime.jsxs(
4630
+ BaseTiptapInput,
4631
+ {
4632
+ editor,
4633
+ field,
4634
+ ...props,
4635
+ ref: forwardedRef,
4636
+ noPresetConfigured: !presetName,
4637
+ children: [
4638
+ /* @__PURE__ */ jsxRuntime.jsxs(FeatureGuard, { featureValue: config?.heading, children: [
4639
+ heading.headingSelect,
4640
+ heading.headingTagSelect,
4641
+ /* @__PURE__ */ jsxRuntime.jsx(Spacer, { width: 8 })
4642
+ ] }),
4643
+ starterKit.boldButton,
4644
+ starterKit.italicButton,
4645
+ starterKit.underlineButton,
4646
+ starterKit.strikeButton,
4647
+ script.superscriptButton,
4648
+ script.subscriptButton,
4649
+ /* @__PURE__ */ jsxRuntime.jsx(Spacer, { width: 8 }),
4650
+ /* @__PURE__ */ jsxRuntime.jsxs(FeatureGuard, { featureValue: config?.textAlign, children: [
4651
+ textAlign.textAlignLeftButton,
4652
+ textAlign.textAlignCenterButton,
4653
+ textAlign.textAlignRightButton,
4654
+ textAlign.textAlignJustifyButton,
4655
+ /* @__PURE__ */ jsxRuntime.jsx(Spacer, { width: 8 })
4656
+ ] }),
4657
+ starterKit.bulletButton,
4658
+ starterKit.orderedButton,
4659
+ /* @__PURE__ */ jsxRuntime.jsx(Spacer, { width: 8 }),
4660
+ starterKit.codeButton,
4661
+ starterKit.blockquoteButton,
4662
+ link.linkButton,
4663
+ link.linkDialog,
4664
+ /* @__PURE__ */ jsxRuntime.jsxs(FeatureGuard, { featureValue: config?.table, children: [
4665
+ /* @__PURE__ */ jsxRuntime.jsx(Spacer, { width: 8 }),
4666
+ table.tableButton,
4667
+ table.addColumnButton,
4668
+ table.removeColumnButton,
4669
+ table.addRowButton,
4670
+ table.removeRowButton,
4671
+ table.tableDialog
4672
+ ] })
4673
+ ]
4674
+ }
4675
+ ) });
4486
4676
  });
4487
4677
  exports.default = RichTextInput;
4488
- //# sourceMappingURL=RichTextInput-BlxoJMa2.js.map
4678
+ //# sourceMappingURL=RichTextInput-Wc2q__HY.js.map