@frontlit/text-editor 0.1.0-alpha.1 → 0.1.0-alpha.3
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 +45 -0
- package/dist/docs.d.ts +90 -0
- package/dist/docs.js +905 -0
- package/dist/docs.js.map +1 -0
- package/dist/headings.d.ts +17 -0
- package/dist/headings.js +35 -0
- package/dist/headings.js.map +1 -0
- package/dist/index.d.ts +13 -7
- package/dist/index.js +264 -128
- package/dist/index.js.map +1 -1
- package/dist/prose.css +191 -193
- package/dist/renderer/index.d.ts +70 -0
- package/dist/renderer/index.js +356 -0
- package/dist/renderer/index.js.map +1 -0
- package/dist/renderer/islands/copy-code-button.js +56 -0
- package/dist/renderer/islands/copy-code-button.js.map +1 -0
- package/dist/styles.css +114 -63
- package/dist/tokens.css +43 -43
- package/package.json +36 -11
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
// src/text-renderer.tsx
|
|
2
|
+
import { renderToReactElement } from "@tiptap/static-renderer";
|
|
3
|
+
import { getSchema } from "@tiptap/core";
|
|
4
|
+
|
|
5
|
+
// src/extract-text-from-node.ts
|
|
6
|
+
function extractTextFromNode(node) {
|
|
7
|
+
if (!node) {
|
|
8
|
+
return "";
|
|
9
|
+
}
|
|
10
|
+
if (typeof node.textContent === "string") {
|
|
11
|
+
return node.textContent;
|
|
12
|
+
}
|
|
13
|
+
if (node.content && node.content.content && Array.isArray(node.content.content)) {
|
|
14
|
+
return node.content.content.map((child) => {
|
|
15
|
+
if (child.text) {
|
|
16
|
+
return child.text;
|
|
17
|
+
}
|
|
18
|
+
return extractTextFromNode(child);
|
|
19
|
+
}).join("");
|
|
20
|
+
}
|
|
21
|
+
if (Array.isArray(node.content)) {
|
|
22
|
+
return node.content.map((child) => {
|
|
23
|
+
if (child.text) {
|
|
24
|
+
return child.text;
|
|
25
|
+
}
|
|
26
|
+
return extractTextFromNode(child);
|
|
27
|
+
}).join("");
|
|
28
|
+
}
|
|
29
|
+
return "";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// src/empty-doc.ts
|
|
33
|
+
var emptyDoc = {
|
|
34
|
+
type: "doc",
|
|
35
|
+
content: []
|
|
36
|
+
};
|
|
37
|
+
var empty_doc_default = emptyDoc;
|
|
38
|
+
|
|
39
|
+
// src/extensions.ts
|
|
40
|
+
import StarterKit from "@tiptap/starter-kit";
|
|
41
|
+
import Placeholder from "@tiptap/extension-placeholder";
|
|
42
|
+
import Link from "@tiptap/extension-link";
|
|
43
|
+
import { Table, TableCell, TableHeader, TableRow } from "@tiptap/extension-table";
|
|
44
|
+
import Image from "@tiptap/extension-image";
|
|
45
|
+
import Dropcursor from "@tiptap/extension-dropcursor";
|
|
46
|
+
import Gapcursor from "@tiptap/extension-gapcursor";
|
|
47
|
+
import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
|
|
48
|
+
import Heading from "@tiptap/extension-heading";
|
|
49
|
+
import Highlight from "@tiptap/extension-highlight";
|
|
50
|
+
import { lowlight } from "lowlight";
|
|
51
|
+
import javascript from "highlight.js/lib/languages/javascript";
|
|
52
|
+
import typescript from "highlight.js/lib/languages/typescript";
|
|
53
|
+
import json from "highlight.js/lib/languages/json";
|
|
54
|
+
import css from "highlight.js/lib/languages/css";
|
|
55
|
+
import xml from "highlight.js/lib/languages/xml";
|
|
56
|
+
|
|
57
|
+
// src/create-id.ts
|
|
58
|
+
function createId(textContent) {
|
|
59
|
+
return textContent.replace(/\s/g, "-").toLowerCase();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/components/custom-code-mirror.tsx
|
|
63
|
+
import { Node } from "@tiptap/core";
|
|
64
|
+
var CodeMirrorNode = Node.create({
|
|
65
|
+
name: "codeMirror",
|
|
66
|
+
content: "block+",
|
|
67
|
+
code: true,
|
|
68
|
+
addOptions() {
|
|
69
|
+
return {
|
|
70
|
+
HTMLAttributes: {}
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
parseHTML() {
|
|
74
|
+
return [
|
|
75
|
+
{
|
|
76
|
+
tag: "pre"
|
|
77
|
+
}
|
|
78
|
+
];
|
|
79
|
+
},
|
|
80
|
+
renderHTML({ HTMLAttributes }) {
|
|
81
|
+
return ["pre", HTMLAttributes, 0];
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// src/extensions.ts
|
|
86
|
+
lowlight.registerLanguage("javascript", javascript);
|
|
87
|
+
lowlight.registerLanguage("typescript", typescript);
|
|
88
|
+
lowlight.registerLanguage("json", json);
|
|
89
|
+
lowlight.registerLanguage("css", css);
|
|
90
|
+
lowlight.registerLanguage("html", xml);
|
|
91
|
+
var createExtensions = ({
|
|
92
|
+
placeholder
|
|
93
|
+
} = {}) => [
|
|
94
|
+
StarterKit.configure({
|
|
95
|
+
codeBlock: false,
|
|
96
|
+
// Configured/replaced explicitly below (custom options or a
|
|
97
|
+
// `.extend()`) — disabled here to avoid tiptap's duplicate-extension
|
|
98
|
+
// warning and constructing throwaway instances of each.
|
|
99
|
+
link: false,
|
|
100
|
+
dropcursor: false,
|
|
101
|
+
gapcursor: false,
|
|
102
|
+
heading: false
|
|
103
|
+
}),
|
|
104
|
+
Placeholder.configure({
|
|
105
|
+
placeholder: placeholder || "Write something\u2026"
|
|
106
|
+
}),
|
|
107
|
+
Link.configure({
|
|
108
|
+
openOnClick: false,
|
|
109
|
+
autolink: true,
|
|
110
|
+
linkOnPaste: true
|
|
111
|
+
}),
|
|
112
|
+
Table.configure({
|
|
113
|
+
resizable: true
|
|
114
|
+
}),
|
|
115
|
+
TableRow,
|
|
116
|
+
TableHeader,
|
|
117
|
+
TableCell,
|
|
118
|
+
Image.configure({
|
|
119
|
+
// We embed uploaded images as data URLs (no backend/media library in
|
|
120
|
+
// this package), so base64 sources must be allowed.
|
|
121
|
+
allowBase64: true,
|
|
122
|
+
HTMLAttributes: {
|
|
123
|
+
class: "max-w-full h-auto rounded-md"
|
|
124
|
+
},
|
|
125
|
+
resize: {
|
|
126
|
+
enabled: true,
|
|
127
|
+
directions: ["top", "bottom", "left", "right"],
|
|
128
|
+
alwaysPreserveAspectRatio: true
|
|
129
|
+
}
|
|
130
|
+
}),
|
|
131
|
+
Dropcursor.configure({
|
|
132
|
+
color: "hsl(var(--foreground))",
|
|
133
|
+
width: 2
|
|
134
|
+
}),
|
|
135
|
+
Gapcursor,
|
|
136
|
+
CodeBlockLowlight.configure({
|
|
137
|
+
lowlight
|
|
138
|
+
}),
|
|
139
|
+
Heading.extend({
|
|
140
|
+
renderHTML({ node, HTMLAttributes }) {
|
|
141
|
+
const level = this.options.levels.includes(node.attrs.level) ? node.attrs.level : this.options.levels[0];
|
|
142
|
+
const id = typeof node.textContent === "string" ? createId(node.textContent) : void 0;
|
|
143
|
+
return [
|
|
144
|
+
`h${level}`,
|
|
145
|
+
{
|
|
146
|
+
...HTMLAttributes,
|
|
147
|
+
id
|
|
148
|
+
},
|
|
149
|
+
0
|
|
150
|
+
];
|
|
151
|
+
}
|
|
152
|
+
}),
|
|
153
|
+
Highlight,
|
|
154
|
+
CodeMirrorNode
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
// src/lib/cn.ts
|
|
158
|
+
import { clsx } from "clsx";
|
|
159
|
+
import { twMerge } from "tailwind-merge";
|
|
160
|
+
function cn(...inputs) {
|
|
161
|
+
return twMerge(clsx(inputs));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/components/rendered-code-block.tsx
|
|
165
|
+
import React from "react";
|
|
166
|
+
import { CopyCodeButton } from "./islands/copy-code-button.js";
|
|
167
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
168
|
+
function textFromChildren(node) {
|
|
169
|
+
if (node == null || typeof node === "boolean") return "";
|
|
170
|
+
if (typeof node === "string" || typeof node === "number") {
|
|
171
|
+
return String(node);
|
|
172
|
+
}
|
|
173
|
+
if (Array.isArray(node)) return node.map(textFromChildren).join("");
|
|
174
|
+
if (React.isValidElement(node)) {
|
|
175
|
+
return textFromChildren(node.props.children);
|
|
176
|
+
}
|
|
177
|
+
return "";
|
|
178
|
+
}
|
|
179
|
+
function RenderedCodeBlock({
|
|
180
|
+
node,
|
|
181
|
+
children,
|
|
182
|
+
language
|
|
183
|
+
}) {
|
|
184
|
+
const source = extractTextFromNode(node) || textFromChildren(children);
|
|
185
|
+
return /* @__PURE__ */ jsxs("div", { className: "te-code-block", children: [
|
|
186
|
+
/* @__PURE__ */ jsx("pre", { children: /* @__PURE__ */ jsx("code", { className: language ? `language-${language}` : void 0, children }) }),
|
|
187
|
+
/* @__PURE__ */ jsx(CopyCodeButton, { source })
|
|
188
|
+
] });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// src/docs/headings.ts
|
|
192
|
+
function docsNodeText(node) {
|
|
193
|
+
if (typeof node.text === "string") return node.text;
|
|
194
|
+
if (node.type === "hardBreak") return "\n";
|
|
195
|
+
return (node.content ?? []).map(docsNodeText).join("");
|
|
196
|
+
}
|
|
197
|
+
function createHeadingIdGenerator() {
|
|
198
|
+
const used = /* @__PURE__ */ new Set();
|
|
199
|
+
return (text) => {
|
|
200
|
+
const base = text.normalize("NFKC").toLowerCase().trim().replace(/[^\p{L}\p{N}\s_-]/gu, "").replace(/\s+/g, "-") || "section";
|
|
201
|
+
let id = base;
|
|
202
|
+
let suffix = 2;
|
|
203
|
+
while (used.has(id)) id = `${base}-${suffix++}`;
|
|
204
|
+
used.add(id);
|
|
205
|
+
return id;
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function extractDocsHeadings(json2) {
|
|
209
|
+
const nextId = createHeadingIdGenerator();
|
|
210
|
+
const headings = [];
|
|
211
|
+
function walk(node) {
|
|
212
|
+
if (node.type === "heading") {
|
|
213
|
+
const text = docsNodeText(node);
|
|
214
|
+
headings.push({ level: Number(node.attrs?.level ?? 1), text, id: nextId(text) });
|
|
215
|
+
}
|
|
216
|
+
for (const child of node.content ?? []) walk(child);
|
|
217
|
+
}
|
|
218
|
+
if (json2) walk(json2);
|
|
219
|
+
return headings;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// src/text-renderer.tsx
|
|
223
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
224
|
+
var typographyClasses = (typography) => [
|
|
225
|
+
typography?.fontFamily,
|
|
226
|
+
typography?.fontSize,
|
|
227
|
+
typography?.fontWeight,
|
|
228
|
+
typography?.lineHeight,
|
|
229
|
+
typography?.letterSpacing,
|
|
230
|
+
typography?.textTransform,
|
|
231
|
+
typography?.textDecoration,
|
|
232
|
+
typography?.textOverflow
|
|
233
|
+
];
|
|
234
|
+
function removeEmptyTextNodes(node) {
|
|
235
|
+
if (!node || typeof node !== "object") {
|
|
236
|
+
return node;
|
|
237
|
+
}
|
|
238
|
+
if (node.type === "text" && node.text === "") {
|
|
239
|
+
return void 0;
|
|
240
|
+
}
|
|
241
|
+
if (!Array.isArray(node.content)) {
|
|
242
|
+
return node;
|
|
243
|
+
}
|
|
244
|
+
const content = node.content.map(removeEmptyTextNodes).filter((child) => child !== void 0);
|
|
245
|
+
return {
|
|
246
|
+
...node,
|
|
247
|
+
content
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
function TextRenderer({
|
|
251
|
+
json: json2,
|
|
252
|
+
className,
|
|
253
|
+
theme,
|
|
254
|
+
profile = "default"
|
|
255
|
+
}) {
|
|
256
|
+
const extensions = createExtensions();
|
|
257
|
+
const content = removeEmptyTextNodes(
|
|
258
|
+
json2 ?? empty_doc_default
|
|
259
|
+
);
|
|
260
|
+
const docsHeadings = profile === "documentation" ? extractDocsHeadings(content) : [];
|
|
261
|
+
const renderContent = profile === "documentation" ? getSchema(extensions).nodeFromJSON(content) : content;
|
|
262
|
+
const headingIds = /* @__PURE__ */ new WeakMap();
|
|
263
|
+
if (profile === "documentation") {
|
|
264
|
+
let headingIndex = 0;
|
|
265
|
+
renderContent.descendants((node) => {
|
|
266
|
+
if (node.type.name === "heading") {
|
|
267
|
+
headingIds.set(node, docsHeadings[headingIndex++].id);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
const rendered = renderToReactElement({
|
|
272
|
+
extensions,
|
|
273
|
+
content: renderContent,
|
|
274
|
+
options: {
|
|
275
|
+
nodeMapping: {
|
|
276
|
+
paragraph: ({ children }) => {
|
|
277
|
+
const classes = cn(
|
|
278
|
+
"text-base",
|
|
279
|
+
...typographyClasses(theme?.typography?.text1)
|
|
280
|
+
);
|
|
281
|
+
return /* @__PURE__ */ jsx2("p", { className: classes, children });
|
|
282
|
+
},
|
|
283
|
+
hardBreak: () => /* @__PURE__ */ jsx2("br", {}),
|
|
284
|
+
heading: ({ node, children }) => {
|
|
285
|
+
const level = node?.attrs?.level ?? 1;
|
|
286
|
+
const textContent = extractTextFromNode(node);
|
|
287
|
+
const id = profile === "documentation" ? headingIds.get(node) : createId(textContent);
|
|
288
|
+
if (!theme) {
|
|
289
|
+
const Tag2 = `h${level}`;
|
|
290
|
+
return /* @__PURE__ */ jsx2(Tag2, { id, children });
|
|
291
|
+
}
|
|
292
|
+
if (level === 1) {
|
|
293
|
+
const classes2 = cn(
|
|
294
|
+
"scroll-m-20 text-4xl font-bold tracking-tight",
|
|
295
|
+
...typographyClasses(theme?.typography?.header1)
|
|
296
|
+
);
|
|
297
|
+
return /* @__PURE__ */ jsx2("h1", { id, className: classes2, children });
|
|
298
|
+
}
|
|
299
|
+
if (level === 2) {
|
|
300
|
+
const classes2 = cn(
|
|
301
|
+
"scroll-m-20 text-3xl font-bold tracking-tight",
|
|
302
|
+
...typographyClasses(theme?.typography?.header2)
|
|
303
|
+
);
|
|
304
|
+
return /* @__PURE__ */ jsx2("h2", { id, className: classes2, children });
|
|
305
|
+
}
|
|
306
|
+
const classes = cn(
|
|
307
|
+
"scroll-m-20 text-2xl font-semibold tracking-normal",
|
|
308
|
+
...typographyClasses(
|
|
309
|
+
profile === "documentation" && level >= 4 ? theme?.typography?.header4 ?? theme?.typography?.header3 : theme?.typography?.header3
|
|
310
|
+
)
|
|
311
|
+
);
|
|
312
|
+
const Tag = profile === "documentation" ? `h${level}` : "h3";
|
|
313
|
+
return /* @__PURE__ */ jsx2(Tag, { id, className: classes, children });
|
|
314
|
+
},
|
|
315
|
+
codeBlock: ({ node, children }) => /* @__PURE__ */ jsx2(RenderedCodeBlock, { node, language: node?.attrs?.language, children }),
|
|
316
|
+
codeMirror: ({ node, children }) => /* @__PURE__ */ jsx2(RenderedCodeBlock, { node, children })
|
|
317
|
+
},
|
|
318
|
+
markMapping: {
|
|
319
|
+
link: ({ mark, children }) => {
|
|
320
|
+
const href = mark?.attrs?.href;
|
|
321
|
+
const linkStyle = theme?.interactives?.link;
|
|
322
|
+
const classes = cn(
|
|
323
|
+
"font-medium cursor-pointer",
|
|
324
|
+
...typographyClasses(theme?.typography?.link),
|
|
325
|
+
linkStyle?.padding?.x,
|
|
326
|
+
linkStyle?.padding?.y,
|
|
327
|
+
linkStyle?.border?.width,
|
|
328
|
+
linkStyle?.border?.radius,
|
|
329
|
+
linkStyle?.border?.style,
|
|
330
|
+
linkStyle?.custom
|
|
331
|
+
);
|
|
332
|
+
return /* @__PURE__ */ jsx2(
|
|
333
|
+
"a",
|
|
334
|
+
{
|
|
335
|
+
href,
|
|
336
|
+
target: "_blank",
|
|
337
|
+
rel: "noopener noreferrer",
|
|
338
|
+
className: classes,
|
|
339
|
+
style: {
|
|
340
|
+
textShadow: linkStyle?.textShadow || void 0
|
|
341
|
+
},
|
|
342
|
+
children
|
|
343
|
+
}
|
|
344
|
+
);
|
|
345
|
+
},
|
|
346
|
+
highlight: ({ children }) => /* @__PURE__ */ jsx2("mark", { className: "bg-accent text-accent-foreground", children })
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
const combinedClassName = ["tiptap-renderer", className].filter(Boolean).join(" ");
|
|
351
|
+
return /* @__PURE__ */ jsx2("div", { className: "text-editor flex flex-col gap-4", children: /* @__PURE__ */ jsx2("div", { className: combinedClassName, children: rendered }) });
|
|
352
|
+
}
|
|
353
|
+
export {
|
|
354
|
+
TextRenderer
|
|
355
|
+
};
|
|
356
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/text-renderer.tsx","../../src/extract-text-from-node.ts","../../src/empty-doc.ts","../../src/extensions.ts","../../src/create-id.ts","../../src/components/custom-code-mirror.tsx","../../src/lib/cn.ts","../../src/components/rendered-code-block.tsx","../../src/docs/headings.ts"],"sourcesContent":["import React from \"react\";\nimport { renderToReactElement } from \"@tiptap/static-renderer\";\nimport { getSchema } from \"@tiptap/core\";\nimport type { Node as ProseMirrorNode } from \"@tiptap/pm/model\";\nimport { extractTextFromNode } from \"./extract-text-from-node\";\nimport emptyDoc from \"./empty-doc\";\nimport { createExtensions } from \"./extensions\";\nimport { createId } from \"./create-id\";\nimport type { TextEditorContent } from \"./text-editor-content\";\nimport type { TextRendererTheme } from \"./renderer-theme\";\nimport { cn } from \"./lib/cn\";\nimport { RenderedCodeBlock } from \"./components/rendered-code-block\";\nimport { extractDocsHeadings } from \"./docs/headings\";\n\ninterface TextRendererProps {\n profile?: \"default\" | \"documentation\";\n json: TextEditorContent;\n className?: string;\n theme?: TextRendererTheme;\n}\n\nconst typographyClasses = (typography?: {\n fontFamily?: string;\n fontSize?: string;\n fontWeight?: string;\n lineHeight?: string;\n letterSpacing?: string;\n textTransform?: string;\n textDecoration?: string;\n textOverflow?: string;\n}) => [\n typography?.fontFamily,\n typography?.fontSize,\n typography?.fontWeight,\n typography?.lineHeight,\n typography?.letterSpacing,\n typography?.textTransform,\n typography?.textDecoration,\n typography?.textOverflow,\n];\n\nfunction removeEmptyTextNodes(node: any): any {\n if (!node || typeof node !== \"object\") {\n return node;\n }\n\n if (node.type === \"text\" && node.text === \"\") {\n return undefined;\n }\n\n if (!Array.isArray(node.content)) {\n return node;\n }\n\n const content = node.content\n .map(removeEmptyTextNodes)\n .filter((child: any) => child !== undefined);\n\n return {\n ...node,\n content,\n };\n}\n\n/**\n * Renders TipTap `TextEditorContent` JSON as static React elements — used to\n * display rich text saved by the `Editor` (e.g. inside a block's body). When\n * a `theme` is provided, headings/paragraphs/links pick up its typography;\n * otherwise plain semantic HTML tags are used.\n */\nexport function TextRenderer({\n json,\n className,\n theme,\n profile = \"default\",\n}: TextRendererProps) {\n const extensions = createExtensions();\n const content = removeEmptyTextNodes(\n (json as any) ?? (emptyDoc as any),\n ) as TextEditorContent;\n const docsHeadings = profile === \"documentation\" ? extractDocsHeadings(content) : [];\n // React can replay each mapped node (StrictMode, hydration, or a retry).\n // Bind IDs before rendering instead of consuming an iterator in a component.\n const renderContent =\n profile === \"documentation\" ? getSchema(extensions).nodeFromJSON(content) : content;\n const headingIds = new WeakMap<ProseMirrorNode, string>();\n if (profile === \"documentation\") {\n let headingIndex = 0;\n (renderContent as ProseMirrorNode).descendants((node) => {\n if (node.type.name === \"heading\") {\n headingIds.set(node, docsHeadings[headingIndex++].id);\n }\n });\n }\n\n const rendered = renderToReactElement({\n extensions,\n content: renderContent,\n options: {\n nodeMapping: {\n paragraph: ({ children }) => {\n const classes = cn(\n \"text-base\",\n ...typographyClasses(theme?.typography?.text1),\n );\n return <p className={classes}>{children}</p>;\n },\n hardBreak: () => <br />,\n heading: ({ node, children }) => {\n const level = node?.attrs?.level ?? 1;\n // Extract text from the node structure (same as extractHeadings does)\n const textContent = extractTextFromNode(node);\n const id =\n profile === \"documentation\" ? headingIds.get(node) : createId(textContent);\n\n if (!theme) {\n const Tag = `h${level}` as React.ElementType;\n return <Tag id={id}>{children}</Tag>;\n }\n\n if (level === 1) {\n const classes = cn(\n \"scroll-m-20 text-4xl font-bold tracking-tight\",\n ...typographyClasses(theme?.typography?.header1),\n );\n return (\n <h1 id={id} className={classes}>\n {children}\n </h1>\n );\n }\n if (level === 2) {\n const classes = cn(\n \"scroll-m-20 text-3xl font-bold tracking-tight\",\n ...typographyClasses(theme?.typography?.header2),\n );\n return (\n <h2 id={id} className={classes}>\n {children}\n </h2>\n );\n }\n const classes = cn(\n \"scroll-m-20 text-2xl font-semibold tracking-normal\",\n ...typographyClasses(\n profile === \"documentation\" && level >= 4\n ? (theme?.typography?.header4 ?? theme?.typography?.header3)\n : theme?.typography?.header3,\n ),\n );\n const Tag = (\n profile === \"documentation\" ? `h${level}` : \"h3\"\n ) as React.ElementType;\n return (\n <Tag id={id} className={classes}>\n {children}\n </Tag>\n );\n },\n codeBlock: ({ node, children }) => (\n <RenderedCodeBlock node={node} language={node?.attrs?.language}>\n {children}\n </RenderedCodeBlock>\n ),\n codeMirror: ({ node, children }) => (\n <RenderedCodeBlock node={node}>{children}</RenderedCodeBlock>\n ),\n },\n markMapping: {\n link: ({ mark, children }) => {\n const href = mark?.attrs?.href;\n const linkStyle = theme?.interactives?.link;\n const classes = cn(\n \"font-medium cursor-pointer\",\n ...typographyClasses(theme?.typography?.link),\n linkStyle?.padding?.x,\n linkStyle?.padding?.y,\n linkStyle?.border?.width,\n linkStyle?.border?.radius,\n linkStyle?.border?.style,\n linkStyle?.custom,\n );\n return (\n <a\n href={href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className={classes}\n style={{\n textShadow: linkStyle?.textShadow || undefined,\n }}\n >\n {children}\n </a>\n );\n },\n highlight: ({ children }) => (\n <mark className=\"bg-accent text-accent-foreground\">{children}</mark>\n ),\n },\n },\n });\n\n const combinedClassName = [\"tiptap-renderer\", className].filter(Boolean).join(\" \");\n\n return (\n <div className=\"text-editor flex flex-col gap-4\">\n <div className={combinedClassName}>{rendered}</div>\n </div>\n );\n}\n\nexport default TextRenderer;\n","export function extractTextFromNode(node): string {\n if (!node) {\n return \"\";\n }\n\n // ProseMirror nodes have a textContent property that contains all text\n if (typeof node.textContent === \"string\") {\n return node.textContent;\n }\n\n // Fallback: if content is a Fragment, access its content array\n if (node.content && node.content.content && Array.isArray(node.content.content)) {\n return node.content.content\n .map((child: any) => {\n // Text nodes have a text property\n if (child.text) {\n return child.text;\n }\n // Recursively extract from child nodes\n return extractTextFromNode(child);\n })\n .join(\"\");\n }\n\n // Fallback: if content is directly an array\n if (Array.isArray(node.content)) {\n return node.content\n .map((child: any) => {\n if (child.text) {\n return child.text;\n }\n return extractTextFromNode(child);\n })\n .join(\"\");\n }\n\n return \"\";\n}\n","import type { TextEditorContent } from \"./text-editor-content\";\n\nconst emptyDoc: TextEditorContent = {\n type: \"doc\",\n content: [],\n};\n\nexport default emptyDoc;\n","import StarterKit from \"@tiptap/starter-kit\";\nimport Placeholder from \"@tiptap/extension-placeholder\";\nimport Link from \"@tiptap/extension-link\";\nimport { Table, TableCell, TableHeader, TableRow } from \"@tiptap/extension-table\";\nimport Image from \"@tiptap/extension-image\";\nimport Dropcursor from \"@tiptap/extension-dropcursor\";\nimport Gapcursor from \"@tiptap/extension-gapcursor\";\nimport CodeBlockLowlight from \"@tiptap/extension-code-block-lowlight\";\nimport Heading from \"@tiptap/extension-heading\";\nimport Highlight from \"@tiptap/extension-highlight\";\nimport { lowlight } from \"lowlight\";\nimport javascript from \"highlight.js/lib/languages/javascript\";\nimport typescript from \"highlight.js/lib/languages/typescript\";\nimport json from \"highlight.js/lib/languages/json\";\nimport css from \"highlight.js/lib/languages/css\";\nimport xml from \"highlight.js/lib/languages/xml\";\nimport type { Extensions } from \"@tiptap/core\";\nimport { createId } from \"./create-id\";\nimport { CodeMirrorNode } from \"./components/custom-code-mirror\";\n\nlowlight.registerLanguage(\"javascript\", javascript);\nlowlight.registerLanguage(\"typescript\", typescript);\nlowlight.registerLanguage(\"json\", json);\nlowlight.registerLanguage(\"css\", css);\nlowlight.registerLanguage(\"html\", xml);\n\ninterface ExtensionOptions {\n placeholder?: string;\n}\n\nexport const createExtensions = ({\n placeholder,\n}: ExtensionOptions = {}): Extensions => [\n StarterKit.configure({\n codeBlock: false,\n // Configured/replaced explicitly below (custom options or a\n // `.extend()`) — disabled here to avoid tiptap's duplicate-extension\n // warning and constructing throwaway instances of each.\n link: false,\n dropcursor: false,\n gapcursor: false,\n heading: false,\n }),\n Placeholder.configure({\n placeholder: placeholder || \"Write something…\",\n }),\n Link.configure({\n openOnClick: false,\n autolink: true,\n linkOnPaste: true,\n }),\n Table.configure({\n resizable: true,\n }),\n TableRow,\n TableHeader,\n TableCell,\n Image.configure({\n // We embed uploaded images as data URLs (no backend/media library in\n // this package), so base64 sources must be allowed.\n allowBase64: true,\n HTMLAttributes: {\n class: \"max-w-full h-auto rounded-md\",\n },\n resize: {\n enabled: true,\n directions: [\"top\", \"bottom\", \"left\", \"right\"],\n alwaysPreserveAspectRatio: true,\n },\n }),\n Dropcursor.configure({\n color: \"hsl(var(--foreground))\",\n width: 2,\n }),\n Gapcursor,\n CodeBlockLowlight.configure({\n lowlight,\n }),\n Heading.extend({\n renderHTML({ node, HTMLAttributes }) {\n const level = this.options.levels.includes(node.attrs.level)\n ? node.attrs.level\n : this.options.levels[0];\n const id =\n typeof node.textContent === \"string\" ? createId(node.textContent) : undefined;\n\n return [\n `h${level}`,\n {\n ...HTMLAttributes,\n id,\n },\n 0,\n ];\n },\n }),\n Highlight,\n CodeMirrorNode,\n];\n","export function createId(textContent: string) {\n return textContent.replace(/\\s/g, \"-\").toLowerCase();\n}\n","// This is only to maintain backward compatibility for codeMirror node type\n\nimport { Node } from \"@tiptap/core\";\n\nexport const CodeMirrorNode = Node.create({\n name: \"codeMirror\",\n content: \"block+\",\n code: true,\n\n addOptions() {\n return {\n HTMLAttributes: {},\n };\n },\n\n parseHTML() {\n return [\n {\n tag: \"pre\",\n },\n ];\n },\n\n renderHTML({ HTMLAttributes }) {\n return [\"pre\", HTMLAttributes, 0];\n },\n});\n","import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","import React from \"react\";\nimport { extractTextFromNode } from \"../extract-text-from-node\";\nimport { CopyCodeButton } from \"./copy-code-button\";\n\nfunction textFromChildren(node: React.ReactNode): string {\n if (node == null || typeof node === \"boolean\") return \"\";\n if (typeof node === \"string\" || typeof node === \"number\") {\n return String(node);\n }\n if (Array.isArray(node)) return node.map(textFromChildren).join(\"\");\n if (React.isValidElement<{ children?: React.ReactNode }>(node)) {\n return textFromChildren(node.props.children);\n }\n return \"\";\n}\n\nexport function RenderedCodeBlock({\n node,\n children,\n language,\n}: {\n node?: { textContent?: string; content?: unknown };\n children?: React.ReactNode;\n language?: string;\n}): React.JSX.Element {\n const source = extractTextFromNode(node) || textFromChildren(children);\n\n return (\n <div className=\"te-code-block\">\n <pre>\n <code className={language ? `language-${language}` : undefined}>\n {children}\n </code>\n </pre>\n <CopyCodeButton source={source} />\n </div>\n );\n}\n","export const DOCS_HEADING_POLICY_VERSION = 1;\n\nexport interface HeadingNode {\n type?: string;\n text?: string;\n attrs?: Record<string, unknown>;\n content?: HeadingNode[];\n}\n\nexport function docsNodeText(node: HeadingNode): string {\n if (typeof node.text === \"string\") return node.text;\n if (node.type === \"hardBreak\") return \"\\n\";\n return (node.content ?? []).map(docsNodeText).join(\"\");\n}\n\n/** DM-CONVERT: deterministic Unicode anchors, including duplicate-base collisions. */\nexport function createHeadingIdGenerator(): (text: string) => string {\n const used = new Set<string>();\n return (text) => {\n const base =\n text\n .normalize(\"NFKC\")\n .toLowerCase()\n .trim()\n .replace(/[^\\p{L}\\p{N}\\s_-]/gu, \"\")\n .replace(/\\s+/g, \"-\") || \"section\";\n let id = base;\n let suffix = 2;\n while (used.has(id)) id = `${base}-${suffix++}`;\n used.add(id);\n return id;\n };\n}\n\nexport function extractDocsHeadings(json: HeadingNode | null | undefined) {\n const nextId = createHeadingIdGenerator();\n const headings: { level: number; text: string; id: string }[] = [];\n function walk(node: HeadingNode) {\n if (node.type === \"heading\") {\n const text = docsNodeText(node);\n headings.push({ level: Number(node.attrs?.level ?? 1), text, id: nextId(text) });\n }\n for (const child of node.content ?? []) walk(child);\n }\n if (json) walk(json);\n return headings;\n}\n"],"mappings":"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","names":["json","jsx","json","Tag","classes"]}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
// src/components/copy-code-button.tsx
|
|
5
|
+
import { useEffect, useState } from "react";
|
|
6
|
+
import { Check, Copy } from "lucide-react";
|
|
7
|
+
import { jsx } from "react/jsx-runtime";
|
|
8
|
+
async function writeClipboard(text) {
|
|
9
|
+
if (navigator.clipboard?.writeText) {
|
|
10
|
+
await navigator.clipboard.writeText(text);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const area = document.createElement("textarea");
|
|
14
|
+
area.value = text;
|
|
15
|
+
area.setAttribute("readonly", "");
|
|
16
|
+
area.style.position = "fixed";
|
|
17
|
+
area.style.left = "-9999px";
|
|
18
|
+
document.body.appendChild(area);
|
|
19
|
+
area.select();
|
|
20
|
+
document.execCommand("copy");
|
|
21
|
+
area.remove();
|
|
22
|
+
}
|
|
23
|
+
function CopyCodeButton({ source }) {
|
|
24
|
+
const [copied, setCopied] = useState(false);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (!copied) return;
|
|
27
|
+
const timer = window.setTimeout(() => setCopied(false), 1500);
|
|
28
|
+
return () => window.clearTimeout(timer);
|
|
29
|
+
}, [copied]);
|
|
30
|
+
async function handleCopy() {
|
|
31
|
+
if (!source) return;
|
|
32
|
+
try {
|
|
33
|
+
await writeClipboard(source);
|
|
34
|
+
setCopied(true);
|
|
35
|
+
} catch {
|
|
36
|
+
setCopied(false);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return /* @__PURE__ */ jsx(
|
|
40
|
+
"button",
|
|
41
|
+
{
|
|
42
|
+
type: "button",
|
|
43
|
+
className: "te-code-copy",
|
|
44
|
+
"aria-label": copied ? "Copied" : "Copy code",
|
|
45
|
+
title: copied ? "Copied" : "Copy",
|
|
46
|
+
onClick: () => {
|
|
47
|
+
void handleCopy();
|
|
48
|
+
},
|
|
49
|
+
children: copied ? /* @__PURE__ */ jsx(Check, { className: "te-code-copy__icon", "aria-hidden": true }) : /* @__PURE__ */ jsx(Copy, { className: "te-code-copy__icon", "aria-hidden": true })
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
export {
|
|
54
|
+
CopyCodeButton
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=copy-code-button.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/copy-code-button.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { Check, Copy } from \"lucide-react\";\n\nasync function writeClipboard(text: string): Promise<void> {\n if (navigator.clipboard?.writeText) {\n await navigator.clipboard.writeText(text);\n return;\n }\n const area = document.createElement(\"textarea\");\n area.value = text;\n area.setAttribute(\"readonly\", \"\");\n area.style.position = \"fixed\";\n area.style.left = \"-9999px\";\n document.body.appendChild(area);\n area.select();\n document.execCommand(\"copy\");\n area.remove();\n}\n\n/** The copy button of a rendered code block; the only client part of it. */\nexport function CopyCodeButton({ source }: { source: string }) {\n const [copied, setCopied] = useState(false);\n\n useEffect(() => {\n if (!copied) return;\n const timer = window.setTimeout(() => setCopied(false), 1500);\n return () => window.clearTimeout(timer);\n }, [copied]);\n\n async function handleCopy() {\n if (!source) return;\n try {\n await writeClipboard(source);\n setCopied(true);\n } catch {\n setCopied(false);\n }\n }\n\n return (\n <button\n type=\"button\"\n className=\"te-code-copy\"\n aria-label={copied ? \"Copied\" : \"Copy code\"}\n title={copied ? \"Copied\" : \"Copy\"}\n onClick={() => {\n void handleCopy();\n }}\n >\n {copied ? (\n <Check className=\"te-code-copy__icon\" aria-hidden />\n ) : (\n <Copy className=\"te-code-copy__icon\" aria-hidden />\n )}\n </button>\n );\n}\n"],"mappings":";;;;AAEA,SAAS,WAAW,gBAAgB;AACpC,SAAS,OAAO,YAAY;AAiDpB;AA/CR,eAAe,eAAe,MAA6B;AACzD,MAAI,UAAU,WAAW,WAAW;AAClC,UAAM,UAAU,UAAU,UAAU,IAAI;AACxC;AAAA,EACF;AACA,QAAM,OAAO,SAAS,cAAc,UAAU;AAC9C,OAAK,QAAQ;AACb,OAAK,aAAa,YAAY,EAAE;AAChC,OAAK,MAAM,WAAW;AACtB,OAAK,MAAM,OAAO;AAClB,WAAS,KAAK,YAAY,IAAI;AAC9B,OAAK,OAAO;AACZ,WAAS,YAAY,MAAM;AAC3B,OAAK,OAAO;AACd;AAGO,SAAS,eAAe,EAAE,OAAO,GAAuB;AAC7D,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,KAAK;AAE1C,YAAU,MAAM;AACd,QAAI,CAAC,OAAQ;AACb,UAAM,QAAQ,OAAO,WAAW,MAAM,UAAU,KAAK,GAAG,IAAI;AAC5D,WAAO,MAAM,OAAO,aAAa,KAAK;AAAA,EACxC,GAAG,CAAC,MAAM,CAAC;AAEX,iBAAe,aAAa;AAC1B,QAAI,CAAC,OAAQ;AACb,QAAI;AACF,YAAM,eAAe,MAAM;AAC3B,gBAAU,IAAI;AAAA,IAChB,QAAQ;AACN,gBAAU,KAAK;AAAA,IACjB;AAAA,EACF;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,WAAU;AAAA,MACV,cAAY,SAAS,WAAW;AAAA,MAChC,OAAO,SAAS,WAAW;AAAA,MAC3B,SAAS,MAAM;AACb,aAAK,WAAW;AAAA,MAClB;AAAA,MAEC,mBACC,oBAAC,SAAM,WAAU,sBAAqB,eAAW,MAAC,IAElD,oBAAC,QAAK,WAAU,sBAAqB,eAAW,MAAC;AAAA;AAAA,EAErD;AAEJ;","names":[]}
|