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