@favish/staffbase-utils 0.1.1 → 0.3.0

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.
@@ -0,0 +1,2 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=()=>{if(typeof navigator>`u`)return!1;let e=navigator.userAgent.toLowerCase(),t=/mobile|android|ios|iphone|ipad|ipod|windows phone/i.test(e),n=/wv|webview/i.test(e);return t||n},t=()=>typeof window>`u`?!1:window.innerWidth<=768,n=()=>e()||t();exports.isMobile=n,exports.isMobileOrWebview=e,exports.isMobileViewport=t;
2
+ //# sourceMappingURL=device.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device.cjs.js","names":[],"sources":["../src/device/isMobileOrWebview.ts","../src/device/isMobileViewport.ts","../src/device/isMobile.ts"],"sourcesContent":["/**\n * Detects if the current environment is a mobile device or running inside a\n * webview, using userAgent heuristics common on iOS/Android and webviews.\n * Guards against non-browser environments (SSR/tests without navigator).\n * @returns {boolean} True if likely mobile or webview.\n */\nexport const isMobileOrWebview = (): boolean => {\n if (typeof navigator === 'undefined') return false\n\n const userAgent = navigator.userAgent.toLowerCase()\n const isMobilePlatform =\n /mobile|android|ios|iphone|ipad|ipod|windows phone/i.test(userAgent)\n const isWebview = /wv|webview/i.test(userAgent)\n\n return isMobilePlatform || isWebview\n}\n","/**\n * Checks if the viewport width is mobile-sized (<= 768px).\n * Guards against non-browser environments (SSR/tests without window).\n * @returns {boolean} True if the viewport width is 768px or less.\n */\nexport const isMobileViewport = (): boolean => {\n if (typeof window === 'undefined') return false\n return window.innerWidth <= 768\n}\n","import { isMobileOrWebview } from './isMobileOrWebview'\nimport { isMobileViewport } from './isMobileViewport'\n\n/**\n * Checks if the device is mobile either by user agent/webview or viewport size.\n * @returns {boolean} True if either a mobile/webview UA or a mobile viewport.\n */\nexport const isMobile = (): boolean =>\n isMobileOrWebview() || isMobileViewport()\n"],"mappings":"mEAMA,IAAa,MAAmC,CAC9C,GAAI,OAAO,UAAc,IAAa,MAAO,GAE7C,IAAM,EAAY,UAAU,UAAU,YAAY,EAC5C,EACJ,qDAAqD,KAAK,CAAS,EAC/D,EAAY,cAAc,KAAK,CAAS,EAE9C,OAAO,GAAoB,CAC7B,ECVa,MACP,OAAO,OAAW,IAAoB,GACnC,OAAO,YAAc,ICAjB,MACX,EAAkB,GAAK,EAAiB"}
@@ -0,0 +1,10 @@
1
+ //#region src/device/isMobileOrWebview.ts
2
+ var e = () => {
3
+ if (typeof navigator > "u") return !1;
4
+ let e = navigator.userAgent.toLowerCase(), t = /mobile|android|ios|iphone|ipad|ipod|windows phone/i.test(e), n = /wv|webview/i.test(e);
5
+ return t || n;
6
+ }, t = () => typeof window > "u" ? !1 : window.innerWidth <= 768, n = () => e() || t();
7
+ //#endregion
8
+ export { n as isMobile, e as isMobileOrWebview, t as isMobileViewport };
9
+
10
+ //# sourceMappingURL=device.es.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device.es.mjs","names":[],"sources":["../src/device/isMobileOrWebview.ts","../src/device/isMobileViewport.ts","../src/device/isMobile.ts"],"sourcesContent":["/**\n * Detects if the current environment is a mobile device or running inside a\n * webview, using userAgent heuristics common on iOS/Android and webviews.\n * Guards against non-browser environments (SSR/tests without navigator).\n * @returns {boolean} True if likely mobile or webview.\n */\nexport const isMobileOrWebview = (): boolean => {\n if (typeof navigator === 'undefined') return false\n\n const userAgent = navigator.userAgent.toLowerCase()\n const isMobilePlatform =\n /mobile|android|ios|iphone|ipad|ipod|windows phone/i.test(userAgent)\n const isWebview = /wv|webview/i.test(userAgent)\n\n return isMobilePlatform || isWebview\n}\n","/**\n * Checks if the viewport width is mobile-sized (<= 768px).\n * Guards against non-browser environments (SSR/tests without window).\n * @returns {boolean} True if the viewport width is 768px or less.\n */\nexport const isMobileViewport = (): boolean => {\n if (typeof window === 'undefined') return false\n return window.innerWidth <= 768\n}\n","import { isMobileOrWebview } from './isMobileOrWebview'\nimport { isMobileViewport } from './isMobileViewport'\n\n/**\n * Checks if the device is mobile either by user agent/webview or viewport size.\n * @returns {boolean} True if either a mobile/webview UA or a mobile viewport.\n */\nexport const isMobile = (): boolean =>\n isMobileOrWebview() || isMobileViewport()\n"],"mappings":";AAMA,IAAa,UAAmC;CAC9C,IAAI,OAAO,YAAc,KAAa,OAAO;CAE7C,IAAM,IAAY,UAAU,UAAU,YAAY,GAC5C,IACJ,qDAAqD,KAAK,CAAS,GAC/D,IAAY,cAAc,KAAK,CAAS;CAE9C,OAAO,KAAoB;AAC7B,GCVa,UACP,OAAO,SAAW,MAAoB,KACnC,OAAO,cAAc,KCAjB,UACX,EAAkB,KAAK,EAAiB"}
@@ -0,0 +1,2 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require("dompurify");c=s(c);var l=e=>{if(!e)return``;try{return c.default.sanitize(e,{ALLOWED_TAGS:[],KEEP_CONTENT:!0})}catch{return e.replace(/<\/?[^>]+(>|$)/g,``)}},u=e=>l(e).toLowerCase().replace(/[.,!?;:"'()[\]\-_/\\]/g,` `).replace(/\s+/g,` `).trim(),d=(0,c.default)(window),f=null;d.addHook(`afterSanitizeAttributes`,e=>{if(e instanceof Element&&(e.tagName===`A`&&e.getAttribute(`target`)===`_blank`&&e.setAttribute(`rel`,`noopener noreferrer`),e.tagName===`IFRAME`&&f)){let t=e.getAttribute(`src`)??``;f(t)||e.parentNode?.removeChild(e)}});var p=(e,t={})=>{f=t.isAllowedIframeSrc??null;try{return d.sanitize(e,{USE_PROFILES:{html:!0},ADD_TAGS:[`iframe`],ADD_ATTR:[`target`,`allow`,`allowfullscreen`,`frameborder`,`scrolling`,`loading`,`referrerpolicy`],FORBID_TAGS:[`script`,`style`],FORBID_ATTR:[`onerror`,`onload`,`onclick`]})}finally{f=null}},m=e=>c.default.sanitize(e);exports.cleanHTML=u,exports.sanitizeArticleHtml=p,exports.sanitizeHtml=m,exports.stripHtmlTags=l;
2
+ //# sourceMappingURL=html.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.cjs.js","names":[],"sources":["../src/html/stripHtmlTags.ts","../src/html/cleanHTML.ts","../src/html/sanitizeArticleHtml.ts","../src/html/sanitizeHtml.ts"],"sourcesContent":["import DOMPurify from 'dompurify'\n\n/**\n * Strips all HTML tags, keeping the text content. From smart-search's\n * stripHtmlTags; its html-react-parser fallback is intentionally dropped to\n * avoid a heavy runtime dependency — the DOMPurify path plus a regex fallback is\n * sufficient and never throws in practice.\n * @param {string | undefined} html - String that may contain HTML tags.\n * @returns {string} The text content with tags removed.\n */\nexport const stripHtmlTags = (html?: string): string => {\n if (!html) return ''\n\n try {\n return DOMPurify.sanitize(html, { ALLOWED_TAGS: [], KEEP_CONTENT: true })\n } catch {\n return html.replace(/<\\/?[^>]+(>|$)/g, '')\n }\n}\n","import { stripHtmlTags } from './stripHtmlTags'\n\n/**\n * Normalizes HTML into a lowercase, punctuation-free, single-spaced token string\n * for search indexing/matching. From alerts' cleanHTML; builds on stripHtmlTags\n * so tag removal stays single-sourced.\n * @param {string} html - The HTML string to clean.\n * @returns {string} The cleaned, normalized text.\n */\nexport const cleanHTML = (html: string): string =>\n stripHtmlTags(html)\n .toLowerCase()\n .replace(/[.,!?;:\"'()[\\]\\-_/\\\\]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n","import createDOMPurify from 'dompurify'\n\nimport type { SanitizeArticleHtmlOptions } from '../types/html/SanitizeArticleHtmlOptions'\n\n// Dedicated DOMPurify instance so the hardening hook below is scoped to this\n// sanitizer and never pollutes the consumer's shared default DOMPurify instance\n// (other code may sanitize through the default instance directly).\nconst purifier = createDOMPurify(window)\n\n// Set synchronously around each (synchronous) sanitize call. JS is\n// single-threaded, so the guard cannot leak across calls.\nlet activeIframeGuard: ((src: string) => boolean) | null = null\n\n// afterSanitizeAttributes hook: force rel=\"noopener noreferrer\" on\n// target=\"_blank\" anchors (reverse-tabnabbing), and drop iframes whose src fails\n// the injected allowlist predicate when one is active.\npurifier.addHook('afterSanitizeAttributes', (node) => {\n if (!(node instanceof Element)) return\n\n if (node.tagName === 'A' && node.getAttribute('target') === '_blank') {\n node.setAttribute('rel', 'noopener noreferrer')\n }\n\n if (node.tagName === 'IFRAME' && activeIframeGuard) {\n const src = node.getAttribute('src') ?? ''\n if (!activeIframeGuard(src)) node.parentNode?.removeChild(node)\n }\n})\n\n/**\n * Canonical sanitizer for rich article HTML rendered into a session-bearing\n * webview. Superset of the alerts and unacknowledged-bulletins variants: keeps\n * iframes and data-* attributes (renderWidgets discovers embedded sub-widgets via\n * data-*), strips scripts/inline handlers/javascript:, and forces\n * rel=\"noopener noreferrer\" on target=\"_blank\" anchors. When options.isAllowedIframeSrc\n * is provided, iframes whose src fails it are dropped (injected so this module\n * does not depend on /links, which owns isAllowedIframeSrc).\n * @param {string} html - Raw article HTML from the Staffbase API.\n * @param {SanitizeArticleHtmlOptions} options - Optional iframe-src allowlist.\n * @returns {string} Sanitized HTML safe to inject/parse.\n */\nexport const sanitizeArticleHtml = (\n html: string,\n options: SanitizeArticleHtmlOptions = {},\n): string => {\n activeIframeGuard = options.isAllowedIframeSrc ?? null\n try {\n return purifier.sanitize(html, {\n USE_PROFILES: { html: true },\n ADD_TAGS: ['iframe'],\n ADD_ATTR: [\n 'target',\n 'allow',\n 'allowfullscreen',\n 'frameborder',\n 'scrolling',\n 'loading',\n 'referrerpolicy',\n ],\n FORBID_TAGS: ['script', 'style'],\n FORBID_ATTR: ['onerror', 'onload', 'onclick'],\n })\n } finally {\n activeIframeGuard = null\n }\n}\n","import DOMPurify from 'dompurify'\n\n/**\n * Strict sanitizer for untrusted snippet/teaser HTML rendered through a\n * non-sanitizing parser (e.g. html-react-parser). The default DOMPurify profile\n * strips scripts, inline handlers and dangerous URLs AND drops iframes, keeping\n * only basic rich-text markup. From global-content's sanitizeHtml.\n *\n * Use sanitizeArticleHtml instead when rendering full article bodies that must\n * keep iframes / data-* embeds.\n * @param {string} html - Raw HTML string from the API.\n * @returns {string} Sanitized HTML.\n */\nexport const sanitizeHtml = (html: string): string => DOMPurify.sanitize(html)\n"],"mappings":"mkBAUA,IAAa,EAAiB,GAA0B,CACtD,GAAI,CAAC,EAAM,MAAO,GAElB,GAAI,CACF,OAAO,EAAA,QAAU,SAAS,EAAM,CAAE,aAAc,CAAC,EAAG,aAAc,EAAK,CAAC,CAC1E,MAAQ,CACN,OAAO,EAAK,QAAQ,kBAAmB,EAAE,CAC3C,CACF,ECTa,EAAa,GACxB,EAAc,CAAI,EACf,YAAY,EACZ,QAAQ,yBAA0B,GAAG,EACrC,QAAQ,OAAQ,GAAG,EACnB,KAAK,ECPJ,GAAA,EAAA,EAAA,SAA2B,MAAM,EAInC,EAAuD,KAK3D,EAAS,QAAQ,0BAA4B,GAAS,CAC9C,gBAAgB,UAElB,EAAK,UAAY,KAAO,EAAK,aAAa,QAAQ,IAAM,UAC1D,EAAK,aAAa,MAAO,qBAAqB,EAG5C,EAAK,UAAY,UAAY,GAAmB,CAClD,IAAM,EAAM,EAAK,aAAa,KAAK,GAAK,GACnC,EAAkB,CAAG,GAAG,EAAK,YAAY,YAAY,CAAI,CAChE,CACF,CAAC,EAcD,IAAa,GACX,EACA,EAAsC,CAAC,IAC5B,CACX,EAAoB,EAAQ,oBAAsB,KAClD,GAAI,CACF,OAAO,EAAS,SAAS,EAAM,CAC7B,aAAc,CAAE,KAAM,EAAK,EAC3B,SAAU,CAAC,QAAQ,EACnB,SAAU,CACR,SACA,QACA,kBACA,cACA,YACA,UACA,gBACF,EACA,YAAa,CAAC,SAAU,OAAO,EAC/B,YAAa,CAAC,UAAW,SAAU,SAAS,CAC9C,CAAC,CACH,QAAU,CACR,EAAoB,IACtB,CACF,ECpDa,EAAgB,GAAyB,EAAA,QAAU,SAAS,CAAI"}
@@ -0,0 +1,49 @@
1
+ import e from "dompurify";
2
+ //#region src/html/stripHtmlTags.ts
3
+ var t = (t) => {
4
+ if (!t) return "";
5
+ try {
6
+ return e.sanitize(t, {
7
+ ALLOWED_TAGS: [],
8
+ KEEP_CONTENT: !0
9
+ });
10
+ } catch {
11
+ return t.replace(/<\/?[^>]+(>|$)/g, "");
12
+ }
13
+ }, n = (e) => t(e).toLowerCase().replace(/[.,!?;:"'()[\]\-_/\\]/g, " ").replace(/\s+/g, " ").trim(), r = e(window), i = null;
14
+ r.addHook("afterSanitizeAttributes", (e) => {
15
+ if (e instanceof Element && (e.tagName === "A" && e.getAttribute("target") === "_blank" && e.setAttribute("rel", "noopener noreferrer"), e.tagName === "IFRAME" && i)) {
16
+ let t = e.getAttribute("src") ?? "";
17
+ i(t) || e.parentNode?.removeChild(e);
18
+ }
19
+ });
20
+ var a = (e, t = {}) => {
21
+ i = t.isAllowedIframeSrc ?? null;
22
+ try {
23
+ return r.sanitize(e, {
24
+ USE_PROFILES: { html: !0 },
25
+ ADD_TAGS: ["iframe"],
26
+ ADD_ATTR: [
27
+ "target",
28
+ "allow",
29
+ "allowfullscreen",
30
+ "frameborder",
31
+ "scrolling",
32
+ "loading",
33
+ "referrerpolicy"
34
+ ],
35
+ FORBID_TAGS: ["script", "style"],
36
+ FORBID_ATTR: [
37
+ "onerror",
38
+ "onload",
39
+ "onclick"
40
+ ]
41
+ });
42
+ } finally {
43
+ i = null;
44
+ }
45
+ }, o = (t) => e.sanitize(t);
46
+ //#endregion
47
+ export { n as cleanHTML, a as sanitizeArticleHtml, o as sanitizeHtml, t as stripHtmlTags };
48
+
49
+ //# sourceMappingURL=html.es.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.es.mjs","names":[],"sources":["../src/html/stripHtmlTags.ts","../src/html/cleanHTML.ts","../src/html/sanitizeArticleHtml.ts","../src/html/sanitizeHtml.ts"],"sourcesContent":["import DOMPurify from 'dompurify'\n\n/**\n * Strips all HTML tags, keeping the text content. From smart-search's\n * stripHtmlTags; its html-react-parser fallback is intentionally dropped to\n * avoid a heavy runtime dependency — the DOMPurify path plus a regex fallback is\n * sufficient and never throws in practice.\n * @param {string | undefined} html - String that may contain HTML tags.\n * @returns {string} The text content with tags removed.\n */\nexport const stripHtmlTags = (html?: string): string => {\n if (!html) return ''\n\n try {\n return DOMPurify.sanitize(html, { ALLOWED_TAGS: [], KEEP_CONTENT: true })\n } catch {\n return html.replace(/<\\/?[^>]+(>|$)/g, '')\n }\n}\n","import { stripHtmlTags } from './stripHtmlTags'\n\n/**\n * Normalizes HTML into a lowercase, punctuation-free, single-spaced token string\n * for search indexing/matching. From alerts' cleanHTML; builds on stripHtmlTags\n * so tag removal stays single-sourced.\n * @param {string} html - The HTML string to clean.\n * @returns {string} The cleaned, normalized text.\n */\nexport const cleanHTML = (html: string): string =>\n stripHtmlTags(html)\n .toLowerCase()\n .replace(/[.,!?;:\"'()[\\]\\-_/\\\\]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n","import createDOMPurify from 'dompurify'\n\nimport type { SanitizeArticleHtmlOptions } from '../types/html/SanitizeArticleHtmlOptions'\n\n// Dedicated DOMPurify instance so the hardening hook below is scoped to this\n// sanitizer and never pollutes the consumer's shared default DOMPurify instance\n// (other code may sanitize through the default instance directly).\nconst purifier = createDOMPurify(window)\n\n// Set synchronously around each (synchronous) sanitize call. JS is\n// single-threaded, so the guard cannot leak across calls.\nlet activeIframeGuard: ((src: string) => boolean) | null = null\n\n// afterSanitizeAttributes hook: force rel=\"noopener noreferrer\" on\n// target=\"_blank\" anchors (reverse-tabnabbing), and drop iframes whose src fails\n// the injected allowlist predicate when one is active.\npurifier.addHook('afterSanitizeAttributes', (node) => {\n if (!(node instanceof Element)) return\n\n if (node.tagName === 'A' && node.getAttribute('target') === '_blank') {\n node.setAttribute('rel', 'noopener noreferrer')\n }\n\n if (node.tagName === 'IFRAME' && activeIframeGuard) {\n const src = node.getAttribute('src') ?? ''\n if (!activeIframeGuard(src)) node.parentNode?.removeChild(node)\n }\n})\n\n/**\n * Canonical sanitizer for rich article HTML rendered into a session-bearing\n * webview. Superset of the alerts and unacknowledged-bulletins variants: keeps\n * iframes and data-* attributes (renderWidgets discovers embedded sub-widgets via\n * data-*), strips scripts/inline handlers/javascript:, and forces\n * rel=\"noopener noreferrer\" on target=\"_blank\" anchors. When options.isAllowedIframeSrc\n * is provided, iframes whose src fails it are dropped (injected so this module\n * does not depend on /links, which owns isAllowedIframeSrc).\n * @param {string} html - Raw article HTML from the Staffbase API.\n * @param {SanitizeArticleHtmlOptions} options - Optional iframe-src allowlist.\n * @returns {string} Sanitized HTML safe to inject/parse.\n */\nexport const sanitizeArticleHtml = (\n html: string,\n options: SanitizeArticleHtmlOptions = {},\n): string => {\n activeIframeGuard = options.isAllowedIframeSrc ?? null\n try {\n return purifier.sanitize(html, {\n USE_PROFILES: { html: true },\n ADD_TAGS: ['iframe'],\n ADD_ATTR: [\n 'target',\n 'allow',\n 'allowfullscreen',\n 'frameborder',\n 'scrolling',\n 'loading',\n 'referrerpolicy',\n ],\n FORBID_TAGS: ['script', 'style'],\n FORBID_ATTR: ['onerror', 'onload', 'onclick'],\n })\n } finally {\n activeIframeGuard = null\n }\n}\n","import DOMPurify from 'dompurify'\n\n/**\n * Strict sanitizer for untrusted snippet/teaser HTML rendered through a\n * non-sanitizing parser (e.g. html-react-parser). The default DOMPurify profile\n * strips scripts, inline handlers and dangerous URLs AND drops iframes, keeping\n * only basic rich-text markup. From global-content's sanitizeHtml.\n *\n * Use sanitizeArticleHtml instead when rendering full article bodies that must\n * keep iframes / data-* embeds.\n * @param {string} html - Raw HTML string from the API.\n * @returns {string} Sanitized HTML.\n */\nexport const sanitizeHtml = (html: string): string => DOMPurify.sanitize(html)\n"],"mappings":";;AAUA,IAAa,KAAiB,MAA0B;CACtD,IAAI,CAAC,GAAM,OAAO;CAElB,IAAI;EACF,OAAO,EAAU,SAAS,GAAM;GAAE,cAAc,CAAC;GAAG,cAAc;EAAK,CAAC;CAC1E,QAAQ;EACN,OAAO,EAAK,QAAQ,mBAAmB,EAAE;CAC3C;AACF,GCTa,KAAa,MACxB,EAAc,CAAI,EACf,YAAY,EACZ,QAAQ,0BAA0B,GAAG,EACrC,QAAQ,QAAQ,GAAG,EACnB,KAAK,GCPJ,IAAW,EAAgB,MAAM,GAInC,IAAuD;AAK3D,EAAS,QAAQ,4BAA4B,MAAS;CAC9C,iBAAgB,YAElB,EAAK,YAAY,OAAO,EAAK,aAAa,QAAQ,MAAM,YAC1D,EAAK,aAAa,OAAO,qBAAqB,GAG5C,EAAK,YAAY,YAAY,IAAmB;EAClD,IAAM,IAAM,EAAK,aAAa,KAAK,KAAK;EACxC,AAAK,EAAkB,CAAG,KAAG,EAAK,YAAY,YAAY,CAAI;CAChE;AACF,CAAC;AAcD,IAAa,KACX,GACA,IAAsC,CAAC,MAC5B;CACX,IAAoB,EAAQ,sBAAsB;CAClD,IAAI;EACF,OAAO,EAAS,SAAS,GAAM;GAC7B,cAAc,EAAE,MAAM,GAAK;GAC3B,UAAU,CAAC,QAAQ;GACnB,UAAU;IACR;IACA;IACA;IACA;IACA;IACA;IACA;GACF;GACA,aAAa,CAAC,UAAU,OAAO;GAC/B,aAAa;IAAC;IAAW;IAAU;GAAS;EAC9C,CAAC;CACH,UAAU;EACR,IAAoB;CACtB;AACF,GCpDa,KAAgB,MAAyB,EAAU,SAAS,CAAI"}
@@ -0,0 +1,4 @@
1
+ export { isMobile } from './isMobile';
2
+ export { isMobileOrWebview } from './isMobileOrWebview';
3
+ export { isMobileViewport } from './isMobileViewport';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/device/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AACrC,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Checks if the device is mobile either by user agent/webview or viewport size.
3
+ * @returns {boolean} True if either a mobile/webview UA or a mobile viewport.
4
+ */
5
+ export declare const isMobile: () => boolean;
6
+ //# sourceMappingURL=isMobile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isMobile.d.ts","sourceRoot":"","sources":["../../../src/device/isMobile.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,eAAO,MAAM,QAAQ,QAAO,OACe,CAAA"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Detects if the current environment is a mobile device or running inside a
3
+ * webview, using userAgent heuristics common on iOS/Android and webviews.
4
+ * Guards against non-browser environments (SSR/tests without navigator).
5
+ * @returns {boolean} True if likely mobile or webview.
6
+ */
7
+ export declare const isMobileOrWebview: () => boolean;
8
+ //# sourceMappingURL=isMobileOrWebview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isMobileOrWebview.d.ts","sourceRoot":"","sources":["../../../src/device/isMobileOrWebview.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,QAAO,OASpC,CAAA"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Checks if the viewport width is mobile-sized (<= 768px).
3
+ * Guards against non-browser environments (SSR/tests without window).
4
+ * @returns {boolean} True if the viewport width is 768px or less.
5
+ */
6
+ export declare const isMobileViewport: () => boolean;
7
+ //# sourceMappingURL=isMobileViewport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isMobileViewport.d.ts","sourceRoot":"","sources":["../../../src/device/isMobileViewport.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,QAAO,OAGnC,CAAA"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Normalizes HTML into a lowercase, punctuation-free, single-spaced token string
3
+ * for search indexing/matching. From alerts' cleanHTML; builds on stripHtmlTags
4
+ * so tag removal stays single-sourced.
5
+ * @param {string} html - The HTML string to clean.
6
+ * @returns {string} The cleaned, normalized text.
7
+ */
8
+ export declare const cleanHTML: (html: string) => string;
9
+ //# sourceMappingURL=cleanHTML.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cleanHTML.d.ts","sourceRoot":"","sources":["../../../src/html/cleanHTML.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,GAAI,MAAM,MAAM,KAAG,MAK9B,CAAA"}
@@ -0,0 +1,5 @@
1
+ export { cleanHTML } from './cleanHTML';
2
+ export { sanitizeArticleHtml } from './sanitizeArticleHtml';
3
+ export { sanitizeHtml } from './sanitizeHtml';
4
+ export { stripHtmlTags } from './stripHtmlTags';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/html/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA"}
@@ -0,0 +1,15 @@
1
+ import { SanitizeArticleHtmlOptions } from '../types/html/SanitizeArticleHtmlOptions';
2
+ /**
3
+ * Canonical sanitizer for rich article HTML rendered into a session-bearing
4
+ * webview. Superset of the alerts and unacknowledged-bulletins variants: keeps
5
+ * iframes and data-* attributes (renderWidgets discovers embedded sub-widgets via
6
+ * data-*), strips scripts/inline handlers/javascript:, and forces
7
+ * rel="noopener noreferrer" on target="_blank" anchors. When options.isAllowedIframeSrc
8
+ * is provided, iframes whose src fails it are dropped (injected so this module
9
+ * does not depend on /links, which owns isAllowedIframeSrc).
10
+ * @param {string} html - Raw article HTML from the Staffbase API.
11
+ * @param {SanitizeArticleHtmlOptions} options - Optional iframe-src allowlist.
12
+ * @returns {string} Sanitized HTML safe to inject/parse.
13
+ */
14
+ export declare const sanitizeArticleHtml: (html: string, options?: SanitizeArticleHtmlOptions) => string;
15
+ //# sourceMappingURL=sanitizeArticleHtml.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitizeArticleHtml.d.ts","sourceRoot":"","sources":["../../../src/html/sanitizeArticleHtml.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,0CAA0C,CAAA;AA2B1F;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,mBAAmB,GAC9B,MAAM,MAAM,EACZ,UAAS,0BAA+B,KACvC,MAqBF,CAAA"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Strict sanitizer for untrusted snippet/teaser HTML rendered through a
3
+ * non-sanitizing parser (e.g. html-react-parser). The default DOMPurify profile
4
+ * strips scripts, inline handlers and dangerous URLs AND drops iframes, keeping
5
+ * only basic rich-text markup. From global-content's sanitizeHtml.
6
+ *
7
+ * Use sanitizeArticleHtml instead when rendering full article bodies that must
8
+ * keep iframes / data-* embeds.
9
+ * @param {string} html - Raw HTML string from the API.
10
+ * @returns {string} Sanitized HTML.
11
+ */
12
+ export declare const sanitizeHtml: (html: string) => string;
13
+ //# sourceMappingURL=sanitizeHtml.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitizeHtml.d.ts","sourceRoot":"","sources":["../../../src/html/sanitizeHtml.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,KAAG,MAAkC,CAAA"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Strips all HTML tags, keeping the text content. From smart-search's
3
+ * stripHtmlTags; its html-react-parser fallback is intentionally dropped to
4
+ * avoid a heavy runtime dependency — the DOMPurify path plus a regex fallback is
5
+ * sufficient and never throws in practice.
6
+ * @param {string | undefined} html - String that may contain HTML tags.
7
+ * @returns {string} The text content with tags removed.
8
+ */
9
+ export declare const stripHtmlTags: (html?: string) => string;
10
+ //# sourceMappingURL=stripHtmlTags.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stripHtmlTags.d.ts","sourceRoot":"","sources":["../../../src/html/stripHtmlTags.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,GAAI,OAAO,MAAM,KAAG,MAQ7C,CAAA"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Options for sanitizeArticleHtml.
3
+ */
4
+ export interface SanitizeArticleHtmlOptions {
5
+ /**
6
+ * Predicate deciding whether an iframe `src` may be kept. When provided,
7
+ * iframes whose src fails the predicate are dropped. When omitted, iframes are
8
+ * kept (DOMPurify still blocks `src="javascript:"`). Injected so the /html
9
+ * module does not depend on /links, which owns isAllowedIframeSrc.
10
+ * @param {string} src - The iframe src attribute value.
11
+ * @returns {boolean} True when the iframe may be kept.
12
+ */
13
+ isAllowedIframeSrc?: (src: string) => boolean;
14
+ }
15
+ //# sourceMappingURL=SanitizeArticleHtmlOptions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SanitizeArticleHtmlOptions.d.ts","sourceRoot":"","sources":["../../../../src/types/html/SanitizeArticleHtmlOptions.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;;;;;OAOG;IACH,kBAAkB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAA;CAC9C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@favish/staffbase-utils",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "Shared internal/host utilities for Staffbase widgets",
5
5
  "author": "Favish <dev@favish.com>",
6
6
  "license": "UNLICENSED",
@@ -12,6 +12,16 @@
12
12
  "types": "./dist/src/log/index.d.ts",
13
13
  "import": "./dist/log.es.mjs",
14
14
  "require": "./dist/log.cjs.js"
15
+ },
16
+ "./device": {
17
+ "types": "./dist/src/device/index.d.ts",
18
+ "import": "./dist/device.es.mjs",
19
+ "require": "./dist/device.cjs.js"
20
+ },
21
+ "./html": {
22
+ "types": "./dist/src/html/index.d.ts",
23
+ "import": "./dist/html.es.mjs",
24
+ "require": "./dist/html.cjs.js"
15
25
  }
16
26
  },
17
27
  "repository": {
@@ -58,6 +68,9 @@
58
68
  "vite": "^8.0.14",
59
69
  "vite-plugin-dts": "^5.0.1"
60
70
  },
71
+ "dependencies": {
72
+ "dompurify": "3.4.7"
73
+ },
61
74
  "scripts": {
62
75
  "build": "vite build",
63
76
  "check:all": "pnpm run format && pnpm run lint:fix && pnpm run type-check && pnpm run test",