@vicacha-devs/astro-tiptap-blocks 1.2.0 → 1.2.1
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 +6 -0
- package/dist/lib/html-parser/html-custom-blocks.js +15 -1
- package/dist/lib/html-parser/html-inline.d.ts +5 -16
- package/dist/lib/html-parser/html-inline.js +52 -103
- package/dist/lib/html-parser/html-parser.js +27 -51
- package/dist/lib/html-parser/html-tokenizer.d.ts +15 -0
- package/dist/lib/html-parser/html-tokenizer.js +91 -0
- package/dist/lib/markdown-parser/markdown-inline.d.ts +5 -0
- package/dist/lib/markdown-parser/markdown-inline.js +135 -0
- package/dist/lib/markdown-parser/markdown-parser.d.ts +3 -3
- package/dist/lib/markdown-parser/markdown-parser.js +20 -136
- package/dist/lib/parser/index.d.ts +1 -0
- package/dist/lib/parser/index.js +1 -0
- package/dist/lib/parser/parser-inline.d.ts +14 -0
- package/dist/lib/parser/parser-inline.js +122 -0
- package/dist/lib/parser/parser-sanitize.d.ts +2 -0
- package/dist/lib/parser/parser-sanitize.js +149 -0
- package/dist/lib/parser/parser.js +28 -238
- package/dist/lib/relations/relations-uri.d.ts +16 -0
- package/dist/lib/relations/relations-uri.js +39 -0
- package/dist/lib/relations/relations.d.ts +4 -17
- package/dist/lib/relations/relations.js +6 -41
- package/dist/lib/sanitize/sanitize-format.d.ts +12 -0
- package/dist/lib/sanitize/sanitize-format.js +158 -0
- package/dist/lib/sanitize/sanitize.d.ts +10 -10
- package/dist/lib/sanitize/sanitize.js +48 -118
- package/dist/lib/theme/theme-default.d.ts +2 -0
- package/dist/lib/theme/theme-default.js +119 -0
- package/dist/lib/theme/theme.d.ts +3 -1
- package/dist/lib/theme/theme.js +10 -118
- package/dist/types/custom.d.ts +19 -0
- package/dist/types/tiptap-inline.d.ts +1 -0
- package/dist/types/tiptap-media.d.ts +146 -0
- package/dist/types/tiptap-media.js +1 -0
- package/dist/types/tiptap.d.ts +3 -132
- package/dist/types/tiptap.js +1 -0
- package/package.json +1 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { normalizeEmbedUrl } from "./sanitize-format";
|
|
2
|
+
export * from "./sanitize-format";
|
|
1
3
|
const SAFE_URL_PROTOCOLS = [
|
|
2
4
|
"http:",
|
|
3
5
|
"https:",
|
|
@@ -18,19 +20,30 @@ const HTML_ENTITIES = {
|
|
|
18
20
|
"'": "'",
|
|
19
21
|
" ": " ",
|
|
20
22
|
};
|
|
23
|
+
const safeFromCodePoint = (code, fallback) => {
|
|
24
|
+
if (!Number.isInteger(code) ||
|
|
25
|
+
code < 0 ||
|
|
26
|
+
code > 0x10ffff ||
|
|
27
|
+
(code >= 0xd800 && code <= 0xdfff)) {
|
|
28
|
+
return fallback;
|
|
29
|
+
}
|
|
30
|
+
return String.fromCodePoint(code);
|
|
31
|
+
};
|
|
21
32
|
export const decodeHtmlEntities = (text) => text
|
|
22
|
-
.replace(/&(?:amp|lt|gt|quot|#39|apos|nbsp);/
|
|
23
|
-
.replace(/&#(\d+);/g, (
|
|
24
|
-
.replace(/&#x([0-9a-fA-F]+);/g, (
|
|
33
|
+
.replace(/&(?:amp|lt|gt|quot|#39|apos|nbsp);/gi, (match) => HTML_ENTITIES[match.toLowerCase()] ?? match)
|
|
34
|
+
.replace(/&#(\d+);/g, (match, dec) => safeFromCodePoint(Number(dec), match))
|
|
35
|
+
.replace(/&#x([0-9a-fA-F]+);/g, (match, hex) => safeFromCodePoint(parseInt(hex, 16), match));
|
|
25
36
|
/**
|
|
26
37
|
* Validates and sanitizes a URL for safe rendering in `href` or `src` attributes.
|
|
27
|
-
* Blocks `javascript:`, `vbscript:`, `data:text/html`, `data:image/svg+xml`, and `//` protocol-relative URLs.
|
|
38
|
+
* Blocks `javascript:`, `vbscript:`, `data:text/html`, `data:image/svg+xml`, and `//` or `/\` protocol-relative URLs.
|
|
28
39
|
*/
|
|
29
40
|
export const sanitizeTiptapUrl = (rawUrl) => {
|
|
30
41
|
if (!rawUrl || typeof rawUrl !== "string")
|
|
31
42
|
return "#";
|
|
32
|
-
const trimmed = decodeHtmlEntities(rawUrl)
|
|
33
|
-
|
|
43
|
+
const trimmed = decodeHtmlEntities(rawUrl)
|
|
44
|
+
.replace(/[\u0000-\u001F\u007F-\u009F]+/g, "")
|
|
45
|
+
.trim();
|
|
46
|
+
if (!trimmed || /^[/\\]{2}|^\\/.test(trimmed))
|
|
34
47
|
return "#";
|
|
35
48
|
if (trimmed.startsWith("/") ||
|
|
36
49
|
trimmed.startsWith("#") ||
|
|
@@ -38,7 +51,7 @@ export const sanitizeTiptapUrl = (rawUrl) => {
|
|
|
38
51
|
trimmed.startsWith("../")) {
|
|
39
52
|
return trimmed;
|
|
40
53
|
}
|
|
41
|
-
const normalized = trimmed.replace(
|
|
54
|
+
const normalized = trimmed.replace(/\s+/g, "").toLowerCase();
|
|
42
55
|
if (normalized.startsWith("javascript:") || normalized.startsWith("vbscript:"))
|
|
43
56
|
return "#";
|
|
44
57
|
if (normalized.startsWith("data:"))
|
|
@@ -53,7 +66,7 @@ export const sanitizeTiptapUrl = (rawUrl) => {
|
|
|
53
66
|
return SAFE_URL_PROTOCOLS.includes(parsed.protocol.toLowerCase()) ? trimmed : "#";
|
|
54
67
|
}
|
|
55
68
|
catch {
|
|
56
|
-
return !trimmed.includes(":") ? trimmed : "#";
|
|
69
|
+
return !trimmed.includes(":") && !trimmed.includes("\\") ? trimmed : "#";
|
|
57
70
|
}
|
|
58
71
|
};
|
|
59
72
|
/**
|
|
@@ -65,12 +78,37 @@ export const resolveTiptapMediaUrl = (rawUrl, options) => {
|
|
|
65
78
|
return "#";
|
|
66
79
|
const custom = options?.resolveMediaUrl ? options.resolveMediaUrl(rawUrl) : rawUrl;
|
|
67
80
|
const candidate = (custom ?? rawUrl).trim();
|
|
68
|
-
if (options?.mediaBaseUrl && candidate.startsWith("/") &&
|
|
81
|
+
if (options?.mediaBaseUrl && candidate.startsWith("/") && !/^[/\\]{2}/.test(candidate)) {
|
|
69
82
|
const base = options.mediaBaseUrl.trim().replace(/\/+$/, "");
|
|
70
83
|
return sanitizeTiptapUrl(`${base}${candidate}`);
|
|
71
84
|
}
|
|
72
85
|
return sanitizeTiptapUrl(candidate);
|
|
73
86
|
};
|
|
87
|
+
/**
|
|
88
|
+
* Sanitizes a responsive `srcset` attribute string by validating and resolving every candidate URL.
|
|
89
|
+
*/
|
|
90
|
+
export const sanitizeTiptapSrcset = (rawSrcset, options) => {
|
|
91
|
+
if (!rawSrcset || typeof rawSrcset !== "string")
|
|
92
|
+
return undefined;
|
|
93
|
+
const safeParts = [];
|
|
94
|
+
for (const entry of rawSrcset.split(",")) {
|
|
95
|
+
const tokens = entry.trim().split(/\s+/).filter(Boolean);
|
|
96
|
+
if (tokens.length === 0 || tokens.length > 2)
|
|
97
|
+
continue;
|
|
98
|
+
const safeUrl = resolveTiptapMediaUrl(tokens[0], options);
|
|
99
|
+
if (safeUrl === "#")
|
|
100
|
+
continue;
|
|
101
|
+
if (tokens.length === 2) {
|
|
102
|
+
if (!/^\d+(?:\.\d+)?[wx]$/i.test(tokens[1]))
|
|
103
|
+
continue;
|
|
104
|
+
safeParts.push(`${safeUrl} ${tokens[1]}`);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
safeParts.push(safeUrl);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return safeParts.length > 0 ? safeParts.join(", ") : undefined;
|
|
111
|
+
};
|
|
74
112
|
/**
|
|
75
113
|
* Enforces HTTPS-only URLs and optional domain allowlist for sandboxed `<iframe>` embeds.
|
|
76
114
|
*/
|
|
@@ -79,7 +117,7 @@ export const sanitizeTiptapIframeUrl = (rawUrl, allowedDomains) => {
|
|
|
79
117
|
if (safe === "#")
|
|
80
118
|
return "#";
|
|
81
119
|
try {
|
|
82
|
-
const parsed = new URL(safe);
|
|
120
|
+
const parsed = new URL(normalizeEmbedUrl(safe));
|
|
83
121
|
if (parsed.protocol.toLowerCase() !== "https:")
|
|
84
122
|
return "#";
|
|
85
123
|
if (allowedDomains && allowedDomains.length > 0) {
|
|
@@ -97,111 +135,3 @@ export const sanitizeTiptapIframeUrl = (rawUrl, allowedDomains) => {
|
|
|
97
135
|
return "#";
|
|
98
136
|
}
|
|
99
137
|
};
|
|
100
|
-
const SAFE_COLOR_REGEX = /^(#[0-9a-fA-F]{3,8}|(?:rgb|rgba|hsl|hsla)\(\s*[\d.,%\s/]+\)|var\(--[a-zA-Z0-9_-]+\)|[a-zA-Z]{3,20})$/;
|
|
101
|
-
const SAFE_DIMENSION_REGEX = /^\d+(?:\.\d+)?(?:px|rem|em|%|pt|ch|vh|vw)?$/;
|
|
102
|
-
const SAFE_FONT_FAMILY_REGEX = /^[a-zA-Z0-9\s,'"-]+$/;
|
|
103
|
-
export const sanitizeCssColor = (color) => {
|
|
104
|
-
if (!color || typeof color !== "string")
|
|
105
|
-
return null;
|
|
106
|
-
const trimmed = color.trim();
|
|
107
|
-
return SAFE_COLOR_REGEX.test(trimmed) ? trimmed : null;
|
|
108
|
-
};
|
|
109
|
-
export const sanitizeCssDimension = (val) => {
|
|
110
|
-
if (!val || typeof val !== "string")
|
|
111
|
-
return null;
|
|
112
|
-
const trimmed = val.trim();
|
|
113
|
-
return SAFE_DIMENSION_REGEX.test(trimmed) ? trimmed : null;
|
|
114
|
-
};
|
|
115
|
-
export const sanitizeCssFontFamily = (font) => {
|
|
116
|
-
if (!font || typeof font !== "string")
|
|
117
|
-
return null;
|
|
118
|
-
const trimmed = font.trim();
|
|
119
|
-
return SAFE_FONT_FAMILY_REGEX.test(trimmed) && !/url\(|expression\(|;/i.test(trimmed)
|
|
120
|
-
? trimmed
|
|
121
|
-
: null;
|
|
122
|
-
};
|
|
123
|
-
export const sanitizeTextStyleAttrs = (attrs) => {
|
|
124
|
-
if (!attrs)
|
|
125
|
-
return undefined;
|
|
126
|
-
const color = sanitizeCssColor(attrs.color);
|
|
127
|
-
const fontFamily = sanitizeCssFontFamily(attrs.fontFamily);
|
|
128
|
-
const fontSize = sanitizeCssDimension(attrs.fontSize);
|
|
129
|
-
const lineHeight = sanitizeCssDimension(attrs.lineHeight);
|
|
130
|
-
if (!color && !fontFamily && !fontSize && !lineHeight)
|
|
131
|
-
return undefined;
|
|
132
|
-
return {
|
|
133
|
-
...(color ? { color } : {}),
|
|
134
|
-
...(fontFamily ? { fontFamily } : {}),
|
|
135
|
-
...(fontSize ? { fontSize } : {}),
|
|
136
|
-
...(lineHeight ? { lineHeight } : {}),
|
|
137
|
-
};
|
|
138
|
-
};
|
|
139
|
-
export const buildSafeInlineStyle = (attrs) => {
|
|
140
|
-
const safe = sanitizeTextStyleAttrs(attrs);
|
|
141
|
-
if (!safe)
|
|
142
|
-
return undefined;
|
|
143
|
-
const parts = [];
|
|
144
|
-
if (safe.color)
|
|
145
|
-
parts.push(`color: ${safe.color}`);
|
|
146
|
-
if (safe.fontFamily)
|
|
147
|
-
parts.push(`font-family: ${safe.fontFamily}`);
|
|
148
|
-
if (safe.fontSize)
|
|
149
|
-
parts.push(`font-size: ${safe.fontSize}`);
|
|
150
|
-
if (safe.lineHeight)
|
|
151
|
-
parts.push(`line-height: ${safe.lineHeight}`);
|
|
152
|
-
return parts.length > 0 ? parts.join("; ") : undefined;
|
|
153
|
-
};
|
|
154
|
-
export const slugifyHeadingText = (text) => text
|
|
155
|
-
.toLowerCase()
|
|
156
|
-
.trim()
|
|
157
|
-
.replace(/[^\w\s-]/g, "")
|
|
158
|
-
.replace(/[\s_-]+/g, "-")
|
|
159
|
-
.replace(/^-+|-+$/g, "") || "section";
|
|
160
|
-
export const formatFileSize = (bytes) => {
|
|
161
|
-
const num = typeof bytes === "string" ? Number(bytes) : bytes;
|
|
162
|
-
if (!num || Number.isNaN(num) || num <= 0)
|
|
163
|
-
return "";
|
|
164
|
-
if (num < 1024)
|
|
165
|
-
return `${num} B`;
|
|
166
|
-
if (num < 1024 * 1024)
|
|
167
|
-
return `${(num / 1024).toFixed(1)} KB`;
|
|
168
|
-
return `${(num / (1024 * 1024)).toFixed(1)} MB`;
|
|
169
|
-
};
|
|
170
|
-
const LATEX_SYMBOLS = [
|
|
171
|
-
[/\\alpha(?![a-zA-Z])/g, "α"],
|
|
172
|
-
[/\\beta(?![a-zA-Z])/g, "β"],
|
|
173
|
-
[/\\gamma(?![a-zA-Z])/g, "γ"],
|
|
174
|
-
[/\\delta(?![a-zA-Z])/g, "δ"],
|
|
175
|
-
[/\\pi(?![a-zA-Z])/g, "π"],
|
|
176
|
-
[/\\theta(?![a-zA-Z])/g, "θ"],
|
|
177
|
-
[/\\lambda(?![a-zA-Z])/g, "λ"],
|
|
178
|
-
[/\\sigma(?![a-zA-Z])/g, "σ"],
|
|
179
|
-
[/\\omega(?![a-zA-Z])/g, "ω"],
|
|
180
|
-
[/\\infty(?![a-zA-Z])/g, "∞"],
|
|
181
|
-
[/\\sum(?![a-zA-Z])/g, "∑"],
|
|
182
|
-
[/\\prod(?![a-zA-Z])/g, "∏"],
|
|
183
|
-
[/\\int(?![a-zA-Z])/g, "∫"],
|
|
184
|
-
[/\\sqrt\{([^}]+)\}/g, "√($1)"],
|
|
185
|
-
[/\\sqrt(?![a-zA-Z])/g, "√"],
|
|
186
|
-
[/\\frac\{([^}]+)\}\{([^}]+)\}/g, "($1)/($2)"],
|
|
187
|
-
[/\\pm(?![a-zA-Z])/g, "±"],
|
|
188
|
-
[/\\times(?![a-zA-Z])/g, "×"],
|
|
189
|
-
[/\\div(?![a-zA-Z])/g, "÷"],
|
|
190
|
-
[/\\cdot(?![a-zA-Z])/g, "·"],
|
|
191
|
-
[/\\neq(?![a-zA-Z])/g, "≠"],
|
|
192
|
-
[/\\leq(?![a-zA-Z])/g, "≤"],
|
|
193
|
-
[/\\geq(?![a-zA-Z])/g, "≥"],
|
|
194
|
-
[/\\approx(?![a-zA-Z])/g, "≈"],
|
|
195
|
-
[/\\rightarrow(?![a-zA-Z])|\\to(?![a-zA-Z])/g, "→"],
|
|
196
|
-
[/\\leftarrow(?![a-zA-Z])/g, "←"],
|
|
197
|
-
[/\\Rightarrow(?![a-zA-Z])/g, "⇒"],
|
|
198
|
-
];
|
|
199
|
-
export const formatLatexPreview = (latex) => {
|
|
200
|
-
if (!latex)
|
|
201
|
-
return "";
|
|
202
|
-
let result = latex.trim();
|
|
203
|
-
for (const [pattern, replacement] of LATEX_SYMBOLS) {
|
|
204
|
-
result = result.replace(pattern, replacement);
|
|
205
|
-
}
|
|
206
|
-
return result;
|
|
207
|
-
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
export const TiptapBlockThemeDefault = {
|
|
2
|
+
default: "",
|
|
3
|
+
heading: {
|
|
4
|
+
default: "font-bold tracking-tight leading-tight",
|
|
5
|
+
h1: "text-4xl mt-8 mb-4",
|
|
6
|
+
h2: "text-3xl mt-7 mb-3",
|
|
7
|
+
h3: "text-2xl mt-6 mb-3",
|
|
8
|
+
h4: "text-xl mt-5 mb-2",
|
|
9
|
+
h5: "text-lg mt-4 mb-2",
|
|
10
|
+
h6: "text-base mt-3 mb-2",
|
|
11
|
+
},
|
|
12
|
+
paragraph: {
|
|
13
|
+
default: "text-base leading-relaxed my-4",
|
|
14
|
+
link: "underline underline-offset-4 hover:opacity-80 transition-opacity",
|
|
15
|
+
bold: "font-bold",
|
|
16
|
+
italic: "italic",
|
|
17
|
+
underline: "underline",
|
|
18
|
+
strikethrough: "line-through",
|
|
19
|
+
code: "font-mono text-sm px-1.5 py-0.5 rounded bg-black/5 dark:bg-white/10",
|
|
20
|
+
highlight: "bg-yellow-200/70 dark:bg-yellow-500/30 px-1 rounded",
|
|
21
|
+
subscript: "text-xs align-sub",
|
|
22
|
+
superscript: "text-xs align-super",
|
|
23
|
+
abbr: "underline decoration-dotted cursor-help",
|
|
24
|
+
language: "",
|
|
25
|
+
linker: "inline-flex items-center gap-1 underline underline-offset-4 font-medium hover:opacity-80",
|
|
26
|
+
mathInline: "font-mono text-sm px-1.5 py-0.5 rounded bg-indigo-500/10 text-indigo-700 dark:text-indigo-300",
|
|
27
|
+
},
|
|
28
|
+
leadParagraph: { default: "text-xl leading-relaxed my-5 opacity-90 font-normal" },
|
|
29
|
+
quote: { default: "border-s-4 ps-4 italic my-6 opacity-90" },
|
|
30
|
+
informationPane: {
|
|
31
|
+
default: "my-6 p-4 rounded-lg border-s-4",
|
|
32
|
+
info: "bg-blue-500/10 border-blue-500 text-blue-950 dark:text-blue-100",
|
|
33
|
+
success: "bg-emerald-500/10 border-emerald-500 text-emerald-950 dark:text-emerald-100",
|
|
34
|
+
warning: "bg-amber-500/10 border-amber-500 text-amber-950 dark:text-amber-100",
|
|
35
|
+
danger: "bg-rose-500/10 border-rose-500 text-rose-950 dark:text-rose-100",
|
|
36
|
+
neutral: "bg-neutral-500/10 border-neutral-500 text-neutral-900 dark:text-neutral-100",
|
|
37
|
+
tip: "bg-purple-500/10 border-purple-500 text-purple-950 dark:text-purple-100",
|
|
38
|
+
title: "font-semibold mb-1 block",
|
|
39
|
+
body: "space-y-2 text-sm leading-relaxed",
|
|
40
|
+
},
|
|
41
|
+
conversationHistory: {
|
|
42
|
+
default: "my-6 p-4 rounded-lg border bg-black/[0.02] dark:bg-white/[0.03]",
|
|
43
|
+
header: "flex items-center justify-between gap-2 mb-2 text-xs opacity-75",
|
|
44
|
+
author: "font-semibold",
|
|
45
|
+
timestamp: "font-mono",
|
|
46
|
+
body: "space-y-2 text-sm",
|
|
47
|
+
},
|
|
48
|
+
details: {
|
|
49
|
+
default: "my-6 rounded-lg border p-4",
|
|
50
|
+
summary: "font-semibold cursor-pointer select-none",
|
|
51
|
+
content: "mt-3 space-y-2",
|
|
52
|
+
},
|
|
53
|
+
columns: {
|
|
54
|
+
default: "my-6 grid gap-6 md:grid-cols-2",
|
|
55
|
+
column: "min-w-0 space-y-4",
|
|
56
|
+
},
|
|
57
|
+
list: {
|
|
58
|
+
default: "my-4 ps-6 space-y-2",
|
|
59
|
+
ordered: "list-decimal",
|
|
60
|
+
unordered: "list-disc",
|
|
61
|
+
item: "leading-relaxed",
|
|
62
|
+
},
|
|
63
|
+
taskList: {
|
|
64
|
+
default: "my-4 space-y-2 list-none ps-0",
|
|
65
|
+
item: "flex items-start gap-2.5 leading-relaxed",
|
|
66
|
+
checkbox: "mt-1.5 h-4 w-4 rounded border accent-current",
|
|
67
|
+
content: "flex-1",
|
|
68
|
+
checked: "line-through opacity-70",
|
|
69
|
+
},
|
|
70
|
+
code: {
|
|
71
|
+
default: "font-mono text-sm p-4 my-6 rounded-lg overflow-x-auto bg-neutral-900 text-neutral-100",
|
|
72
|
+
line: "block",
|
|
73
|
+
},
|
|
74
|
+
image: {
|
|
75
|
+
default: "my-6",
|
|
76
|
+
image: "max-w-full h-auto rounded-lg",
|
|
77
|
+
caption: "text-sm opacity-75 mt-2 text-center",
|
|
78
|
+
},
|
|
79
|
+
video: {
|
|
80
|
+
default: "my-6",
|
|
81
|
+
video: "w-full rounded-lg",
|
|
82
|
+
iframe: "w-full aspect-video rounded-lg border-0",
|
|
83
|
+
caption: "text-sm opacity-75 mt-2 text-center",
|
|
84
|
+
},
|
|
85
|
+
audio: {
|
|
86
|
+
default: "my-6",
|
|
87
|
+
audio: "w-full",
|
|
88
|
+
caption: "text-sm opacity-75 mt-2 text-center",
|
|
89
|
+
},
|
|
90
|
+
attachment: {
|
|
91
|
+
default: "my-4 flex items-center justify-between gap-4 p-3.5 rounded-lg border",
|
|
92
|
+
badge: "px-2 py-1 text-xs font-mono font-bold uppercase rounded bg-black/10 dark:bg-white/10",
|
|
93
|
+
title: "font-medium text-sm truncate",
|
|
94
|
+
meta: "text-xs opacity-70",
|
|
95
|
+
button: "text-xs font-semibold underline underline-offset-4 shrink-0",
|
|
96
|
+
},
|
|
97
|
+
iframe: { default: "my-6", iframe: "w-full rounded-lg border" },
|
|
98
|
+
mathematics: {
|
|
99
|
+
default: "my-6 p-4 rounded-lg bg-black/[0.03] dark:bg-white/[0.04] overflow-x-auto text-center",
|
|
100
|
+
formula: "font-mono text-base",
|
|
101
|
+
},
|
|
102
|
+
tableOfContents: {
|
|
103
|
+
default: "my-6 p-4 rounded-lg border bg-black/[0.02] dark:bg-white/[0.02]",
|
|
104
|
+
title: "font-semibold text-sm uppercase tracking-wider opacity-75 mb-2",
|
|
105
|
+
list: "space-y-1.5 text-sm list-none ps-0",
|
|
106
|
+
item: "leading-snug",
|
|
107
|
+
link: "underline underline-offset-4 hover:opacity-80",
|
|
108
|
+
},
|
|
109
|
+
pageBreak: { default: "my-8 border-t-2 border-dashed opacity-30 print:break-after-page" },
|
|
110
|
+
table: {
|
|
111
|
+
default: "w-full border-collapse my-6 text-sm",
|
|
112
|
+
head: "border-b-2",
|
|
113
|
+
body: "divide-y",
|
|
114
|
+
row: "transition-colors",
|
|
115
|
+
headerCell: "px-4 py-3 text-start font-semibold",
|
|
116
|
+
cell: "px-4 py-3 align-top",
|
|
117
|
+
},
|
|
118
|
+
hr: { default: "my-8 border-t opacity-20" },
|
|
119
|
+
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { TiptapBlockTheme, TiptapBlockThemeProperty, TiptapBlockThemePropertyValue, TiptapBlockUserTheme } from "../../types";
|
|
2
|
-
|
|
2
|
+
import { TiptapBlockThemeDefault } from "./theme-default";
|
|
3
|
+
export { TiptapBlockThemeDefault };
|
|
4
|
+
export declare const mapNodeTypeToThemeKey: (nodeType: string) => string;
|
|
3
5
|
export declare const getPropertyClass: (value: TiptapBlockThemePropertyValue) => string;
|
|
4
6
|
export declare const renderPropertyClasses: <T extends TiptapBlockThemeProperty>(theme: T, key?: keyof Omit<T, "default"> | Array<keyof Omit<T, "default">>) => string;
|
|
5
7
|
export declare const buildThemeObject: (userTheme?: TiptapBlockUserTheme, defaultTheme?: TiptapBlockTheme) => TiptapBlockTheme;
|
package/dist/lib/theme/theme.js
CHANGED
|
@@ -1,122 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
h6: "text-base mt-3 mb-2",
|
|
11
|
-
},
|
|
12
|
-
paragraph: {
|
|
13
|
-
default: "text-base leading-relaxed my-4",
|
|
14
|
-
link: "underline underline-offset-4 hover:opacity-80 transition-opacity",
|
|
15
|
-
bold: "font-bold",
|
|
16
|
-
italic: "italic",
|
|
17
|
-
underline: "underline",
|
|
18
|
-
strikethrough: "line-through",
|
|
19
|
-
code: "font-mono text-sm px-1.5 py-0.5 rounded bg-black/5 dark:bg-white/10",
|
|
20
|
-
highlight: "bg-yellow-200/70 dark:bg-yellow-500/30 px-1 rounded",
|
|
21
|
-
subscript: "text-xs align-sub",
|
|
22
|
-
superscript: "text-xs align-super",
|
|
23
|
-
abbr: "underline decoration-dotted cursor-help",
|
|
24
|
-
language: "",
|
|
25
|
-
linker: "inline-flex items-center gap-1 underline underline-offset-4 font-medium hover:opacity-80",
|
|
26
|
-
mathInline: "font-mono text-sm px-1.5 py-0.5 rounded bg-indigo-500/10 text-indigo-700 dark:text-indigo-300",
|
|
27
|
-
},
|
|
28
|
-
leadParagraph: { default: "text-xl leading-relaxed my-5 opacity-90 font-normal" },
|
|
29
|
-
quote: { default: "border-s-4 ps-4 italic my-6 opacity-90" },
|
|
30
|
-
informationPane: {
|
|
31
|
-
default: "my-6 p-4 rounded-lg border-s-4",
|
|
32
|
-
info: "bg-blue-500/10 border-blue-500 text-blue-950 dark:text-blue-100",
|
|
33
|
-
success: "bg-emerald-500/10 border-emerald-500 text-emerald-950 dark:text-emerald-100",
|
|
34
|
-
warning: "bg-amber-500/10 border-amber-500 text-amber-950 dark:text-amber-100",
|
|
35
|
-
danger: "bg-rose-500/10 border-rose-500 text-rose-950 dark:text-rose-100",
|
|
36
|
-
neutral: "bg-neutral-500/10 border-neutral-500 text-neutral-900 dark:text-neutral-100",
|
|
37
|
-
tip: "bg-purple-500/10 border-purple-500 text-purple-950 dark:text-purple-100",
|
|
38
|
-
title: "font-semibold mb-1 block",
|
|
39
|
-
body: "space-y-2 text-sm leading-relaxed",
|
|
40
|
-
},
|
|
41
|
-
conversationHistory: {
|
|
42
|
-
default: "my-6 p-4 rounded-lg border bg-black/[0.02] dark:bg-white/[0.03]",
|
|
43
|
-
header: "flex items-center justify-between gap-2 mb-2 text-xs opacity-75",
|
|
44
|
-
author: "font-semibold",
|
|
45
|
-
timestamp: "font-mono",
|
|
46
|
-
body: "space-y-2 text-sm",
|
|
47
|
-
},
|
|
48
|
-
details: {
|
|
49
|
-
default: "my-6 rounded-lg border p-4",
|
|
50
|
-
summary: "font-semibold cursor-pointer select-none",
|
|
51
|
-
content: "mt-3 space-y-2",
|
|
52
|
-
},
|
|
53
|
-
columns: {
|
|
54
|
-
default: "my-6 grid gap-6 md:grid-cols-2",
|
|
55
|
-
column: "min-w-0 space-y-4",
|
|
56
|
-
},
|
|
57
|
-
list: {
|
|
58
|
-
default: "my-4 ps-6 space-y-2",
|
|
59
|
-
ordered: "list-decimal",
|
|
60
|
-
unordered: "list-disc",
|
|
61
|
-
item: "leading-relaxed",
|
|
62
|
-
},
|
|
63
|
-
taskList: {
|
|
64
|
-
default: "my-4 space-y-2 list-none ps-0",
|
|
65
|
-
item: "flex items-start gap-2.5 leading-relaxed",
|
|
66
|
-
checkbox: "mt-1.5 h-4 w-4 rounded border accent-current",
|
|
67
|
-
content: "flex-1",
|
|
68
|
-
checked: "line-through opacity-70",
|
|
69
|
-
},
|
|
70
|
-
code: {
|
|
71
|
-
default: "font-mono text-sm p-4 my-6 rounded-lg overflow-x-auto bg-neutral-900 text-neutral-100",
|
|
72
|
-
line: "block",
|
|
73
|
-
},
|
|
74
|
-
image: {
|
|
75
|
-
default: "my-6",
|
|
76
|
-
image: "max-w-full h-auto rounded-lg",
|
|
77
|
-
caption: "text-sm opacity-75 mt-2 text-center",
|
|
78
|
-
},
|
|
79
|
-
video: {
|
|
80
|
-
default: "my-6",
|
|
81
|
-
video: "w-full rounded-lg",
|
|
82
|
-
iframe: "w-full aspect-video rounded-lg border-0",
|
|
83
|
-
caption: "text-sm opacity-75 mt-2 text-center",
|
|
84
|
-
},
|
|
85
|
-
audio: {
|
|
86
|
-
default: "my-6",
|
|
87
|
-
audio: "w-full",
|
|
88
|
-
caption: "text-sm opacity-75 mt-2 text-center",
|
|
89
|
-
},
|
|
90
|
-
attachment: {
|
|
91
|
-
default: "my-4 flex items-center justify-between gap-4 p-3.5 rounded-lg border",
|
|
92
|
-
badge: "px-2 py-1 text-xs font-mono font-bold uppercase rounded bg-black/10 dark:bg-white/10",
|
|
93
|
-
title: "font-medium text-sm truncate",
|
|
94
|
-
meta: "text-xs opacity-70",
|
|
95
|
-
button: "text-xs font-semibold underline underline-offset-4 shrink-0",
|
|
96
|
-
},
|
|
97
|
-
iframe: { default: "my-6", iframe: "w-full rounded-lg border" },
|
|
98
|
-
mathematics: {
|
|
99
|
-
default: "my-6 p-4 rounded-lg bg-black/[0.03] dark:bg-white/[0.04] overflow-x-auto text-center",
|
|
100
|
-
formula: "font-mono text-base",
|
|
101
|
-
},
|
|
102
|
-
tableOfContents: {
|
|
103
|
-
default: "my-6 p-4 rounded-lg border bg-black/[0.02] dark:bg-white/[0.02]",
|
|
104
|
-
title: "font-semibold text-sm uppercase tracking-wider opacity-75 mb-2",
|
|
105
|
-
list: "space-y-1.5 text-sm list-none ps-0",
|
|
106
|
-
item: "leading-snug",
|
|
107
|
-
link: "underline underline-offset-4 hover:opacity-80",
|
|
108
|
-
},
|
|
109
|
-
pageBreak: { default: "my-8 border-t-2 border-dashed opacity-30 print:break-after-page" },
|
|
110
|
-
table: {
|
|
111
|
-
default: "w-full border-collapse my-6 text-sm",
|
|
112
|
-
head: "border-b-2",
|
|
113
|
-
body: "divide-y",
|
|
114
|
-
row: "transition-colors",
|
|
115
|
-
headerCell: "px-4 py-3 text-start font-semibold",
|
|
116
|
-
cell: "px-4 py-3 align-top",
|
|
117
|
-
},
|
|
118
|
-
hr: { default: "my-8 border-t opacity-20" },
|
|
1
|
+
import { TiptapBlockThemeDefault } from "./theme-default";
|
|
2
|
+
export { TiptapBlockThemeDefault };
|
|
3
|
+
const NODE_TYPE_TO_THEME_KEY = {
|
|
4
|
+
blockquote: "quote",
|
|
5
|
+
layoutContainer: "columns",
|
|
6
|
+
codeBlock: "code",
|
|
7
|
+
bulletList: "list",
|
|
8
|
+
orderedList: "list",
|
|
9
|
+
horizontalRule: "hr",
|
|
119
10
|
};
|
|
11
|
+
export const mapNodeTypeToThemeKey = (nodeType) => NODE_TYPE_TO_THEME_KEY[nodeType] ?? nodeType;
|
|
120
12
|
export const getPropertyClass = (value) => {
|
|
121
13
|
if (!value)
|
|
122
14
|
return "";
|
package/dist/types/custom.d.ts
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
import type { AstroComponentFactory } from "astro/runtime/server/index.js";
|
|
2
2
|
import type { DeepPartial } from "./common";
|
|
3
3
|
import type { TiptapBlockThemeCustomComponents } from "./theme";
|
|
4
|
+
import type { TiptapInternalLinkResolver, TiptapMediaUrlResolver } from "./tiptap-inline";
|
|
4
5
|
export type TiptapBlockUserComponents = DeepPartial<TiptapBlockThemeCustomComponents> & {
|
|
5
6
|
[customBlockType: string]: AstroComponentFactory | undefined;
|
|
6
7
|
};
|
|
8
|
+
export type TiptapRenderContextProps = {
|
|
9
|
+
/** Optional custom Astro block component overrides. @default {} */
|
|
10
|
+
customBlocks?: TiptapBlockUserComponents;
|
|
11
|
+
/** Renders an accessible `#` permalink anchor inside headings. @default false */
|
|
12
|
+
headingAnchors?: boolean;
|
|
13
|
+
/** Adds `data-line-number` attributes to code blocks. @default false */
|
|
14
|
+
showCodeLineNumbers?: boolean;
|
|
15
|
+
/** Renders a header bar with copy button on code blocks. @default false */
|
|
16
|
+
copyableCodeBlocks?: boolean;
|
|
17
|
+
/** Optional media base URL prefixed to relative `/uploads/...` media URLs. */
|
|
18
|
+
mediaBaseUrl?: string;
|
|
19
|
+
/** Optional custom resolver for media URLs before sanitization. */
|
|
20
|
+
resolveMediaUrl?: TiptapMediaUrlResolver;
|
|
21
|
+
/** Optional domain allowlist for HTTPS `<iframe>` and video embeds. */
|
|
22
|
+
allowedIframeDomains?: ReadonlyArray<string>;
|
|
23
|
+
/** Optional callback to resolve internal links (`tiptap://collectionName/documentId` and `linker` nodes). */
|
|
24
|
+
resolveInternalLink?: TiptapInternalLinkResolver;
|
|
25
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { TiptapBlockNode } from "./tiptap";
|
|
2
|
+
import type { TiptapElementAlign, TiptapHeadingLevel, TiptapInternalLinkResolver, TiptapMediaUrlResolver } from "./tiptap-inline";
|
|
3
|
+
export type TiptapMediaCommonAttrs = {
|
|
4
|
+
"data-align"?: TiptapElementAlign | null;
|
|
5
|
+
"data-asset-id"?: string | number | null;
|
|
6
|
+
"data-document-id"?: string | null;
|
|
7
|
+
};
|
|
8
|
+
export type TiptapImageNode = {
|
|
9
|
+
type: "image";
|
|
10
|
+
attrs: TiptapMediaCommonAttrs & {
|
|
11
|
+
src: string;
|
|
12
|
+
alt?: string | null;
|
|
13
|
+
title?: string | null;
|
|
14
|
+
caption?: string | null;
|
|
15
|
+
width?: number | string | null;
|
|
16
|
+
height?: number | string | null;
|
|
17
|
+
srcset?: string | null;
|
|
18
|
+
sizes?: string | null;
|
|
19
|
+
loading?: "lazy" | "eager" | null;
|
|
20
|
+
decoding?: "async" | "sync" | "auto" | null;
|
|
21
|
+
fetchpriority?: "high" | "low" | "auto" | null;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
export type TiptapVideoNode = {
|
|
25
|
+
type: "video";
|
|
26
|
+
attrs: TiptapMediaCommonAttrs & {
|
|
27
|
+
src: string;
|
|
28
|
+
embedType?: "embed" | "file" | null;
|
|
29
|
+
title?: string | null;
|
|
30
|
+
caption?: string | null;
|
|
31
|
+
width?: number | string | null;
|
|
32
|
+
height?: number | string | null;
|
|
33
|
+
mime?: string | null;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
export type TiptapAudioNode = {
|
|
37
|
+
type: "audio";
|
|
38
|
+
attrs: TiptapMediaCommonAttrs & {
|
|
39
|
+
src: string;
|
|
40
|
+
title?: string | null;
|
|
41
|
+
caption?: string | null;
|
|
42
|
+
mime?: string | null;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
export type TiptapAttachmentNode = {
|
|
46
|
+
type: "attachment";
|
|
47
|
+
attrs: TiptapMediaCommonAttrs & {
|
|
48
|
+
href: string;
|
|
49
|
+
filename?: string | null;
|
|
50
|
+
title?: string | null;
|
|
51
|
+
size?: number | string | null;
|
|
52
|
+
mime?: string | null;
|
|
53
|
+
ext?: string | null;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
export type TiptapIframeNode = {
|
|
57
|
+
type: "iframe";
|
|
58
|
+
attrs: {
|
|
59
|
+
"src": string;
|
|
60
|
+
"title"?: string | null;
|
|
61
|
+
"width"?: number | string | null;
|
|
62
|
+
"height"?: number | string | null;
|
|
63
|
+
"data-align"?: TiptapElementAlign | null;
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
export type TiptapMediaBlockAttrs = TiptapMediaCommonAttrs & {
|
|
67
|
+
src?: string;
|
|
68
|
+
href?: string;
|
|
69
|
+
embedType?: "embed" | "file" | null;
|
|
70
|
+
title?: string | null;
|
|
71
|
+
caption?: string | null;
|
|
72
|
+
filename?: string | null;
|
|
73
|
+
size?: number | string | null;
|
|
74
|
+
mime?: string | null;
|
|
75
|
+
ext?: string | null;
|
|
76
|
+
width?: number | string | null;
|
|
77
|
+
height?: number | string | null;
|
|
78
|
+
};
|
|
79
|
+
export type TiptapCustomBlockNode = {
|
|
80
|
+
type: string;
|
|
81
|
+
attrs?: Record<string, unknown>;
|
|
82
|
+
content?: Array<TiptapBlockNode>;
|
|
83
|
+
};
|
|
84
|
+
export type TiptapTocHeadingItem = {
|
|
85
|
+
id: string;
|
|
86
|
+
text: string;
|
|
87
|
+
level: TiptapHeadingLevel;
|
|
88
|
+
};
|
|
89
|
+
export type TiptapTableOfContentsNode = {
|
|
90
|
+
type: "tableOfContents";
|
|
91
|
+
attrs?: {
|
|
92
|
+
headings?: Array<TiptapTocHeadingItem>;
|
|
93
|
+
title?: string | null;
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
export type TiptapPageBreakNode = {
|
|
97
|
+
type: "pageBreak";
|
|
98
|
+
};
|
|
99
|
+
export type TiptapHorizontalRuleNode = {
|
|
100
|
+
type: "horizontalRule";
|
|
101
|
+
};
|
|
102
|
+
export type TiptapTableCellNode = {
|
|
103
|
+
type: "tableHeader" | "tableCell";
|
|
104
|
+
attrs?: {
|
|
105
|
+
colspan?: number;
|
|
106
|
+
rowspan?: number;
|
|
107
|
+
colwidth?: Array<number> | null;
|
|
108
|
+
backgroundColor?: string | null;
|
|
109
|
+
};
|
|
110
|
+
content?: Array<TiptapBlockNode>;
|
|
111
|
+
};
|
|
112
|
+
export type TiptapTableRowNode = {
|
|
113
|
+
type: "tableRow";
|
|
114
|
+
content?: Array<TiptapTableCellNode>;
|
|
115
|
+
};
|
|
116
|
+
export type TiptapTableNode = {
|
|
117
|
+
type: "table";
|
|
118
|
+
content?: Array<TiptapTableRowNode>;
|
|
119
|
+
};
|
|
120
|
+
export type TiptapDocStats = {
|
|
121
|
+
words: number;
|
|
122
|
+
characters: number;
|
|
123
|
+
readingTimeMinutes: number;
|
|
124
|
+
readingTime?: string;
|
|
125
|
+
wordsPerMinute?: number;
|
|
126
|
+
};
|
|
127
|
+
export type TiptapDocNode = {
|
|
128
|
+
type: "doc";
|
|
129
|
+
content: Array<TiptapBlockNode>;
|
|
130
|
+
words?: number;
|
|
131
|
+
characters?: number;
|
|
132
|
+
readingTimeMinutes?: number;
|
|
133
|
+
readingTime?: string;
|
|
134
|
+
stats?: TiptapDocStats;
|
|
135
|
+
};
|
|
136
|
+
export type TiptapContentInput = TiptapDocNode | {
|
|
137
|
+
type: "doc";
|
|
138
|
+
content: Array<TiptapBlockNode | TiptapCustomBlockNode>;
|
|
139
|
+
} | Array<TiptapBlockNode | TiptapCustomBlockNode> | string | ReadonlyArray<string> | null | undefined;
|
|
140
|
+
export type TiptapParserOptions = {
|
|
141
|
+
allowedIframeDomains?: ReadonlyArray<string>;
|
|
142
|
+
resolveInternalLink?: TiptapInternalLinkResolver;
|
|
143
|
+
mediaBaseUrl?: string;
|
|
144
|
+
resolveMediaUrl?: TiptapMediaUrlResolver;
|
|
145
|
+
};
|
|
146
|
+
export type { TiptapMediaUrlResolver };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|