@editorcn/static-renderer 0.2.1 → 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 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(/&colon;/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
@@ -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\";\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\";\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};\n\nexport const StaticRenderer = ({\n as: Tag = \"div\",\n className,\n content,\n extensions,\n staticEditorOptions,\n options,\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: content },\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"],"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(/&colon;/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
- export { StaticRenderer, type StaticRendererProps };
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
- export { StaticRenderer, type StaticRendererProps };
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(/&colon;/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\";\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\";\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};\n\nexport const StaticRenderer = ({\n as: Tag = \"div\",\n className,\n content,\n extensions,\n staticEditorOptions,\n options,\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: content },\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"],"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(/&colon;/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.1",
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",