@questpie/admin 3.5.2 → 3.5.4

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 (195) hide show
  1. package/README.md +8 -0
  2. package/dist/client/blocks/block-renderer.d.mts +2 -2
  3. package/dist/client/builder/index.d.mts +1 -1
  4. package/dist/client/builder/types/collection-types.d.mts +89 -5
  5. package/dist/client/builder/types/common.d.mts +5 -0
  6. package/dist/client/builder/types/field-types.d.mts +41 -1
  7. package/dist/client/builder/view/view.d.mts +3 -2
  8. package/dist/client/components/actions/action-dialog.mjs +5 -0
  9. package/dist/client/components/admin-link.d.mts +2 -2
  10. package/dist/client/components/fields/boolean-field.mjs +2 -1
  11. package/dist/client/components/fields/date-field.mjs +2 -1
  12. package/dist/client/components/fields/datetime-field.mjs +2 -1
  13. package/dist/client/components/fields/email-field.mjs +2 -1
  14. package/dist/client/components/fields/field-utils.d.mts +11 -0
  15. package/dist/client/components/fields/field-utils.mjs +3 -1
  16. package/dist/client/components/fields/field-wrapper.mjs +3 -3
  17. package/dist/client/components/fields/number-field.mjs +2 -1
  18. package/dist/client/components/fields/object-field.mjs +2 -1
  19. package/dist/client/components/fields/relation/displays/types.mjs +3 -3
  20. package/dist/client/components/fields/rich-text-editor/bubble-menu.mjs +7 -0
  21. package/dist/client/components/fields/rich-text-editor/extensions.mjs +19 -2
  22. package/dist/client/components/fields/rich-text-editor/image-popover.mjs +6 -2
  23. package/dist/client/components/fields/rich-text-editor/image-upload.mjs +2 -1
  24. package/dist/client/components/fields/rich-text-editor/index.d.mts +5 -3
  25. package/dist/client/components/fields/rich-text-editor/index.mjs +38 -76
  26. package/dist/client/components/fields/rich-text-editor/slash-commands.mjs +30 -7
  27. package/dist/client/components/fields/rich-text-editor/toolbar.mjs +1 -312
  28. package/dist/client/components/fields/rich-text-editor/types.d.mts +4 -0
  29. package/dist/client/components/fields/rich-text-editor/types.mjs +1 -1
  30. package/dist/client/components/fields/rich-text-editor/utils.mjs +6 -12
  31. package/dist/client/components/fields/select-field.mjs +2 -1
  32. package/dist/client/components/fields/text-field.mjs +2 -1
  33. package/dist/client/components/fields/textarea-field.mjs +2 -1
  34. package/dist/client/components/fields/time-field.mjs +2 -1
  35. package/dist/client/components/filter-builder/filter-builder-sheet.mjs +75 -22
  36. package/dist/client/components/layout/field-layout-renderer.mjs +4 -4
  37. package/dist/client/components/media/media-grid.mjs +2 -1
  38. package/dist/client/components/primitives/asset-preview.mjs +4 -2
  39. package/dist/client/components/primitives/dropzone.d.mts +100 -0
  40. package/dist/client/components/primitives/field-select-control.mjs +2 -1
  41. package/dist/client/components/ui/button.d.mts +23 -0
  42. package/dist/client/components/ui/button.mjs +2 -2
  43. package/dist/client/components/ui/dropdown-menu.d.mts +49 -0
  44. package/dist/client/components/ui/dropdown-menu.mjs +7 -19
  45. package/dist/client/components/ui/popover.mjs +1 -1
  46. package/dist/client/components/ui/search-input.d.mts +56 -0
  47. package/dist/client/components/ui/select.mjs +2 -2
  48. package/dist/client/components/ui/sheet.d.mts +40 -0
  49. package/dist/client/components/ui/table.d.mts +49 -0
  50. package/dist/client/components/ui/table.mjs +15 -1
  51. package/dist/client/components/ui/tooltip.d.mts +21 -0
  52. package/dist/client/contexts/focus-context.d.mts +2 -2
  53. package/dist/client/hooks/query-access.d.mts +9 -0
  54. package/dist/client/hooks/query-access.mjs +20 -0
  55. package/dist/client/hooks/typed-hooks.d.mts +4 -2
  56. package/dist/client/hooks/typed-hooks.mjs +30 -29
  57. package/dist/client/hooks/use-admin-config.mjs +20 -1
  58. package/dist/client/hooks/use-autosave.mjs +91 -0
  59. package/dist/client/hooks/use-collection.mjs +65 -23
  60. package/dist/client/hooks/use-reactive-fields.d.mts +1 -0
  61. package/dist/client/hooks/use-reactive-fields.mjs +16 -1
  62. package/dist/client/hooks/use-server-actions.mjs +12 -1
  63. package/dist/client/hooks/use-upload.d.mts +40 -0
  64. package/dist/client/hooks/use-upload.mjs +4 -2
  65. package/dist/client/hooks/use-view-state.mjs +15 -7
  66. package/dist/client/i18n/hooks.d.mts +20 -0
  67. package/dist/client/lib/utils.d.mts +6 -0
  68. package/dist/client/lib/view-filter-utils.mjs +30 -0
  69. package/dist/client/preview/block-scope-context.d.mts +2 -2
  70. package/dist/client/preview/preview-banner.d.mts +2 -2
  71. package/dist/client/preview/preview-field.d.mts +4 -4
  72. package/dist/client/runtime/provider.mjs +22 -3
  73. package/dist/client/scope/picker.d.mts +2 -2
  74. package/dist/client/scope/provider.d.mts +2 -2
  75. package/dist/client/styles/base.css +75 -79
  76. package/dist/client/utils/asset-url.mjs +27 -0
  77. package/dist/client/utils/build-field-definitions-from-schema.mjs +1 -0
  78. package/dist/client/views/auth/accept-invite-form.d.mts +2 -2
  79. package/dist/client/views/auth/auth-layout.d.mts +3 -3
  80. package/dist/client/views/auth/forgot-password-form.d.mts +2 -2
  81. package/dist/client/views/auth/login-form.d.mts +2 -2
  82. package/dist/client/views/auth/reset-password-form.d.mts +2 -2
  83. package/dist/client/views/auth/setup-form.d.mts +2 -2
  84. package/dist/client/views/collection/auto-form-fields.mjs +7 -6
  85. package/dist/client/views/collection/cells/primitive-cells.mjs +9 -6
  86. package/dist/client/views/collection/cells/shared/asset-thumbnail.d.mts +7 -0
  87. package/dist/client/views/collection/cells/shared/asset-thumbnail.mjs +3 -2
  88. package/dist/client/views/collection/cells/shared/cell-helpers.mjs +3 -2
  89. package/dist/client/views/collection/cells/upload-cells.mjs +2 -1
  90. package/dist/client/views/collection/columns/build-columns.mjs +3 -1
  91. package/dist/client/views/collection/document-view.d.mts +30 -0
  92. package/dist/client/views/collection/document-view.mjs +377 -0
  93. package/dist/client/views/collection/field-context.mjs +3 -2
  94. package/dist/client/views/collection/field-renderer.mjs +13 -5
  95. package/dist/client/views/collection/form-view.mjs +221 -282
  96. package/dist/client/views/collection/list-view.mjs +592 -190
  97. package/dist/client/views/collection/outline.mjs +44 -19
  98. package/dist/client/views/collection/quick-filter-bar.mjs +45 -0
  99. package/dist/client/views/collection/table-view.mjs +61 -17
  100. package/dist/client/views/globals/global-form-view.mjs +12 -9
  101. package/dist/client/views/layout/admin-layout-provider.mjs +4 -3
  102. package/dist/client/views/layout/admin-layout.mjs +108 -21
  103. package/dist/client/views/layout/admin-router.mjs +19 -3
  104. package/dist/client/views/layout/admin-sidebar.mjs +70 -20
  105. package/dist/client/views/layout/admin-theme.mjs +5 -4
  106. package/dist/client/views/layout/admin-view-layout.d.mts +36 -0
  107. package/dist/client/views/pages/accept-invite-page.d.mts +2 -2
  108. package/dist/client/views/pages/dashboard-page.d.mts +2 -2
  109. package/dist/client/views/pages/forgot-password-page.d.mts +2 -2
  110. package/dist/client/views/pages/invite-page.d.mts +2 -2
  111. package/dist/client/views/pages/login-page.d.mts +2 -2
  112. package/dist/client/views/pages/reset-password-page.d.mts +2 -2
  113. package/dist/client/views/pages/setup-page.d.mts +2 -2
  114. package/dist/client.d.mts +17 -2
  115. package/dist/client.mjs +17 -2
  116. package/dist/components/rich-text/rich-text-renderer.d.mts +5 -5
  117. package/dist/components/rich-text/rich-text-renderer.mjs +5 -2
  118. package/dist/factories.d.mts +4 -2
  119. package/dist/factories.mjs +2 -2
  120. package/dist/index.d.mts +17 -3
  121. package/dist/index.mjs +17 -2
  122. package/dist/modules/admin.d.mts +1 -1
  123. package/dist/server/adapters/index.d.mts +2 -0
  124. package/dist/server/adapters/nextjs.d.mts +1 -0
  125. package/dist/server/augmentation/actions.d.mts +9 -3
  126. package/dist/server/augmentation/dashboard.d.mts +11 -11
  127. package/dist/server/augmentation/form-layout.d.mts +16 -6
  128. package/dist/server/augmentation/index.d.mts +7 -0
  129. package/dist/server/augmentation/sidebar.d.mts +8 -8
  130. package/dist/server/augmentation/views.d.mts +4 -1
  131. package/dist/server/auth-helpers.d.mts +1 -0
  132. package/dist/server/codegen/admin-client-template.mjs +7 -6
  133. package/dist/server/fields/blocks.mjs +4 -1
  134. package/dist/server/fields/index.d.mts +1 -1
  135. package/dist/server/fields/reactive-runtime.mjs +3 -0
  136. package/dist/server/fields/rich-text.d.mts +16 -17
  137. package/dist/server/fields/rich-text.mjs +18 -7
  138. package/dist/server/i18n/messages/cs.mjs +2 -0
  139. package/dist/server/i18n/messages/de.mjs +2 -0
  140. package/dist/server/i18n/messages/en.mjs +4 -0
  141. package/dist/server/i18n/messages/es.mjs +2 -0
  142. package/dist/server/i18n/messages/fr.mjs +2 -0
  143. package/dist/server/i18n/messages/pl.mjs +2 -0
  144. package/dist/server/i18n/messages/pt.mjs +2 -0
  145. package/dist/server/i18n/messages/sk.mjs +2 -0
  146. package/dist/server/modules/admin/.generated/module.d.mts +1 -1
  147. package/dist/server/modules/admin/auth-helpers.mjs +7 -1
  148. package/dist/server/modules/admin/block/block-builder.d.mts +0 -8
  149. package/dist/server/modules/admin/block/introspection.d.mts +2 -2
  150. package/dist/server/modules/admin/block/introspection.mjs +28 -4
  151. package/dist/server/modules/admin/block/prefetch.d.mts +11 -0
  152. package/dist/server/modules/admin/block/prefetch.mjs +108 -27
  153. package/dist/server/modules/admin/client/.generated/module.d.mts +68 -67
  154. package/dist/server/modules/admin/client/.generated/module.mjs +2 -0
  155. package/dist/server/modules/admin/client/views/collection-document.d.mts +6 -0
  156. package/dist/server/modules/admin/client/views/collection-document.mjs +10 -0
  157. package/dist/server/modules/admin/collections/account.d.mts +53 -52
  158. package/dist/server/modules/admin/collections/admin-locks.d.mts +57 -56
  159. package/dist/server/modules/admin/collections/admin-preferences.d.mts +38 -37
  160. package/dist/server/modules/admin/collections/admin-saved-views.d.mts +50 -49
  161. package/dist/server/modules/admin/collections/apikey.d.mts +76 -67
  162. package/dist/server/modules/admin/collections/assets.d.mts +37 -36
  163. package/dist/server/modules/admin/collections/session.d.mts +42 -41
  164. package/dist/server/modules/admin/collections/user.d.mts +57 -56
  165. package/dist/server/modules/admin/collections/verification.d.mts +34 -33
  166. package/dist/server/modules/admin/dto/admin-config.dto.mjs +34 -4
  167. package/dist/server/modules/admin/factories.mjs +4 -34
  168. package/dist/server/modules/admin/index.d.mts +3 -3
  169. package/dist/server/modules/admin/routes/admin-config.d.mts +4 -2
  170. package/dist/server/modules/admin/routes/admin-config.mjs +56 -24
  171. package/dist/server/modules/admin/routes/execute-action.d.mts +9 -9
  172. package/dist/server/modules/admin/routes/execute-action.mjs +35 -9
  173. package/dist/server/modules/admin/routes/locales.mjs +1 -1
  174. package/dist/server/modules/admin/routes/preview.d.mts +11 -11
  175. package/dist/server/modules/admin/routes/preview.mjs +6 -5
  176. package/dist/server/modules/admin/routes/reactive.d.mts +9 -9
  177. package/dist/server/modules/admin/routes/reactive.mjs +2 -2
  178. package/dist/server/modules/admin/routes/route-helpers.d.mts +11 -7
  179. package/dist/server/modules/admin/routes/route-helpers.mjs +1 -1
  180. package/dist/server/modules/admin/routes/setup.d.mts +7 -7
  181. package/dist/server/modules/admin/routes/translations.d.mts +4 -4
  182. package/dist/server/modules/admin/routes/widget-data.d.mts +5 -5
  183. package/dist/server/modules/admin/routes/widget-data.mjs +12 -4
  184. package/dist/server/modules/admin-preferences/collections/saved-views.d.mts +45 -45
  185. package/dist/server/modules/audit/.generated/module.d.mts +6 -6
  186. package/dist/server/modules/audit/collections/audit-log.d.mts +81 -80
  187. package/dist/server/modules/audit/jobs/audit-cleanup.d.mts +2 -2
  188. package/dist/server/plugin.mjs +10 -5
  189. package/dist/server/proxy-factories.d.mts +8 -1
  190. package/dist/server/proxy-factories.mjs +33 -1
  191. package/dist/server.d.mts +3 -1
  192. package/dist/shared/types/index.d.mts +1 -0
  193. package/dist/shared/types/saved-views.types.d.mts +14 -7
  194. package/dist/shared.d.mts +3 -2
  195. package/package.json +5 -4
@@ -5,48 +5,24 @@ import { Skeleton } from "../../ui/skeleton.mjs";
5
5
  import { isModifierShortcut } from "../../../utils/keyboard-shortcuts.mjs";
6
6
  import { LocaleBadge } from "../locale-badge.mjs";
7
7
  import { createLinkAttributes, isLikelyLinkHref } from "./link-utils.mjs";
8
- import { RichTextToolbar } from "./toolbar.mjs";
8
+ import { TableControls } from "./table-controls.mjs";
9
+ import { ToolbarButton } from "./toolbar.mjs";
9
10
  import { RichTextBubbleMenu } from "./bubble-menu.mjs";
10
11
  import { buildExtensions } from "./extensions.mjs";
11
12
  import { getImageAltFromFile, getImageFilesFromDataTransfer, useRichTextImageUpload } from "./image-upload.mjs";
12
13
  import { ImagePopover } from "./image-popover.mjs";
13
14
  import { mergePresetFeatures } from "./presets.mjs";
14
15
  import { defaultFeatures } from "./types.mjs";
15
- import { getCharacterCount, getHeadingLevel, getOutput, isSameValue } from "./utils.mjs";
16
+ import { getCharacterCount, getOutput, isSameValue } from "./utils.mjs";
16
17
  import * as React from "react";
17
18
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
18
19
  import { toast } from "sonner";
19
20
  import { EditorContent, useEditor } from "@tiptap/react";
20
21
 
21
22
  //#region src/client/components/fields/rich-text-editor/index.tsx
22
- function RichTextToolbarSkeleton() {
23
- return /* @__PURE__ */ jsxs("div", {
24
- className: "qp-rich-text-editor__toolbar border-border-subtle bg-surface-low flex flex-nowrap items-center gap-1.5 overflow-hidden border-b p-1.5",
25
- "aria-hidden": "true",
26
- children: [
27
- /* @__PURE__ */ jsxs("div", {
28
- className: "flex items-center gap-1",
29
- role: "presentation",
30
- children: [/* @__PURE__ */ jsx(Skeleton, { className: "size-8" }), /* @__PURE__ */ jsx(Skeleton, { className: "size-8" })]
31
- }),
32
- /* @__PURE__ */ jsx(Skeleton, { className: "h-8 min-w-[136px]" }),
33
- /* @__PURE__ */ jsxs("div", {
34
- className: "flex items-center gap-1",
35
- role: "presentation",
36
- children: [
37
- /* @__PURE__ */ jsx(Skeleton, { className: "size-8" }),
38
- /* @__PURE__ */ jsx(Skeleton, { className: "size-8" }),
39
- /* @__PURE__ */ jsx(Skeleton, { className: "size-8" })
40
- ]
41
- }),
42
- /* @__PURE__ */ jsx(Skeleton, { className: "size-8" }),
43
- /* @__PURE__ */ jsx(Skeleton, { className: "size-8" })
44
- ]
45
- });
46
- }
47
23
  function RichTextEditorContentSkeleton() {
48
24
  return /* @__PURE__ */ jsxs("div", {
49
- className: "qp-rich-text-editor__content space-y-3",
25
+ className: "qp-rich-text-editor__content space-y-4",
50
26
  "aria-hidden": "true",
51
27
  children: [
52
28
  /* @__PURE__ */ jsx(Skeleton, {
@@ -62,7 +38,7 @@ function RichTextEditorContentSkeleton() {
62
38
  className: "h-4 w-full max-w-[86%]"
63
39
  }),
64
40
  /* @__PURE__ */ jsx("div", {
65
- className: "pt-3",
41
+ className: "pt-2",
66
42
  children: /* @__PURE__ */ jsx(Skeleton, {
67
43
  variant: "text",
68
44
  className: "h-4 w-full max-w-[58%]"
@@ -71,19 +47,15 @@ function RichTextEditorContentSkeleton() {
71
47
  ]
72
48
  });
73
49
  }
74
- function RichTextEditorLoadingSkeleton({ disabled, readOnly, error, showToolbar }) {
50
+ function RichTextEditorLoadingSkeleton({ disabled, readOnly }) {
75
51
  return /* @__PURE__ */ jsxs("div", {
76
- className: cn("qp-rich-text-editor control-surface h-auto min-h-[160px] overflow-hidden", disabled || readOnly ? "opacity-60" : "", error ? "border-destructive" : "border-border"),
52
+ className: cn("qp-rich-text-editor", disabled || readOnly ? "opacity-60" : ""),
77
53
  "data-admin-rich-text-editor": "root",
78
54
  "aria-busy": "true",
79
- children: [
80
- /* @__PURE__ */ jsx("span", {
81
- className: "sr-only",
82
- children: "Loading editor"
83
- }),
84
- showToolbar && /* @__PURE__ */ jsx(RichTextToolbarSkeleton, {}),
85
- /* @__PURE__ */ jsx(RichTextEditorContentSkeleton, {})
86
- ]
55
+ children: [/* @__PURE__ */ jsx("span", {
56
+ className: "sr-only",
57
+ children: "Loading editor"
58
+ }), /* @__PURE__ */ jsx(RichTextEditorContentSkeleton, {})]
87
59
  });
88
60
  }
89
61
  /**
@@ -94,7 +66,7 @@ function RichTextEditorLoadingSkeleton({ disabled, readOnly, error, showToolbar
94
66
  * `useEditor` with an empty extension list which causes the ProseMirror
95
67
  * "Schema is missing its top node type" error.
96
68
  */
97
- function RichTextEditor({ name, value, onChange, disabled, readOnly, label, description, placeholder, required, error, localized, locale, extensions, preset, features, showCharacterCount, maxCharacters, enableImages, onImageUpload, imageCollection, enableMediaLibrary }) {
69
+ function RichTextEditor({ name, value, onChange, disabled, readOnly, label, description, placeholder, required, error, localized, locale, hideLabel, extensions, preset, features, showCharacterCount, maxCharacters, enableImages, onImageUpload, imageCollection, enableMediaLibrary, outputMode }) {
98
70
  const { t } = useTranslation();
99
71
  const resolveText = useResolveText();
100
72
  const resolvedLabel = label ? resolveText(label) : void 0;
@@ -134,12 +106,14 @@ function RichTextEditor({ name, value, onChange, disabled, readOnly, label, desc
134
106
  labels: extensionLabels,
135
107
  placeholder: placeholder || t("editor.startWriting"),
136
108
  maxCharacters,
109
+ outputMode,
137
110
  customExtensions: extensions
138
111
  }), [
139
112
  resolvedFeatures,
140
113
  extensionLabels,
141
114
  placeholder,
142
115
  maxCharacters,
116
+ outputMode,
143
117
  extensions,
144
118
  t
145
119
  ]);
@@ -163,7 +137,7 @@ function RichTextEditor({ name, value, onChange, disabled, readOnly, label, desc
163
137
  className: "space-y-2",
164
138
  "data-disabled": disabled || readOnly,
165
139
  children: [
166
- resolvedLabel && /* @__PURE__ */ jsxs("div", {
140
+ !hideLabel && resolvedLabel && /* @__PURE__ */ jsxs("div", {
167
141
  className: "flex items-center gap-2",
168
142
  children: [/* @__PURE__ */ jsxs(Label, {
169
143
  htmlFor: name,
@@ -190,12 +164,11 @@ function RichTextEditor({ name, value, onChange, disabled, readOnly, label, desc
190
164
  enableImages,
191
165
  onImageUpload,
192
166
  imageCollection,
193
- enableMediaLibrary
167
+ enableMediaLibrary,
168
+ outputMode
194
169
  }) : /* @__PURE__ */ jsx(RichTextEditorLoadingSkeleton, {
195
170
  disabled,
196
- readOnly,
197
- error,
198
- showToolbar: resolvedFeatures.toolbar
171
+ readOnly
199
172
  }),
200
173
  resolvedDescription && /* @__PURE__ */ jsx("p", {
201
174
  id: descriptionId,
@@ -215,17 +188,16 @@ function RichTextEditor({ name, value, onChange, disabled, readOnly, label, desc
215
188
  * `useEditor` always receives a complete extension list, so the ProseMirror
216
189
  * schema always has its `doc` node.
217
190
  */
218
- function RichTextEditorCore({ name, ariaLabel, ariaDescribedBy, value, onChange, disabled, readOnly, error, locale, features, resolvedExtensions, showCharacterCount, maxCharacters, enableImages, onImageUpload, imageCollection, enableMediaLibrary }) {
191
+ function RichTextEditorCore({ name, ariaLabel, ariaDescribedBy, value, onChange, disabled, readOnly, error, locale, features, resolvedExtensions, showCharacterCount, maxCharacters, enableImages, onImageUpload, imageCollection, enableMediaLibrary, outputMode }) {
219
192
  const { t } = useTranslation();
220
193
  const [linkOpen, setLinkOpen] = React.useState(false);
221
194
  const [imageOpen, setImageOpen] = React.useState(false);
222
195
  const [uploadingInlineImage, setUploadingInlineImage] = React.useState(false);
223
- const lastEmittedValueRef = React.useRef(void 0);
196
+ const lastEmittedValueRef = React.useRef(value);
224
197
  const editorRef = React.useRef(null);
225
198
  const allowImages = features.image && (enableImages ?? true);
226
199
  const allowLinks = features.link;
227
200
  const allowBubbleMenu = features.bubbleMenu;
228
- const allowToolbar = features.toolbar;
229
201
  const allowCharacterCount = features.characterCount && (showCharacterCount || maxCharacters);
230
202
  const isEditable = !disabled && !readOnly;
231
203
  const editorStateRef = React.useRef({
@@ -339,7 +311,8 @@ function RichTextEditorCore({ name, ariaLabel, ariaDescribedBy, value, onChange,
339
311
  editable: isEditable,
340
312
  onUpdate: ({ editor: currentEditor }) => {
341
313
  if (disabled || readOnly) return;
342
- const nextValue = getOutput(currentEditor);
314
+ const nextValue = getOutput(currentEditor, outputMode);
315
+ if (isSameValue(nextValue, lastEmittedValueRef.current)) return;
343
316
  lastEmittedValueRef.current = nextValue;
344
317
  onChange?.(nextValue);
345
318
  }
@@ -350,7 +323,6 @@ function RichTextEditorCore({ name, ariaLabel, ariaDescribedBy, value, onChange,
350
323
  if (editorRef.current === editor) editorRef.current = null;
351
324
  };
352
325
  }, [editor]);
353
- const headingValue = getHeadingLevel(editor);
354
326
  const inTable = editor?.isActive("table") ?? false;
355
327
  React.useEffect(() => {
356
328
  if (!editor) return;
@@ -372,33 +344,9 @@ function RichTextEditorCore({ name, ariaLabel, ariaDescribedBy, value, onChange,
372
344
  }, [editor, value]);
373
345
  const characterCount = getCharacterCount(editor);
374
346
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
375
- className: cn("qp-rich-text-editor control-surface h-auto min-h-[160px] overflow-hidden", disabled || readOnly ? "opacity-60" : "", error ? "border-destructive" : "border-border"),
347
+ className: cn("qp-rich-text-editor", disabled || readOnly ? "opacity-60" : ""),
376
348
  "data-admin-rich-text-editor": "root",
377
349
  children: [
378
- editor && allowToolbar && /* @__PURE__ */ jsx(RichTextToolbar, {
379
- editor,
380
- features,
381
- disabled: !isEditable,
382
- headingValue,
383
- onHeadingChange: (value$1) => {
384
- if (!editor) return;
385
- if (value$1 === "paragraph") {
386
- editor.chain().focus().setParagraph().run();
387
- return;
388
- }
389
- editor.chain().focus().toggleHeading({ level: Number(value$1) }).run();
390
- },
391
- onLinkClick: () => setLinkOpen(true),
392
- onImageClick: () => setImageOpen(true),
393
- onTableClick: () => {
394
- if (!inTable) editor.chain().focus().insertTable({
395
- rows: 3,
396
- cols: 3,
397
- withHeaderRow: true
398
- }).run();
399
- },
400
- inTable
401
- }),
402
350
  editor && (allowBubbleMenu || linkOpen) && /* @__PURE__ */ jsx(RichTextBubbleMenu, {
403
351
  editor,
404
352
  features,
@@ -412,6 +360,20 @@ function RichTextEditorCore({ name, ariaLabel, ariaDescribedBy, value, onChange,
412
360
  editor,
413
361
  id: name
414
362
  }) : /* @__PURE__ */ jsx(RichTextEditorContentSkeleton, {}),
363
+ editor && features.table && features.tableControls && inTable && /* @__PURE__ */ jsx("div", {
364
+ className: "flex justify-end px-2 py-1",
365
+ children: /* @__PURE__ */ jsx(TableControls, {
366
+ editor,
367
+ disabled: !isEditable,
368
+ inTable,
369
+ triggerButton: /* @__PURE__ */ jsx(ToolbarButton, {
370
+ icon: "ph:table",
371
+ active: inTable,
372
+ disabled: !isEditable,
373
+ title: t("editor.table")
374
+ })
375
+ })
376
+ }),
415
377
  uploadingInlineImage && /* @__PURE__ */ jsx("div", {
416
378
  className: "qp-rich-text-editor__upload-status",
417
379
  role: "status",
@@ -419,7 +381,7 @@ function RichTextEditorCore({ name, ariaLabel, ariaDescribedBy, value, onChange,
419
381
  children: t("editor.uploading")
420
382
  }),
421
383
  allowCharacterCount && showCharacterCount && /* @__PURE__ */ jsxs("div", {
422
- className: "bg-surface-low text-muted-foreground border-border-subtle flex items-center justify-between border-t px-2 py-1 text-xs",
384
+ className: "text-muted-foreground flex items-center justify-between px-1.5 pt-2 text-xs",
423
385
  children: [/* @__PURE__ */ jsxs("span", { children: [
424
386
  characterCount.words,
425
387
  " word",
@@ -47,21 +47,44 @@ const SlashCommandList = React.forwardRef(function SlashCommandList$1({ items, c
47
47
  }
48
48
  return false;
49
49
  } }));
50
+ const grouped = React.useMemo(() => {
51
+ const groups = [];
52
+ let currentLabel;
53
+ for (let i = 0; i < items.length; i++) {
54
+ const item = items[i];
55
+ if (item.group !== currentLabel || i === 0) {
56
+ currentLabel = item.group;
57
+ groups.push({
58
+ label: currentLabel,
59
+ items: []
60
+ });
61
+ }
62
+ groups[groups.length - 1].items.push({
63
+ item,
64
+ globalIndex: i
65
+ });
66
+ }
67
+ return groups;
68
+ }, [items]);
50
69
  return /* @__PURE__ */ jsxs("div", {
51
70
  className: "qp-rich-text-editor__slash",
52
71
  role: "listbox",
53
72
  children: [items.length === 0 && /* @__PURE__ */ jsx("div", {
54
73
  className: "qp-rich-text-editor__slash-empty",
55
74
  children: "No results"
56
- }), items.map((item, index) => /* @__PURE__ */ jsxs("button", {
75
+ }), grouped.map((group) => /* @__PURE__ */ jsxs("div", { children: [group.label && /* @__PURE__ */ jsx("div", {
76
+ className: "qp-rich-text-editor__slash-group",
77
+ "aria-hidden": "true",
78
+ children: group.label
79
+ }), group.items.map(({ item, globalIndex }) => /* @__PURE__ */ jsxs("button", {
57
80
  type: "button",
58
- "aria-selected": index === safeIndex,
81
+ "aria-selected": globalIndex === safeIndex,
59
82
  role: "option",
60
83
  tabIndex: -1,
61
- className: cn("qp-rich-text-editor__slash-item", index === safeIndex ? "qp-rich-text-editor__slash-item--active" : ""),
62
- onMouseEnter: () => setSelectedIndex(index),
84
+ className: cn("qp-rich-text-editor__slash-item", globalIndex === safeIndex ? "qp-rich-text-editor__slash-item--active" : ""),
85
+ onMouseEnter: () => setSelectedIndex(globalIndex),
63
86
  onMouseDown: (event) => event.preventDefault(),
64
- onClick: () => selectItem(index),
87
+ onClick: () => selectItem(globalIndex),
65
88
  children: [item.icon && /* @__PURE__ */ jsx("span", {
66
89
  className: "qp-rich-text-editor__slash-icon",
67
90
  "aria-hidden": "true",
@@ -80,7 +103,7 @@ const SlashCommandList = React.forwardRef(function SlashCommandList$1({ items, c
80
103
  children: item.description
81
104
  })]
82
105
  })]
83
- }, item.title))]
106
+ }, item.title))] }, group.label ?? "ungrouped"))]
84
107
  });
85
108
  });
86
109
  /**
@@ -92,7 +115,7 @@ function createSlashCommandExtension(getItems) {
92
115
  addOptions() {
93
116
  return { suggestion: {
94
117
  char: "/",
95
- startOfLine: true,
118
+ startOfLine: false,
96
119
  command: ({ editor, range, props }) => {
97
120
  editor.chain().focus().deleteRange(range).run();
98
121
  props.command(editor);
@@ -1,9 +1,5 @@
1
- import { useTranslation } from "../../../i18n/hooks.mjs";
2
1
  import { cn } from "../../../lib/utils.mjs";
3
2
  import { Button } from "../../ui/button.mjs";
4
- import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue } from "../../ui/select.mjs";
5
- import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuTrigger } from "../../ui/dropdown-menu.mjs";
6
- import { TableControls } from "./table-controls.mjs";
7
3
  import { Icon } from "@iconify/react";
8
4
  import "react";
9
5
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -43,36 +39,6 @@ const EDITOR_ICONS = {
43
39
  insert: "ph:plus",
44
40
  more: "ph:dots-three-vertical"
45
41
  };
46
- const HEADING_OPTIONS = [
47
- {
48
- value: "paragraph",
49
- group: "text"
50
- },
51
- {
52
- value: "1",
53
- group: "heading"
54
- },
55
- {
56
- value: "2",
57
- group: "heading"
58
- },
59
- {
60
- value: "3",
61
- group: "heading"
62
- },
63
- {
64
- value: "4",
65
- group: "heading"
66
- },
67
- {
68
- value: "5",
69
- group: "heading"
70
- },
71
- {
72
- value: "6",
73
- group: "heading"
74
- }
75
- ];
76
42
  const ALIGNMENT_OPTIONS = [
77
43
  {
78
44
  value: "left",
@@ -95,16 +61,6 @@ const ALIGNMENT_OPTIONS = [
95
61
  labelKey: "editor.alignJustify"
96
62
  }
97
63
  ];
98
- function getHeadingOptionLabel(value, t) {
99
- if (value === "paragraph") return t("editor.paragraph");
100
- return t("editor.heading", { level: value });
101
- }
102
- function getCurrentAlignment(editor) {
103
- if (editor.isActive({ textAlign: "center" })) return "center";
104
- if (editor.isActive({ textAlign: "right" })) return "right";
105
- if (editor.isActive({ textAlign: "justify" })) return "justify";
106
- return "left";
107
- }
108
64
  /**
109
65
  * Icon-based toolbar button with tooltip showing keyboard shortcuts
110
66
  */
@@ -132,273 +88,6 @@ function ToolbarButton({ active, disabled, title, onClick, icon: iconName, child
132
88
  }) : children
133
89
  });
134
90
  }
135
- /**
136
- * Toolbar button group with divider
137
- */
138
- function ToolbarGroup({ children }) {
139
- return /* @__PURE__ */ jsx("div", {
140
- className: "flex items-center gap-1",
141
- role: "group",
142
- children
143
- });
144
- }
145
- function ToolbarMenuItem({ active, disabled, icon, label, onClick, shortcut }) {
146
- return /* @__PURE__ */ jsxs(DropdownMenuItem, {
147
- "aria-current": active ? "true" : void 0,
148
- disabled,
149
- onClick,
150
- className: cn("min-w-44", active ? "bg-accent text-accent-foreground" : ""),
151
- children: [
152
- /* @__PURE__ */ jsx(Icon, {
153
- "aria-hidden": "true",
154
- icon
155
- }),
156
- /* @__PURE__ */ jsx("span", { children: label }),
157
- shortcut ? /* @__PURE__ */ jsx(DropdownMenuShortcut, { children: shortcut }) : active ? /* @__PURE__ */ jsx(Icon, {
158
- "aria-hidden": "true",
159
- icon: "ph:check",
160
- className: "ml-auto size-3.5"
161
- }) : null
162
- ]
163
- });
164
- }
165
- /**
166
- * Main toolbar component with icon-based buttons
167
- */
168
- function RichTextToolbar({ editor, features, disabled, headingValue, onHeadingChange, onLinkClick, onImageClick, onTableClick, inTable }) {
169
- const { t } = useTranslation();
170
- const isEditable = !disabled;
171
- const currentAlignment = getCurrentAlignment(editor);
172
- const currentAlignmentOption = ALIGNMENT_OPTIONS.find((option) => option.value === currentAlignment) ?? ALIGNMENT_OPTIONS[0];
173
- const hasMoreFormatting = features.underline || features.strike || features.code;
174
- const hasBlockMenu = features.bulletList || features.orderedList || features.blockquote || features.codeBlock || features.horizontalRule;
175
- const hasInsertMenu = features.link || features.image || features.table;
176
- return /* @__PURE__ */ jsxs("div", {
177
- "aria-label": t("editor.richTextToolbar"),
178
- className: "qp-rich-text-editor__toolbar border-border-subtle bg-surface-low flex flex-nowrap items-center gap-1.5 overflow-x-auto border-b p-1.5",
179
- role: "toolbar",
180
- children: [
181
- features.history && /* @__PURE__ */ jsxs(ToolbarGroup, { children: [/* @__PURE__ */ jsx(ToolbarButton, {
182
- icon: EDITOR_ICONS.undo,
183
- disabled: !isEditable || !editor.can().undo(),
184
- title: t("editor.undo"),
185
- shortcut: "⌘Z",
186
- onClick: () => editor.chain().focus().undo().run()
187
- }), /* @__PURE__ */ jsx(ToolbarButton, {
188
- icon: EDITOR_ICONS.redo,
189
- disabled: !isEditable || !editor.can().redo(),
190
- title: t("editor.redo"),
191
- shortcut: "⌘⇧Z",
192
- onClick: () => editor.chain().focus().redo().run()
193
- })] }),
194
- features.heading && /* @__PURE__ */ jsx(ToolbarGroup, { children: /* @__PURE__ */ jsxs(Select, {
195
- value: headingValue,
196
- disabled: !isEditable,
197
- onValueChange: (value) => {
198
- if (typeof value === "string") onHeadingChange(value);
199
- },
200
- children: [/* @__PURE__ */ jsx(SelectTrigger, {
201
- "aria-label": t("editor.blockType"),
202
- className: "h-8 min-w-[136px] px-2.5 text-sm",
203
- size: "sm",
204
- children: /* @__PURE__ */ jsx(SelectValue, { children: HEADING_OPTIONS.find((option) => option.value === headingValue)?.value ? getHeadingOptionLabel(HEADING_OPTIONS.find((option) => option.value === headingValue)?.value ?? "paragraph", t) : t("editor.paragraph") })
205
- }), /* @__PURE__ */ jsxs(SelectContent, {
206
- align: "start",
207
- className: "min-w-[136px]",
208
- children: [
209
- /* @__PURE__ */ jsxs(SelectGroup, { children: [/* @__PURE__ */ jsx(SelectLabel, { children: t("editor.textBlocks") }), /* @__PURE__ */ jsx(SelectItem, {
210
- value: "paragraph",
211
- children: t("editor.paragraph")
212
- })] }),
213
- /* @__PURE__ */ jsx(SelectSeparator, {}),
214
- /* @__PURE__ */ jsxs(SelectGroup, { children: [/* @__PURE__ */ jsx(SelectLabel, { children: t("editor.headings") }), HEADING_OPTIONS.filter((option) => option.group === "heading").map((option) => /* @__PURE__ */ jsx(SelectItem, {
215
- value: option.value,
216
- children: getHeadingOptionLabel(option.value, t)
217
- }, option.value))] })
218
- ]
219
- })]
220
- }) }),
221
- /* @__PURE__ */ jsxs(ToolbarGroup, { children: [
222
- features.bold && /* @__PURE__ */ jsx(ToolbarButton, {
223
- icon: EDITOR_ICONS.bold,
224
- active: editor.isActive("bold"),
225
- disabled: !isEditable,
226
- title: t("editor.bold"),
227
- shortcut: "⌘B",
228
- onClick: () => editor.chain().focus().toggleBold().run()
229
- }),
230
- features.italic && /* @__PURE__ */ jsx(ToolbarButton, {
231
- icon: EDITOR_ICONS.italic,
232
- active: editor.isActive("italic"),
233
- disabled: !isEditable,
234
- title: t("editor.italic"),
235
- shortcut: "⌘I",
236
- onClick: () => editor.chain().focus().toggleItalic().run()
237
- }),
238
- hasMoreFormatting && /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
239
- nativeButton: false,
240
- render: /* @__PURE__ */ jsx(ToolbarButton, {
241
- icon: EDITOR_ICONS.formatting,
242
- active: editor.isActive("underline") || editor.isActive("strike") || editor.isActive("code"),
243
- disabled: !isEditable,
244
- title: t("editor.moreFormatting")
245
- })
246
- }), /* @__PURE__ */ jsxs(DropdownMenuContent, {
247
- align: "start",
248
- className: "w-56",
249
- children: [
250
- /* @__PURE__ */ jsx(DropdownMenuLabel, { children: t("editor.formatting") }),
251
- features.underline && /* @__PURE__ */ jsx(ToolbarMenuItem, {
252
- icon: EDITOR_ICONS.underline,
253
- active: editor.isActive("underline"),
254
- disabled: !isEditable,
255
- label: t("editor.underline"),
256
- shortcut: "⌘U",
257
- onClick: () => editor.chain().focus().toggleUnderline().run()
258
- }),
259
- features.strike && /* @__PURE__ */ jsx(ToolbarMenuItem, {
260
- icon: EDITOR_ICONS.strikethrough,
261
- active: editor.isActive("strike"),
262
- disabled: !isEditable,
263
- label: t("editor.strikethrough"),
264
- onClick: () => editor.chain().focus().toggleStrike().run()
265
- }),
266
- features.code && /* @__PURE__ */ jsx(ToolbarMenuItem, {
267
- icon: EDITOR_ICONS.code,
268
- active: editor.isActive("code"),
269
- disabled: !isEditable,
270
- label: t("editor.code"),
271
- shortcut: "⌘E",
272
- onClick: () => editor.chain().focus().toggleCode().run()
273
- })
274
- ]
275
- })] })
276
- ] }),
277
- hasBlockMenu && /* @__PURE__ */ jsx(ToolbarGroup, { children: /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
278
- nativeButton: false,
279
- render: /* @__PURE__ */ jsx(ToolbarButton, {
280
- icon: EDITOR_ICONS.blocks,
281
- active: editor.isActive("bulletList") || editor.isActive("orderedList") || editor.isActive("blockquote") || editor.isActive("codeBlock"),
282
- disabled: !isEditable,
283
- title: t("editor.blocks")
284
- })
285
- }), /* @__PURE__ */ jsxs(DropdownMenuContent, {
286
- align: "start",
287
- className: "w-56",
288
- children: [
289
- /* @__PURE__ */ jsx(DropdownMenuLabel, { children: t("editor.list") }),
290
- features.bulletList && /* @__PURE__ */ jsx(ToolbarMenuItem, {
291
- icon: EDITOR_ICONS.bulletList,
292
- active: editor.isActive("bulletList"),
293
- disabled: !isEditable,
294
- label: t("editor.unorderedList"),
295
- onClick: () => editor.chain().focus().toggleBulletList().run()
296
- }),
297
- features.orderedList && /* @__PURE__ */ jsx(ToolbarMenuItem, {
298
- icon: EDITOR_ICONS.orderedList,
299
- active: editor.isActive("orderedList"),
300
- disabled: !isEditable,
301
- label: t("editor.orderedList"),
302
- onClick: () => editor.chain().focus().toggleOrderedList().run()
303
- }),
304
- (features.blockquote || features.codeBlock || features.horizontalRule) && /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
305
- features.blockquote && /* @__PURE__ */ jsx(ToolbarMenuItem, {
306
- icon: EDITOR_ICONS.blockquote,
307
- active: editor.isActive("blockquote"),
308
- disabled: !isEditable,
309
- label: t("editor.quote"),
310
- onClick: () => editor.chain().focus().toggleBlockquote().run()
311
- }),
312
- features.codeBlock && /* @__PURE__ */ jsx(ToolbarMenuItem, {
313
- icon: EDITOR_ICONS.codeBlock,
314
- active: editor.isActive("codeBlock"),
315
- disabled: !isEditable,
316
- label: t("editor.codeBlock"),
317
- onClick: () => editor.chain().focus().toggleCodeBlock().run()
318
- }),
319
- features.horizontalRule && /* @__PURE__ */ jsx(ToolbarMenuItem, {
320
- icon: EDITOR_ICONS.horizontalRule,
321
- disabled: !isEditable,
322
- label: t("editor.horizontalRule"),
323
- onClick: () => editor.chain().focus().setHorizontalRule().run()
324
- })
325
- ]
326
- })] }) }),
327
- features.align && /* @__PURE__ */ jsx(ToolbarGroup, { children: /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
328
- nativeButton: false,
329
- render: /* @__PURE__ */ jsx(ToolbarButton, {
330
- icon: currentAlignmentOption.icon,
331
- active: currentAlignment !== "left",
332
- disabled: !isEditable,
333
- title: t("editor.alignment")
334
- })
335
- }), /* @__PURE__ */ jsxs(DropdownMenuContent, {
336
- align: "start",
337
- className: "w-56",
338
- children: [/* @__PURE__ */ jsx(DropdownMenuLabel, { children: t("editor.alignment") }), /* @__PURE__ */ jsx(DropdownMenuRadioGroup, {
339
- value: currentAlignment,
340
- onValueChange: (value) => {
341
- editor.chain().focus().setTextAlign(value).run();
342
- },
343
- children: ALIGNMENT_OPTIONS.map((option) => /* @__PURE__ */ jsxs(DropdownMenuRadioItem, {
344
- value: option.value,
345
- disabled: !isEditable,
346
- children: [/* @__PURE__ */ jsx(Icon, {
347
- "aria-hidden": "true",
348
- icon: option.icon
349
- }), /* @__PURE__ */ jsx("span", { children: t(option.labelKey) })]
350
- }, option.value))
351
- })]
352
- })] }) }),
353
- hasInsertMenu && /* @__PURE__ */ jsxs(ToolbarGroup, { children: [/* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
354
- nativeButton: false,
355
- render: /* @__PURE__ */ jsx(ToolbarButton, {
356
- icon: EDITOR_ICONS.insert,
357
- active: editor.isActive("link"),
358
- disabled: !isEditable,
359
- title: t("editor.insert")
360
- })
361
- }), /* @__PURE__ */ jsxs(DropdownMenuContent, {
362
- align: "start",
363
- className: "w-56",
364
- children: [
365
- /* @__PURE__ */ jsx(DropdownMenuLabel, { children: t("editor.insert") }),
366
- features.link && /* @__PURE__ */ jsx(ToolbarMenuItem, {
367
- icon: EDITOR_ICONS.link,
368
- active: editor.isActive("link"),
369
- disabled: !isEditable,
370
- label: t("editor.link"),
371
- shortcut: "⌘K",
372
- onClick: onLinkClick
373
- }),
374
- features.image && /* @__PURE__ */ jsx(ToolbarMenuItem, {
375
- icon: EDITOR_ICONS.image,
376
- disabled: !isEditable,
377
- label: t("editor.image"),
378
- onClick: onImageClick
379
- }),
380
- features.table && /* @__PURE__ */ jsx(ToolbarMenuItem, {
381
- icon: EDITOR_ICONS.table,
382
- active: inTable,
383
- disabled: !isEditable,
384
- label: t("editor.table"),
385
- onClick: onTableClick
386
- })
387
- ]
388
- })] }), features.table && features.tableControls && inTable && /* @__PURE__ */ jsx(TableControls, {
389
- editor,
390
- disabled: !isEditable,
391
- inTable,
392
- triggerButton: /* @__PURE__ */ jsx(ToolbarButton, {
393
- icon: EDITOR_ICONS.table,
394
- active: inTable,
395
- disabled: !isEditable,
396
- title: t("editor.table")
397
- })
398
- })] })
399
- ]
400
- });
401
- }
402
91
 
403
92
  //#endregion
404
- export { EDITOR_ICONS, RichTextToolbar, ToolbarButton };
93
+ export { EDITOR_ICONS, ToolbarButton };
@@ -72,6 +72,10 @@ interface RichTextEditorProps extends FieldComponentProps<any> {
72
72
  * Enable media library picker for images
73
73
  */
74
74
  enableMediaLibrary?: boolean;
75
+ /**
76
+ * Output mode: "json" (default TipTap JSON) or "markdown" (plain markdown string)
77
+ */
78
+ outputMode?: "json" | "markdown";
75
79
  }
76
80
  //#endregion
77
81
  export { RichTextEditorProps };
@@ -3,7 +3,7 @@
3
3
  * Default feature configuration (all enabled)
4
4
  */
5
5
  const defaultFeatures = {
6
- toolbar: true,
6
+ toolbar: false,
7
7
  bubbleMenu: true,
8
8
  slashCommands: true,
9
9
  history: true,