@frontlit/text-editor 0.1.0-alpha.2 → 0.1.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,14 +1,14 @@
1
1
  "use client";
2
- import React5, { createContext, useState, useRef, useMemo, useEffect, useCallback, useContext } from 'react';
3
- import { Node, getSchema, generateText } from '@tiptap/core';
4
- import { useEditor, EditorContent } from '@tiptap/react';
2
+ import React5, { createContext, useState, useRef, useMemo, useEffect, useCallback, useContext, useId } from 'react';
3
+ import { Node, Extension, getSchema, generateText } from '@tiptap/core';
4
+ import { useEditor, EditorContent, ReactNodeViewRenderer, NodeViewWrapper, NodeViewContent } from '@tiptap/react';
5
5
  import StarterKit from '@tiptap/starter-kit';
6
6
  import Placeholder from '@tiptap/extension-placeholder';
7
7
  import Link from '@tiptap/extension-link';
8
8
  import { TableRow, Table, TableCell, TableHeader } from '@tiptap/extension-table';
9
9
  import Image from '@tiptap/extension-image';
10
10
  import Dropcursor from '@tiptap/extension-dropcursor';
11
- import Gapcursor from '@tiptap/extension-gapcursor';
11
+ import Gapcursor$1 from '@tiptap/extension-gapcursor';
12
12
  import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight';
13
13
  import Heading from '@tiptap/extension-heading';
14
14
  import Highlight from '@tiptap/extension-highlight';
@@ -18,12 +18,17 @@ import typescript from 'highlight.js/lib/languages/typescript';
18
18
  import json from 'highlight.js/lib/languages/json';
19
19
  import css from 'highlight.js/lib/languages/css';
20
20
  import xml from 'highlight.js/lib/languages/xml';
21
- import { Undo2, Redo2, ChevronDown, Bold, Italic, Underline, Code, Link as Link$1, List, ListOrdered, Quote, Plus, Minus, Table as Table$1, Image as Image$1, Trash2, Strikethrough, X, Check, Copy } from 'lucide-react';
21
+ import Code from '@tiptap/extension-code';
22
+ import { Undo2, Redo2, ChevronDown, Bold, Italic, Underline, Code as Code$1, Link as Link$1, List, ListOrdered, Quote, Plus, Minus, Table as Table$1, Image as Image$1, Trash2, Strikethrough, X, Check, OctagonAlert, CircleAlert, Info, Lightbulb, StickyNote, Copy } from 'lucide-react';
22
23
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
24
+ import { Fragment as Fragment$1 } from '@tiptap/pm/model';
23
25
  import { BubbleMenuPlugin } from '@tiptap/extension-bubble-menu';
24
26
  import { clsx } from 'clsx';
25
27
  import { twMerge } from 'tailwind-merge';
28
+ import CodeBlock from '@tiptap/extension-code-block';
29
+ import { Gapcursor, TrailingNode } from '@tiptap/extensions';
26
30
  import { renderToReactElement } from '@tiptap/static-renderer';
31
+ import { DynamicIcon } from 'lucide-react/dynamic';
27
32
 
28
33
  // src/editor.tsx
29
34
 
@@ -58,6 +63,92 @@ var CodeMirrorNode = Node.create({
58
63
  return ["pre", HTMLAttributes, 0];
59
64
  }
60
65
  });
66
+ var CALLOUT_TYPES = ["note", "tip", "info", "warning", "danger"];
67
+ function attr(name, fallback = null) {
68
+ return {
69
+ [name]: {
70
+ default: fallback,
71
+ parseHTML: (element) => {
72
+ const value = element.getAttribute(`data-${name}`);
73
+ if (value === null) return fallback;
74
+ try {
75
+ return JSON.parse(value);
76
+ } catch {
77
+ return fallback;
78
+ }
79
+ },
80
+ renderHTML: (attributes) => attributes[name] === null || attributes[name] === void 0 ? {} : { [`data-${name}`]: JSON.stringify(attributes[name]) }
81
+ }
82
+ };
83
+ }
84
+ function block(name, spec) {
85
+ const tag = `div[data-docs-block="${name}"]`;
86
+ return Node.create({
87
+ name,
88
+ group: spec.group,
89
+ content: spec.content,
90
+ atom: spec.atom,
91
+ defining: true,
92
+ isolating: true,
93
+ addAttributes() {
94
+ return Object.assign(
95
+ {},
96
+ ...Object.entries(spec.attrs ?? {}).map(([key, value]) => attr(key, value))
97
+ );
98
+ },
99
+ parseHTML() {
100
+ return [{ tag }];
101
+ },
102
+ renderHTML({ HTMLAttributes }) {
103
+ return spec.atom ? ["div", { ...HTMLAttributes, "data-docs-block": name }] : ["div", { ...HTMLAttributes, "data-docs-block": name }, 0];
104
+ }
105
+ });
106
+ }
107
+ var DocsCode = Code.extend({ excludes: "" });
108
+ function createDocsBlockExtensions() {
109
+ return [
110
+ DocsCode,
111
+ block("callout", {
112
+ group: "block",
113
+ content: "block+",
114
+ attrs: { type: null, title: null }
115
+ }),
116
+ block("tabs", { group: "block", content: "tab+" }),
117
+ block("tab", { content: "block+", attrs: { label: "" } }),
118
+ block("steps", { group: "block", content: "step+" }),
119
+ block("step", { content: "block+" }),
120
+ block("cards", { group: "block", content: "card+" }),
121
+ block("card", {
122
+ content: "block*",
123
+ attrs: { title: "", href: null, icon: null }
124
+ }),
125
+ block("accordions", {
126
+ group: "block",
127
+ content: "accordion+",
128
+ attrs: { type: null }
129
+ }),
130
+ block("accordion", { content: "block+", attrs: { title: "" } }),
131
+ block("codeGroup", { group: "block", content: "codeBlock+" }),
132
+ block("typeTable", { group: "block", atom: true, attrs: { rows: [] } })
133
+ ];
134
+ }
135
+ var CodeBlockTitle = Extension.create({
136
+ name: "codeBlockTitle",
137
+ addGlobalAttributes() {
138
+ return [
139
+ {
140
+ types: ["codeBlock"],
141
+ attributes: {
142
+ title: {
143
+ default: null,
144
+ parseHTML: (element) => element.getAttribute("data-title"),
145
+ renderHTML: (attributes) => attributes.title ? { "data-title": attributes.title } : {}
146
+ }
147
+ }
148
+ }
149
+ ];
150
+ }
151
+ });
61
152
 
62
153
  // src/extensions.ts
63
154
  lowlight.registerLanguage("javascript", javascript);
@@ -69,6 +160,7 @@ var createExtensions = ({
69
160
  placeholder
70
161
  } = {}) => [
71
162
  StarterKit.configure({
163
+ code: false,
72
164
  codeBlock: false,
73
165
  // Configured/replaced explicitly below (custom options or a
74
166
  // `.extend()`) — disabled here to avoid tiptap's duplicate-extension
@@ -109,7 +201,7 @@ var createExtensions = ({
109
201
  color: "hsl(var(--foreground))",
110
202
  width: 2
111
203
  }),
112
- Gapcursor,
204
+ Gapcursor$1,
113
205
  CodeBlockLowlight.configure({
114
206
  lowlight
115
207
  }),
@@ -128,7 +220,9 @@ var createExtensions = ({
128
220
  }
129
221
  }),
130
222
  Highlight,
131
- CodeMirrorNode
223
+ CodeMirrorNode,
224
+ CodeBlockTitle,
225
+ ...createDocsBlockExtensions()
132
226
  ];
133
227
  var LinkEditorPopover = ({
134
228
  editor,
@@ -296,6 +390,394 @@ var LinkEditorPopover = ({
296
390
  );
297
391
  };
298
392
  var link_editor_popover_default = LinkEditorPopover;
393
+ var input = "h-7 min-w-0 rounded border border-input bg-card px-2 text-xs text-foreground placeholder:text-muted-foreground";
394
+ var smallButton = "inline-flex h-7 items-center gap-1 rounded px-2 text-xs text-muted-foreground hover:bg-muted hover:text-foreground disabled:opacity-40";
395
+ var paragraph = (text = "") => text ? { type: "paragraph", content: [{ type: "text", text }] } : { type: "paragraph" };
396
+ var docsBlockTemplates = {
397
+ callout: {
398
+ label: "Callout",
399
+ content: { type: "callout", attrs: { type: "info" }, content: [paragraph()] }
400
+ },
401
+ tabs: {
402
+ label: "Tabs",
403
+ content: {
404
+ type: "tabs",
405
+ content: ["Tab 1", "Tab 2"].map((label) => ({
406
+ type: "tab",
407
+ attrs: { label },
408
+ content: [paragraph()]
409
+ }))
410
+ }
411
+ },
412
+ steps: {
413
+ label: "Steps",
414
+ content: {
415
+ type: "steps",
416
+ content: [
417
+ {
418
+ type: "step",
419
+ content: [{ type: "heading", attrs: { level: 3 } }, paragraph()]
420
+ }
421
+ ]
422
+ }
423
+ },
424
+ cards: {
425
+ label: "Cards",
426
+ content: { type: "cards", content: [{ type: "card", attrs: { title: "Card" } }] }
427
+ },
428
+ accordions: {
429
+ label: "Accordion",
430
+ content: {
431
+ type: "accordions",
432
+ content: [
433
+ { type: "accordion", attrs: { title: "Question" }, content: [paragraph()] }
434
+ ]
435
+ }
436
+ },
437
+ codeGroup: {
438
+ label: "Code group",
439
+ content: {
440
+ type: "codeGroup",
441
+ content: [
442
+ { type: "codeBlock", attrs: { language: "bash", title: "Terminal" } },
443
+ { type: "codeBlock", attrs: { language: "ts", title: "index.ts" } }
444
+ ]
445
+ }
446
+ },
447
+ typeTable: {
448
+ label: "Type table",
449
+ content: {
450
+ type: "typeTable",
451
+ attrs: {
452
+ rows: [
453
+ {
454
+ name: "name",
455
+ type: "string",
456
+ default: null,
457
+ description: null,
458
+ required: false
459
+ }
460
+ ]
461
+ }
462
+ }
463
+ }
464
+ };
465
+ var childTemplates = {
466
+ tabs: (count) => ({
467
+ type: "tab",
468
+ attrs: { label: `Tab ${count + 1}` },
469
+ content: [paragraph()]
470
+ }),
471
+ steps: () => ({
472
+ type: "step",
473
+ content: [{ type: "heading", attrs: { level: 3 } }, paragraph()]
474
+ }),
475
+ cards: () => ({ type: "card", attrs: { title: "Card" } }),
476
+ accordions: () => ({
477
+ type: "accordion",
478
+ attrs: { title: "Question" },
479
+ content: [paragraph()]
480
+ }),
481
+ codeGroup: () => ({ type: "codeBlock", attrs: { language: null, title: "Code" } })
482
+ };
483
+ var childLabels = {
484
+ tabs: "Add tab",
485
+ steps: "Add step",
486
+ cards: "Add card",
487
+ accordions: "Add item",
488
+ codeGroup: "Add code block"
489
+ };
490
+ function BlockFrame({
491
+ label,
492
+ props,
493
+ children,
494
+ fields
495
+ }) {
496
+ const { node, editor, getPos, deleteNode } = props;
497
+ const add = childTemplates[node.type.name];
498
+ return /* @__PURE__ */ jsxs(NodeViewWrapper, { className: "te-block", "data-block": node.type.name, children: [
499
+ /* @__PURE__ */ jsxs("div", { className: "te-block__bar", contentEditable: false, children: [
500
+ /* @__PURE__ */ jsx("span", { className: "te-block__label", children: label }),
501
+ fields,
502
+ /* @__PURE__ */ jsxs("span", { className: "ml-auto flex items-center gap-1", children: [
503
+ add && editor.isEditable && /* @__PURE__ */ jsxs(
504
+ "button",
505
+ {
506
+ type: "button",
507
+ className: smallButton,
508
+ onClick: () => {
509
+ const pos = getPos();
510
+ const current = typeof pos === "number" ? editor.state.doc.nodeAt(pos) : null;
511
+ if (typeof pos !== "number" || current?.type !== node.type) return;
512
+ const child = editor.schema.nodeFromJSON(add(current.childCount));
513
+ const end = current.childCount;
514
+ if (!current.canReplace(end, end, Fragment$1.from(child))) return;
515
+ const at = pos + current.nodeSize - 1;
516
+ editor.view.dispatch(
517
+ editor.state.tr.insert(at, child).scrollIntoView()
518
+ );
519
+ editor.view.focus();
520
+ },
521
+ children: [
522
+ /* @__PURE__ */ jsx(Plus, { className: "size-3.5" }),
523
+ childLabels[node.type.name]
524
+ ]
525
+ }
526
+ ),
527
+ editor.isEditable && /* @__PURE__ */ jsx(
528
+ "button",
529
+ {
530
+ type: "button",
531
+ className: smallButton,
532
+ "aria-label": `Remove ${label.toLowerCase()}`,
533
+ title: `Remove ${label.toLowerCase()}`,
534
+ onClick: deleteNode,
535
+ children: /* @__PURE__ */ jsx(Trash2, { className: "size-3.5" })
536
+ }
537
+ )
538
+ ] })
539
+ ] }),
540
+ children ?? /* @__PURE__ */ jsx(NodeViewContent, { className: "te-block__content" })
541
+ ] });
542
+ }
543
+ function ItemFrame({
544
+ props,
545
+ fields
546
+ }) {
547
+ const { editor, getPos, deleteNode, node } = props;
548
+ const pos = getPos();
549
+ const parent = typeof pos === "number" ? editor.state.doc.resolve(pos).parent : null;
550
+ return /* @__PURE__ */ jsxs(NodeViewWrapper, { className: "te-block-item", "data-block": node.type.name, children: [
551
+ (fields || (parent?.childCount ?? 0) > 1) && /* @__PURE__ */ jsxs("div", { className: "te-block__bar", contentEditable: false, children: [
552
+ fields,
553
+ (parent?.childCount ?? 0) > 1 && editor.isEditable && /* @__PURE__ */ jsx(
554
+ "button",
555
+ {
556
+ type: "button",
557
+ className: `${smallButton} ml-auto`,
558
+ "aria-label": "Remove item",
559
+ title: "Remove item",
560
+ onClick: deleteNode,
561
+ children: /* @__PURE__ */ jsx(X, { className: "size-3.5" })
562
+ }
563
+ )
564
+ ] }),
565
+ /* @__PURE__ */ jsx(NodeViewContent, { className: "te-block__content" })
566
+ ] });
567
+ }
568
+ function TextField({
569
+ props,
570
+ name,
571
+ placeholder,
572
+ nullable,
573
+ className = "w-40"
574
+ }) {
575
+ const value = props.node.attrs[name] ?? "";
576
+ return /* @__PURE__ */ jsx(
577
+ "input",
578
+ {
579
+ className: `${input} ${className}`,
580
+ "aria-label": placeholder,
581
+ placeholder,
582
+ value,
583
+ disabled: !props.editor.isEditable,
584
+ onChange: (event) => props.updateAttributes({
585
+ [name]: nullable && !event.target.value ? null : event.target.value
586
+ })
587
+ }
588
+ );
589
+ }
590
+ function CalloutView(props) {
591
+ return /* @__PURE__ */ jsx(
592
+ BlockFrame,
593
+ {
594
+ label: "Callout",
595
+ props,
596
+ fields: /* @__PURE__ */ jsxs(Fragment, { children: [
597
+ /* @__PURE__ */ jsx(
598
+ "select",
599
+ {
600
+ className: input,
601
+ "aria-label": "Callout type",
602
+ value: props.node.attrs.type ?? "info",
603
+ disabled: !props.editor.isEditable,
604
+ onChange: (event) => props.updateAttributes({ type: event.target.value }),
605
+ children: CALLOUT_TYPES.map((type) => /* @__PURE__ */ jsx("option", { value: type, children: type }, type))
606
+ }
607
+ ),
608
+ /* @__PURE__ */ jsx(
609
+ TextField,
610
+ {
611
+ props,
612
+ name: "title",
613
+ placeholder: "Title (optional)",
614
+ nullable: true
615
+ }
616
+ )
617
+ ] })
618
+ }
619
+ );
620
+ }
621
+ function CodeBlockView(props) {
622
+ return /* @__PURE__ */ jsxs(NodeViewWrapper, { className: "te-block-code", children: [
623
+ /* @__PURE__ */ jsxs("div", { className: "te-block__bar", contentEditable: false, children: [
624
+ /* @__PURE__ */ jsx(TextField, { props, name: "title", placeholder: "File name", nullable: true }),
625
+ /* @__PURE__ */ jsx(
626
+ TextField,
627
+ {
628
+ props,
629
+ name: "language",
630
+ placeholder: "Language",
631
+ nullable: true,
632
+ className: "w-24"
633
+ }
634
+ )
635
+ ] }),
636
+ /* @__PURE__ */ jsx("pre", { children: /* @__PURE__ */ jsx(NodeViewContent, { as: "code" }) })
637
+ ] });
638
+ }
639
+ function TypeTableView(props) {
640
+ const rows = props.node.attrs.rows ?? [];
641
+ const editable = props.editor.isEditable;
642
+ const update = (index, patch) => props.updateAttributes({
643
+ rows: rows.map((row, i) => i === index ? { ...row, ...patch } : row)
644
+ });
645
+ const text = (index, key) => /* @__PURE__ */ jsx(
646
+ "input",
647
+ {
648
+ className: `${input} w-full`,
649
+ "aria-label": key,
650
+ placeholder: key,
651
+ value: rows[index][key] ?? "",
652
+ disabled: !editable,
653
+ onChange: (event) => update(index, {
654
+ [key]: (key === "default" || key === "description") && !event.target.value ? null : event.target.value
655
+ })
656
+ }
657
+ );
658
+ return /* @__PURE__ */ jsx(BlockFrame, { label: "Type table", props, children: /* @__PURE__ */ jsxs("div", { contentEditable: false, className: "te-block__content", children: [
659
+ /* @__PURE__ */ jsxs("table", { className: "te-block-table", children: [
660
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
661
+ /* @__PURE__ */ jsx("th", { children: "Prop" }),
662
+ /* @__PURE__ */ jsx("th", { children: "Type" }),
663
+ /* @__PURE__ */ jsx("th", { children: "Default" }),
664
+ /* @__PURE__ */ jsx("th", { children: "Description" }),
665
+ /* @__PURE__ */ jsx("th", { children: "Required" }),
666
+ /* @__PURE__ */ jsx("th", {})
667
+ ] }) }),
668
+ /* @__PURE__ */ jsx("tbody", { children: rows.map((row, index) => (
669
+ // biome-ignore lint/suspicious/noArrayIndexKey: row names are edited in place.
670
+ /* @__PURE__ */ jsxs("tr", { children: [
671
+ /* @__PURE__ */ jsx("td", { children: text(index, "name") }),
672
+ /* @__PURE__ */ jsx("td", { children: text(index, "type") }),
673
+ /* @__PURE__ */ jsx("td", { children: text(index, "default") }),
674
+ /* @__PURE__ */ jsx("td", { children: text(index, "description") }),
675
+ /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx(
676
+ "input",
677
+ {
678
+ type: "checkbox",
679
+ "aria-label": "Required",
680
+ checked: row.required,
681
+ disabled: !editable,
682
+ onChange: (event) => update(index, { required: event.target.checked })
683
+ }
684
+ ) }),
685
+ /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx(
686
+ "button",
687
+ {
688
+ type: "button",
689
+ className: smallButton,
690
+ "aria-label": "Remove row",
691
+ disabled: !editable || rows.length === 1,
692
+ onClick: () => props.updateAttributes({
693
+ rows: rows.filter((_, i) => i !== index)
694
+ }),
695
+ children: /* @__PURE__ */ jsx(X, { className: "size-3.5" })
696
+ }
697
+ ) })
698
+ ] }, index)
699
+ )) })
700
+ ] }),
701
+ editable && /* @__PURE__ */ jsxs(
702
+ "button",
703
+ {
704
+ type: "button",
705
+ className: smallButton,
706
+ onClick: () => props.updateAttributes({
707
+ rows: [
708
+ ...rows,
709
+ {
710
+ name: `prop${rows.length + 1}`,
711
+ type: "string",
712
+ default: null,
713
+ description: null,
714
+ required: false
715
+ }
716
+ ]
717
+ }),
718
+ children: [
719
+ /* @__PURE__ */ jsx(Plus, { className: "size-3.5" }),
720
+ "Add row"
721
+ ]
722
+ }
723
+ )
724
+ ] }) });
725
+ }
726
+ var views = {
727
+ callout: CalloutView,
728
+ tabs: (props) => /* @__PURE__ */ jsx(BlockFrame, { label: "Tabs", props }),
729
+ tab: (props) => /* @__PURE__ */ jsx(
730
+ ItemFrame,
731
+ {
732
+ props,
733
+ fields: /* @__PURE__ */ jsx(TextField, { props, name: "label", placeholder: "Tab label" })
734
+ }
735
+ ),
736
+ steps: (props) => /* @__PURE__ */ jsx(BlockFrame, { label: "Steps", props }),
737
+ step: (props) => /* @__PURE__ */ jsx(ItemFrame, { props }),
738
+ cards: (props) => /* @__PURE__ */ jsx(BlockFrame, { label: "Cards", props }),
739
+ card: (props) => /* @__PURE__ */ jsx(
740
+ ItemFrame,
741
+ {
742
+ props,
743
+ fields: /* @__PURE__ */ jsxs(Fragment, { children: [
744
+ /* @__PURE__ */ jsx(TextField, { props, name: "title", placeholder: "Title" }),
745
+ /* @__PURE__ */ jsx(TextField, { props, name: "href", placeholder: "Link (/docs/\u2026)", nullable: true }),
746
+ /* @__PURE__ */ jsx(
747
+ TextField,
748
+ {
749
+ props,
750
+ name: "icon",
751
+ placeholder: "Icon (e.g. Rocket)",
752
+ nullable: true,
753
+ className: "w-32"
754
+ }
755
+ )
756
+ ] })
757
+ }
758
+ ),
759
+ accordions: (props) => /* @__PURE__ */ jsx(BlockFrame, { label: "Accordion", props }),
760
+ accordion: (props) => /* @__PURE__ */ jsx(
761
+ ItemFrame,
762
+ {
763
+ props,
764
+ fields: /* @__PURE__ */ jsx(TextField, { props, name: "title", placeholder: "Title" })
765
+ }
766
+ ),
767
+ codeGroup: (props) => /* @__PURE__ */ jsx(BlockFrame, { label: "Code group", props }),
768
+ typeTable: TypeTableView
769
+ };
770
+ function withDocsBlockViews(extensions, profile) {
771
+ return extensions.map((extension) => {
772
+ const view = views[extension.name] ?? (profile === "documentation" && extension.name === "codeBlock" ? CodeBlockView : void 0);
773
+ if (!view || !(extension instanceof Node)) return extension;
774
+ return extension.extend({
775
+ addNodeView() {
776
+ return ReactNodeViewRenderer(view);
777
+ }
778
+ });
779
+ });
780
+ }
299
781
  var iconButtonBase = "inline-flex h-8 w-8 items-center justify-center rounded border border-transparent text-foreground transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:cursor-not-allowed disabled:opacity-50";
300
782
  function IconButton({
301
783
  icon: Icon,
@@ -473,7 +955,7 @@ var Toolbar = ({
473
955
  /* @__PURE__ */ jsx(
474
956
  IconButton,
475
957
  {
476
- icon: Code,
958
+ icon: Code$1,
477
959
  label: "Inline code",
478
960
  active: editor.isActive("code"),
479
961
  onClick: () => editor.chain().focus().toggleCode().run()
@@ -563,7 +1045,7 @@ var Toolbar = ({
563
1045
  setActivePopover(null);
564
1046
  },
565
1047
  children: [
566
- /* @__PURE__ */ jsx(Code, { className: "size-4" }),
1048
+ /* @__PURE__ */ jsx(Code$1, { className: "size-4" }),
567
1049
  "Code block"
568
1050
  ]
569
1051
  }
@@ -601,6 +1083,22 @@ var Toolbar = ({
601
1083
  ]
602
1084
  }
603
1085
  ),
1086
+ profile === "documentation" && /* @__PURE__ */ jsxs(Fragment, { children: [
1087
+ /* @__PURE__ */ jsx("div", { className: "my-1 h-px bg-border" }),
1088
+ Object.entries(docsBlockTemplates).map(([name, block2]) => /* @__PURE__ */ jsx(
1089
+ "button",
1090
+ {
1091
+ type: "button",
1092
+ className: "flex w-full rounded px-2.5 py-2 text-sm hover:bg-muted",
1093
+ onClick: () => {
1094
+ editor.chain().focus().insertContent(block2.content).run();
1095
+ setActivePopover(null);
1096
+ },
1097
+ children: block2.label
1098
+ },
1099
+ name
1100
+ ))
1101
+ ] }),
604
1102
  editor.isActive("table") && /* @__PURE__ */ jsxs(Fragment, { children: [
605
1103
  /* @__PURE__ */ jsx("div", { className: "my-1 h-px bg-border" }),
606
1104
  /* @__PURE__ */ jsx(
@@ -797,7 +1295,7 @@ var InlineBubbleMenu = ({
797
1295
  /* @__PURE__ */ jsx(
798
1296
  InlineButton,
799
1297
  {
800
- icon: Code,
1298
+ icon: Code$1,
801
1299
  label: "Inline code",
802
1300
  active: editor.isActive("code"),
803
1301
  onClick: () => editor.chain().focus().toggleCode().run()
@@ -882,6 +1380,8 @@ function isSafeDocsUrl(value, image = false) {
882
1380
  function createDocsExtensions() {
883
1381
  return [
884
1382
  StarterKit.configure({
1383
+ code: false,
1384
+ codeBlock: false,
885
1385
  document: false,
886
1386
  underline: false,
887
1387
  link: false,
@@ -899,7 +1399,10 @@ function createDocsExtensions() {
899
1399
  Table.configure({ resizable: false }),
900
1400
  TableRow,
901
1401
  TableCell.extend({ content: "paragraph" }),
902
- TableHeader.extend({ content: "paragraph" })
1402
+ TableHeader.extend({ content: "paragraph" }),
1403
+ CodeBlock,
1404
+ CodeBlockTitle,
1405
+ ...createDocsBlockExtensions()
903
1406
  ];
904
1407
  }
905
1408
  getSchema(createDocsExtensions());
@@ -931,10 +1434,19 @@ var Editor = Object.assign(
931
1434
  const lastSerializedContentRef = useRef(JSON.stringify(initialContent ?? empty_doc_default));
932
1435
  const refreshRef = useRef(refresh);
933
1436
  const extensions = useMemo(
934
- () => profile === "documentation" ? [
935
- ...createDocsExtensions(),
936
- Placeholder.configure({ placeholder: placeholder || "Write something\u2026" })
937
- ] : createExtensions({ placeholder }),
1437
+ () => withDocsBlockViews(
1438
+ profile === "documentation" ? [
1439
+ ...createDocsExtensions(),
1440
+ Placeholder.configure({
1441
+ placeholder: placeholder || "Write something\u2026"
1442
+ }),
1443
+ // Editing only: lets authors type before or after a block that
1444
+ // starts or ends the doc. Empty paragraphs are not saved to Markdown.
1445
+ Gapcursor,
1446
+ TrailingNode
1447
+ ] : createExtensions({ placeholder }),
1448
+ profile ?? "default"
1449
+ ),
938
1450
  [placeholder, profile]
939
1451
  );
940
1452
  const changeTimeoutRef = useRef(null);
@@ -1289,10 +1801,12 @@ function textFromChildren(node) {
1289
1801
  function RenderedCodeBlock({
1290
1802
  node,
1291
1803
  children,
1292
- language
1804
+ language,
1805
+ title
1293
1806
  }) {
1294
1807
  const source = extractTextFromNode(node) || textFromChildren(children);
1295
1808
  return /* @__PURE__ */ jsxs("div", { className: "te-code-block", children: [
1809
+ title && /* @__PURE__ */ jsx("div", { className: "te-code-block__title", children: title }),
1296
1810
  /* @__PURE__ */ jsx("pre", { children: /* @__PURE__ */ jsx("code", { className: language ? `language-${language}` : void 0, children }) }),
1297
1811
  /* @__PURE__ */ jsx(CopyCodeButton, { source })
1298
1812
  ] });
@@ -1328,6 +1842,148 @@ function extractDocsHeadings(json2) {
1328
1842
  if (json2) walk(json2);
1329
1843
  return headings;
1330
1844
  }
1845
+ function DocsTabs({
1846
+ labels,
1847
+ children,
1848
+ className
1849
+ }) {
1850
+ const id = useId();
1851
+ const [active, setActive] = useState(0);
1852
+ const panels = React5.Children.toArray(children);
1853
+ function onKeyDown(event, index) {
1854
+ const step = event.key === "ArrowRight" ? 1 : event.key === "ArrowLeft" ? -1 : 0;
1855
+ if (!step) return;
1856
+ event.preventDefault();
1857
+ const next = (index + step + labels.length) % labels.length;
1858
+ setActive(next);
1859
+ document.getElementById(`${id}-tab-${next}`)?.focus();
1860
+ }
1861
+ return /* @__PURE__ */ jsxs("div", { className, children: [
1862
+ /* @__PURE__ */ jsx("div", { role: "tablist", className: "te-tabs__list", children: labels.map((label, index) => /* @__PURE__ */ jsx(
1863
+ "button",
1864
+ {
1865
+ id: `${id}-tab-${index}`,
1866
+ type: "button",
1867
+ role: "tab",
1868
+ "aria-selected": index === active,
1869
+ "aria-controls": `${id}-panel-${index}`,
1870
+ tabIndex: index === active ? 0 : -1,
1871
+ className: "te-tabs__tab",
1872
+ onClick: () => setActive(index),
1873
+ onKeyDown: (event) => onKeyDown(event, index),
1874
+ children: label
1875
+ },
1876
+ index
1877
+ )) }),
1878
+ panels.map((panel, index) => /* @__PURE__ */ jsx(
1879
+ "div",
1880
+ {
1881
+ id: `${id}-panel-${index}`,
1882
+ role: "tabpanel",
1883
+ "aria-labelledby": `${id}-tab-${index}`,
1884
+ hidden: index !== active,
1885
+ className: "te-tabs__panel",
1886
+ children: panel
1887
+ },
1888
+ index
1889
+ ))
1890
+ ] });
1891
+ }
1892
+ var childAttrs = (node) => {
1893
+ const attrs = [];
1894
+ node.forEach((child) => {
1895
+ attrs.push(child.attrs);
1896
+ });
1897
+ return attrs;
1898
+ };
1899
+ var CALLOUT_ICONS = {
1900
+ note: StickyNote,
1901
+ tip: Lightbulb,
1902
+ info: Info,
1903
+ warning: CircleAlert,
1904
+ danger: OctagonAlert
1905
+ };
1906
+ function Callout({ node, children }) {
1907
+ const type = node.attrs.type ?? "info";
1908
+ const Icon = CALLOUT_ICONS[type] ?? Info;
1909
+ return /* @__PURE__ */ jsxs("div", { className: "te-callout", "data-type": type, role: "note", children: [
1910
+ /* @__PURE__ */ jsx(Icon, { className: "te-callout__icon", "aria-hidden": true }),
1911
+ /* @__PURE__ */ jsxs("div", { className: "te-callout__body", children: [
1912
+ node.attrs.title && /* @__PURE__ */ jsx("p", { className: "te-callout__title", children: node.attrs.title }),
1913
+ children
1914
+ ] })
1915
+ ] });
1916
+ }
1917
+ function Tabs({ node, children }) {
1918
+ return /* @__PURE__ */ jsx(DocsTabs, { className: "te-tabs", labels: childAttrs(node).map((attrs) => attrs.label), children });
1919
+ }
1920
+ function CodeGroup({ node, children }) {
1921
+ const labels = childAttrs(node).map(
1922
+ (attrs, index) => attrs.title ?? attrs.language ?? `Code ${index + 1}`
1923
+ );
1924
+ return /* @__PURE__ */ jsx(DocsTabs, { className: "te-tabs te-code-group", labels, children });
1925
+ }
1926
+ function Steps({ children }) {
1927
+ return /* @__PURE__ */ jsx("ol", { className: "te-steps", children });
1928
+ }
1929
+ function Step({ children }) {
1930
+ return /* @__PURE__ */ jsx("li", { className: "te-step", children });
1931
+ }
1932
+ function Cards({ children }) {
1933
+ return /* @__PURE__ */ jsx("div", { className: "te-cards", children });
1934
+ }
1935
+ var iconName = (name) => name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Za-z])([0-9])/g, "$1-$2").toLowerCase();
1936
+ function Card({ node, children }) {
1937
+ const { title, href, icon } = node.attrs;
1938
+ const body = /* @__PURE__ */ jsxs(Fragment, { children: [
1939
+ icon && /* @__PURE__ */ jsx(DynamicIcon, { name: iconName(icon), className: "te-card__icon", "aria-hidden": true }),
1940
+ /* @__PURE__ */ jsx("span", { className: "te-card__title", children: title }),
1941
+ node.childCount > 0 && /* @__PURE__ */ jsx("div", { className: "te-card__body", children })
1942
+ ] });
1943
+ return href ? /* @__PURE__ */ jsx("a", { className: "te-card", href, children: body }) : /* @__PURE__ */ jsx("div", { className: "te-card", children: body });
1944
+ }
1945
+ function Accordions({ children }) {
1946
+ return /* @__PURE__ */ jsx("div", { className: "te-accordions", children });
1947
+ }
1948
+ function Accordion({ node, children }) {
1949
+ return /* @__PURE__ */ jsxs("details", { className: "te-accordion", children: [
1950
+ /* @__PURE__ */ jsx("summary", { className: "te-accordion__title", children: node.attrs.title }),
1951
+ /* @__PURE__ */ jsx("div", { className: "te-accordion__body", children })
1952
+ ] });
1953
+ }
1954
+ function TypeTable({ node }) {
1955
+ const rows = node.attrs.rows ?? [];
1956
+ return /* @__PURE__ */ jsx("div", { className: "te-type-table", children: /* @__PURE__ */ jsxs("table", { children: [
1957
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
1958
+ /* @__PURE__ */ jsx("th", { children: "Prop" }),
1959
+ /* @__PURE__ */ jsx("th", { children: "Type" }),
1960
+ /* @__PURE__ */ jsx("th", { children: "Default" }),
1961
+ /* @__PURE__ */ jsx("th", { children: "Description" })
1962
+ ] }) }),
1963
+ /* @__PURE__ */ jsx("tbody", { children: rows.map((row) => /* @__PURE__ */ jsxs("tr", { children: [
1964
+ /* @__PURE__ */ jsxs("td", { children: [
1965
+ /* @__PURE__ */ jsx("code", { children: row.name }),
1966
+ row.required && /* @__PURE__ */ jsx("span", { className: "te-type-table__required", children: " (required)" })
1967
+ ] }),
1968
+ /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("code", { children: row.type }) }),
1969
+ /* @__PURE__ */ jsx("td", { children: row.default === null ? "\u2014" : /* @__PURE__ */ jsx("code", { children: row.default }) }),
1970
+ /* @__PURE__ */ jsx("td", { children: row.description ?? "" })
1971
+ ] }, row.name)) })
1972
+ ] }) });
1973
+ }
1974
+ var docsBlockMapping = {
1975
+ callout: Callout,
1976
+ tabs: Tabs,
1977
+ tab: ({ children }) => /* @__PURE__ */ jsx(Fragment, { children }),
1978
+ codeGroup: CodeGroup,
1979
+ steps: Steps,
1980
+ step: Step,
1981
+ cards: Cards,
1982
+ card: Card,
1983
+ accordions: Accordions,
1984
+ accordion: Accordion,
1985
+ typeTable: TypeTable
1986
+ };
1331
1987
  var typographyClasses = (typography) => [
1332
1988
  typography?.fontFamily,
1333
1989
  typography?.fontSize,
@@ -1419,7 +2075,16 @@ function TextRenderer({
1419
2075
  const Tag = profile === "documentation" ? `h${level}` : "h3";
1420
2076
  return /* @__PURE__ */ jsx(Tag, { id, className: classes, children });
1421
2077
  },
1422
- codeBlock: ({ node, children }) => /* @__PURE__ */ jsx(RenderedCodeBlock, { node, language: node?.attrs?.language, children }),
2078
+ ...docsBlockMapping,
2079
+ codeBlock: ({ node, children }) => /* @__PURE__ */ jsx(
2080
+ RenderedCodeBlock,
2081
+ {
2082
+ node,
2083
+ language: node?.attrs?.language,
2084
+ title: node?.attrs?.title,
2085
+ children
2086
+ }
2087
+ ),
1423
2088
  codeMirror: ({ node, children }) => /* @__PURE__ */ jsx(RenderedCodeBlock, { node, children })
1424
2089
  },
1425
2090
  markMapping: {
@@ -1487,7 +2152,7 @@ function normalizeTextEditorContent(json2) {
1487
2152
  const text = typeof json2 === "string" ? json2.replace(/\r\n?/g, "\n") : "";
1488
2153
  return {
1489
2154
  type: "doc",
1490
- content: text ? text.split(/\n{2,}/).map((paragraph) => createParagraphNode(paragraph)) : []
2155
+ content: text ? text.split(/\n{2,}/).map((paragraph2) => createParagraphNode(paragraph2)) : []
1491
2156
  };
1492
2157
  }
1493
2158