next-blogpanel 1.0.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/LICENSE +21 -0
  2. package/README.md +211 -0
  3. package/dist/admin/index.cjs +3062 -0
  4. package/dist/admin/index.cjs.map +1 -0
  5. package/dist/admin/index.d.cts +51 -0
  6. package/dist/admin/index.d.ts +51 -0
  7. package/dist/admin/index.js +3029 -0
  8. package/dist/admin/index.js.map +1 -0
  9. package/dist/api/categories.cjs +82 -0
  10. package/dist/api/categories.cjs.map +1 -0
  11. package/dist/api/categories.d.cts +27 -0
  12. package/dist/api/categories.d.ts +27 -0
  13. package/dist/api/categories.js +77 -0
  14. package/dist/api/categories.js.map +1 -0
  15. package/dist/api/media-file.cjs +46 -0
  16. package/dist/api/media-file.cjs.map +1 -0
  17. package/dist/api/media-file.d.cts +7 -0
  18. package/dist/api/media-file.d.ts +7 -0
  19. package/dist/api/media-file.js +44 -0
  20. package/dist/api/media-file.js.map +1 -0
  21. package/dist/api/media.cjs +180 -0
  22. package/dist/api/media.cjs.map +1 -0
  23. package/dist/api/media.d.cts +22 -0
  24. package/dist/api/media.d.ts +22 -0
  25. package/dist/api/media.js +176 -0
  26. package/dist/api/media.js.map +1 -0
  27. package/dist/api/posts.cjs +103 -0
  28. package/dist/api/posts.cjs.map +1 -0
  29. package/dist/api/posts.d.cts +27 -0
  30. package/dist/api/posts.d.ts +27 -0
  31. package/dist/api/posts.js +98 -0
  32. package/dist/api/posts.js.map +1 -0
  33. package/dist/api/rss.cjs +25 -0
  34. package/dist/api/rss.cjs.map +1 -0
  35. package/dist/api/rss.d.cts +5 -0
  36. package/dist/api/rss.d.ts +5 -0
  37. package/dist/api/rss.js +23 -0
  38. package/dist/api/rss.js.map +1 -0
  39. package/dist/api/settings.cjs +40 -0
  40. package/dist/api/settings.cjs.map +1 -0
  41. package/dist/api/settings.d.cts +17 -0
  42. package/dist/api/settings.d.ts +17 -0
  43. package/dist/api/settings.js +37 -0
  44. package/dist/api/settings.js.map +1 -0
  45. package/dist/api/sitemap.cjs +25 -0
  46. package/dist/api/sitemap.cjs.map +1 -0
  47. package/dist/api/sitemap.d.cts +5 -0
  48. package/dist/api/sitemap.d.ts +5 -0
  49. package/dist/api/sitemap.js +23 -0
  50. package/dist/api/sitemap.js.map +1 -0
  51. package/dist/api/tokens.cjs +56 -0
  52. package/dist/api/tokens.cjs.map +1 -0
  53. package/dist/api/tokens.d.cts +22 -0
  54. package/dist/api/tokens.d.ts +22 -0
  55. package/dist/api/tokens.js +52 -0
  56. package/dist/api/tokens.js.map +1 -0
  57. package/dist/chunk-2M46MMWN.cjs +76 -0
  58. package/dist/chunk-2M46MMWN.cjs.map +1 -0
  59. package/dist/chunk-4GK22Q3R.cjs +229 -0
  60. package/dist/chunk-4GK22Q3R.cjs.map +1 -0
  61. package/dist/chunk-6PZ772NQ.js +595 -0
  62. package/dist/chunk-6PZ772NQ.js.map +1 -0
  63. package/dist/chunk-BZXPRGLN.cjs +351 -0
  64. package/dist/chunk-BZXPRGLN.cjs.map +1 -0
  65. package/dist/chunk-DAX5C6SC.cjs +86 -0
  66. package/dist/chunk-DAX5C6SC.cjs.map +1 -0
  67. package/dist/chunk-DWNMHQ4I.cjs +57 -0
  68. package/dist/chunk-DWNMHQ4I.cjs.map +1 -0
  69. package/dist/chunk-FPGVR2BX.cjs +121 -0
  70. package/dist/chunk-FPGVR2BX.cjs.map +1 -0
  71. package/dist/chunk-JBP3G4QJ.js +50 -0
  72. package/dist/chunk-JBP3G4QJ.js.map +1 -0
  73. package/dist/chunk-JDP4DIHI.js +215 -0
  74. package/dist/chunk-JDP4DIHI.js.map +1 -0
  75. package/dist/chunk-LIZOVA4W.js +118 -0
  76. package/dist/chunk-LIZOVA4W.js.map +1 -0
  77. package/dist/chunk-QLBTOCT3.js +118 -0
  78. package/dist/chunk-QLBTOCT3.js.map +1 -0
  79. package/dist/chunk-UI2KDZ5H.js +344 -0
  80. package/dist/chunk-UI2KDZ5H.js.map +1 -0
  81. package/dist/chunk-V7QPRX4A.cjs +125 -0
  82. package/dist/chunk-V7QPRX4A.cjs.map +1 -0
  83. package/dist/chunk-VEJFN65Y.js +82 -0
  84. package/dist/chunk-VEJFN65Y.js.map +1 -0
  85. package/dist/chunk-WYZFAI6O.js +74 -0
  86. package/dist/chunk-WYZFAI6O.js.map +1 -0
  87. package/dist/chunk-XLYTATRQ.cjs +637 -0
  88. package/dist/chunk-XLYTATRQ.cjs.map +1 -0
  89. package/dist/cli/index.cjs +2036 -0
  90. package/dist/components/index.cjs +647 -0
  91. package/dist/components/index.cjs.map +1 -0
  92. package/dist/components/index.d.cts +187 -0
  93. package/dist/components/index.d.ts +187 -0
  94. package/dist/components/index.js +633 -0
  95. package/dist/components/index.js.map +1 -0
  96. package/dist/db-M3KXLBUP.js +3 -0
  97. package/dist/db-M3KXLBUP.js.map +1 -0
  98. package/dist/db-TDCZNY3Q.cjs +112 -0
  99. package/dist/db-TDCZNY3Q.cjs.map +1 -0
  100. package/dist/editor/index.cjs +1126 -0
  101. package/dist/editor/index.cjs.map +1 -0
  102. package/dist/editor/index.d.cts +137 -0
  103. package/dist/editor/index.d.ts +137 -0
  104. package/dist/editor/index.js +1093 -0
  105. package/dist/editor/index.js.map +1 -0
  106. package/dist/index-BcNka7gK.d.cts +475 -0
  107. package/dist/index-DPRgaNEE.d.ts +475 -0
  108. package/dist/index.cjs +433 -0
  109. package/dist/index.cjs.map +1 -0
  110. package/dist/index.d.cts +27 -0
  111. package/dist/index.d.ts +27 -0
  112. package/dist/index.js +209 -0
  113. package/dist/index.js.map +1 -0
  114. package/dist/lib/index.cjs +193 -0
  115. package/dist/lib/index.cjs.map +1 -0
  116. package/dist/lib/index.d.cts +4 -0
  117. package/dist/lib/index.d.ts +4 -0
  118. package/dist/lib/index.js +8 -0
  119. package/dist/lib/index.js.map +1 -0
  120. package/dist/styles/admin.css +667 -0
  121. package/dist/styles/blog.css +851 -0
  122. package/dist/styles/editor.css +452 -0
  123. package/dist/styles/globals.css +270 -0
  124. package/dist/styles/prose.css +299 -0
  125. package/dist/types-CgrBuig-.d.cts +778 -0
  126. package/dist/types-CgrBuig-.d.ts +778 -0
  127. package/package.json +140 -0
@@ -0,0 +1,1093 @@
1
+ "use client";
2
+ import React, { useState, useRef, useCallback, useEffect } from 'react';
3
+ import { useEditor, BubbleMenu, EditorContent } from '@tiptap/react';
4
+ import StarterKit from '@tiptap/starter-kit';
5
+ import Placeholder from '@tiptap/extension-placeholder';
6
+ import Link from '@tiptap/extension-link';
7
+ import Underline from '@tiptap/extension-underline';
8
+ import Highlight from '@tiptap/extension-highlight';
9
+ import Typography from '@tiptap/extension-typography';
10
+ import TaskList from '@tiptap/extension-task-list';
11
+ import TaskItem from '@tiptap/extension-task-item';
12
+ import Table from '@tiptap/extension-table';
13
+ import TableRow from '@tiptap/extension-table-row';
14
+ import TableCell from '@tiptap/extension-table-cell';
15
+ import TableHeader from '@tiptap/extension-table-header';
16
+ import { Image } from '@tiptap/extension-image';
17
+ import { Plugin, PluginKey } from '@tiptap/pm/state';
18
+ import { Node, mergeAttributes, Extension } from '@tiptap/core';
19
+ import CodeBlockLowlight from '@tiptap/extension-code-block';
20
+ import { jsxs, jsx } from 'react/jsx-runtime';
21
+
22
+ // src/editor/Editor.tsx
23
+ var DEFAULT_MAX_UPLOAD_SIZE = 10 * 1024 * 1024;
24
+
25
+ // src/editor/extensions/image-upload.ts
26
+ var ImageUpload = Image.extend({
27
+ addOptions() {
28
+ return {
29
+ ...this.parent?.(),
30
+ uploadFn: async (_file) => ({ url: "" }),
31
+ maxSize: DEFAULT_MAX_UPLOAD_SIZE,
32
+ // the ingest ceiling, NOT maxImageSize — see src/lib/limits.ts
33
+ allowedTypes: ["image/jpeg", "image/png", "image/gif", "image/webp", "image/svg+xml"]
34
+ };
35
+ },
36
+ addAttributes() {
37
+ return {
38
+ ...this.parent?.(),
39
+ loading: {
40
+ default: false,
41
+ renderHTML: (attributes) => {
42
+ if (!attributes.loading) return {};
43
+ return { "data-loading": "true" };
44
+ }
45
+ },
46
+ width: { default: null },
47
+ height: { default: null },
48
+ caption: {
49
+ default: null,
50
+ renderHTML: (attributes) => {
51
+ if (!attributes.caption) return {};
52
+ return { "data-caption": attributes.caption };
53
+ }
54
+ }
55
+ };
56
+ },
57
+ addCommands() {
58
+ return {
59
+ ...this.parent?.(),
60
+ uploadImage: (file) => ({ commands, editor }) => {
61
+ const opts = this.options;
62
+ const { uploadFn, maxSize, allowedTypes } = opts;
63
+ if (maxSize && file.size > maxSize) {
64
+ console.error(`File too large: ${file.size} > ${maxSize}`);
65
+ return false;
66
+ }
67
+ if (allowedTypes && !allowedTypes.includes(file.type)) {
68
+ console.error(`File type not allowed: ${file.type}`);
69
+ return false;
70
+ }
71
+ const placeholderUrl = URL.createObjectURL(file);
72
+ commands.insertContent({
73
+ type: "image",
74
+ attrs: { src: placeholderUrl, loading: true, alt: file.name }
75
+ });
76
+ uploadFn(file).then((result) => {
77
+ const { state } = editor;
78
+ const { doc } = state;
79
+ let pos = null;
80
+ doc.descendants((node, nodePos) => {
81
+ if (node.type.name === "image" && node.attrs.src === placeholderUrl) {
82
+ pos = nodePos;
83
+ return false;
84
+ }
85
+ });
86
+ if (pos !== null) {
87
+ editor.chain().focus().setNodeSelection(pos).updateAttributes("image", {
88
+ src: result.url,
89
+ alt: result.alt || file.name,
90
+ loading: false
91
+ }).run();
92
+ }
93
+ URL.revokeObjectURL(placeholderUrl);
94
+ }).catch((err) => {
95
+ console.error("Image upload failed:", err);
96
+ URL.revokeObjectURL(placeholderUrl);
97
+ });
98
+ return true;
99
+ }
100
+ };
101
+ },
102
+ addProseMirrorPlugins() {
103
+ const opts = this.options;
104
+ const { maxSize, allowedTypes } = opts;
105
+ const editorRef = this.editor;
106
+ return [
107
+ new Plugin({
108
+ key: new PluginKey("imageUploadDrop"),
109
+ props: {
110
+ handleDOMEvents: {
111
+ drop(view, event) {
112
+ const files = event.dataTransfer?.files;
113
+ if (!files || files.length === 0) return false;
114
+ const imageFiles = Array.from(files).filter(
115
+ (f) => (allowedTypes || []).includes(f.type)
116
+ );
117
+ if (imageFiles.length === 0) return false;
118
+ event.preventDefault();
119
+ for (const file of imageFiles) {
120
+ if (maxSize && file.size > maxSize) continue;
121
+ editorRef.commands.uploadImage(file);
122
+ }
123
+ return true;
124
+ },
125
+ paste(view, event) {
126
+ const files = event.clipboardData?.files;
127
+ if (!files || files.length === 0) return false;
128
+ const imageFiles = Array.from(files).filter(
129
+ (f) => (allowedTypes || []).includes(f.type)
130
+ );
131
+ if (imageFiles.length === 0) return false;
132
+ event.preventDefault();
133
+ for (const file of imageFiles) {
134
+ if (maxSize && file.size > maxSize) continue;
135
+ editorRef.commands.uploadImage(file);
136
+ }
137
+ return true;
138
+ }
139
+ }
140
+ }
141
+ })
142
+ ];
143
+ }
144
+ });
145
+ var Callout = Node.create({
146
+ name: "callout",
147
+ group: "block",
148
+ content: "block+",
149
+ defining: true,
150
+ addAttributes() {
151
+ return {
152
+ type: {
153
+ default: "info",
154
+ parseHTML: (element) => element.getAttribute("data-callout-type") || "info",
155
+ renderHTML: (attributes) => ({
156
+ "data-callout-type": attributes.type
157
+ })
158
+ }
159
+ };
160
+ },
161
+ parseHTML() {
162
+ return [{ tag: "div[data-callout]" }];
163
+ },
164
+ renderHTML({ HTMLAttributes }) {
165
+ return [
166
+ "div",
167
+ mergeAttributes(HTMLAttributes, { "data-callout": "", class: `nbp-callout nbp-callout-${HTMLAttributes["data-callout-type"] || "info"}` }),
168
+ 0
169
+ ];
170
+ },
171
+ addCommands() {
172
+ return {
173
+ setCallout: (attrs) => ({ commands }) => {
174
+ return commands.wrapIn(this.name, attrs);
175
+ },
176
+ toggleCallout: (attrs) => ({ commands }) => {
177
+ return commands.toggleWrap(this.name, attrs);
178
+ }
179
+ };
180
+ }
181
+ });
182
+ var FAQItem = Node.create({
183
+ name: "faqItem",
184
+ group: "block",
185
+ content: "faqQuestion faqAnswer",
186
+ defining: true,
187
+ parseHTML() {
188
+ return [{ tag: "div[data-faq-item]" }];
189
+ },
190
+ renderHTML({ HTMLAttributes }) {
191
+ return ["div", mergeAttributes(HTMLAttributes, { "data-faq-item": "", class: "nbp-faq-item" }), 0];
192
+ }
193
+ });
194
+ var FAQQuestion = Node.create({
195
+ name: "faqQuestion",
196
+ content: "inline*",
197
+ defining: true,
198
+ parseHTML() {
199
+ return [{ tag: "div[data-faq-question]" }];
200
+ },
201
+ renderHTML({ HTMLAttributes }) {
202
+ return ["div", mergeAttributes(HTMLAttributes, { "data-faq-question": "", class: "nbp-faq-question" }), 0];
203
+ }
204
+ });
205
+ var FAQAnswer = Node.create({
206
+ name: "faqAnswer",
207
+ content: "block+",
208
+ defining: true,
209
+ parseHTML() {
210
+ return [{ tag: "div[data-faq-answer]" }];
211
+ },
212
+ renderHTML({ HTMLAttributes }) {
213
+ return ["div", mergeAttributes(HTMLAttributes, { "data-faq-answer": "", class: "nbp-faq-answer" }), 0];
214
+ }
215
+ });
216
+ var FAQ = Node.create({
217
+ name: "faq",
218
+ group: "block",
219
+ content: "faqItem+",
220
+ defining: true,
221
+ parseHTML() {
222
+ return [{ tag: "div[data-faq]" }];
223
+ },
224
+ renderHTML({ HTMLAttributes }) {
225
+ return ["div", mergeAttributes(HTMLAttributes, { "data-faq": "", class: "nbp-faq" }), 0];
226
+ },
227
+ addCommands() {
228
+ return {
229
+ insertFAQ: () => ({ chain }) => {
230
+ return chain().insertContent({
231
+ type: "faq",
232
+ content: [
233
+ {
234
+ type: "faqItem",
235
+ content: [
236
+ { type: "faqQuestion", content: [{ type: "text", text: "Question?" }] },
237
+ { type: "faqAnswer", content: [{ type: "paragraph", content: [{ type: "text", text: "Answer." }] }] }
238
+ ]
239
+ }
240
+ ]
241
+ }).run();
242
+ }
243
+ };
244
+ }
245
+ });
246
+ var TableOfContents = Node.create({
247
+ name: "tableOfContents",
248
+ group: "block",
249
+ atom: true,
250
+ parseHTML() {
251
+ return [{ tag: "div[data-toc]" }];
252
+ },
253
+ renderHTML({ HTMLAttributes }) {
254
+ return [
255
+ "div",
256
+ mergeAttributes(HTMLAttributes, {
257
+ "data-toc": "",
258
+ class: "nbp-toc-placeholder"
259
+ }),
260
+ "Table of Contents (auto-generated)"
261
+ ];
262
+ },
263
+ addCommands() {
264
+ return {
265
+ insertTableOfContents: () => ({ commands }) => {
266
+ return commands.insertContent({ type: this.name });
267
+ }
268
+ };
269
+ }
270
+ });
271
+ var CodeBlockEnhanced = CodeBlockLowlight.extend({
272
+ addAttributes() {
273
+ return {
274
+ ...this.parent?.(),
275
+ language: {
276
+ default: "plaintext",
277
+ parseHTML: (element) => element.getAttribute("data-language") || element.querySelector("code")?.className?.replace("language-", "") || "plaintext",
278
+ renderHTML: (attributes) => ({
279
+ "data-language": attributes.language
280
+ })
281
+ },
282
+ filename: {
283
+ default: null,
284
+ parseHTML: (element) => element.getAttribute("data-filename"),
285
+ renderHTML: (attributes) => {
286
+ if (!attributes.filename) return {};
287
+ return { "data-filename": attributes.filename };
288
+ }
289
+ }
290
+ };
291
+ }
292
+ });
293
+ var SUPPORTED_LANGUAGES = [
294
+ "plaintext",
295
+ "javascript",
296
+ "typescript",
297
+ "jsx",
298
+ "tsx",
299
+ "html",
300
+ "css",
301
+ "scss",
302
+ "json",
303
+ "python",
304
+ "rust",
305
+ "go",
306
+ "java",
307
+ "kotlin",
308
+ "swift",
309
+ "ruby",
310
+ "php",
311
+ "c",
312
+ "cpp",
313
+ "csharp",
314
+ "sql",
315
+ "bash",
316
+ "shell",
317
+ "yaml",
318
+ "toml",
319
+ "markdown",
320
+ "graphql",
321
+ "docker",
322
+ "nginx"
323
+ ];
324
+ var defaultSlashCommands = [
325
+ {
326
+ title: "Heading 2",
327
+ description: "Large section heading",
328
+ icon: "H2",
329
+ command: (editor) => editor.chain().focus().toggleHeading({ level: 2 }).run()
330
+ },
331
+ {
332
+ title: "Heading 3",
333
+ description: "Medium section heading",
334
+ icon: "H3",
335
+ command: (editor) => editor.chain().focus().toggleHeading({ level: 3 }).run()
336
+ },
337
+ {
338
+ title: "Heading 4",
339
+ description: "Small section heading",
340
+ icon: "H4",
341
+ command: (editor) => editor.chain().focus().toggleHeading({ level: 4 }).run()
342
+ },
343
+ {
344
+ title: "Bullet List",
345
+ description: "Create a simple bullet list",
346
+ icon: "\u2022",
347
+ command: (editor) => editor.chain().focus().toggleBulletList().run()
348
+ },
349
+ {
350
+ title: "Numbered List",
351
+ description: "Create a numbered list",
352
+ icon: "1.",
353
+ command: (editor) => editor.chain().focus().toggleOrderedList().run()
354
+ },
355
+ {
356
+ title: "Task List",
357
+ description: "Create a checklist",
358
+ icon: "\u2611",
359
+ command: (editor) => editor.chain().focus().toggleTaskList().run()
360
+ },
361
+ {
362
+ title: "Blockquote",
363
+ description: "Add a quote block",
364
+ icon: '"',
365
+ command: (editor) => editor.chain().focus().toggleBlockquote().run()
366
+ },
367
+ {
368
+ title: "Code Block",
369
+ description: "Add a code snippet",
370
+ icon: "</>",
371
+ command: (editor) => editor.chain().focus().toggleCodeBlock().run()
372
+ },
373
+ {
374
+ title: "Divider",
375
+ description: "Add a horizontal divider",
376
+ icon: "\u2014",
377
+ command: (editor) => editor.chain().focus().setHorizontalRule().run()
378
+ },
379
+ {
380
+ title: "Image",
381
+ description: "Upload or embed an image",
382
+ icon: "\u{1F5BC}",
383
+ command: (editor) => {
384
+ const input = document.createElement("input");
385
+ input.type = "file";
386
+ input.accept = "image/*";
387
+ input.onchange = () => {
388
+ const file = input.files?.[0];
389
+ if (file) {
390
+ editor.commands.uploadImage(file);
391
+ }
392
+ };
393
+ input.click();
394
+ }
395
+ },
396
+ {
397
+ title: "Table",
398
+ description: "Add a table",
399
+ icon: "\u229E",
400
+ command: (editor) => editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run()
401
+ },
402
+ {
403
+ title: "Callout",
404
+ description: "Add an info callout box",
405
+ icon: "\u2139",
406
+ command: (editor) => editor.chain().focus().setCallout({ type: "info" }).run()
407
+ },
408
+ {
409
+ title: "FAQ",
410
+ description: "Add a FAQ section",
411
+ icon: "?",
412
+ command: (editor) => editor.chain().focus().insertFAQ().run()
413
+ },
414
+ {
415
+ title: "Table of Contents",
416
+ description: "Auto-generated from headings",
417
+ icon: "\u2261",
418
+ command: (editor) => editor.chain().focus().insertTableOfContents().run()
419
+ }
420
+ ];
421
+ var SlashCommand = Extension.create({
422
+ name: "slashCommand",
423
+ addOptions() {
424
+ return {
425
+ commands: defaultSlashCommands,
426
+ onStateChange: (_state) => {
427
+ }
428
+ };
429
+ },
430
+ addStorage() {
431
+ return {
432
+ deleteSlashAndRun: (_item) => {
433
+ }
434
+ };
435
+ },
436
+ addProseMirrorPlugins() {
437
+ const { commands, onStateChange } = this.options;
438
+ const editorRef = this.editor;
439
+ const storage = this.editor.storage.slashCommand;
440
+ let state = {
441
+ isOpen: false,
442
+ query: "",
443
+ position: null,
444
+ selectedIndex: 0,
445
+ items: commands
446
+ };
447
+ function updateState(partial) {
448
+ state = { ...state, ...partial };
449
+ onStateChange(state);
450
+ }
451
+ function deleteSlashText(view) {
452
+ const { $from } = view.state.selection;
453
+ const textBefore = $from.parent.textContent.slice(0, $from.parentOffset);
454
+ const slashIndex = textBefore.lastIndexOf("/");
455
+ if (slashIndex >= 0) {
456
+ const start = $from.start() + slashIndex;
457
+ const end = $from.pos;
458
+ view.dispatch(view.state.tr.delete(start, end));
459
+ }
460
+ }
461
+ storage.deleteSlashAndRun = (item) => {
462
+ deleteSlashText(editorRef.view);
463
+ item.command(editorRef);
464
+ updateState({ isOpen: false, query: "", selectedIndex: 0 });
465
+ };
466
+ return [
467
+ new Plugin({
468
+ key: new PluginKey("slashCommand"),
469
+ props: {
470
+ handleKeyDown(view, event) {
471
+ if (!state.isOpen) {
472
+ return false;
473
+ }
474
+ if (event.key === "ArrowDown") {
475
+ event.preventDefault();
476
+ updateState({
477
+ selectedIndex: (state.selectedIndex + 1) % state.items.length
478
+ });
479
+ return true;
480
+ }
481
+ if (event.key === "ArrowUp") {
482
+ event.preventDefault();
483
+ updateState({
484
+ selectedIndex: (state.selectedIndex - 1 + state.items.length) % state.items.length
485
+ });
486
+ return true;
487
+ }
488
+ if (event.key === "Enter") {
489
+ event.preventDefault();
490
+ const item = state.items[state.selectedIndex];
491
+ if (item) {
492
+ deleteSlashText(view);
493
+ item.command(editorRef);
494
+ }
495
+ updateState({ isOpen: false, query: "", selectedIndex: 0 });
496
+ return true;
497
+ }
498
+ if (event.key === "Escape") {
499
+ updateState({ isOpen: false, query: "", selectedIndex: 0 });
500
+ return true;
501
+ }
502
+ return false;
503
+ },
504
+ handleTextInput(view, from, _to, text) {
505
+ const { $from } = view.state.selection;
506
+ const textBefore = $from.parent.textContent.slice(0, $from.parentOffset) + text;
507
+ const slashIndex = textBefore.lastIndexOf("/");
508
+ if (slashIndex >= 0) {
509
+ const query = textBefore.slice(slashIndex + 1).toLowerCase();
510
+ const filtered = commands.filter(
511
+ (cmd) => cmd.title.toLowerCase().includes(query) || cmd.description.toLowerCase().includes(query)
512
+ );
513
+ if (filtered.length > 0) {
514
+ const coords = view.coordsAtPos(from);
515
+ updateState({
516
+ isOpen: true,
517
+ query,
518
+ position: { top: coords.bottom + 4, left: coords.left },
519
+ items: filtered,
520
+ selectedIndex: 0
521
+ });
522
+ } else {
523
+ updateState({ isOpen: false, query: "", selectedIndex: 0 });
524
+ }
525
+ } else if (state.isOpen) {
526
+ updateState({ isOpen: false, query: "", selectedIndex: 0 });
527
+ }
528
+ return false;
529
+ }
530
+ }
531
+ })
532
+ ];
533
+ }
534
+ });
535
+ function BlogEditor({
536
+ content,
537
+ onChange,
538
+ onSave,
539
+ uploadImage,
540
+ onBrowseMedia,
541
+ placeholder = 'Start writing your post... Type "/" for commands',
542
+ autosaveInterval = 3e4,
543
+ className = ""
544
+ }) {
545
+ const [slashState, setSlashState] = useState({
546
+ isOpen: false,
547
+ query: "",
548
+ position: null,
549
+ selectedIndex: 0,
550
+ items: []
551
+ });
552
+ const [wordCount, setWordCount] = useState(0);
553
+ const [isSaving, setIsSaving] = useState(false);
554
+ const autosaveTimerRef = useRef(null);
555
+ const lastSavedRef = useRef("");
556
+ const defaultUpload = useCallback(async (file) => {
557
+ if (!uploadImage) {
558
+ console.warn("[Next BlogPanel] No uploadImage handler provided. Using blob URL \u2014 image will not persist across page reloads. Pass an uploadImage handler (e.g. one that POSTs to your /media API route) to persist images via GridFS or Cloudflare R2.");
559
+ return { url: URL.createObjectURL(file), alt: file.name };
560
+ }
561
+ return uploadImage(file);
562
+ }, [uploadImage]);
563
+ const slashCommands = React.useMemo(() => {
564
+ if (!onBrowseMedia) return defaultSlashCommands;
565
+ const imageIndex = defaultSlashCommands.findIndex((c) => c.title === "Image");
566
+ const mediaItem = {
567
+ title: "Media Library",
568
+ description: "Choose from uploaded images",
569
+ icon: "\u{1F4C1}",
570
+ command: (editor2) => {
571
+ onBrowseMedia().then((result) => {
572
+ if (result) {
573
+ editor2.chain().focus().setImage({ src: result.url, alt: result.alt || "" }).run();
574
+ }
575
+ }).catch((err) => {
576
+ console.error("[Next BlogPanel] onBrowseMedia failed:", err);
577
+ });
578
+ }
579
+ };
580
+ const cmds = [...defaultSlashCommands];
581
+ cmds.splice(imageIndex + 1, 0, mediaItem);
582
+ return cmds;
583
+ }, [onBrowseMedia]);
584
+ const editor = useEditor({
585
+ extensions: [
586
+ StarterKit.configure({
587
+ codeBlock: false,
588
+ dropcursor: { color: "#2563eb", width: 2 }
589
+ }),
590
+ Placeholder.configure({ placeholder }),
591
+ Link.configure({ openOnClick: false, HTMLAttributes: { class: "nbp-link" } }),
592
+ Underline,
593
+ Highlight.configure({ multicolor: false }),
594
+ Typography,
595
+ TaskList,
596
+ TaskItem.configure({ nested: true }),
597
+ Table.configure({ resizable: true }),
598
+ TableRow,
599
+ TableCell,
600
+ TableHeader,
601
+ ImageUpload.configure({ uploadFn: defaultUpload }),
602
+ CodeBlockEnhanced,
603
+ Callout,
604
+ FAQ,
605
+ FAQItem,
606
+ FAQQuestion,
607
+ FAQAnswer,
608
+ TableOfContents,
609
+ SlashCommand.configure({
610
+ commands: slashCommands,
611
+ onStateChange: setSlashState
612
+ })
613
+ ],
614
+ content: content || { type: "doc", content: [{ type: "paragraph" }] },
615
+ onUpdate: ({ editor: editor2 }) => {
616
+ const json = editor2.getJSON();
617
+ onChange?.(json);
618
+ const text = editor2.getText();
619
+ setWordCount(text.trim() ? text.trim().split(/\s+/).length : 0);
620
+ },
621
+ editorProps: {
622
+ attributes: {
623
+ class: `nbp-editor-content ${className}`
624
+ }
625
+ }
626
+ });
627
+ useEffect(() => {
628
+ if (!onSave || !autosaveInterval || !editor) return;
629
+ autosaveTimerRef.current = setInterval(() => {
630
+ const json = JSON.stringify(editor.getJSON());
631
+ if (json !== lastSavedRef.current) {
632
+ setIsSaving(true);
633
+ onSave(editor.getJSON());
634
+ lastSavedRef.current = json;
635
+ setTimeout(() => setIsSaving(false), 1e3);
636
+ }
637
+ }, autosaveInterval);
638
+ return () => {
639
+ if (autosaveTimerRef.current) clearInterval(autosaveTimerRef.current);
640
+ };
641
+ }, [editor, onSave, autosaveInterval]);
642
+ const readingTime = Math.max(1, Math.ceil(wordCount / 200));
643
+ return /* @__PURE__ */ jsxs("div", { className: "nbp-editor", children: [
644
+ editor && /* @__PURE__ */ jsxs("div", { className: "nbp-editor-toolbar", children: [
645
+ /* @__PURE__ */ jsxs("div", { className: "nbp-toolbar-group", children: [
646
+ /* @__PURE__ */ jsx(
647
+ "button",
648
+ {
649
+ onClick: () => editor.chain().focus().toggleBold().run(),
650
+ className: editor.isActive("bold") ? "nbp-toolbar-btn active" : "nbp-toolbar-btn",
651
+ title: "Bold",
652
+ children: /* @__PURE__ */ jsx("strong", { children: "B" })
653
+ }
654
+ ),
655
+ /* @__PURE__ */ jsx(
656
+ "button",
657
+ {
658
+ onClick: () => editor.chain().focus().toggleItalic().run(),
659
+ className: editor.isActive("italic") ? "nbp-toolbar-btn active" : "nbp-toolbar-btn",
660
+ title: "Italic",
661
+ children: /* @__PURE__ */ jsx("em", { children: "I" })
662
+ }
663
+ ),
664
+ /* @__PURE__ */ jsx(
665
+ "button",
666
+ {
667
+ onClick: () => editor.chain().focus().toggleUnderline().run(),
668
+ className: editor.isActive("underline") ? "nbp-toolbar-btn active" : "nbp-toolbar-btn",
669
+ title: "Underline",
670
+ children: /* @__PURE__ */ jsx("u", { children: "U" })
671
+ }
672
+ ),
673
+ /* @__PURE__ */ jsx(
674
+ "button",
675
+ {
676
+ onClick: () => editor.chain().focus().toggleStrike().run(),
677
+ className: editor.isActive("strike") ? "nbp-toolbar-btn active" : "nbp-toolbar-btn",
678
+ title: "Strikethrough",
679
+ children: /* @__PURE__ */ jsx("s", { children: "S" })
680
+ }
681
+ ),
682
+ /* @__PURE__ */ jsx(
683
+ "button",
684
+ {
685
+ onClick: () => editor.chain().focus().toggleCode().run(),
686
+ className: editor.isActive("code") ? "nbp-toolbar-btn active" : "nbp-toolbar-btn",
687
+ title: "Inline Code",
688
+ children: "</>"
689
+ }
690
+ ),
691
+ /* @__PURE__ */ jsx(
692
+ "button",
693
+ {
694
+ onClick: () => editor.chain().focus().toggleHighlight().run(),
695
+ className: editor.isActive("highlight") ? "nbp-toolbar-btn active" : "nbp-toolbar-btn",
696
+ title: "Highlight",
697
+ children: "H"
698
+ }
699
+ )
700
+ ] }),
701
+ /* @__PURE__ */ jsx("div", { className: "nbp-toolbar-divider" }),
702
+ /* @__PURE__ */ jsx("div", { className: "nbp-toolbar-group", children: [2, 3, 4].map((level) => /* @__PURE__ */ jsxs(
703
+ "button",
704
+ {
705
+ onClick: () => editor.chain().focus().toggleHeading({ level }).run(),
706
+ className: editor.isActive("heading", { level }) ? "nbp-toolbar-btn active" : "nbp-toolbar-btn",
707
+ title: `Heading ${level}`,
708
+ children: [
709
+ "H",
710
+ level
711
+ ]
712
+ },
713
+ level
714
+ )) }),
715
+ /* @__PURE__ */ jsx("div", { className: "nbp-toolbar-divider" }),
716
+ /* @__PURE__ */ jsxs("div", { className: "nbp-toolbar-group", children: [
717
+ /* @__PURE__ */ jsx(
718
+ "button",
719
+ {
720
+ onClick: () => editor.chain().focus().toggleBulletList().run(),
721
+ className: editor.isActive("bulletList") ? "nbp-toolbar-btn active" : "nbp-toolbar-btn",
722
+ title: "Bullet List",
723
+ children: "\u2022"
724
+ }
725
+ ),
726
+ /* @__PURE__ */ jsx(
727
+ "button",
728
+ {
729
+ onClick: () => editor.chain().focus().toggleOrderedList().run(),
730
+ className: editor.isActive("orderedList") ? "nbp-toolbar-btn active" : "nbp-toolbar-btn",
731
+ title: "Numbered List",
732
+ children: "1."
733
+ }
734
+ ),
735
+ /* @__PURE__ */ jsx(
736
+ "button",
737
+ {
738
+ onClick: () => editor.chain().focus().toggleTaskList().run(),
739
+ className: editor.isActive("taskList") ? "nbp-toolbar-btn active" : "nbp-toolbar-btn",
740
+ title: "Task List",
741
+ children: "\u2611"
742
+ }
743
+ )
744
+ ] }),
745
+ /* @__PURE__ */ jsx("div", { className: "nbp-toolbar-divider" }),
746
+ /* @__PURE__ */ jsxs("div", { className: "nbp-toolbar-group", children: [
747
+ /* @__PURE__ */ jsx(
748
+ "button",
749
+ {
750
+ onClick: () => editor.chain().focus().toggleBlockquote().run(),
751
+ className: editor.isActive("blockquote") ? "nbp-toolbar-btn active" : "nbp-toolbar-btn",
752
+ title: "Quote",
753
+ children: "\u201C"
754
+ }
755
+ ),
756
+ /* @__PURE__ */ jsx(
757
+ "button",
758
+ {
759
+ onClick: () => editor.chain().focus().toggleCodeBlock().run(),
760
+ className: editor.isActive("codeBlock") ? "nbp-toolbar-btn active" : "nbp-toolbar-btn",
761
+ title: "Code Block",
762
+ children: "{ }"
763
+ }
764
+ ),
765
+ /* @__PURE__ */ jsx(
766
+ "button",
767
+ {
768
+ onClick: () => editor.chain().focus().setHorizontalRule().run(),
769
+ className: "nbp-toolbar-btn",
770
+ title: "Divider",
771
+ children: "\u2014"
772
+ }
773
+ ),
774
+ /* @__PURE__ */ jsx(
775
+ "button",
776
+ {
777
+ onClick: () => editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(),
778
+ className: "nbp-toolbar-btn",
779
+ title: "Table",
780
+ children: "\u229E"
781
+ }
782
+ )
783
+ ] }),
784
+ /* @__PURE__ */ jsx("div", { className: "nbp-toolbar-divider" }),
785
+ /* @__PURE__ */ jsxs("div", { className: "nbp-toolbar-group", children: [
786
+ /* @__PURE__ */ jsx(
787
+ "button",
788
+ {
789
+ onClick: () => {
790
+ const url = window.prompt("Enter URL");
791
+ if (url) editor.chain().focus().setLink({ href: url }).run();
792
+ },
793
+ className: editor.isActive("link") ? "nbp-toolbar-btn active" : "nbp-toolbar-btn",
794
+ title: "Link",
795
+ children: "\u{1F517}"
796
+ }
797
+ ),
798
+ /* @__PURE__ */ jsx(
799
+ "button",
800
+ {
801
+ onClick: () => {
802
+ const input = document.createElement("input");
803
+ input.type = "file";
804
+ input.accept = "image/*";
805
+ input.onchange = () => {
806
+ const file = input.files?.[0];
807
+ if (file) editor.commands.uploadImage(file);
808
+ };
809
+ input.click();
810
+ },
811
+ className: "nbp-toolbar-btn",
812
+ title: "Upload Image",
813
+ children: "\u{1F4F7}"
814
+ }
815
+ ),
816
+ onBrowseMedia && /* @__PURE__ */ jsx(
817
+ "button",
818
+ {
819
+ onClick: async () => {
820
+ const result = await onBrowseMedia();
821
+ if (result && editor) {
822
+ editor.chain().focus().setImage({ src: result.url, alt: result.alt || "" }).run();
823
+ }
824
+ },
825
+ className: "nbp-toolbar-btn",
826
+ title: "Choose from Media Library",
827
+ children: "\u{1F5BC}"
828
+ }
829
+ )
830
+ ] })
831
+ ] }),
832
+ editor && /* @__PURE__ */ jsx(BubbleMenu, { editor, tippyOptions: { duration: 100 }, children: /* @__PURE__ */ jsxs("div", { className: "nbp-bubble-menu", children: [
833
+ /* @__PURE__ */ jsx("button", { onClick: () => editor.chain().focus().toggleBold().run(), className: editor.isActive("bold") ? "active" : "", children: "B" }),
834
+ /* @__PURE__ */ jsx("button", { onClick: () => editor.chain().focus().toggleItalic().run(), className: editor.isActive("italic") ? "active" : "", children: "I" }),
835
+ /* @__PURE__ */ jsx("button", { onClick: () => editor.chain().focus().toggleCode().run(), className: editor.isActive("code") ? "active" : "", children: "</>" }),
836
+ /* @__PURE__ */ jsx(
837
+ "button",
838
+ {
839
+ onClick: () => {
840
+ const url = window.prompt("Enter URL");
841
+ if (url) editor.chain().focus().setLink({ href: url }).run();
842
+ },
843
+ className: editor.isActive("link") ? "active" : "",
844
+ children: "Link"
845
+ }
846
+ )
847
+ ] }) }),
848
+ /* @__PURE__ */ jsx(EditorContent, { editor }),
849
+ slashState.isOpen && slashState.position && /* @__PURE__ */ jsx(
850
+ "div",
851
+ {
852
+ className: "nbp-slash-menu",
853
+ style: {
854
+ position: "fixed",
855
+ top: slashState.position.top,
856
+ left: slashState.position.left
857
+ },
858
+ children: slashState.items.map((item, index) => /* @__PURE__ */ jsxs(
859
+ "button",
860
+ {
861
+ className: `nbp-slash-item ${index === slashState.selectedIndex ? "selected" : ""}`,
862
+ onMouseDown: (e) => {
863
+ e.preventDefault();
864
+ editor.storage.slashCommand.deleteSlashAndRun(item);
865
+ },
866
+ children: [
867
+ /* @__PURE__ */ jsx("span", { className: "nbp-slash-icon", children: item.icon }),
868
+ /* @__PURE__ */ jsxs("div", { children: [
869
+ /* @__PURE__ */ jsx("div", { className: "nbp-slash-title", children: item.title }),
870
+ /* @__PURE__ */ jsx("div", { className: "nbp-slash-desc", children: item.description })
871
+ ] })
872
+ ]
873
+ },
874
+ item.title
875
+ ))
876
+ }
877
+ ),
878
+ /* @__PURE__ */ jsxs("div", { className: "nbp-editor-status", children: [
879
+ /* @__PURE__ */ jsxs("span", { children: [
880
+ wordCount,
881
+ " words"
882
+ ] }),
883
+ /* @__PURE__ */ jsxs("span", { children: [
884
+ readingTime,
885
+ " min read"
886
+ ] }),
887
+ isSaving && /* @__PURE__ */ jsx("span", { className: "nbp-saving", children: "Saving..." })
888
+ ] })
889
+ ] });
890
+ }
891
+
892
+ // src/editor/renderer.ts
893
+ function renderBlocksToHTML(doc) {
894
+ if (!doc.content) return "";
895
+ return doc.content.map(renderNode).join("");
896
+ }
897
+ function renderNode(node) {
898
+ switch (node.type) {
899
+ case "paragraph":
900
+ return `<p>${renderInline(node)}</p>`;
901
+ case "heading": {
902
+ const level = node.attrs?.level || 2;
903
+ const text = renderInline(node);
904
+ const id = slugify(stripTags(text));
905
+ return `<h${level} id="${id}">${text}</h${level}>`;
906
+ }
907
+ case "bulletList":
908
+ return `<ul>${renderChildren(node)}</ul>`;
909
+ case "orderedList":
910
+ return `<ol>${renderChildren(node)}</ol>`;
911
+ case "listItem":
912
+ return `<li>${renderChildren(node)}</li>`;
913
+ case "taskList":
914
+ return `<ul class="nbp-task-list">${renderChildren(node)}</ul>`;
915
+ case "taskItem": {
916
+ const checked = node.attrs?.checked ? "checked" : "";
917
+ return `<li class="nbp-task-item" data-checked="${checked}"><input type="checkbox" ${checked} disabled />${renderChildren(node)}</li>`;
918
+ }
919
+ case "blockquote":
920
+ return `<blockquote>${renderChildren(node)}</blockquote>`;
921
+ case "codeBlock": {
922
+ const lang = node.attrs?.language || "plaintext";
923
+ const filename = node.attrs?.filename;
924
+ const code = escapeHtml(getTextContent(node));
925
+ const header = filename ? `<div class="nbp-code-header">${escapeHtml(filename)}</div>` : "";
926
+ return `${header}<pre><code class="language-${lang}">${code}</code></pre>`;
927
+ }
928
+ case "image": {
929
+ const src = node.attrs?.src || "";
930
+ const alt = node.attrs?.alt || "";
931
+ const caption = node.attrs?.caption;
932
+ const width = node.attrs?.width;
933
+ const height = node.attrs?.height;
934
+ let img = `<img src="${escapeAttr(src)}" alt="${escapeAttr(alt)}"`;
935
+ if (width) img += ` width="${width}"`;
936
+ if (height) img += ` height="${height}"`;
937
+ img += ' loading="lazy" />';
938
+ if (caption) {
939
+ return `<figure>${img}<figcaption>${escapeHtml(caption)}</figcaption></figure>`;
940
+ }
941
+ return img;
942
+ }
943
+ case "horizontalRule":
944
+ return "<hr />";
945
+ case "table":
946
+ return `<table>${renderChildren(node)}</table>`;
947
+ case "tableRow":
948
+ return `<tr>${renderChildren(node)}</tr>`;
949
+ case "tableHeader":
950
+ return `<th>${renderInline(node)}</th>`;
951
+ case "tableCell":
952
+ return `<td>${renderInline(node)}</td>`;
953
+ case "callout": {
954
+ const calloutType = node.attrs?.type || "info";
955
+ const icons = {
956
+ info: "\u2139\uFE0F",
957
+ warning: "\u26A0\uFE0F",
958
+ tip: "\u{1F4A1}",
959
+ danger: "\u{1F6A8}"
960
+ };
961
+ return `<div class="nbp-callout nbp-callout-${calloutType}"><span class="nbp-callout-icon">${icons[calloutType] || ""}</span><div class="nbp-callout-content">${renderChildren(node)}</div></div>`;
962
+ }
963
+ case "faq":
964
+ return `<div class="nbp-faq" itemscope itemtype="https://schema.org/FAQPage">${renderChildren(node)}</div>`;
965
+ case "faqItem":
966
+ return `<div class="nbp-faq-item" itemscope itemprop="mainEntity" itemtype="https://schema.org/Question">${renderChildren(node)}</div>`;
967
+ case "faqQuestion":
968
+ return `<h3 itemprop="name">${renderInline(node)}</h3>`;
969
+ case "faqAnswer":
970
+ return `<div itemprop="acceptedAnswer" itemscope itemtype="https://schema.org/Answer"><div itemprop="text">${renderChildren(node)}</div></div>`;
971
+ case "tableOfContents":
972
+ return '<div data-toc="true" class="nbp-toc"></div>';
973
+ case "html":
974
+ return getTextContent(node);
975
+ case "embed": {
976
+ const embedUrl = node.attrs?.src || "";
977
+ return `<div class="nbp-embed"><iframe src="${escapeAttr(embedUrl)}" frameborder="0" allowfullscreen loading="lazy"></iframe></div>`;
978
+ }
979
+ case "text":
980
+ return renderTextNode(node);
981
+ case "hardBreak":
982
+ return "<br />";
983
+ default:
984
+ if (node.content) return renderChildren(node);
985
+ if (node.text) return escapeHtml(node.text);
986
+ return "";
987
+ }
988
+ }
989
+ function renderChildren(node) {
990
+ if (!node.content) return "";
991
+ return node.content.map(renderNode).join("");
992
+ }
993
+ function renderInline(node) {
994
+ if (!node.content) return "";
995
+ return node.content.map(renderNode).join("");
996
+ }
997
+ function renderTextNode(node) {
998
+ let text = escapeHtml(node.text || "");
999
+ if (node.marks) {
1000
+ for (const mark of node.marks) {
1001
+ switch (mark.type) {
1002
+ case "bold":
1003
+ text = `<strong>${text}</strong>`;
1004
+ break;
1005
+ case "italic":
1006
+ text = `<em>${text}</em>`;
1007
+ break;
1008
+ case "strike":
1009
+ text = `<s>${text}</s>`;
1010
+ break;
1011
+ case "code":
1012
+ text = `<code>${text}</code>`;
1013
+ break;
1014
+ case "underline":
1015
+ text = `<u>${text}</u>`;
1016
+ break;
1017
+ case "highlight":
1018
+ text = `<mark>${text}</mark>`;
1019
+ break;
1020
+ case "link": {
1021
+ const href = mark.attrs?.href || "";
1022
+ const target = href.startsWith("http") ? ' target="_blank" rel="noopener noreferrer"' : "";
1023
+ text = `<a href="${escapeAttr(href)}"${target}>${text}</a>`;
1024
+ break;
1025
+ }
1026
+ }
1027
+ }
1028
+ }
1029
+ return text;
1030
+ }
1031
+ function getTextContent(node) {
1032
+ if (node.text) return node.text;
1033
+ if (!node.content) return "";
1034
+ return node.content.map(getTextContent).join("");
1035
+ }
1036
+ function escapeHtml(str) {
1037
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1038
+ }
1039
+ function escapeAttr(str) {
1040
+ return str.replace(/"/g, "&quot;").replace(/&/g, "&amp;");
1041
+ }
1042
+ function stripTags(html) {
1043
+ return html.replace(/<[^>]+>/g, "");
1044
+ }
1045
+ function slugify(text) {
1046
+ return text.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_]+/g, "-").replace(/-+/g, "-");
1047
+ }
1048
+ function extractHeadings(doc) {
1049
+ const headings = [];
1050
+ function walk(node) {
1051
+ if (node.type === "heading") {
1052
+ const text = getTextContent(node);
1053
+ headings.push({
1054
+ id: slugify(text),
1055
+ text,
1056
+ level: node.attrs?.level || 2
1057
+ });
1058
+ }
1059
+ if (node.content) {
1060
+ node.content.forEach(walk);
1061
+ }
1062
+ }
1063
+ if (doc.content) {
1064
+ doc.content.forEach(walk);
1065
+ }
1066
+ return headings;
1067
+ }
1068
+ function extractFAQItems(doc) {
1069
+ const items = [];
1070
+ function walk(node) {
1071
+ if (node.type === "faqItem" && node.content) {
1072
+ const question = node.content.find((c) => c.type === "faqQuestion");
1073
+ const answer = node.content.find((c) => c.type === "faqAnswer");
1074
+ if (question && answer) {
1075
+ items.push({
1076
+ question: getTextContent(question),
1077
+ answer: renderChildren(answer)
1078
+ });
1079
+ }
1080
+ }
1081
+ if (node.content) {
1082
+ node.content.forEach(walk);
1083
+ }
1084
+ }
1085
+ if (doc.content) {
1086
+ doc.content.forEach(walk);
1087
+ }
1088
+ return items;
1089
+ }
1090
+
1091
+ export { BlogEditor, Callout, CodeBlockEnhanced, FAQ, FAQAnswer, FAQItem, FAQQuestion, ImageUpload, SUPPORTED_LANGUAGES, SlashCommand, TableOfContents, defaultSlashCommands, extractFAQItems, extractHeadings, renderBlocksToHTML };
1092
+ //# sourceMappingURL=index.js.map
1093
+ //# sourceMappingURL=index.js.map