@openrails/contentkit-ui 0.70.0 → 0.72.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/README.md +98 -10
  2. package/dist/client/content/bans.d.ts +2 -0
  3. package/dist/client/content/bans.js +24 -12
  4. package/dist/client/content/bans.js.map +1 -1
  5. package/dist/client/content/comments.d.ts +4 -2
  6. package/dist/client/content/comments.js.map +1 -1
  7. package/dist/client/content/polls.d.ts +10 -3
  8. package/dist/client/content/polls.js.map +1 -1
  9. package/dist/client/content/posts.d.ts +4 -4
  10. package/dist/client/content/posts.js.map +1 -1
  11. package/dist/client/generated/error-codes.js +2 -2
  12. package/dist/client/generated/error-codes.js.map +1 -1
  13. package/dist/client/generated/routes.d.ts +146 -120
  14. package/dist/client/generated/routes.js +245 -117
  15. package/dist/client/generated/routes.js.map +1 -1
  16. package/dist/client/generated/wire.d.ts +5 -0
  17. package/dist/client/generated/wire.js.map +1 -1
  18. package/dist/client/index.d.ts +2 -2
  19. package/dist/client/taxonomy.d.ts +6 -2
  20. package/dist/client/taxonomy.js +7 -0
  21. package/dist/client/taxonomy.js.map +1 -1
  22. package/dist/components/admin/poll-manager.d.ts +28 -0
  23. package/dist/components/admin/poll-manager.js +297 -0
  24. package/dist/components/admin/poll-manager.js.map +1 -0
  25. package/dist/components/admin/post-manager.d.ts +45 -0
  26. package/dist/components/admin/post-manager.js +602 -0
  27. package/dist/components/admin/post-manager.js.map +1 -0
  28. package/dist/components/admin/shell.d.ts +73 -0
  29. package/dist/components/admin/shell.js +148 -0
  30. package/dist/components/admin/shell.js.map +1 -0
  31. package/dist/components/admin/taxonomy-manager.d.ts +54 -0
  32. package/dist/components/admin/taxonomy-manager.js +976 -0
  33. package/dist/components/admin/taxonomy-manager.js.map +1 -0
  34. package/dist/components/admin/user-comment-standing.d.ts +25 -0
  35. package/dist/components/admin/user-comment-standing.js +229 -0
  36. package/dist/components/admin/user-comment-standing.js.map +1 -0
  37. package/dist/components/social/bans.d.ts +5 -2
  38. package/dist/components/social/bans.js +132 -134
  39. package/dist/components/social/bans.js.map +1 -1
  40. package/dist/components/social/index.d.ts +1 -1
  41. package/dist/components/social/moderation.d.ts +11 -13
  42. package/dist/components/social/moderation.js +299 -218
  43. package/dist/components/social/moderation.js.map +1 -1
  44. package/dist/components/social/parts.d.ts +3 -2
  45. package/dist/components/social/parts.js +74 -48
  46. package/dist/components/social/parts.js.map +1 -1
  47. package/dist/components/social/poll-editor.d.ts +1 -1
  48. package/dist/components/social/poll-editor.js +1 -0
  49. package/dist/components/social/poll-editor.js.map +1 -1
  50. package/dist/editor/index.d.ts +1 -0
  51. package/dist/editor/rich-text-editor.d.ts +13 -0
  52. package/dist/editor/rich-text-editor.js +228 -0
  53. package/dist/editor/rich-text-editor.js.map +1 -0
  54. package/dist/editor.js +2 -0
  55. package/dist/index.d.ts +6 -0
  56. package/dist/index.js +13 -8
  57. package/dist/locales/admin/de.d.ts +2 -0
  58. package/dist/locales/admin/de.js +176 -0
  59. package/dist/locales/admin/de.js.map +1 -0
  60. package/dist/locales/admin/en.d.ts +171 -0
  61. package/dist/locales/admin/en.js +176 -0
  62. package/dist/locales/admin/en.js.map +1 -0
  63. package/dist/locales/admin/es.d.ts +2 -0
  64. package/dist/locales/admin/es.js +176 -0
  65. package/dist/locales/admin/es.js.map +1 -0
  66. package/dist/locales/admin/ja.d.ts +2 -0
  67. package/dist/locales/admin/ja.js +176 -0
  68. package/dist/locales/admin/ja.js.map +1 -0
  69. package/dist/locales/admin/ko.d.ts +2 -0
  70. package/dist/locales/admin/ko.js +176 -0
  71. package/dist/locales/admin/ko.js.map +1 -0
  72. package/dist/locales/admin/zh.d.ts +2 -0
  73. package/dist/locales/admin/zh.js +176 -0
  74. package/dist/locales/admin/zh.js.map +1 -0
  75. package/dist/locales/de.js +5 -3
  76. package/dist/locales/de.js.map +1 -1
  77. package/dist/locales/en.d.ts +176 -0
  78. package/dist/locales/en.js +5 -3
  79. package/dist/locales/en.js.map +1 -1
  80. package/dist/locales/es.js +5 -3
  81. package/dist/locales/es.js.map +1 -1
  82. package/dist/locales/ja.js +5 -3
  83. package/dist/locales/ja.js.map +1 -1
  84. package/dist/locales/ko.js +5 -3
  85. package/dist/locales/ko.js.map +1 -1
  86. package/dist/locales/social/de.js +8 -1
  87. package/dist/locales/social/de.js.map +1 -1
  88. package/dist/locales/social/en.d.ts +7 -0
  89. package/dist/locales/social/en.js +8 -1
  90. package/dist/locales/social/en.js.map +1 -1
  91. package/dist/locales/social/es.js +8 -1
  92. package/dist/locales/social/es.js.map +1 -1
  93. package/dist/locales/social/ja.js +8 -1
  94. package/dist/locales/social/ja.js.map +1 -1
  95. package/dist/locales/social/ko.js +8 -1
  96. package/dist/locales/social/ko.js.map +1 -1
  97. package/dist/locales/social/zh.js +8 -1
  98. package/dist/locales/social/zh.js.map +1 -1
  99. package/dist/locales/zh.js +5 -3
  100. package/dist/locales/zh.js.map +1 -1
  101. package/dist/provider.d.ts +2 -0
  102. package/dist/provider.js +23 -19
  103. package/dist/provider.js.map +1 -1
  104. package/dist/react/admin.d.ts +55 -0
  105. package/dist/react/admin.js +97 -0
  106. package/dist/react/admin.js.map +1 -0
  107. package/dist/react/comments.d.ts +18 -2
  108. package/dist/react/comments.js +24 -8
  109. package/dist/react/comments.js.map +1 -1
  110. package/dist/react/context.d.ts +7 -1
  111. package/dist/react/context.js +6 -1
  112. package/dist/react/context.js.map +1 -1
  113. package/dist/react/index.d.ts +4 -3
  114. package/dist/react/polls.d.ts +2 -2
  115. package/dist/react/polls.js +88 -87
  116. package/dist/react/polls.js.map +1 -1
  117. package/dist/react/posts.d.ts +3 -3
  118. package/dist/react/posts.js +123 -122
  119. package/dist/react/posts.js.map +1 -1
  120. package/dist/react/resources.js +3 -0
  121. package/dist/react/resources.js.map +1 -1
  122. package/dist/react/use-resource.d.ts +16 -0
  123. package/dist/react/use-resource.js +20 -1
  124. package/dist/react/use-resource.js.map +1 -1
  125. package/dist/react.js +21 -20
  126. package/dist/styles.css +1 -1
  127. package/package.json +37 -2
@@ -0,0 +1,13 @@
1
+ import { ReactNode } from 'react';
2
+ import { PostEditorProps } from '../components/admin/post-manager.js';
3
+ export type RichTextEditorProps = PostEditorProps & {
4
+ className?: string;
5
+ };
6
+ /**
7
+ * A rich-text field over Tiptap: headings, emphasis, lists, quotes, links
8
+ * and images (picked, pasted or dropped, through uploadImage). Its value is
9
+ * HTML.
10
+ */
11
+ export declare function RichTextEditor({ value, onChange, uploadImage, disabled, placeholder, className, ...aria }: RichTextEditorProps): import('react').JSX.Element;
12
+ /** A render prop for PostManager: `renderEditor={richTextEditor}`. */
13
+ export declare function richTextEditor(props: PostEditorProps): ReactNode;
@@ -0,0 +1,228 @@
1
+ import { toContentKitError as e } from "../client/errors.js";
2
+ import { useMessages as t } from "../i18n/context.js";
3
+ import { Button as n } from "../ui/button.js";
4
+ import { Input as r } from "../ui/input.js";
5
+ import { ErrorLine as i } from "../components/social/parts.js";
6
+ import { useEffect as a, useId as o, useRef as s, useState as c } from "react";
7
+ import { jsx as l, jsxs as u } from "react/jsx-runtime";
8
+ import { cn as d } from "cn";
9
+ import { ArrowTurnBackwardIcon as f, ArrowTurnForwardIcon as p, Heading02Icon as m, Heading03Icon as h, Image01Icon as g, LeftToRightListBulletIcon as _, LeftToRightListNumberIcon as v, Link01Icon as y, QuoteDownIcon as b, TextBoldIcon as x, TextItalicIcon as S, TextStrikethroughIcon as C, TextUnderlineIcon as w } from "@hugeicons/core-free-icons";
10
+ import { HugeiconsIcon as T } from "@hugeicons/react";
11
+ import E from "@tiptap/extension-image";
12
+ import { Placeholder as D } from "@tiptap/extensions";
13
+ import { EditorContent as O, useEditor as k, useEditorState as A } from "@tiptap/react";
14
+ import j from "@tiptap/starter-kit";
15
+ //#region src/editor/rich-text-editor.tsx
16
+ var M = "min-h-48 px-3 py-2 outline-none break-words [&_p]:my-2 [&_h2]:mt-4 [&_h2]:mb-2 [&_h2]:text-lg [&_h2]:font-semibold [&_h3]:mt-3 [&_h3]:mb-1.5 [&_h3]:font-semibold [&_ul]:my-2 [&_ul]:list-disc [&_ul]:pl-6 [&_ol]:my-2 [&_ol]:list-decimal [&_ol]:pl-6 [&_blockquote]:my-2 [&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-3 [&_blockquote]:text-muted-foreground [&_a]:text-primary [&_a]:underline [&_img]:my-2 [&_img]:max-h-96 [&_img]:max-w-full [&_img]:rounded-md [&_img.ProseMirror-selectednode]:ring-2 [&_img.ProseMirror-selectednode]:ring-ring [&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_pre]:my-2 [&_pre]:rounded-md [&_pre]:bg-muted [&_pre]:p-3 [&_p.is-editor-empty:first-child]:before:pointer-events-none [&_p.is-editor-empty:first-child]:before:float-left [&_p.is-editor-empty:first-child]:before:h-0 [&_p.is-editor-empty:first-child]:before:text-muted-foreground [&_p.is-editor-empty:first-child]:before:content-[attr(data-placeholder)]";
17
+ function N({ value: n, onChange: r, uploadImage: o, disabled: f, placeholder: p, className: m, ...h }) {
18
+ let { t: g } = t(), [_, v] = c(null), [y, b] = c(0), x = s(o), S = s(r);
19
+ a(() => {
20
+ x.current = o, S.current = r;
21
+ });
22
+ let C = async (t, n, r) => {
23
+ if (x.current) {
24
+ v(null);
25
+ for (let i of n) {
26
+ b((e) => e + 1);
27
+ try {
28
+ let e = await x.current(i), n = t.chain().focus();
29
+ (r === void 0 ? n : n.setTextSelection(r)).setImage({
30
+ src: e,
31
+ alt: ""
32
+ }).run();
33
+ } catch (t) {
34
+ v(e(t));
35
+ } finally {
36
+ b((e) => e - 1);
37
+ }
38
+ }
39
+ }
40
+ }, w = (e) => [...e ?? []].filter((e) => e.type.startsWith("image/")), T = k({
41
+ extensions: [
42
+ j.configure({
43
+ heading: { levels: [2, 3] },
44
+ link: {
45
+ openOnClick: !1,
46
+ autolink: !0,
47
+ defaultProtocol: "https"
48
+ }
49
+ }),
50
+ E.configure({ inline: !1 }),
51
+ D.configure({ placeholder: p ?? g("editor.placeholder") })
52
+ ],
53
+ content: n,
54
+ editable: !f,
55
+ immediatelyRender: !1,
56
+ editorProps: {
57
+ attributes: {
58
+ class: M,
59
+ role: "textbox",
60
+ "aria-multiline": "true",
61
+ ...h["aria-label"] ? { "aria-label": h["aria-label"] } : {}
62
+ },
63
+ handlePaste: (e, t) => {
64
+ let n = w(t.clipboardData?.files);
65
+ return !n.length || !x.current || !A.current ? !1 : (C(A.current, n), !0);
66
+ },
67
+ handleDrop: (e, t) => {
68
+ let n = w(t.dataTransfer?.files);
69
+ if (!n.length || !x.current || !A.current) return !1;
70
+ let r = e.posAtCoords({
71
+ left: t.clientX,
72
+ top: t.clientY
73
+ });
74
+ return C(A.current, n, r?.pos), !0;
75
+ }
76
+ },
77
+ onUpdate: ({ editor: e }) => S.current(e.isEmpty ? "" : e.getHTML())
78
+ }), A = s(null);
79
+ return A.current = T, a(() => {
80
+ T && n !== (T.isEmpty ? "" : T.getHTML()) && T.commands.setContent(n, { emitUpdate: !1 });
81
+ }, [T, n]), a(() => {
82
+ T?.setEditable(!f);
83
+ }, [T, f]), /* @__PURE__ */ u("div", {
84
+ className: d("grid overflow-hidden rounded-lg border border-input bg-background focus-within:ring-2 focus-within:ring-ring/50", m),
85
+ "data-ckui": "rich-text-editor",
86
+ children: [
87
+ T && /* @__PURE__ */ l(F, {
88
+ editor: T,
89
+ disabled: f,
90
+ canUpload: !!o,
91
+ uploading: y > 0,
92
+ onImages: (e) => void C(T, e)
93
+ }),
94
+ /* @__PURE__ */ l(O, { editor: T }),
95
+ _ && /* @__PURE__ */ l(i, {
96
+ error: _,
97
+ className: "px-3 pb-2"
98
+ }),
99
+ y > 0 && /* @__PURE__ */ l("p", {
100
+ className: "px-3 pb-2 text-xs text-muted-foreground",
101
+ role: "status",
102
+ children: g("editor.uploading")
103
+ })
104
+ ]
105
+ });
106
+ }
107
+ function P(e) {
108
+ return /* @__PURE__ */ l(N, { ...e });
109
+ }
110
+ function F({ editor: e, disabled: i, canUpload: a, uploading: E, onImages: D }) {
111
+ let { t: O } = t(), k = o(), j = s(null), [M, N] = c(null), P = A({
112
+ editor: e,
113
+ selector: ({ editor: e }) => ({
114
+ bold: e.isActive("bold"),
115
+ italic: e.isActive("italic"),
116
+ underline: e.isActive("underline"),
117
+ strike: e.isActive("strike"),
118
+ h2: e.isActive("heading", { level: 2 }),
119
+ h3: e.isActive("heading", { level: 3 }),
120
+ bullet: e.isActive("bulletList"),
121
+ ordered: e.isActive("orderedList"),
122
+ quote: e.isActive("blockquote"),
123
+ link: e.isActive("link"),
124
+ href: e.getAttributes("link").href ?? "",
125
+ undo: e.can().undo(),
126
+ redo: e.can().redo()
127
+ })
128
+ }), F = (e, t, r, a, o = !0) => /* @__PURE__ */ l(n, {
129
+ type: "button",
130
+ size: "icon-sm",
131
+ variant: "ghost",
132
+ className: d(r && "bg-accent text-accent-foreground"),
133
+ "aria-label": e,
134
+ title: e,
135
+ "aria-pressed": r === void 0 ? void 0 : r,
136
+ disabled: i || !o,
137
+ onMouseDown: (e) => e.preventDefault(),
138
+ onClick: a,
139
+ children: /* @__PURE__ */ l(T, { icon: t })
140
+ }, e), I = () => e.chain().focus(), L = () => {
141
+ let e = M?.trim();
142
+ e ? I().extendMarkRange("link").setLink({ href: e }).run() : I().extendMarkRange("link").unsetLink().run(), N(null);
143
+ };
144
+ return /* @__PURE__ */ u("div", {
145
+ className: "grid gap-1 border-b border-border bg-muted/40 p-1",
146
+ children: [/* @__PURE__ */ u("div", {
147
+ className: "flex flex-wrap items-center gap-0.5",
148
+ role: "toolbar",
149
+ "aria-label": O("editor.toolbar"),
150
+ children: [
151
+ F(O("editor.bold"), x, P.bold, () => I().toggleBold().run()),
152
+ F(O("editor.italic"), S, P.italic, () => I().toggleItalic().run()),
153
+ F(O("editor.underline"), w, P.underline, () => I().toggleUnderline().run()),
154
+ F(O("editor.strike"), C, P.strike, () => I().toggleStrike().run()),
155
+ /* @__PURE__ */ l("span", {
156
+ className: "mx-1 h-5 w-px bg-border",
157
+ "aria-hidden": !0
158
+ }),
159
+ F(O("editor.heading2"), m, P.h2, () => I().toggleHeading({ level: 2 }).run()),
160
+ F(O("editor.heading3"), h, P.h3, () => I().toggleHeading({ level: 3 }).run()),
161
+ F(O("editor.bulletList"), _, P.bullet, () => I().toggleBulletList().run()),
162
+ F(O("editor.orderedList"), v, P.ordered, () => I().toggleOrderedList().run()),
163
+ F(O("editor.quote"), b, P.quote, () => I().toggleBlockquote().run()),
164
+ /* @__PURE__ */ l("span", {
165
+ className: "mx-1 h-5 w-px bg-border",
166
+ "aria-hidden": !0
167
+ }),
168
+ F(O("editor.link"), y, P.link, () => N(P.href)),
169
+ a && F(O("editor.image"), g, void 0, () => j.current?.click(), !E),
170
+ /* @__PURE__ */ l("span", {
171
+ className: "mx-1 h-5 w-px bg-border",
172
+ "aria-hidden": !0
173
+ }),
174
+ F(O("editor.undo"), f, void 0, () => I().undo().run(), P.undo),
175
+ F(O("editor.redo"), p, void 0, () => I().redo().run(), P.redo),
176
+ /* @__PURE__ */ l("input", {
177
+ ref: j,
178
+ type: "file",
179
+ accept: "image/*",
180
+ multiple: !0,
181
+ hidden: !0,
182
+ "aria-label": O("editor.image"),
183
+ onChange: (e) => {
184
+ let t = [...e.target.files ?? []];
185
+ e.target.value = "", t.length && D(t);
186
+ }
187
+ })
188
+ ]
189
+ }), M !== null && /* @__PURE__ */ u("form", {
190
+ className: "flex flex-wrap items-center gap-2 px-1 pb-1",
191
+ onSubmit: (e) => {
192
+ e.preventDefault(), L();
193
+ },
194
+ children: [
195
+ /* @__PURE__ */ l("label", {
196
+ htmlFor: `${k}-href`,
197
+ className: "sr-only",
198
+ children: O("editor.linkUrl")
199
+ }),
200
+ /* @__PURE__ */ l(r, {
201
+ id: `${k}-href`,
202
+ className: "h-8 min-w-48 flex-1",
203
+ type: "url",
204
+ placeholder: "https://",
205
+ value: M,
206
+ autoFocus: !0,
207
+ onChange: (e) => N(e.target.value)
208
+ }),
209
+ /* @__PURE__ */ l(n, {
210
+ type: "submit",
211
+ size: "sm",
212
+ children: O(M.trim() ? "editor.applyLink" : "editor.removeLink")
213
+ }),
214
+ /* @__PURE__ */ l(n, {
215
+ type: "button",
216
+ size: "sm",
217
+ variant: "ghost",
218
+ onClick: () => N(null),
219
+ children: O("common.cancel")
220
+ })
221
+ ]
222
+ })]
223
+ });
224
+ }
225
+ //#endregion
226
+ export { N as RichTextEditor, P as richTextEditor };
227
+
228
+ //# sourceMappingURL=rich-text-editor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rich-text-editor.js","names":[],"sources":["../../src/editor/rich-text-editor.tsx"],"sourcesContent":["import {\n ArrowTurnBackwardIcon,\n ArrowTurnForwardIcon,\n Heading02Icon,\n Heading03Icon,\n Image01Icon,\n LeftToRightListBulletIcon,\n LeftToRightListNumberIcon,\n Link01Icon,\n QuoteDownIcon,\n TextBoldIcon,\n TextItalicIcon,\n TextStrikethroughIcon,\n TextUnderlineIcon,\n} from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon, type IconSvgElement } from \"@hugeicons/react\";\nimport Image from \"@tiptap/extension-image\";\nimport { Placeholder } from \"@tiptap/extensions\";\nimport { EditorContent, useEditor, useEditorState, type Editor } from \"@tiptap/react\";\nimport StarterKit from \"@tiptap/starter-kit\";\nimport { cn } from \"cn\";\nimport { useEffect, useId, useRef, useState, type ReactNode } from \"react\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { PostEditorProps } from \"../components/admin/post-manager.js\";\nimport { useMessages } from \"../i18n/context.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Input } from \"#ckui/ui/input\";\nimport { ErrorLine } from \"../components/social/parts.js\";\n\nexport type RichTextEditorProps = PostEditorProps & {\n className?: string;\n};\n\nconst content =\n \"min-h-48 px-3 py-2 outline-none break-words [&_p]:my-2 [&_h2]:mt-4 [&_h2]:mb-2 [&_h2]:text-lg [&_h2]:font-semibold [&_h3]:mt-3 [&_h3]:mb-1.5 [&_h3]:font-semibold \" +\n \"[&_ul]:my-2 [&_ul]:list-disc [&_ul]:pl-6 [&_ol]:my-2 [&_ol]:list-decimal [&_ol]:pl-6 [&_blockquote]:my-2 [&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-3 [&_blockquote]:text-muted-foreground \" +\n \"[&_a]:text-primary [&_a]:underline [&_img]:my-2 [&_img]:max-h-96 [&_img]:max-w-full [&_img]:rounded-md [&_img.ProseMirror-selectednode]:ring-2 [&_img.ProseMirror-selectednode]:ring-ring \" +\n \"[&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_pre]:my-2 [&_pre]:rounded-md [&_pre]:bg-muted [&_pre]:p-3 \" +\n \"[&_p.is-editor-empty:first-child]:before:pointer-events-none [&_p.is-editor-empty:first-child]:before:float-left [&_p.is-editor-empty:first-child]:before:h-0 [&_p.is-editor-empty:first-child]:before:text-muted-foreground [&_p.is-editor-empty:first-child]:before:content-[attr(data-placeholder)]\";\n\n/**\n * A rich-text field over Tiptap: headings, emphasis, lists, quotes, links\n * and images (picked, pasted or dropped, through uploadImage). Its value is\n * HTML.\n */\nexport function RichTextEditor({ value, onChange, uploadImage, disabled, placeholder, className, ...aria }: RichTextEditorProps) {\n const { t } = useMessages();\n const [error, setError] = useState<ContentKitError | null>(null);\n const [uploading, setUploading] = useState(0);\n const upload = useRef(uploadImage);\n const change = useRef(onChange);\n useEffect(() => {\n upload.current = uploadImage;\n change.current = onChange;\n });\n const insertImages = async (ed: Editor, files: File[], pos?: number) => {\n if (!upload.current) return;\n setError(null);\n for (const file of files) {\n setUploading((n) => n + 1);\n try {\n const src = await upload.current(file);\n const chain = ed.chain().focus();\n (pos === undefined ? chain : chain.setTextSelection(pos)).setImage({ src, alt: \"\" }).run();\n } catch (e) {\n setError(toContentKitError(e));\n } finally {\n setUploading((n) => n - 1);\n }\n }\n };\n const images = (list: FileList | null | undefined) => [...(list ?? [])].filter((f) => f.type.startsWith(\"image/\"));\n const editor = useEditor({\n extensions: [\n StarterKit.configure({ heading: { levels: [2, 3] }, link: { openOnClick: false, autolink: true, defaultProtocol: \"https\" } }),\n Image.configure({ inline: false }),\n Placeholder.configure({ placeholder: placeholder ?? t(\"editor.placeholder\") }),\n ],\n content: value,\n editable: !disabled,\n immediatelyRender: false,\n editorProps: {\n attributes: { class: content, role: \"textbox\", \"aria-multiline\": \"true\", ...(aria[\"aria-label\"] ? { \"aria-label\": aria[\"aria-label\"] } : {}) },\n handlePaste: (view, event) => {\n const files = images(event.clipboardData?.files);\n if (!files.length || !upload.current || !editorRef.current) return false;\n void insertImages(editorRef.current, files);\n return true;\n },\n handleDrop: (view, event) => {\n const files = images((event as DragEvent).dataTransfer?.files);\n if (!files.length || !upload.current || !editorRef.current) return false;\n const at = view.posAtCoords({ left: event.clientX, top: event.clientY });\n void insertImages(editorRef.current, files, at?.pos);\n return true;\n },\n },\n onUpdate: ({ editor }) => change.current(editor.isEmpty ? \"\" : editor.getHTML()),\n });\n const editorRef = useRef<Editor | null>(null);\n editorRef.current = editor;\n // A value from outside (a load, a reset) replaces the document.\n useEffect(() => {\n if (!editor) return;\n const html = editor.isEmpty ? \"\" : editor.getHTML();\n if (value !== html) editor.commands.setContent(value, { emitUpdate: false });\n }, [editor, value]);\n useEffect(() => {\n editor?.setEditable(!disabled);\n }, [editor, disabled]);\n return (\n <div className={cn(\"grid overflow-hidden rounded-lg border border-input bg-background focus-within:ring-2 focus-within:ring-ring/50\", className)} data-ckui=\"rich-text-editor\">\n {editor && <Toolbar editor={editor} disabled={disabled} canUpload={!!uploadImage} uploading={uploading > 0} onImages={(files) => void insertImages(editor, files)} />}\n <EditorContent editor={editor} />\n {error && <ErrorLine error={error} className=\"px-3 pb-2\" />}\n {uploading > 0 && (\n <p className=\"px-3 pb-2 text-xs text-muted-foreground\" role=\"status\">\n {t(\"editor.uploading\")}\n </p>\n )}\n </div>\n );\n}\n\n/** A render prop for PostManager: `renderEditor={richTextEditor}`. */\nexport function richTextEditor(props: PostEditorProps): ReactNode {\n return <RichTextEditor {...props} />;\n}\n\nfunction Toolbar({ editor, disabled, canUpload, uploading, onImages }: { editor: Editor; disabled?: boolean; canUpload: boolean; uploading: boolean; onImages: (files: File[]) => void }) {\n const { t } = useMessages();\n const id = useId();\n const file = useRef<HTMLInputElement>(null);\n const [linking, setLinking] = useState<string | null>(null);\n const state = useEditorState({\n editor,\n selector: ({ editor: e }) => ({\n bold: e.isActive(\"bold\"),\n italic: e.isActive(\"italic\"),\n underline: e.isActive(\"underline\"),\n strike: e.isActive(\"strike\"),\n h2: e.isActive(\"heading\", { level: 2 }),\n h3: e.isActive(\"heading\", { level: 3 }),\n bullet: e.isActive(\"bulletList\"),\n ordered: e.isActive(\"orderedList\"),\n quote: e.isActive(\"blockquote\"),\n link: e.isActive(\"link\"),\n href: (e.getAttributes(\"link\").href as string | undefined) ?? \"\",\n undo: e.can().undo(),\n redo: e.can().redo(),\n }),\n });\n const tool = (label: string, icon: IconSvgElement, active: boolean | undefined, run: () => void, enabled = true) => (\n <Button\n key={label}\n type=\"button\"\n size=\"icon-sm\"\n variant=\"ghost\"\n className={cn(active && \"bg-accent text-accent-foreground\")}\n aria-label={label}\n title={label}\n aria-pressed={active === undefined ? undefined : active}\n disabled={disabled || !enabled}\n onMouseDown={(e) => e.preventDefault()}\n onClick={run}\n >\n <HugeiconsIcon icon={icon} />\n </Button>\n );\n const c = () => editor.chain().focus();\n const saveLink = () => {\n const href = linking?.trim();\n if (href) c().extendMarkRange(\"link\").setLink({ href }).run();\n else c().extendMarkRange(\"link\").unsetLink().run();\n setLinking(null);\n };\n return (\n <div className=\"grid gap-1 border-b border-border bg-muted/40 p-1\">\n <div className=\"flex flex-wrap items-center gap-0.5\" role=\"toolbar\" aria-label={t(\"editor.toolbar\")}>\n {tool(t(\"editor.bold\"), TextBoldIcon, state.bold, () => c().toggleBold().run())}\n {tool(t(\"editor.italic\"), TextItalicIcon, state.italic, () => c().toggleItalic().run())}\n {tool(t(\"editor.underline\"), TextUnderlineIcon, state.underline, () => c().toggleUnderline().run())}\n {tool(t(\"editor.strike\"), TextStrikethroughIcon, state.strike, () => c().toggleStrike().run())}\n <span className=\"mx-1 h-5 w-px bg-border\" aria-hidden />\n {tool(t(\"editor.heading2\"), Heading02Icon, state.h2, () => c().toggleHeading({ level: 2 }).run())}\n {tool(t(\"editor.heading3\"), Heading03Icon, state.h3, () => c().toggleHeading({ level: 3 }).run())}\n {tool(t(\"editor.bulletList\"), LeftToRightListBulletIcon, state.bullet, () => c().toggleBulletList().run())}\n {tool(t(\"editor.orderedList\"), LeftToRightListNumberIcon, state.ordered, () => c().toggleOrderedList().run())}\n {tool(t(\"editor.quote\"), QuoteDownIcon, state.quote, () => c().toggleBlockquote().run())}\n <span className=\"mx-1 h-5 w-px bg-border\" aria-hidden />\n {tool(t(\"editor.link\"), Link01Icon, state.link, () => setLinking(state.href))}\n {canUpload && tool(t(\"editor.image\"), Image01Icon, undefined, () => file.current?.click(), !uploading)}\n <span className=\"mx-1 h-5 w-px bg-border\" aria-hidden />\n {tool(t(\"editor.undo\"), ArrowTurnBackwardIcon, undefined, () => c().undo().run(), state.undo)}\n {tool(t(\"editor.redo\"), ArrowTurnForwardIcon, undefined, () => c().redo().run(), state.redo)}\n <input\n ref={file}\n type=\"file\"\n accept=\"image/*\"\n multiple\n hidden\n aria-label={t(\"editor.image\")}\n onChange={(e) => {\n const files = [...(e.target.files ?? [])];\n e.target.value = \"\";\n if (files.length) onImages(files);\n }}\n />\n </div>\n {linking !== null && (\n <form\n className=\"flex flex-wrap items-center gap-2 px-1 pb-1\"\n onSubmit={(e) => {\n e.preventDefault();\n saveLink();\n }}\n >\n <label htmlFor={`${id}-href`} className=\"sr-only\">\n {t(\"editor.linkUrl\")}\n </label>\n <Input id={`${id}-href`} className=\"h-8 min-w-48 flex-1\" type=\"url\" placeholder=\"https://\" value={linking} autoFocus onChange={(e) => setLinking(e.target.value)} />\n <Button type=\"submit\" size=\"sm\">\n {t(linking.trim() ? \"editor.applyLink\" : \"editor.removeLink\")}\n </Button>\n <Button type=\"button\" size=\"sm\" variant=\"ghost\" onClick={() => setLinking(null)}>\n {t(\"common.cancel\")}\n </Button>\n </form>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
package/dist/editor.js ADDED
@@ -0,0 +1,2 @@
1
+ import { RichTextEditor as e, richTextEditor as t } from "./editor/rich-text-editor.js";
2
+ export { e as RichTextEditor, t as richTextEditor };
package/dist/index.d.ts CHANGED
@@ -19,5 +19,11 @@ export { HoverPreview, type HoverPreviewProps } from './components/hover-preview
19
19
  export { LazyMount, type LazyMountProps } from './components/lazy-mount.js';
20
20
  export { MediaReadinessNotice, type MediaReadinessNoticeProps } from './components/media-readiness-notice.js';
21
21
  export * from './components/social/index.js';
22
+ export { UserCommentStanding, useUserCommentStandingAccess, type UserCommentStandingProps } from './components/admin/user-comment-standing.js';
23
+ export { PollManager, pollState, usePollManagerAccess, type PollManagerProps } from './components/admin/poll-manager.js';
24
+ export { PostManager, postStatus, usePostManagerAccess, type PostEditorProps, type PostManagerProps, type PostStatus } from './components/admin/post-manager.js';
25
+ export { TaxonomyManager, slugify, useTaxonomyManagerAccess, type TaxonomyKind, type TaxonomyManagerProps, type TaxonomyRelation, } from './components/admin/taxonomy-manager.js';
26
+ export { NoAccess, Pager, useConfirm, type AdminFlowProps, type AdminRouteProps, type ConfirmOptions, type ContentKitGuard } from './components/admin/shell.js';
27
+ export type { AdminNavigation, ContentKitAuth, StaffAccess, StaffArea } from './react/admin.js';
22
28
  export { createTranslator, defaultMessages, defineMessages, resolveMessages, type MessageKey, type MessageVars, type Translator, type ContentKitUiMessageBundle, type ContentKitUiMessages, type ContentKitUiTranslate, } from './i18n/messages.js';
23
29
  export { useMessages } from './i18n/context.js';