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