@frontlit/text-editor 0.1.0-alpha.1 → 0.1.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -0
- package/dist/docs.d.ts +90 -0
- package/dist/docs.js +905 -0
- package/dist/docs.js.map +1 -0
- package/dist/headings.d.ts +17 -0
- package/dist/headings.js +35 -0
- package/dist/headings.js.map +1 -0
- package/dist/index.d.ts +13 -7
- package/dist/index.js +264 -128
- package/dist/index.js.map +1 -1
- package/dist/prose.css +191 -193
- package/dist/renderer/index.d.ts +70 -0
- package/dist/renderer/index.js +356 -0
- package/dist/renderer/index.js.map +1 -0
- package/dist/renderer/islands/copy-code-button.js +56 -0
- package/dist/renderer/islands/copy-code-button.js.map +1 -0
- package/dist/styles.css +114 -63
- package/dist/tokens.css +43 -43
- package/package.json +36 -11
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import React5, { createContext, useState, useRef, useMemo, useEffect, useCallback, useContext } from 'react';
|
|
3
|
-
import { Node, generateText } from '@tiptap/core';
|
|
3
|
+
import { Node, getSchema, generateText } from '@tiptap/core';
|
|
4
4
|
import { useEditor, EditorContent } 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
|
-
import { TableRow,
|
|
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
11
|
import Gapcursor from '@tiptap/extension-gapcursor';
|
|
@@ -190,7 +190,8 @@ var LinkEditorPopover = ({
|
|
|
190
190
|
"input",
|
|
191
191
|
{
|
|
192
192
|
ref: inputRef,
|
|
193
|
-
type: "
|
|
193
|
+
type: "text",
|
|
194
|
+
inputMode: "url",
|
|
194
195
|
placeholder: "https://example.com",
|
|
195
196
|
value: linkValue,
|
|
196
197
|
onChange: (event) => setLinkValue(event.target.value),
|
|
@@ -243,7 +244,8 @@ var LinkEditorPopover = ({
|
|
|
243
244
|
"input",
|
|
244
245
|
{
|
|
245
246
|
ref: inputRef,
|
|
246
|
-
type: "
|
|
247
|
+
type: "text",
|
|
248
|
+
inputMode: "url",
|
|
247
249
|
placeholder: "https://example.com",
|
|
248
250
|
value: linkValue,
|
|
249
251
|
onChange: (event) => setLinkValue(event.target.value),
|
|
@@ -327,7 +329,8 @@ function Divider() {
|
|
|
327
329
|
var Toolbar = ({
|
|
328
330
|
editor,
|
|
329
331
|
onRequestImageUpload,
|
|
330
|
-
isUploadingImage
|
|
332
|
+
isUploadingImage,
|
|
333
|
+
profile = "default"
|
|
331
334
|
}) => {
|
|
332
335
|
const [activePopover, setActivePopover] = useState(null);
|
|
333
336
|
const [wordCount, setWordCount] = useState(0);
|
|
@@ -362,7 +365,13 @@ var Toolbar = ({
|
|
|
362
365
|
};
|
|
363
366
|
}, [editor]);
|
|
364
367
|
if (!editor) return null;
|
|
365
|
-
const
|
|
368
|
+
const availableHeadingOptions = profile === "documentation" ? [
|
|
369
|
+
...headingOptions,
|
|
370
|
+
{ label: "Heading 4", value: "h4", level: 4 },
|
|
371
|
+
{ label: "Heading 5", value: "h5", level: 5 },
|
|
372
|
+
{ label: "Heading 6", value: "h6", level: 6 }
|
|
373
|
+
] : headingOptions;
|
|
374
|
+
const activeHeading = availableHeadingOptions.find(
|
|
366
375
|
(option) => option.level ? editor.isActive("heading", { level: option.level }) : editor.isActive("paragraph")
|
|
367
376
|
) ?? headingOptions[0];
|
|
368
377
|
const readTime = wordCount === 0 ? 0 : Math.max(1, Math.ceil(wordCount / 200));
|
|
@@ -419,7 +428,7 @@ var Toolbar = ({
|
|
|
419
428
|
]
|
|
420
429
|
}
|
|
421
430
|
),
|
|
422
|
-
activePopover === "heading" && /* @__PURE__ */ jsx("div", { className: "te-popover absolute left-0 top-full z-30 mt-1 w-40 rounded-md p-1 shadow-lg", children:
|
|
431
|
+
activePopover === "heading" && /* @__PURE__ */ jsx("div", { className: "te-popover absolute left-0 top-full z-30 mt-1 w-40 rounded-md p-1 shadow-lg", children: availableHeadingOptions.map((option) => /* @__PURE__ */ jsx(
|
|
423
432
|
"button",
|
|
424
433
|
{
|
|
425
434
|
type: "button",
|
|
@@ -452,7 +461,7 @@ var Toolbar = ({
|
|
|
452
461
|
onClick: () => editor.chain().focus().toggleItalic().run()
|
|
453
462
|
}
|
|
454
463
|
),
|
|
455
|
-
/* @__PURE__ */ jsx(
|
|
464
|
+
profile !== "documentation" && /* @__PURE__ */ jsx(
|
|
456
465
|
IconButton,
|
|
457
466
|
{
|
|
458
467
|
icon: Underline,
|
|
@@ -476,9 +485,7 @@ var Toolbar = ({
|
|
|
476
485
|
icon: Link$1,
|
|
477
486
|
label: "Insert link",
|
|
478
487
|
active: editor.isActive("link"),
|
|
479
|
-
onClick: () => setActivePopover(
|
|
480
|
-
(current) => current === "link" ? null : "link"
|
|
481
|
-
)
|
|
488
|
+
onClick: () => setActivePopover((current) => current === "link" ? null : "link")
|
|
482
489
|
}
|
|
483
490
|
),
|
|
484
491
|
/* @__PURE__ */ jsx(Divider, {}),
|
|
@@ -523,9 +530,7 @@ var Toolbar = ({
|
|
|
523
530
|
className: "flex h-8 items-center gap-1.5 rounded border border-primary px-2.5 text-sm font-semibold text-primary hover:bg-[var(--primary-soft)]",
|
|
524
531
|
"aria-haspopup": "menu",
|
|
525
532
|
"aria-expanded": activePopover === "insert",
|
|
526
|
-
onClick: () => setActivePopover(
|
|
527
|
-
(current) => current === "insert" ? null : "insert"
|
|
528
|
-
),
|
|
533
|
+
onClick: () => setActivePopover((current) => current === "insert" ? null : "insert"),
|
|
529
534
|
children: [
|
|
530
535
|
/* @__PURE__ */ jsx(Plus, { className: "size-4" }),
|
|
531
536
|
"Insert"
|
|
@@ -706,15 +711,7 @@ var TipTapBubbleMenu = ({
|
|
|
706
711
|
getReferencedVirtualElement,
|
|
707
712
|
options
|
|
708
713
|
]);
|
|
709
|
-
return /* @__PURE__ */ jsx(
|
|
710
|
-
"div",
|
|
711
|
-
{
|
|
712
|
-
ref: menuRef,
|
|
713
|
-
className,
|
|
714
|
-
style: { visibility: "hidden" },
|
|
715
|
-
children
|
|
716
|
-
}
|
|
717
|
-
);
|
|
714
|
+
return /* @__PURE__ */ jsx("div", { ref: menuRef, className, style: { visibility: "hidden" }, children });
|
|
718
715
|
};
|
|
719
716
|
function InlineButton({
|
|
720
717
|
icon: Icon,
|
|
@@ -735,7 +732,8 @@ function InlineButton({
|
|
|
735
732
|
);
|
|
736
733
|
}
|
|
737
734
|
var InlineBubbleMenu = ({
|
|
738
|
-
editor
|
|
735
|
+
editor,
|
|
736
|
+
profile = "default"
|
|
739
737
|
}) => {
|
|
740
738
|
const [isLinkEditing, setIsLinkEditing] = useState(false);
|
|
741
739
|
const openLinkEditor = useCallback(() => {
|
|
@@ -778,7 +776,7 @@ var InlineBubbleMenu = ({
|
|
|
778
776
|
onClick: () => editor.chain().focus().toggleItalic().run()
|
|
779
777
|
}
|
|
780
778
|
),
|
|
781
|
-
/* @__PURE__ */ jsx(
|
|
779
|
+
profile !== "documentation" && /* @__PURE__ */ jsx(
|
|
782
780
|
InlineButton,
|
|
783
781
|
{
|
|
784
782
|
icon: Underline,
|
|
@@ -831,7 +829,9 @@ var bubble_menu_default = InlineBubbleMenu;
|
|
|
831
829
|
function cn(...inputs) {
|
|
832
830
|
return twMerge(clsx(inputs));
|
|
833
831
|
}
|
|
834
|
-
var ImagePickerContext = createContext(
|
|
832
|
+
var ImagePickerContext = createContext(
|
|
833
|
+
null
|
|
834
|
+
);
|
|
835
835
|
function ImagePickerContextProvider({
|
|
836
836
|
pickImage,
|
|
837
837
|
children
|
|
@@ -841,10 +841,74 @@ function ImagePickerContextProvider({
|
|
|
841
841
|
function useImagePicker() {
|
|
842
842
|
return useContext(ImagePickerContext);
|
|
843
843
|
}
|
|
844
|
+
|
|
845
|
+
// src/docs/urls.ts
|
|
846
|
+
function isSafeDocsUrl(value, image = false) {
|
|
847
|
+
if (typeof value !== "string" || !value || value !== value.trim()) return false;
|
|
848
|
+
if (
|
|
849
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: Browsers ignore controls in dangerous protocols.
|
|
850
|
+
/[\u0000-\u0020\u007f\\]/.test(value) || /%(?:0[0-9a-f]|1[0-9a-f]|7f|5c)/i.test(value)
|
|
851
|
+
)
|
|
852
|
+
return false;
|
|
853
|
+
if (value.startsWith("//")) return false;
|
|
854
|
+
const privatePath = /\/(?:attachments|private|ticket-attachments|conversation-attachments)(?:\/|$)/i;
|
|
855
|
+
const signedKey = /^(?:x-amz-.+|x-goog-.+|signature|sig|token|access_token|expires)$/i;
|
|
856
|
+
if (/^(?:https?:|mailto:)/i.test(value)) {
|
|
857
|
+
try {
|
|
858
|
+
const url = new URL(value);
|
|
859
|
+
if (image && url.protocol !== "https:") return false;
|
|
860
|
+
if (url.username || url.password) return false;
|
|
861
|
+
if (url.protocol === "mailto:") return !image && Boolean(url.pathname);
|
|
862
|
+
if (privatePath.test(decodeURIComponent(url.pathname))) return false;
|
|
863
|
+
for (const key of url.searchParams.keys()) {
|
|
864
|
+
if (signedKey.test(key)) return false;
|
|
865
|
+
}
|
|
866
|
+
return Boolean(url.hostname);
|
|
867
|
+
} catch {
|
|
868
|
+
return false;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
if (image) return false;
|
|
872
|
+
if (/^[^/?#]*:/.test(value) || /%(?:2f|3a)/i.test(value)) return false;
|
|
873
|
+
try {
|
|
874
|
+
const url = new URL(value, "https://docs.invalid/");
|
|
875
|
+
return !privatePath.test(decodeURIComponent(url.pathname)) && ![...url.searchParams.keys()].some((key) => signedKey.test(key));
|
|
876
|
+
} catch {
|
|
877
|
+
return false;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// src/docs/extensions.ts
|
|
882
|
+
function createDocsExtensions() {
|
|
883
|
+
return [
|
|
884
|
+
StarterKit.configure({
|
|
885
|
+
document: false,
|
|
886
|
+
underline: false,
|
|
887
|
+
link: false,
|
|
888
|
+
dropcursor: false,
|
|
889
|
+
gapcursor: false,
|
|
890
|
+
trailingNode: false
|
|
891
|
+
}),
|
|
892
|
+
Node.create({ name: "doc", topNode: true, content: "block*" }),
|
|
893
|
+
Link.extend({
|
|
894
|
+
addAttributes() {
|
|
895
|
+
return { ...this.parent?.(), title: { default: null } };
|
|
896
|
+
}
|
|
897
|
+
}).configure({ openOnClick: false, isAllowedUri: (url) => isSafeDocsUrl(url) }),
|
|
898
|
+
Image.configure({ allowBase64: false, resize: false }),
|
|
899
|
+
Table.configure({ resizable: false }),
|
|
900
|
+
TableRow,
|
|
901
|
+
TableCell.extend({ content: "paragraph" }),
|
|
902
|
+
TableHeader.extend({ content: "paragraph" })
|
|
903
|
+
];
|
|
904
|
+
}
|
|
905
|
+
getSchema(createDocsExtensions());
|
|
844
906
|
var Editor = Object.assign(
|
|
845
907
|
({
|
|
846
908
|
initialContent,
|
|
847
909
|
onChange,
|
|
910
|
+
onFlushChangeReady,
|
|
911
|
+
debounceMs = 1e3,
|
|
848
912
|
placeholder,
|
|
849
913
|
children,
|
|
850
914
|
showToolbar = true,
|
|
@@ -857,23 +921,28 @@ var Editor = Object.assign(
|
|
|
857
921
|
className,
|
|
858
922
|
editorClassName,
|
|
859
923
|
contentClassName,
|
|
860
|
-
beforeContent
|
|
924
|
+
beforeContent,
|
|
925
|
+
profile = "default"
|
|
861
926
|
}) => {
|
|
862
927
|
const [isUploading, setIsUploading] = useState(false);
|
|
863
928
|
const fileInputRef = useRef(null);
|
|
864
929
|
const pickImage = useImagePicker();
|
|
865
930
|
const hasInteractedRef = useRef(false);
|
|
866
|
-
const lastSerializedContentRef = useRef(
|
|
867
|
-
JSON.stringify(initialContent ?? empty_doc_default)
|
|
868
|
-
);
|
|
931
|
+
const lastSerializedContentRef = useRef(JSON.stringify(initialContent ?? empty_doc_default));
|
|
869
932
|
const refreshRef = useRef(refresh);
|
|
870
933
|
const extensions = useMemo(
|
|
871
|
-
() =>
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
934
|
+
() => profile === "documentation" ? [
|
|
935
|
+
...createDocsExtensions(),
|
|
936
|
+
Placeholder.configure({ placeholder: placeholder || "Write something\u2026" })
|
|
937
|
+
] : createExtensions({ placeholder }),
|
|
938
|
+
[placeholder, profile]
|
|
876
939
|
);
|
|
940
|
+
const changeTimeoutRef = useRef(null);
|
|
941
|
+
const pendingJsonRef = useRef(null);
|
|
942
|
+
const onChangeRef = useRef(onChange);
|
|
943
|
+
onChangeRef.current = onChange;
|
|
944
|
+
const debounceMsRef = useRef(debounceMs);
|
|
945
|
+
debounceMsRef.current = debounceMs;
|
|
877
946
|
const editor = useEditor(
|
|
878
947
|
{
|
|
879
948
|
immediatelyRender: false,
|
|
@@ -894,10 +963,13 @@ var Editor = Object.assign(
|
|
|
894
963
|
if (changeTimeoutRef.current) {
|
|
895
964
|
clearTimeout(changeTimeoutRef.current);
|
|
896
965
|
}
|
|
966
|
+
pendingJsonRef.current = instance.getJSON();
|
|
897
967
|
changeTimeoutRef.current = setTimeout(() => {
|
|
898
|
-
onChange(instance.getJSON());
|
|
899
968
|
changeTimeoutRef.current = null;
|
|
900
|
-
|
|
969
|
+
const json2 = pendingJsonRef.current;
|
|
970
|
+
pendingJsonRef.current = null;
|
|
971
|
+
if (json2) onChangeRef.current(json2);
|
|
972
|
+
}, debounceMsRef.current);
|
|
901
973
|
},
|
|
902
974
|
onSelectionUpdate: ({ editor: instance }) => {
|
|
903
975
|
hasInteractedRef.current = true;
|
|
@@ -905,11 +977,26 @@ var Editor = Object.assign(
|
|
|
905
977
|
},
|
|
906
978
|
[extensions]
|
|
907
979
|
);
|
|
980
|
+
useEffect(() => {
|
|
981
|
+
onFlushChangeReady?.(() => {
|
|
982
|
+
if (!editor || !changeTimeoutRef.current) return;
|
|
983
|
+
clearTimeout(changeTimeoutRef.current);
|
|
984
|
+
changeTimeoutRef.current = null;
|
|
985
|
+
pendingJsonRef.current = null;
|
|
986
|
+
onChangeRef.current(editor.getJSON());
|
|
987
|
+
});
|
|
988
|
+
return () => onFlushChangeReady?.(() => {
|
|
989
|
+
});
|
|
990
|
+
}, [editor, onFlushChangeReady]);
|
|
908
991
|
useEffect(() => {
|
|
909
992
|
return () => {
|
|
910
993
|
if (changeTimeoutRef.current) {
|
|
911
994
|
clearTimeout(changeTimeoutRef.current);
|
|
995
|
+
changeTimeoutRef.current = null;
|
|
912
996
|
}
|
|
997
|
+
const json2 = pendingJsonRef.current;
|
|
998
|
+
pendingJsonRef.current = null;
|
|
999
|
+
if (json2) onChangeRef.current(json2);
|
|
913
1000
|
};
|
|
914
1001
|
}, []);
|
|
915
1002
|
useEffect(() => {
|
|
@@ -947,6 +1034,12 @@ var Editor = Object.assign(
|
|
|
947
1034
|
}, [editor, initialContent, refresh]);
|
|
948
1035
|
const handleImageUpload = useCallback(
|
|
949
1036
|
async (file) => {
|
|
1037
|
+
if (profile === "documentation") {
|
|
1038
|
+
onError?.(
|
|
1039
|
+
"Upload a public image using the media library before inserting it."
|
|
1040
|
+
);
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
950
1043
|
const sizeLimit = imageSizeLimit ?? 2097152;
|
|
951
1044
|
if (file.size > sizeLimit) {
|
|
952
1045
|
onError?.("File is larger than the allowed size");
|
|
@@ -954,14 +1047,12 @@ var Editor = Object.assign(
|
|
|
954
1047
|
}
|
|
955
1048
|
try {
|
|
956
1049
|
setIsUploading(true);
|
|
957
|
-
const dataUrl = await new Promise(
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
}
|
|
964
|
-
);
|
|
1050
|
+
const dataUrl = await new Promise((resolve, reject) => {
|
|
1051
|
+
const reader = new FileReader();
|
|
1052
|
+
reader.onload = () => resolve(reader.result);
|
|
1053
|
+
reader.onerror = reject;
|
|
1054
|
+
reader.readAsDataURL(file);
|
|
1055
|
+
});
|
|
965
1056
|
editor?.chain().focus().setImage({
|
|
966
1057
|
src: dataUrl,
|
|
967
1058
|
alt: file.name,
|
|
@@ -973,7 +1064,7 @@ var Editor = Object.assign(
|
|
|
973
1064
|
setIsUploading(false);
|
|
974
1065
|
}
|
|
975
1066
|
},
|
|
976
|
-
[editor]
|
|
1067
|
+
[editor, profile, onError, imageSizeLimit]
|
|
977
1068
|
);
|
|
978
1069
|
const handleSelectFile = useCallback(
|
|
979
1070
|
(event) => {
|
|
@@ -989,13 +1080,17 @@ var Editor = Object.assign(
|
|
|
989
1080
|
);
|
|
990
1081
|
const insertPickedImage = useCallback(
|
|
991
1082
|
(image) => {
|
|
1083
|
+
if (profile === "documentation" && !isSafeDocsUrl(image.src, true)) {
|
|
1084
|
+
onError?.("Documentation images require a stable public HTTPS URL.");
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
992
1087
|
editor?.chain().focus().setImage({
|
|
993
1088
|
src: image.src,
|
|
994
1089
|
alt: image.alt,
|
|
995
1090
|
title: image.alt
|
|
996
1091
|
}).run();
|
|
997
1092
|
},
|
|
998
|
-
[editor]
|
|
1093
|
+
[editor, profile, onError]
|
|
999
1094
|
);
|
|
1000
1095
|
const openFileDialog = useCallback(() => {
|
|
1001
1096
|
if (pickImage) {
|
|
@@ -1012,10 +1107,13 @@ var Editor = Object.assign(
|
|
|
1012
1107
|
return /* @__PURE__ */ jsxs(
|
|
1013
1108
|
"div",
|
|
1014
1109
|
{
|
|
1015
|
-
className: cn(
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1110
|
+
className: cn("tiptap-editor flex flex-col gap-4 border", className),
|
|
1111
|
+
onPointerDownCapture: () => {
|
|
1112
|
+
hasInteractedRef.current = true;
|
|
1113
|
+
},
|
|
1114
|
+
onKeyDownCapture: () => {
|
|
1115
|
+
hasInteractedRef.current = true;
|
|
1116
|
+
},
|
|
1019
1117
|
children: [
|
|
1020
1118
|
editable && showToolbar && /* @__PURE__ */ jsx(
|
|
1021
1119
|
"div",
|
|
@@ -1029,42 +1127,34 @@ var Editor = Object.assign(
|
|
|
1029
1127
|
{
|
|
1030
1128
|
editor,
|
|
1031
1129
|
onRequestImageUpload: openFileDialog,
|
|
1032
|
-
isUploadingImage: isUploading
|
|
1130
|
+
isUploadingImage: isUploading,
|
|
1131
|
+
profile
|
|
1033
1132
|
}
|
|
1034
1133
|
)
|
|
1035
1134
|
}
|
|
1036
1135
|
),
|
|
1037
|
-
/* @__PURE__ */ jsx("div", { className: "flex w-full justify-center", children: /* @__PURE__ */ jsxs(
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
className: "hidden",
|
|
1060
|
-
onChange: handleSelectFile
|
|
1061
|
-
}
|
|
1062
|
-
),
|
|
1063
|
-
children,
|
|
1064
|
-
editor && /* @__PURE__ */ jsx(bubble_menu_default, { editor })
|
|
1065
|
-
]
|
|
1066
|
-
}
|
|
1067
|
-
) })
|
|
1136
|
+
/* @__PURE__ */ jsx("div", { className: "flex w-full justify-center", children: /* @__PURE__ */ jsxs("div", { className: cn("flex w-full max-w-3xl flex-col", contentClassName), children: [
|
|
1137
|
+
beforeContent,
|
|
1138
|
+
/* @__PURE__ */ jsx(
|
|
1139
|
+
EditorContent,
|
|
1140
|
+
{
|
|
1141
|
+
editor,
|
|
1142
|
+
className: `${editorClassName ?? ""} ${editorClassName?.includes("min-h-") ? "" : "min-h-[200px]"}`
|
|
1143
|
+
}
|
|
1144
|
+
),
|
|
1145
|
+
pickImage ? null : /* @__PURE__ */ jsx(
|
|
1146
|
+
"input",
|
|
1147
|
+
{
|
|
1148
|
+
ref: fileInputRef,
|
|
1149
|
+
type: "file",
|
|
1150
|
+
accept: "image/*",
|
|
1151
|
+
className: "hidden",
|
|
1152
|
+
onChange: handleSelectFile
|
|
1153
|
+
}
|
|
1154
|
+
),
|
|
1155
|
+
children,
|
|
1156
|
+
editor && /* @__PURE__ */ jsx(bubble_menu_default, { editor, profile })
|
|
1157
|
+
] }) })
|
|
1068
1158
|
]
|
|
1069
1159
|
}
|
|
1070
1160
|
);
|
|
@@ -1140,17 +1230,6 @@ function extractTextFromNode(node) {
|
|
|
1140
1230
|
}
|
|
1141
1231
|
return "";
|
|
1142
1232
|
}
|
|
1143
|
-
function textFromChildren(node) {
|
|
1144
|
-
if (node == null || typeof node === "boolean") return "";
|
|
1145
|
-
if (typeof node === "string" || typeof node === "number") {
|
|
1146
|
-
return String(node);
|
|
1147
|
-
}
|
|
1148
|
-
if (Array.isArray(node)) return node.map(textFromChildren).join("");
|
|
1149
|
-
if (React5.isValidElement(node)) {
|
|
1150
|
-
return textFromChildren(node.props.children);
|
|
1151
|
-
}
|
|
1152
|
-
return "";
|
|
1153
|
-
}
|
|
1154
1233
|
async function writeClipboard(text) {
|
|
1155
1234
|
if (navigator.clipboard?.writeText) {
|
|
1156
1235
|
await navigator.clipboard.writeText(text);
|
|
@@ -1166,13 +1245,8 @@ async function writeClipboard(text) {
|
|
|
1166
1245
|
document.execCommand("copy");
|
|
1167
1246
|
area.remove();
|
|
1168
1247
|
}
|
|
1169
|
-
function
|
|
1170
|
-
node,
|
|
1171
|
-
children,
|
|
1172
|
-
language
|
|
1173
|
-
}) {
|
|
1248
|
+
function CopyCodeButton({ source }) {
|
|
1174
1249
|
const [copied, setCopied] = useState(false);
|
|
1175
|
-
const source = extractTextFromNode(node) || textFromChildren(children);
|
|
1176
1250
|
useEffect(() => {
|
|
1177
1251
|
if (!copied) return;
|
|
1178
1252
|
const timer = window.setTimeout(() => setCopied(false), 1500);
|
|
@@ -1187,23 +1261,73 @@ function RenderedCodeBlock({
|
|
|
1187
1261
|
setCopied(false);
|
|
1188
1262
|
}
|
|
1189
1263
|
}
|
|
1264
|
+
return /* @__PURE__ */ jsx(
|
|
1265
|
+
"button",
|
|
1266
|
+
{
|
|
1267
|
+
type: "button",
|
|
1268
|
+
className: "te-code-copy",
|
|
1269
|
+
"aria-label": copied ? "Copied" : "Copy code",
|
|
1270
|
+
title: copied ? "Copied" : "Copy",
|
|
1271
|
+
onClick: () => {
|
|
1272
|
+
void handleCopy();
|
|
1273
|
+
},
|
|
1274
|
+
children: copied ? /* @__PURE__ */ jsx(Check, { className: "te-code-copy__icon", "aria-hidden": true }) : /* @__PURE__ */ jsx(Copy, { className: "te-code-copy__icon", "aria-hidden": true })
|
|
1275
|
+
}
|
|
1276
|
+
);
|
|
1277
|
+
}
|
|
1278
|
+
function textFromChildren(node) {
|
|
1279
|
+
if (node == null || typeof node === "boolean") return "";
|
|
1280
|
+
if (typeof node === "string" || typeof node === "number") {
|
|
1281
|
+
return String(node);
|
|
1282
|
+
}
|
|
1283
|
+
if (Array.isArray(node)) return node.map(textFromChildren).join("");
|
|
1284
|
+
if (React5.isValidElement(node)) {
|
|
1285
|
+
return textFromChildren(node.props.children);
|
|
1286
|
+
}
|
|
1287
|
+
return "";
|
|
1288
|
+
}
|
|
1289
|
+
function RenderedCodeBlock({
|
|
1290
|
+
node,
|
|
1291
|
+
children,
|
|
1292
|
+
language
|
|
1293
|
+
}) {
|
|
1294
|
+
const source = extractTextFromNode(node) || textFromChildren(children);
|
|
1190
1295
|
return /* @__PURE__ */ jsxs("div", { className: "te-code-block", children: [
|
|
1191
1296
|
/* @__PURE__ */ jsx("pre", { children: /* @__PURE__ */ jsx("code", { className: language ? `language-${language}` : void 0, children }) }),
|
|
1192
|
-
/* @__PURE__ */ jsx(
|
|
1193
|
-
"button",
|
|
1194
|
-
{
|
|
1195
|
-
type: "button",
|
|
1196
|
-
className: "te-code-copy",
|
|
1197
|
-
"aria-label": copied ? "Copied" : "Copy code",
|
|
1198
|
-
title: copied ? "Copied" : "Copy",
|
|
1199
|
-
onClick: () => {
|
|
1200
|
-
void handleCopy();
|
|
1201
|
-
},
|
|
1202
|
-
children: copied ? /* @__PURE__ */ jsx(Check, { className: "te-code-copy__icon", "aria-hidden": true }) : /* @__PURE__ */ jsx(Copy, { className: "te-code-copy__icon", "aria-hidden": true })
|
|
1203
|
-
}
|
|
1204
|
-
)
|
|
1297
|
+
/* @__PURE__ */ jsx(CopyCodeButton, { source })
|
|
1205
1298
|
] });
|
|
1206
1299
|
}
|
|
1300
|
+
|
|
1301
|
+
// src/docs/headings.ts
|
|
1302
|
+
function docsNodeText(node) {
|
|
1303
|
+
if (typeof node.text === "string") return node.text;
|
|
1304
|
+
if (node.type === "hardBreak") return "\n";
|
|
1305
|
+
return (node.content ?? []).map(docsNodeText).join("");
|
|
1306
|
+
}
|
|
1307
|
+
function createHeadingIdGenerator() {
|
|
1308
|
+
const used = /* @__PURE__ */ new Set();
|
|
1309
|
+
return (text) => {
|
|
1310
|
+
const base = text.normalize("NFKC").toLowerCase().trim().replace(/[^\p{L}\p{N}\s_-]/gu, "").replace(/\s+/g, "-") || "section";
|
|
1311
|
+
let id = base;
|
|
1312
|
+
let suffix = 2;
|
|
1313
|
+
while (used.has(id)) id = `${base}-${suffix++}`;
|
|
1314
|
+
used.add(id);
|
|
1315
|
+
return id;
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
function extractDocsHeadings(json2) {
|
|
1319
|
+
const nextId = createHeadingIdGenerator();
|
|
1320
|
+
const headings = [];
|
|
1321
|
+
function walk(node) {
|
|
1322
|
+
if (node.type === "heading") {
|
|
1323
|
+
const text = docsNodeText(node);
|
|
1324
|
+
headings.push({ level: Number(node.attrs?.level ?? 1), text, id: nextId(text) });
|
|
1325
|
+
}
|
|
1326
|
+
for (const child of node.content ?? []) walk(child);
|
|
1327
|
+
}
|
|
1328
|
+
if (json2) walk(json2);
|
|
1329
|
+
return headings;
|
|
1330
|
+
}
|
|
1207
1331
|
var typographyClasses = (typography) => [
|
|
1208
1332
|
typography?.fontFamily,
|
|
1209
1333
|
typography?.fontSize,
|
|
@@ -1230,14 +1354,30 @@ function removeEmptyTextNodes(node) {
|
|
|
1230
1354
|
content
|
|
1231
1355
|
};
|
|
1232
1356
|
}
|
|
1233
|
-
function TextRenderer({
|
|
1357
|
+
function TextRenderer({
|
|
1358
|
+
json: json2,
|
|
1359
|
+
className,
|
|
1360
|
+
theme,
|
|
1361
|
+
profile = "default"
|
|
1362
|
+
}) {
|
|
1234
1363
|
const extensions = createExtensions();
|
|
1235
1364
|
const content = removeEmptyTextNodes(
|
|
1236
1365
|
json2 ?? empty_doc_default
|
|
1237
1366
|
);
|
|
1367
|
+
const docsHeadings = profile === "documentation" ? extractDocsHeadings(content) : [];
|
|
1368
|
+
const renderContent = profile === "documentation" ? getSchema(extensions).nodeFromJSON(content) : content;
|
|
1369
|
+
const headingIds = /* @__PURE__ */ new WeakMap();
|
|
1370
|
+
if (profile === "documentation") {
|
|
1371
|
+
let headingIndex = 0;
|
|
1372
|
+
renderContent.descendants((node) => {
|
|
1373
|
+
if (node.type.name === "heading") {
|
|
1374
|
+
headingIds.set(node, docsHeadings[headingIndex++].id);
|
|
1375
|
+
}
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1238
1378
|
const rendered = renderToReactElement({
|
|
1239
1379
|
extensions,
|
|
1240
|
-
content,
|
|
1380
|
+
content: renderContent,
|
|
1241
1381
|
options: {
|
|
1242
1382
|
nodeMapping: {
|
|
1243
1383
|
paragraph: ({ children }) => {
|
|
@@ -1251,10 +1391,10 @@ function TextRenderer({ json: json2, className, theme }) {
|
|
|
1251
1391
|
heading: ({ node, children }) => {
|
|
1252
1392
|
const level = node?.attrs?.level ?? 1;
|
|
1253
1393
|
const textContent = extractTextFromNode(node);
|
|
1254
|
-
const id = createId(textContent);
|
|
1394
|
+
const id = profile === "documentation" ? headingIds.get(node) : createId(textContent);
|
|
1255
1395
|
if (!theme) {
|
|
1256
|
-
const
|
|
1257
|
-
return /* @__PURE__ */ jsx(
|
|
1396
|
+
const Tag2 = `h${level}`;
|
|
1397
|
+
return /* @__PURE__ */ jsx(Tag2, { id, children });
|
|
1258
1398
|
}
|
|
1259
1399
|
if (level === 1) {
|
|
1260
1400
|
const classes2 = cn(
|
|
@@ -1272,18 +1412,14 @@ function TextRenderer({ json: json2, className, theme }) {
|
|
|
1272
1412
|
}
|
|
1273
1413
|
const classes = cn(
|
|
1274
1414
|
"scroll-m-20 text-2xl font-semibold tracking-normal",
|
|
1275
|
-
...typographyClasses(
|
|
1415
|
+
...typographyClasses(
|
|
1416
|
+
profile === "documentation" && level >= 4 ? theme?.typography?.header4 ?? theme?.typography?.header3 : theme?.typography?.header3
|
|
1417
|
+
)
|
|
1276
1418
|
);
|
|
1277
|
-
|
|
1419
|
+
const Tag = profile === "documentation" ? `h${level}` : "h3";
|
|
1420
|
+
return /* @__PURE__ */ jsx(Tag, { id, className: classes, children });
|
|
1278
1421
|
},
|
|
1279
|
-
codeBlock: ({ node, children }) => /* @__PURE__ */ jsx(
|
|
1280
|
-
RenderedCodeBlock,
|
|
1281
|
-
{
|
|
1282
|
-
node,
|
|
1283
|
-
language: node?.attrs?.language,
|
|
1284
|
-
children
|
|
1285
|
-
}
|
|
1286
|
-
),
|
|
1422
|
+
codeBlock: ({ node, children }) => /* @__PURE__ */ jsx(RenderedCodeBlock, { node, language: node?.attrs?.language, children }),
|
|
1287
1423
|
codeMirror: ({ node, children }) => /* @__PURE__ */ jsx(RenderedCodeBlock, { node, children })
|
|
1288
1424
|
},
|
|
1289
1425
|
markMapping: {
|