@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,10 +1,9 @@
1
1
  import { jsxs, jsx, Fragment as Fragment$1 } from "react/jsx-runtime";
2
- import { Field, Box, Flex, Tooltip, Button, Dialog, TextInput, SingleSelect, SingleSelectOption } from "@strapi/design-system";
2
+ import React, { forwardRef, Component, useState, useRef, useEffect, useMemo } from "react";
3
+ import { Field, Box, Status, Typography, Flex, Button, Tooltip, Dialog, TextInput, SingleSelect, SingleSelectOption } from "@strapi/design-system";
3
4
  import { EditorContent, useEditor, useEditorState } from "@tiptap/react";
4
5
  import styled from "styled-components";
5
- import React, { forwardRef, useState, useRef, useEffect } from "react";
6
- import { useField } from "@strapi/strapi/admin";
7
- import StarterKit from "@tiptap/starter-kit";
6
+ import { useField, useFetchClient } from "@strapi/strapi/admin";
8
7
  import { Quotes, Code, NumberList, BulletList, StrikeThrough, Underline, Italic, Bold, Link, GridNine } from "@strapi/icons";
9
8
  import { RemoveMarkStep, Transform, liftTarget, joinPoint, canSplit, ReplaceStep, ReplaceAroundStep, canJoin } from "@tiptap/pm/transform";
10
9
  import { createParagraphNear as createParagraphNear$1, deleteSelection as deleteSelection$1, exitCode as exitCode$1, joinUp as joinUp$1, joinDown as joinDown$1, joinBackward as joinBackward$1, joinForward as joinForward$1, joinTextblockBackward as joinTextblockBackward$1, joinTextblockForward as joinTextblockForward$1, lift as lift$1, liftEmptyBlock as liftEmptyBlock$1, newlineInCode as newlineInCode$1, selectNodeBackward as selectNodeBackward$1, selectNodeForward as selectNodeForward$1, selectParentNode as selectParentNode$1, selectTextblockEnd as selectTextblockEnd$1, selectTextblockStart as selectTextblockStart$1, setBlockType, wrapIn as wrapIn$1 } from "@tiptap/pm/commands";
@@ -13,13 +12,14 @@ import { Fragment, Slice, Node, Schema, DOMParser } from "@tiptap/pm/model";
13
12
  import { liftListItem as liftListItem$1, sinkListItem as sinkListItem$1, wrapInList as wrapInList$1 } from "@tiptap/pm/schema-list";
14
13
  import { DecorationSet, Decoration } from "@tiptap/pm/view";
15
14
  import "@tiptap/pm/keymap";
15
+ import StarterKit from "@tiptap/starter-kit";
16
16
  import Superscript from "@tiptap/extension-superscript";
17
17
  import Subscript from "@tiptap/extension-subscript";
18
18
  import { TableKit } from "@tiptap/extension-table";
19
+ import TextAlign from "@tiptap/extension-text-align";
19
20
  import { dropCursor } from "@tiptap/pm/dropcursor";
20
21
  import { gapCursor } from "@tiptap/pm/gapcursor";
21
22
  import { history, redo, undo } from "@tiptap/pm/history";
22
- import TextAlign from "@tiptap/extension-text-align";
23
23
  const TiptapInputStyles = styled.div`
24
24
  .editor-toolbar {
25
25
  margin-bottom: 0.25rem;
@@ -207,7 +207,7 @@ const TiptapInputStyles = styled.div`
207
207
  }
208
208
  `;
209
209
  const BaseTiptapInput = forwardRef(
210
- ({ hint, disabled = false, labelAction, label, name, required = false, editor, field, children }, forwardedRef) => {
210
+ ({ hint, disabled = false, labelAction, label, name, required = false, editor, field, children, noPresetConfigured }, forwardedRef) => {
211
211
  const borderColor = field.error ? "danger600" : "neutral200";
212
212
  const background = disabled ? "neutral200" : "neutral100";
213
213
  return /* @__PURE__ */ jsxs(Field.Root, { name, id: name, hint, error: field.error, required, children: [
@@ -224,6 +224,7 @@ const BaseTiptapInput = forwardRef(
224
224
  paddingLeft: 0,
225
225
  paddingRight: 0,
226
226
  children: [
227
+ noPresetConfigured && /* @__PURE__ */ jsx(Box, { paddingLeft: 2, paddingRight: 2, paddingTop: 2, children: /* @__PURE__ */ jsx(Status, { variant: "secondary", showBullet: false, children: /* @__PURE__ */ jsx(Typography, { variant: "pi", children: "No editor preset configured — showing minimal editor" }) }) }),
227
228
  /* @__PURE__ */ jsx(Box, { className: "editor-toolbar", paddingLeft: 2, paddingRight: 2, paddingBottom: 2, children: /* @__PURE__ */ jsx(Flex, { gap: 1, wrap: "wrap", children }) }),
228
229
  /* @__PURE__ */ jsx(
229
230
  Box,
@@ -245,6 +246,71 @@ const BaseTiptapInput = forwardRef(
245
246
  ] });
246
247
  }
247
248
  );
249
+ class EditorErrorBoundary extends Component {
250
+ state = { hasError: false, error: null };
251
+ static getDerivedStateFromError(error) {
252
+ return { hasError: true, error };
253
+ }
254
+ componentDidCatch(error, errorInfo) {
255
+ console.error("[TiptapEditor] Editor crashed:", error, errorInfo);
256
+ }
257
+ handleRetry = () => {
258
+ this.setState({ hasError: false, error: null });
259
+ };
260
+ render() {
261
+ if (this.state.hasError) {
262
+ return /* @__PURE__ */ jsxs(Box, { padding: 4, background: "danger100", hasRadius: true, children: [
263
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "danger700", children: "The editor encountered an error and could not render." }),
264
+ /* @__PURE__ */ jsx(Box, { marginTop: 2, children: /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "S", onClick: this.handleRetry, children: "Retry" }) })
265
+ ] });
266
+ }
267
+ return this.props.children;
268
+ }
269
+ }
270
+ const MINIMAL_PRESET_CONFIG = {
271
+ bold: true,
272
+ italic: true
273
+ };
274
+ const isPlainObject$1 = (value) => {
275
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
276
+ const prototype = Object.getPrototypeOf(value);
277
+ return prototype === Object.prototype || prototype === null;
278
+ };
279
+ const isFeatureEnabled = (value) => {
280
+ if (value === void 0) {
281
+ return true;
282
+ }
283
+ if (typeof value === "boolean") {
284
+ return value;
285
+ }
286
+ if (!isPlainObject$1(value)) {
287
+ return false;
288
+ }
289
+ const obj = value;
290
+ if (typeof obj.enabled === "boolean") {
291
+ return obj.enabled;
292
+ }
293
+ if (typeof obj.disabled === "boolean") {
294
+ return !obj.disabled;
295
+ }
296
+ return true;
297
+ };
298
+ const getFeatureOptions = (value, defaults) => {
299
+ if (value === false) {
300
+ return null;
301
+ }
302
+ if (!isPlainObject$1(value)) {
303
+ return defaults;
304
+ }
305
+ const { enabled: _e, disabled: _d, ...rest } = value;
306
+ return { ...defaults, ...rest };
307
+ };
308
+ function FeatureGuard({ featureValue, children }) {
309
+ if (!isFeatureEnabled(featureValue)) {
310
+ return null;
311
+ }
312
+ return children;
313
+ }
248
314
  function tiptapContent(text) {
249
315
  return {
250
316
  type: "doc",
@@ -275,10 +341,10 @@ function parseJSONContent(value, defaultValue) {
275
341
  `);
276
342
  }
277
343
  }
278
- function useTiptapEditor(name, defaultValue = "", extensions2 = []) {
344
+ function useTiptapEditor(name, defaultValue = "", extensions = []) {
279
345
  const field = useField(name);
280
346
  const editor = useEditor({
281
- extensions: extensions2,
347
+ extensions,
282
348
  content: parseJSONContent(field.value, defaultValue),
283
349
  onUpdate: ({ editor: editor2 }) => {
284
350
  const json = editor2.getJSON();
@@ -1670,10 +1736,10 @@ function callOrReturn(value, context = void 0, ...props) {
1670
1736
  }
1671
1737
  return value;
1672
1738
  }
1673
- function splitExtensions(extensions2) {
1674
- const baseExtensions = extensions2.filter((extension) => extension.type === "extension");
1675
- const nodeExtensions = extensions2.filter((extension) => extension.type === "node");
1676
- const markExtensions = extensions2.filter((extension) => extension.type === "mark");
1739
+ function splitExtensions(extensions) {
1740
+ const baseExtensions = extensions.filter((extension) => extension.type === "extension");
1741
+ const nodeExtensions = extensions.filter((extension) => extension.type === "node");
1742
+ const markExtensions = extensions.filter((extension) => extension.type === "mark");
1677
1743
  return {
1678
1744
  baseExtensions,
1679
1745
  nodeExtensions,
@@ -1862,8 +1928,8 @@ function isMarkActive(state, typeOrName, attributes = {}) {
1862
1928
  const range = matchedRange > 0 ? matchedRange + excludedRange : matchedRange;
1863
1929
  return range >= selectionRange;
1864
1930
  }
1865
- function isList(name, extensions2) {
1866
- const { nodeExtensions } = splitExtensions(extensions2);
1931
+ function isList(name, extensions) {
1932
+ const { nodeExtensions } = splitExtensions(extensions);
1867
1933
  const extension = nodeExtensions.find((item) => item.name === name);
1868
1934
  if (!extension) {
1869
1935
  return false;
@@ -2249,7 +2315,7 @@ var joinListForwards = (tr, listType) => {
2249
2315
  return true;
2250
2316
  };
2251
2317
  var toggleList = (listTypeOrName, itemTypeOrName, keepMarks, attributes = {}) => ({ editor, tr, state, dispatch, chain, commands, can }) => {
2252
- const { extensions: extensions2, splittableMarks } = editor.extensionManager;
2318
+ const { extensions, splittableMarks } = editor.extensionManager;
2253
2319
  const listType = getNodeType(listTypeOrName, state.schema);
2254
2320
  const itemType = getNodeType(itemTypeOrName, state.schema);
2255
2321
  const { selection, storedMarks } = state;
@@ -2259,12 +2325,12 @@ var toggleList = (listTypeOrName, itemTypeOrName, keepMarks, attributes = {}) =>
2259
2325
  if (!range) {
2260
2326
  return false;
2261
2327
  }
2262
- const parentList = findParentNode((node) => isList(node.type.name, extensions2))(selection);
2328
+ const parentList = findParentNode((node) => isList(node.type.name, extensions))(selection);
2263
2329
  if (range.depth >= 1 && parentList && range.depth - parentList.depth <= 1) {
2264
2330
  if (parentList.node.type === listType) {
2265
2331
  return commands.liftListItem(itemType);
2266
2332
  }
2267
- if (isList(parentList.node.type.name, extensions2) && listType.validContent(parentList.node.content) && dispatch) {
2333
+ if (isList(parentList.node.type.name, extensions) && listType.validContent(parentList.node.content) && dispatch) {
2268
2334
  return chain().command(() => {
2269
2335
  tr.setNodeMarkup(parentList.pos, listType);
2270
2336
  return true;
@@ -3599,7 +3665,7 @@ var Heading = Node3.create({
3599
3665
  }
3600
3666
  });
3601
3667
  var index_default = Heading;
3602
- const HeadingWithSEOTag = index_default.extend({
3668
+ const BaseHeadingWithSEOTag = index_default.extend({
3603
3669
  addAttributes() {
3604
3670
  return {
3605
3671
  ...this.parent?.(),
@@ -3607,7 +3673,8 @@ const HeadingWithSEOTag = index_default.extend({
3607
3673
  tag: { default: null }
3608
3674
  };
3609
3675
  }
3610
- }).configure({ levels: [1, 2, 3, 4] });
3676
+ });
3677
+ BaseHeadingWithSEOTag.configure({ levels: [1, 2, 3, 4] });
3611
3678
  function useHeading(editor, props = { disabled: false }) {
3612
3679
  const editorState = useEditorState({
3613
3680
  editor,
@@ -3726,425 +3793,72 @@ function useScript(editor, props = { disabled: false }) {
3726
3793
  )
3727
3794
  };
3728
3795
  }
3729
- Extension.create({
3730
- name: "characterCount",
3731
- addOptions() {
3732
- return {
3733
- limit: null,
3734
- mode: "textSize",
3735
- textCounter: (text) => text.length,
3736
- wordCounter: (text) => text.split(" ").filter((word) => word !== "").length
3737
- };
3738
- },
3739
- addStorage() {
3740
- return {
3741
- characters: () => 0,
3742
- words: () => 0
3743
- };
3744
- },
3745
- onBeforeCreate() {
3746
- this.storage.characters = (options) => {
3747
- const node = (options == null ? void 0 : options.node) || this.editor.state.doc;
3748
- const mode = (options == null ? void 0 : options.mode) || this.options.mode;
3749
- if (mode === "textSize") {
3750
- const text = node.textBetween(0, node.content.size, void 0, " ");
3751
- return this.options.textCounter(text);
3752
- }
3753
- return node.nodeSize;
3754
- };
3755
- this.storage.words = (options) => {
3756
- const node = (options == null ? void 0 : options.node) || this.editor.state.doc;
3757
- const text = node.textBetween(0, node.content.size, " ", " ");
3758
- return this.options.wordCounter(text);
3759
- };
3760
- },
3761
- addProseMirrorPlugins() {
3762
- let initialEvaluationDone = false;
3763
- return [
3764
- new Plugin({
3765
- key: new PluginKey("characterCount"),
3766
- appendTransaction: (transactions, oldState, newState) => {
3767
- if (initialEvaluationDone) {
3768
- return;
3769
- }
3770
- const limit = this.options.limit;
3771
- if (limit === null || limit === void 0 || limit === 0) {
3772
- initialEvaluationDone = true;
3773
- return;
3774
- }
3775
- const initialContentSize = this.storage.characters({ node: newState.doc });
3776
- if (initialContentSize > limit) {
3777
- const over = initialContentSize - limit;
3778
- const from = 0;
3779
- const to = over;
3780
- console.warn(
3781
- `[CharacterCount] Initial content exceeded limit of ${limit} characters. Content was automatically trimmed.`
3782
- );
3783
- const tr = newState.tr.deleteRange(from, to);
3784
- initialEvaluationDone = true;
3785
- return tr;
3786
- }
3787
- initialEvaluationDone = true;
3788
- },
3789
- filterTransaction: (transaction, state) => {
3790
- const limit = this.options.limit;
3791
- if (!transaction.docChanged || limit === 0 || limit === null || limit === void 0) {
3792
- return true;
3793
- }
3794
- const oldSize = this.storage.characters({ node: state.doc });
3795
- const newSize = this.storage.characters({ node: transaction.doc });
3796
- if (newSize <= limit) {
3797
- return true;
3798
- }
3799
- if (oldSize > limit && newSize > limit && newSize <= oldSize) {
3800
- return true;
3801
- }
3802
- if (oldSize > limit && newSize > limit && newSize > oldSize) {
3803
- return false;
3804
- }
3805
- const isPaste = transaction.getMeta("paste");
3806
- if (!isPaste) {
3807
- return false;
3808
- }
3809
- const pos = transaction.selection.$head.pos;
3810
- const over = newSize - limit;
3811
- const from = pos - over;
3812
- const to = pos;
3813
- transaction.deleteRange(from, to);
3814
- const updatedSize = this.storage.characters({ node: transaction.doc });
3815
- if (updatedSize > limit) {
3816
- return false;
3817
- }
3818
- return true;
3819
- }
3820
- })
3821
- ];
3822
- }
3823
- });
3824
- Extension.create({
3825
- name: "dropCursor",
3826
- addOptions() {
3827
- return {
3828
- color: "currentColor",
3829
- width: 1,
3830
- class: void 0
3831
- };
3832
- },
3833
- addProseMirrorPlugins() {
3834
- return [dropCursor(this.options)];
3835
- }
3836
- });
3837
- Extension.create({
3838
- name: "focus",
3839
- addOptions() {
3840
- return {
3841
- className: "has-focus",
3842
- mode: "all"
3843
- };
3844
- },
3845
- addProseMirrorPlugins() {
3846
- return [
3847
- new Plugin({
3848
- key: new PluginKey("focus"),
3849
- props: {
3850
- decorations: ({ doc, selection }) => {
3851
- const { isEditable, isFocused } = this.editor;
3852
- const { anchor } = selection;
3853
- const decorations = [];
3854
- if (!isEditable || !isFocused) {
3855
- return DecorationSet.create(doc, []);
3856
- }
3857
- let maxLevels = 0;
3858
- if (this.options.mode === "deepest") {
3859
- doc.descendants((node, pos) => {
3860
- if (node.isText) {
3861
- return;
3862
- }
3863
- const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1;
3864
- if (!isCurrent) {
3865
- return false;
3866
- }
3867
- maxLevels += 1;
3868
- });
3869
- }
3870
- let currentLevel = 0;
3871
- doc.descendants((node, pos) => {
3872
- if (node.isText) {
3873
- return false;
3874
- }
3875
- const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1;
3876
- if (!isCurrent) {
3877
- return false;
3796
+ const clamp = (val, min, max) => Math.max(min, Math.min(max, val));
3797
+ const parseNum = (value, fallback) => {
3798
+ const n = Number(value);
3799
+ return Number.isFinite(n) ? n : fallback;
3800
+ };
3801
+ const TableSizeDialog = ({
3802
+ open,
3803
+ defaultRows = 3,
3804
+ defaultCols = 3,
3805
+ onClose,
3806
+ onSave
3807
+ }) => {
3808
+ const [rows, setRows] = useState(defaultRows);
3809
+ const [cols, setCols] = useState(defaultCols);
3810
+ useEffect(() => {
3811
+ if (open) {
3812
+ setRows(defaultRows);
3813
+ setCols(defaultCols);
3814
+ }
3815
+ }, [open, defaultRows, defaultCols]);
3816
+ const min = 1;
3817
+ const max = 10;
3818
+ const isValid = rows >= min && rows <= max && cols >= min && cols <= max;
3819
+ const handleSave = () => {
3820
+ if (!isValid) return;
3821
+ onSave(clamp(rows, min, max), clamp(cols, min, max));
3822
+ };
3823
+ return /* @__PURE__ */ jsx(
3824
+ Dialog.Root,
3825
+ {
3826
+ open,
3827
+ onOpenChange: (v) => {
3828
+ if (!v) onClose();
3829
+ },
3830
+ children: open && /* @__PURE__ */ jsxs(Dialog.Content, { children: [
3831
+ /* @__PURE__ */ jsx(Dialog.Header, { children: "Insert table" }),
3832
+ /* @__PURE__ */ jsx(Dialog.Body, { children: /* @__PURE__ */ jsxs(Flex, { gap: 4, alignItems: "flex-end", children: [
3833
+ /* @__PURE__ */ jsxs(Field.Root, { width: "100%", children: [
3834
+ /* @__PURE__ */ jsx(Field.Label, { children: "Rows" }),
3835
+ /* @__PURE__ */ jsx(
3836
+ TextInput,
3837
+ {
3838
+ name: "table-rows",
3839
+ type: "number",
3840
+ value: String(rows),
3841
+ onChange: (e) => setRows(clamp(parseNum(e.target.value, rows), min, max)),
3842
+ placeholder: String(defaultRows)
3878
3843
  }
3879
- currentLevel += 1;
3880
- const outOfScope = this.options.mode === "deepest" && maxLevels - currentLevel > 0 || this.options.mode === "shallowest" && currentLevel > 1;
3881
- if (outOfScope) {
3882
- return this.options.mode === "deepest";
3883
- }
3884
- decorations.push(
3885
- Decoration.node(pos, pos + node.nodeSize, {
3886
- class: this.options.className
3887
- })
3888
- );
3889
- });
3890
- return DecorationSet.create(doc, decorations);
3891
- }
3892
- }
3893
- })
3894
- ];
3895
- }
3896
- });
3897
- var Gapcursor = Extension.create({
3898
- name: "gapCursor",
3899
- addProseMirrorPlugins() {
3900
- return [gapCursor()];
3901
- },
3902
- extendNodeSchema(extension) {
3903
- var _a;
3904
- const context = {
3905
- name: extension.name,
3906
- options: extension.options,
3907
- storage: extension.storage
3908
- };
3909
- return {
3910
- allowGapCursor: (_a = callOrReturn(getExtensionField(extension, "allowGapCursor", context))) != null ? _a : null
3911
- };
3912
- }
3913
- });
3914
- var DEFAULT_DATA_ATTRIBUTE = "placeholder";
3915
- function preparePlaceholderAttribute(attr) {
3916
- return attr.replace(/\s+/g, "-").replace(/[^a-zA-Z0-9-]/g, "").replace(/^[0-9-]+/, "").replace(/^-+/, "").toLowerCase();
3917
- }
3918
- Extension.create({
3919
- name: "placeholder",
3920
- addOptions() {
3921
- return {
3922
- emptyEditorClass: "is-editor-empty",
3923
- emptyNodeClass: "is-empty",
3924
- dataAttribute: DEFAULT_DATA_ATTRIBUTE,
3925
- placeholder: "Write something …",
3926
- showOnlyWhenEditable: true,
3927
- showOnlyCurrent: true,
3928
- includeChildren: false
3929
- };
3930
- },
3931
- addProseMirrorPlugins() {
3932
- const dataAttribute = this.options.dataAttribute ? `data-${preparePlaceholderAttribute(this.options.dataAttribute)}` : `data-${DEFAULT_DATA_ATTRIBUTE}`;
3933
- return [
3934
- new Plugin({
3935
- key: new PluginKey("placeholder"),
3936
- props: {
3937
- decorations: ({ doc, selection }) => {
3938
- const active = this.editor.isEditable || !this.options.showOnlyWhenEditable;
3939
- const { anchor } = selection;
3940
- const decorations = [];
3941
- if (!active) {
3942
- return null;
3943
- }
3944
- const isEmptyDoc = this.editor.isEmpty;
3945
- doc.descendants((node, pos) => {
3946
- const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize;
3947
- const isEmpty = !node.isLeaf && isNodeEmpty(node);
3948
- if ((hasAnchor || !this.options.showOnlyCurrent) && isEmpty) {
3949
- const classes = [this.options.emptyNodeClass];
3950
- if (isEmptyDoc) {
3951
- classes.push(this.options.emptyEditorClass);
3952
- }
3953
- const decoration = Decoration.node(pos, pos + node.nodeSize, {
3954
- class: classes.join(" "),
3955
- [dataAttribute]: typeof this.options.placeholder === "function" ? this.options.placeholder({
3956
- editor: this.editor,
3957
- node,
3958
- pos,
3959
- hasAnchor
3960
- }) : this.options.placeholder
3961
- });
3962
- decorations.push(decoration);
3963
- }
3964
- return this.options.includeChildren;
3965
- });
3966
- return DecorationSet.create(doc, decorations);
3967
- }
3968
- }
3969
- })
3970
- ];
3971
- }
3972
- });
3973
- Extension.create({
3974
- name: "selection",
3975
- addOptions() {
3976
- return {
3977
- className: "selection"
3978
- };
3979
- },
3980
- addProseMirrorPlugins() {
3981
- const { editor, options } = this;
3982
- return [
3983
- new Plugin({
3984
- key: new PluginKey("selection"),
3985
- props: {
3986
- decorations(state) {
3987
- if (state.selection.empty || editor.isFocused || !editor.isEditable || isNodeSelection(state.selection) || editor.view.dragging) {
3988
- return null;
3989
- }
3990
- return DecorationSet.create(state.doc, [
3991
- Decoration.inline(state.selection.from, state.selection.to, {
3992
- class: options.className
3993
- })
3994
- ]);
3995
- }
3996
- }
3997
- })
3998
- ];
3999
- }
4000
- });
4001
- function nodeEqualsType({ types, node }) {
4002
- return node && Array.isArray(types) && types.includes(node.type) || (node == null ? void 0 : node.type) === types;
4003
- }
4004
- Extension.create({
4005
- name: "trailingNode",
4006
- addOptions() {
4007
- return {
4008
- node: void 0,
4009
- notAfter: []
4010
- };
4011
- },
4012
- addProseMirrorPlugins() {
4013
- var _a;
4014
- const plugin = new PluginKey(this.name);
4015
- const defaultNode = this.options.node || ((_a = this.editor.schema.topNodeType.contentMatch.defaultType) == null ? void 0 : _a.name) || "paragraph";
4016
- const disabledNodes = Object.entries(this.editor.schema.nodes).map(([, value]) => value).filter((node) => (this.options.notAfter || []).concat(defaultNode).includes(node.name));
4017
- return [
4018
- new Plugin({
4019
- key: plugin,
4020
- appendTransaction: (_, __, state) => {
4021
- const { doc, tr, schema } = state;
4022
- const shouldInsertNodeAtEnd = plugin.getState(state);
4023
- const endPosition = doc.content.size;
4024
- const type = schema.nodes[defaultNode];
4025
- if (!shouldInsertNodeAtEnd) {
4026
- return;
4027
- }
4028
- return tr.insert(endPosition, type.create());
4029
- },
4030
- state: {
4031
- init: (_, state) => {
4032
- const lastNode = state.tr.doc.lastChild;
4033
- return !nodeEqualsType({ node: lastNode, types: disabledNodes });
4034
- },
4035
- apply: (tr, value) => {
4036
- if (!tr.docChanged) {
4037
- return value;
4038
- }
4039
- if (tr.getMeta("__uniqueIDTransaction")) {
4040
- return value;
4041
- }
4042
- const lastNode = tr.doc.lastChild;
4043
- return !nodeEqualsType({ node: lastNode, types: disabledNodes });
4044
- }
4045
- }
4046
- })
4047
- ];
4048
- }
4049
- });
4050
- Extension.create({
4051
- name: "undoRedo",
4052
- addOptions() {
4053
- return {
4054
- depth: 100,
4055
- newGroupDelay: 500
4056
- };
4057
- },
4058
- addCommands() {
4059
- return {
4060
- undo: () => ({ state, dispatch }) => {
4061
- return undo(state, dispatch);
4062
- },
4063
- redo: () => ({ state, dispatch }) => {
4064
- return redo(state, dispatch);
4065
- }
4066
- };
4067
- },
4068
- addProseMirrorPlugins() {
4069
- return [history(this.options)];
4070
- },
4071
- addKeyboardShortcuts() {
4072
- return {
4073
- "Mod-z": () => this.editor.commands.undo(),
4074
- "Shift-Mod-z": () => this.editor.commands.redo(),
4075
- "Mod-y": () => this.editor.commands.redo(),
4076
- // Russian keyboard layouts
4077
- "Mod-я": () => this.editor.commands.undo(),
4078
- "Shift-Mod-я": () => this.editor.commands.redo()
4079
- };
4080
- }
4081
- });
4082
- const clamp = (val, min, max) => Math.max(min, Math.min(max, val));
4083
- const parseNum = (value, fallback) => {
4084
- const n = Number(value);
4085
- return Number.isFinite(n) ? n : fallback;
4086
- };
4087
- const TableSizeDialog = ({
4088
- open,
4089
- defaultRows = 3,
4090
- defaultCols = 3,
4091
- onClose,
4092
- onSave
4093
- }) => {
4094
- const [rows, setRows] = useState(defaultRows);
4095
- const [cols, setCols] = useState(defaultCols);
4096
- useEffect(() => {
4097
- if (open) {
4098
- setRows(defaultRows);
4099
- setCols(defaultCols);
4100
- }
4101
- }, [open, defaultRows, defaultCols]);
4102
- const min = 1;
4103
- const max = 10;
4104
- const isValid = rows >= min && rows <= max && cols >= min && cols <= max;
4105
- const handleSave = () => {
4106
- if (!isValid) return;
4107
- onSave(clamp(rows, min, max), clamp(cols, min, max));
4108
- };
4109
- return /* @__PURE__ */ jsx(
4110
- Dialog.Root,
4111
- {
4112
- open,
4113
- onOpenChange: (v) => {
4114
- if (!v) onClose();
4115
- },
4116
- children: open && /* @__PURE__ */ jsxs(Dialog.Content, { children: [
4117
- /* @__PURE__ */ jsx(Dialog.Header, { children: "Insert table" }),
4118
- /* @__PURE__ */ jsx(Dialog.Body, { children: /* @__PURE__ */ jsxs(Flex, { gap: 4, alignItems: "flex-end", children: [
4119
- /* @__PURE__ */ jsxs(Field.Root, { width: "100%", children: [
4120
- /* @__PURE__ */ jsx(Field.Label, { children: "Rows" }),
4121
- /* @__PURE__ */ jsx(
4122
- TextInput,
4123
- {
4124
- name: "table-rows",
4125
- type: "number",
4126
- value: String(rows),
4127
- onChange: (e) => setRows(clamp(parseNum(e.target.value, rows), min, max)),
4128
- placeholder: String(defaultRows)
4129
- }
4130
- ),
4131
- /* @__PURE__ */ jsxs(Field.Hint, { children: [
4132
- "Min ",
4133
- min,
4134
- ", max ",
4135
- max
4136
- ] })
4137
- ] }),
4138
- /* @__PURE__ */ jsxs(Field.Root, { width: "100%", children: [
4139
- /* @__PURE__ */ jsx(Field.Label, { children: "Columns" }),
4140
- /* @__PURE__ */ jsx(
4141
- TextInput,
4142
- {
4143
- name: "table-cols",
4144
- type: "number",
4145
- value: String(cols),
4146
- onChange: (e) => setCols(clamp(parseNum(e.target.value, cols), min, max)),
4147
- placeholder: String(defaultCols)
3844
+ ),
3845
+ /* @__PURE__ */ jsxs(Field.Hint, { children: [
3846
+ "Min ",
3847
+ min,
3848
+ ", max ",
3849
+ max
3850
+ ] })
3851
+ ] }),
3852
+ /* @__PURE__ */ jsxs(Field.Root, { width: "100%", children: [
3853
+ /* @__PURE__ */ jsx(Field.Label, { children: "Columns" }),
3854
+ /* @__PURE__ */ jsx(
3855
+ TextInput,
3856
+ {
3857
+ name: "table-cols",
3858
+ type: "number",
3859
+ value: String(cols),
3860
+ onChange: (e) => setCols(clamp(parseNum(e.target.value, cols), min, max)),
3861
+ placeholder: String(defaultCols)
4148
3862
  }
4149
3863
  ),
4150
3864
  /* @__PURE__ */ jsxs(Field.Hint, { children: [
@@ -4302,140 +4016,597 @@ function TextAlignJustify(props) {
4302
4016
  "stroke-linecap": "round",
4303
4017
  "stroke-linejoin": "round"
4304
4018
  }
4305
- )
4306
- }
4307
- );
4308
- }
4309
- function TextAlignRight(props) {
4310
- return /* @__PURE__ */ jsx(
4311
- "svg",
4312
- {
4313
- width: "16",
4314
- height: "16",
4315
- viewBox: "0 0 24 24",
4316
- fill: "currentColor",
4317
- xmlns: "http://www.w3.org/2000/svg",
4318
- ...props,
4319
- children: /* @__PURE__ */ jsx(
4320
- "path",
4321
- {
4322
- d: "M8 10H21M3 14H21M8 18H21M3 6H21",
4323
- stroke: "currentColor",
4324
- "stroke-width": "2",
4325
- "stroke-linecap": "round",
4326
- "stroke-linejoin": "round"
4019
+ )
4020
+ }
4021
+ );
4022
+ }
4023
+ function TextAlignRight(props) {
4024
+ return /* @__PURE__ */ jsx(
4025
+ "svg",
4026
+ {
4027
+ width: "16",
4028
+ height: "16",
4029
+ viewBox: "0 0 24 24",
4030
+ fill: "currentColor",
4031
+ xmlns: "http://www.w3.org/2000/svg",
4032
+ ...props,
4033
+ children: /* @__PURE__ */ jsx(
4034
+ "path",
4035
+ {
4036
+ d: "M8 10H21M3 14H21M8 18H21M3 6H21",
4037
+ stroke: "currentColor",
4038
+ "stroke-width": "2",
4039
+ "stroke-linecap": "round",
4040
+ "stroke-linejoin": "round"
4041
+ }
4042
+ )
4043
+ }
4044
+ );
4045
+ }
4046
+ function TextAlignCenter(props) {
4047
+ return /* @__PURE__ */ jsx(
4048
+ "svg",
4049
+ {
4050
+ width: "16",
4051
+ height: "16",
4052
+ viewBox: "0 0 24 24",
4053
+ fill: "currentColor",
4054
+ xmlns: "http://www.w3.org/2000/svg",
4055
+ ...props,
4056
+ children: /* @__PURE__ */ jsx(
4057
+ "path",
4058
+ {
4059
+ d: "M3 6H21M3 14H21M17 10H7M17 18H7",
4060
+ stroke: "currentColor",
4061
+ "stroke-width": "2",
4062
+ "stroke-linecap": "round",
4063
+ "stroke-linejoin": "round"
4064
+ }
4065
+ )
4066
+ }
4067
+ );
4068
+ }
4069
+ function useTextAlign(editor, props = { disabled: false }) {
4070
+ const editorState = useEditorState({
4071
+ editor,
4072
+ selector: (ctx) => {
4073
+ return {
4074
+ isTextAlignLeft: ctx.editor.isActive({ textAlign: "left" }) ?? false,
4075
+ isTextAlignRight: ctx.editor.isActive({ textAlign: "right" }) ?? false,
4076
+ isTextAlignCenter: ctx.editor.isActive({ textAlign: "center" }) ?? false,
4077
+ isTextAlignJustify: ctx.editor.isActive({ textAlign: "justify" }) ?? false,
4078
+ canToggleAlign: ctx.editor.can().chain().setTextAlign("left").run() ?? false
4079
+ };
4080
+ }
4081
+ });
4082
+ const setTextAlign = (alignment) => {
4083
+ editor.chain().focus().setTextAlign(alignment).run();
4084
+ };
4085
+ return {
4086
+ textAlignLeftButton: /* @__PURE__ */ jsx(
4087
+ ToolbarButton,
4088
+ {
4089
+ onClick: () => setTextAlign("left"),
4090
+ icon: /* @__PURE__ */ jsx(TextAlignLeft, {}),
4091
+ active: editorState.isTextAlignLeft,
4092
+ disabled: props.disabled || !editor || !editorState.canToggleAlign,
4093
+ tooltip: "Text Align Left"
4094
+ },
4095
+ "text-align-left"
4096
+ ),
4097
+ textAlignCenterButton: /* @__PURE__ */ jsx(
4098
+ ToolbarButton,
4099
+ {
4100
+ onClick: () => setTextAlign("center"),
4101
+ icon: /* @__PURE__ */ jsx(TextAlignCenter, {}),
4102
+ active: editorState.isTextAlignCenter,
4103
+ disabled: props.disabled || !editor || !editorState.canToggleAlign,
4104
+ tooltip: "Text Align Center"
4105
+ },
4106
+ "text-align-center"
4107
+ ),
4108
+ textAlignRightButton: /* @__PURE__ */ jsx(
4109
+ ToolbarButton,
4110
+ {
4111
+ onClick: () => setTextAlign("right"),
4112
+ icon: /* @__PURE__ */ jsx(TextAlignRight, {}),
4113
+ active: editorState.isTextAlignRight,
4114
+ disabled: props.disabled || !editor || !editorState.canToggleAlign,
4115
+ tooltip: "Text Align Right"
4116
+ },
4117
+ "text-align-right"
4118
+ ),
4119
+ textAlignJustifyButton: /* @__PURE__ */ jsx(
4120
+ ToolbarButton,
4121
+ {
4122
+ onClick: () => setTextAlign("justify"),
4123
+ icon: /* @__PURE__ */ jsx(TextAlignJustify, {}),
4124
+ active: editorState.isTextAlignJustify,
4125
+ disabled: props.disabled || !editor || !editorState.canToggleAlign,
4126
+ tooltip: "Text Align Justify"
4127
+ },
4128
+ "text-align-justify"
4129
+ )
4130
+ };
4131
+ }
4132
+ function usePresetConfig(presetName) {
4133
+ const { get } = useFetchClient();
4134
+ const normalizedPresetName = useMemo(
4135
+ () => presetName?.trim() || void 0,
4136
+ [presetName]
4137
+ );
4138
+ const [config, setConfig] = useState(
4139
+ normalizedPresetName ? null : MINIMAL_PRESET_CONFIG
4140
+ );
4141
+ const [isLoading, setIsLoading] = useState(Boolean(normalizedPresetName));
4142
+ useEffect(() => {
4143
+ let mounted = true;
4144
+ if (!normalizedPresetName) {
4145
+ setConfig(MINIMAL_PRESET_CONFIG);
4146
+ setIsLoading(false);
4147
+ return;
4148
+ }
4149
+ const fetchPreset = async () => {
4150
+ setIsLoading(true);
4151
+ try {
4152
+ const response = await get(
4153
+ `/api/tiptap-editor/presets/${normalizedPresetName}`
4154
+ );
4155
+ if (!mounted) return;
4156
+ setConfig(response.data || MINIMAL_PRESET_CONFIG);
4157
+ } catch (error) {
4158
+ console.warn(
4159
+ "[TiptapEditor] Failed to fetch preset config:",
4160
+ error
4161
+ );
4162
+ if (!mounted) return;
4163
+ setConfig(MINIMAL_PRESET_CONFIG);
4164
+ } finally {
4165
+ if (mounted) {
4166
+ setIsLoading(false);
4167
+ }
4168
+ }
4169
+ };
4170
+ fetchPreset();
4171
+ return () => {
4172
+ mounted = false;
4173
+ };
4174
+ }, [get, normalizedPresetName]);
4175
+ return { config, isLoading };
4176
+ }
4177
+ Extension.create({
4178
+ name: "characterCount",
4179
+ addOptions() {
4180
+ return {
4181
+ limit: null,
4182
+ mode: "textSize",
4183
+ textCounter: (text) => text.length,
4184
+ wordCounter: (text) => text.split(" ").filter((word) => word !== "").length
4185
+ };
4186
+ },
4187
+ addStorage() {
4188
+ return {
4189
+ characters: () => 0,
4190
+ words: () => 0
4191
+ };
4192
+ },
4193
+ onBeforeCreate() {
4194
+ this.storage.characters = (options) => {
4195
+ const node = (options == null ? void 0 : options.node) || this.editor.state.doc;
4196
+ const mode = (options == null ? void 0 : options.mode) || this.options.mode;
4197
+ if (mode === "textSize") {
4198
+ const text = node.textBetween(0, node.content.size, void 0, " ");
4199
+ return this.options.textCounter(text);
4200
+ }
4201
+ return node.nodeSize;
4202
+ };
4203
+ this.storage.words = (options) => {
4204
+ const node = (options == null ? void 0 : options.node) || this.editor.state.doc;
4205
+ const text = node.textBetween(0, node.content.size, " ", " ");
4206
+ return this.options.wordCounter(text);
4207
+ };
4208
+ },
4209
+ addProseMirrorPlugins() {
4210
+ let initialEvaluationDone = false;
4211
+ return [
4212
+ new Plugin({
4213
+ key: new PluginKey("characterCount"),
4214
+ appendTransaction: (transactions, oldState, newState) => {
4215
+ if (initialEvaluationDone) {
4216
+ return;
4217
+ }
4218
+ const limit = this.options.limit;
4219
+ if (limit === null || limit === void 0 || limit === 0) {
4220
+ initialEvaluationDone = true;
4221
+ return;
4222
+ }
4223
+ const initialContentSize = this.storage.characters({ node: newState.doc });
4224
+ if (initialContentSize > limit) {
4225
+ const over = initialContentSize - limit;
4226
+ const from = 0;
4227
+ const to = over;
4228
+ console.warn(
4229
+ `[CharacterCount] Initial content exceeded limit of ${limit} characters. Content was automatically trimmed.`
4230
+ );
4231
+ const tr = newState.tr.deleteRange(from, to);
4232
+ initialEvaluationDone = true;
4233
+ return tr;
4234
+ }
4235
+ initialEvaluationDone = true;
4236
+ },
4237
+ filterTransaction: (transaction, state) => {
4238
+ const limit = this.options.limit;
4239
+ if (!transaction.docChanged || limit === 0 || limit === null || limit === void 0) {
4240
+ return true;
4241
+ }
4242
+ const oldSize = this.storage.characters({ node: state.doc });
4243
+ const newSize = this.storage.characters({ node: transaction.doc });
4244
+ if (newSize <= limit) {
4245
+ return true;
4246
+ }
4247
+ if (oldSize > limit && newSize > limit && newSize <= oldSize) {
4248
+ return true;
4249
+ }
4250
+ if (oldSize > limit && newSize > limit && newSize > oldSize) {
4251
+ return false;
4252
+ }
4253
+ const isPaste = transaction.getMeta("paste");
4254
+ if (!isPaste) {
4255
+ return false;
4256
+ }
4257
+ const pos = transaction.selection.$head.pos;
4258
+ const over = newSize - limit;
4259
+ const from = pos - over;
4260
+ const to = pos;
4261
+ transaction.deleteRange(from, to);
4262
+ const updatedSize = this.storage.characters({ node: transaction.doc });
4263
+ if (updatedSize > limit) {
4264
+ return false;
4265
+ }
4266
+ return true;
4267
+ }
4268
+ })
4269
+ ];
4270
+ }
4271
+ });
4272
+ Extension.create({
4273
+ name: "dropCursor",
4274
+ addOptions() {
4275
+ return {
4276
+ color: "currentColor",
4277
+ width: 1,
4278
+ class: void 0
4279
+ };
4280
+ },
4281
+ addProseMirrorPlugins() {
4282
+ return [dropCursor(this.options)];
4283
+ }
4284
+ });
4285
+ Extension.create({
4286
+ name: "focus",
4287
+ addOptions() {
4288
+ return {
4289
+ className: "has-focus",
4290
+ mode: "all"
4291
+ };
4292
+ },
4293
+ addProseMirrorPlugins() {
4294
+ return [
4295
+ new Plugin({
4296
+ key: new PluginKey("focus"),
4297
+ props: {
4298
+ decorations: ({ doc, selection }) => {
4299
+ const { isEditable, isFocused } = this.editor;
4300
+ const { anchor } = selection;
4301
+ const decorations = [];
4302
+ if (!isEditable || !isFocused) {
4303
+ return DecorationSet.create(doc, []);
4304
+ }
4305
+ let maxLevels = 0;
4306
+ if (this.options.mode === "deepest") {
4307
+ doc.descendants((node, pos) => {
4308
+ if (node.isText) {
4309
+ return;
4310
+ }
4311
+ const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1;
4312
+ if (!isCurrent) {
4313
+ return false;
4314
+ }
4315
+ maxLevels += 1;
4316
+ });
4317
+ }
4318
+ let currentLevel = 0;
4319
+ doc.descendants((node, pos) => {
4320
+ if (node.isText) {
4321
+ return false;
4322
+ }
4323
+ const isCurrent = anchor >= pos && anchor <= pos + node.nodeSize - 1;
4324
+ if (!isCurrent) {
4325
+ return false;
4326
+ }
4327
+ currentLevel += 1;
4328
+ const outOfScope = this.options.mode === "deepest" && maxLevels - currentLevel > 0 || this.options.mode === "shallowest" && currentLevel > 1;
4329
+ if (outOfScope) {
4330
+ return this.options.mode === "deepest";
4331
+ }
4332
+ decorations.push(
4333
+ Decoration.node(pos, pos + node.nodeSize, {
4334
+ class: this.options.className
4335
+ })
4336
+ );
4337
+ });
4338
+ return DecorationSet.create(doc, decorations);
4339
+ }
4340
+ }
4341
+ })
4342
+ ];
4343
+ }
4344
+ });
4345
+ var Gapcursor = Extension.create({
4346
+ name: "gapCursor",
4347
+ addProseMirrorPlugins() {
4348
+ return [gapCursor()];
4349
+ },
4350
+ extendNodeSchema(extension) {
4351
+ var _a;
4352
+ const context = {
4353
+ name: extension.name,
4354
+ options: extension.options,
4355
+ storage: extension.storage
4356
+ };
4357
+ return {
4358
+ allowGapCursor: (_a = callOrReturn(getExtensionField(extension, "allowGapCursor", context))) != null ? _a : null
4359
+ };
4360
+ }
4361
+ });
4362
+ var DEFAULT_DATA_ATTRIBUTE = "placeholder";
4363
+ function preparePlaceholderAttribute(attr) {
4364
+ return attr.replace(/\s+/g, "-").replace(/[^a-zA-Z0-9-]/g, "").replace(/^[0-9-]+/, "").replace(/^-+/, "").toLowerCase();
4365
+ }
4366
+ Extension.create({
4367
+ name: "placeholder",
4368
+ addOptions() {
4369
+ return {
4370
+ emptyEditorClass: "is-editor-empty",
4371
+ emptyNodeClass: "is-empty",
4372
+ dataAttribute: DEFAULT_DATA_ATTRIBUTE,
4373
+ placeholder: "Write something …",
4374
+ showOnlyWhenEditable: true,
4375
+ showOnlyCurrent: true,
4376
+ includeChildren: false
4377
+ };
4378
+ },
4379
+ addProseMirrorPlugins() {
4380
+ const dataAttribute = this.options.dataAttribute ? `data-${preparePlaceholderAttribute(this.options.dataAttribute)}` : `data-${DEFAULT_DATA_ATTRIBUTE}`;
4381
+ return [
4382
+ new Plugin({
4383
+ key: new PluginKey("placeholder"),
4384
+ props: {
4385
+ decorations: ({ doc, selection }) => {
4386
+ const active = this.editor.isEditable || !this.options.showOnlyWhenEditable;
4387
+ const { anchor } = selection;
4388
+ const decorations = [];
4389
+ if (!active) {
4390
+ return null;
4391
+ }
4392
+ const isEmptyDoc = this.editor.isEmpty;
4393
+ doc.descendants((node, pos) => {
4394
+ const hasAnchor = anchor >= pos && anchor <= pos + node.nodeSize;
4395
+ const isEmpty = !node.isLeaf && isNodeEmpty(node);
4396
+ if ((hasAnchor || !this.options.showOnlyCurrent) && isEmpty) {
4397
+ const classes = [this.options.emptyNodeClass];
4398
+ if (isEmptyDoc) {
4399
+ classes.push(this.options.emptyEditorClass);
4400
+ }
4401
+ const decoration = Decoration.node(pos, pos + node.nodeSize, {
4402
+ class: classes.join(" "),
4403
+ [dataAttribute]: typeof this.options.placeholder === "function" ? this.options.placeholder({
4404
+ editor: this.editor,
4405
+ node,
4406
+ pos,
4407
+ hasAnchor
4408
+ }) : this.options.placeholder
4409
+ });
4410
+ decorations.push(decoration);
4411
+ }
4412
+ return this.options.includeChildren;
4413
+ });
4414
+ return DecorationSet.create(doc, decorations);
4415
+ }
4416
+ }
4417
+ })
4418
+ ];
4419
+ }
4420
+ });
4421
+ Extension.create({
4422
+ name: "selection",
4423
+ addOptions() {
4424
+ return {
4425
+ className: "selection"
4426
+ };
4427
+ },
4428
+ addProseMirrorPlugins() {
4429
+ const { editor, options } = this;
4430
+ return [
4431
+ new Plugin({
4432
+ key: new PluginKey("selection"),
4433
+ props: {
4434
+ decorations(state) {
4435
+ if (state.selection.empty || editor.isFocused || !editor.isEditable || isNodeSelection(state.selection) || editor.view.dragging) {
4436
+ return null;
4437
+ }
4438
+ return DecorationSet.create(state.doc, [
4439
+ Decoration.inline(state.selection.from, state.selection.to, {
4440
+ class: options.className
4441
+ })
4442
+ ]);
4443
+ }
4327
4444
  }
4328
- )
4329
- }
4330
- );
4445
+ })
4446
+ ];
4447
+ }
4448
+ });
4449
+ function nodeEqualsType({ types, node }) {
4450
+ return node && Array.isArray(types) && types.includes(node.type) || (node == null ? void 0 : node.type) === types;
4331
4451
  }
4332
- function TextAlignCenter(props) {
4333
- return /* @__PURE__ */ jsx(
4334
- "svg",
4335
- {
4336
- width: "16",
4337
- height: "16",
4338
- viewBox: "0 0 24 24",
4339
- fill: "currentColor",
4340
- xmlns: "http://www.w3.org/2000/svg",
4341
- ...props,
4342
- children: /* @__PURE__ */ jsx(
4343
- "path",
4344
- {
4345
- d: "M3 6H21M3 14H21M17 10H7M17 18H7",
4346
- stroke: "currentColor",
4347
- "stroke-width": "2",
4348
- "stroke-linecap": "round",
4349
- "stroke-linejoin": "round"
4452
+ Extension.create({
4453
+ name: "trailingNode",
4454
+ addOptions() {
4455
+ return {
4456
+ node: void 0,
4457
+ notAfter: []
4458
+ };
4459
+ },
4460
+ addProseMirrorPlugins() {
4461
+ var _a;
4462
+ const plugin = new PluginKey(this.name);
4463
+ const defaultNode = this.options.node || ((_a = this.editor.schema.topNodeType.contentMatch.defaultType) == null ? void 0 : _a.name) || "paragraph";
4464
+ const disabledNodes = Object.entries(this.editor.schema.nodes).map(([, value]) => value).filter((node) => (this.options.notAfter || []).concat(defaultNode).includes(node.name));
4465
+ return [
4466
+ new Plugin({
4467
+ key: plugin,
4468
+ appendTransaction: (_, __, state) => {
4469
+ const { doc, tr, schema } = state;
4470
+ const shouldInsertNodeAtEnd = plugin.getState(state);
4471
+ const endPosition = doc.content.size;
4472
+ const type = schema.nodes[defaultNode];
4473
+ if (!shouldInsertNodeAtEnd) {
4474
+ return;
4475
+ }
4476
+ return tr.insert(endPosition, type.create());
4477
+ },
4478
+ state: {
4479
+ init: (_, state) => {
4480
+ const lastNode = state.tr.doc.lastChild;
4481
+ return !nodeEqualsType({ node: lastNode, types: disabledNodes });
4482
+ },
4483
+ apply: (tr, value) => {
4484
+ if (!tr.docChanged) {
4485
+ return value;
4486
+ }
4487
+ if (tr.getMeta("__uniqueIDTransaction")) {
4488
+ return value;
4489
+ }
4490
+ const lastNode = tr.doc.lastChild;
4491
+ return !nodeEqualsType({ node: lastNode, types: disabledNodes });
4492
+ }
4350
4493
  }
4351
- )
4352
- }
4353
- );
4354
- }
4355
- function useTextAlign(editor, props = { disabled: false }) {
4356
- const editorState = useEditorState({
4357
- editor,
4358
- selector: (ctx) => {
4359
- return {
4360
- isTextAlignLeft: ctx.editor.isActive({ textAlign: "left" }) ?? false,
4361
- isTextAlignRight: ctx.editor.isActive({ textAlign: "right" }) ?? false,
4362
- isTextAlignCenter: ctx.editor.isActive({ textAlign: "center" }) ?? false,
4363
- isTextAlignJustify: ctx.editor.isActive({ textAlign: "justify" }) ?? false,
4364
- canToggleAlign: ctx.editor.can().chain().setTextAlign("left").run() ?? false
4365
- };
4366
- }
4367
- });
4368
- const setTextAlign = (alignment) => {
4369
- editor.chain().focus().setTextAlign(alignment).run();
4370
- };
4371
- return {
4372
- textAlignLeftButton: /* @__PURE__ */ jsx(
4373
- ToolbarButton,
4374
- {
4375
- onClick: () => setTextAlign("left"),
4376
- icon: /* @__PURE__ */ jsx(TextAlignLeft, {}),
4377
- active: editorState.isTextAlignLeft,
4378
- disabled: props.disabled || !editor || !editorState.canToggleAlign,
4379
- tooltip: "Text Align Left"
4380
- },
4381
- "text-align-left"
4382
- ),
4383
- textAlignCenterButton: /* @__PURE__ */ jsx(
4384
- ToolbarButton,
4385
- {
4386
- onClick: () => setTextAlign("center"),
4387
- icon: /* @__PURE__ */ jsx(TextAlignCenter, {}),
4388
- active: editorState.isTextAlignCenter,
4389
- disabled: props.disabled || !editor || !editorState.canToggleAlign,
4390
- tooltip: "Text Align Center"
4391
- },
4392
- "text-align-center"
4393
- ),
4394
- textAlignRightButton: /* @__PURE__ */ jsx(
4395
- ToolbarButton,
4396
- {
4397
- onClick: () => setTextAlign("right"),
4398
- icon: /* @__PURE__ */ jsx(TextAlignRight, {}),
4399
- active: editorState.isTextAlignRight,
4400
- disabled: props.disabled || !editor || !editorState.canToggleAlign,
4401
- tooltip: "Text Align Right"
4402
- },
4403
- "text-align-right"
4404
- ),
4405
- textAlignJustifyButton: /* @__PURE__ */ jsx(
4406
- ToolbarButton,
4407
- {
4408
- onClick: () => setTextAlign("justify"),
4409
- icon: /* @__PURE__ */ jsx(TextAlignJustify, {}),
4410
- active: editorState.isTextAlignJustify,
4411
- disabled: props.disabled || !editor || !editorState.canToggleAlign,
4412
- tooltip: "Text Align Justify"
4494
+ })
4495
+ ];
4496
+ }
4497
+ });
4498
+ Extension.create({
4499
+ name: "undoRedo",
4500
+ addOptions() {
4501
+ return {
4502
+ depth: 100,
4503
+ newGroupDelay: 500
4504
+ };
4505
+ },
4506
+ addCommands() {
4507
+ return {
4508
+ undo: () => ({ state, dispatch }) => {
4509
+ return undo(state, dispatch);
4413
4510
  },
4414
- "text-align-justify"
4415
- )
4416
- };
4417
- }
4418
- const extensions = [
4419
- StarterKit.configure({
4511
+ redo: () => ({ state, dispatch }) => {
4512
+ return redo(state, dispatch);
4513
+ }
4514
+ };
4515
+ },
4516
+ addProseMirrorPlugins() {
4517
+ return [history(this.options)];
4518
+ },
4519
+ addKeyboardShortcuts() {
4520
+ return {
4521
+ "Mod-z": () => this.editor.commands.undo(),
4522
+ "Shift-Mod-z": () => this.editor.commands.redo(),
4523
+ "Mod-y": () => this.editor.commands.redo(),
4524
+ // Russian keyboard layouts
4525
+ "Mod-я": () => this.editor.commands.undo(),
4526
+ "Shift-Mod-я": () => this.editor.commands.redo()
4527
+ };
4528
+ }
4529
+ });
4530
+ const starterKitFeatureValue = (value) => {
4531
+ if (!isFeatureEnabled(value)) {
4532
+ return false;
4533
+ }
4534
+ return getFeatureOptions(value, {}) ?? {};
4535
+ };
4536
+ function buildExtensions(config) {
4537
+ const starterKitConfig = {
4420
4538
  heading: false,
4421
- // disable default so we can use our custom version
4422
- link: {
4423
- openOnClick: false
4539
+ // ALWAYS false heading handled separately via BaseHeadingWithSEOTag
4540
+ bold: starterKitFeatureValue(config.bold),
4541
+ italic: starterKitFeatureValue(config.italic),
4542
+ strike: starterKitFeatureValue(config.strike),
4543
+ code: starterKitFeatureValue(config.code),
4544
+ codeBlock: starterKitFeatureValue(config.codeBlock),
4545
+ blockquote: starterKitFeatureValue(config.blockquote),
4546
+ bulletList: starterKitFeatureValue(config.bulletList),
4547
+ orderedList: starterKitFeatureValue(config.orderedList),
4548
+ hardBreak: starterKitFeatureValue(config.hardBreak),
4549
+ horizontalRule: starterKitFeatureValue(config.horizontalRule),
4550
+ history: starterKitFeatureValue(config.history),
4551
+ link: !isFeatureEnabled(config.link) ? false : {
4552
+ openOnClick: false,
4553
+ ...getFeatureOptions(config.link, {})
4424
4554
  }
4425
- }),
4426
- HeadingWithSEOTag,
4427
- Superscript,
4428
- Subscript,
4429
- Gapcursor,
4430
- // cursor for resizing tables
4431
- TableKit.configure({
4432
- table: { resizable: true }
4433
- }),
4434
- TextAlign.configure({
4435
- types: ["heading", "paragraph"]
4436
- })
4437
- ];
4555
+ };
4556
+ const extensions = [
4557
+ StarterKit.configure(starterKitConfig)
4558
+ ];
4559
+ if (isFeatureEnabled(config.heading)) {
4560
+ const headingConfig = getFeatureOptions(config.heading, {
4561
+ levels: [1, 2, 3, 4]
4562
+ });
4563
+ const levels = headingConfig?.levels || [1, 2, 3, 4];
4564
+ extensions.push(BaseHeadingWithSEOTag.configure({ levels }));
4565
+ }
4566
+ if (isFeatureEnabled(config.superscript)) {
4567
+ extensions.push(Superscript);
4568
+ }
4569
+ if (isFeatureEnabled(config.subscript)) {
4570
+ extensions.push(Subscript);
4571
+ }
4572
+ if (isFeatureEnabled(config.table)) {
4573
+ extensions.push(
4574
+ TableKit.configure({
4575
+ table: {
4576
+ resizable: true,
4577
+ ...getFeatureOptions(config.table, {})
4578
+ }
4579
+ })
4580
+ );
4581
+ }
4582
+ if (isFeatureEnabled(config.textAlign)) {
4583
+ const textAlignConfig = getFeatureOptions(config.textAlign, {
4584
+ types: ["heading", "paragraph"],
4585
+ alignments: ["left", "center", "right", "justify"]
4586
+ });
4587
+ extensions.push(
4588
+ TextAlign.configure({
4589
+ types: textAlignConfig?.types || ["heading", "paragraph"],
4590
+ alignments: textAlignConfig?.alignments || [
4591
+ "left",
4592
+ "center",
4593
+ "right",
4594
+ "justify"
4595
+ ]
4596
+ })
4597
+ );
4598
+ }
4599
+ extensions.push(Gapcursor);
4600
+ return extensions;
4601
+ }
4438
4602
  const RichTextInput = forwardRef((props, forwardedRef) => {
4603
+ const attribute = props.attribute;
4604
+ const presetName = attribute?.options?.preset;
4605
+ const { config, isLoading } = usePresetConfig(presetName);
4606
+ const extensions = useMemo(() => {
4607
+ if (!config) return [];
4608
+ return buildExtensions(config);
4609
+ }, [presetName]);
4439
4610
  const { editor, field } = useTiptapEditor(props.name, "", extensions);
4440
4611
  const starterKit = useStarterKit(editor, { disabled: props.disabled });
4441
4612
  const heading = useHeading(editor, { disabled: props.disabled });
@@ -4443,39 +4614,58 @@ const RichTextInput = forwardRef((props, forwardedRef) => {
4443
4614
  const script = useScript(editor, { disabled: props.disabled });
4444
4615
  const table = useTable(editor, { disabled: props.disabled });
4445
4616
  const textAlign = useTextAlign(editor, { disabled: props.disabled });
4446
- return /* @__PURE__ */ jsxs(BaseTiptapInput, { editor, field, ...props, ref: forwardedRef, children: [
4447
- heading.headingSelect,
4448
- heading.headingTagSelect,
4449
- /* @__PURE__ */ jsx(Spacer, { width: 8 }),
4450
- starterKit.boldButton,
4451
- starterKit.italicButton,
4452
- starterKit.underlineButton,
4453
- starterKit.strikeButton,
4454
- script.superscriptButton,
4455
- script.subscriptButton,
4456
- /* @__PURE__ */ jsx(Spacer, { width: 8 }),
4457
- textAlign.textAlignLeftButton,
4458
- textAlign.textAlignCenterButton,
4459
- textAlign.textAlignRightButton,
4460
- textAlign.textAlignJustifyButton,
4461
- /* @__PURE__ */ jsx(Spacer, { width: 8 }),
4462
- starterKit.bulletButton,
4463
- starterKit.orderedButton,
4464
- /* @__PURE__ */ jsx(Spacer, { width: 8 }),
4465
- starterKit.codeButton,
4466
- starterKit.blockquoteButton,
4467
- link.linkButton,
4468
- link.linkDialog,
4469
- /* @__PURE__ */ jsx(Spacer, { width: 8 }),
4470
- table.tableButton,
4471
- table.addColumnButton,
4472
- table.removeColumnButton,
4473
- table.addRowButton,
4474
- table.removeRowButton,
4475
- table.tableDialog
4476
- ] });
4617
+ if (isLoading) {
4618
+ return /* @__PURE__ */ jsx(Box, { padding: 4, children: "Loading editor..." });
4619
+ }
4620
+ return /* @__PURE__ */ jsx(EditorErrorBoundary, { children: /* @__PURE__ */ jsxs(
4621
+ BaseTiptapInput,
4622
+ {
4623
+ editor,
4624
+ field,
4625
+ ...props,
4626
+ ref: forwardedRef,
4627
+ noPresetConfigured: !presetName,
4628
+ children: [
4629
+ /* @__PURE__ */ jsxs(FeatureGuard, { featureValue: config?.heading, children: [
4630
+ heading.headingSelect,
4631
+ heading.headingTagSelect,
4632
+ /* @__PURE__ */ jsx(Spacer, { width: 8 })
4633
+ ] }),
4634
+ starterKit.boldButton,
4635
+ starterKit.italicButton,
4636
+ starterKit.underlineButton,
4637
+ starterKit.strikeButton,
4638
+ script.superscriptButton,
4639
+ script.subscriptButton,
4640
+ /* @__PURE__ */ jsx(Spacer, { width: 8 }),
4641
+ /* @__PURE__ */ jsxs(FeatureGuard, { featureValue: config?.textAlign, children: [
4642
+ textAlign.textAlignLeftButton,
4643
+ textAlign.textAlignCenterButton,
4644
+ textAlign.textAlignRightButton,
4645
+ textAlign.textAlignJustifyButton,
4646
+ /* @__PURE__ */ jsx(Spacer, { width: 8 })
4647
+ ] }),
4648
+ starterKit.bulletButton,
4649
+ starterKit.orderedButton,
4650
+ /* @__PURE__ */ jsx(Spacer, { width: 8 }),
4651
+ starterKit.codeButton,
4652
+ starterKit.blockquoteButton,
4653
+ link.linkButton,
4654
+ link.linkDialog,
4655
+ /* @__PURE__ */ jsxs(FeatureGuard, { featureValue: config?.table, children: [
4656
+ /* @__PURE__ */ jsx(Spacer, { width: 8 }),
4657
+ table.tableButton,
4658
+ table.addColumnButton,
4659
+ table.removeColumnButton,
4660
+ table.addRowButton,
4661
+ table.removeRowButton,
4662
+ table.tableDialog
4663
+ ] })
4664
+ ]
4665
+ }
4666
+ ) });
4477
4667
  });
4478
4668
  export {
4479
4669
  RichTextInput as default
4480
4670
  };
4481
- //# sourceMappingURL=RichTextInput-BZQ2iVqa.mjs.map
4671
+ //# sourceMappingURL=RichTextInput-COGVRWOW.mjs.map