@iit/precision-ui 0.8.36 → 0.8.38
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SafeHtml.d.ts","sourceRoot":"","sources":["../../src/components/SafeHtml.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SafeHtml.d.ts","sourceRoot":"","sources":["../../src/components/SafeHtml.tsx"],"names":[],"mappings":"AA+LA,QAAA,MAAM,gBAAgB,8DAKnB;IACD,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB,gDAuBA,CAAA;AAED,eAAe,gBAAgB,CAAA"}
|
package/dist/index.es19.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
const
|
|
3
|
-
const e = /* @__PURE__ */ new Set(["br", "img", "input", "hr", "meta", "link"]),
|
|
1
|
+
import g from "react";
|
|
2
|
+
const h = (t) => {
|
|
3
|
+
const e = /* @__PURE__ */ new Set(["br", "img", "input", "hr", "meta", "link"]), o = /<\/?([a-z]+)(\s[^>]*)?>/gi, a = [];
|
|
4
4
|
let r;
|
|
5
|
-
for (; (r =
|
|
5
|
+
for (; (r = o.exec(t)) !== null; ) {
|
|
6
6
|
const s = r[0], n = r[1].toLowerCase();
|
|
7
7
|
if (s === "</br>")
|
|
8
8
|
return !1;
|
|
@@ -13,75 +13,81 @@ const m = (t) => {
|
|
|
13
13
|
!e.has(n) && !s.endsWith("/>") && a.push(n);
|
|
14
14
|
}
|
|
15
15
|
return a.length === 0;
|
|
16
|
-
},
|
|
17
|
-
const e = /<[a-z]+\s+([^>]+)>/gi,
|
|
18
|
-
for (const [s, n] of
|
|
19
|
-
const
|
|
20
|
-
for (const
|
|
21
|
-
if (
|
|
22
|
-
const [f, ...u] =
|
|
16
|
+
}, p = (t) => {
|
|
17
|
+
const e = /<[a-z]+\s+([^>]+)>/gi, o = t.matchAll(e);
|
|
18
|
+
for (const [s, n] of o) {
|
|
19
|
+
const c = n.match(/\w+\s*=\s*(['"])(.*?)\1|\w+(?!=)/g) || [];
|
|
20
|
+
for (const l of c)
|
|
21
|
+
if (l.includes("=")) {
|
|
22
|
+
const [f, ...u] = l.split("="), i = u.join("=");
|
|
23
23
|
if (!(i.startsWith('"') && i.endsWith('"') || i.startsWith("'") && i.endsWith("'")))
|
|
24
24
|
return !1;
|
|
25
|
-
const
|
|
26
|
-
if (i.slice(1, -1).includes(
|
|
25
|
+
const d = i[0];
|
|
26
|
+
if (i.slice(1, -1).includes(d))
|
|
27
27
|
return !1;
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
const a = /<[^>]*?['"][^'"]*>/g, r = t.match(a) || [];
|
|
31
31
|
for (const s of r) {
|
|
32
|
-
let n = !1,
|
|
33
|
-
for (let
|
|
34
|
-
(s[
|
|
32
|
+
let n = !1, c = "";
|
|
33
|
+
for (let l = 0; l < s.length; l++)
|
|
34
|
+
(s[l] === "'" || s[l] === '"') && (n ? s[l] === c && (n = !1) : (n = !0, c = s[l]));
|
|
35
35
|
if (n)
|
|
36
36
|
return !1;
|
|
37
37
|
}
|
|
38
38
|
return !0;
|
|
39
|
-
},
|
|
39
|
+
}, m = (t) => {
|
|
40
40
|
const e = [...t.matchAll(/<([a-z]+)(\s[^>]*)\/>/gi)];
|
|
41
|
-
for (const [
|
|
41
|
+
for (const [o, a, r] of e)
|
|
42
42
|
if (r) {
|
|
43
43
|
const s = /(\w+)=["']([^"']*)["']/g, n = [...r.matchAll(s)];
|
|
44
44
|
for (const [f, u, i] of n)
|
|
45
45
|
if (!u || !i)
|
|
46
46
|
return !1;
|
|
47
|
-
const
|
|
48
|
-
if ([...r.matchAll(
|
|
47
|
+
const c = /(\w+)=["'][^"']*$/g;
|
|
48
|
+
if ([...r.matchAll(c)].length > 0)
|
|
49
49
|
return !1;
|
|
50
50
|
}
|
|
51
51
|
return !0;
|
|
52
|
-
},
|
|
53
|
-
const e = /style=["']([^"']*)["']/gi,
|
|
54
|
-
for (const [a, r] of
|
|
52
|
+
}, y = (t) => {
|
|
53
|
+
const e = /style=["']([^"']*)["']/gi, o = [...t.matchAll(e)];
|
|
54
|
+
for (const [a, r] of o) {
|
|
55
55
|
const s = r.split(";");
|
|
56
56
|
for (const n of s)
|
|
57
57
|
if (n.trim()) {
|
|
58
|
-
const [
|
|
59
|
-
if (!
|
|
58
|
+
const [c, l] = n.split(":").map((f) => f.trim());
|
|
59
|
+
if (!c || !l)
|
|
60
60
|
return !1;
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
return !0;
|
|
64
|
-
},
|
|
65
|
-
const e =
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
}, T = (t) => h(t) && p(t) && m(t) && y(t), v = (t) => {
|
|
65
|
+
const e = {
|
|
66
|
+
nbsp: " ",
|
|
67
|
+
amp: "&",
|
|
68
|
+
lt: "<",
|
|
69
|
+
gt: ">",
|
|
70
|
+
quot: '"'
|
|
71
|
+
}, o = (a) => Object.prototype.hasOwnProperty.call(e, a);
|
|
72
|
+
return t.replace(/<[^>]*>/g, "").replace(/&([a-z]+);/g, (a, r) => o(r) ? e[r] : "").replace(/\s+/g, " ").trim();
|
|
73
|
+
}, C = (t, e) => t.length <= e ? t : t.slice(0, e).trim() + "...", A = ({
|
|
68
74
|
html: t,
|
|
69
75
|
className: e,
|
|
70
|
-
withoutContentClass:
|
|
76
|
+
withoutContentClass: o,
|
|
71
77
|
truncateLength: a
|
|
72
78
|
}) => {
|
|
73
|
-
if (!
|
|
74
|
-
return console.error("Invalid HTML content:", t), /* @__PURE__ */
|
|
75
|
-
const s = a ?
|
|
76
|
-
return /* @__PURE__ */
|
|
79
|
+
if (!T(t))
|
|
80
|
+
return console.error("Invalid HTML content:", t), /* @__PURE__ */ g.createElement("div", { style: { color: "red" } }, "Некорректное HTML содержимое");
|
|
81
|
+
const s = a ? C(v(t), a) : t;
|
|
82
|
+
return /* @__PURE__ */ g.createElement(
|
|
77
83
|
"div",
|
|
78
84
|
{
|
|
79
|
-
className: [!
|
|
85
|
+
className: [!o && "content", e].filter(Boolean).join(" "),
|
|
80
86
|
dangerouslySetInnerHTML: { __html: s }
|
|
81
87
|
}
|
|
82
88
|
);
|
|
83
|
-
},
|
|
89
|
+
}, w = A;
|
|
84
90
|
export {
|
|
85
|
-
|
|
91
|
+
w as default
|
|
86
92
|
};
|
|
87
93
|
//# sourceMappingURL=index.es19.js.map
|
package/dist/index.es19.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.es19.js","sources":["../src/components/SafeHtml.tsx"],"sourcesContent":["import React from 'react'\r\n\r\n// Check if tags are correctly opened and closed\r\nconst validateTags = (htmlString: string) => {\r\n // List of known self-closing tags\r\n const selfClosingTags = new Set(['br', 'img', 'input', 'hr', 'meta', 'link'])\r\n\r\n const tagPattern = /<\\/?([a-z]+)(\\s[^>]*)?>/gi\r\n const tagStack: string[] = []\r\n let match\r\n\r\n while ((match = tagPattern.exec(htmlString)) !== null) {\r\n const fullTag = match[0]\r\n const tagName = match[1].toLowerCase()\r\n\r\n // Check for incorrect </br> usage\r\n if (fullTag === '</br>') {\r\n return false // </br> is invalid HTML\r\n }\r\n\r\n if (fullTag.startsWith('</')) {\r\n // Closing tag\r\n if (selfClosingTags.has(tagName)) {\r\n return false // Self-closing tags shouldn't have closing tags\r\n }\r\n if (tagStack.length === 0) return false\r\n const lastTag = tagStack.pop()\r\n if (lastTag !== tagName) return false\r\n } else {\r\n // Opening tag\r\n if (!selfClosingTags.has(tagName) && !fullTag.endsWith('/>')) {\r\n tagStack.push(tagName)\r\n }\r\n }\r\n }\r\n\r\n return tagStack.length === 0\r\n}\r\n\r\nconst validateAttributes = (htmlString: string) => {\r\n const attributeRegex = /<[a-z]+\\s+([^>]+)>/gi\r\n const matches = htmlString.matchAll(attributeRegex)\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n for (const [fullMatch, attributes] of matches) {\r\n // Split attributes by space, but keep quoted values together\r\n const attrs = attributes.match(/\\w+\\s*=\\s*(['\"])(.*?)\\1|\\w+(?!=)/g) || []\r\n\r\n for (const attr of attrs) {\r\n // Check if attribute has quotes\r\n if (attr.includes('=')) {\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n const [name, ...rest] = attr.split('=')\r\n const value = rest.join('=') // Rejoin in case value contains =\r\n\r\n // Check for proper quote matching\r\n if (\r\n !(\r\n (value.startsWith('\"') && value.endsWith('\"')) ||\r\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\r\n )\r\n ) {\r\n return false\r\n }\r\n\r\n // Extract the quote character used\r\n const quoteChar = value[0]\r\n // Check if there are any unescaped quotes of the same type inside the value\r\n const valueContent = value.slice(1, -1)\r\n if (valueContent.includes(quoteChar)) {\r\n return false\r\n }\r\n }\r\n }\r\n }\r\n\r\n // Additional check for unclosed quotes before >\r\n const unclosedQuoteCheck = /<[^>]*?['\"][^'\"]*>/g\r\n const potentiallyUnclosedTags = htmlString.match(unclosedQuoteCheck) || []\r\n for (const tag of potentiallyUnclosedTags) {\r\n let inQuote = false\r\n let quoteChar = ''\r\n for (let i = 0; i < tag.length; i++) {\r\n if (tag[i] === \"'\" || tag[i] === '\"') {\r\n if (!inQuote) {\r\n inQuote = true\r\n quoteChar = tag[i]\r\n } else if (tag[i] === quoteChar) {\r\n inQuote = false\r\n }\r\n }\r\n }\r\n if (inQuote) {\r\n return false\r\n }\r\n }\r\n\r\n return true\r\n}\r\n\r\n// Check if self-closing tags are valid\r\nconst validateSelfClosingTags = (htmlString: string) => {\r\n const selfClosingTags = [...htmlString.matchAll(/<([a-z]+)(\\s[^>]*)\\/>/gi)]\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n for (const [_, tag, attributes] of selfClosingTags) {\r\n if (attributes) {\r\n const attrRegex = /(\\w+)=[\"']([^\"']*)[\"']/g\r\n const attrs = [...attributes.matchAll(attrRegex)]\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n for (const [__, attrName, attrValue] of attrs) {\r\n if (!attrName || !attrValue) {\r\n return false // Invalid attribute format\r\n }\r\n }\r\n // Check for unclosed attributes\r\n const unclosedAttrRegex = /(\\w+)=[\"'][^\"']*$/g\r\n const unclosedAttrs = [...attributes.matchAll(unclosedAttrRegex)]\r\n if (unclosedAttrs.length > 0) {\r\n return false // Unclosed attribute in self-closing tag\r\n }\r\n }\r\n }\r\n\r\n return true // Self-closing tags are valid\r\n}\r\n\r\n// Check for inline styles and ensure they follow a valid format\r\nconst validateInlineStyles = (htmlString: string) => {\r\n const styleRegex = /style=[\"']([^\"']*)[\"']/gi\r\n const styles = [...htmlString.matchAll(styleRegex)]\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n for (const [_, styleContent] of styles) {\r\n // A basic check for properly formatted style declarations (e.g., \"property: value;\")\r\n const styleRules = styleContent.split(';')\r\n for (const rule of styleRules) {\r\n if (rule.trim()) {\r\n const [property, value] = rule.split(':').map((s) => s.trim())\r\n if (!property || !value) {\r\n return false // Invalid style declaration\r\n }\r\n }\r\n }\r\n }\r\n\r\n return true // Inline styles are valid\r\n}\r\n\r\n// Main function that calls the smaller subfunctions\r\nconst validateHtmlBasic = (htmlString: string) => {\r\n return (\r\n validateTags(htmlString) &&\r\n validateAttributes(htmlString) &&\r\n validateSelfClosingTags(htmlString) &&\r\n validateInlineStyles(htmlString)\r\n )\r\n}\r\n\r\n// Utility to extract plain text from HTML string\r\nconst htmlToText = (html: string): string => {\r\n const tempDiv = document.createElement('div')\r\n tempDiv.innerHTML = html\r\n return tempDiv.textContent || tempDiv.innerText || ''\r\n}\r\n\r\n// Utility to truncate text safely\r\nconst truncateText = (text: string, maxLength: number): string => {\r\n if (text.length <= maxLength) return text\r\n return text.slice(0, maxLength).trim() + '...'\r\n}\r\n\r\nconst SafeHtmlRenderer = ({\r\n html,\r\n className,\r\n withoutContentClass,\r\n truncateLength,\r\n}: {\r\n html: string\r\n className?: string\r\n withoutContentClass?: boolean\r\n truncateLength?: number\r\n}) => {\r\n const isValid = validateHtmlBasic(html)\r\n // console.log(\"isValidHTML\", isValid);\r\n // TODO: add send error to TG\r\n\r\n if (!isValid) {\r\n console.error('Invalid HTML content:', html)\r\n return <div style={{ color: 'red' }}>Некорректное HTML содержимое</div>\r\n }\r\n\r\n // If truncateLength is provided, extract text, truncate, and render as plain HTML-safe text\r\n const processedHtml = truncateLength\r\n ? truncateText(htmlToText(html), truncateLength)\r\n : html\r\n\r\n return (\r\n <div\r\n className={[!withoutContentClass && 'content', className]\r\n .filter(Boolean)\r\n .join(' ')}\r\n dangerouslySetInnerHTML={{ __html: processedHtml }}\r\n />\r\n )\r\n}\r\n\r\nexport default SafeHtmlRenderer\r\n"],"names":["validateTags","htmlString","selfClosingTags","tagPattern","tagStack","match","fullTag","tagName","validateAttributes","attributeRegex","matches","fullMatch","attributes","attrs","attr","name","rest","value","quoteChar","unclosedQuoteCheck","potentiallyUnclosedTags","tag","inQuote","i","validateSelfClosingTags","_","attrRegex","__","attrName","attrValue","unclosedAttrRegex","validateInlineStyles","styleRegex","styles","styleContent","styleRules","rule","property","s","validateHtmlBasic","htmlToText","html","tempDiv","truncateText","text","maxLength","SafeHtmlRenderer","className","withoutContentClass","truncateLength","processedHtml","React","SafeHtmlRenderer$1"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.es19.js","sources":["../src/components/SafeHtml.tsx"],"sourcesContent":["import React from 'react'\r\n\r\n// Check if tags are correctly opened and closed\r\nconst validateTags = (htmlString: string) => {\r\n // List of known self-closing tags\r\n const selfClosingTags = new Set(['br', 'img', 'input', 'hr', 'meta', 'link'])\r\n\r\n const tagPattern = /<\\/?([a-z]+)(\\s[^>]*)?>/gi\r\n const tagStack: string[] = []\r\n let match\r\n\r\n while ((match = tagPattern.exec(htmlString)) !== null) {\r\n const fullTag = match[0]\r\n const tagName = match[1].toLowerCase()\r\n\r\n // Check for incorrect </br> usage\r\n if (fullTag === '</br>') {\r\n return false // </br> is invalid HTML\r\n }\r\n\r\n if (fullTag.startsWith('</')) {\r\n // Closing tag\r\n if (selfClosingTags.has(tagName)) {\r\n return false // Self-closing tags shouldn't have closing tags\r\n }\r\n if (tagStack.length === 0) return false\r\n const lastTag = tagStack.pop()\r\n if (lastTag !== tagName) return false\r\n } else {\r\n // Opening tag\r\n if (!selfClosingTags.has(tagName) && !fullTag.endsWith('/>')) {\r\n tagStack.push(tagName)\r\n }\r\n }\r\n }\r\n\r\n return tagStack.length === 0\r\n}\r\n\r\nconst validateAttributes = (htmlString: string) => {\r\n const attributeRegex = /<[a-z]+\\s+([^>]+)>/gi\r\n const matches = htmlString.matchAll(attributeRegex)\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n for (const [fullMatch, attributes] of matches) {\r\n // Split attributes by space, but keep quoted values together\r\n const attrs = attributes.match(/\\w+\\s*=\\s*(['\"])(.*?)\\1|\\w+(?!=)/g) || []\r\n\r\n for (const attr of attrs) {\r\n // Check if attribute has quotes\r\n if (attr.includes('=')) {\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n const [name, ...rest] = attr.split('=')\r\n const value = rest.join('=') // Rejoin in case value contains =\r\n\r\n // Check for proper quote matching\r\n if (\r\n !(\r\n (value.startsWith('\"') && value.endsWith('\"')) ||\r\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\r\n )\r\n ) {\r\n return false\r\n }\r\n\r\n // Extract the quote character used\r\n const quoteChar = value[0]\r\n // Check if there are any unescaped quotes of the same type inside the value\r\n const valueContent = value.slice(1, -1)\r\n if (valueContent.includes(quoteChar)) {\r\n return false\r\n }\r\n }\r\n }\r\n }\r\n\r\n // Additional check for unclosed quotes before >\r\n const unclosedQuoteCheck = /<[^>]*?['\"][^'\"]*>/g\r\n const potentiallyUnclosedTags = htmlString.match(unclosedQuoteCheck) || []\r\n for (const tag of potentiallyUnclosedTags) {\r\n let inQuote = false\r\n let quoteChar = ''\r\n for (let i = 0; i < tag.length; i++) {\r\n if (tag[i] === \"'\" || tag[i] === '\"') {\r\n if (!inQuote) {\r\n inQuote = true\r\n quoteChar = tag[i]\r\n } else if (tag[i] === quoteChar) {\r\n inQuote = false\r\n }\r\n }\r\n }\r\n if (inQuote) {\r\n return false\r\n }\r\n }\r\n\r\n return true\r\n}\r\n\r\n// Check if self-closing tags are valid\r\nconst validateSelfClosingTags = (htmlString: string) => {\r\n const selfClosingTags = [...htmlString.matchAll(/<([a-z]+)(\\s[^>]*)\\/>/gi)]\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n for (const [_, tag, attributes] of selfClosingTags) {\r\n if (attributes) {\r\n const attrRegex = /(\\w+)=[\"']([^\"']*)[\"']/g\r\n const attrs = [...attributes.matchAll(attrRegex)]\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n for (const [__, attrName, attrValue] of attrs) {\r\n if (!attrName || !attrValue) {\r\n return false // Invalid attribute format\r\n }\r\n }\r\n // Check for unclosed attributes\r\n const unclosedAttrRegex = /(\\w+)=[\"'][^\"']*$/g\r\n const unclosedAttrs = [...attributes.matchAll(unclosedAttrRegex)]\r\n if (unclosedAttrs.length > 0) {\r\n return false // Unclosed attribute in self-closing tag\r\n }\r\n }\r\n }\r\n\r\n return true // Self-closing tags are valid\r\n}\r\n\r\n// Check for inline styles and ensure they follow a valid format\r\nconst validateInlineStyles = (htmlString: string) => {\r\n const styleRegex = /style=[\"']([^\"']*)[\"']/gi\r\n const styles = [...htmlString.matchAll(styleRegex)]\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n for (const [_, styleContent] of styles) {\r\n // A basic check for properly formatted style declarations (e.g., \"property: value;\")\r\n const styleRules = styleContent.split(';')\r\n for (const rule of styleRules) {\r\n if (rule.trim()) {\r\n const [property, value] = rule.split(':').map((s) => s.trim())\r\n if (!property || !value) {\r\n return false // Invalid style declaration\r\n }\r\n }\r\n }\r\n }\r\n\r\n return true // Inline styles are valid\r\n}\r\n\r\n// Main function that calls the smaller subfunctions\r\nconst validateHtmlBasic = (htmlString: string) => {\r\n return (\r\n validateTags(htmlString) &&\r\n validateAttributes(htmlString) &&\r\n validateSelfClosingTags(htmlString) &&\r\n validateInlineStyles(htmlString)\r\n )\r\n}\r\n\r\n// Utility to extract plain text from HTML string\r\nconst htmlToText = (html: string): string => {\r\n const entityMap = {\r\n nbsp: ' ',\r\n amp: '&',\r\n lt: '<',\r\n gt: '>',\r\n quot: '\"',\r\n } as const // `as const` makes TypeScript infer literal types and enables key inference\r\n\r\n type EntityKey = keyof typeof entityMap\r\n\r\n const isValidEntity = (name: string): name is EntityKey => {\r\n return Object.prototype.hasOwnProperty.call(entityMap, name)\r\n }\r\n\r\n return html\r\n .replace(/<[^>]*>/g, '') // Strip HTML\r\n .replace(/&([a-z]+);/g, (_, name: string) => {\r\n return isValidEntity(name) ? entityMap[name] : ''\r\n })\r\n .replace(/\\s+/g, ' ') // Normalize whitespace\r\n .trim()\r\n}\r\n\r\n// Utility to truncate text safely\r\nconst truncateText = (text: string, maxLength: number): string => {\r\n if (text.length <= maxLength) return text\r\n return text.slice(0, maxLength).trim() + '...'\r\n}\r\n\r\nconst SafeHtmlRenderer = ({\r\n html,\r\n className,\r\n withoutContentClass,\r\n truncateLength,\r\n}: {\r\n html: string\r\n className?: string\r\n withoutContentClass?: boolean\r\n truncateLength?: number\r\n}) => {\r\n const isValid = validateHtmlBasic(html)\r\n // console.log(\"isValidHTML\", isValid);\r\n // TODO: add send error to TG\r\n\r\n if (!isValid) {\r\n console.error('Invalid HTML content:', html)\r\n return <div style={{ color: 'red' }}>Некорректное HTML содержимое</div>\r\n }\r\n\r\n // If truncateLength is provided, extract text, truncate, and render as plain HTML-safe text\r\n const processedHtml = truncateLength\r\n ? truncateText(htmlToText(html), truncateLength)\r\n : html\r\n\r\n return (\r\n <div\r\n className={[!withoutContentClass && 'content', className]\r\n .filter(Boolean)\r\n .join(' ')}\r\n dangerouslySetInnerHTML={{ __html: processedHtml }}\r\n />\r\n )\r\n}\r\n\r\nexport default SafeHtmlRenderer\r\n"],"names":["validateTags","htmlString","selfClosingTags","tagPattern","tagStack","match","fullTag","tagName","validateAttributes","attributeRegex","matches","fullMatch","attributes","attrs","attr","name","rest","value","quoteChar","unclosedQuoteCheck","potentiallyUnclosedTags","tag","inQuote","i","validateSelfClosingTags","_","attrRegex","__","attrName","attrValue","unclosedAttrRegex","validateInlineStyles","styleRegex","styles","styleContent","styleRules","rule","property","s","validateHtmlBasic","htmlToText","html","entityMap","isValidEntity","truncateText","text","maxLength","SafeHtmlRenderer","className","withoutContentClass","truncateLength","processedHtml","React","SafeHtmlRenderer$1"],"mappings":"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"}
|
package/dist/index.es28.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { Accordion as
|
|
1
|
+
import { Accordion as n, AccordionItem as c, AccordionTrigger as m, AccordionContent as o } from "./index.es51.js";
|
|
2
2
|
import i from "./index.es6.js";
|
|
3
3
|
import e from "react";
|
|
4
4
|
import s from "./index.es19.js";
|
|
5
|
-
const d = ({ questions: l, type: a }) => /* @__PURE__ */ e.createElement("div", null, /* @__PURE__ */ e.createElement(
|
|
5
|
+
const d = ({ questions: l, type: a }) => /* @__PURE__ */ e.createElement("div", null, /* @__PURE__ */ e.createElement(n, { type: a, collapsible: !0, className: "space-y-3" }, l.map((t, r) => /* @__PURE__ */ e.createElement(c, { key: r, value: `item-${r}` }, /* @__PURE__ */ e.createElement(m, { size: "small" }, t.question), /* @__PURE__ */ e.createElement(o, null, /* @__PURE__ */ e.createElement(s, { html: t.answer, truncateLength: 300 }), t.linkToFull && /* @__PURE__ */ e.createElement("div", { className: "pt-6 no-content-rules" }, /* @__PURE__ */ e.createElement(i, { icon: "arrowRight", asChild: !0 }, /* @__PURE__ */ e.createElement("a", { href: t.linkToFull, className: "more-link" }, "Читать полностью")))))))), A = d;
|
|
6
6
|
export {
|
|
7
7
|
A as default
|
|
8
8
|
};
|
package/dist/index.es28.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.es28.js","sources":["../src/components/cards/FAQ.tsx"],"sourcesContent":["import {\r\n Accordion,\r\n AccordionContent,\r\n AccordionItem,\r\n AccordionTrigger,\r\n} from '../ui/accordion'\r\n\r\nimport { ButtonText } from '../ui/button'\r\nimport React from 'react'\r\nimport SafeHtmlRenderer from '../SafeHtml'\r\n\r\nexport interface FAQItem {\r\n question: string\r\n answer: string\r\n linkToFull?: string\r\n}\r\n\r\nexport interface FAQCardProps {\r\n questions: FAQItem[]\r\n type: 'single' | 'multiple'\r\n}\r\n\r\nconst FAQCard: React.FC<FAQCardProps> = ({ questions, type }) => {\r\n return (\r\n <div>\r\n <Accordion type={type} collapsible={true} className=\"space-y-3\">\r\n {questions.map((question, index) => (\r\n <AccordionItem key={index} value={`item-${index}`}>\r\n <AccordionTrigger size=\"small\">\r\n {question.question}\r\n </AccordionTrigger>\r\n <AccordionContent>\r\n <SafeHtmlRenderer html={question.answer} />\r\n\r\n {question.linkToFull && (\r\n <div className=\"pt-6 no-content-rules\">\r\n <ButtonText icon=\"arrowRight\" asChild>\r\n <a href={question.linkToFull} className=\"more-link\">\r\n Читать полностью\r\n </a>\r\n </ButtonText>\r\n </div>\r\n )}\r\n </AccordionContent>\r\n </AccordionItem>\r\n ))}\r\n </Accordion>\r\n </div>\r\n )\r\n}\r\n\r\nexport default FAQCard\r\n"],"names":["FAQCard","questions","type","React","Accordion","question","index","AccordionItem","AccordionTrigger","AccordionContent","SafeHtmlRenderer","ButtonText","FAQCard$1"],"mappings":";;;;AAsBA,MAAMA,IAAkC,CAAC,EAAE,WAAAC,GAAW,MAAAC,QAEjDC,gBAAAA,EAAA,cAAA,OAAA,MACEA,gBAAAA,EAAA,cAAAC,GAAA,EAAU,MAAAF,GAAY,aAAa,IAAM,WAAU,eACjDD,EAAU,IAAI,CAACI,GAAUC,
|
|
1
|
+
{"version":3,"file":"index.es28.js","sources":["../src/components/cards/FAQ.tsx"],"sourcesContent":["import {\r\n Accordion,\r\n AccordionContent,\r\n AccordionItem,\r\n AccordionTrigger,\r\n} from '../ui/accordion'\r\n\r\nimport { ButtonText } from '../ui/button'\r\nimport React from 'react'\r\nimport SafeHtmlRenderer from '../SafeHtml'\r\n\r\nexport interface FAQItem {\r\n question: string\r\n answer: string\r\n linkToFull?: string\r\n}\r\n\r\nexport interface FAQCardProps {\r\n questions: FAQItem[]\r\n type: 'single' | 'multiple'\r\n}\r\n\r\nconst FAQCard: React.FC<FAQCardProps> = ({ questions, type }) => {\r\n return (\r\n <div>\r\n <Accordion type={type} collapsible={true} className=\"space-y-3\">\r\n {questions.map((question, index) => (\r\n <AccordionItem key={index} value={`item-${index}`}>\r\n <AccordionTrigger size=\"small\">\r\n {question.question}\r\n </AccordionTrigger>\r\n <AccordionContent>\r\n <SafeHtmlRenderer html={question.answer} truncateLength={300} />\r\n\r\n {question.linkToFull && (\r\n <div className=\"pt-6 no-content-rules\">\r\n <ButtonText icon=\"arrowRight\" asChild>\r\n <a href={question.linkToFull} className=\"more-link\">\r\n Читать полностью\r\n </a>\r\n </ButtonText>\r\n </div>\r\n )}\r\n </AccordionContent>\r\n </AccordionItem>\r\n ))}\r\n </Accordion>\r\n </div>\r\n )\r\n}\r\n\r\nexport default FAQCard\r\n"],"names":["FAQCard","questions","type","React","Accordion","question","index","AccordionItem","AccordionTrigger","AccordionContent","SafeHtmlRenderer","ButtonText","FAQCard$1"],"mappings":";;;;AAsBA,MAAMA,IAAkC,CAAC,EAAE,WAAAC,GAAW,MAAAC,QAEjDC,gBAAAA,EAAA,cAAA,OAAA,MACEA,gBAAAA,EAAA,cAAAC,GAAA,EAAU,MAAAF,GAAY,aAAa,IAAM,WAAU,eACjDD,EAAU,IAAI,CAACI,GAAUC,MACxBH,gBAAAA,EAAA,cAACI,GAAc,EAAA,KAAKD,GAAO,OAAO,QAAQA,CAAK,GAC7C,GAAAH,gBAAAA,EAAA,cAACK,GAAiB,EAAA,MAAK,QACpB,GAAAH,EAAS,QACZ,mCACCI,GACC,MAAAN,gBAAAA,EAAA,cAACO,GAAiB,EAAA,MAAML,EAAS,QAAQ,gBAAgB,IAAK,CAAA,GAE7DA,EAAS,cACRF,gBAAAA,EAAA,cAAC,OAAI,EAAA,WAAU,2BACbA,gBAAAA,EAAA,cAACQ,KAAW,MAAK,cAAa,SAAO,GAAA,GAClCR,gBAAAA,EAAA,cAAA,KAAA,EAAE,MAAME,EAAS,YAAY,WAAU,YAAA,GAAY,kBAEpD,CACF,CACF,CAEJ,CACF,CACD,CACH,CACF,GAIJO,IAAeZ;"}
|