@editorcn/static-renderer 0.2.2 → 0.2.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/dist/index.cjs +140 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -2
- package/dist/index.d.ts +5 -2
- package/dist/index.js +138 -2
- package/dist/index.js.map +1 -1
- package/package.json +16 -3
package/dist/index.cjs
CHANGED
|
@@ -20,7 +20,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
-
StaticRenderer: () => StaticRenderer
|
|
23
|
+
StaticRenderer: () => StaticRenderer,
|
|
24
|
+
sanitizeHtml: () => sanitizeHtml
|
|
24
25
|
});
|
|
25
26
|
module.exports = __toCommonJS(index_exports);
|
|
26
27
|
|
|
@@ -33,7 +34,141 @@ var import_clsx = require("clsx");
|
|
|
33
34
|
var import_tailwind_merge = require("tailwind-merge");
|
|
34
35
|
var cn = (...inputs) => (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
|
|
35
36
|
|
|
37
|
+
// src/sanitize.ts
|
|
38
|
+
var import_xss = require("xss");
|
|
39
|
+
var EMBED_HOSTS = /* @__PURE__ */ new Set([
|
|
40
|
+
"www.youtube.com",
|
|
41
|
+
"youtube.com",
|
|
42
|
+
"www.youtube-nocookie.com",
|
|
43
|
+
"youtube-nocookie.com",
|
|
44
|
+
"player.vimeo.com",
|
|
45
|
+
"platform.twitter.com"
|
|
46
|
+
]);
|
|
47
|
+
var RASTER_DATA_IMAGE = /^data:image\/(?:png|jpe?g|gif|webp|avif)[;,]/i;
|
|
48
|
+
var DATA_URL = /^data:/i;
|
|
49
|
+
var toCodePoint = (code) => code <= 1114111 ? String.fromCodePoint(code) : "";
|
|
50
|
+
var decodeUrl = (value) => [
|
|
51
|
+
...value.replaceAll(
|
|
52
|
+
/&#x([0-9a-f]+);?/gi,
|
|
53
|
+
(_, hex) => toCodePoint(Number.parseInt(hex, 16))
|
|
54
|
+
).replaceAll(/&#(\d+);?/g, (_, dec) => toCodePoint(Number(dec))).replaceAll(/:/gi, ":")
|
|
55
|
+
].filter((char) => (char.codePointAt(0) ?? 0) > 32).join("");
|
|
56
|
+
var isEmbedUrl = (value) => {
|
|
57
|
+
try {
|
|
58
|
+
const url = new URL(value);
|
|
59
|
+
return url.protocol === "https:" && EMBED_HOSTS.has(url.hostname);
|
|
60
|
+
} catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
var EMBED_ATTRS = [
|
|
65
|
+
"data-type",
|
|
66
|
+
"data-src",
|
|
67
|
+
"data-tweet-id",
|
|
68
|
+
"data-width",
|
|
69
|
+
"data-height",
|
|
70
|
+
"data-align",
|
|
71
|
+
"data-youtube-video"
|
|
72
|
+
];
|
|
73
|
+
var filter = new import_xss.FilterXSS({
|
|
74
|
+
css: {
|
|
75
|
+
whiteList: {
|
|
76
|
+
"background-color": true,
|
|
77
|
+
color: true,
|
|
78
|
+
"font-family": true,
|
|
79
|
+
"font-size": true,
|
|
80
|
+
"line-height": true,
|
|
81
|
+
"min-width": true,
|
|
82
|
+
"text-align": true,
|
|
83
|
+
width: true
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
onTagAttr: (tag, name, value) => {
|
|
87
|
+
if (tag === "iframe" && name === "src") {
|
|
88
|
+
return isEmbedUrl(value) ? void 0 : "";
|
|
89
|
+
}
|
|
90
|
+
if (name !== "src" && name !== "href") {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const url = decodeUrl(value);
|
|
94
|
+
if (DATA_URL.test(url)) {
|
|
95
|
+
const allowed = tag === "img" && name === "src" && RASTER_DATA_IMAGE.test(url);
|
|
96
|
+
return allowed ? void 0 : "";
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
stripIgnoreTag: true,
|
|
100
|
+
stripIgnoreTagBody: ["script", "style"],
|
|
101
|
+
whiteList: {
|
|
102
|
+
a: ["href", "target", "rel"],
|
|
103
|
+
blockquote: [],
|
|
104
|
+
br: [],
|
|
105
|
+
code: ["class"],
|
|
106
|
+
col: ["span", "style"],
|
|
107
|
+
colgroup: [],
|
|
108
|
+
div: [...EMBED_ATTRS, "style"],
|
|
109
|
+
em: [],
|
|
110
|
+
h1: ["style"],
|
|
111
|
+
h2: ["style"],
|
|
112
|
+
h3: ["style"],
|
|
113
|
+
h4: ["style"],
|
|
114
|
+
h5: ["style"],
|
|
115
|
+
h6: ["style"],
|
|
116
|
+
hr: [],
|
|
117
|
+
iframe: ["src", "width", "height", "title", "allowfullscreen"],
|
|
118
|
+
img: ["src", "alt", "title", "width", "height", "style"],
|
|
119
|
+
input: ["type", "checked", "disabled"],
|
|
120
|
+
label: [],
|
|
121
|
+
li: ["data-type", "data-checked"],
|
|
122
|
+
mark: ["data-color", "style"],
|
|
123
|
+
ol: ["start", "type"],
|
|
124
|
+
p: ["style"],
|
|
125
|
+
pre: [],
|
|
126
|
+
s: [],
|
|
127
|
+
span: ["style"],
|
|
128
|
+
strong: [],
|
|
129
|
+
sub: [],
|
|
130
|
+
sup: [],
|
|
131
|
+
table: ["style"],
|
|
132
|
+
tbody: [],
|
|
133
|
+
td: ["colspan", "rowspan", "colwidth", "style"],
|
|
134
|
+
tfoot: [],
|
|
135
|
+
th: ["colspan", "rowspan", "colwidth", "style"],
|
|
136
|
+
thead: [],
|
|
137
|
+
tr: [],
|
|
138
|
+
u: [],
|
|
139
|
+
ul: ["data-type"]
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
var sanitizeHtml = (html) => filter.process(html);
|
|
143
|
+
|
|
36
144
|
// src/static-renderer.tsx
|
|
145
|
+
var warnedUnsanitized = false;
|
|
146
|
+
var isProduction = () => {
|
|
147
|
+
try {
|
|
148
|
+
return process.env.NODE_ENV === "production";
|
|
149
|
+
} catch {
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
var warnUnsanitized = () => {
|
|
154
|
+
if (warnedUnsanitized || isProduction()) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
warnedUnsanitized = true;
|
|
158
|
+
console.warn(
|
|
159
|
+
"StaticRenderer: rendering an HTML string without `sanitize`. Pass `sanitize` when the HTML can come from users, or `sanitize={false}` if it is trusted."
|
|
160
|
+
);
|
|
161
|
+
};
|
|
162
|
+
var toSafeHtml = (html, sanitize) => {
|
|
163
|
+
if (sanitize === void 0) {
|
|
164
|
+
warnUnsanitized();
|
|
165
|
+
return html;
|
|
166
|
+
}
|
|
167
|
+
if (typeof sanitize === "function") {
|
|
168
|
+
return sanitize(html);
|
|
169
|
+
}
|
|
170
|
+
return sanitize ? sanitizeHtml(html) : html;
|
|
171
|
+
};
|
|
37
172
|
var resolveUniqueExtensions = (extensions) => {
|
|
38
173
|
const byName = /* @__PURE__ */ new Map();
|
|
39
174
|
for (const extension of extensions) {
|
|
@@ -48,13 +183,14 @@ var StaticRenderer = ({
|
|
|
48
183
|
extensions,
|
|
49
184
|
staticEditorOptions,
|
|
50
185
|
options,
|
|
186
|
+
sanitize,
|
|
51
187
|
...props
|
|
52
188
|
}) => {
|
|
53
189
|
if (typeof content === "string") {
|
|
54
190
|
return (0, import_react.createElement)(Tag, {
|
|
55
191
|
...props,
|
|
56
192
|
className: cn("rte-static-renderer", className),
|
|
57
|
-
dangerouslySetInnerHTML: { __html: content }
|
|
193
|
+
dangerouslySetInnerHTML: { __html: toSafeHtml(content, sanitize) }
|
|
58
194
|
});
|
|
59
195
|
}
|
|
60
196
|
const children = (0, import_static_renderer.renderToReactElement)({
|
|
@@ -71,6 +207,7 @@ var StaticRenderer = ({
|
|
|
71
207
|
};
|
|
72
208
|
// Annotate the CommonJS export names for ESM import in node:
|
|
73
209
|
0 && (module.exports = {
|
|
74
|
-
StaticRenderer
|
|
210
|
+
StaticRenderer,
|
|
211
|
+
sanitizeHtml
|
|
75
212
|
});
|
|
76
213
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/static-renderer.tsx","../src/lib/utils.ts"],"sourcesContent":["export * from \"./static-renderer\";\r\n","import type { Extensions, JSONContent } from \"@tiptap/core\";\r\nimport type { Mark, Node } from \"@tiptap/pm/model\";\r\nimport { renderToReactElement } from \"@tiptap/static-renderer\";\r\nimport type {\r\n StaticEditorOptions,\r\n TiptapStaticRendererOptions,\r\n} from \"@tiptap/static-renderer\";\r\nimport { createElement } from \"react\";\r\nimport type { ElementType, HTMLAttributes, ReactNode } from \"react\";\r\n\r\nimport { cn } from \"./lib/utils\";\r\n\r\nconst resolveUniqueExtensions = (extensions: Extensions) => {\r\n const byName = new Map<string, (typeof extensions)[number]>();\r\n for (const extension of extensions) {\r\n byName.set(extension.name, extension);\r\n }\r\n return [...byName.values()];\r\n};\r\n\r\nexport type StaticRendererProps = Omit<\r\n HTMLAttributes<HTMLElement>,\r\n \"content\" | \"dangerouslySetInnerHTML\"\r\n> & {\r\n // HTML from editor.getHTML() rendered read-only, or a ProseMirror JSON document.\r\n content: string | JSONContent | Node;\r\n // Root element, defaults to \"div\".\r\n as?: ElementType;\r\n // Tiptap extensions used to render JSON content; every node/mark in the document must be registered.\r\n extensions?: Extensions;\r\n // Editor-level options applied while rendering JSON.\r\n staticEditorOptions?: StaticEditorOptions;\r\n // renderToReactElement options — nodeMapping/markMapping for custom nodes.\r\n options?: Partial<TiptapStaticRendererOptions<ReactNode, Mark, Node>>;\r\n};\r\n\r\nexport const StaticRenderer = ({\r\n as: Tag = \"div\",\r\n className,\r\n content,\r\n extensions,\r\n staticEditorOptions,\r\n options,\r\n ...props\r\n}: StaticRendererProps) => {\r\n if (typeof content === \"string\") {\r\n return createElement(Tag, {\r\n ...props,\r\n className: cn(\"rte-static-renderer\", className),\r\n dangerouslySetInnerHTML: { __html: content },\r\n });\r\n }\r\n\r\n const children = renderToReactElement({\r\n content,\r\n extensions: resolveUniqueExtensions(extensions ?? []),\r\n options,\r\n staticEditorOptions,\r\n });\r\n\r\n return createElement(\r\n Tag,\r\n { ...props, className: cn(\"rte-static-renderer\", className) },\r\n children\r\n );\r\n};\r\n","import { clsx } from \"clsx\";\r\nimport type { ClassValue } from \"clsx\";\r\nimport { twMerge } from \"tailwind-merge\";\r\n\r\nexport const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs));\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEA,6BAAqC;AAKrC,mBAA8B;;;ACP9B,kBAAqB;AAErB,4BAAwB;AAEjB,IAAM,KAAK,IAAI,eAAyB,mCAAQ,kBAAK,MAAM,CAAC;;;ADQnE,IAAM,0BAA0B,CAAC,eAA2B;AAC1D,QAAM,SAAS,oBAAI,IAAyC;AAC5D,aAAW,aAAa,YAAY;AAClC,WAAO,IAAI,UAAU,MAAM,SAAS;AAAA,EACtC;AACA,SAAO,CAAC,GAAG,OAAO,OAAO,CAAC;AAC5B;AAkBO,IAAM,iBAAiB,CAAC;AAAA,EAC7B,IAAI,MAAM;AAAA,EACV;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,MAA2B;AACzB,MAAI,OAAO,YAAY,UAAU;AAC/B,eAAO,4BAAc,KAAK;AAAA,MACxB,GAAG;AAAA,MACH,WAAW,GAAG,uBAAuB,SAAS;AAAA,MAC9C,yBAAyB,EAAE,QAAQ,QAAQ;AAAA,IAC7C,CAAC;AAAA,EACH;AAEA,QAAM,eAAW,6CAAqB;AAAA,IACpC;AAAA,IACA,YAAY,wBAAwB,cAAc,CAAC,CAAC;AAAA,IACpD;AAAA,IACA;AAAA,EACF,CAAC;AAED,aAAO;AAAA,IACL;AAAA,IACA,EAAE,GAAG,OAAO,WAAW,GAAG,uBAAuB,SAAS,EAAE;AAAA,IAC5D;AAAA,EACF;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/static-renderer.tsx","../src/lib/utils.ts","../src/sanitize.ts"],"sourcesContent":["export * from \"./static-renderer\";\r\nexport { sanitizeHtml } from \"./sanitize\";\r\n","import type { Extensions, JSONContent } from \"@tiptap/core\";\nimport type { Mark, Node } from \"@tiptap/pm/model\";\nimport { renderToReactElement } from \"@tiptap/static-renderer\";\nimport type {\n StaticEditorOptions,\n TiptapStaticRendererOptions,\n} from \"@tiptap/static-renderer\";\nimport { createElement } from \"react\";\nimport type { ElementType, HTMLAttributes, ReactNode } from \"react\";\n\nimport { cn } from \"./lib/utils\";\nimport { sanitizeHtml } from \"./sanitize\";\n\ndeclare const process: { env: { NODE_ENV?: string } };\n\nlet warnedUnsanitized = false;\n\nconst isProduction = () => {\n try {\n return process.env.NODE_ENV === \"production\";\n } catch {\n return true;\n }\n};\n\nconst warnUnsanitized = () => {\n if (warnedUnsanitized || isProduction()) {\n return;\n }\n warnedUnsanitized = true;\n console.warn(\n \"StaticRenderer: rendering an HTML string without `sanitize`. Pass `sanitize` when the HTML can come from users, or `sanitize={false}` if it is trusted.\"\n );\n};\n\nconst toSafeHtml = (\n html: string,\n sanitize: StaticRendererProps[\"sanitize\"]\n) => {\n if (sanitize === undefined) {\n warnUnsanitized();\n return html;\n }\n if (typeof sanitize === \"function\") {\n return sanitize(html);\n }\n return sanitize ? sanitizeHtml(html) : html;\n};\n\nconst resolveUniqueExtensions = (extensions: Extensions) => {\n const byName = new Map<string, (typeof extensions)[number]>();\n for (const extension of extensions) {\n byName.set(extension.name, extension);\n }\n return [...byName.values()];\n};\n\nexport type StaticRendererProps = Omit<\n HTMLAttributes<HTMLElement>,\n \"content\" | \"dangerouslySetInnerHTML\"\n> & {\n // HTML from editor.getHTML() rendered read-only, or a ProseMirror JSON document.\n content: string | JSONContent | Node;\n // Root element, defaults to \"div\".\n as?: ElementType;\n // Tiptap extensions used to render JSON content; every node/mark in the document must be registered.\n extensions?: Extensions;\n // Editor-level options applied while rendering JSON.\n staticEditorOptions?: StaticEditorOptions;\n // renderToReactElement options — nodeMapping/markMapping for custom nodes.\n options?: Partial<TiptapStaticRendererOptions<ReactNode, Mark, Node>>;\n sanitize?: boolean | ((html: string) => string);\n};\n\nexport const StaticRenderer = ({\n as: Tag = \"div\",\n className,\n content,\n extensions,\n staticEditorOptions,\n options,\n sanitize,\n ...props\n}: StaticRendererProps) => {\n if (typeof content === \"string\") {\n return createElement(Tag, {\n ...props,\n className: cn(\"rte-static-renderer\", className),\n dangerouslySetInnerHTML: { __html: toSafeHtml(content, sanitize) },\n });\n }\n\n const children = renderToReactElement({\n content,\n extensions: resolveUniqueExtensions(extensions ?? []),\n options,\n staticEditorOptions,\n });\n\n return createElement(\n Tag,\n { ...props, className: cn(\"rte-static-renderer\", className) },\n children\n );\n};\n","import { clsx } from \"clsx\";\r\nimport type { ClassValue } from \"clsx\";\r\nimport { twMerge } from \"tailwind-merge\";\r\n\r\nexport const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs));\r\n","import { FilterXSS } from \"xss\";\n\nconst EMBED_HOSTS = new Set([\n \"www.youtube.com\",\n \"youtube.com\",\n \"www.youtube-nocookie.com\",\n \"youtube-nocookie.com\",\n \"player.vimeo.com\",\n \"platform.twitter.com\",\n]);\n\nconst RASTER_DATA_IMAGE = /^data:image\\/(?:png|jpe?g|gif|webp|avif)[;,]/i;\nconst DATA_URL = /^data:/i;\n\nconst toCodePoint = (code: number) =>\n code <= 1_114_111 ? String.fromCodePoint(code) : \"\";\n\nconst decodeUrl = (value: string) =>\n [\n ...value\n .replaceAll(/&#x([0-9a-f]+);?/gi, (_, hex: string) =>\n toCodePoint(Number.parseInt(hex, 16))\n )\n .replaceAll(/&#(\\d+);?/g, (_, dec: string) => toCodePoint(Number(dec)))\n .replaceAll(/:/gi, \":\"),\n ]\n .filter((char) => (char.codePointAt(0) ?? 0) > 0x20)\n .join(\"\");\n\nconst isEmbedUrl = (value: string) => {\n try {\n const url = new URL(value);\n return url.protocol === \"https:\" && EMBED_HOSTS.has(url.hostname);\n } catch {\n return false;\n }\n};\n\nconst EMBED_ATTRS = [\n \"data-type\",\n \"data-src\",\n \"data-tweet-id\",\n \"data-width\",\n \"data-height\",\n \"data-align\",\n \"data-youtube-video\",\n];\n\nconst filter = new FilterXSS({\n css: {\n whiteList: {\n \"background-color\": true,\n color: true,\n \"font-family\": true,\n \"font-size\": true,\n \"line-height\": true,\n \"min-width\": true,\n \"text-align\": true,\n width: true,\n },\n },\n onTagAttr: (tag, name, value) => {\n if (tag === \"iframe\" && name === \"src\") {\n return isEmbedUrl(value) ? undefined : \"\";\n }\n if (name !== \"src\" && name !== \"href\") {\n return;\n }\n const url = decodeUrl(value);\n if (DATA_URL.test(url)) {\n const allowed =\n tag === \"img\" && name === \"src\" && RASTER_DATA_IMAGE.test(url);\n return allowed ? undefined : \"\";\n }\n },\n stripIgnoreTag: true,\n stripIgnoreTagBody: [\"script\", \"style\"],\n whiteList: {\n a: [\"href\", \"target\", \"rel\"],\n blockquote: [],\n br: [],\n code: [\"class\"],\n col: [\"span\", \"style\"],\n colgroup: [],\n div: [...EMBED_ATTRS, \"style\"],\n em: [],\n h1: [\"style\"],\n h2: [\"style\"],\n h3: [\"style\"],\n h4: [\"style\"],\n h5: [\"style\"],\n h6: [\"style\"],\n hr: [],\n iframe: [\"src\", \"width\", \"height\", \"title\", \"allowfullscreen\"],\n img: [\"src\", \"alt\", \"title\", \"width\", \"height\", \"style\"],\n input: [\"type\", \"checked\", \"disabled\"],\n label: [],\n li: [\"data-type\", \"data-checked\"],\n mark: [\"data-color\", \"style\"],\n ol: [\"start\", \"type\"],\n p: [\"style\"],\n pre: [],\n s: [],\n span: [\"style\"],\n strong: [],\n sub: [],\n sup: [],\n table: [\"style\"],\n tbody: [],\n td: [\"colspan\", \"rowspan\", \"colwidth\", \"style\"],\n tfoot: [],\n th: [\"colspan\", \"rowspan\", \"colwidth\", \"style\"],\n thead: [],\n tr: [],\n u: [],\n ul: [\"data-type\"],\n },\n});\n\nexport const sanitizeHtml = (html: string) => filter.process(html);\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -10,7 +10,10 @@ type StaticRendererProps = Omit<HTMLAttributes<HTMLElement>, "content" | "danger
|
|
|
10
10
|
extensions?: Extensions;
|
|
11
11
|
staticEditorOptions?: StaticEditorOptions;
|
|
12
12
|
options?: Partial<TiptapStaticRendererOptions<ReactNode, Mark, Node>>;
|
|
13
|
+
sanitize?: boolean | ((html: string) => string);
|
|
13
14
|
};
|
|
14
|
-
declare const StaticRenderer: ({ as: Tag, className, content, extensions, staticEditorOptions, options, ...props }: StaticRendererProps) => react.ReactElement<any, string | react.JSXElementConstructor<any>>;
|
|
15
|
+
declare const StaticRenderer: ({ as: Tag, className, content, extensions, staticEditorOptions, options, sanitize, ...props }: StaticRendererProps) => react.ReactElement<any, string | react.JSXElementConstructor<any>>;
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
declare const sanitizeHtml: (html: string) => string;
|
|
18
|
+
|
|
19
|
+
export { StaticRenderer, type StaticRendererProps, sanitizeHtml };
|
package/dist/index.d.ts
CHANGED
|
@@ -10,7 +10,10 @@ type StaticRendererProps = Omit<HTMLAttributes<HTMLElement>, "content" | "danger
|
|
|
10
10
|
extensions?: Extensions;
|
|
11
11
|
staticEditorOptions?: StaticEditorOptions;
|
|
12
12
|
options?: Partial<TiptapStaticRendererOptions<ReactNode, Mark, Node>>;
|
|
13
|
+
sanitize?: boolean | ((html: string) => string);
|
|
13
14
|
};
|
|
14
|
-
declare const StaticRenderer: ({ as: Tag, className, content, extensions, staticEditorOptions, options, ...props }: StaticRendererProps) => react.ReactElement<any, string | react.JSXElementConstructor<any>>;
|
|
15
|
+
declare const StaticRenderer: ({ as: Tag, className, content, extensions, staticEditorOptions, options, sanitize, ...props }: StaticRendererProps) => react.ReactElement<any, string | react.JSXElementConstructor<any>>;
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
declare const sanitizeHtml: (html: string) => string;
|
|
18
|
+
|
|
19
|
+
export { StaticRenderer, type StaticRendererProps, sanitizeHtml };
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,141 @@ import { clsx } from "clsx";
|
|
|
7
7
|
import { twMerge } from "tailwind-merge";
|
|
8
8
|
var cn = (...inputs) => twMerge(clsx(inputs));
|
|
9
9
|
|
|
10
|
+
// src/sanitize.ts
|
|
11
|
+
import { FilterXSS } from "xss";
|
|
12
|
+
var EMBED_HOSTS = /* @__PURE__ */ new Set([
|
|
13
|
+
"www.youtube.com",
|
|
14
|
+
"youtube.com",
|
|
15
|
+
"www.youtube-nocookie.com",
|
|
16
|
+
"youtube-nocookie.com",
|
|
17
|
+
"player.vimeo.com",
|
|
18
|
+
"platform.twitter.com"
|
|
19
|
+
]);
|
|
20
|
+
var RASTER_DATA_IMAGE = /^data:image\/(?:png|jpe?g|gif|webp|avif)[;,]/i;
|
|
21
|
+
var DATA_URL = /^data:/i;
|
|
22
|
+
var toCodePoint = (code) => code <= 1114111 ? String.fromCodePoint(code) : "";
|
|
23
|
+
var decodeUrl = (value) => [
|
|
24
|
+
...value.replaceAll(
|
|
25
|
+
/&#x([0-9a-f]+);?/gi,
|
|
26
|
+
(_, hex) => toCodePoint(Number.parseInt(hex, 16))
|
|
27
|
+
).replaceAll(/&#(\d+);?/g, (_, dec) => toCodePoint(Number(dec))).replaceAll(/:/gi, ":")
|
|
28
|
+
].filter((char) => (char.codePointAt(0) ?? 0) > 32).join("");
|
|
29
|
+
var isEmbedUrl = (value) => {
|
|
30
|
+
try {
|
|
31
|
+
const url = new URL(value);
|
|
32
|
+
return url.protocol === "https:" && EMBED_HOSTS.has(url.hostname);
|
|
33
|
+
} catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var EMBED_ATTRS = [
|
|
38
|
+
"data-type",
|
|
39
|
+
"data-src",
|
|
40
|
+
"data-tweet-id",
|
|
41
|
+
"data-width",
|
|
42
|
+
"data-height",
|
|
43
|
+
"data-align",
|
|
44
|
+
"data-youtube-video"
|
|
45
|
+
];
|
|
46
|
+
var filter = new FilterXSS({
|
|
47
|
+
css: {
|
|
48
|
+
whiteList: {
|
|
49
|
+
"background-color": true,
|
|
50
|
+
color: true,
|
|
51
|
+
"font-family": true,
|
|
52
|
+
"font-size": true,
|
|
53
|
+
"line-height": true,
|
|
54
|
+
"min-width": true,
|
|
55
|
+
"text-align": true,
|
|
56
|
+
width: true
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
onTagAttr: (tag, name, value) => {
|
|
60
|
+
if (tag === "iframe" && name === "src") {
|
|
61
|
+
return isEmbedUrl(value) ? void 0 : "";
|
|
62
|
+
}
|
|
63
|
+
if (name !== "src" && name !== "href") {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const url = decodeUrl(value);
|
|
67
|
+
if (DATA_URL.test(url)) {
|
|
68
|
+
const allowed = tag === "img" && name === "src" && RASTER_DATA_IMAGE.test(url);
|
|
69
|
+
return allowed ? void 0 : "";
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
stripIgnoreTag: true,
|
|
73
|
+
stripIgnoreTagBody: ["script", "style"],
|
|
74
|
+
whiteList: {
|
|
75
|
+
a: ["href", "target", "rel"],
|
|
76
|
+
blockquote: [],
|
|
77
|
+
br: [],
|
|
78
|
+
code: ["class"],
|
|
79
|
+
col: ["span", "style"],
|
|
80
|
+
colgroup: [],
|
|
81
|
+
div: [...EMBED_ATTRS, "style"],
|
|
82
|
+
em: [],
|
|
83
|
+
h1: ["style"],
|
|
84
|
+
h2: ["style"],
|
|
85
|
+
h3: ["style"],
|
|
86
|
+
h4: ["style"],
|
|
87
|
+
h5: ["style"],
|
|
88
|
+
h6: ["style"],
|
|
89
|
+
hr: [],
|
|
90
|
+
iframe: ["src", "width", "height", "title", "allowfullscreen"],
|
|
91
|
+
img: ["src", "alt", "title", "width", "height", "style"],
|
|
92
|
+
input: ["type", "checked", "disabled"],
|
|
93
|
+
label: [],
|
|
94
|
+
li: ["data-type", "data-checked"],
|
|
95
|
+
mark: ["data-color", "style"],
|
|
96
|
+
ol: ["start", "type"],
|
|
97
|
+
p: ["style"],
|
|
98
|
+
pre: [],
|
|
99
|
+
s: [],
|
|
100
|
+
span: ["style"],
|
|
101
|
+
strong: [],
|
|
102
|
+
sub: [],
|
|
103
|
+
sup: [],
|
|
104
|
+
table: ["style"],
|
|
105
|
+
tbody: [],
|
|
106
|
+
td: ["colspan", "rowspan", "colwidth", "style"],
|
|
107
|
+
tfoot: [],
|
|
108
|
+
th: ["colspan", "rowspan", "colwidth", "style"],
|
|
109
|
+
thead: [],
|
|
110
|
+
tr: [],
|
|
111
|
+
u: [],
|
|
112
|
+
ul: ["data-type"]
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
var sanitizeHtml = (html) => filter.process(html);
|
|
116
|
+
|
|
10
117
|
// src/static-renderer.tsx
|
|
118
|
+
var warnedUnsanitized = false;
|
|
119
|
+
var isProduction = () => {
|
|
120
|
+
try {
|
|
121
|
+
return process.env.NODE_ENV === "production";
|
|
122
|
+
} catch {
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
var warnUnsanitized = () => {
|
|
127
|
+
if (warnedUnsanitized || isProduction()) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
warnedUnsanitized = true;
|
|
131
|
+
console.warn(
|
|
132
|
+
"StaticRenderer: rendering an HTML string without `sanitize`. Pass `sanitize` when the HTML can come from users, or `sanitize={false}` if it is trusted."
|
|
133
|
+
);
|
|
134
|
+
};
|
|
135
|
+
var toSafeHtml = (html, sanitize) => {
|
|
136
|
+
if (sanitize === void 0) {
|
|
137
|
+
warnUnsanitized();
|
|
138
|
+
return html;
|
|
139
|
+
}
|
|
140
|
+
if (typeof sanitize === "function") {
|
|
141
|
+
return sanitize(html);
|
|
142
|
+
}
|
|
143
|
+
return sanitize ? sanitizeHtml(html) : html;
|
|
144
|
+
};
|
|
11
145
|
var resolveUniqueExtensions = (extensions) => {
|
|
12
146
|
const byName = /* @__PURE__ */ new Map();
|
|
13
147
|
for (const extension of extensions) {
|
|
@@ -22,13 +156,14 @@ var StaticRenderer = ({
|
|
|
22
156
|
extensions,
|
|
23
157
|
staticEditorOptions,
|
|
24
158
|
options,
|
|
159
|
+
sanitize,
|
|
25
160
|
...props
|
|
26
161
|
}) => {
|
|
27
162
|
if (typeof content === "string") {
|
|
28
163
|
return createElement(Tag, {
|
|
29
164
|
...props,
|
|
30
165
|
className: cn("rte-static-renderer", className),
|
|
31
|
-
dangerouslySetInnerHTML: { __html: content }
|
|
166
|
+
dangerouslySetInnerHTML: { __html: toSafeHtml(content, sanitize) }
|
|
32
167
|
});
|
|
33
168
|
}
|
|
34
169
|
const children = renderToReactElement({
|
|
@@ -44,6 +179,7 @@ var StaticRenderer = ({
|
|
|
44
179
|
);
|
|
45
180
|
};
|
|
46
181
|
export {
|
|
47
|
-
StaticRenderer
|
|
182
|
+
StaticRenderer,
|
|
183
|
+
sanitizeHtml
|
|
48
184
|
};
|
|
49
185
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/static-renderer.tsx","../src/lib/utils.ts"],"sourcesContent":["import type { Extensions, JSONContent } from \"@tiptap/core\";\r\nimport type { Mark, Node } from \"@tiptap/pm/model\";\r\nimport { renderToReactElement } from \"@tiptap/static-renderer\";\r\nimport type {\r\n StaticEditorOptions,\r\n TiptapStaticRendererOptions,\r\n} from \"@tiptap/static-renderer\";\r\nimport { createElement } from \"react\";\r\nimport type { ElementType, HTMLAttributes, ReactNode } from \"react\";\r\n\r\nimport { cn } from \"./lib/utils\";\r\n\r\nconst resolveUniqueExtensions = (extensions: Extensions) => {\r\n const byName = new Map<string, (typeof extensions)[number]>();\r\n for (const extension of extensions) {\r\n byName.set(extension.name, extension);\r\n }\r\n return [...byName.values()];\r\n};\r\n\r\nexport type StaticRendererProps = Omit<\r\n HTMLAttributes<HTMLElement>,\r\n \"content\" | \"dangerouslySetInnerHTML\"\r\n> & {\r\n // HTML from editor.getHTML() rendered read-only, or a ProseMirror JSON document.\r\n content: string | JSONContent | Node;\r\n // Root element, defaults to \"div\".\r\n as?: ElementType;\r\n // Tiptap extensions used to render JSON content; every node/mark in the document must be registered.\r\n extensions?: Extensions;\r\n // Editor-level options applied while rendering JSON.\r\n staticEditorOptions?: StaticEditorOptions;\r\n // renderToReactElement options — nodeMapping/markMapping for custom nodes.\r\n options?: Partial<TiptapStaticRendererOptions<ReactNode, Mark, Node>>;\r\n};\r\n\r\nexport const StaticRenderer = ({\r\n as: Tag = \"div\",\r\n className,\r\n content,\r\n extensions,\r\n staticEditorOptions,\r\n options,\r\n ...props\r\n}: StaticRendererProps) => {\r\n if (typeof content === \"string\") {\r\n return createElement(Tag, {\r\n ...props,\r\n className: cn(\"rte-static-renderer\", className),\r\n dangerouslySetInnerHTML: { __html: content },\r\n });\r\n }\r\n\r\n const children = renderToReactElement({\r\n content,\r\n extensions: resolveUniqueExtensions(extensions ?? []),\r\n options,\r\n staticEditorOptions,\r\n });\r\n\r\n return createElement(\r\n Tag,\r\n { ...props, className: cn(\"rte-static-renderer\", className) },\r\n children\r\n );\r\n};\r\n","import { clsx } from \"clsx\";\r\nimport type { ClassValue } from \"clsx\";\r\nimport { twMerge } from \"tailwind-merge\";\r\n\r\nexport const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs));\r\n"],"mappings":";AAEA,SAAS,4BAA4B;AAKrC,SAAS,qBAAqB;;;ACP9B,SAAS,YAAY;AAErB,SAAS,eAAe;AAEjB,IAAM,KAAK,IAAI,WAAyB,QAAQ,KAAK,MAAM,CAAC;;;ADQnE,IAAM,0BAA0B,CAAC,eAA2B;AAC1D,QAAM,SAAS,oBAAI,IAAyC;AAC5D,aAAW,aAAa,YAAY;AAClC,WAAO,IAAI,UAAU,MAAM,SAAS;AAAA,EACtC;AACA,SAAO,CAAC,GAAG,OAAO,OAAO,CAAC;AAC5B;AAkBO,IAAM,iBAAiB,CAAC;AAAA,EAC7B,IAAI,MAAM;AAAA,EACV;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,MAA2B;AACzB,MAAI,OAAO,YAAY,UAAU;AAC/B,WAAO,cAAc,KAAK;AAAA,MACxB,GAAG;AAAA,MACH,WAAW,GAAG,uBAAuB,SAAS;AAAA,MAC9C,yBAAyB,EAAE,QAAQ,QAAQ;AAAA,IAC7C,CAAC;AAAA,EACH;AAEA,QAAM,WAAW,qBAAqB;AAAA,IACpC;AAAA,IACA,YAAY,wBAAwB,cAAc,CAAC,CAAC;AAAA,IACpD;AAAA,IACA;AAAA,EACF,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA,EAAE,GAAG,OAAO,WAAW,GAAG,uBAAuB,SAAS,EAAE;AAAA,IAC5D;AAAA,EACF;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/static-renderer.tsx","../src/lib/utils.ts","../src/sanitize.ts"],"sourcesContent":["import type { Extensions, JSONContent } from \"@tiptap/core\";\nimport type { Mark, Node } from \"@tiptap/pm/model\";\nimport { renderToReactElement } from \"@tiptap/static-renderer\";\nimport type {\n StaticEditorOptions,\n TiptapStaticRendererOptions,\n} from \"@tiptap/static-renderer\";\nimport { createElement } from \"react\";\nimport type { ElementType, HTMLAttributes, ReactNode } from \"react\";\n\nimport { cn } from \"./lib/utils\";\nimport { sanitizeHtml } from \"./sanitize\";\n\ndeclare const process: { env: { NODE_ENV?: string } };\n\nlet warnedUnsanitized = false;\n\nconst isProduction = () => {\n try {\n return process.env.NODE_ENV === \"production\";\n } catch {\n return true;\n }\n};\n\nconst warnUnsanitized = () => {\n if (warnedUnsanitized || isProduction()) {\n return;\n }\n warnedUnsanitized = true;\n console.warn(\n \"StaticRenderer: rendering an HTML string without `sanitize`. Pass `sanitize` when the HTML can come from users, or `sanitize={false}` if it is trusted.\"\n );\n};\n\nconst toSafeHtml = (\n html: string,\n sanitize: StaticRendererProps[\"sanitize\"]\n) => {\n if (sanitize === undefined) {\n warnUnsanitized();\n return html;\n }\n if (typeof sanitize === \"function\") {\n return sanitize(html);\n }\n return sanitize ? sanitizeHtml(html) : html;\n};\n\nconst resolveUniqueExtensions = (extensions: Extensions) => {\n const byName = new Map<string, (typeof extensions)[number]>();\n for (const extension of extensions) {\n byName.set(extension.name, extension);\n }\n return [...byName.values()];\n};\n\nexport type StaticRendererProps = Omit<\n HTMLAttributes<HTMLElement>,\n \"content\" | \"dangerouslySetInnerHTML\"\n> & {\n // HTML from editor.getHTML() rendered read-only, or a ProseMirror JSON document.\n content: string | JSONContent | Node;\n // Root element, defaults to \"div\".\n as?: ElementType;\n // Tiptap extensions used to render JSON content; every node/mark in the document must be registered.\n extensions?: Extensions;\n // Editor-level options applied while rendering JSON.\n staticEditorOptions?: StaticEditorOptions;\n // renderToReactElement options — nodeMapping/markMapping for custom nodes.\n options?: Partial<TiptapStaticRendererOptions<ReactNode, Mark, Node>>;\n sanitize?: boolean | ((html: string) => string);\n};\n\nexport const StaticRenderer = ({\n as: Tag = \"div\",\n className,\n content,\n extensions,\n staticEditorOptions,\n options,\n sanitize,\n ...props\n}: StaticRendererProps) => {\n if (typeof content === \"string\") {\n return createElement(Tag, {\n ...props,\n className: cn(\"rte-static-renderer\", className),\n dangerouslySetInnerHTML: { __html: toSafeHtml(content, sanitize) },\n });\n }\n\n const children = renderToReactElement({\n content,\n extensions: resolveUniqueExtensions(extensions ?? []),\n options,\n staticEditorOptions,\n });\n\n return createElement(\n Tag,\n { ...props, className: cn(\"rte-static-renderer\", className) },\n children\n );\n};\n","import { clsx } from \"clsx\";\r\nimport type { ClassValue } from \"clsx\";\r\nimport { twMerge } from \"tailwind-merge\";\r\n\r\nexport const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs));\r\n","import { FilterXSS } from \"xss\";\n\nconst EMBED_HOSTS = new Set([\n \"www.youtube.com\",\n \"youtube.com\",\n \"www.youtube-nocookie.com\",\n \"youtube-nocookie.com\",\n \"player.vimeo.com\",\n \"platform.twitter.com\",\n]);\n\nconst RASTER_DATA_IMAGE = /^data:image\\/(?:png|jpe?g|gif|webp|avif)[;,]/i;\nconst DATA_URL = /^data:/i;\n\nconst toCodePoint = (code: number) =>\n code <= 1_114_111 ? String.fromCodePoint(code) : \"\";\n\nconst decodeUrl = (value: string) =>\n [\n ...value\n .replaceAll(/&#x([0-9a-f]+);?/gi, (_, hex: string) =>\n toCodePoint(Number.parseInt(hex, 16))\n )\n .replaceAll(/&#(\\d+);?/g, (_, dec: string) => toCodePoint(Number(dec)))\n .replaceAll(/:/gi, \":\"),\n ]\n .filter((char) => (char.codePointAt(0) ?? 0) > 0x20)\n .join(\"\");\n\nconst isEmbedUrl = (value: string) => {\n try {\n const url = new URL(value);\n return url.protocol === \"https:\" && EMBED_HOSTS.has(url.hostname);\n } catch {\n return false;\n }\n};\n\nconst EMBED_ATTRS = [\n \"data-type\",\n \"data-src\",\n \"data-tweet-id\",\n \"data-width\",\n \"data-height\",\n \"data-align\",\n \"data-youtube-video\",\n];\n\nconst filter = new FilterXSS({\n css: {\n whiteList: {\n \"background-color\": true,\n color: true,\n \"font-family\": true,\n \"font-size\": true,\n \"line-height\": true,\n \"min-width\": true,\n \"text-align\": true,\n width: true,\n },\n },\n onTagAttr: (tag, name, value) => {\n if (tag === \"iframe\" && name === \"src\") {\n return isEmbedUrl(value) ? undefined : \"\";\n }\n if (name !== \"src\" && name !== \"href\") {\n return;\n }\n const url = decodeUrl(value);\n if (DATA_URL.test(url)) {\n const allowed =\n tag === \"img\" && name === \"src\" && RASTER_DATA_IMAGE.test(url);\n return allowed ? undefined : \"\";\n }\n },\n stripIgnoreTag: true,\n stripIgnoreTagBody: [\"script\", \"style\"],\n whiteList: {\n a: [\"href\", \"target\", \"rel\"],\n blockquote: [],\n br: [],\n code: [\"class\"],\n col: [\"span\", \"style\"],\n colgroup: [],\n div: [...EMBED_ATTRS, \"style\"],\n em: [],\n h1: [\"style\"],\n h2: [\"style\"],\n h3: [\"style\"],\n h4: [\"style\"],\n h5: [\"style\"],\n h6: [\"style\"],\n hr: [],\n iframe: [\"src\", \"width\", \"height\", \"title\", \"allowfullscreen\"],\n img: [\"src\", \"alt\", \"title\", \"width\", \"height\", \"style\"],\n input: [\"type\", \"checked\", \"disabled\"],\n label: [],\n li: [\"data-type\", \"data-checked\"],\n mark: [\"data-color\", \"style\"],\n ol: [\"start\", \"type\"],\n p: [\"style\"],\n pre: [],\n s: [],\n span: [\"style\"],\n strong: [],\n sub: [],\n sup: [],\n table: [\"style\"],\n tbody: [],\n td: [\"colspan\", \"rowspan\", \"colwidth\", \"style\"],\n tfoot: [],\n th: [\"colspan\", \"rowspan\", \"colwidth\", \"style\"],\n thead: [],\n tr: [],\n u: [],\n ul: [\"data-type\"],\n },\n});\n\nexport const sanitizeHtml = (html: string) => filter.process(html);\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@editorcn/static-renderer",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"files": [
|
|
5
5
|
"dist"
|
|
6
6
|
],
|
|
@@ -22,17 +22,30 @@
|
|
|
22
22
|
"scripts": {
|
|
23
23
|
"build": "tsup && node -e \"require('fs').copyFileSync('src/style.css','dist/style.css')\"",
|
|
24
24
|
"dev": "tsup --watch",
|
|
25
|
+
"test": "vitest run",
|
|
25
26
|
"typecheck": "tsc --noEmit",
|
|
26
27
|
"prepublishOnly": "pnpm build"
|
|
27
28
|
},
|
|
28
29
|
"dependencies": {
|
|
29
30
|
"clsx": "^2.1.1",
|
|
30
|
-
"tailwind-merge": "^3.0.0"
|
|
31
|
+
"tailwind-merge": "^3.0.0",
|
|
32
|
+
"xss": "^1.0.17"
|
|
31
33
|
},
|
|
32
34
|
"devDependencies": {
|
|
35
|
+
"@tiptap/extension-highlight": "^3.30.5",
|
|
36
|
+
"@tiptap/extension-image": "^3.30.5",
|
|
37
|
+
"@tiptap/extension-list": "^3.30.5",
|
|
38
|
+
"@tiptap/extension-subscript": "^3.30.5",
|
|
39
|
+
"@tiptap/extension-superscript": "^3.30.5",
|
|
40
|
+
"@tiptap/extension-table": "^3.30.5",
|
|
41
|
+
"@tiptap/extension-text-align": "^3.30.5",
|
|
42
|
+
"@tiptap/extension-text-style": "^3.30.5",
|
|
43
|
+
"@tiptap/starter-kit": "^3.30.5",
|
|
33
44
|
"@types/react": "^19",
|
|
45
|
+
"@types/react-dom": "^19.2.3",
|
|
34
46
|
"tsup": "^8.4.0",
|
|
35
|
-
"typescript": "^5"
|
|
47
|
+
"typescript": "^5",
|
|
48
|
+
"vitest": "^3.2.7"
|
|
36
49
|
},
|
|
37
50
|
"peerDependencies": {
|
|
38
51
|
"@tiptap/core": ">=3.21.0 <4",
|