@openrails/contentkit-ui 0.71.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.
- package/README.md +98 -10
- package/dist/client/content/bans.d.ts +2 -0
- package/dist/client/content/bans.js +24 -12
- package/dist/client/content/bans.js.map +1 -1
- package/dist/client/content/comments.d.ts +2 -0
- package/dist/client/content/comments.js.map +1 -1
- package/dist/client/content/polls.d.ts +9 -2
- package/dist/client/content/polls.js.map +1 -1
- package/dist/client/generated/routes.d.ts +28 -2
- package/dist/client/generated/routes.js +40 -0
- package/dist/client/generated/routes.js.map +1 -1
- package/dist/client/generated/wire.d.ts +4 -0
- package/dist/client/generated/wire.js.map +1 -1
- package/dist/client/index.d.ts +2 -2
- package/dist/client/taxonomy.d.ts +5 -1
- package/dist/client/taxonomy.js +7 -0
- package/dist/client/taxonomy.js.map +1 -1
- package/dist/components/admin/poll-manager.d.ts +28 -0
- package/dist/components/admin/poll-manager.js +297 -0
- package/dist/components/admin/poll-manager.js.map +1 -0
- package/dist/components/admin/post-manager.d.ts +45 -0
- package/dist/components/admin/post-manager.js +602 -0
- package/dist/components/admin/post-manager.js.map +1 -0
- package/dist/components/admin/shell.d.ts +73 -0
- package/dist/components/admin/shell.js +148 -0
- package/dist/components/admin/shell.js.map +1 -0
- package/dist/components/admin/taxonomy-manager.d.ts +54 -0
- package/dist/components/admin/taxonomy-manager.js +976 -0
- package/dist/components/admin/taxonomy-manager.js.map +1 -0
- package/dist/components/admin/user-comment-standing.d.ts +25 -0
- package/dist/components/admin/user-comment-standing.js +229 -0
- package/dist/components/admin/user-comment-standing.js.map +1 -0
- package/dist/components/social/bans.d.ts +5 -2
- package/dist/components/social/bans.js +132 -134
- package/dist/components/social/bans.js.map +1 -1
- package/dist/components/social/index.d.ts +1 -1
- package/dist/components/social/moderation.d.ts +11 -13
- package/dist/components/social/moderation.js +299 -218
- package/dist/components/social/moderation.js.map +1 -1
- package/dist/components/social/parts.d.ts +3 -2
- package/dist/components/social/parts.js +74 -48
- package/dist/components/social/parts.js.map +1 -1
- package/dist/components/social/poll-editor.js +1 -0
- package/dist/components/social/poll-editor.js.map +1 -1
- package/dist/editor/index.d.ts +1 -0
- package/dist/editor/rich-text-editor.d.ts +13 -0
- package/dist/editor/rich-text-editor.js +228 -0
- package/dist/editor/rich-text-editor.js.map +1 -0
- package/dist/editor.js +2 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +13 -8
- package/dist/locales/admin/de.d.ts +2 -0
- package/dist/locales/admin/de.js +176 -0
- package/dist/locales/admin/de.js.map +1 -0
- package/dist/locales/admin/en.d.ts +171 -0
- package/dist/locales/admin/en.js +176 -0
- package/dist/locales/admin/en.js.map +1 -0
- package/dist/locales/admin/es.d.ts +2 -0
- package/dist/locales/admin/es.js +176 -0
- package/dist/locales/admin/es.js.map +1 -0
- package/dist/locales/admin/ja.d.ts +2 -0
- package/dist/locales/admin/ja.js +176 -0
- package/dist/locales/admin/ja.js.map +1 -0
- package/dist/locales/admin/ko.d.ts +2 -0
- package/dist/locales/admin/ko.js +176 -0
- package/dist/locales/admin/ko.js.map +1 -0
- package/dist/locales/admin/zh.d.ts +2 -0
- package/dist/locales/admin/zh.js +176 -0
- package/dist/locales/admin/zh.js.map +1 -0
- package/dist/locales/de.js +5 -3
- package/dist/locales/de.js.map +1 -1
- package/dist/locales/en.d.ts +176 -0
- package/dist/locales/en.js +5 -3
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +5 -3
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/ja.js +5 -3
- package/dist/locales/ja.js.map +1 -1
- package/dist/locales/ko.js +5 -3
- package/dist/locales/ko.js.map +1 -1
- package/dist/locales/social/de.js +8 -1
- package/dist/locales/social/de.js.map +1 -1
- package/dist/locales/social/en.d.ts +7 -0
- package/dist/locales/social/en.js +8 -1
- package/dist/locales/social/en.js.map +1 -1
- package/dist/locales/social/es.js +8 -1
- package/dist/locales/social/es.js.map +1 -1
- package/dist/locales/social/ja.js +8 -1
- package/dist/locales/social/ja.js.map +1 -1
- package/dist/locales/social/ko.js +8 -1
- package/dist/locales/social/ko.js.map +1 -1
- package/dist/locales/social/zh.js +8 -1
- package/dist/locales/social/zh.js.map +1 -1
- package/dist/locales/zh.js +5 -3
- package/dist/locales/zh.js.map +1 -1
- package/dist/provider.d.ts +2 -0
- package/dist/provider.js +23 -19
- package/dist/provider.js.map +1 -1
- package/dist/react/admin.d.ts +55 -0
- package/dist/react/admin.js +97 -0
- package/dist/react/admin.js.map +1 -0
- package/dist/react/comments.d.ts +18 -2
- package/dist/react/comments.js +24 -8
- package/dist/react/comments.js.map +1 -1
- package/dist/react/context.d.ts +7 -1
- package/dist/react/context.js +6 -1
- package/dist/react/context.js.map +1 -1
- package/dist/react/index.d.ts +4 -3
- package/dist/react/polls.js +88 -87
- package/dist/react/polls.js.map +1 -1
- package/dist/react/posts.js +123 -122
- package/dist/react/posts.js.map +1 -1
- package/dist/react/resources.js +3 -0
- package/dist/react/resources.js.map +1 -1
- package/dist/react/use-resource.d.ts +16 -0
- package/dist/react/use-resource.js +20 -1
- package/dist/react/use-resource.js.map +1 -1
- package/dist/react.js +21 -20
- package/dist/styles.css +1 -1
- package/package.json +37 -2
|
@@ -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
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';
|