{"version":3,"file":"index.cjs","sources":["../../../app/utils/index.ts"],"sourcesContent":["import xss, { escapeAttrValue } from \"xss\"\n\nexport const ALLOWED_HTML_ATTRIBUTES = [\"href\", \"name\", \"target\", \"title\", \"class\", \"id\", \"style\"]\n\nexport const ALLOWED_HTML_TAGS = [\n  \"p\",\n  \"strong\",\n  \"b\",\n  \"code\",\n  \"a\",\n  \"br\",\n  \"i\",\n  \"ul\",\n  \"li\",\n  \"em\",\n  \"small\",\n  \"details\",\n  \"summary\",\n  \"mark\",\n]\n\n/**\n * Constants and utility functions that help in HTML, CSS and DOM manipulation\n */\nexport function sanitizeHtml(dirtyHtml: string) {\n  const sanitizedHtml = xss(dirtyHtml, {\n    onTagAttr: (tag, name, value) => {\n      if (tag === \"img\" && name === \"src\") {\n        // Only allow http requests to supported image files from the `static` directory\n        const isImageFile = value.split(\"#\")[0].match(/\\.(jpeg|jpg|gif|png|webp)$/) !== null\n        const isStaticImageFile = isImageFile && value.startsWith(\"/static/\")\n        if (!value.startsWith(\"https://\") && !isStaticImageFile) {\n          return \"\"\n        }\n      }\n\n      if (ALLOWED_HTML_ATTRIBUTES.includes(name) || name.startsWith(\"data-\")) {\n        // href is allowed but we allow only https and relative URLs\n        if (name === \"href\" && !value.match(/^https?:\\/\\//gm) && !value.startsWith(\"/\")) {\n          return \"\"\n        }\n        return `${name}=\"${escapeAttrValue(value)}\"`\n      }\n\n      return\n      // Return nothing, means keep the default handling measure\n    },\n    onTag: (tag) => {\n      if (!ALLOWED_HTML_TAGS.includes(tag)) return \"\"\n      return\n    },\n  })\n\n  return sanitizedHtml\n}\n\n/**\n * 检查第一个字符串是否以第二个字符串为前缀（不区分大小写）\n * @param first 要检查的字符串\n * @param second 前缀字符串\n * @returns 如果first以second开头则返回true，否则返回false\n */\nexport const prefixMatch = (first: string, second: string) =>\n  first.toLocaleLowerCase().startsWith(second.toLocaleLowerCase())\n\n/**\n * 计算多个字符串的最长公共前缀\n * @param strings 要计算公共前缀的字符串数组\n * @returns 所有字符串的最长公共前缀，如果没有公共前缀则返回空字符串\n * @example\n * longestCommonPrefix(\"hello\", \"help\", \"helicopter\") // 返回 \"he\"\n * longestCommonPrefix(\"apple\", \"banana\", \"cherry\") // 返回 \"\"\n */\nexport function longestCommonPrefix(...strings: string[]) {\n  if (strings.length < 2) return \"\"\n\n  return strings.reduce((prefix, str) => {\n    while (!str.startsWith(prefix)) {\n      prefix = prefix.slice(0, -1)\n      if (prefix === \"\") return \"\"\n    }\n    return prefix\n  }, strings[0])\n}\n"],"names":["xss","escapeAttrValue"],"mappings":";;;AAEO,MAAM,0BAA0B,CAAC,QAAQ,QAAQ,UAAU,SAAS,SAAS,MAAM,OAAO;AAE1F,MAAM,oBAAoB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAKO,SAAS,aAAa,WAAmB;AAC9C,QAAM,gBAAgBA,MAAAA,QAAI,WAAW;AAAA,IACnC,WAAW,CAAC,KAAK,MAAM,UAAU;AAC/B,UAAI,QAAQ,SAAS,SAAS,OAAO;AAEnC,cAAM,cAAc,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE,MAAM,4BAA4B,MAAM;AAChF,cAAM,oBAAoB,eAAe,MAAM,WAAW,UAAU;AACpE,YAAI,CAAC,MAAM,WAAW,UAAU,KAAK,CAAC,mBAAmB;AACvD,iBAAO;AAAA,QACT;AAAA,MACF;AAEA,UAAI,wBAAwB,SAAS,IAAI,KAAK,KAAK,WAAW,OAAO,GAAG;AAEtE,YAAI,SAAS,UAAU,CAAC,MAAM,MAAM,gBAAgB,KAAK,CAAC,MAAM,WAAW,GAAG,GAAG;AAC/E,iBAAO;AAAA,QACT;AACA,eAAO,GAAG,IAAI,KAAKC,MAAAA,WAAAA,gBAAgB,KAAK,CAAC;AAAA,MAC3C;AAEA;AAAA,IAEF;AAAA,IACA,OAAO,CAAC,QAAQ;AACd,UAAI,CAAC,kBAAkB,SAAS,GAAG,EAAG,QAAO;AAC7C;AAAA,IACF;AAAA,EAAA,CACD;AAED,SAAO;AACT;AAQO,MAAM,cAAc,CAAC,OAAe,WACzC,MAAM,oBAAoB,WAAW,OAAO,kBAAA,CAAmB;AAU1D,SAAS,uBAAuB,SAAmB;AACxD,MAAI,QAAQ,SAAS,EAAG,QAAO;AAE/B,SAAO,QAAQ,OAAO,CAAC,QAAQ,QAAQ;AACrC,WAAO,CAAC,IAAI,WAAW,MAAM,GAAG;AAC9B,eAAS,OAAO,MAAM,GAAG,EAAE;AAC3B,UAAI,WAAW,GAAI,QAAO;AAAA,IAC5B;AACA,WAAO;AAAA,EACT,GAAG,QAAQ,CAAC,CAAC;AACf;;;;;;"}