open-chat-studio-widget 0.9.0 → 0.10.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.
- package/dist/cjs/{index-DDod9Zyw.js → index-DMXmZhVD.js} +3 -3
- package/dist/cjs/index-DMXmZhVD.js.map +1 -0
- package/dist/cjs/loader.cjs.js +2 -2
- package/dist/cjs/open-chat-studio-widget.cjs.entry.js +1261 -322
- package/dist/cjs/open-chat-studio-widget.cjs.entry.js.map +1 -1
- package/dist/cjs/open-chat-studio-widget.cjs.js +2 -2
- package/dist/cjs/open-chat-studio-widget.entry.cjs.js.map +1 -1
- package/dist/collection/components/ocs-chat/ocs-chat.js +91 -6
- package/dist/collection/components/ocs-chat/ocs-chat.js.map +1 -1
- package/dist/collection/services/chat-session-service.js +12 -2
- package/dist/collection/services/chat-session-service.js.map +1 -1
- package/dist/collection/services/file-attachment-manager.js +2 -5
- package/dist/collection/services/file-attachment-manager.js.map +1 -1
- package/dist/components/open-chat-studio-widget.js +1261 -321
- package/dist/components/open-chat-studio-widget.js.map +1 -1
- package/dist/esm/{index-iUBQH9om.js → index-JDApwJx_.js} +3 -3
- package/dist/esm/index-JDApwJx_.js.map +1 -0
- package/dist/esm/loader.js +3 -3
- package/dist/esm/open-chat-studio-widget.entry.js +1261 -322
- package/dist/esm/open-chat-studio-widget.entry.js.map +1 -1
- package/dist/esm/open-chat-studio-widget.js +3 -3
- package/dist/open-chat-studio-widget/open-chat-studio-widget.entry.esm.js.map +1 -1
- package/dist/open-chat-studio-widget/open-chat-studio-widget.esm.js +1 -1
- package/dist/open-chat-studio-widget/{p-iUBQH9om.js → p-JDApwJx_.js} +2 -2
- package/dist/open-chat-studio-widget/p-JDApwJx_.js.map +1 -0
- package/dist/open-chat-studio-widget/p-c2d3b2d1.entry.js +4 -0
- package/dist/open-chat-studio-widget/p-c2d3b2d1.entry.js.map +1 -0
- package/dist/types/components/ocs-chat/ocs-chat.d.ts +15 -0
- package/dist/types/services/chat-session-service.d.ts +4 -0
- package/dist/types/services/file-attachment-manager.d.ts +2 -1
- package/package.json +2 -2
- package/dist/cjs/index-DDod9Zyw.js.map +0 -1
- package/dist/esm/index-iUBQH9om.js.map +0 -1
- package/dist/open-chat-studio-widget/p-9c925476.entry.js +0 -4
- package/dist/open-chat-studio-widget/p-9c925476.entry.js.map +0 -1
- package/dist/open-chat-studio-widget/p-iUBQH9om.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { h, r as registerInstance, E as Env, H as Host, g as getElement } from './index-
|
|
1
|
+
import { h, r as registerInstance, E as Env, H as Host, g as getElement } from './index-JDApwJx_.js';
|
|
2
2
|
|
|
3
3
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
4
4
|
const OcsWidgetAvatar = () => {
|
|
@@ -2942,24 +2942,65 @@ marked.Slugger = Slugger;
|
|
|
2942
2942
|
marked.Hooks = Hooks;
|
|
2943
2943
|
marked.parse = marked;
|
|
2944
2944
|
|
|
2945
|
-
/*! @license DOMPurify 3.4.
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2945
|
+
/*! @license DOMPurify 3.4.11 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.11/LICENSE */
|
|
2946
|
+
|
|
2947
|
+
function _arrayLikeToArray(r, a) {
|
|
2948
|
+
(null == a || a > r.length) && (a = r.length);
|
|
2949
|
+
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
|
|
2950
|
+
return n;
|
|
2951
|
+
}
|
|
2952
|
+
function _arrayWithHoles(r) {
|
|
2953
|
+
if (Array.isArray(r)) return r;
|
|
2954
|
+
}
|
|
2955
|
+
function _iterableToArrayLimit(r, l) {
|
|
2956
|
+
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
|
|
2957
|
+
if (null != t) {
|
|
2958
|
+
var e,
|
|
2959
|
+
n,
|
|
2960
|
+
i,
|
|
2961
|
+
u,
|
|
2962
|
+
a = [],
|
|
2963
|
+
f = true,
|
|
2964
|
+
o = false;
|
|
2965
|
+
try {
|
|
2966
|
+
if (i = (t = t.call(r)).next, 0 === l) ; else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = true);
|
|
2967
|
+
} catch (r) {
|
|
2968
|
+
o = true, n = r;
|
|
2969
|
+
} finally {
|
|
2970
|
+
try {
|
|
2971
|
+
if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
|
|
2972
|
+
} finally {
|
|
2973
|
+
if (o) throw n;
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
return a;
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
function _nonIterableRest() {
|
|
2980
|
+
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
2981
|
+
}
|
|
2982
|
+
function _slicedToArray(r, e) {
|
|
2983
|
+
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
|
|
2984
|
+
}
|
|
2985
|
+
function _unsupportedIterableToArray(r, a) {
|
|
2986
|
+
if (r) {
|
|
2987
|
+
if ("string" == typeof r) return _arrayLikeToArray(r, a);
|
|
2988
|
+
var t = {}.toString.call(r).slice(8, -1);
|
|
2989
|
+
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
|
|
2990
|
+
}
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2993
|
+
const entries = Object.entries,
|
|
2994
|
+
setPrototypeOf = Object.setPrototypeOf,
|
|
2995
|
+
isFrozen = Object.isFrozen,
|
|
2996
|
+
getPrototypeOf = Object.getPrototypeOf,
|
|
2997
|
+
getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|
2998
|
+
let freeze = Object.freeze,
|
|
2999
|
+
seal = Object.seal,
|
|
3000
|
+
create = Object.create; // eslint-disable-line import/no-mutable-exports
|
|
3001
|
+
let _ref = typeof Reflect !== 'undefined' && Reflect,
|
|
3002
|
+
apply = _ref.apply,
|
|
3003
|
+
construct = _ref.construct;
|
|
2963
3004
|
if (!freeze) {
|
|
2964
3005
|
freeze = function freeze(x) {
|
|
2965
3006
|
return x;
|
|
@@ -2991,13 +3032,19 @@ const arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);
|
|
|
2991
3032
|
const arrayPop = unapply(Array.prototype.pop);
|
|
2992
3033
|
const arrayPush = unapply(Array.prototype.push);
|
|
2993
3034
|
const arraySplice = unapply(Array.prototype.splice);
|
|
3035
|
+
const arrayIsArray = Array.isArray;
|
|
2994
3036
|
const stringToLowerCase = unapply(String.prototype.toLowerCase);
|
|
2995
3037
|
const stringToString = unapply(String.prototype.toString);
|
|
2996
3038
|
const stringMatch = unapply(String.prototype.match);
|
|
2997
3039
|
const stringReplace = unapply(String.prototype.replace);
|
|
2998
3040
|
const stringIndexOf = unapply(String.prototype.indexOf);
|
|
2999
3041
|
const stringTrim = unapply(String.prototype.trim);
|
|
3042
|
+
const numberToString = unapply(Number.prototype.toString);
|
|
3043
|
+
const booleanToString = unapply(Boolean.prototype.toString);
|
|
3044
|
+
const bigintToString = typeof BigInt === 'undefined' ? null : unapply(BigInt.prototype.toString);
|
|
3045
|
+
const symbolToString = typeof Symbol === 'undefined' ? null : unapply(Symbol.prototype.toString);
|
|
3000
3046
|
const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);
|
|
3047
|
+
const objectToString = unapply(Object.prototype.toString);
|
|
3001
3048
|
const regExpTest = unapply(RegExp.prototype.test);
|
|
3002
3049
|
const typeErrorCreate = unconstruct(TypeError);
|
|
3003
3050
|
/**
|
|
@@ -3047,6 +3094,9 @@ function addToSet(set, array) {
|
|
|
3047
3094
|
// Prevent prototype setters from intercepting set as a this value.
|
|
3048
3095
|
setPrototypeOf(set, null);
|
|
3049
3096
|
}
|
|
3097
|
+
if (!arrayIsArray(array)) {
|
|
3098
|
+
return set;
|
|
3099
|
+
}
|
|
3050
3100
|
let l = array.length;
|
|
3051
3101
|
while (l--) {
|
|
3052
3102
|
let element = array[l];
|
|
@@ -3087,10 +3137,13 @@ function cleanArray(array) {
|
|
|
3087
3137
|
*/
|
|
3088
3138
|
function clone(object) {
|
|
3089
3139
|
const newObject = create(null);
|
|
3090
|
-
for (const
|
|
3140
|
+
for (const _ref2 of entries(object)) {
|
|
3141
|
+
var _ref3 = _slicedToArray(_ref2, 2);
|
|
3142
|
+
const property = _ref3[0];
|
|
3143
|
+
const value = _ref3[1];
|
|
3091
3144
|
const isPropertyExist = objectHasOwnProperty(object, property);
|
|
3092
3145
|
if (isPropertyExist) {
|
|
3093
|
-
if (
|
|
3146
|
+
if (arrayIsArray(value)) {
|
|
3094
3147
|
newObject[property] = cleanArray(value);
|
|
3095
3148
|
} else if (value && typeof value === 'object' && value.constructor === Object) {
|
|
3096
3149
|
newObject[property] = clone(value);
|
|
@@ -3101,6 +3154,58 @@ function clone(object) {
|
|
|
3101
3154
|
}
|
|
3102
3155
|
return newObject;
|
|
3103
3156
|
}
|
|
3157
|
+
/**
|
|
3158
|
+
* Convert non-node values into strings without depending on direct property access.
|
|
3159
|
+
*
|
|
3160
|
+
* @param value - The value to stringify.
|
|
3161
|
+
* @returns A string representation of the provided value.
|
|
3162
|
+
*/
|
|
3163
|
+
function stringifyValue(value) {
|
|
3164
|
+
switch (typeof value) {
|
|
3165
|
+
case 'string':
|
|
3166
|
+
{
|
|
3167
|
+
return value;
|
|
3168
|
+
}
|
|
3169
|
+
case 'number':
|
|
3170
|
+
{
|
|
3171
|
+
return numberToString(value);
|
|
3172
|
+
}
|
|
3173
|
+
case 'boolean':
|
|
3174
|
+
{
|
|
3175
|
+
return booleanToString(value);
|
|
3176
|
+
}
|
|
3177
|
+
case 'bigint':
|
|
3178
|
+
{
|
|
3179
|
+
return bigintToString ? bigintToString(value) : '0';
|
|
3180
|
+
}
|
|
3181
|
+
case 'symbol':
|
|
3182
|
+
{
|
|
3183
|
+
return symbolToString ? symbolToString(value) : 'Symbol()';
|
|
3184
|
+
}
|
|
3185
|
+
case 'undefined':
|
|
3186
|
+
{
|
|
3187
|
+
return objectToString(value);
|
|
3188
|
+
}
|
|
3189
|
+
case 'function':
|
|
3190
|
+
case 'object':
|
|
3191
|
+
{
|
|
3192
|
+
if (value === null) {
|
|
3193
|
+
return objectToString(value);
|
|
3194
|
+
}
|
|
3195
|
+
const valueAsRecord = value;
|
|
3196
|
+
const valueToString = lookupGetter(valueAsRecord, 'toString');
|
|
3197
|
+
if (typeof valueToString === 'function') {
|
|
3198
|
+
const stringified = valueToString(valueAsRecord);
|
|
3199
|
+
return typeof stringified === 'string' ? stringified : objectToString(stringified);
|
|
3200
|
+
}
|
|
3201
|
+
return objectToString(value);
|
|
3202
|
+
}
|
|
3203
|
+
default:
|
|
3204
|
+
{
|
|
3205
|
+
return objectToString(value);
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3104
3209
|
/**
|
|
3105
3210
|
* This method automatically checks if the prop is function or getter and behaves accordingly.
|
|
3106
3211
|
*
|
|
@@ -3126,6 +3231,14 @@ function lookupGetter(object, prop) {
|
|
|
3126
3231
|
}
|
|
3127
3232
|
return fallbackValue;
|
|
3128
3233
|
}
|
|
3234
|
+
function isRegex(value) {
|
|
3235
|
+
try {
|
|
3236
|
+
regExpTest(value, '');
|
|
3237
|
+
return true;
|
|
3238
|
+
} catch (_unused) {
|
|
3239
|
+
return false;
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
3129
3242
|
|
|
3130
3243
|
const html$1 = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'search', 'section', 'select', 'shadow', 'slot', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']);
|
|
3131
3244
|
const svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'enterkeyhint', 'exportparts', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'inputmode', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'part', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);
|
|
@@ -3141,15 +3254,14 @@ const mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mgly
|
|
|
3141
3254
|
const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
|
|
3142
3255
|
const text = freeze(['#text']);
|
|
3143
3256
|
|
|
3144
|
-
const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'exportparts', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inert', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'part', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'slot', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns'
|
|
3257
|
+
const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'command', 'commandfor', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'exportparts', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inert', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'part', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'slot', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns']);
|
|
3145
3258
|
const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'amplitude', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'exponent', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'mask-type', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'slope', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'tablevalues', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']);
|
|
3146
3259
|
const mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnalign', 'columnlines', 'columnspacing', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lquote', 'lspace', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']);
|
|
3147
3260
|
const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
|
|
3148
3261
|
|
|
3149
|
-
|
|
3150
|
-
const
|
|
3151
|
-
const
|
|
3152
|
-
const TMPLIT_EXPR = seal(/\$\{[\w\W]*/gm); // eslint-disable-line unicorn/better-regex
|
|
3262
|
+
const MUSTACHE_EXPR = seal(/{{[\w\W]*|^[\w\W]*}}/g);
|
|
3263
|
+
const ERB_EXPR = seal(/<%[\w\W]*|^[\w\W]*%>/g);
|
|
3264
|
+
const TMPLIT_EXPR = seal(/\${[\w\W]*/g);
|
|
3153
3265
|
const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]+$/); // eslint-disable-line no-useless-escape
|
|
3154
3266
|
const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
|
|
3155
3267
|
const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
|
|
@@ -3159,30 +3271,23 @@ const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205
|
|
|
3159
3271
|
);
|
|
3160
3272
|
const DOCTYPE_NAME = seal(/^html$/i);
|
|
3161
3273
|
const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);
|
|
3274
|
+
// Markup-significant character probes used by _sanitizeElements.
|
|
3275
|
+
// Shared module-level instances are safe despite the sticky /g flags:
|
|
3276
|
+
// unapply() resets lastIndex for RegExp receivers before every call.
|
|
3277
|
+
const ELEMENT_MARKUP_PROBE = seal(/<[/\w!]/g);
|
|
3278
|
+
const COMMENT_MARKUP_PROBE = seal(/<[/\w]/g);
|
|
3279
|
+
const FALLBACK_TAG_CLOSE = seal(/<\/no(script|embed|frames)/i);
|
|
3280
|
+
const SELF_CLOSING_TAG = seal(/\/>/i);
|
|
3162
3281
|
|
|
3163
|
-
var EXPRESSIONS = /*#__PURE__*/Object.freeze({
|
|
3164
|
-
__proto__: null,
|
|
3165
|
-
ARIA_ATTR: ARIA_ATTR,
|
|
3166
|
-
ATTR_WHITESPACE: ATTR_WHITESPACE,
|
|
3167
|
-
CUSTOM_ELEMENT: CUSTOM_ELEMENT,
|
|
3168
|
-
DATA_ATTR: DATA_ATTR,
|
|
3169
|
-
DOCTYPE_NAME: DOCTYPE_NAME,
|
|
3170
|
-
ERB_EXPR: ERB_EXPR,
|
|
3171
|
-
IS_ALLOWED_URI: IS_ALLOWED_URI,
|
|
3172
|
-
IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,
|
|
3173
|
-
MUSTACHE_EXPR: MUSTACHE_EXPR,
|
|
3174
|
-
TMPLIT_EXPR: TMPLIT_EXPR
|
|
3175
|
-
});
|
|
3176
|
-
|
|
3177
|
-
/* eslint-disable @typescript-eslint/indent */
|
|
3178
3282
|
// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
|
|
3179
3283
|
const NODE_TYPE = {
|
|
3180
3284
|
element: 1,
|
|
3181
3285
|
text: 3,
|
|
3182
3286
|
// Deprecated
|
|
3183
|
-
|
|
3287
|
+
processingInstruction: 7,
|
|
3184
3288
|
comment: 8,
|
|
3185
|
-
document: 9
|
|
3289
|
+
document: 9,
|
|
3290
|
+
documentFragment: 11};
|
|
3186
3291
|
const getGlobal = function getGlobal() {
|
|
3187
3292
|
return typeof window === 'undefined' ? null : window;
|
|
3188
3293
|
};
|
|
@@ -3237,10 +3342,25 @@ const _createHooksMap = function _createHooksMap() {
|
|
|
3237
3342
|
uponSanitizeShadowNode: []
|
|
3238
3343
|
};
|
|
3239
3344
|
};
|
|
3345
|
+
/**
|
|
3346
|
+
* Resolve a set-valued configuration option: a fresh set built from
|
|
3347
|
+
* cfg[key] when it is an own array property (seeded with a clone of
|
|
3348
|
+
* options.base when given, case-normalized via options.transform),
|
|
3349
|
+
* the fallback set otherwise.
|
|
3350
|
+
*
|
|
3351
|
+
* @param cfg the cloned, prototype-free configuration object
|
|
3352
|
+
* @param key the configuration property to read
|
|
3353
|
+
* @param fallback the set to use when the option is absent or not an array
|
|
3354
|
+
* @param options transform and optional base set to merge into
|
|
3355
|
+
* @returns the resolved set
|
|
3356
|
+
*/
|
|
3357
|
+
const _resolveSetOption = function _resolveSetOption(cfg, key, fallback, options) {
|
|
3358
|
+
return objectHasOwnProperty(cfg, key) && arrayIsArray(cfg[key]) ? addToSet(options.base ? clone(options.base) : {}, cfg[key], options.transform) : fallback;
|
|
3359
|
+
};
|
|
3240
3360
|
function createDOMPurify() {
|
|
3241
3361
|
let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
|
|
3242
3362
|
const DOMPurify = root => createDOMPurify(root);
|
|
3243
|
-
DOMPurify.version = '3.4.
|
|
3363
|
+
DOMPurify.version = '3.4.11';
|
|
3244
3364
|
DOMPurify.removed = [];
|
|
3245
3365
|
if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
|
|
3246
3366
|
// Not running in a browser, provide a factory function
|
|
@@ -3248,28 +3368,25 @@ function createDOMPurify() {
|
|
|
3248
3368
|
DOMPurify.isSupported = false;
|
|
3249
3369
|
return DOMPurify;
|
|
3250
3370
|
}
|
|
3251
|
-
let
|
|
3252
|
-
document
|
|
3253
|
-
} = window;
|
|
3371
|
+
let document = window.document;
|
|
3254
3372
|
const originalDocument = document;
|
|
3255
3373
|
const currentScript = originalDocument.currentScript;
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap,
|
|
3263
|
-
HTMLFormElement,
|
|
3264
|
-
DOMParser,
|
|
3265
|
-
trustedTypes
|
|
3266
|
-
} = window;
|
|
3374
|
+
const HTMLTemplateElement = window.HTMLTemplateElement,
|
|
3375
|
+
Node = window.Node,
|
|
3376
|
+
Element = window.Element,
|
|
3377
|
+
NodeFilter = window.NodeFilter;
|
|
3378
|
+
const DOMParser = window.DOMParser,
|
|
3379
|
+
trustedTypes = window.trustedTypes;
|
|
3267
3380
|
const ElementPrototype = Element.prototype;
|
|
3268
3381
|
const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
|
|
3269
3382
|
const remove = lookupGetter(ElementPrototype, 'remove');
|
|
3270
3383
|
const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
|
|
3271
3384
|
const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
|
|
3272
3385
|
const getParentNode = lookupGetter(ElementPrototype, 'parentNode');
|
|
3386
|
+
const getShadowRoot = lookupGetter(ElementPrototype, 'shadowRoot');
|
|
3387
|
+
const getAttributes = lookupGetter(ElementPrototype, 'attributes');
|
|
3388
|
+
const getNodeType = Node && Node.prototype ? lookupGetter(Node.prototype, 'nodeType') : null;
|
|
3389
|
+
const getNodeName = Node && Node.prototype ? lookupGetter(Node.prototype, 'nodeName') : null;
|
|
3273
3390
|
// As per issue #47, the web-components registry is inherited by a
|
|
3274
3391
|
// new document created via createHTMLDocument. As per the spec
|
|
3275
3392
|
// (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
|
|
@@ -3284,33 +3401,74 @@ function createDOMPurify() {
|
|
|
3284
3401
|
}
|
|
3285
3402
|
let trustedTypesPolicy;
|
|
3286
3403
|
let emptyHTML = '';
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3404
|
+
// The instance's own internal Trusted Types policy. Unlike a caller-supplied
|
|
3405
|
+
// `TRUSTED_TYPES_POLICY`, this is created at most once — Trusted Types throws
|
|
3406
|
+
// on duplicate policy names — and is the only policy allowed to persist
|
|
3407
|
+
// across configurations and survive `clearConfig()`.
|
|
3408
|
+
let defaultTrustedTypesPolicy;
|
|
3409
|
+
let defaultTrustedTypesPolicyResolved = false;
|
|
3410
|
+
// Tracks whether we are already inside a call to the configured Trusted Types
|
|
3411
|
+
// policy (`createHTML` or `createScriptURL`). If a supplied policy callback
|
|
3412
|
+
// itself calls `DOMPurify.sanitize` (the cause of #1422), `sanitize` would
|
|
3413
|
+
// re-enter the policy and recurse until the stack overflows. We detect that
|
|
3414
|
+
// re-entry and throw a clear, actionable error instead. The guard is shared
|
|
3415
|
+
// across both callbacks, because either one re-entering `sanitize` triggers
|
|
3416
|
+
// the same unbounded recursion.
|
|
3417
|
+
let IN_TRUSTED_TYPES_POLICY = 0;
|
|
3418
|
+
const _assertNotInTrustedTypesPolicy = function _assertNotInTrustedTypesPolicy() {
|
|
3419
|
+
if (IN_TRUSTED_TYPES_POLICY > 0) {
|
|
3420
|
+
throw typeErrorCreate('A configured TRUSTED_TYPES_POLICY callback (createHTML or ' + 'createScriptURL) must not call DOMPurify.sanitize, as that causes ' + 'infinite recursion. Do not pass a policy whose callbacks wrap ' + 'DOMPurify as TRUSTED_TYPES_POLICY; see the "DOMPurify and Trusted ' + 'Types" section of the README.');
|
|
3421
|
+
}
|
|
3422
|
+
};
|
|
3423
|
+
const _createTrustedHTML = function _createTrustedHTML(html) {
|
|
3424
|
+
_assertNotInTrustedTypesPolicy();
|
|
3425
|
+
IN_TRUSTED_TYPES_POLICY++;
|
|
3426
|
+
try {
|
|
3427
|
+
return trustedTypesPolicy.createHTML(html);
|
|
3428
|
+
} finally {
|
|
3429
|
+
IN_TRUSTED_TYPES_POLICY--;
|
|
3430
|
+
}
|
|
3431
|
+
};
|
|
3432
|
+
const _createTrustedScriptURL = function _createTrustedScriptURL(scriptUrl) {
|
|
3433
|
+
_assertNotInTrustedTypesPolicy();
|
|
3434
|
+
IN_TRUSTED_TYPES_POLICY++;
|
|
3435
|
+
try {
|
|
3436
|
+
return trustedTypesPolicy.createScriptURL(scriptUrl);
|
|
3437
|
+
} finally {
|
|
3438
|
+
IN_TRUSTED_TYPES_POLICY--;
|
|
3439
|
+
}
|
|
3440
|
+
};
|
|
3441
|
+
// Lazily resolve (and cache) the instance's internal default policy.
|
|
3442
|
+
// Resolution is attempted at most once: a successful `createPolicy` cannot be
|
|
3443
|
+
// repeated (Trusted Types throws on duplicate names), and a failed or
|
|
3444
|
+
// unsupported attempt must not be retried on every parse.
|
|
3445
|
+
const _getDefaultTrustedTypesPolicy = function _getDefaultTrustedTypesPolicy() {
|
|
3446
|
+
if (!defaultTrustedTypesPolicyResolved) {
|
|
3447
|
+
defaultTrustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript);
|
|
3448
|
+
defaultTrustedTypesPolicyResolved = true;
|
|
3449
|
+
}
|
|
3450
|
+
return defaultTrustedTypesPolicy;
|
|
3451
|
+
};
|
|
3452
|
+
const _document = document,
|
|
3453
|
+
implementation = _document.implementation,
|
|
3454
|
+
createNodeIterator = _document.createNodeIterator,
|
|
3455
|
+
createDocumentFragment = _document.createDocumentFragment,
|
|
3456
|
+
getElementsByTagName = _document.getElementsByTagName;
|
|
3457
|
+
const importNode = originalDocument.importNode;
|
|
3296
3458
|
let hooks = _createHooksMap();
|
|
3297
3459
|
/**
|
|
3298
3460
|
* Expose whether this browser supports running the full DOMPurify.
|
|
3299
3461
|
*/
|
|
3300
3462
|
DOMPurify.isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined;
|
|
3301
|
-
const
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
} = EXPRESSIONS;
|
|
3311
|
-
let {
|
|
3312
|
-
IS_ALLOWED_URI: IS_ALLOWED_URI$1
|
|
3313
|
-
} = EXPRESSIONS;
|
|
3463
|
+
const MUSTACHE_EXPR$1 = MUSTACHE_EXPR,
|
|
3464
|
+
ERB_EXPR$1 = ERB_EXPR,
|
|
3465
|
+
TMPLIT_EXPR$1 = TMPLIT_EXPR,
|
|
3466
|
+
DATA_ATTR$1 = DATA_ATTR,
|
|
3467
|
+
ARIA_ATTR$1 = ARIA_ATTR,
|
|
3468
|
+
IS_SCRIPT_OR_DATA$1 = IS_SCRIPT_OR_DATA,
|
|
3469
|
+
ATTR_WHITESPACE$1 = ATTR_WHITESPACE,
|
|
3470
|
+
CUSTOM_ELEMENT$1 = CUSTOM_ELEMENT;
|
|
3471
|
+
let IS_ALLOWED_URI$1 = IS_ALLOWED_URI;
|
|
3314
3472
|
/**
|
|
3315
3473
|
* We consider the elements and attributes below to be safe. Ideally
|
|
3316
3474
|
* don't add any new ones but feel free to remove unwanted ones.
|
|
@@ -3387,6 +3545,13 @@ function createDOMPurify() {
|
|
|
3387
3545
|
let WHOLE_DOCUMENT = false;
|
|
3388
3546
|
/* Track whether config is already set on this instance of DOMPurify. */
|
|
3389
3547
|
let SET_CONFIG = false;
|
|
3548
|
+
/* Pristine allowlist bindings captured at setConfig() time. On the
|
|
3549
|
+
* persistent-config path sanitize() restores the sets from these before
|
|
3550
|
+
* the per-walk hook clone-guard, so a hook's in-call widening cannot
|
|
3551
|
+
* carry across calls. Null until setConfig() is called; reset by
|
|
3552
|
+
* clearConfig(). */
|
|
3553
|
+
let SET_CONFIG_ALLOWED_TAGS = null;
|
|
3554
|
+
let SET_CONFIG_ALLOWED_ATTR = null;
|
|
3390
3555
|
/* Decide if all elements (e.g. style, script) must be children of
|
|
3391
3556
|
* document.body. By default, browsers might move them to document.head */
|
|
3392
3557
|
let FORCE_BODY = false;
|
|
@@ -3429,7 +3594,17 @@ function createDOMPurify() {
|
|
|
3429
3594
|
let USE_PROFILES = {};
|
|
3430
3595
|
/* Tags to ignore content of when KEEP_CONTENT is true */
|
|
3431
3596
|
let FORBID_CONTENTS = null;
|
|
3432
|
-
const DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script',
|
|
3597
|
+
const DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script',
|
|
3598
|
+
// <selectedcontent> mirrors the selected <option>'s subtree, cloned by
|
|
3599
|
+
// the UA (customizable <select>) — including any on* handlers — and the
|
|
3600
|
+
// engine re-mirrors synchronously whenever a removal changes which
|
|
3601
|
+
// option/selectedcontent is current, even inside DOMPurify's inert
|
|
3602
|
+
// DOMParser document. Hoisting its children on removal re-inserts a fresh
|
|
3603
|
+
// mirror target ahead of the walk, which the engine refills, looping
|
|
3604
|
+
// forever (DoS) and amplifying output. Dropping its content on removal
|
|
3605
|
+
// (rather than hoisting) breaks that cascade; the content is a duplicate
|
|
3606
|
+
// of the option, which is sanitized on its own. See campaign-3 F1/F6.
|
|
3607
|
+
'selectedcontent', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);
|
|
3433
3608
|
/* Tags that are safe for data: URIs */
|
|
3434
3609
|
let DATA_URI_TAGS = null;
|
|
3435
3610
|
const DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);
|
|
@@ -3445,8 +3620,10 @@ function createDOMPurify() {
|
|
|
3445
3620
|
/* Allowed XHTML+XML namespaces */
|
|
3446
3621
|
let ALLOWED_NAMESPACES = null;
|
|
3447
3622
|
const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);
|
|
3448
|
-
|
|
3449
|
-
let
|
|
3623
|
+
const DEFAULT_MATHML_TEXT_INTEGRATION_POINTS = freeze(['mi', 'mo', 'mn', 'ms', 'mtext']);
|
|
3624
|
+
let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, DEFAULT_MATHML_TEXT_INTEGRATION_POINTS);
|
|
3625
|
+
const DEFAULT_HTML_INTEGRATION_POINTS = freeze(['annotation-xml']);
|
|
3626
|
+
let HTML_INTEGRATION_POINTS = addToSet({}, DEFAULT_HTML_INTEGRATION_POINTS);
|
|
3450
3627
|
// Certain elements are allowed in both SVG and HTML
|
|
3451
3628
|
// namespace. We need to specify them explicitly
|
|
3452
3629
|
// so that they don't get erroneously deleted from
|
|
@@ -3488,15 +3665,33 @@ function createDOMPurify() {
|
|
|
3488
3665
|
// HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
|
|
3489
3666
|
transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;
|
|
3490
3667
|
/* Set configuration parameters */
|
|
3491
|
-
ALLOWED_TAGS =
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3668
|
+
ALLOWED_TAGS = _resolveSetOption(cfg, 'ALLOWED_TAGS', DEFAULT_ALLOWED_TAGS, {
|
|
3669
|
+
transform: transformCaseFunc
|
|
3670
|
+
});
|
|
3671
|
+
ALLOWED_ATTR = _resolveSetOption(cfg, 'ALLOWED_ATTR', DEFAULT_ALLOWED_ATTR, {
|
|
3672
|
+
transform: transformCaseFunc
|
|
3673
|
+
});
|
|
3674
|
+
ALLOWED_NAMESPACES = _resolveSetOption(cfg, 'ALLOWED_NAMESPACES', DEFAULT_ALLOWED_NAMESPACES, {
|
|
3675
|
+
transform: stringToString
|
|
3676
|
+
});
|
|
3677
|
+
URI_SAFE_ATTRIBUTES = _resolveSetOption(cfg, 'ADD_URI_SAFE_ATTR', DEFAULT_URI_SAFE_ATTRIBUTES, {
|
|
3678
|
+
transform: transformCaseFunc,
|
|
3679
|
+
base: DEFAULT_URI_SAFE_ATTRIBUTES
|
|
3680
|
+
});
|
|
3681
|
+
DATA_URI_TAGS = _resolveSetOption(cfg, 'ADD_DATA_URI_TAGS', DEFAULT_DATA_URI_TAGS, {
|
|
3682
|
+
transform: transformCaseFunc,
|
|
3683
|
+
base: DEFAULT_DATA_URI_TAGS
|
|
3684
|
+
});
|
|
3685
|
+
FORBID_CONTENTS = _resolveSetOption(cfg, 'FORBID_CONTENTS', DEFAULT_FORBID_CONTENTS, {
|
|
3686
|
+
transform: transformCaseFunc
|
|
3687
|
+
});
|
|
3688
|
+
FORBID_TAGS = _resolveSetOption(cfg, 'FORBID_TAGS', clone({}), {
|
|
3689
|
+
transform: transformCaseFunc
|
|
3690
|
+
});
|
|
3691
|
+
FORBID_ATTR = _resolveSetOption(cfg, 'FORBID_ATTR', clone({}), {
|
|
3692
|
+
transform: transformCaseFunc
|
|
3693
|
+
});
|
|
3694
|
+
USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES && typeof cfg.USE_PROFILES === 'object' ? clone(cfg.USE_PROFILES) : cfg.USE_PROFILES : false;
|
|
3500
3695
|
ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
|
|
3501
3696
|
ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
|
|
3502
3697
|
ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
|
|
@@ -3512,20 +3707,22 @@ function createDOMPurify() {
|
|
|
3512
3707
|
SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false
|
|
3513
3708
|
KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
|
|
3514
3709
|
IN_PLACE = cfg.IN_PLACE || false; // Default false
|
|
3515
|
-
IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP
|
|
3516
|
-
NAMESPACE = cfg.NAMESPACE
|
|
3517
|
-
MATHML_TEXT_INTEGRATION_POINTS = cfg.MATHML_TEXT_INTEGRATION_POINTS
|
|
3518
|
-
HTML_INTEGRATION_POINTS = cfg.HTML_INTEGRATION_POINTS
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3710
|
+
IS_ALLOWED_URI$1 = isRegex(cfg.ALLOWED_URI_REGEXP) ? cfg.ALLOWED_URI_REGEXP : IS_ALLOWED_URI; // Default regexp
|
|
3711
|
+
NAMESPACE = typeof cfg.NAMESPACE === 'string' ? cfg.NAMESPACE : HTML_NAMESPACE; // Default HTML namespace
|
|
3712
|
+
MATHML_TEXT_INTEGRATION_POINTS = objectHasOwnProperty(cfg, 'MATHML_TEXT_INTEGRATION_POINTS') && cfg.MATHML_TEXT_INTEGRATION_POINTS && typeof cfg.MATHML_TEXT_INTEGRATION_POINTS === 'object' ? clone(cfg.MATHML_TEXT_INTEGRATION_POINTS) : addToSet({}, DEFAULT_MATHML_TEXT_INTEGRATION_POINTS); // Default built-in map
|
|
3713
|
+
HTML_INTEGRATION_POINTS = objectHasOwnProperty(cfg, 'HTML_INTEGRATION_POINTS') && cfg.HTML_INTEGRATION_POINTS && typeof cfg.HTML_INTEGRATION_POINTS === 'object' ? clone(cfg.HTML_INTEGRATION_POINTS) : addToSet({}, DEFAULT_HTML_INTEGRATION_POINTS); // Default built-in map
|
|
3714
|
+
const customElementHandling = objectHasOwnProperty(cfg, 'CUSTOM_ELEMENT_HANDLING') && cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING === 'object' ? clone(cfg.CUSTOM_ELEMENT_HANDLING) : create(null);
|
|
3715
|
+
CUSTOM_ELEMENT_HANDLING = create(null);
|
|
3716
|
+
if (objectHasOwnProperty(customElementHandling, 'tagNameCheck') && isRegexOrFunction(customElementHandling.tagNameCheck)) {
|
|
3717
|
+
CUSTOM_ELEMENT_HANDLING.tagNameCheck = customElementHandling.tagNameCheck; // Default undefined
|
|
3718
|
+
}
|
|
3719
|
+
if (objectHasOwnProperty(customElementHandling, 'attributeNameCheck') && isRegexOrFunction(customElementHandling.attributeNameCheck)) {
|
|
3720
|
+
CUSTOM_ELEMENT_HANDLING.attributeNameCheck = customElementHandling.attributeNameCheck; // Default undefined
|
|
3721
|
+
}
|
|
3722
|
+
if (objectHasOwnProperty(customElementHandling, 'allowCustomizedBuiltInElements') && typeof customElementHandling.allowCustomizedBuiltInElements === 'boolean') {
|
|
3723
|
+
CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = customElementHandling.allowCustomizedBuiltInElements; // Default undefined
|
|
3724
|
+
}
|
|
3725
|
+
seal(CUSTOM_ELEMENT_HANDLING);
|
|
3529
3726
|
if (SAFE_FOR_TEMPLATES) {
|
|
3530
3727
|
ALLOW_DATA_ATTR = false;
|
|
3531
3728
|
}
|
|
@@ -3561,36 +3758,36 @@ function createDOMPurify() {
|
|
|
3561
3758
|
EXTRA_ELEMENT_HANDLING.tagCheck = null;
|
|
3562
3759
|
EXTRA_ELEMENT_HANDLING.attributeCheck = null;
|
|
3563
3760
|
/* Merge configuration parameters */
|
|
3564
|
-
if (cfg
|
|
3761
|
+
if (objectHasOwnProperty(cfg, 'ADD_TAGS')) {
|
|
3565
3762
|
if (typeof cfg.ADD_TAGS === 'function') {
|
|
3566
3763
|
EXTRA_ELEMENT_HANDLING.tagCheck = cfg.ADD_TAGS;
|
|
3567
|
-
} else {
|
|
3764
|
+
} else if (arrayIsArray(cfg.ADD_TAGS)) {
|
|
3568
3765
|
if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
|
|
3569
3766
|
ALLOWED_TAGS = clone(ALLOWED_TAGS);
|
|
3570
3767
|
}
|
|
3571
3768
|
addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);
|
|
3572
3769
|
}
|
|
3573
3770
|
}
|
|
3574
|
-
if (cfg
|
|
3771
|
+
if (objectHasOwnProperty(cfg, 'ADD_ATTR')) {
|
|
3575
3772
|
if (typeof cfg.ADD_ATTR === 'function') {
|
|
3576
3773
|
EXTRA_ELEMENT_HANDLING.attributeCheck = cfg.ADD_ATTR;
|
|
3577
|
-
} else {
|
|
3774
|
+
} else if (arrayIsArray(cfg.ADD_ATTR)) {
|
|
3578
3775
|
if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
|
|
3579
3776
|
ALLOWED_ATTR = clone(ALLOWED_ATTR);
|
|
3580
3777
|
}
|
|
3581
3778
|
addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
|
|
3582
3779
|
}
|
|
3583
3780
|
}
|
|
3584
|
-
if (cfg.ADD_URI_SAFE_ATTR) {
|
|
3781
|
+
if (objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') && arrayIsArray(cfg.ADD_URI_SAFE_ATTR)) {
|
|
3585
3782
|
addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
|
|
3586
3783
|
}
|
|
3587
|
-
if (cfg.FORBID_CONTENTS) {
|
|
3784
|
+
if (objectHasOwnProperty(cfg, 'FORBID_CONTENTS') && arrayIsArray(cfg.FORBID_CONTENTS)) {
|
|
3588
3785
|
if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
|
|
3589
3786
|
FORBID_CONTENTS = clone(FORBID_CONTENTS);
|
|
3590
3787
|
}
|
|
3591
3788
|
addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
|
|
3592
3789
|
}
|
|
3593
|
-
if (cfg.ADD_FORBID_CONTENTS) {
|
|
3790
|
+
if (objectHasOwnProperty(cfg, 'ADD_FORBID_CONTENTS') && arrayIsArray(cfg.ADD_FORBID_CONTENTS)) {
|
|
3594
3791
|
if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
|
|
3595
3792
|
FORBID_CONTENTS = clone(FORBID_CONTENTS);
|
|
3596
3793
|
}
|
|
@@ -3609,6 +3806,13 @@ function createDOMPurify() {
|
|
|
3609
3806
|
addToSet(ALLOWED_TAGS, ['tbody']);
|
|
3610
3807
|
delete FORBID_TAGS.tbody;
|
|
3611
3808
|
}
|
|
3809
|
+
// Re-derive the active Trusted Types policy from this configuration on
|
|
3810
|
+
// every parse. The active policy must never be sticky closure state that
|
|
3811
|
+
// outlives the config that set it: a caller-supplied policy left in place
|
|
3812
|
+
// after `clearConfig()` — or after a later call that supplied none, or
|
|
3813
|
+
// `TRUSTED_TYPES_POLICY: null` — could sign a subsequent "default"
|
|
3814
|
+
// `RETURN_TRUSTED_TYPE` result with a foreign, possibly unsafe policy.
|
|
3815
|
+
// See GHSA-vxr8-fq34-vvx9.
|
|
3612
3816
|
if (cfg.TRUSTED_TYPES_POLICY) {
|
|
3613
3817
|
if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== 'function') {
|
|
3614
3818
|
throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');
|
|
@@ -3616,18 +3820,45 @@ function createDOMPurify() {
|
|
|
3616
3820
|
if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') {
|
|
3617
3821
|
throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');
|
|
3618
3822
|
}
|
|
3619
|
-
//
|
|
3823
|
+
// A caller-supplied policy applies to this configuration only.
|
|
3824
|
+
const previousTrustedTypesPolicy = trustedTypesPolicy;
|
|
3620
3825
|
trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;
|
|
3621
|
-
// Sign local variables required by `sanitize`.
|
|
3622
|
-
|
|
3826
|
+
// Sign local variables required by `sanitize`. If the supplied policy's
|
|
3827
|
+
// `createHTML` is circular (i.e. it calls `DOMPurify.sanitize`), this
|
|
3828
|
+
// throws via the re-entrancy guard. Restore the previous policy first so
|
|
3829
|
+
// the instance is not left in a poisoned state. See #1422.
|
|
3830
|
+
try {
|
|
3831
|
+
emptyHTML = _createTrustedHTML('');
|
|
3832
|
+
} catch (error) {
|
|
3833
|
+
trustedTypesPolicy = previousTrustedTypesPolicy;
|
|
3834
|
+
throw error;
|
|
3835
|
+
}
|
|
3836
|
+
} else if (cfg.TRUSTED_TYPES_POLICY === null) {
|
|
3837
|
+
// Explicit opt-out for this call: perform no Trusted Types signing and
|
|
3838
|
+
// create nothing (so a strict `trusted-types` CSP that disallows a
|
|
3839
|
+
// `dompurify` policy can still call `sanitize` from inside its own
|
|
3840
|
+
// policy — see #1422). Resetting to `undefined` rather than a sticky
|
|
3841
|
+
// `null` also drops any previously retained caller policy, so it cannot
|
|
3842
|
+
// resurface on a later call, while still allowing the next config-less
|
|
3843
|
+
// call to restore the internal default policy. See GHSA-vxr8-fq34-vvx9.
|
|
3844
|
+
trustedTypesPolicy = undefined;
|
|
3845
|
+
emptyHTML = '';
|
|
3623
3846
|
} else {
|
|
3624
|
-
//
|
|
3847
|
+
// No policy supplied: keep the currently active policy if one is set — a
|
|
3848
|
+
// previously supplied policy is intentionally sticky across config-less
|
|
3849
|
+
// calls — otherwise fall back to the instance's own internal policy,
|
|
3850
|
+
// created at most once. (A policy supplied for a *single* call still
|
|
3851
|
+
// lingers by design; what must not linger is a policy whose configuration
|
|
3852
|
+
// has been torn down via `clearConfig()`, which restores the default.)
|
|
3625
3853
|
if (trustedTypesPolicy === undefined) {
|
|
3626
|
-
trustedTypesPolicy =
|
|
3854
|
+
trustedTypesPolicy = _getDefaultTrustedTypesPolicy();
|
|
3627
3855
|
}
|
|
3628
|
-
//
|
|
3629
|
-
|
|
3630
|
-
|
|
3856
|
+
// Sign internal variables only when a policy is active. A falsy policy
|
|
3857
|
+
// (Trusted Types unsupported, creation failed, or an explicit opt-out)
|
|
3858
|
+
// leaves `emptyHTML` as a plain string, so we never call `.createHTML` on
|
|
3859
|
+
// a non-policy and throw. See #1422.
|
|
3860
|
+
if (trustedTypesPolicy && typeof emptyHTML === 'string') {
|
|
3861
|
+
emptyHTML = _createTrustedHTML('');
|
|
3631
3862
|
}
|
|
3632
3863
|
}
|
|
3633
3864
|
// Prevent further manipulation of configuration.
|
|
@@ -3642,6 +3873,77 @@ function createDOMPurify() {
|
|
|
3642
3873
|
* correctly. */
|
|
3643
3874
|
const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]);
|
|
3644
3875
|
const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]);
|
|
3876
|
+
/**
|
|
3877
|
+
* Namespace rules for an element in the SVG namespace.
|
|
3878
|
+
*
|
|
3879
|
+
* @param tagName the element's lowercase tag name
|
|
3880
|
+
* @param parent the (possibly simulated) parent node
|
|
3881
|
+
* @param parentTagName the parent's lowercase tag name
|
|
3882
|
+
* @returns true if a spec-compliant parser could produce this element
|
|
3883
|
+
*/
|
|
3884
|
+
const _checkSvgNamespace = function _checkSvgNamespace(tagName, parent, parentTagName) {
|
|
3885
|
+
// The only way to switch from HTML namespace to SVG
|
|
3886
|
+
// is via <svg>. If it happens via any other tag, then
|
|
3887
|
+
// it should be killed.
|
|
3888
|
+
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
3889
|
+
return tagName === 'svg';
|
|
3890
|
+
}
|
|
3891
|
+
// The only way to switch from MathML to SVG is via <svg>
|
|
3892
|
+
// if the parent is either <annotation-xml> or a MathML
|
|
3893
|
+
// text integration point.
|
|
3894
|
+
if (parent.namespaceURI === MATHML_NAMESPACE) {
|
|
3895
|
+
return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
|
|
3896
|
+
}
|
|
3897
|
+
// We only allow elements that are defined in SVG
|
|
3898
|
+
// spec. All others are disallowed in SVG namespace.
|
|
3899
|
+
return Boolean(ALL_SVG_TAGS[tagName]);
|
|
3900
|
+
};
|
|
3901
|
+
/**
|
|
3902
|
+
* Namespace rules for an element in the MathML namespace.
|
|
3903
|
+
*
|
|
3904
|
+
* @param tagName the element's lowercase tag name
|
|
3905
|
+
* @param parent the (possibly simulated) parent node
|
|
3906
|
+
* @param parentTagName the parent's lowercase tag name
|
|
3907
|
+
* @returns true if a spec-compliant parser could produce this element
|
|
3908
|
+
*/
|
|
3909
|
+
const _checkMathMlNamespace = function _checkMathMlNamespace(tagName, parent, parentTagName) {
|
|
3910
|
+
// The only way to switch from HTML namespace to MathML
|
|
3911
|
+
// is via <math>. If it happens via any other tag, then
|
|
3912
|
+
// it should be killed.
|
|
3913
|
+
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
3914
|
+
return tagName === 'math';
|
|
3915
|
+
}
|
|
3916
|
+
// The only way to switch from SVG to MathML is via
|
|
3917
|
+
// <math> and HTML integration points
|
|
3918
|
+
if (parent.namespaceURI === SVG_NAMESPACE) {
|
|
3919
|
+
return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
|
|
3920
|
+
}
|
|
3921
|
+
// We only allow elements that are defined in MathML
|
|
3922
|
+
// spec. All others are disallowed in MathML namespace.
|
|
3923
|
+
return Boolean(ALL_MATHML_TAGS[tagName]);
|
|
3924
|
+
};
|
|
3925
|
+
/**
|
|
3926
|
+
* Namespace rules for an element in the HTML namespace.
|
|
3927
|
+
*
|
|
3928
|
+
* @param tagName the element's lowercase tag name
|
|
3929
|
+
* @param parent the (possibly simulated) parent node
|
|
3930
|
+
* @param parentTagName the parent's lowercase tag name
|
|
3931
|
+
* @returns true if a spec-compliant parser could produce this element
|
|
3932
|
+
*/
|
|
3933
|
+
const _checkHtmlNamespace = function _checkHtmlNamespace(tagName, parent, parentTagName) {
|
|
3934
|
+
// The only way to switch from SVG to HTML is via
|
|
3935
|
+
// HTML integration points, and from MathML to HTML
|
|
3936
|
+
// is via MathML text integration points
|
|
3937
|
+
if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
|
|
3938
|
+
return false;
|
|
3939
|
+
}
|
|
3940
|
+
if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
|
|
3941
|
+
return false;
|
|
3942
|
+
}
|
|
3943
|
+
// We disallow tags that are specific for MathML
|
|
3944
|
+
// or SVG and should never appear in HTML namespace
|
|
3945
|
+
return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
|
|
3946
|
+
};
|
|
3645
3947
|
/**
|
|
3646
3948
|
* @param element a DOM element whose namespace is being checked
|
|
3647
3949
|
* @returns Return false if the element has a
|
|
@@ -3664,51 +3966,13 @@ function createDOMPurify() {
|
|
|
3664
3966
|
return false;
|
|
3665
3967
|
}
|
|
3666
3968
|
if (element.namespaceURI === SVG_NAMESPACE) {
|
|
3667
|
-
|
|
3668
|
-
// is via <svg>. If it happens via any other tag, then
|
|
3669
|
-
// it should be killed.
|
|
3670
|
-
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
3671
|
-
return tagName === 'svg';
|
|
3672
|
-
}
|
|
3673
|
-
// The only way to switch from MathML to SVG is via`
|
|
3674
|
-
// svg if parent is either <annotation-xml> or MathML
|
|
3675
|
-
// text integration points.
|
|
3676
|
-
if (parent.namespaceURI === MATHML_NAMESPACE) {
|
|
3677
|
-
return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
|
|
3678
|
-
}
|
|
3679
|
-
// We only allow elements that are defined in SVG
|
|
3680
|
-
// spec. All others are disallowed in SVG namespace.
|
|
3681
|
-
return Boolean(ALL_SVG_TAGS[tagName]);
|
|
3969
|
+
return _checkSvgNamespace(tagName, parent, parentTagName);
|
|
3682
3970
|
}
|
|
3683
3971
|
if (element.namespaceURI === MATHML_NAMESPACE) {
|
|
3684
|
-
|
|
3685
|
-
// is via <math>. If it happens via any other tag, then
|
|
3686
|
-
// it should be killed.
|
|
3687
|
-
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
3688
|
-
return tagName === 'math';
|
|
3689
|
-
}
|
|
3690
|
-
// The only way to switch from SVG to MathML is via
|
|
3691
|
-
// <math> and HTML integration points
|
|
3692
|
-
if (parent.namespaceURI === SVG_NAMESPACE) {
|
|
3693
|
-
return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
|
|
3694
|
-
}
|
|
3695
|
-
// We only allow elements that are defined in MathML
|
|
3696
|
-
// spec. All others are disallowed in MathML namespace.
|
|
3697
|
-
return Boolean(ALL_MATHML_TAGS[tagName]);
|
|
3972
|
+
return _checkMathMlNamespace(tagName, parent, parentTagName);
|
|
3698
3973
|
}
|
|
3699
3974
|
if (element.namespaceURI === HTML_NAMESPACE) {
|
|
3700
|
-
|
|
3701
|
-
// HTML integration points, and from MathML to HTML
|
|
3702
|
-
// is via MathML text integration points
|
|
3703
|
-
if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
|
|
3704
|
-
return false;
|
|
3705
|
-
}
|
|
3706
|
-
if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
|
|
3707
|
-
return false;
|
|
3708
|
-
}
|
|
3709
|
-
// We disallow tags that are specific for MathML
|
|
3710
|
-
// or SVG and should never appear in HTML namespace
|
|
3711
|
-
return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
|
|
3975
|
+
return _checkHtmlNamespace(tagName, parent, parentTagName);
|
|
3712
3976
|
}
|
|
3713
3977
|
// For XHTML and XML documents that support custom namespaces
|
|
3714
3978
|
if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) {
|
|
@@ -3733,7 +3997,74 @@ function createDOMPurify() {
|
|
|
3733
3997
|
// eslint-disable-next-line unicorn/prefer-dom-node-remove
|
|
3734
3998
|
getParentNode(node).removeChild(node);
|
|
3735
3999
|
} catch (_) {
|
|
4000
|
+
/* The normal detach failed — this is reached for a parentless node
|
|
4001
|
+
(getParentNode() is null, so .removeChild throws). Element.prototype
|
|
4002
|
+
.remove() is itself a spec no-op on a parentless node, so a recorded
|
|
4003
|
+
"removal" would otherwise hand the caller back an intact,
|
|
4004
|
+
payload-bearing node (e.g. a detached IN_PLACE root the mXSS canary or
|
|
4005
|
+
the style-with-element-child rule decided to kill). Fail closed by
|
|
4006
|
+
throwing — exactly as a clobbered root does at the IN_PLACE entry —
|
|
4007
|
+
rather than trying to "neutralize" the node via its own methods.
|
|
4008
|
+
Neutralizing would mean calling getAttributeNames()/removeAttribute()
|
|
4009
|
+
on the node, both of which a <form> root can clobber via a named child
|
|
4010
|
+
(and _isClobbered does not even probe getAttributeNames), so the
|
|
4011
|
+
neutralize step could itself be silently defeated, leaving the payload
|
|
4012
|
+
intact. A throw touches only the cached, clobber-safe remove() and
|
|
4013
|
+
getParentNode(). Generalizes GHSA-r47g-fvhr-h676 (clobbered-form root)
|
|
4014
|
+
to every root-kill reason. REPORT-3.
|
|
4015
|
+
This lives inside the catch, so it never fires for a normally-removed
|
|
4016
|
+
in-tree node: those have a parent, removeChild() succeeds, and the
|
|
4017
|
+
catch is not entered. Only a kept (parentless) root reaches here. */
|
|
3736
4018
|
remove(node);
|
|
4019
|
+
if (!getParentNode(node)) {
|
|
4020
|
+
throw typeErrorCreate('a node selected for removal could not be detached from its tree ' + 'and cannot be safely returned; refusing to sanitize in place');
|
|
4021
|
+
}
|
|
4022
|
+
}
|
|
4023
|
+
};
|
|
4024
|
+
/**
|
|
4025
|
+
* _neutralizeRoot
|
|
4026
|
+
*
|
|
4027
|
+
* Fail-closed teardown of an in-place root after the sanitize walk aborts
|
|
4028
|
+
* (campaign-3 F2). An internal throw mid-walk — e.g. a page-registered
|
|
4029
|
+
* custom element's reaction detaches a node so `_forceRemove`'s deliberate
|
|
4030
|
+
* parentless guard throws, or any other re-entrant engine mutation — would
|
|
4031
|
+
* otherwise leave the caller's *live* tree half-sanitized, with everything
|
|
4032
|
+
* after the abort point still carrying its handlers. There is no safe way
|
|
4033
|
+
* to resume the walk (the tree mutated under us), so we strip the root bare:
|
|
4034
|
+
* remove every child and every attribute, then let the caller's catch see
|
|
4035
|
+
* the original error. Clobber-safe (cached `remove`/`childNodes`/`attributes`
|
|
4036
|
+
* getters; the root was already clobber-pre-flighted at the IN_PLACE entry).
|
|
4037
|
+
*
|
|
4038
|
+
* @param root the in-place root to empty
|
|
4039
|
+
*/
|
|
4040
|
+
const _neutralizeRoot = function _neutralizeRoot(root) {
|
|
4041
|
+
const childNodes = getChildNodes(root);
|
|
4042
|
+
if (childNodes) {
|
|
4043
|
+
const snapshot = [];
|
|
4044
|
+
arrayForEach(childNodes, child => {
|
|
4045
|
+
arrayPush(snapshot, child);
|
|
4046
|
+
});
|
|
4047
|
+
arrayForEach(snapshot, child => {
|
|
4048
|
+
try {
|
|
4049
|
+
remove(child);
|
|
4050
|
+
} catch (_) {
|
|
4051
|
+
/* Best-effort teardown; a still-attached child is handled below */
|
|
4052
|
+
}
|
|
4053
|
+
});
|
|
4054
|
+
}
|
|
4055
|
+
const attributes = getAttributes(root);
|
|
4056
|
+
if (attributes) {
|
|
4057
|
+
for (let i = attributes.length - 1; i >= 0; --i) {
|
|
4058
|
+
const attribute = attributes[i];
|
|
4059
|
+
const name = attribute && attribute.name;
|
|
4060
|
+
if (typeof name === 'string') {
|
|
4061
|
+
try {
|
|
4062
|
+
root.removeAttribute(name);
|
|
4063
|
+
} catch (_) {
|
|
4064
|
+
/* Clobbered removeAttribute — ignore (fail-closed best effort) */
|
|
4065
|
+
}
|
|
4066
|
+
}
|
|
4067
|
+
}
|
|
3737
4068
|
}
|
|
3738
4069
|
};
|
|
3739
4070
|
/**
|
|
@@ -3768,6 +4099,72 @@ function createDOMPurify() {
|
|
|
3768
4099
|
}
|
|
3769
4100
|
}
|
|
3770
4101
|
};
|
|
4102
|
+
/**
|
|
4103
|
+
* _stripDisallowedAttributes
|
|
4104
|
+
*
|
|
4105
|
+
* Removes every attribute the active configuration does not allow from a
|
|
4106
|
+
* single element, using the same allowlist as the main attribute pass (so
|
|
4107
|
+
* `on*` handlers go, but no `/^on/` blocklist is introduced). Used only to
|
|
4108
|
+
* neutralise nodes that are being discarded from an in-place tree.
|
|
4109
|
+
*
|
|
4110
|
+
* @param element the element to strip
|
|
4111
|
+
*/
|
|
4112
|
+
const _stripDisallowedAttributes = function _stripDisallowedAttributes(element) {
|
|
4113
|
+
const attributes = getAttributes(element);
|
|
4114
|
+
if (!attributes) {
|
|
4115
|
+
return;
|
|
4116
|
+
}
|
|
4117
|
+
for (let i = attributes.length - 1; i >= 0; --i) {
|
|
4118
|
+
const attribute = attributes[i];
|
|
4119
|
+
const name = attribute && attribute.name;
|
|
4120
|
+
if (typeof name !== 'string' || ALLOWED_ATTR[transformCaseFunc(name)]) {
|
|
4121
|
+
continue;
|
|
4122
|
+
}
|
|
4123
|
+
try {
|
|
4124
|
+
element.removeAttribute(name);
|
|
4125
|
+
} catch (_) {
|
|
4126
|
+
/* Clobbered removeAttribute on a doomed node — ignore */
|
|
4127
|
+
}
|
|
4128
|
+
}
|
|
4129
|
+
};
|
|
4130
|
+
/**
|
|
4131
|
+
* _neutralizeSubtree
|
|
4132
|
+
*
|
|
4133
|
+
* Completes the audit-5 F1 fix across every removal path. The KEEP_CONTENT
|
|
4134
|
+
* move-hoist neutralises only disallowed-tag removals; clobber, mXSS-canary,
|
|
4135
|
+
* namespace, comment, processing-instruction and KEEP_CONTENT:false removals
|
|
4136
|
+
* all drop their subtree wholesale via `_forceRemove`. On the IN_PLACE path
|
|
4137
|
+
* those dropped nodes are detached from the caller's LIVE tree but a
|
|
4138
|
+
* handler-bearing original among them (an `<img onerror>`/`<video>` that was
|
|
4139
|
+
* loading) keeps its queued resource event, which fires in page scope after
|
|
4140
|
+
* sanitize returns. This walks a removed subtree and strips every attribute
|
|
4141
|
+
* the active configuration does not allow — so `on*` handlers are cancelled
|
|
4142
|
+
* through the SAME allowlist that governs kept nodes, not a separate `/^on/`
|
|
4143
|
+
* blocklist. Run synchronously before sanitize returns, i.e. before any
|
|
4144
|
+
* queued event can fire. Hook-free by design: these nodes leave the output,
|
|
4145
|
+
* so firing attribute hooks for them would be surprising. Clobber-safe reads;
|
|
4146
|
+
* a doomed clobbered node may shadow `removeAttribute` (its own attributes are
|
|
4147
|
+
* irrelevant — it is discarded — while its non-clobbered descendants, e.g.
|
|
4148
|
+
* the `<img>`, are reached and scrubbed).
|
|
4149
|
+
*
|
|
4150
|
+
* @param root the root of a removed subtree to neutralise
|
|
4151
|
+
*/
|
|
4152
|
+
const _neutralizeSubtree = function _neutralizeSubtree(root) {
|
|
4153
|
+
const stack = [root];
|
|
4154
|
+
while (stack.length > 0) {
|
|
4155
|
+
const node = stack.pop();
|
|
4156
|
+
const nodeType = getNodeType ? getNodeType(node) : node.nodeType;
|
|
4157
|
+
if (nodeType === NODE_TYPE.element) {
|
|
4158
|
+
_stripDisallowedAttributes(node);
|
|
4159
|
+
}
|
|
4160
|
+
const childNodes = getChildNodes(node);
|
|
4161
|
+
if (childNodes) {
|
|
4162
|
+
for (let i = childNodes.length - 1; i >= 0; --i) {
|
|
4163
|
+
stack.push(childNodes[i]);
|
|
4164
|
+
}
|
|
4165
|
+
}
|
|
4166
|
+
}
|
|
4167
|
+
};
|
|
3771
4168
|
/**
|
|
3772
4169
|
* _initDocument
|
|
3773
4170
|
*
|
|
@@ -3789,7 +4186,7 @@ function createDOMPurify() {
|
|
|
3789
4186
|
// Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
|
|
3790
4187
|
dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>';
|
|
3791
4188
|
}
|
|
3792
|
-
const dirtyPayload = trustedTypesPolicy ?
|
|
4189
|
+
const dirtyPayload = trustedTypesPolicy ? _createTrustedHTML(dirty) : dirty;
|
|
3793
4190
|
/*
|
|
3794
4191
|
* Use the DOMParser API by default, fallback later if needs be
|
|
3795
4192
|
* DOMParser not work for svg when has multiple root element.
|
|
@@ -3829,29 +4226,254 @@ function createDOMPurify() {
|
|
|
3829
4226
|
// eslint-disable-next-line no-bitwise
|
|
3830
4227
|
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
|
|
3831
4228
|
};
|
|
4229
|
+
/**
|
|
4230
|
+
* Replace template expression syntax (mustache, ERB, template
|
|
4231
|
+
* literal) with a space; shared by all SAFE_FOR_TEMPLATES scrub
|
|
4232
|
+
* sites. Order matters: mustache, then ERB, then template literal.
|
|
4233
|
+
*
|
|
4234
|
+
* @param value the string to scrub
|
|
4235
|
+
* @returns the scrubbed string
|
|
4236
|
+
*/
|
|
4237
|
+
const _stripTemplateExpressions = function _stripTemplateExpressions(value) {
|
|
4238
|
+
value = stringReplace(value, MUSTACHE_EXPR$1, ' ');
|
|
4239
|
+
value = stringReplace(value, ERB_EXPR$1, ' ');
|
|
4240
|
+
value = stringReplace(value, TMPLIT_EXPR$1, ' ');
|
|
4241
|
+
return value;
|
|
4242
|
+
};
|
|
4243
|
+
/**
|
|
4244
|
+
* Strip template-engine expressions ({{...}}, ${...}, <%...%>) from the
|
|
4245
|
+
* character data of an element subtree. Used as the final safety net for
|
|
4246
|
+
* SAFE_FOR_TEMPLATES on every DOM-returning code path so that expressions
|
|
4247
|
+
* which only form after text-node normalization (e.g. fragments split across
|
|
4248
|
+
* stripped elements) cannot survive into a template-evaluating framework.
|
|
4249
|
+
*
|
|
4250
|
+
* Walks text/comment/CDATA/processing-instruction nodes and mutates `.data`
|
|
4251
|
+
* in place rather than round-tripping through innerHTML. This preserves
|
|
4252
|
+
* descendant node references (important for IN_PLACE callers), avoids a
|
|
4253
|
+
* serialize/reparse cycle, and reads literal character data — which means
|
|
4254
|
+
* `<%...%>` in text content matches the ERB regex against its real bytes
|
|
4255
|
+
* instead of the HTML-entity-escaped form innerHTML would produce.
|
|
4256
|
+
*
|
|
4257
|
+
* Attribute values are not visited here; SAFE_FOR_TEMPLATES handling for
|
|
4258
|
+
* attributes is performed during the per-node `_sanitizeAttributes` pass.
|
|
4259
|
+
*
|
|
4260
|
+
* @param node The root element whose character data should be scrubbed.
|
|
4261
|
+
*/
|
|
4262
|
+
const _scrubTemplateExpressions2 = function _scrubTemplateExpressions(node) {
|
|
4263
|
+
var _node$querySelectorAl;
|
|
4264
|
+
node.normalize();
|
|
4265
|
+
const walker = createNodeIterator.call(node.ownerDocument || node, node,
|
|
4266
|
+
// eslint-disable-next-line no-bitwise
|
|
4267
|
+
NodeFilter.SHOW_TEXT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_CDATA_SECTION | NodeFilter.SHOW_PROCESSING_INSTRUCTION, null);
|
|
4268
|
+
let currentNode = walker.nextNode();
|
|
4269
|
+
while (currentNode) {
|
|
4270
|
+
currentNode.data = _stripTemplateExpressions(currentNode.data);
|
|
4271
|
+
currentNode = walker.nextNode();
|
|
4272
|
+
}
|
|
4273
|
+
// NodeIterator does not descend into <template>.content per the DOM spec,
|
|
4274
|
+
// so we must explicitly recurse into each template's content fragment,
|
|
4275
|
+
// mirroring the approach used by _sanitizeShadowDOM.
|
|
4276
|
+
const templates = (_node$querySelectorAl = node.querySelectorAll) === null || _node$querySelectorAl === void 0 ? void 0 : _node$querySelectorAl.call(node, 'template');
|
|
4277
|
+
if (templates) {
|
|
4278
|
+
arrayForEach(templates, tmpl => {
|
|
4279
|
+
if (_isDocumentFragment(tmpl.content)) {
|
|
4280
|
+
_scrubTemplateExpressions2(tmpl.content);
|
|
4281
|
+
}
|
|
4282
|
+
});
|
|
4283
|
+
}
|
|
4284
|
+
};
|
|
3832
4285
|
/**
|
|
3833
4286
|
* _isClobbered
|
|
3834
4287
|
*
|
|
4288
|
+
* Detect DOM-clobbering on HTMLFormElement nodes. Form is the only HTML
|
|
4289
|
+
* interface with [LegacyOverrideBuiltIns]; a descendant element with a
|
|
4290
|
+
* `name` attribute matching a prototype property shadows that property
|
|
4291
|
+
* on direct reads. We use this check at the IN_PLACE entry-point and
|
|
4292
|
+
* during attribute sanitization to refuse clobbered forms.
|
|
4293
|
+
*
|
|
3835
4294
|
* @param element element to check for clobbering attacks
|
|
3836
4295
|
* @return true if clobbered, false if safe
|
|
3837
4296
|
*/
|
|
3838
4297
|
const _isClobbered = function _isClobbered(element) {
|
|
3839
|
-
|
|
4298
|
+
// Realm-independent tag-name probe. If we can't determine the tag
|
|
4299
|
+
// name at all, we can't reason about clobbering — return false
|
|
4300
|
+
// (the caller's other defences still apply).
|
|
4301
|
+
const realTagName = getNodeName ? getNodeName(element) : null;
|
|
4302
|
+
if (typeof realTagName !== 'string') {
|
|
4303
|
+
return false;
|
|
4304
|
+
}
|
|
4305
|
+
if (transformCaseFunc(realTagName) !== 'form') {
|
|
4306
|
+
return false;
|
|
4307
|
+
}
|
|
4308
|
+
return typeof element.nodeName !== 'string' || typeof element.textContent !== 'string' || typeof element.removeChild !== 'function' ||
|
|
4309
|
+
// Realm-safe NamedNodeMap detection: equality against the cached
|
|
4310
|
+
// prototype getter. Clobbered .attributes (e.g. <input name="attributes">)
|
|
4311
|
+
// makes the direct read diverge from the cached read; a clean form
|
|
4312
|
+
// (same-realm OR foreign-realm) has both reads pointing at the same
|
|
4313
|
+
// canonical NamedNodeMap.
|
|
4314
|
+
element.attributes !== getAttributes(element) || typeof element.removeAttribute !== 'function' || typeof element.setAttribute !== 'function' || typeof element.namespaceURI !== 'string' || typeof element.insertBefore !== 'function' || typeof element.hasChildNodes !== 'function' ||
|
|
4315
|
+
// NodeType clobbering probe. Cached Node.prototype.nodeType getter
|
|
4316
|
+
// returns the integer 1 for any Element regardless of realm; direct
|
|
4317
|
+
// read on a clobbered form (e.g. <input name="nodeType">) returns
|
|
4318
|
+
// the named child element. Cheap addition — nodeType is read from
|
|
4319
|
+
// an internal slot, no serialization cost — and removes a residual
|
|
4320
|
+
// clobbering surface used by several mXSS / PI / comment branches
|
|
4321
|
+
// in _sanitizeElements that compare currentNode.nodeType directly.
|
|
4322
|
+
element.nodeType !== getNodeType(element) ||
|
|
4323
|
+
// HTMLFormElement has [LegacyOverrideBuiltIns]: a descendant named
|
|
4324
|
+
// "childNodes" shadows the prototype getter. Direct reads of
|
|
4325
|
+
// form.childNodes from a clobbered form return the named child
|
|
4326
|
+
// instead of the real NodeList, so any walk that reads it directly
|
|
4327
|
+
// skips the form's real children. Compare the direct read to the
|
|
4328
|
+
// cached Node.prototype getter — when the form's named-property
|
|
4329
|
+
// getter intercepts the read, the two values differ and we flag
|
|
4330
|
+
// the form. This catches every clobbering child type (input,
|
|
4331
|
+
// select, etc.) regardless of whether the named child happens to
|
|
4332
|
+
// carry a numeric .length, which a typeof-based probe would miss
|
|
4333
|
+
// (e.g. HTMLSelectElement.length is a defined unsigned-long).
|
|
4334
|
+
element.childNodes !== getChildNodes(element);
|
|
4335
|
+
};
|
|
4336
|
+
/**
|
|
4337
|
+
* Checks whether the given value is a DocumentFragment from any realm.
|
|
4338
|
+
*
|
|
4339
|
+
* The realm-independent replacement reads `nodeType` through the cached
|
|
4340
|
+
* Node.prototype getter and compares to the DOCUMENT_FRAGMENT_NODE
|
|
4341
|
+
* constant (11). nodeType is a numeric value resolved from the node's
|
|
4342
|
+
* internal slot, identical across realms for the same kind of node.
|
|
4343
|
+
*
|
|
4344
|
+
* @param value object to check
|
|
4345
|
+
* @return true if value is a DocumentFragment-shaped node from any realm
|
|
4346
|
+
*/
|
|
4347
|
+
const _isDocumentFragment = function _isDocumentFragment(value) {
|
|
4348
|
+
if (!getNodeType || typeof value !== 'object' || value === null) {
|
|
4349
|
+
return false;
|
|
4350
|
+
}
|
|
4351
|
+
try {
|
|
4352
|
+
return getNodeType(value) === NODE_TYPE.documentFragment;
|
|
4353
|
+
} catch (_) {
|
|
4354
|
+
return false;
|
|
4355
|
+
}
|
|
3840
4356
|
};
|
|
3841
4357
|
/**
|
|
3842
|
-
* Checks whether the given object is a DOM node
|
|
4358
|
+
* Checks whether the given object is a DOM node, including nodes that
|
|
4359
|
+
* originate from a different window/realm (e.g. an iframe's
|
|
4360
|
+
* contentDocument). The previous `value instanceof Node` check was
|
|
4361
|
+
* realm-bound: nodes from a different window failed it, causing
|
|
4362
|
+
* sanitize() to silently stringify them and reset IN_PLACE to false,
|
|
4363
|
+
* returning the original node unsanitized. See GHSA-4w3q-35jp-p934.
|
|
3843
4364
|
*
|
|
3844
4365
|
* @param value object to check whether it's a DOM node
|
|
3845
|
-
* @return true
|
|
4366
|
+
* @return true if value is a DOM node from any realm
|
|
3846
4367
|
*/
|
|
3847
4368
|
const _isNode = function _isNode(value) {
|
|
3848
|
-
|
|
4369
|
+
if (!getNodeType || typeof value !== 'object' || value === null) {
|
|
4370
|
+
return false;
|
|
4371
|
+
}
|
|
4372
|
+
try {
|
|
4373
|
+
return typeof getNodeType(value) === 'number';
|
|
4374
|
+
} catch (_) {
|
|
4375
|
+
return false;
|
|
4376
|
+
}
|
|
3849
4377
|
};
|
|
3850
4378
|
function _executeHooks(hooks, currentNode, data) {
|
|
4379
|
+
if (hooks.length === 0) {
|
|
4380
|
+
return;
|
|
4381
|
+
}
|
|
3851
4382
|
arrayForEach(hooks, hook => {
|
|
3852
4383
|
hook.call(DOMPurify, currentNode, data, CONFIG);
|
|
3853
4384
|
});
|
|
3854
4385
|
}
|
|
4386
|
+
/**
|
|
4387
|
+
* Structural-threat checks that condemn a node regardless of the
|
|
4388
|
+
* allowlists: mXSS via namespace confusion, risky CSS construction,
|
|
4389
|
+
* processing instructions, markup-bearing comments. Pure predicate;
|
|
4390
|
+
* the caller removes. Check order is load-bearing.
|
|
4391
|
+
*
|
|
4392
|
+
* @param currentNode the node to inspect
|
|
4393
|
+
* @param tagName the node's transformCaseFunc'd tag name
|
|
4394
|
+
* @return true if the node must be removed
|
|
4395
|
+
*/
|
|
4396
|
+
const _isUnsafeNode = function _isUnsafeNode(currentNode, tagName) {
|
|
4397
|
+
/* Detect mXSS attempts abusing namespace confusion */
|
|
4398
|
+
if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.textContent) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.innerHTML)) {
|
|
4399
|
+
return true;
|
|
4400
|
+
}
|
|
4401
|
+
/* Remove risky CSS construction leading to mXSS */
|
|
4402
|
+
if (SAFE_FOR_XML && currentNode.namespaceURI === HTML_NAMESPACE && tagName === 'style' && _isNode(currentNode.firstElementChild)) {
|
|
4403
|
+
return true;
|
|
4404
|
+
}
|
|
4405
|
+
/* Remove any occurrence of processing instructions */
|
|
4406
|
+
if (currentNode.nodeType === NODE_TYPE.processingInstruction) {
|
|
4407
|
+
return true;
|
|
4408
|
+
}
|
|
4409
|
+
/* Remove any kind of possibly harmful comments */
|
|
4410
|
+
if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(COMMENT_MARKUP_PROBE, currentNode.data)) {
|
|
4411
|
+
return true;
|
|
4412
|
+
}
|
|
4413
|
+
return false;
|
|
4414
|
+
};
|
|
4415
|
+
/**
|
|
4416
|
+
* Handle a node whose tag is forbidden or not allowlisted: keep
|
|
4417
|
+
* allowed custom elements (false return exits _sanitizeElements
|
|
4418
|
+
* early - namespace/fallback checks and the afterSanitizeElements
|
|
4419
|
+
* hook are intentionally skipped for kept custom elements), else
|
|
4420
|
+
* hoist content per KEEP_CONTENT and remove.
|
|
4421
|
+
*
|
|
4422
|
+
* @param currentNode the disallowed node
|
|
4423
|
+
* @param tagName the node's transformCaseFunc'd tag name
|
|
4424
|
+
* @return true if the node was removed, false if kept
|
|
4425
|
+
*/
|
|
4426
|
+
const _sanitizeDisallowedNode = function _sanitizeDisallowedNode(currentNode, tagName) {
|
|
4427
|
+
/* Check if we have a custom element to handle */
|
|
4428
|
+
if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
|
|
4429
|
+
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {
|
|
4430
|
+
return false;
|
|
4431
|
+
}
|
|
4432
|
+
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) {
|
|
4433
|
+
return false;
|
|
4434
|
+
}
|
|
4435
|
+
}
|
|
4436
|
+
/* Keep content except for bad-listed elements.
|
|
4437
|
+
Use the cached prototype getters exclusively — the previous code
|
|
4438
|
+
had `|| currentNode.parentNode` / `|| currentNode.childNodes`
|
|
4439
|
+
fallbacks, but the cached getters always return the canonical
|
|
4440
|
+
value (or null for a real parent-less node), so the fallback
|
|
4441
|
+
path was dead in safe cases and a clobbering surface in unsafe
|
|
4442
|
+
ones. Falsy cached results stay falsy; the `if (childNodes &&
|
|
4443
|
+
parentNode)` check already gates correctly. */
|
|
4444
|
+
if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
|
|
4445
|
+
const parentNode = getParentNode(currentNode);
|
|
4446
|
+
const childNodes = getChildNodes(currentNode);
|
|
4447
|
+
if (childNodes && parentNode) {
|
|
4448
|
+
const childCount = childNodes.length;
|
|
4449
|
+
/* In-place: hoist the *original* children so the iterator visits
|
|
4450
|
+
and sanitises them through the same allowlist pass as every other
|
|
4451
|
+
node. The caller built the tree in the live document, so the
|
|
4452
|
+
originals carry already-queued resource events (`<img onerror>`,
|
|
4453
|
+
`<video>`/`<audio>` error, lazy/`onload`, …); cloning would leave
|
|
4454
|
+
those originals detached but still armed, firing in page scope
|
|
4455
|
+
while the returned tree looked clean. Moving is safe in-place: the
|
|
4456
|
+
root is pre-validated as an allowed tag and so is never the node
|
|
4457
|
+
being removed, which keeps `parentNode` inside the iterator root
|
|
4458
|
+
and the relocated child inside the serialised tree.
|
|
4459
|
+
Otherwise (string / DOM-copy paths): clone. The iterator is rooted
|
|
4460
|
+
at — and the result serialised from — `body`, so a restrictive
|
|
4461
|
+
ALLOWED_TAGS that removes `body` itself must leave its content in
|
|
4462
|
+
place, which only cloning does; and those paths parse into an
|
|
4463
|
+
inert document, so their discarded originals never had a queued
|
|
4464
|
+
event to neutralise.
|
|
4465
|
+
`childNodes` is live; a tail-to-head walk keeps `childNodes[i]`
|
|
4466
|
+
valid whether we move (drops the trailing entry) or clone (leaves
|
|
4467
|
+
the list intact). */
|
|
4468
|
+
for (let i = childCount - 1; i >= 0; --i) {
|
|
4469
|
+
const hoisted = IN_PLACE ? childNodes[i] : cloneNode(childNodes[i], true);
|
|
4470
|
+
parentNode.insertBefore(hoisted, getNextSibling(currentNode));
|
|
4471
|
+
}
|
|
4472
|
+
}
|
|
4473
|
+
}
|
|
4474
|
+
_forceRemove(currentNode);
|
|
4475
|
+
return true;
|
|
4476
|
+
};
|
|
3855
4477
|
/**
|
|
3856
4478
|
* _sanitizeElements
|
|
3857
4479
|
*
|
|
@@ -3862,7 +4484,6 @@ function createDOMPurify() {
|
|
|
3862
4484
|
* @return true if node was killed, false if left alive
|
|
3863
4485
|
*/
|
|
3864
4486
|
const _sanitizeElements = function _sanitizeElements(currentNode) {
|
|
3865
|
-
let content = null;
|
|
3866
4487
|
/* Execute a hook if present */
|
|
3867
4488
|
_executeHooks(hooks.beforeSanitizeElements, currentNode, null);
|
|
3868
4489
|
/* Check if element is clobbered or can clobber */
|
|
@@ -3871,76 +4492,41 @@ function createDOMPurify() {
|
|
|
3871
4492
|
return true;
|
|
3872
4493
|
}
|
|
3873
4494
|
/* Now let's check the element's type and name */
|
|
3874
|
-
const tagName = transformCaseFunc(currentNode.nodeName);
|
|
4495
|
+
const tagName = transformCaseFunc(getNodeName ? getNodeName(currentNode) : currentNode.nodeName);
|
|
3875
4496
|
/* Execute a hook if present */
|
|
3876
4497
|
_executeHooks(hooks.uponSanitizeElement, currentNode, {
|
|
3877
4498
|
tagName,
|
|
3878
4499
|
allowedTags: ALLOWED_TAGS
|
|
3879
4500
|
});
|
|
3880
|
-
/*
|
|
3881
|
-
if (
|
|
3882
|
-
_forceRemove(currentNode);
|
|
3883
|
-
return true;
|
|
3884
|
-
}
|
|
3885
|
-
/* Remove risky CSS construction leading to mXSS */
|
|
3886
|
-
if (SAFE_FOR_XML && currentNode.namespaceURI === HTML_NAMESPACE && tagName === 'style' && _isNode(currentNode.firstElementChild)) {
|
|
3887
|
-
_forceRemove(currentNode);
|
|
3888
|
-
return true;
|
|
3889
|
-
}
|
|
3890
|
-
/* Remove any occurrence of processing instructions */
|
|
3891
|
-
if (currentNode.nodeType === NODE_TYPE.progressingInstruction) {
|
|
3892
|
-
_forceRemove(currentNode);
|
|
3893
|
-
return true;
|
|
3894
|
-
}
|
|
3895
|
-
/* Remove any kind of possibly harmful comments */
|
|
3896
|
-
if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(/<[/\w]/g, currentNode.data)) {
|
|
4501
|
+
/* Remove mXSS vectors, processing instructions and risky comments */
|
|
4502
|
+
if (_isUnsafeNode(currentNode, tagName)) {
|
|
3897
4503
|
_forceRemove(currentNode);
|
|
3898
4504
|
return true;
|
|
3899
4505
|
}
|
|
3900
4506
|
/* Remove element if anything forbids its presence */
|
|
3901
4507
|
if (FORBID_TAGS[tagName] || !(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && !ALLOWED_TAGS[tagName]) {
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
|
|
3913
|
-
const parentNode = getParentNode(currentNode) || currentNode.parentNode;
|
|
3914
|
-
const childNodes = getChildNodes(currentNode) || currentNode.childNodes;
|
|
3915
|
-
if (childNodes && parentNode) {
|
|
3916
|
-
const childCount = childNodes.length;
|
|
3917
|
-
for (let i = childCount - 1; i >= 0; --i) {
|
|
3918
|
-
const childClone = cloneNode(childNodes[i], true);
|
|
3919
|
-
childClone.__removalCount = (currentNode.__removalCount || 0) + 1;
|
|
3920
|
-
parentNode.insertBefore(childClone, getNextSibling(currentNode));
|
|
3921
|
-
}
|
|
3922
|
-
}
|
|
3923
|
-
}
|
|
3924
|
-
_forceRemove(currentNode);
|
|
3925
|
-
return true;
|
|
3926
|
-
}
|
|
3927
|
-
/* Check whether element has a valid namespace */
|
|
3928
|
-
if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
|
|
4508
|
+
return _sanitizeDisallowedNode(currentNode, tagName);
|
|
4509
|
+
}
|
|
4510
|
+
/* Check whether element has a valid namespace.
|
|
4511
|
+
Realm-safe check (GHSA-hpcv-96wg-7vj8): use the cached Node.prototype
|
|
4512
|
+
nodeType getter rather than `instanceof Element`, which is realm-
|
|
4513
|
+
bound and short-circuits to false for any node minted in a different
|
|
4514
|
+
realm — letting a foreign-realm element with a forbidden namespace
|
|
4515
|
+
slip past the namespace check entirely. */
|
|
4516
|
+
const nt = getNodeType ? getNodeType(currentNode) : currentNode.nodeType;
|
|
4517
|
+
if (nt === NODE_TYPE.element && !_checkValidNamespace(currentNode)) {
|
|
3929
4518
|
_forceRemove(currentNode);
|
|
3930
4519
|
return true;
|
|
3931
4520
|
}
|
|
3932
4521
|
/* Make sure that older browsers don't get fallback-tag mXSS */
|
|
3933
|
-
if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(
|
|
4522
|
+
if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(FALLBACK_TAG_CLOSE, currentNode.innerHTML)) {
|
|
3934
4523
|
_forceRemove(currentNode);
|
|
3935
4524
|
return true;
|
|
3936
4525
|
}
|
|
3937
4526
|
/* Sanitize element content to be template-safe */
|
|
3938
4527
|
if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
|
|
3939
4528
|
/* Get the element's text content */
|
|
3940
|
-
content = currentNode.textContent;
|
|
3941
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
3942
|
-
content = stringReplace(content, expr, ' ');
|
|
3943
|
-
});
|
|
4529
|
+
const content = _stripTemplateExpressions(currentNode.textContent);
|
|
3944
4530
|
if (currentNode.textContent !== content) {
|
|
3945
4531
|
arrayPush(DOMPurify.removed, {
|
|
3946
4532
|
element: currentNode.cloneNode()
|
|
@@ -3970,11 +4556,12 @@ function createDOMPurify() {
|
|
|
3970
4556
|
if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
|
|
3971
4557
|
return false;
|
|
3972
4558
|
}
|
|
4559
|
+
const nameIsPermitted = ALLOWED_ATTR[lcName] || EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag);
|
|
3973
4560
|
/* Allow valid data-* attributes: At least one character after "-"
|
|
3974
4561
|
(https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
|
|
3975
4562
|
XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
|
|
3976
4563
|
We don't need to check the value; it's always URI safe. */
|
|
3977
|
-
if (ALLOW_DATA_ATTR &&
|
|
4564
|
+
if (ALLOW_DATA_ATTR && regExpTest(DATA_ATTR$1, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR$1, lcName)) ; else if (!nameIsPermitted) {
|
|
3978
4565
|
if (
|
|
3979
4566
|
// First condition does a very basic check if a) it's basically a valid custom element tagname AND
|
|
3980
4567
|
// b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
|
|
@@ -3986,11 +4573,15 @@ function createDOMPurify() {
|
|
|
3986
4573
|
return false;
|
|
3987
4574
|
}
|
|
3988
4575
|
/* Check value is safe. First, is attr inert? If so, is safe */
|
|
3989
|
-
} else if (URI_SAFE_ATTRIBUTES[lcName]) ; else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && stringIndexOf(value, 'data:') === 0 && DATA_URI_TAGS[lcTag]) ; else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if (value) {
|
|
4576
|
+
} else if (URI_SAFE_ATTRIBUTES[lcName]) ; else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE$1, ''))) ; else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && stringIndexOf(value, 'data:') === 0 && DATA_URI_TAGS[lcTag]) ; else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA$1, stringReplace(value, ATTR_WHITESPACE$1, ''))) ; else if (value) {
|
|
3990
4577
|
return false;
|
|
3991
4578
|
} else ;
|
|
3992
4579
|
return true;
|
|
3993
4580
|
};
|
|
4581
|
+
/* Names the HTML spec reserves from valid-custom-element-name; these must
|
|
4582
|
+
* never be treated as basic custom elements even when a permissive
|
|
4583
|
+
* CUSTOM_ELEMENT_HANDLING.tagNameCheck is configured. */
|
|
4584
|
+
const RESERVED_CUSTOM_ELEMENT_NAMES = addToSet({}, ['annotation-xml', 'color-profile', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'missing-glyph']);
|
|
3994
4585
|
/**
|
|
3995
4586
|
* _isBasicCustomElement
|
|
3996
4587
|
* checks if at least one dash is included in tagName, and it's not the first char
|
|
@@ -4000,7 +4591,64 @@ function createDOMPurify() {
|
|
|
4000
4591
|
* @returns Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
|
|
4001
4592
|
*/
|
|
4002
4593
|
const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
|
|
4003
|
-
return tagName
|
|
4594
|
+
return !RESERVED_CUSTOM_ELEMENT_NAMES[stringToLowerCase(tagName)] && regExpTest(CUSTOM_ELEMENT$1, tagName);
|
|
4595
|
+
};
|
|
4596
|
+
/**
|
|
4597
|
+
* Wrap an attribute value in the matching Trusted Types object when
|
|
4598
|
+
* the active policy requires it. Namespaced attributes pass through
|
|
4599
|
+
* unchanged (no TT support yet, see
|
|
4600
|
+
* https://bugs.chromium.org/p/chromium/issues/detail?id=1305293).
|
|
4601
|
+
*
|
|
4602
|
+
* @param lcTag lowercase tag name of the containing element
|
|
4603
|
+
* @param lcName lowercase attribute name
|
|
4604
|
+
* @param namespaceURI the attribute's namespace, if any
|
|
4605
|
+
* @param value the attribute value to wrap
|
|
4606
|
+
* @return the value, wrapped when Trusted Types demand it
|
|
4607
|
+
*/
|
|
4608
|
+
const _applyTrustedTypesToAttribute = function _applyTrustedTypesToAttribute(lcTag, lcName, namespaceURI, value) {
|
|
4609
|
+
if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function' && !namespaceURI) {
|
|
4610
|
+
switch (trustedTypes.getAttributeType(lcTag, lcName)) {
|
|
4611
|
+
case 'TrustedHTML':
|
|
4612
|
+
{
|
|
4613
|
+
return _createTrustedHTML(value);
|
|
4614
|
+
}
|
|
4615
|
+
case 'TrustedScriptURL':
|
|
4616
|
+
{
|
|
4617
|
+
return _createTrustedScriptURL(value);
|
|
4618
|
+
}
|
|
4619
|
+
}
|
|
4620
|
+
}
|
|
4621
|
+
return value;
|
|
4622
|
+
};
|
|
4623
|
+
/**
|
|
4624
|
+
* Write a modified attribute value back onto the element. On
|
|
4625
|
+
* success, re-probe for clobbering introduced by the new value and
|
|
4626
|
+
* remove the element when found; otherwise pop the removal entry
|
|
4627
|
+
* recorded by the earlier _removeAttribute (long-standing pairing
|
|
4628
|
+
* with the SANITIZE_NAMED_PROPS path - do not "fix" casually). On
|
|
4629
|
+
* failure, remove the attribute instead.
|
|
4630
|
+
*
|
|
4631
|
+
* @param currentNode the element carrying the attribute
|
|
4632
|
+
* @param name the attribute name as present on the element
|
|
4633
|
+
* @param namespaceURI the attribute's namespace, if any
|
|
4634
|
+
* @param value the new attribute value
|
|
4635
|
+
*/
|
|
4636
|
+
const _setAttributeValue = function _setAttributeValue(currentNode, name, namespaceURI, value) {
|
|
4637
|
+
try {
|
|
4638
|
+
if (namespaceURI) {
|
|
4639
|
+
currentNode.setAttributeNS(namespaceURI, name, value);
|
|
4640
|
+
} else {
|
|
4641
|
+
/* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
|
|
4642
|
+
currentNode.setAttribute(name, value);
|
|
4643
|
+
}
|
|
4644
|
+
if (_isClobbered(currentNode)) {
|
|
4645
|
+
_forceRemove(currentNode);
|
|
4646
|
+
} else {
|
|
4647
|
+
arrayPop(DOMPurify.removed);
|
|
4648
|
+
}
|
|
4649
|
+
} catch (_) {
|
|
4650
|
+
_removeAttribute(name, currentNode);
|
|
4651
|
+
}
|
|
4004
4652
|
};
|
|
4005
4653
|
/**
|
|
4006
4654
|
* _sanitizeAttributes
|
|
@@ -4015,9 +4663,7 @@ function createDOMPurify() {
|
|
|
4015
4663
|
const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
|
|
4016
4664
|
/* Execute a hook if present */
|
|
4017
4665
|
_executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
|
|
4018
|
-
const
|
|
4019
|
-
attributes
|
|
4020
|
-
} = currentNode;
|
|
4666
|
+
const attributes = currentNode.attributes;
|
|
4021
4667
|
/* Check if we have attributes; if not we might have a text node */
|
|
4022
4668
|
if (!attributes || _isClobbered(currentNode)) {
|
|
4023
4669
|
return;
|
|
@@ -4030,14 +4676,13 @@ function createDOMPurify() {
|
|
|
4030
4676
|
forceKeepAttr: undefined
|
|
4031
4677
|
};
|
|
4032
4678
|
let l = attributes.length;
|
|
4679
|
+
const lcTag = transformCaseFunc(currentNode.nodeName);
|
|
4033
4680
|
/* Go backwards over all attributes; safely remove bad ones */
|
|
4034
4681
|
while (l--) {
|
|
4035
4682
|
const attr = attributes[l];
|
|
4036
|
-
const
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
value: attrValue
|
|
4040
|
-
} = attr;
|
|
4683
|
+
const name = attr.name,
|
|
4684
|
+
namespaceURI = attr.namespaceURI,
|
|
4685
|
+
attrValue = attr.value;
|
|
4041
4686
|
const lcName = transformCaseFunc(name);
|
|
4042
4687
|
const initValue = attrValue;
|
|
4043
4688
|
let value = name === 'value' ? initValue : stringTrim(initValue);
|
|
@@ -4051,12 +4696,14 @@ function createDOMPurify() {
|
|
|
4051
4696
|
/* Full DOM Clobbering protection via namespace isolation,
|
|
4052
4697
|
* Prefix id and name attributes with `user-content-`
|
|
4053
4698
|
*/
|
|
4054
|
-
if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
|
|
4699
|
+
if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name') && stringIndexOf(value, SANITIZE_NAMED_PROPS_PREFIX) !== 0) {
|
|
4055
4700
|
// Remove the attribute with this value
|
|
4056
4701
|
_removeAttribute(name, currentNode);
|
|
4057
4702
|
// Prefix the value and later re-create the attribute with the sanitized value
|
|
4058
4703
|
value = SANITIZE_NAMED_PROPS_PREFIX + value;
|
|
4059
4704
|
}
|
|
4705
|
+
// Else: already prefixed, leave the attribute alone — the prefix is
|
|
4706
|
+
// itself the clobbering protection, and re-applying it is incorrect.
|
|
4060
4707
|
/* Work around a security issue with comments inside attributes */
|
|
4061
4708
|
if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, value)) {
|
|
4062
4709
|
_removeAttribute(name, currentNode);
|
|
@@ -4067,7 +4714,7 @@ function createDOMPurify() {
|
|
|
4067
4714
|
_removeAttribute(name, currentNode);
|
|
4068
4715
|
continue;
|
|
4069
4716
|
}
|
|
4070
|
-
/* Did the hooks
|
|
4717
|
+
/* Did the hooks force-keep the attribute? */
|
|
4071
4718
|
if (hookEvent.forceKeepAttr) {
|
|
4072
4719
|
continue;
|
|
4073
4720
|
}
|
|
@@ -4077,56 +4724,24 @@ function createDOMPurify() {
|
|
|
4077
4724
|
continue;
|
|
4078
4725
|
}
|
|
4079
4726
|
/* Work around a security issue in jQuery 3.0 */
|
|
4080
|
-
if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(
|
|
4727
|
+
if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(SELF_CLOSING_TAG, value)) {
|
|
4081
4728
|
_removeAttribute(name, currentNode);
|
|
4082
4729
|
continue;
|
|
4083
4730
|
}
|
|
4084
4731
|
/* Sanitize attribute content to be template-safe */
|
|
4085
4732
|
if (SAFE_FOR_TEMPLATES) {
|
|
4086
|
-
|
|
4087
|
-
value = stringReplace(value, expr, ' ');
|
|
4088
|
-
});
|
|
4733
|
+
value = _stripTemplateExpressions(value);
|
|
4089
4734
|
}
|
|
4090
4735
|
/* Is `value` valid for this attribute? */
|
|
4091
|
-
const lcTag = transformCaseFunc(currentNode.nodeName);
|
|
4092
4736
|
if (!_isValidAttribute(lcTag, lcName, value)) {
|
|
4093
4737
|
_removeAttribute(name, currentNode);
|
|
4094
4738
|
continue;
|
|
4095
4739
|
}
|
|
4096
4740
|
/* Handle attributes that require Trusted Types */
|
|
4097
|
-
|
|
4098
|
-
if (namespaceURI) ; else {
|
|
4099
|
-
switch (trustedTypes.getAttributeType(lcTag, lcName)) {
|
|
4100
|
-
case 'TrustedHTML':
|
|
4101
|
-
{
|
|
4102
|
-
value = trustedTypesPolicy.createHTML(value);
|
|
4103
|
-
break;
|
|
4104
|
-
}
|
|
4105
|
-
case 'TrustedScriptURL':
|
|
4106
|
-
{
|
|
4107
|
-
value = trustedTypesPolicy.createScriptURL(value);
|
|
4108
|
-
break;
|
|
4109
|
-
}
|
|
4110
|
-
}
|
|
4111
|
-
}
|
|
4112
|
-
}
|
|
4741
|
+
value = _applyTrustedTypesToAttribute(lcTag, lcName, namespaceURI, value);
|
|
4113
4742
|
/* Handle invalid data-* attribute set by try-catching it */
|
|
4114
4743
|
if (value !== initValue) {
|
|
4115
|
-
|
|
4116
|
-
if (namespaceURI) {
|
|
4117
|
-
currentNode.setAttributeNS(namespaceURI, name, value);
|
|
4118
|
-
} else {
|
|
4119
|
-
/* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
|
|
4120
|
-
currentNode.setAttribute(name, value);
|
|
4121
|
-
}
|
|
4122
|
-
if (_isClobbered(currentNode)) {
|
|
4123
|
-
_forceRemove(currentNode);
|
|
4124
|
-
} else {
|
|
4125
|
-
arrayPop(DOMPurify.removed);
|
|
4126
|
-
}
|
|
4127
|
-
} catch (_) {
|
|
4128
|
-
_removeAttribute(name, currentNode);
|
|
4129
|
-
}
|
|
4744
|
+
_setAttributeValue(currentNode, name, namespaceURI, value);
|
|
4130
4745
|
}
|
|
4131
4746
|
}
|
|
4132
4747
|
/* Execute a hook if present */
|
|
@@ -4149,14 +4764,133 @@ function createDOMPurify() {
|
|
|
4149
4764
|
_sanitizeElements(shadowNode);
|
|
4150
4765
|
/* Check attributes next */
|
|
4151
4766
|
_sanitizeAttributes(shadowNode);
|
|
4152
|
-
/* Deep shadow DOM detected
|
|
4153
|
-
|
|
4767
|
+
/* Deep shadow DOM detected.
|
|
4768
|
+
Realm-safe check (GHSA-hpcv-96wg-7vj8): use nodeType against the
|
|
4769
|
+
DOCUMENT_FRAGMENT_NODE constant rather than instanceof, so we
|
|
4770
|
+
recurse into <template>.content from foreign realms too. */
|
|
4771
|
+
if (_isDocumentFragment(shadowNode.content)) {
|
|
4154
4772
|
_sanitizeShadowDOM2(shadowNode.content);
|
|
4155
4773
|
}
|
|
4774
|
+
/* An element iterated here may itself host an attached
|
|
4775
|
+
shadow root. The default NodeIterator does not enter shadow
|
|
4776
|
+
trees, so a shadow root nested inside template.content was
|
|
4777
|
+
previously reached by no walk at all (the pre-pass at
|
|
4778
|
+
_sanitizeAttachedShadowRoots descends via childNodes, which
|
|
4779
|
+
doesn't enter template.content; the template-content recursion
|
|
4780
|
+
above iterates the content but never inspected shadowRoot).
|
|
4781
|
+
Walk it explicitly. The nodeType guard avoids reading
|
|
4782
|
+
shadowRoot off text / comment / CDATA / PI nodes that the
|
|
4783
|
+
iterator also surfaces. */
|
|
4784
|
+
const shadowNodeType = getNodeType ? getNodeType(shadowNode) : shadowNode.nodeType;
|
|
4785
|
+
if (shadowNodeType === NODE_TYPE.element) {
|
|
4786
|
+
const innerSr = getShadowRoot(shadowNode);
|
|
4787
|
+
if (_isDocumentFragment(innerSr)) {
|
|
4788
|
+
_sanitizeAttachedShadowRoots(innerSr);
|
|
4789
|
+
_sanitizeShadowDOM2(innerSr);
|
|
4790
|
+
}
|
|
4791
|
+
}
|
|
4156
4792
|
}
|
|
4157
4793
|
/* Execute a hook if present */
|
|
4158
4794
|
_executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
|
|
4159
4795
|
};
|
|
4796
|
+
/**
|
|
4797
|
+
* _sanitizeAttachedShadowRoots
|
|
4798
|
+
*
|
|
4799
|
+
* Walks `root` and feeds every attached shadow root we encounter into
|
|
4800
|
+
* the existing _sanitizeShadowDOM pipeline. The default node iterator
|
|
4801
|
+
* does not descend into shadow trees, so nodes inside an attached
|
|
4802
|
+
* shadow root would otherwise be skipped entirely.
|
|
4803
|
+
*
|
|
4804
|
+
* Two real input paths put attached shadow roots in front of us:
|
|
4805
|
+
* 1. IN_PLACE on a DOM node that already has shadow roots attached.
|
|
4806
|
+
* 2. DOM-node input where importNode(dirty, true) deep-clones the
|
|
4807
|
+
* shadow root because it was created with `clonable: true`.
|
|
4808
|
+
*
|
|
4809
|
+
* This pass runs once, up front, so the main iteration loop (and the
|
|
4810
|
+
* existing _sanitizeShadowDOM template-content recursion) stay
|
|
4811
|
+
* untouched — string-input paths are not affected.
|
|
4812
|
+
*
|
|
4813
|
+
* @param root the subtree root to walk for attached shadow roots
|
|
4814
|
+
*/
|
|
4815
|
+
const _sanitizeAttachedShadowRoots = function _sanitizeAttachedShadowRoots(root) {
|
|
4816
|
+
/* Iterative (explicit stack) rather than per-child recursion. DOM APIs
|
|
4817
|
+
impose no depth cap, so an attacker-shaped tree (JSON/CRDT/editor data
|
|
4818
|
+
built straight into the DOM — the IN_PLACE surface) deeper than the JS
|
|
4819
|
+
call-stack budget would otherwise overflow native recursion here and
|
|
4820
|
+
throw at the IN_PLACE entry pre-pass, before a single node is
|
|
4821
|
+
sanitized, leaving the caller's live tree untouched (fail-open). See
|
|
4822
|
+
campaign-3 F4. A heap stack keeps depth off the call stack.
|
|
4823
|
+
Each work item is either a node to descend into, or a deferred
|
|
4824
|
+
`_sanitizeShadowDOM` for an already-walked shadow root. The deferred
|
|
4825
|
+
form preserves the original post-order discipline: a shadow root's
|
|
4826
|
+
nested shadow roots are discovered before the outer shadow is
|
|
4827
|
+
sanitized (which may remove hosts). Pushes are in reverse of the
|
|
4828
|
+
desired processing order (LIFO): template content, then children, then
|
|
4829
|
+
the shadow-sanitize, then the shadow walk — so the order matches the
|
|
4830
|
+
previous recursion exactly. */
|
|
4831
|
+
const stack = [{
|
|
4832
|
+
node: root,
|
|
4833
|
+
shadow: null
|
|
4834
|
+
}];
|
|
4835
|
+
while (stack.length > 0) {
|
|
4836
|
+
const item = stack.pop();
|
|
4837
|
+
/* Deferred shadow-DOM sanitisation: runs after its subtree was walked. */
|
|
4838
|
+
if (item.shadow) {
|
|
4839
|
+
_sanitizeShadowDOM2(item.shadow);
|
|
4840
|
+
continue;
|
|
4841
|
+
}
|
|
4842
|
+
const node = item.node;
|
|
4843
|
+
const nodeType = getNodeType ? getNodeType(node) : node.nodeType;
|
|
4844
|
+
const isElement = nodeType === NODE_TYPE.element;
|
|
4845
|
+
/* (pushed last → processed first) Children, snapshotted in reverse so
|
|
4846
|
+
the first child is processed first. Snapshotting matters because a
|
|
4847
|
+
hook may detach siblings mid-walk. */
|
|
4848
|
+
const childNodes = getChildNodes(node);
|
|
4849
|
+
if (childNodes) {
|
|
4850
|
+
for (let i = childNodes.length - 1; i >= 0; --i) {
|
|
4851
|
+
stack.push({
|
|
4852
|
+
node: childNodes[i],
|
|
4853
|
+
shadow: null
|
|
4854
|
+
});
|
|
4855
|
+
}
|
|
4856
|
+
}
|
|
4857
|
+
/* (pushed before children → processed after them, matching the old
|
|
4858
|
+
"template content last" order) When the node is a <template>,
|
|
4859
|
+
descend into its content. */
|
|
4860
|
+
if (isElement) {
|
|
4861
|
+
const rootName = getNodeName ? getNodeName(node) : null;
|
|
4862
|
+
if (typeof rootName === 'string' && transformCaseFunc(rootName) === 'template') {
|
|
4863
|
+
const content = node.content;
|
|
4864
|
+
if (_isDocumentFragment(content)) {
|
|
4865
|
+
stack.push({
|
|
4866
|
+
node: content,
|
|
4867
|
+
shadow: null
|
|
4868
|
+
});
|
|
4869
|
+
}
|
|
4870
|
+
}
|
|
4871
|
+
}
|
|
4872
|
+
/* Shadow root (processed first): walk its subtree, then sanitise it.
|
|
4873
|
+
Realm-safe check (GHSA-hpcv-96wg-7vj8): nodeType-based detection
|
|
4874
|
+
rather than `instanceof DocumentFragment`, which is realm-bound and
|
|
4875
|
+
silently skipped foreign-realm shadow roots (e.g.
|
|
4876
|
+
iframe.contentDocument attachShadow). */
|
|
4877
|
+
if (isElement) {
|
|
4878
|
+
const sr = getShadowRoot(node);
|
|
4879
|
+
if (_isDocumentFragment(sr)) {
|
|
4880
|
+
/* Push the deferred sanitise first so it pops after the shadow
|
|
4881
|
+
walk we push next, i.e. nested shadow roots are discovered
|
|
4882
|
+
before this one is sanitised. */
|
|
4883
|
+
stack.push({
|
|
4884
|
+
node: null,
|
|
4885
|
+
shadow: sr
|
|
4886
|
+
}, {
|
|
4887
|
+
node: sr,
|
|
4888
|
+
shadow: null
|
|
4889
|
+
});
|
|
4890
|
+
}
|
|
4891
|
+
}
|
|
4892
|
+
}
|
|
4893
|
+
};
|
|
4160
4894
|
// eslint-disable-next-line complexity
|
|
4161
4895
|
DOMPurify.sanitize = function (dirty) {
|
|
4162
4896
|
let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
@@ -4173,13 +4907,9 @@ function createDOMPurify() {
|
|
|
4173
4907
|
}
|
|
4174
4908
|
/* Stringify, in case dirty is an object */
|
|
4175
4909
|
if (typeof dirty !== 'string' && !_isNode(dirty)) {
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
throw typeErrorCreate('dirty is not a string, aborting');
|
|
4180
|
-
}
|
|
4181
|
-
} else {
|
|
4182
|
-
throw typeErrorCreate('toString is not a function');
|
|
4910
|
+
dirty = stringifyValue(dirty);
|
|
4911
|
+
if (typeof dirty !== 'string') {
|
|
4912
|
+
throw typeErrorCreate('dirty is not a string, aborting');
|
|
4183
4913
|
}
|
|
4184
4914
|
}
|
|
4185
4915
|
/* Return dirty HTML if DOMPurify cannot run */
|
|
@@ -4187,24 +4917,78 @@ function createDOMPurify() {
|
|
|
4187
4917
|
return dirty;
|
|
4188
4918
|
}
|
|
4189
4919
|
/* Assign config vars */
|
|
4190
|
-
if (
|
|
4920
|
+
if (SET_CONFIG) {
|
|
4921
|
+
/* Persistent setConfig() path: _parseConfig is skipped, so the sets are
|
|
4922
|
+
* not re-derived per call. Restore them from the pristine bindings
|
|
4923
|
+
* captured at setConfig() time so a previous call's hook clone (mutated
|
|
4924
|
+
* below) does not carry over. */
|
|
4925
|
+
ALLOWED_TAGS = SET_CONFIG_ALLOWED_TAGS;
|
|
4926
|
+
ALLOWED_ATTR = SET_CONFIG_ALLOWED_ATTR;
|
|
4927
|
+
} else {
|
|
4191
4928
|
_parseConfig(cfg);
|
|
4192
4929
|
}
|
|
4930
|
+
/* Clone the hook-mutable allowlists before the walk whenever an
|
|
4931
|
+
* uponSanitize* hook is registered. The hook event exposes ALLOWED_TAGS
|
|
4932
|
+
* and ALLOWED_ATTR by reference (as allowedTags / allowedAttributes), so
|
|
4933
|
+
* a hook that widens them would otherwise mutate the shared set
|
|
4934
|
+
* permanently: across later calls and across every element. Cloning per
|
|
4935
|
+
* walk keeps documented in-call widening working while scoping it to the
|
|
4936
|
+
* call. A single guard for both config paths - the per-call path rebinds
|
|
4937
|
+
* the sets in _parseConfig each call, the persistent path restores them
|
|
4938
|
+
* from the captured bindings just above - so the two cannot diverge. */
|
|
4939
|
+
if (hooks.uponSanitizeElement.length > 0 || hooks.uponSanitizeAttribute.length > 0) {
|
|
4940
|
+
ALLOWED_TAGS = clone(ALLOWED_TAGS);
|
|
4941
|
+
}
|
|
4942
|
+
if (hooks.uponSanitizeAttribute.length > 0) {
|
|
4943
|
+
ALLOWED_ATTR = clone(ALLOWED_ATTR);
|
|
4944
|
+
}
|
|
4193
4945
|
/* Clean up removed elements */
|
|
4194
4946
|
DOMPurify.removed = [];
|
|
4195
|
-
/*
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4947
|
+
/* Resolve IN_PLACE for this call without mutating persistent config.
|
|
4948
|
+
Writing the IN_PLACE closure variable here leaks under setConfig(),
|
|
4949
|
+
where _parseConfig is skipped on later calls: a single string call would
|
|
4950
|
+
disable in-place mode for every subsequent node call, returning a
|
|
4951
|
+
sanitized copy while leaving the caller's node — which in-place callers
|
|
4952
|
+
keep using and whose return value they ignore — unsanitized. REPORT-2. */
|
|
4953
|
+
const inPlace = IN_PLACE && typeof dirty !== 'string' && _isNode(dirty);
|
|
4954
|
+
if (inPlace) {
|
|
4955
|
+
/* Do some early pre-sanitization to avoid unsafe root nodes.
|
|
4956
|
+
Read nodeName through the cached prototype getter — a clobbering
|
|
4957
|
+
child named "nodeName" on the form root would otherwise shadow
|
|
4958
|
+
the property and let this check skip the root-allowlist
|
|
4959
|
+
validation entirely. */
|
|
4960
|
+
const nn = getNodeName ? getNodeName(dirty) : dirty.nodeName;
|
|
4961
|
+
if (typeof nn === 'string') {
|
|
4962
|
+
const tagName = transformCaseFunc(nn);
|
|
4203
4963
|
if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
|
|
4204
4964
|
throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
|
|
4205
4965
|
}
|
|
4206
4966
|
}
|
|
4207
|
-
|
|
4967
|
+
/* Pre-flight the root through _isClobbered. The iterator-driven
|
|
4968
|
+
removal path can not detach a parent-less root: _forceRemove
|
|
4969
|
+
falls through to Element.prototype.remove(), which per spec
|
|
4970
|
+
is a no-op on a node with no parent. A clobbered root would
|
|
4971
|
+
then survive the main loop with its attributes uninspected,
|
|
4972
|
+
because _sanitizeAttributes early-returns on _isClobbered. The
|
|
4973
|
+
result would be an attacker-controlled form, complete with any
|
|
4974
|
+
event-handler attributes the caller passed in, handed back to
|
|
4975
|
+
the application unsanitized. Refuse to sanitize such a root
|
|
4976
|
+
the same way we refuse a forbidden tag. GHSA-r47g-fvhr-h676. */
|
|
4977
|
+
if (_isClobbered(dirty)) {
|
|
4978
|
+
throw typeErrorCreate('root node is clobbered and cannot be sanitized in-place');
|
|
4979
|
+
}
|
|
4980
|
+
/* Sanitize attached shadow roots before the main iterator runs.
|
|
4981
|
+
The iterator does not descend into shadow trees. Same fail-closed
|
|
4982
|
+
barrier as the main walk (campaign-3 F2): a custom-element reaction
|
|
4983
|
+
inside a shadow root could abort this pre-pass before the walk runs,
|
|
4984
|
+
which would otherwise leave the entire live tree unsanitized. */
|
|
4985
|
+
try {
|
|
4986
|
+
_sanitizeAttachedShadowRoots(dirty);
|
|
4987
|
+
} catch (error) {
|
|
4988
|
+
_neutralizeRoot(dirty);
|
|
4989
|
+
throw error;
|
|
4990
|
+
}
|
|
4991
|
+
} else if (_isNode(dirty)) {
|
|
4208
4992
|
/* If dirty is a DOM element, append to an empty document to avoid
|
|
4209
4993
|
elements being stripped by the parser */
|
|
4210
4994
|
body = _initDocument('<!---->');
|
|
@@ -4218,12 +5002,18 @@ function createDOMPurify() {
|
|
|
4218
5002
|
// eslint-disable-next-line unicorn/prefer-dom-node-append
|
|
4219
5003
|
body.appendChild(importedNode);
|
|
4220
5004
|
}
|
|
5005
|
+
/* Clonable shadow roots are deep-cloned by importNode(); sanitize
|
|
5006
|
+
them before the main iterator runs, since the iterator does not
|
|
5007
|
+
descend into shadow trees. The walk routes every read through a
|
|
5008
|
+
cached prototype getter so clobbering descendants on a form root
|
|
5009
|
+
cannot hide a shadow host from this pass. */
|
|
5010
|
+
_sanitizeAttachedShadowRoots(importedNode);
|
|
4221
5011
|
} else {
|
|
4222
5012
|
/* Exit directly if we have nothing to do */
|
|
4223
5013
|
if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
|
|
4224
5014
|
// eslint-disable-next-line unicorn/prefer-includes
|
|
4225
5015
|
dirty.indexOf('<') === -1) {
|
|
4226
|
-
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ?
|
|
5016
|
+
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? _createTrustedHTML(dirty) : dirty;
|
|
4227
5017
|
}
|
|
4228
5018
|
/* Initialize the document to work on */
|
|
4229
5019
|
body = _initDocument(dirty);
|
|
@@ -4237,31 +5027,59 @@ function createDOMPurify() {
|
|
|
4237
5027
|
_forceRemove(body.firstChild);
|
|
4238
5028
|
}
|
|
4239
5029
|
/* Get node iterator */
|
|
4240
|
-
const nodeIterator = _createNodeIterator(
|
|
4241
|
-
/* Now start iterating over the created document
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
5030
|
+
const nodeIterator = _createNodeIterator(inPlace ? dirty : body);
|
|
5031
|
+
/* Now start iterating over the created document.
|
|
5032
|
+
The walk runs inside an exception barrier (campaign-3 F2): a re-entrant
|
|
5033
|
+
engine/custom-element mutation can detach a node mid-walk so
|
|
5034
|
+
`_forceRemove`'s parentless guard throws, aborting the loop. Without the
|
|
5035
|
+
barrier the caller's in-place tree would be left half-sanitized with the
|
|
5036
|
+
unvisited tail still armed. On any throw we fail closed — strip the
|
|
5037
|
+
in-place root bare — then rethrow so the existing throw contract is
|
|
5038
|
+
preserved. (String/DOM-copy paths never return the partial body, so the
|
|
5039
|
+
propagating throw is already fail-closed there.) */
|
|
5040
|
+
try {
|
|
5041
|
+
while (currentNode = nodeIterator.nextNode()) {
|
|
5042
|
+
/* Sanitize tags and elements */
|
|
5043
|
+
_sanitizeElements(currentNode);
|
|
5044
|
+
/* Check attributes next */
|
|
5045
|
+
_sanitizeAttributes(currentNode);
|
|
5046
|
+
/* Shadow DOM detected, sanitize it.
|
|
5047
|
+
Realm-safe check (GHSA-hpcv-96wg-7vj8): nodeType-based detection
|
|
5048
|
+
instead of instanceof, so foreign-realm <template>.content is
|
|
5049
|
+
walked correctly. */
|
|
5050
|
+
if (_isDocumentFragment(currentNode.content)) {
|
|
5051
|
+
_sanitizeShadowDOM2(currentNode.content);
|
|
5052
|
+
}
|
|
4250
5053
|
}
|
|
5054
|
+
} catch (error) {
|
|
5055
|
+
if (inPlace) {
|
|
5056
|
+
_neutralizeRoot(dirty);
|
|
5057
|
+
}
|
|
5058
|
+
throw error;
|
|
4251
5059
|
}
|
|
4252
5060
|
/* If we sanitized `dirty` in-place, return it. */
|
|
4253
|
-
if (
|
|
5061
|
+
if (inPlace) {
|
|
5062
|
+
/* Fail-closed completion of the audit-5 F1 fix: every node removed from
|
|
5063
|
+
the caller's live tree is detached but may still hold a queued
|
|
5064
|
+
resource-event handler that fires in page scope after we return. The
|
|
5065
|
+
move-hoist covers only disallowed-tag KEEP_CONTENT removals; strip the
|
|
5066
|
+
non-allow-listed attributes off every other removed subtree (clobber,
|
|
5067
|
+
mXSS, namespace, comments, KEEP_CONTENT:false, …) so those handlers are
|
|
5068
|
+
cancelled before any event can fire. Runs synchronously, pre-return. */
|
|
5069
|
+
arrayForEach(DOMPurify.removed, entry => {
|
|
5070
|
+
if (entry.element) {
|
|
5071
|
+
_neutralizeSubtree(entry.element);
|
|
5072
|
+
}
|
|
5073
|
+
});
|
|
5074
|
+
if (SAFE_FOR_TEMPLATES) {
|
|
5075
|
+
_scrubTemplateExpressions2(dirty);
|
|
5076
|
+
}
|
|
4254
5077
|
return dirty;
|
|
4255
5078
|
}
|
|
4256
5079
|
/* Return sanitized string or DOM */
|
|
4257
5080
|
if (RETURN_DOM) {
|
|
4258
5081
|
if (SAFE_FOR_TEMPLATES) {
|
|
4259
|
-
body
|
|
4260
|
-
let html = body.innerHTML;
|
|
4261
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
4262
|
-
html = stringReplace(html, expr, ' ');
|
|
4263
|
-
});
|
|
4264
|
-
body.innerHTML = html;
|
|
5082
|
+
_scrubTemplateExpressions2(body);
|
|
4265
5083
|
}
|
|
4266
5084
|
if (RETURN_DOM_FRAGMENT) {
|
|
4267
5085
|
returnNode = createDocumentFragment.call(body.ownerDocument);
|
|
@@ -4291,20 +5109,28 @@ function createDOMPurify() {
|
|
|
4291
5109
|
}
|
|
4292
5110
|
/* Sanitize final string template-safe */
|
|
4293
5111
|
if (SAFE_FOR_TEMPLATES) {
|
|
4294
|
-
|
|
4295
|
-
serializedHTML = stringReplace(serializedHTML, expr, ' ');
|
|
4296
|
-
});
|
|
5112
|
+
serializedHTML = _stripTemplateExpressions(serializedHTML);
|
|
4297
5113
|
}
|
|
4298
|
-
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ?
|
|
5114
|
+
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? _createTrustedHTML(serializedHTML) : serializedHTML;
|
|
4299
5115
|
};
|
|
4300
5116
|
DOMPurify.setConfig = function () {
|
|
4301
5117
|
let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
4302
5118
|
_parseConfig(cfg);
|
|
4303
5119
|
SET_CONFIG = true;
|
|
5120
|
+
SET_CONFIG_ALLOWED_TAGS = ALLOWED_TAGS;
|
|
5121
|
+
SET_CONFIG_ALLOWED_ATTR = ALLOWED_ATTR;
|
|
4304
5122
|
};
|
|
4305
5123
|
DOMPurify.clearConfig = function () {
|
|
4306
5124
|
CONFIG = null;
|
|
4307
5125
|
SET_CONFIG = false;
|
|
5126
|
+
SET_CONFIG_ALLOWED_TAGS = null;
|
|
5127
|
+
SET_CONFIG_ALLOWED_ATTR = null;
|
|
5128
|
+
// Drop any caller-supplied Trusted Types policy so it cannot poison later
|
|
5129
|
+
// `RETURN_TRUSTED_TYPE` output. The internal default policy (cached, and
|
|
5130
|
+
// never recreated — Trusted Types throws on duplicate names) is restored by
|
|
5131
|
+
// the next `_parseConfig`. See GHSA-vxr8-fq34-vvx9.
|
|
5132
|
+
trustedTypesPolicy = defaultTrustedTypesPolicy;
|
|
5133
|
+
emptyHTML = '';
|
|
4308
5134
|
};
|
|
4309
5135
|
DOMPurify.isValidAttribute = function (tag, attr, value) {
|
|
4310
5136
|
/* Initialize shared config vars if necessary. */
|
|
@@ -4319,9 +5145,19 @@ function createDOMPurify() {
|
|
|
4319
5145
|
if (typeof hookFunction !== 'function') {
|
|
4320
5146
|
return;
|
|
4321
5147
|
}
|
|
5148
|
+
/* Reject unknown entry points. Without this, a non-hook key (e.g.
|
|
5149
|
+
* '__proto__') indexes off the prototype chain rather than a real
|
|
5150
|
+
* hook array, and arrayPush then writes to Object.prototype. Guard
|
|
5151
|
+
* with an own-property check against the known hook names. */
|
|
5152
|
+
if (!objectHasOwnProperty(hooks, entryPoint)) {
|
|
5153
|
+
return;
|
|
5154
|
+
}
|
|
4322
5155
|
arrayPush(hooks[entryPoint], hookFunction);
|
|
4323
5156
|
};
|
|
4324
5157
|
DOMPurify.removeHook = function (entryPoint, hookFunction) {
|
|
5158
|
+
if (!objectHasOwnProperty(hooks, entryPoint)) {
|
|
5159
|
+
return undefined;
|
|
5160
|
+
}
|
|
4325
5161
|
if (hookFunction !== undefined) {
|
|
4326
5162
|
const index = arrayLastIndexOf(hooks[entryPoint], hookFunction);
|
|
4327
5163
|
return index === -1 ? undefined : arraySplice(hooks[entryPoint], index, 1)[0];
|
|
@@ -4329,6 +5165,9 @@ function createDOMPurify() {
|
|
|
4329
5165
|
return arrayPop(hooks[entryPoint]);
|
|
4330
5166
|
};
|
|
4331
5167
|
DOMPurify.removeHooks = function (entryPoint) {
|
|
5168
|
+
if (!objectHasOwnProperty(hooks, entryPoint)) {
|
|
5169
|
+
return;
|
|
5170
|
+
}
|
|
4332
5171
|
hooks[entryPoint] = [];
|
|
4333
5172
|
};
|
|
4334
5173
|
DOMPurify.removeAllHooks = function () {
|
|
@@ -4470,6 +5309,7 @@ var ar = {
|
|
|
4470
5309
|
"status.uploading": "جارٍ التحميل",
|
|
4471
5310
|
"status.sessionExpired": "انتهت صلاحية جلسة الدردشة. سيتم بدء محادثة جديدة — يُرجى إعادة إرسال رسالتك.",
|
|
4472
5311
|
"status.sessionError": "لم تعد جلسة الدردشة هذه متاحة.",
|
|
5312
|
+
"status.chatEnded": "انتهت هذه المحادثة.",
|
|
4473
5313
|
"modal.newChatTitle": "بدء محادثة جديدة",
|
|
4474
5314
|
"modal.newChatBody": "بدء محادثة جديدة سيؤدي إلى مسح المحادثة الحالية. هل ترغب بالمتابعة؟",
|
|
4475
5315
|
"modal.cancel": "إلغاء",
|
|
@@ -4504,6 +5344,7 @@ var en = {
|
|
|
4504
5344
|
"status.uploading": "Uploading",
|
|
4505
5345
|
"status.sessionExpired": "Your chat session expired. Starting a new chat — please resend your message.",
|
|
4506
5346
|
"status.sessionError": "This chat session is no longer available.",
|
|
5347
|
+
"status.chatEnded": "This chat has ended.",
|
|
4507
5348
|
"modal.newChatTitle": "Start New Chat",
|
|
4508
5349
|
"modal.newChatBody": "Starting a new chat will clear your current conversation. Continue?",
|
|
4509
5350
|
"modal.cancel": "Cancel",
|
|
@@ -4538,6 +5379,7 @@ var es = {
|
|
|
4538
5379
|
"status.uploading": "Subiendo",
|
|
4539
5380
|
"status.sessionExpired": "Tu sesión de chat ha expirado. Se iniciará un nuevo chat: vuelve a enviar tu mensaje.",
|
|
4540
5381
|
"status.sessionError": "Esta sesión de chat ya no está disponible.",
|
|
5382
|
+
"status.chatEnded": "Este chat ha finalizado.",
|
|
4541
5383
|
"modal.newChatTitle": "Iniciar Nuevo Chat",
|
|
4542
5384
|
"modal.newChatBody": "Iniciar un nuevo chat borrará tu conversación actual. ¿Continuar?",
|
|
4543
5385
|
"modal.cancel": "Cancelar",
|
|
@@ -4572,6 +5414,7 @@ var fr = {
|
|
|
4572
5414
|
"status.uploading": "Téléversement",
|
|
4573
5415
|
"status.sessionExpired": "Votre session de chat a expiré. Un nouveau chat va démarrer — veuillez renvoyer votre message.",
|
|
4574
5416
|
"status.sessionError": "Cette session de chat n'est plus disponible.",
|
|
5417
|
+
"status.chatEnded": "Cette conversation est terminée.",
|
|
4575
5418
|
"modal.newChatTitle": "Nouveau Chat",
|
|
4576
5419
|
"modal.newChatBody": "Démarrer un nouveau chat effacera votre conversation actuelle. Continuer?",
|
|
4577
5420
|
"modal.cancel": "Annuler",
|
|
@@ -4606,6 +5449,7 @@ var hi = {
|
|
|
4606
5449
|
"status.uploading": "अपलोड हो रहा है",
|
|
4607
5450
|
"status.sessionExpired": "आपका चैट सत्र समाप्त हो गया है। एक नई चैट शुरू हो रही है — कृपया अपना संदेश फिर से भेजें।",
|
|
4608
5451
|
"status.sessionError": "यह चैट सत्र अब उपलब्ध नहीं है।",
|
|
5452
|
+
"status.chatEnded": "यह चैट समाप्त हो गई है।",
|
|
4609
5453
|
"modal.newChatTitle": "नई चैट शुरू करें",
|
|
4610
5454
|
"modal.newChatBody": "नई चैट शुरू करने से आपकी मौजूदा बातचीत हट जाएगी। क्या आप जारी रखना चाहते हैं?",
|
|
4611
5455
|
"modal.cancel": "रद्द करें",
|
|
@@ -4640,6 +5484,7 @@ var it = {
|
|
|
4640
5484
|
"status.uploading": "Caricamento",
|
|
4641
5485
|
"status.sessionExpired": "La tua sessione di chat è scaduta. Verrà avviata una nuova chat: invia di nuovo il tuo messaggio.",
|
|
4642
5486
|
"status.sessionError": "Questa sessione di chat non è più disponibile.",
|
|
5487
|
+
"status.chatEnded": "Questa chat è terminata.",
|
|
4643
5488
|
"modal.newChatTitle": "Avvia nuova chat",
|
|
4644
5489
|
"modal.newChatBody": "Avviare una nuova chat cancellerà la conversazione attuale. Continuare?",
|
|
4645
5490
|
"modal.cancel": "Annulla",
|
|
@@ -4674,6 +5519,7 @@ var pt = {
|
|
|
4674
5519
|
"status.uploading": "Carregando",
|
|
4675
5520
|
"status.sessionExpired": "Sua sessão de chat expirou. Iniciando um novo chat — reenvie sua mensagem.",
|
|
4676
5521
|
"status.sessionError": "Esta sessão de chat não está mais disponível.",
|
|
5522
|
+
"status.chatEnded": "Este chat foi encerrado.",
|
|
4677
5523
|
"modal.newChatTitle": "Iniciar novo chat",
|
|
4678
5524
|
"modal.newChatBody": "Iniciar um novo chat apagará sua conversa atual. Deseja continuar?",
|
|
4679
5525
|
"modal.cancel": "Cancelar",
|
|
@@ -4708,6 +5554,7 @@ var sw = {
|
|
|
4708
5554
|
"status.uploading": "Inapakia",
|
|
4709
5555
|
"status.sessionExpired": "Kipindi chako cha gumzo kimeisha. Gumzo jipya linaanza — tafadhali tuma tena ujumbe wako.",
|
|
4710
5556
|
"status.sessionError": "Kipindi hiki cha gumzo hakipatikani tena.",
|
|
5557
|
+
"status.chatEnded": "Mazungumzo haya yamekamilika.",
|
|
4711
5558
|
"modal.newChatTitle": "Anza gumzo jipya",
|
|
4712
5559
|
"modal.newChatBody": "Kuanza gumzo jipya kutafuta gumzo lako la sasa. Je, ungependa kuendelea?",
|
|
4713
5560
|
"modal.cancel": "Ghairi",
|
|
@@ -4742,6 +5589,7 @@ var uk = {
|
|
|
4742
5589
|
"status.uploading": "Завантаження",
|
|
4743
5590
|
"status.sessionExpired": "Ваш сеанс чату завершився. Розпочинається новий чат — будь ласка, надішліть повідомлення ще раз.",
|
|
4744
5591
|
"status.sessionError": "Цей сеанс чату більше недоступний.",
|
|
5592
|
+
"status.chatEnded": "Цей чат завершено.",
|
|
4745
5593
|
"modal.newChatTitle": "Почати новий чат",
|
|
4746
5594
|
"modal.newChatBody": "Початок нового чату видалить вашу поточну розмову. Продовжити?",
|
|
4747
5595
|
"modal.cancel": "Скасувати",
|
|
@@ -5138,12 +5986,17 @@ class ChatSessionService {
|
|
|
5138
5986
|
}
|
|
5139
5987
|
startMessagePolling(sessionId, callbacks) {
|
|
5140
5988
|
const poll = async () => {
|
|
5989
|
+
var _a;
|
|
5141
5990
|
try {
|
|
5142
5991
|
const since = callbacks.getSince();
|
|
5143
5992
|
const data = await this.fetchMessages(sessionId, since);
|
|
5144
5993
|
if (data.messages.length > 0) {
|
|
5145
5994
|
callbacks.onMessages(data.messages);
|
|
5146
5995
|
}
|
|
5996
|
+
if (data.session_status === 'ended') {
|
|
5997
|
+
this.stopMessagePolling();
|
|
5998
|
+
(_a = callbacks.onSessionEnded) === null || _a === void 0 ? void 0 : _a.call(callbacks);
|
|
5999
|
+
}
|
|
5147
6000
|
}
|
|
5148
6001
|
catch (error) {
|
|
5149
6002
|
if (callbacks.onError) {
|
|
@@ -5234,15 +6087,20 @@ class ChatSessionService {
|
|
|
5234
6087
|
}
|
|
5235
6088
|
throw new Error(message);
|
|
5236
6089
|
}
|
|
5237
|
-
|
|
6090
|
+
/** Headers for multipart requests (no Content-Type — fetch sets the boundary). */
|
|
6091
|
+
getUploadHeaders() {
|
|
5238
6092
|
const headers = this.getCommonHeaders();
|
|
5239
|
-
headers['Content-Type'] = 'application/json';
|
|
5240
6093
|
const csrfToken = this.csrfTokenProvider(this.apiBaseUrl);
|
|
5241
6094
|
if (csrfToken) {
|
|
5242
6095
|
headers['X-CSRFToken'] = csrfToken;
|
|
5243
6096
|
}
|
|
5244
6097
|
return headers;
|
|
5245
6098
|
}
|
|
6099
|
+
getJsonHeaders() {
|
|
6100
|
+
const headers = this.getUploadHeaders();
|
|
6101
|
+
headers['Content-Type'] = 'application/json';
|
|
6102
|
+
return headers;
|
|
6103
|
+
}
|
|
5246
6104
|
getCommonHeaders() {
|
|
5247
6105
|
const headers = {
|
|
5248
6106
|
'x-ocs-widget-version': this.widgetVersion,
|
|
@@ -5302,6 +6160,7 @@ class FileAttachmentManager {
|
|
|
5302
6160
|
});
|
|
5303
6161
|
}
|
|
5304
6162
|
async uploadPendingFiles(existingFiles, context) {
|
|
6163
|
+
var _a;
|
|
5305
6164
|
if (existingFiles.length === 0) {
|
|
5306
6165
|
return { selectedFiles: existingFiles, uploadedIds: [] };
|
|
5307
6166
|
}
|
|
@@ -5319,13 +6178,9 @@ class FileAttachmentManager {
|
|
|
5319
6178
|
formData.append('participant_name', context.participantName);
|
|
5320
6179
|
}
|
|
5321
6180
|
try {
|
|
5322
|
-
const headers = {};
|
|
5323
|
-
if (context.sessionToken) {
|
|
5324
|
-
headers['X-Session-Token'] = context.sessionToken;
|
|
5325
|
-
}
|
|
5326
6181
|
const response = await fetch(`${context.apiBaseUrl}/api/chat/${context.sessionId}/upload/`, {
|
|
5327
6182
|
method: 'POST',
|
|
5328
|
-
headers,
|
|
6183
|
+
headers: (_a = context.headers) !== null && _a !== void 0 ? _a : {},
|
|
5329
6184
|
body: formData,
|
|
5330
6185
|
});
|
|
5331
6186
|
if (!response.ok) {
|
|
@@ -5387,7 +6242,7 @@ class FileAttachmentManager {
|
|
|
5387
6242
|
}
|
|
5388
6243
|
}
|
|
5389
6244
|
|
|
5390
|
-
const ocsChatCss = "/*! tailwindcss v4.1.12 | MIT License | https://tailwindcss.com */@layer properties{@supports ((-webkit-hyphens:none) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,::backdrop,:after,:before{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-duration:initial;--tw-ease:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}@layer theme{:host,:root{--font-sans:ui-sans-serif,system-ui,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\",\"Noto Color Emoji\";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace;--color-blue-300:oklch(80.9% .105 251.813);--color-slate-500:oklch(55.4% .046 257.417);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--spacing:.25rem;--breakpoint-lg:64rem;--container-sm:24rem;--font-weight-light:300;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--animate-progress:progress 3s infinite linear;--animate-dots:dots 1s steps(5,end)infinite;--transform-origin-left-right:0% 50%}}@layer base{*,::backdrop,:after,:before{border:0 solid;border-color:var(--color-gray-200,currentcolor);box-sizing:border-box;margin:0;padding:0}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button;background-color:#0000;border:0 solid;border-color:var(--color-gray-200,currentcolor);border-radius:0;box-sizing:border-box;color:inherit;font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;margin:0;margin-inline-end:4px;opacity:1;padding:0}:host,html{-webkit-text-size-adjust:100%;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\",\"Noto Color Emoji\");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-size:1em;font-variation-settings:var(--default-mono-font-variation-settings,normal)}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}menu,ol,ul{list-style:none}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}button,input,optgroup,select,textarea{background-color:#0000;border-radius:0;color:inherit;font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;opacity:1}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::-moz-placeholder{opacity:1}::placeholder{opacity:1}@supports (not (-webkit-appearance:-apple-pay-button)) or (contain-intrinsic-size:1px){::-moz-placeholder{color:currentColor}::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::-moz-placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex;padding-block:0}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-meridiem-field,::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.visible{visibility:visible}.fixed{position:fixed}.relative{position:relative}.static{position:static}.container{width:100%}.block{display:block}.contents{display:contents}.flex{display:flex}.hidden{display:none}.w-full{width:100%}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.resize{resize:both}.items-center{align-items:center}.justify-center{justify-content:center}.gap-\\[0\\.5em\\]{gap:.5em}:where(.space-y-\\[0\\.25em\\]>:not(:last-child)){--tw-space-y-reverse:0;margin-block-end:calc(.25em*(1 - var(--tw-space-y-reverse)));margin-block-start:calc(.25em*var(--tw-space-y-reverse))}.border{border-style:var(--tw-border-style);border-width:1px}.py-\\[2px\\]{padding-block:2px}.text-\\[0\\.8em\\]{font-size:.8em}.font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.text-slate-500{color:var(--color-slate-500)}.underline{text-decoration-line:underline}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a)}.ring,.shadow{box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor)}} /*! tailwindcss v4.1.12 | MIT License | https://tailwindcss.com */@layer properties{}@layer base{}@layer components; /*! tailwindcss v4.1.12 | MIT License | https://tailwindcss.com */@layer properties{}@layer base{}@layer components{#ocs-chat-window{font-size:var(--chat-window-font-size);z-index:var(--chat-z-index)}.starter-question{border-radius:var(--radius-lg);text-align:left;--tw-duration:.2s;background-color:var(--starter-question-bg-color);border:1px solid var(--starter-question-border-color);color:var(--starter-question-text-color);padding:.75em;transition-duration:.2s}.starter-question:hover{background-color:var(--starter-question-bg-hover-color);border-color:var(--starter-question-border-hover-color)}.chat-btn-text{border-radius:var(--radius-lg);border-style:var(--tw-border-style);transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;--tw-ease:var(--ease-in-out);align-items:center;background-color:var(--button-background-color,#fff);border-width:0;border:1px solid var(--button-border-color);color:var(--button-text-color,#111827);display:flex;font-size:var(--button-font-size);gap:8px;padding:.5em;transition-duration:.2s;transition-timing-function:var(--ease-in-out);z-index:var(--chat-z-index,50)}.chat-btn-text:hover{border:1px solid var(--button-border-color-hover);color:var(--button-text-color-hover,#1d4ed8)}.chat-btn-text span{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);white-space:nowrap}.chat-btn-text img{flex-shrink:0;height:var(--button-icon-size);-o-object-fit:contain;object-fit:contain;width:var(--button-icon-size)}.chat-btn-text.round{border-radius:3.40282e+38px}.chat-btn-icon{border-radius:var(--radius-lg);border-style:var(--tw-border-style);transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;--tw-ease:var(--ease-in-out);background-color:var(--button-background-color,#fff);border-width:0;border:1px solid var(--button-border-color);font-size:var(--button-font-size);padding:.5em;transition-duration:.2s;transition-timing-function:var(--ease-in-out);z-index:var(--chat-z-index,50)}.chat-btn-icon:hover{border:1px solid var(--button-border-color-hover);color:var(--button-text-color-hover,#1d4ed8)}.chat-btn-icon img{height:var(--button-icon-size);-o-object-fit:contain;object-fit:contain;width:var(--button-icon-size)}.chat-btn-icon.round,.round .chat-btn-icon,.round.chat-btn-text{border-radius:3.40282e+38px}.error-message{color:var(--error-text-color);padding:.5em}.chat-window-fullscreen{border-style:var(--tw-border-style);inset:calc(var(--spacing)*0);z-index:9999;--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);height:100%;max-height:100%;transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));width:100%;--tw-duration:.2s;border-radius:0;border-width:0;max-width:var(--chat-window-fullscreen-width);transition-duration:.2s}.chat-window-fullscreen,.chat-window-normal{background-color:var(--chat-window-bg-color);display:flex;flex-direction:column;overflow:hidden;position:fixed}.chat-window-normal{border:1px solid var(--chat-window-border-color);border-radius:var(--radius-lg);height:100vh;max-width:var(--breakpoint-lg);min-height:300px;min-width:300px;width:100vw}.chat-window-normal:not(.chat-window-dragging){--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;transition-duration:.2s}.chat-window-dragging,.chat-window-normal:not(.chat-window-dragging){box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.chat-window-dragging{cursor:grabbing;--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040)}.chat-window-kiosk{background-color:var(--chat-window-bg-color);border-radius:0;box-shadow:none;display:flex;flex-direction:column;font-size:var(--chat-window-font-size);height:100%;inset:calc(var(--spacing)*0);overflow:hidden;position:absolute;width:100%;z-index:var(--chat-z-index)}.chat-header{transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.15s;align-items:center;background-color:var(--header-bg-color);border-bottom:1px solid var(--header-border-color);display:flex;font-size:var(--header-font-size);justify-content:space-between;padding:.5em;transition-duration:.15s}.chat-header:active,.chat-header:hover{background-color:var(--header-bg-hover-color)}.header-text{align-items:center;color:var(--header-text-color);display:flex;font-size:var(--header-text-font-size);justify-content:center}.chat-header-draggable{cursor:grab}.chat-header-dragging{cursor:grabbing}.drag-indicator{display:none}.drag-dots{display:flex;gap:2px;margin-left:2px;pointer-events:none}.header-buttons{align-items:center;display:flex;gap:4px}.header-button{border-radius:var(--radius-md);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;color:var(--header-button-text-color);padding:.375em;transition-duration:.2s}.header-button svg{height:var(--header-button-icon-size);width:var(--header-button-icon-size)}.header-button:hover{background-color:var(--header-button-bg-hover-color)}.fullscreen-button{display:none}.chat-content{display:flex;flex-direction:column;flex-grow:1;overflow:hidden}.loading-container{align-items:center;display:flex;flex-grow:1;justify-content:center}.loading-text{color:var(--loading-text-color);margin-left:2px}.messages-container{flex-grow:1;overflow-y:auto;padding:1em}:where(.messages-container>:not(:last-child)){--tw-space-y-reverse:0;margin-block-end:calc(var(--spacing)*2*(1 - var(--tw-space-y-reverse)));margin-block-start:calc(var(--spacing)*2*var(--tw-space-y-reverse))}.message-row{display:flex}.message-row-user{justify-content:flex-end}.message-row-assistant{justify-content:flex-start}.message-bubble{border-radius:var(--radius-lg);padding:.5em 1em}.message-bubble-user{background-color:var(--message-user-bg-color);color:var(--message-user-text-color)}.message-bubble-assistant{background-color:var(--message-assistant-bg-color);color:var(--message-assistant-text-color)}.message-bubble-system{background-color:var(--message-system-bg-color);color:var(--message-system-text-color)}.message-timestamp{font-size:var(--chat-window-font-size-sm);margin-top:4px;opacity:.7}.message-attachments{margin-top:8px}:where(.message-attachments>:not(:last-child)){--tw-space-y-reverse:0;margin-block-end:calc(var(--spacing)*1*(1 - var(--tw-space-y-reverse)));margin-block-start:calc(var(--spacing)*1*var(--tw-space-y-reverse))}.attachment-link{display:block;text-decoration-line:underline}:where(.welcome-messages>:not(:last-child)){--tw-space-y-reverse:0;margin-block-end:calc(var(--spacing)*2*(1 - var(--tw-space-y-reverse)));margin-block-start:calc(var(--spacing)*2*var(--tw-space-y-reverse))}.typing-indicator{height:calc(var(--spacing)*1.5);overflow:hidden;width:100%}.typing-progress{animation:var(--animate-progress);background-color:var(--typing-progress-bg-color);border-radius:var(--radius-lg);height:100%;transform-origin:var(--transform-origin-left-right);width:100%}.typing-text{font-size:var(--chat-window-font-size-sm);justify-content:center;opacity:.7;width:100%}.typing-dots{animation:var(--animate-dots)}:where(.starter-questions>:not(:last-child)){--tw-space-y-reverse:0;margin-block-end:calc(var(--spacing)*2*(1 - var(--tw-space-y-reverse)));margin-block-start:calc(var(--spacing)*2*var(--tw-space-y-reverse))}.starter-questions{padding:1em}.starter-question-row{display:flex;justify-content:flex-end}.input-area{background-color:var(--input-bg-color);border-top:1px solid var(--input-border-color);padding:1em 1em 0}.input-container{display:flex;gap:8px}.message-textarea{background-color:var(--input-bg-color);border-color:var(--color-gray-300);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border:1px solid var(--input-border-color);color:var(--input-text-color);flex-grow:1;padding:.5em .75em;resize:none}.message-textarea:focus{outline-color:var(--input-outline-focus-color)}.send-button{border-radius:var(--radius-md);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;padding:.5em 1em;transition-duration:.2s}.send-button-enabled{background-color:var(--send-button-bg-color);color:var(--send-button-text-color)}.send-button-enabled:hover{background-color:var(--send-button-bg-hover-color)}.send-button-disabled{background-color:var(--send-button-bg-disabled-color);color:var(--send-button-text-disabled-color);cursor:not-allowed}.confirmation-overlay{align-items:center;background-color:var(--confirmation-overlay-bg-color);display:flex;inset:calc(var(--spacing)*0);justify-content:center;position:fixed;z-index:9999}.confirmation-dialog{background-color:var(--confirmation-dialog-bg-color);border:1px solid var(--confirmation-dialog-border-color);border-radius:.75em;box-shadow:0 .625em 1.5625em var(--confirmation-dialog-shadow-color);margin-inline:calc(var(--spacing)*4);max-width:var(--container-sm);width:100%}.confirmation-content{padding:1.5em}.confirmation-title{margin-bottom:calc(var(--spacing)*2);--tw-font-weight:var(--font-weight-semibold);color:var(--confirmation-title-color);font-size:var(--confirmation-title-font-size);font-weight:var(--font-weight-semibold)}.confirmation-message{color:var(--confirmation-message-color);font-size:var(--confirmation-message-font-size);margin-bottom:calc(var(--spacing)*4)}.confirmation-buttons{display:flex;gap:.75em;justify-content:flex-end}.confirmation-button{border-radius:var(--radius-md);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;padding:.5em 1em;transition-duration:.2s}.confirmation-button-cancel{background-color:var(--confirmation-button-cancel-bg-color);color:var(--confirmation-button-cancel-text-color)}.confirmation-button-cancel:hover{background-color:var(--confirmation-button-cancel-bg-hover-color)}.confirmation-button-confirm{background-color:var(--confirmation-button-confirm-bg-color);color:var(--confirmation-button-confirm-text-color)}.confirmation-button-confirm:hover{background-color:var(--confirmation-button-confirm-bg-hover-color)}}@layer utilities{:host{--chat-z-index:50;--button-background-color:#fff;--button-background-color-hover:#f3f4f6;--button-text-color:#111827;--button-text-color-hover:#1d4ed8;--button-border-color:#d1d5db;--button-border-color-hover:#6b7280;--button-font-size:1em;--button-icon-size:1.5em;--chat-window-height:60%;--chat-window-width:25%;--chat-window-fullscreen-width:80%;--chat-window-bg-color:#fff;--chat-window-border-color:#d1d5db;--chat-window-shadow-color:#0000001a;--chat-window-font-size:.875em;--chat-window-font-size-sm:.75em;--header-bg-color:transparent;--header-bg-hover-color:#f9fafb;--header-border-color:#f3f4f6;--header-button-text-color:#6b7280;--header-button-bg-hover-color:#f3f4f6;--header-font-size:1em;--header-text-font-size:1em;--header-text-color:#525762;--header-button-icon-size:1.5em;--starter-question-bg-color:transparent;--starter-question-bg-hover-color:#eff6ff;--starter-question-text-color:#3b82f6;--starter-question-border-color:#3b82f6;--starter-question-border-hover-color:#2563eb;--message-user-bg-color:#e4edfb;--message-user-text-color:#1f2937;--message-user-link-color:#155dfc;--message-assistant-bg-color:#eae7e8;--message-assistant-text-color:var(--message-user-text-color);--message-assistant-link-color:var(--message-user-link-color);--message-system-bg-color:#fbe4f8;--message-system-text-color:var(--message-user-text-color);--message-system-link-color:var(--message-user-link-color);--message-timestamp-color:#ffffffb3;--message-timestamp-assistant-color:#4b5563b3;--input-bg-color:transparent;--input-border-color:#d1d5db;--input-text-color:#111827;--input-placeholder-color:#6b7280;--input-outline-focus-color:#3b82f6;--send-button-bg-color:#3b82f6;--send-button-bg-hover-color:#2563eb;--send-button-text-color:#fff;--send-button-bg-disabled-color:#d1d5db;--send-button-text-disabled-color:#6b7280;--loading-text-color:#6b7280;--loading-spinner-track-color:#e5e7eb;--loading-spinner-fill-color:#3b82f6;--loading-spinner-size:1.25em;--typing-progress-bg-color:#ade3ff;--scrollbar-track-color:#f3f4f6;--scrollbar-thumb-color:#d1d5db;--scrollbar-thumb-hover-color:#9ca3af;--error-text-color:#ef4444;--success-text-color:#10b981;--code-bg-user-color:var(--message-user-bg-color);--code-text-user-color:var(--message-user-text-color);--code-border-user-color:var(--message-user-bg-color);--code-bg-assistant-color:var(--message-assistant-bg-color);--code-text-assistant-color:var(--message-assistant-text-color);--code-border-assistant-color:var(--message-assistant-bg-color);--confirmation-overlay-bg-color:#00000080;--confirmation-dialog-bg-color:var(--chat-window-bg-color);--confirmation-dialog-border-color:var(--chat-window-border-color);--confirmation-dialog-shadow-color:var(--chat-window-shadow-color);--confirmation-title-color:#111827;--confirmation-title-font-size:1.125em;--confirmation-message-color:var(--loading-text-color);--confirmation-message-font-size:1em;--confirmation-button-cancel-bg-color:var(--button-background-color-hover);--confirmation-button-cancel-bg-hover-color:#e5e7eb;--confirmation-button-cancel-text-color:var(--header-button-text-color);--confirmation-button-confirm-bg-color:var(--error-text-color);--confirmation-button-confirm-bg-hover-color:var(--error-text-color);--confirmation-button-confirm-text-color:var(--send-button-text-color);--file-attachment-button-bg-color:transparent;--file-attachment-button-bg-hover-color:var(--header-button-bg-hover-color);--file-attachment-button-text-color:var(--header-button-text-color);--file-attachment-button-text-disabled-color:var(--send-button-text-disabled-color);--selected-files-bg-color:var(--chat-window-bg-color);--selected-files-border-color:var(--header-border-color);--selected-file-bg-color:var(--button-background-color-hover);--selected-file-font-size:var(--chat-window-font-size-sm);--selected-file-name-color:var(--message-assistant-text-color);--selected-file-size-color:var(--input-placeholder-color);--selected-file-icon-size:1.25em;--selected-file-remove-icon-color:var(--error-text-color);--selected-file-remove-icon-hover-color:#dc2626;--message-attachment-icon-size:1em;bottom:30px;display:block;position:fixed;right:30px}@supports (color:color-mix(in lab,red,red)){:host{--code-bg-user-color:color-mix(in srgb,var(--message-user-bg-color)80%,#fff 20%);--code-border-user-color:color-mix(in srgb,var(--message-user-bg-color)90%,#000 10%);--code-bg-assistant-color:color-mix(in srgb,var(--message-assistant-bg-color)50%,#fff 50%);--code-border-assistant-color:color-mix(in srgb,var(--message-assistant-bg-color)90%,#000 10%)}}:host([mode=kiosk]){height:100%;inset:0 auto auto 0;position:absolute;width:100%}}textarea{max-height:calc(var(--spacing)*32);min-height:calc(var(--spacing)*10);overflow-y:auto;resize:none}.loading-spinner{animation:var(--animate-spin);border-color:var(--loading-spinner-track-color);border-radius:3.40282e+38px;border-style:var(--tw-border-style);border-top-color:var(--loading-spinner-fill-color);border-width:2px;height:var(--loading-spinner-size);width:var(--loading-spinner-size)}.overflow-y-auto::-webkit-scrollbar{height:calc(var(--spacing)*1.5);width:calc(var(--spacing)*1.5)}.overflow-y-auto::-webkit-scrollbar-track{background-color:var(--scrollbar-track-color);border-radius:var(--radius-sm)}.overflow-y-auto::-webkit-scrollbar-thumb{background-color:var(--scrollbar-thumb-color);border-radius:var(--radius-sm)}.overflow-y-auto::-webkit-scrollbar-thumb:hover{background-color:var(--scrollbar-thumb-hover-color)}.chat-markdown{color:var(--tw-prose-body);font-size:1rem;font-size:.875rem;line-height:1.75;line-height:1.71429;max-width:65ch;--tw-prose-body:oklch(37.3% .034 259.733);--tw-prose-headings:oklch(21% .034 264.665);--tw-prose-lead:oklch(44.6% .03 256.802);--tw-prose-links:oklch(21% .034 264.665);--tw-prose-bold:oklch(21% .034 264.665);--tw-prose-counters:oklch(55.1% .027 264.364);--tw-prose-bullets:oklch(87.2% .01 258.338);--tw-prose-hr:oklch(92.8% .006 264.531);--tw-prose-quotes:oklch(21% .034 264.665);--tw-prose-quote-borders:oklch(92.8% .006 264.531);--tw-prose-captions:oklch(55.1% .027 264.364);--tw-prose-kbd:oklch(21% .034 264.665);--tw-prose-kbd-shadows:NaN NaN NaN;--tw-prose-code:oklch(21% .034 264.665);--tw-prose-pre-code:oklch(92.8% .006 264.531);--tw-prose-pre-bg:oklch(27.8% .033 256.848);--tw-prose-th-borders:oklch(87.2% .01 258.338);--tw-prose-td-borders:oklch(92.8% .006 264.531);--tw-prose-invert-body:oklch(87.2% .01 258.338);--tw-prose-invert-headings:#fff;--tw-prose-invert-lead:oklch(70.7% .022 261.325);--tw-prose-invert-links:#fff;--tw-prose-invert-bold:#fff;--tw-prose-invert-counters:oklch(70.7% .022 261.325);--tw-prose-invert-bullets:oklch(44.6% .03 256.802);--tw-prose-invert-hr:oklch(37.3% .034 259.733);--tw-prose-invert-quotes:oklch(96.7% .003 264.542);--tw-prose-invert-quote-borders:oklch(37.3% .034 259.733);--tw-prose-invert-captions:oklch(70.7% .022 261.325);--tw-prose-invert-kbd:#fff;--tw-prose-invert-kbd-shadows:255 255 255;--tw-prose-invert-code:#fff;--tw-prose-invert-pre-code:oklch(87.2% .01 258.338);--tw-prose-invert-pre-bg:#00000080;--tw-prose-invert-th-borders:oklch(44.6% .03 256.802);--tw-prose-invert-td-borders:oklch(37.3% .034 259.733);font-size:1em;max-width:none}.chat-markdown :where([class~=lead]):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-lead);font-size:1.25em;font-size:1.28571em;line-height:1.6;line-height:1.55556;margin-bottom:.888889em;margin-top:.888889em}.chat-markdown :where(a):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-links);font-weight:500;text-decoration:underline;text-decoration-line:none}.chat-markdown :where(strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-bold);font-weight:600}.chat-markdown :where(a strong):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(blockquote strong):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(thead th strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.chat-markdown :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:decimal;margin-bottom:1.25em;margin-top:1.25em;padding-inline-start:1.625em}.chat-markdown :where(ol[type=A]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.chat-markdown :where(ol[type=a]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.chat-markdown :where(ol[type=A s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.chat-markdown :where(ol[type=a s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.chat-markdown :where(ol[type=I]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.chat-markdown :where(ol[type=i]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.chat-markdown :where(ol[type=I s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.chat-markdown :where(ol[type=i s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.chat-markdown :where(ol[type=\"1\"]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:decimal}.chat-markdown :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:disc;margin-bottom:1.25em;margin-top:1.25em;padding-inline-start:1.625em}.chat-markdown :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{color:var(--tw-prose-counters);font-weight:400}.chat-markdown :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{color:var(--tw-prose-bullets)}.chat-markdown :where(dt):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;margin-top:1.14286em}.chat-markdown :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:var(--tw-prose-hr);border-top-width:1px;margin-bottom:2.85714em;margin-top:2.85714em}.chat-markdown :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)){border-inline-start-color:var(--tw-prose-quote-borders);border-inline-start-width:.25rem;color:var(--tw-prose-quotes);font-style:italic;font-weight:500;margin-bottom:1.33333em;margin-top:1.33333em;padding-inline-start:1em;padding-inline-start:1.11111em;quotes:\"\u201C\"\"\u201D\"\"\u2018\"\"\u2019\"}.chat-markdown :where(blockquote p:first-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:open-quote}.chat-markdown :where(blockquote p:last-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:close-quote}.chat-markdown :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-size:2.25em;font-size:2.14286em;font-weight:800;line-height:1.11111;line-height:1.2;margin-bottom:.8em;margin-top:0}.chat-markdown :where(h1 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:900}.chat-markdown :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-size:1.5em;font-size:1.42857em;font-weight:700;line-height:1.33333;line-height:1.4;margin-bottom:.8em;margin-top:1.6em}.chat-markdown :where(h2 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:800}.chat-markdown :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-size:1.25em;font-size:1.28571em;font-weight:600;line-height:1.6;line-height:1.55556;margin-bottom:.444444em;margin-top:1.55556em}.chat-markdown :where(h3 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:700}.chat-markdown :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;line-height:1.5;line-height:1.42857;margin-bottom:.571429em;margin-top:1.42857em}.chat-markdown :where(h4 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:700}.chat-markdown :where(picture):not(:where([class~=not-prose],[class~=not-prose] *)){display:block;margin-bottom:2em;margin-top:2em}.chat-markdown :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){border-radius:.3125rem;box-shadow:0 0 0 1px rgb(var(--tw-prose-kbd-shadows)/10%),0 3px 0 rgb(var(--tw-prose-kbd-shadows)/10%);color:var(--tw-prose-kbd);font-family:inherit;font-size:.875em;font-size:.857143em;font-weight:500;padding-inline-end:.375em;padding-inline-end:.357143em;padding-bottom:.142857em;padding-inline-start:.375em;padding-top:.142857em;padding-inline-start:.357143em}.chat-markdown :where(code):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-code);font-size:.875em;font-size:.857143em;font-weight:600}.chat-markdown :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):after,.chat-markdown :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:\"\\`\"}.chat-markdown :where(a code):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(h1 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.chat-markdown :where(h2 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.875em;font-size:.9em}.chat-markdown :where(h3 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.9em;font-size:.888889em}.chat-markdown :where(blockquote code):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(h4 code):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(thead th code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.chat-markdown :where(pre):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:var(--tw-prose-pre-bg);border-radius:.375rem;border-radius:.25rem;color:var(--tw-prose-pre-code);font-size:.875em;font-size:.857143em;font-weight:400;line-height:1.71429;line-height:1.66667;margin-bottom:1.66667em;margin-top:1.66667em;overflow-x:auto;padding-inline-end:1.14286em;padding-inline-end:1em;padding-bottom:.666667em;padding-inline-start:1.14286em;padding-top:.666667em;padding-inline-start:1em}.chat-markdown :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:#0000;border-radius:0;border-width:0;color:inherit;font-family:inherit;font-size:inherit;font-weight:inherit;line-height:inherit;padding:0}.chat-markdown :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):after,.chat-markdown :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:none}.chat-markdown :where(table):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.875em;font-size:.857143em;line-height:1.71429;line-height:1.5;margin-bottom:2em;margin-top:2em;table-layout:auto;width:100%}.chat-markdown :where(thead):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-color:var(--tw-prose-th-borders);border-bottom-width:1px}.chat-markdown :where(thead th):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;padding-inline-end:.571429em;padding-inline-end:1em;padding-bottom:.666667em;padding-inline-start:.571429em;padding-inline-start:1em;vertical-align:bottom}.chat-markdown :where(tbody tr):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-color:var(--tw-prose-td-borders);border-bottom-width:1px}.chat-markdown :where(tbody tr:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:0}.chat-markdown :where(tbody td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:baseline}.chat-markdown :where(tfoot):not(:where([class~=not-prose],[class~=not-prose] *)){border-top-color:var(--tw-prose-th-borders);border-top-width:1px}.chat-markdown :where(tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:top}.chat-markdown :where(th,td):not(:where([class~=not-prose],[class~=not-prose] *)){text-align:start}.chat-markdown :where(figcaption):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-captions);font-size:.875em;font-size:.857143em;line-height:1.42857;line-height:1.33333;margin-top:.666667em}.chat-markdown :where(.prose>ul>li p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.75em;margin-top:.75em}.chat-markdown :where(.prose>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.chat-markdown :where(.prose>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.chat-markdown :where(.prose>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.chat-markdown :where(.prose>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.chat-markdown :where(.prose>:first-child):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(h2+*):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(h3+*):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(h4+*):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(hr+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.chat-markdown :where(.prose>:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0}.chat-markdown :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.14286em;margin-top:1.14286em}.chat-markdown :where(img):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(picture):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.71429em;margin-top:1.71429em}.chat-markdown :where(picture>img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0;margin-top:0}.chat-markdown :where(video):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.71429em;margin-top:1.71429em}.chat-markdown :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.14286em;margin-top:1.14286em;padding-inline-start:1.57143em}.chat-markdown :where(li):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.285714em;margin-top:.285714em}.chat-markdown :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.428571em}.chat-markdown :where(.prose-sm>ul>li p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.571429em;margin-top:.571429em}.chat-markdown :where(.prose-sm>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.14286em}.chat-markdown :where(.prose-sm>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.14286em}.chat-markdown :where(.prose-sm>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.14286em}.chat-markdown :where(.prose-sm>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.14286em}.chat-markdown :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.571429em;margin-top:.571429em}.chat-markdown :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.14286em;margin-top:1.14286em}.chat-markdown :where(dd):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.285714em;padding-inline-start:1.57143em}.chat-markdown :where(h2+*):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(h3+*):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(h4+*):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(hr+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.chat-markdown :where(thead th:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.chat-markdown :where(thead th:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.chat-markdown :where(tbody td,tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:1em;padding-bottom:.666667em;padding-top:.666667em;padding-inline-start:1em}.chat-markdown :where(tbody td:first-child,tfoot td:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.chat-markdown :where(tbody td:last-child,tfoot td:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.chat-markdown :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.71429em;margin-top:1.71429em}.chat-markdown :where(figure>*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0;margin-top:0}.chat-markdown :where(.prose-sm>:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.chat-markdown :where(.prose-sm>:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0}.chat-markdown>*{margin-bottom:.1em;margin-top:.1em}.message-bubble-assistant .chat-markdown{--tw-prose-body:var(--message-assistant-text-color);--tw-prose-headings:var(--message-assistant-text-color);--tw-prose-lead:var(--message-assistant-text-color);--tw-prose-links:var(--message-assistant-link-color);--tw-prose-bold:var(--message-assistant-text-color);--tw-prose-counters:var(--message-assistant-text-color);--tw-prose-bullets:var(--message-assistant-text-color);--tw-prose-hr:var(--message-assistant-text-color);--tw-prose-quotes:var(--message-assistant-text-color);--tw-prose-quote-borders:var(--message-assistant-text-color);--tw-prose-captions:var(--message-assistant-text-color);--tw-prose-kbd:var(--message-assistant-text-color);--tw-prose-kbd-shadows:var(--message-assistant-text-color);--tw-prose-code:var(--code-text-assistant-color);--tw-prose-pre-code:var(--code-text-assistant-color);--tw-prose-pre-bg:var(--code-bg-assistant-color);--tw-prose-th-borders:var(--message-assistant-text-color);--tw-prose-td-borders:var(--message-assistant-text-color)}.message-bubble-user .chat-markdown{--tw-prose-body:var(--message-user-text-color);--tw-prose-headings:var(--message-user-text-color);--tw-prose-lead:var(--message-user-text-color);--tw-prose-links:var(--message-user-link-color);--tw-prose-bold:var(--message-user-text-color);--tw-prose-counters:var(--message-user-text-color);--tw-prose-bullets:var(--message-user-text-color);--tw-prose-hr:var(--message-user-text-color);--tw-prose-quotes:var(--message-user-text-color);--tw-prose-quote-borders:var(--message-user-text-color);--tw-prose-captions:var(--message-user-text-color);--tw-prose-kbd:var(--message-user-text-color);--tw-prose-kbd-shadows:var(--message-user-text-color);--tw-prose-code:var(--code-text-user-color);--tw-prose-pre-code:var(--code-text-user-color);--tw-prose-pre-bg:var(--code-bg-user-color);--tw-prose-th-borders:var(--message-user-text-color);--tw-prose-td-borders:var(--message-user-text-color)}.message-bubble-system .chat-markdown{--tw-prose-body:var(--message-system-text-color);--tw-prose-headings:var(--message-system-text-color);--tw-prose-lead:var(--message-system-text-color);--tw-prose-links:var(--message-system-link-color);--tw-prose-bold:var(--message-system-text-color);--tw-prose-counters:var(--message-system-text-color);--tw-prose-bullets:var(--message-system-text-color);--tw-prose-hr:var(--message-system-text-color);--tw-prose-quotes:var(--message-system-text-color);--tw-prose-quote-borders:var(--message-system-text-color);--tw-prose-captions:var(--message-system-text-color);--tw-prose-kbd:var(--message-system-text-color);--tw-prose-kbd-shadows:var(--message-system-text-color);--tw-prose-code:var(--message-system-text-color);--tw-prose-pre-code:var(--message-system-text-color);--tw-prose-pre-bg:var(--message-system-text-color);--tw-prose-th-borders:var(--message-system-text-color);--tw-prose-td-borders:var(--message-system-text-color)}.message-bubble-user .chat-markdown pre{border:1px solid var(--code-border-user-color)}.message-bubble-assistant .chat-markdown pre{border:1px solid var(--code-border-assistant-color)}.loading:after{content:\" .\"}.file-attachment-button{border-radius:var(--radius-md);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;background-color:var(--file-attachment-button-bg-color);color:var(--file-attachment-button-text-color);padding:.375em;transition-duration:.2s}.file-attachment-button:disabled{color:var(--file-attachment-button-text-disabled-color);cursor:not-allowed;opacity:.5}.file-attachment-button svg{height:1.5em;width:1.5em}.file-attachment-button:hover:not(:disabled){background-color:var(--file-attachment-button-bg-hover-color)}.selected-files-container{background-color:var(--selected-files-bg-color);border-top:1px solid var(--selected-files-border-color);padding:1em 1em .5em}.selected-file-item{align-items:center;background-color:var(--selected-file-bg-color);border-radius:.375em;color:var(--selected-file-name-color);display:flex;font-size:var(--selected-file-font-size);justify-content:space-between;padding:.25em .5em}.selected-file-icon{align-items:center;display:flex;justify-content:center}.selected-file-icon svg{height:var(--selected-file-icon-size);width:var(--selected-file-icon-size)}.selected-file-size{color:var(--selected-file-size-color)}.selected-file-error{color:var(--error-text-color)}.selected-file-success-icon{align-items:center;color:var(--success-text-color);display:flex;height:var(--selected-file-icon-size);justify-content:center;width:var(--selected-file-icon-size)}.selected-file-remove-button{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;color:var(--selected-file-remove-icon-color);padding:.375em;transition-duration:.2s}.selected-file-remove-button svg{height:var(--selected-file-icon-size);width:var(--selected-file-icon-size)}.selected-file-remove-button:hover{color:var(--selected-file-remove-icon-hover-color)}.message-attachments{font-size:var(--chat-window-font-size-sm);margin-top:.5em}:where(.message-attachments>:not(:last-child)){--tw-space-y-reverse:0;margin-block-end:calc(.25em*(1 - var(--tw-space-y-reverse)));margin-block-start:calc(.25em*var(--tw-space-y-reverse))}.message-attachment-icon{align-items:center;display:flex;height:var(--message-attachment-icon-size);justify-content:center;width:var(--message-attachment-icon-size)}.send-button-disabled{background-color:var(--send-button-bg-disabled-color);color:var(--send-button-text-disabled-color);cursor:not-allowed}@property --tw-rotate-x{syntax:\"*\";inherits:false}@property --tw-rotate-y{syntax:\"*\";inherits:false}@property --tw-rotate-z{syntax:\"*\";inherits:false}@property --tw-skew-x{syntax:\"*\";inherits:false}@property --tw-skew-y{syntax:\"*\";inherits:false}@property --tw-space-y-reverse{syntax:\"*\";inherits:false;initial-value:0}@property --tw-border-style{syntax:\"*\";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:\"*\";inherits:false}@property --tw-shadow{syntax:\"*\";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:\"*\";inherits:false}@property --tw-shadow-alpha{syntax:\"<percentage>\";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:\"*\";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:\"*\";inherits:false}@property --tw-inset-shadow-alpha{syntax:\"<percentage>\";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:\"*\";inherits:false}@property --tw-ring-shadow{syntax:\"*\";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:\"*\";inherits:false}@property --tw-inset-ring-shadow{syntax:\"*\";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:\"*\";inherits:false}@property --tw-ring-offset-width{syntax:\"<length>\";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:\"*\";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:\"*\";inherits:false;initial-value:0 0 #0000}@property --tw-duration{syntax:\"*\";inherits:false}@property --tw-ease{syntax:\"*\";inherits:false}@property --tw-scale-x{syntax:\"*\";inherits:false;initial-value:1}@property --tw-scale-y{syntax:\"*\";inherits:false;initial-value:1}@property --tw-scale-z{syntax:\"*\";inherits:false;initial-value:1}@keyframes spin{to{transform:rotate(1turn)}}@keyframes progress{0%{transform:translate(0)scaleX(0)}10%{transform:translate(0)scaleX(.3)}50%{transform:translate(100%)scaleX(.3)}90%{transform:translate(0)scaleX(.3)}to{transform:translate(0)scaleX(0)}}@keyframes dots{0%,20%{color:#0000;text-shadow:.25em 0 #0000,.5em 0 #0000}40%{color:#000;text-shadow:.25em 0 #0000,.5em 0 #0000}60%{text-shadow:.25em 0 #000,.5em 0 #0000}80%,to{text-shadow:.25em 0 #000,.5em 0 #000}}@media (min-width:40rem){.container{max-width:40rem}.chat-window-normal{height:var(--chat-window-height);width:var(--chat-window-width)}.drag-indicator{display:flex}.fullscreen-button{display:block}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}@media (hover:hover){.chat-btn-icon:hover,.chat-btn-text:hover{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x)var(--tw-scale-y)}.attachment-link:hover{text-decoration-line:none}.chat-markdown :where(a):not(:where([class~=not-prose],[class~=not-prose] *)):hover{text-decoration-line:underline}}";
|
|
6245
|
+
const ocsChatCss = "/*! tailwindcss v4.1.12 | MIT License | https://tailwindcss.com */@layer properties{@supports ((-webkit-hyphens:none) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,::backdrop,:after,:before{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-duration:initial;--tw-ease:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}@layer theme{:host,:root{--font-sans:ui-sans-serif,system-ui,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\",\"Noto Color Emoji\";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace;--color-blue-300:oklch(80.9% .105 251.813);--color-slate-500:oklch(55.4% .046 257.417);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--spacing:.25rem;--breakpoint-lg:64rem;--container-sm:24rem;--font-weight-light:300;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--animate-progress:progress 3s infinite linear;--animate-dots:dots 1s steps(5,end)infinite;--transform-origin-left-right:0% 50%}}@layer base{*,::backdrop,:after,:before{border:0 solid;border-color:var(--color-gray-200,currentcolor);box-sizing:border-box;margin:0;padding:0}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button;background-color:#0000;border:0 solid;border-color:var(--color-gray-200,currentcolor);border-radius:0;box-sizing:border-box;color:inherit;font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;margin:0;margin-inline-end:4px;opacity:1;padding:0}:host,html{-webkit-text-size-adjust:100%;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\",\"Noto Color Emoji\");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-size:1em;font-variation-settings:var(--default-mono-font-variation-settings,normal)}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}menu,ol,ul{list-style:none}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}button,input,optgroup,select,textarea{background-color:#0000;border-radius:0;color:inherit;font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;opacity:1}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::-moz-placeholder{opacity:1}::placeholder{opacity:1}@supports (not (-webkit-appearance:-apple-pay-button)) or (contain-intrinsic-size:1px){::-moz-placeholder{color:currentColor}::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::-moz-placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex;padding-block:0}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-meridiem-field,::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.visible{visibility:visible}.fixed{position:fixed}.relative{position:relative}.static{position:static}.container{width:100%}.block{display:block}.contents{display:contents}.flex{display:flex}.hidden{display:none}.w-full{width:100%}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.resize{resize:both}.items-center{align-items:center}.justify-center{justify-content:center}.gap-\\[0\\.5em\\]{gap:.5em}:where(.space-y-\\[0\\.25em\\]>:not(:last-child)){--tw-space-y-reverse:0;margin-block-end:calc(.25em*(1 - var(--tw-space-y-reverse)));margin-block-start:calc(.25em*var(--tw-space-y-reverse))}.border{border-style:var(--tw-border-style);border-width:1px}.py-\\[2px\\]{padding-block:2px}.text-\\[0\\.8em\\]{font-size:.8em}.font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.text-slate-500{color:var(--color-slate-500)}.underline{text-decoration-line:underline}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a)}.ring,.shadow{box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor)}.transition{transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,backdrop-filter,display,visibility,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function))}} /*! tailwindcss v4.1.12 | MIT License | https://tailwindcss.com */@layer properties{}@layer base{}@layer components; /*! tailwindcss v4.1.12 | MIT License | https://tailwindcss.com */@layer properties{}@layer base{}@layer components{#ocs-chat-window{font-size:var(--chat-window-font-size);z-index:var(--chat-z-index)}.starter-question{border-radius:var(--radius-lg);text-align:left;--tw-duration:.2s;background-color:var(--starter-question-bg-color);border:1px solid var(--starter-question-border-color);color:var(--starter-question-text-color);padding:.75em;transition-duration:.2s}.starter-question:hover{background-color:var(--starter-question-bg-hover-color);border-color:var(--starter-question-border-hover-color)}.chat-btn-text{border-radius:var(--radius-lg);border-style:var(--tw-border-style);transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;--tw-ease:var(--ease-in-out);align-items:center;background-color:var(--button-background-color,#fff);border-width:0;border:1px solid var(--button-border-color);color:var(--button-text-color,#111827);display:flex;font-size:var(--button-font-size);gap:8px;padding:.5em;transition-duration:.2s;transition-timing-function:var(--ease-in-out);z-index:var(--chat-z-index,50)}.chat-btn-text:hover{border:1px solid var(--button-border-color-hover);color:var(--button-text-color-hover,#1d4ed8)}.chat-btn-text span{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);white-space:nowrap}.chat-btn-text img{flex-shrink:0;height:var(--button-icon-size);-o-object-fit:contain;object-fit:contain;width:var(--button-icon-size)}.chat-btn-text.round{border-radius:3.40282e+38px}.chat-btn-icon{border-radius:var(--radius-lg);border-style:var(--tw-border-style);transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;--tw-ease:var(--ease-in-out);background-color:var(--button-background-color,#fff);border-width:0;border:1px solid var(--button-border-color);font-size:var(--button-font-size);padding:.5em;transition-duration:.2s;transition-timing-function:var(--ease-in-out);z-index:var(--chat-z-index,50)}.chat-btn-icon:hover{border:1px solid var(--button-border-color-hover);color:var(--button-text-color-hover,#1d4ed8)}.chat-btn-icon img{height:var(--button-icon-size);-o-object-fit:contain;object-fit:contain;width:var(--button-icon-size)}.chat-btn-icon.round,.round .chat-btn-icon,.round.chat-btn-text{border-radius:3.40282e+38px}.error-message{color:var(--error-text-color);padding:.5em}.chat-window-fullscreen{border-style:var(--tw-border-style);inset:calc(var(--spacing)*0);z-index:9999;--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);height:100%;max-height:100%;transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));width:100%;--tw-duration:.2s;border-radius:0;border-width:0;max-width:var(--chat-window-fullscreen-width);transition-duration:.2s}.chat-window-fullscreen,.chat-window-normal{background-color:var(--chat-window-bg-color);display:flex;flex-direction:column;overflow:hidden;position:fixed}.chat-window-normal{border:1px solid var(--chat-window-border-color);border-radius:var(--radius-lg);height:100vh;max-width:var(--breakpoint-lg);min-height:300px;min-width:300px;width:100vw}.chat-window-normal:not(.chat-window-dragging){--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;transition-duration:.2s}.chat-window-dragging,.chat-window-normal:not(.chat-window-dragging){box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.chat-window-dragging{cursor:grabbing;--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040)}.chat-window-kiosk{background-color:var(--chat-window-bg-color);border-radius:0;box-shadow:none;display:flex;flex-direction:column;font-size:var(--chat-window-font-size);height:100%;inset:calc(var(--spacing)*0);overflow:hidden;position:absolute;width:100%;z-index:var(--chat-z-index)}.chat-header{transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.15s;align-items:center;background-color:var(--header-bg-color);border-bottom:1px solid var(--header-border-color);display:flex;font-size:var(--header-font-size);justify-content:space-between;padding:.5em;transition-duration:.15s}.chat-header:active,.chat-header:hover{background-color:var(--header-bg-hover-color)}.header-text{align-items:center;color:var(--header-text-color);display:flex;font-size:var(--header-text-font-size);justify-content:center}.chat-header-draggable{cursor:grab}.chat-header-dragging{cursor:grabbing}.drag-indicator{display:none}.drag-dots{display:flex;gap:2px;margin-left:2px;pointer-events:none}.header-buttons{align-items:center;display:flex;gap:4px}.header-button{border-radius:var(--radius-md);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;color:var(--header-button-text-color);padding:.375em;transition-duration:.2s}.header-button svg{height:var(--header-button-icon-size);width:var(--header-button-icon-size)}.header-button:hover{background-color:var(--header-button-bg-hover-color)}.fullscreen-button{display:none}.chat-content{display:flex;flex-direction:column;flex-grow:1;overflow:hidden}.loading-container{align-items:center;display:flex;flex-grow:1;justify-content:center}.loading-text{color:var(--loading-text-color);margin-left:2px}.messages-container{flex-grow:1;overflow-y:auto;padding:1em}:where(.messages-container>:not(:last-child)){--tw-space-y-reverse:0;margin-block-end:calc(var(--spacing)*2*(1 - var(--tw-space-y-reverse)));margin-block-start:calc(var(--spacing)*2*var(--tw-space-y-reverse))}.message-row{display:flex}.message-row-user{justify-content:flex-end}.message-row-assistant{justify-content:flex-start}.message-bubble{border-radius:var(--radius-lg);padding:.5em 1em}.message-bubble-user{background-color:var(--message-user-bg-color);color:var(--message-user-text-color)}.message-bubble-assistant{background-color:var(--message-assistant-bg-color);color:var(--message-assistant-text-color)}.message-bubble-system{background-color:var(--message-system-bg-color);color:var(--message-system-text-color)}.message-timestamp{font-size:var(--chat-window-font-size-sm);margin-top:4px;opacity:.7}.message-attachments{margin-top:8px}:where(.message-attachments>:not(:last-child)){--tw-space-y-reverse:0;margin-block-end:calc(var(--spacing)*1*(1 - var(--tw-space-y-reverse)));margin-block-start:calc(var(--spacing)*1*var(--tw-space-y-reverse))}.attachment-link{display:block;text-decoration-line:underline}:where(.welcome-messages>:not(:last-child)){--tw-space-y-reverse:0;margin-block-end:calc(var(--spacing)*2*(1 - var(--tw-space-y-reverse)));margin-block-start:calc(var(--spacing)*2*var(--tw-space-y-reverse))}.typing-indicator{height:calc(var(--spacing)*1.5);overflow:hidden;width:100%}.typing-progress{animation:var(--animate-progress);background-color:var(--typing-progress-bg-color);border-radius:var(--radius-lg);height:100%;transform-origin:var(--transform-origin-left-right);width:100%}.typing-text{font-size:var(--chat-window-font-size-sm);justify-content:center;opacity:.7;width:100%}.typing-dots{animation:var(--animate-dots)}:where(.starter-questions>:not(:last-child)){--tw-space-y-reverse:0;margin-block-end:calc(var(--spacing)*2*(1 - var(--tw-space-y-reverse)));margin-block-start:calc(var(--spacing)*2*var(--tw-space-y-reverse))}.starter-questions{padding:1em}.starter-question-row{display:flex;justify-content:flex-end}.input-area{background-color:var(--input-bg-color);border-top:1px solid var(--input-border-color);padding:1em 1em 0}.input-container{display:flex;gap:8px}.message-textarea{background-color:var(--input-bg-color);border-color:var(--color-gray-300);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border:1px solid var(--input-border-color);color:var(--input-text-color);flex-grow:1;padding:.5em .75em;resize:none}.message-textarea:focus{outline-color:var(--input-outline-focus-color)}.send-button{border-radius:var(--radius-md);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;padding:.5em 1em;transition-duration:.2s}.send-button-enabled{background-color:var(--send-button-bg-color);color:var(--send-button-text-color)}.send-button-enabled:hover{background-color:var(--send-button-bg-hover-color)}.send-button-disabled{background-color:var(--send-button-bg-disabled-color);color:var(--send-button-text-disabled-color);cursor:not-allowed}.confirmation-overlay{align-items:center;background-color:var(--confirmation-overlay-bg-color);display:flex;inset:calc(var(--spacing)*0);justify-content:center;position:fixed;z-index:9999}.confirmation-dialog{background-color:var(--confirmation-dialog-bg-color);border:1px solid var(--confirmation-dialog-border-color);border-radius:.75em;box-shadow:0 .625em 1.5625em var(--confirmation-dialog-shadow-color);margin-inline:calc(var(--spacing)*4);max-width:var(--container-sm);width:100%}.confirmation-content{padding:1.5em}.confirmation-title{margin-bottom:calc(var(--spacing)*2);--tw-font-weight:var(--font-weight-semibold);color:var(--confirmation-title-color);font-size:var(--confirmation-title-font-size);font-weight:var(--font-weight-semibold)}.confirmation-message{color:var(--confirmation-message-color);font-size:var(--confirmation-message-font-size);margin-bottom:calc(var(--spacing)*4)}.confirmation-buttons{display:flex;gap:.75em;justify-content:flex-end}.confirmation-button{border-radius:var(--radius-md);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;padding:.5em 1em;transition-duration:.2s}.confirmation-button-cancel{background-color:var(--confirmation-button-cancel-bg-color);color:var(--confirmation-button-cancel-text-color)}.confirmation-button-cancel:hover{background-color:var(--confirmation-button-cancel-bg-hover-color)}.confirmation-button-confirm{background-color:var(--confirmation-button-confirm-bg-color);color:var(--confirmation-button-confirm-text-color)}.confirmation-button-confirm:hover{background-color:var(--confirmation-button-confirm-bg-hover-color)}}@layer utilities{:host{--chat-z-index:50;--button-background-color:#fff;--button-background-color-hover:#f3f4f6;--button-text-color:#111827;--button-text-color-hover:#1d4ed8;--button-border-color:#d1d5db;--button-border-color-hover:#6b7280;--button-font-size:1em;--button-icon-size:1.5em;--chat-window-height:60%;--chat-window-width:25%;--chat-window-fullscreen-width:80%;--chat-window-bg-color:#fff;--chat-window-border-color:#d1d5db;--chat-window-shadow-color:#0000001a;--chat-window-font-size:.875em;--chat-window-font-size-sm:.75em;--header-bg-color:transparent;--header-bg-hover-color:#f9fafb;--header-border-color:#f3f4f6;--header-button-text-color:#6b7280;--header-button-bg-hover-color:#f3f4f6;--header-font-size:1em;--header-text-font-size:1em;--header-text-color:#525762;--header-button-icon-size:1.5em;--starter-question-bg-color:transparent;--starter-question-bg-hover-color:#eff6ff;--starter-question-text-color:#3b82f6;--starter-question-border-color:#3b82f6;--starter-question-border-hover-color:#2563eb;--message-user-bg-color:#e4edfb;--message-user-text-color:#1f2937;--message-user-link-color:#155dfc;--message-assistant-bg-color:#eae7e8;--message-assistant-text-color:var(--message-user-text-color);--message-assistant-link-color:var(--message-user-link-color);--message-system-bg-color:#fbe4f8;--message-system-text-color:var(--message-user-text-color);--message-system-link-color:var(--message-user-link-color);--message-timestamp-color:#ffffffb3;--message-timestamp-assistant-color:#4b5563b3;--input-bg-color:transparent;--input-border-color:#d1d5db;--input-text-color:#111827;--input-placeholder-color:#6b7280;--input-outline-focus-color:#3b82f6;--send-button-bg-color:#3b82f6;--send-button-bg-hover-color:#2563eb;--send-button-text-color:#fff;--send-button-bg-disabled-color:#d1d5db;--send-button-text-disabled-color:#6b7280;--loading-text-color:#6b7280;--loading-spinner-track-color:#e5e7eb;--loading-spinner-fill-color:#3b82f6;--loading-spinner-size:1.25em;--typing-progress-bg-color:#ade3ff;--scrollbar-track-color:#f3f4f6;--scrollbar-thumb-color:#d1d5db;--scrollbar-thumb-hover-color:#9ca3af;--error-text-color:#ef4444;--success-text-color:#10b981;--code-bg-user-color:var(--message-user-bg-color);--code-text-user-color:var(--message-user-text-color);--code-border-user-color:var(--message-user-bg-color);--code-bg-assistant-color:var(--message-assistant-bg-color);--code-text-assistant-color:var(--message-assistant-text-color);--code-border-assistant-color:var(--message-assistant-bg-color);--confirmation-overlay-bg-color:#00000080;--confirmation-dialog-bg-color:var(--chat-window-bg-color);--confirmation-dialog-border-color:var(--chat-window-border-color);--confirmation-dialog-shadow-color:var(--chat-window-shadow-color);--confirmation-title-color:#111827;--confirmation-title-font-size:1.125em;--confirmation-message-color:var(--loading-text-color);--confirmation-message-font-size:1em;--confirmation-button-cancel-bg-color:var(--button-background-color-hover);--confirmation-button-cancel-bg-hover-color:#e5e7eb;--confirmation-button-cancel-text-color:var(--header-button-text-color);--confirmation-button-confirm-bg-color:var(--error-text-color);--confirmation-button-confirm-bg-hover-color:var(--error-text-color);--confirmation-button-confirm-text-color:var(--send-button-text-color);--file-attachment-button-bg-color:transparent;--file-attachment-button-bg-hover-color:var(--header-button-bg-hover-color);--file-attachment-button-text-color:var(--header-button-text-color);--file-attachment-button-text-disabled-color:var(--send-button-text-disabled-color);--selected-files-bg-color:var(--chat-window-bg-color);--selected-files-border-color:var(--header-border-color);--selected-file-bg-color:var(--button-background-color-hover);--selected-file-font-size:var(--chat-window-font-size-sm);--selected-file-name-color:var(--message-assistant-text-color);--selected-file-size-color:var(--input-placeholder-color);--selected-file-icon-size:1.25em;--selected-file-remove-icon-color:var(--error-text-color);--selected-file-remove-icon-hover-color:#dc2626;--message-attachment-icon-size:1em;bottom:30px;display:block;position:fixed;right:30px}@supports (color:color-mix(in lab,red,red)){:host{--code-bg-user-color:color-mix(in srgb,var(--message-user-bg-color)80%,#fff 20%);--code-border-user-color:color-mix(in srgb,var(--message-user-bg-color)90%,#000 10%);--code-bg-assistant-color:color-mix(in srgb,var(--message-assistant-bg-color)50%,#fff 50%);--code-border-assistant-color:color-mix(in srgb,var(--message-assistant-bg-color)90%,#000 10%)}}:host([mode=kiosk]){height:100%;inset:0 auto auto 0;position:absolute;width:100%}}textarea{max-height:calc(var(--spacing)*32);min-height:calc(var(--spacing)*10);overflow-y:auto;resize:none}.loading-spinner{animation:var(--animate-spin);border-color:var(--loading-spinner-track-color);border-radius:3.40282e+38px;border-style:var(--tw-border-style);border-top-color:var(--loading-spinner-fill-color);border-width:2px;height:var(--loading-spinner-size);width:var(--loading-spinner-size)}.overflow-y-auto::-webkit-scrollbar{height:calc(var(--spacing)*1.5);width:calc(var(--spacing)*1.5)}.overflow-y-auto::-webkit-scrollbar-track{background-color:var(--scrollbar-track-color);border-radius:var(--radius-sm)}.overflow-y-auto::-webkit-scrollbar-thumb{background-color:var(--scrollbar-thumb-color);border-radius:var(--radius-sm)}.overflow-y-auto::-webkit-scrollbar-thumb:hover{background-color:var(--scrollbar-thumb-hover-color)}.chat-markdown{color:var(--tw-prose-body);font-size:1rem;font-size:.875rem;line-height:1.75;line-height:1.71429;max-width:65ch;--tw-prose-body:oklch(37.3% .034 259.733);--tw-prose-headings:oklch(21% .034 264.665);--tw-prose-lead:oklch(44.6% .03 256.802);--tw-prose-links:oklch(21% .034 264.665);--tw-prose-bold:oklch(21% .034 264.665);--tw-prose-counters:oklch(55.1% .027 264.364);--tw-prose-bullets:oklch(87.2% .01 258.338);--tw-prose-hr:oklch(92.8% .006 264.531);--tw-prose-quotes:oklch(21% .034 264.665);--tw-prose-quote-borders:oklch(92.8% .006 264.531);--tw-prose-captions:oklch(55.1% .027 264.364);--tw-prose-kbd:oklch(21% .034 264.665);--tw-prose-kbd-shadows:NaN NaN NaN;--tw-prose-code:oklch(21% .034 264.665);--tw-prose-pre-code:oklch(92.8% .006 264.531);--tw-prose-pre-bg:oklch(27.8% .033 256.848);--tw-prose-th-borders:oklch(87.2% .01 258.338);--tw-prose-td-borders:oklch(92.8% .006 264.531);--tw-prose-invert-body:oklch(87.2% .01 258.338);--tw-prose-invert-headings:#fff;--tw-prose-invert-lead:oklch(70.7% .022 261.325);--tw-prose-invert-links:#fff;--tw-prose-invert-bold:#fff;--tw-prose-invert-counters:oklch(70.7% .022 261.325);--tw-prose-invert-bullets:oklch(44.6% .03 256.802);--tw-prose-invert-hr:oklch(37.3% .034 259.733);--tw-prose-invert-quotes:oklch(96.7% .003 264.542);--tw-prose-invert-quote-borders:oklch(37.3% .034 259.733);--tw-prose-invert-captions:oklch(70.7% .022 261.325);--tw-prose-invert-kbd:#fff;--tw-prose-invert-kbd-shadows:255 255 255;--tw-prose-invert-code:#fff;--tw-prose-invert-pre-code:oklch(87.2% .01 258.338);--tw-prose-invert-pre-bg:#00000080;--tw-prose-invert-th-borders:oklch(44.6% .03 256.802);--tw-prose-invert-td-borders:oklch(37.3% .034 259.733);font-size:1em;max-width:none}.chat-markdown :where([class~=lead]):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-lead);font-size:1.25em;font-size:1.28571em;line-height:1.6;line-height:1.55556;margin-bottom:.888889em;margin-top:.888889em}.chat-markdown :where(a):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-links);font-weight:500;text-decoration:underline;text-decoration-line:none}.chat-markdown :where(strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-bold);font-weight:600}.chat-markdown :where(a strong):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(blockquote strong):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(thead th strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.chat-markdown :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:decimal;margin-bottom:1.25em;margin-top:1.25em;padding-inline-start:1.625em}.chat-markdown :where(ol[type=A]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.chat-markdown :where(ol[type=a]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.chat-markdown :where(ol[type=A s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.chat-markdown :where(ol[type=a s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.chat-markdown :where(ol[type=I]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.chat-markdown :where(ol[type=i]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.chat-markdown :where(ol[type=I s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.chat-markdown :where(ol[type=i s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.chat-markdown :where(ol[type=\"1\"]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:decimal}.chat-markdown :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:disc;margin-bottom:1.25em;margin-top:1.25em;padding-inline-start:1.625em}.chat-markdown :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{color:var(--tw-prose-counters);font-weight:400}.chat-markdown :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{color:var(--tw-prose-bullets)}.chat-markdown :where(dt):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;margin-top:1.14286em}.chat-markdown :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:var(--tw-prose-hr);border-top-width:1px;margin-bottom:2.85714em;margin-top:2.85714em}.chat-markdown :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)){border-inline-start-color:var(--tw-prose-quote-borders);border-inline-start-width:.25rem;color:var(--tw-prose-quotes);font-style:italic;font-weight:500;margin-bottom:1.33333em;margin-top:1.33333em;padding-inline-start:1em;padding-inline-start:1.11111em;quotes:\"\u201C\"\"\u201D\"\"\u2018\"\"\u2019\"}.chat-markdown :where(blockquote p:first-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:open-quote}.chat-markdown :where(blockquote p:last-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:close-quote}.chat-markdown :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-size:2.25em;font-size:2.14286em;font-weight:800;line-height:1.11111;line-height:1.2;margin-bottom:.8em;margin-top:0}.chat-markdown :where(h1 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:900}.chat-markdown :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-size:1.5em;font-size:1.42857em;font-weight:700;line-height:1.33333;line-height:1.4;margin-bottom:.8em;margin-top:1.6em}.chat-markdown :where(h2 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:800}.chat-markdown :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-size:1.25em;font-size:1.28571em;font-weight:600;line-height:1.6;line-height:1.55556;margin-bottom:.444444em;margin-top:1.55556em}.chat-markdown :where(h3 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:700}.chat-markdown :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;line-height:1.5;line-height:1.42857;margin-bottom:.571429em;margin-top:1.42857em}.chat-markdown :where(h4 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:700}.chat-markdown :where(picture):not(:where([class~=not-prose],[class~=not-prose] *)){display:block;margin-bottom:2em;margin-top:2em}.chat-markdown :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){border-radius:.3125rem;box-shadow:0 0 0 1px rgb(var(--tw-prose-kbd-shadows)/10%),0 3px 0 rgb(var(--tw-prose-kbd-shadows)/10%);color:var(--tw-prose-kbd);font-family:inherit;font-size:.875em;font-size:.857143em;font-weight:500;padding-inline-end:.375em;padding-inline-end:.357143em;padding-bottom:.142857em;padding-inline-start:.375em;padding-top:.142857em;padding-inline-start:.357143em}.chat-markdown :where(code):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-code);font-size:.875em;font-size:.857143em;font-weight:600}.chat-markdown :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):after,.chat-markdown :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:\"\\`\"}.chat-markdown :where(a code):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(h1 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.chat-markdown :where(h2 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.875em;font-size:.9em}.chat-markdown :where(h3 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.9em;font-size:.888889em}.chat-markdown :where(blockquote code):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(h4 code):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(thead th code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.chat-markdown :where(pre):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:var(--tw-prose-pre-bg);border-radius:.375rem;border-radius:.25rem;color:var(--tw-prose-pre-code);font-size:.875em;font-size:.857143em;font-weight:400;line-height:1.71429;line-height:1.66667;margin-bottom:1.66667em;margin-top:1.66667em;overflow-x:auto;padding-inline-end:1.14286em;padding-inline-end:1em;padding-bottom:.666667em;padding-inline-start:1.14286em;padding-top:.666667em;padding-inline-start:1em}.chat-markdown :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:#0000;border-radius:0;border-width:0;color:inherit;font-family:inherit;font-size:inherit;font-weight:inherit;line-height:inherit;padding:0}.chat-markdown :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):after,.chat-markdown :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:none}.chat-markdown :where(table):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.875em;font-size:.857143em;line-height:1.71429;line-height:1.5;margin-bottom:2em;margin-top:2em;table-layout:auto;width:100%}.chat-markdown :where(thead):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-color:var(--tw-prose-th-borders);border-bottom-width:1px}.chat-markdown :where(thead th):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;padding-inline-end:.571429em;padding-inline-end:1em;padding-bottom:.666667em;padding-inline-start:.571429em;padding-inline-start:1em;vertical-align:bottom}.chat-markdown :where(tbody tr):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-color:var(--tw-prose-td-borders);border-bottom-width:1px}.chat-markdown :where(tbody tr:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:0}.chat-markdown :where(tbody td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:baseline}.chat-markdown :where(tfoot):not(:where([class~=not-prose],[class~=not-prose] *)){border-top-color:var(--tw-prose-th-borders);border-top-width:1px}.chat-markdown :where(tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:top}.chat-markdown :where(th,td):not(:where([class~=not-prose],[class~=not-prose] *)){text-align:start}.chat-markdown :where(figcaption):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-captions);font-size:.875em;font-size:.857143em;line-height:1.42857;line-height:1.33333;margin-top:.666667em}.chat-markdown :where(.prose>ul>li p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.75em;margin-top:.75em}.chat-markdown :where(.prose>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.chat-markdown :where(.prose>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.chat-markdown :where(.prose>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.chat-markdown :where(.prose>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.chat-markdown :where(.prose>:first-child):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(h2+*):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(h3+*):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(h4+*):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(hr+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.chat-markdown :where(.prose>:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0}.chat-markdown :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.14286em;margin-top:1.14286em}.chat-markdown :where(img):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(picture):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.71429em;margin-top:1.71429em}.chat-markdown :where(picture>img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0;margin-top:0}.chat-markdown :where(video):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.71429em;margin-top:1.71429em}.chat-markdown :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.14286em;margin-top:1.14286em;padding-inline-start:1.57143em}.chat-markdown :where(li):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.285714em;margin-top:.285714em}.chat-markdown :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.428571em}.chat-markdown :where(.prose-sm>ul>li p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.571429em;margin-top:.571429em}.chat-markdown :where(.prose-sm>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.14286em}.chat-markdown :where(.prose-sm>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.14286em}.chat-markdown :where(.prose-sm>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.14286em}.chat-markdown :where(.prose-sm>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.14286em}.chat-markdown :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.571429em;margin-top:.571429em}.chat-markdown :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.14286em;margin-top:1.14286em}.chat-markdown :where(dd):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.285714em;padding-inline-start:1.57143em}.chat-markdown :where(h2+*):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(h3+*):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(h4+*):not(:where([class~=not-prose],[class~=not-prose] *)),.chat-markdown :where(hr+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.chat-markdown :where(thead th:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.chat-markdown :where(thead th:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.chat-markdown :where(tbody td,tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:1em;padding-bottom:.666667em;padding-top:.666667em;padding-inline-start:1em}.chat-markdown :where(tbody td:first-child,tfoot td:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.chat-markdown :where(tbody td:last-child,tfoot td:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.chat-markdown :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.71429em;margin-top:1.71429em}.chat-markdown :where(figure>*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0;margin-top:0}.chat-markdown :where(.prose-sm>:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.chat-markdown :where(.prose-sm>:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0}.chat-markdown>*{margin-bottom:.1em;margin-top:.1em}.message-bubble-assistant .chat-markdown{--tw-prose-body:var(--message-assistant-text-color);--tw-prose-headings:var(--message-assistant-text-color);--tw-prose-lead:var(--message-assistant-text-color);--tw-prose-links:var(--message-assistant-link-color);--tw-prose-bold:var(--message-assistant-text-color);--tw-prose-counters:var(--message-assistant-text-color);--tw-prose-bullets:var(--message-assistant-text-color);--tw-prose-hr:var(--message-assistant-text-color);--tw-prose-quotes:var(--message-assistant-text-color);--tw-prose-quote-borders:var(--message-assistant-text-color);--tw-prose-captions:var(--message-assistant-text-color);--tw-prose-kbd:var(--message-assistant-text-color);--tw-prose-kbd-shadows:var(--message-assistant-text-color);--tw-prose-code:var(--code-text-assistant-color);--tw-prose-pre-code:var(--code-text-assistant-color);--tw-prose-pre-bg:var(--code-bg-assistant-color);--tw-prose-th-borders:var(--message-assistant-text-color);--tw-prose-td-borders:var(--message-assistant-text-color)}.message-bubble-user .chat-markdown{--tw-prose-body:var(--message-user-text-color);--tw-prose-headings:var(--message-user-text-color);--tw-prose-lead:var(--message-user-text-color);--tw-prose-links:var(--message-user-link-color);--tw-prose-bold:var(--message-user-text-color);--tw-prose-counters:var(--message-user-text-color);--tw-prose-bullets:var(--message-user-text-color);--tw-prose-hr:var(--message-user-text-color);--tw-prose-quotes:var(--message-user-text-color);--tw-prose-quote-borders:var(--message-user-text-color);--tw-prose-captions:var(--message-user-text-color);--tw-prose-kbd:var(--message-user-text-color);--tw-prose-kbd-shadows:var(--message-user-text-color);--tw-prose-code:var(--code-text-user-color);--tw-prose-pre-code:var(--code-text-user-color);--tw-prose-pre-bg:var(--code-bg-user-color);--tw-prose-th-borders:var(--message-user-text-color);--tw-prose-td-borders:var(--message-user-text-color)}.message-bubble-system .chat-markdown{--tw-prose-body:var(--message-system-text-color);--tw-prose-headings:var(--message-system-text-color);--tw-prose-lead:var(--message-system-text-color);--tw-prose-links:var(--message-system-link-color);--tw-prose-bold:var(--message-system-text-color);--tw-prose-counters:var(--message-system-text-color);--tw-prose-bullets:var(--message-system-text-color);--tw-prose-hr:var(--message-system-text-color);--tw-prose-quotes:var(--message-system-text-color);--tw-prose-quote-borders:var(--message-system-text-color);--tw-prose-captions:var(--message-system-text-color);--tw-prose-kbd:var(--message-system-text-color);--tw-prose-kbd-shadows:var(--message-system-text-color);--tw-prose-code:var(--message-system-text-color);--tw-prose-pre-code:var(--message-system-text-color);--tw-prose-pre-bg:var(--message-system-text-color);--tw-prose-th-borders:var(--message-system-text-color);--tw-prose-td-borders:var(--message-system-text-color)}.message-bubble-user .chat-markdown pre{border:1px solid var(--code-border-user-color)}.message-bubble-assistant .chat-markdown pre{border:1px solid var(--code-border-assistant-color)}.loading:after{content:\" .\"}.file-attachment-button{border-radius:var(--radius-md);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;background-color:var(--file-attachment-button-bg-color);color:var(--file-attachment-button-text-color);padding:.375em;transition-duration:.2s}.file-attachment-button:disabled{color:var(--file-attachment-button-text-disabled-color);cursor:not-allowed;opacity:.5}.file-attachment-button svg{height:1.5em;width:1.5em}.file-attachment-button:hover:not(:disabled){background-color:var(--file-attachment-button-bg-hover-color)}.selected-files-container{background-color:var(--selected-files-bg-color);border-top:1px solid var(--selected-files-border-color);padding:1em 1em .5em}.selected-file-item{align-items:center;background-color:var(--selected-file-bg-color);border-radius:.375em;color:var(--selected-file-name-color);display:flex;font-size:var(--selected-file-font-size);justify-content:space-between;padding:.25em .5em}.selected-file-icon{align-items:center;display:flex;justify-content:center}.selected-file-icon svg{height:var(--selected-file-icon-size);width:var(--selected-file-icon-size)}.selected-file-size{color:var(--selected-file-size-color)}.selected-file-error{color:var(--error-text-color)}.selected-file-success-icon{align-items:center;color:var(--success-text-color);display:flex;height:var(--selected-file-icon-size);justify-content:center;width:var(--selected-file-icon-size)}.selected-file-remove-button{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;color:var(--selected-file-remove-icon-color);padding:.375em;transition-duration:.2s}.selected-file-remove-button svg{height:var(--selected-file-icon-size);width:var(--selected-file-icon-size)}.selected-file-remove-button:hover{color:var(--selected-file-remove-icon-hover-color)}.message-attachments{font-size:var(--chat-window-font-size-sm);margin-top:.5em}:where(.message-attachments>:not(:last-child)){--tw-space-y-reverse:0;margin-block-end:calc(.25em*(1 - var(--tw-space-y-reverse)));margin-block-start:calc(.25em*var(--tw-space-y-reverse))}.message-attachment-icon{align-items:center;display:flex;height:var(--message-attachment-icon-size);justify-content:center;width:var(--message-attachment-icon-size)}.send-button-disabled{background-color:var(--send-button-bg-disabled-color);color:var(--send-button-text-disabled-color);cursor:not-allowed}@property --tw-rotate-x{syntax:\"*\";inherits:false}@property --tw-rotate-y{syntax:\"*\";inherits:false}@property --tw-rotate-z{syntax:\"*\";inherits:false}@property --tw-skew-x{syntax:\"*\";inherits:false}@property --tw-skew-y{syntax:\"*\";inherits:false}@property --tw-space-y-reverse{syntax:\"*\";inherits:false;initial-value:0}@property --tw-border-style{syntax:\"*\";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:\"*\";inherits:false}@property --tw-shadow{syntax:\"*\";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:\"*\";inherits:false}@property --tw-shadow-alpha{syntax:\"<percentage>\";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:\"*\";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:\"*\";inherits:false}@property --tw-inset-shadow-alpha{syntax:\"<percentage>\";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:\"*\";inherits:false}@property --tw-ring-shadow{syntax:\"*\";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:\"*\";inherits:false}@property --tw-inset-ring-shadow{syntax:\"*\";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:\"*\";inherits:false}@property --tw-ring-offset-width{syntax:\"<length>\";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:\"*\";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:\"*\";inherits:false;initial-value:0 0 #0000}@property --tw-duration{syntax:\"*\";inherits:false}@property --tw-ease{syntax:\"*\";inherits:false}@property --tw-scale-x{syntax:\"*\";inherits:false;initial-value:1}@property --tw-scale-y{syntax:\"*\";inherits:false;initial-value:1}@property --tw-scale-z{syntax:\"*\";inherits:false;initial-value:1}@keyframes spin{to{transform:rotate(1turn)}}@keyframes progress{0%{transform:translate(0)scaleX(0)}10%{transform:translate(0)scaleX(.3)}50%{transform:translate(100%)scaleX(.3)}90%{transform:translate(0)scaleX(.3)}to{transform:translate(0)scaleX(0)}}@keyframes dots{0%,20%{color:#0000;text-shadow:.25em 0 #0000,.5em 0 #0000}40%{color:#000;text-shadow:.25em 0 #0000,.5em 0 #0000}60%{text-shadow:.25em 0 #000,.5em 0 #0000}80%,to{text-shadow:.25em 0 #000,.5em 0 #000}}@media (min-width:40rem){.container{max-width:40rem}.chat-window-normal{height:var(--chat-window-height);width:var(--chat-window-width)}.drag-indicator{display:flex}.fullscreen-button{display:block}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}@media (hover:hover){.chat-btn-icon:hover,.chat-btn-text:hover{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x)var(--tw-scale-y)}.attachment-link:hover{text-decoration-line:none}.chat-markdown :where(a):not(:where([class~=not-prose],[class~=not-prose] *)):hover{text-decoration-line:underline}}";
|
|
5391
6246
|
|
|
5392
6247
|
const OcsChat = class {
|
|
5393
6248
|
constructor(hostRef) {
|
|
@@ -5453,6 +6308,7 @@ const OcsChat = class {
|
|
|
5453
6308
|
this.parsedStarterQuestions = [];
|
|
5454
6309
|
this.isFullscreen = false;
|
|
5455
6310
|
this.showNewChatConfirmation = false;
|
|
6311
|
+
this.sessionEnded = false;
|
|
5456
6312
|
this.selectedFiles = [];
|
|
5457
6313
|
this.isUploadingFiles = false;
|
|
5458
6314
|
this.buttonPosition = { x: 30, y: 30 };
|
|
@@ -5678,6 +6534,23 @@ const OcsChat = class {
|
|
|
5678
6534
|
this.removeButtonEventListeners();
|
|
5679
6535
|
window.removeEventListener('resize', this.handleWindowResize);
|
|
5680
6536
|
}
|
|
6537
|
+
// ---------------------------------------------------------------------------
|
|
6538
|
+
// Public event API
|
|
6539
|
+
// ---------------------------------------------------------------------------
|
|
6540
|
+
/**
|
|
6541
|
+
* Dispatch a composed, bubbling CustomEvent on the host element so that
|
|
6542
|
+
* embedders can listen with plain addEventListener outside the shadow root.
|
|
6543
|
+
*
|
|
6544
|
+
* All events are dispatched synchronously so that handlers can update
|
|
6545
|
+
* reactive props (e.g. `pageContext`) before the calling code reads them.
|
|
6546
|
+
*/
|
|
6547
|
+
dispatchWidgetEvent(name, detail = null) {
|
|
6548
|
+
this.host.dispatchEvent(new CustomEvent(name, {
|
|
6549
|
+
bubbles: true,
|
|
6550
|
+
composed: true,
|
|
6551
|
+
detail,
|
|
6552
|
+
}));
|
|
6553
|
+
}
|
|
5681
6554
|
applySessionToken(token) {
|
|
5682
6555
|
var _a;
|
|
5683
6556
|
this.currentSessionToken = token;
|
|
@@ -5729,6 +6602,38 @@ const OcsChat = class {
|
|
|
5729
6602
|
this.clearSessionStorage();
|
|
5730
6603
|
this.addErrorMessage(this.translationManager.get('status.sessionExpired', 'Your chat session expired. Starting a new chat — please resend your message.'));
|
|
5731
6604
|
}
|
|
6605
|
+
/**
|
|
6606
|
+
* The server reported the session has ended (e.g. closed from another tab or
|
|
6607
|
+
* by the bot). Polling has already stopped; disable the composer and tell the
|
|
6608
|
+
* user. Unbound widgets can recover via the "new chat" button (clearSession).
|
|
6609
|
+
*/
|
|
6610
|
+
handleSessionEnded() {
|
|
6611
|
+
var _a;
|
|
6612
|
+
if (this.sessionEnded) {
|
|
6613
|
+
return;
|
|
6614
|
+
}
|
|
6615
|
+
this.sessionEnded = true;
|
|
6616
|
+
this.dispatchWidgetEvent('ocs:session:ended', { sessionId: this.activeSessionId });
|
|
6617
|
+
this.stopMessagePolling();
|
|
6618
|
+
this.isTyping = false;
|
|
6619
|
+
this.typingProgressMessage = '';
|
|
6620
|
+
const content = (_a = this.translationManager.get('status.chatEnded')) !== null && _a !== void 0 ? _a : 'This chat has ended.';
|
|
6621
|
+
// An unbound widget restores persisted messages and re-polls after a reload,
|
|
6622
|
+
// so the notice may already be the last message.
|
|
6623
|
+
const last = this.messages.at(-1);
|
|
6624
|
+
if ((last === null || last === void 0 ? void 0 : last.role) === 'system' && last.content === content) {
|
|
6625
|
+
return;
|
|
6626
|
+
}
|
|
6627
|
+
const notice = {
|
|
6628
|
+
created_at: new Date().toISOString(),
|
|
6629
|
+
role: 'system',
|
|
6630
|
+
content,
|
|
6631
|
+
attachments: [],
|
|
6632
|
+
};
|
|
6633
|
+
this.messages = [...this.messages, notice];
|
|
6634
|
+
this.saveSessionToStorage();
|
|
6635
|
+
this.scrollToBottom();
|
|
6636
|
+
}
|
|
5732
6637
|
handleError(errorText) {
|
|
5733
6638
|
// show as system message
|
|
5734
6639
|
this.addErrorMessage(errorText);
|
|
@@ -5827,6 +6732,7 @@ const OcsChat = class {
|
|
|
5827
6732
|
this.activeSessionId = data.session_id;
|
|
5828
6733
|
this.applySessionToken((_a = data.session_token) !== null && _a !== void 0 ? _a : undefined);
|
|
5829
6734
|
this.saveSessionToStorage();
|
|
6735
|
+
this.dispatchWidgetEvent('ocs:session:started', { sessionId: this.activeSessionId });
|
|
5830
6736
|
this.startMessagePolling();
|
|
5831
6737
|
}
|
|
5832
6738
|
catch (_error) {
|
|
@@ -5877,7 +6783,7 @@ const OcsChat = class {
|
|
|
5877
6783
|
sessionId: this.activeSessionId,
|
|
5878
6784
|
participantId: this.getOrGenerateUserId(),
|
|
5879
6785
|
participantName: this.userName,
|
|
5880
|
-
|
|
6786
|
+
headers: this.getChatService().getUploadHeaders(),
|
|
5881
6787
|
});
|
|
5882
6788
|
this.selectedFiles = uploadResult.selectedFiles;
|
|
5883
6789
|
if (uploadResult.tokenRejected) {
|
|
@@ -5890,7 +6796,8 @@ const OcsChat = class {
|
|
|
5890
6796
|
}
|
|
5891
6797
|
}
|
|
5892
6798
|
async sendMessage(message) {
|
|
5893
|
-
|
|
6799
|
+
var _a;
|
|
6800
|
+
if (!message.trim() || this.sessionEnded)
|
|
5894
6801
|
return;
|
|
5895
6802
|
const epoch = this.sessionEpoch;
|
|
5896
6803
|
// Start session if we don't have one yet
|
|
@@ -5952,6 +6859,12 @@ const OcsChat = class {
|
|
|
5952
6859
|
this.selectedFiles = []; // Clear selected files after sending
|
|
5953
6860
|
}
|
|
5954
6861
|
this.scrollToBottom();
|
|
6862
|
+
// Fire before-send first so handlers can update pageContext synchronously
|
|
6863
|
+
// before we read internalPageContext into the request body.
|
|
6864
|
+
this.dispatchWidgetEvent('ocs:message:before-send', {
|
|
6865
|
+
message: message.trim(),
|
|
6866
|
+
sessionId: (_a = this.activeSessionId) !== null && _a !== void 0 ? _a : '',
|
|
6867
|
+
});
|
|
5955
6868
|
const requestBody = { message: message.trim() };
|
|
5956
6869
|
if (this.allowAttachments && attachmentIds.length > 0) {
|
|
5957
6870
|
requestBody.attachment_ids = attachmentIds;
|
|
@@ -5969,6 +6882,10 @@ const OcsChat = class {
|
|
|
5969
6882
|
throw new Error(data.error || 'Failed to send message');
|
|
5970
6883
|
}
|
|
5971
6884
|
this.internalPageContext = undefined;
|
|
6885
|
+
this.dispatchWidgetEvent('ocs:message:sent', {
|
|
6886
|
+
message: message.trim(),
|
|
6887
|
+
sessionId: this.activeSessionId,
|
|
6888
|
+
});
|
|
5972
6889
|
this.startTaskPolling(data.task_id);
|
|
5973
6890
|
}
|
|
5974
6891
|
catch (error) {
|
|
@@ -6084,6 +7001,7 @@ const OcsChat = class {
|
|
|
6084
7001
|
return;
|
|
6085
7002
|
}
|
|
6086
7003
|
this.saveVisibleState(visible);
|
|
7004
|
+
this.dispatchWidgetEvent(visible ? 'ocs:open' : 'ocs:close');
|
|
6087
7005
|
if (this.isButtonDragging) {
|
|
6088
7006
|
this.isButtonDragging = false;
|
|
6089
7007
|
this.buttonWasDragged = false;
|
|
@@ -6120,8 +7038,13 @@ const OcsChat = class {
|
|
|
6120
7038
|
}
|
|
6121
7039
|
this.taskPollingHandle = this.getChatService().pollTask(this.activeSessionId, taskId, {
|
|
6122
7040
|
onMessage: message => {
|
|
7041
|
+
var _a;
|
|
6123
7042
|
this.messages = [...this.messages, message];
|
|
6124
7043
|
this.saveSessionToStorage();
|
|
7044
|
+
this.dispatchWidgetEvent('ocs:message:received', {
|
|
7045
|
+
message: Object.assign({}, message),
|
|
7046
|
+
sessionId: (_a = this.activeSessionId) !== null && _a !== void 0 ? _a : '',
|
|
7047
|
+
});
|
|
6125
7048
|
this.scrollToBottom();
|
|
6126
7049
|
this.isTyping = false;
|
|
6127
7050
|
this.typingProgressMessage = '';
|
|
@@ -6163,7 +7086,7 @@ const OcsChat = class {
|
|
|
6163
7086
|
});
|
|
6164
7087
|
}
|
|
6165
7088
|
startMessagePolling() {
|
|
6166
|
-
if (!this.activeSessionId || this.currentPollTaskId || !this.visible) {
|
|
7089
|
+
if (!this.activeSessionId || this.currentPollTaskId || !this.visible || this.sessionEnded) {
|
|
6167
7090
|
return;
|
|
6168
7091
|
}
|
|
6169
7092
|
if (this.messagePollingHandle) {
|
|
@@ -6172,13 +7095,26 @@ const OcsChat = class {
|
|
|
6172
7095
|
this.messagePollingHandle = this.getChatService().startMessagePolling(this.activeSessionId, {
|
|
6173
7096
|
getSince: () => { var _a; return (this.messages.length > 0 ? (_a = this.messages.at(-1)) === null || _a === void 0 ? void 0 : _a.created_at : undefined); },
|
|
6174
7097
|
onMessages: messages => {
|
|
7098
|
+
var _a;
|
|
6175
7099
|
if (messages.length === 0)
|
|
6176
7100
|
return;
|
|
6177
7101
|
this.messages = [...this.messages, ...messages];
|
|
6178
7102
|
this.saveSessionToStorage();
|
|
7103
|
+
for (const message of messages) {
|
|
7104
|
+
if (message.role !== 'user') {
|
|
7105
|
+
this.dispatchWidgetEvent('ocs:message:received', {
|
|
7106
|
+
message: Object.assign({}, message),
|
|
7107
|
+
sessionId: (_a = this.activeSessionId) !== null && _a !== void 0 ? _a : '',
|
|
7108
|
+
});
|
|
7109
|
+
}
|
|
7110
|
+
}
|
|
6179
7111
|
this.scrollToBottom();
|
|
6180
7112
|
this.focusInput();
|
|
6181
7113
|
},
|
|
7114
|
+
onSessionEnded: () => {
|
|
7115
|
+
this.messagePollingHandle = undefined;
|
|
7116
|
+
this.handleSessionEnded();
|
|
7117
|
+
},
|
|
6182
7118
|
onError: () => {
|
|
6183
7119
|
// Silently ignore polling errors to match previous behaviour
|
|
6184
7120
|
},
|
|
@@ -6617,7 +7553,9 @@ const OcsChat = class {
|
|
|
6617
7553
|
return newUserId;
|
|
6618
7554
|
}
|
|
6619
7555
|
saveVisibleState(visible) {
|
|
6620
|
-
|
|
7556
|
+
// Kiosk visibility is forced, so persisting it would only leak into a
|
|
7557
|
+
// standard-mode widget for the same chatbot on another page.
|
|
7558
|
+
if (!this.persistentSession || this.isKioskMode())
|
|
6621
7559
|
return;
|
|
6622
7560
|
try {
|
|
6623
7561
|
const keys = this.getStorageKeys();
|
|
@@ -6691,6 +7629,7 @@ const OcsChat = class {
|
|
|
6691
7629
|
this.applySessionToken(this.isSessionBound() ? this.sessionToken : undefined);
|
|
6692
7630
|
this.messages = [];
|
|
6693
7631
|
this.isTyping = false;
|
|
7632
|
+
this.sessionEnded = false;
|
|
6694
7633
|
this.currentPollTaskId = '';
|
|
6695
7634
|
if (this.allowAttachments) {
|
|
6696
7635
|
this.selectedFiles = [];
|
|
@@ -6712,12 +7651,12 @@ const OcsChat = class {
|
|
|
6712
7651
|
if (this.error && !this.activeSessionId) {
|
|
6713
7652
|
return (h(Host, null, h("p", { class: "error-message" }, this.error)));
|
|
6714
7653
|
}
|
|
6715
|
-
return (h(Host, null, this.renderButton(), this.visible && (h("div", { ref: el => (this.chatWindowRef = el), id: "ocs-chat-window", class: this.getPositionClasses(), style: this.getPositionStyles() }, !this.isKioskMode() && (h("div", { class: `chat-header ${this.isDragging ? 'chat-header-dragging' : 'chat-header-draggable'}`, onMouseDown: this.handleMouseDown, onTouchStart: this.handleTouchStart }, h("div", { class: "drag-indicator" }, h("div", { class: "drag-dots header-button" }, h(GripDotsVerticalIcon, null))), h("div", { class: "header-text" }, this.translationManager.get('branding.headerText', this.headerText)), h("div", { class: "header-buttons" }, this.messages.length > 0 && !this.isSessionBound() && (h("button", { class: "header-button", onClick: () => this.showConfirmationDialog(), title: this.translationManager.get('window.newChat'), "aria-label": this.translationManager.get('window.newChat') }, h(PlusWithCircleIcon, null))), this.allowFullScreen && (h("button", { class: "header-button fullscreen-button", onClick: () => this.toggleFullscreen(), title: this.isFullscreen ? this.translationManager.get('window.exitFullscreen') : this.translationManager.get('window.fullscreen'), "aria-label": this.isFullscreen ? this.translationManager.get('window.exitFullscreen') : this.translationManager.get('window.fullscreen') }, this.isFullscreen ? h(ArrowsPointingInIcon, null) : h(ArrowsPointingOutIcon, null))), h("button", { class: "header-button", onClick: () => (this.visible = false), "aria-label": this.translationManager.get('window.close') }, h(XMarkIcon, null))))), !this.isKioskMode() && this.showNewChatConfirmation && (h("div", { class: "confirmation-overlay" }, h("div", { class: "confirmation-dialog" }, h("div", { class: "confirmation-content" }, h("h3", { class: "confirmation-title" }, this.translationManager.get('modal.newChatTitle')), h("p", { class: "confirmation-message" }, this.translationManager.get('modal.newChatBody', this.newChatConfirmationMessage)), h("div", { class: "confirmation-buttons" }, h("button", { class: "confirmation-button confirmation-button-cancel", onClick: () => this.hideConfirmationDialog() }, this.translationManager.get('modal.cancel')), h("button", { class: "confirmation-button confirmation-button-confirm", onClick: () => this.confirmNewChat() }, this.translationManager.get('modal.confirm'))))))), h("div", { class: "chat-content" }, this.isLoading && !this.activeSessionId && (h("div", { class: "loading-container" }, h("div", { class: "loading-spinner" }), h("span", { class: "loading-text" }, this.translationManager.get('status.starting')))), h("div", { ref: el => (this.messageListRef = el), class: "messages-container" }, this.messages.length === 0 && this.getWelcomeMessages().length > 0 && (h("div", { class: "welcome-messages" }, this.getWelcomeMessages().map((message, index) => (h("div", { key: `welcome-${index}`, class: "message-row message-row-assistant" }, h("div", { class: "message-bubble message-bubble-assistant" }, h("div", { class: "chat-markdown", innerHTML: renderMarkdownSync(message) }))))))), this.messages.map((message, index) => (h("div", { key: index, class: `message-row ${message.role === 'user' ? 'message-row-user' : 'message-row-assistant'}` }, h("div", { class: `message-bubble ${message.role === 'user' ? 'message-bubble-user' : message.role === 'assistant' ? 'message-bubble-assistant' : 'message-bubble-system'}` }, h("div", { class: "chat-markdown", innerHTML: renderMarkdownSync(message.content) }), message.attachments && message.attachments.length > 0 && (h("div", { class: "message-attachments" }, message.attachments.map((attachment, attachmentIndex) => (h("div", { key: attachmentIndex, class: "flex items-center gap-[0.5em]" }, h("span", { class: "message-attachment-icon" }, h(PaperClipIcon, null)), h("span", { class: "message-attachment-name" }, attachment.name)))))), h("div", { class: "message-timestamp" }, this.formatTime(message.created_at)))))), this.isTyping && (h("div", null, h("div", { class: "typing-indicator" }, h("div", { class: "typing-progress" })), h("div", { class: "typing-text" }, h("span", null, this.typingProgressMessage || this.translationManager.get('status.typing', this.typingIndicatorText)), h("span", { class: "typing-dots loading" }))))), this.messages.length === 0 && this.getStarterQuestions().length > 0 && (h("div", { class: "starter-questions" }, this.getStarterQuestions().map((question, index) => (h("div", { key: `starter-${index}`, class: "starter-question-row" }, h("button", { class: "starter-question", onClick: () => this.handleStarterQuestionClick(question) }, question)))))), this.allowAttachments && this.selectedFiles.length > 0 && (h("div", { class: "selected-files-container" }, h("div", { class: "space-y-[0.25em]" }, this.selectedFiles.map((selectedFile, index) => (h("div", { key: index, class: "selected-file-item" }, h("div", { class: "flex items-center gap-[0.5em]" }, h("span", { class: "selected-file-icon" }, h(PaperClipIcon, null)), h("span", null, selectedFile.file.name), h("span", { class: "selected-file-size" }, "(", this.formatFileSize(selectedFile.file.size), ")"), selectedFile.error && h("span", { class: "selected-file-error" }, selectedFile.error), selectedFile.uploaded && (h("span", { class: "selected-file-success-icon" }, h(CheckDocumentIcon, null)))), h("button", { onClick: () => this.removeSelectedFile(index), class: "selected-file-remove-button", "aria-label": this.translationManager.get('attach.remove') }, h(XIcon, null)))))))), h("div", { class: "input-area" }, h("div", { class: "input-container" }, h("textarea", { ref: el => (this.textareaRef = el), class: "message-textarea", rows: 1, placeholder: this.translationManager.get('composer.placeholder'), value: this.messageInput, onInput: e => this.handleInputChange(e), onKeyPress: e => this.handleKeyPress(e), disabled: this.isTyping || this.isUploadingFiles || this.isLoading }), this.allowAttachments && (h("input", { ref: el => {
|
|
7654
|
+
return (h(Host, null, this.renderButton(), this.visible && (h("div", { ref: el => (this.chatWindowRef = el), id: "ocs-chat-window", class: this.getPositionClasses(), style: this.getPositionStyles() }, !this.isKioskMode() && (h("div", { class: `chat-header ${this.isDragging ? 'chat-header-dragging' : 'chat-header-draggable'}`, onMouseDown: this.handleMouseDown, onTouchStart: this.handleTouchStart }, h("div", { class: "drag-indicator" }, h("div", { class: "drag-dots header-button" }, h(GripDotsVerticalIcon, null))), h("div", { class: "header-text" }, this.translationManager.get('branding.headerText', this.headerText)), h("div", { class: "header-buttons" }, this.messages.length > 0 && !this.isSessionBound() && (h("button", { class: "header-button", onClick: () => this.showConfirmationDialog(), title: this.translationManager.get('window.newChat'), "aria-label": this.translationManager.get('window.newChat') }, h(PlusWithCircleIcon, null))), this.allowFullScreen && (h("button", { class: "header-button fullscreen-button", onClick: () => this.toggleFullscreen(), title: this.isFullscreen ? this.translationManager.get('window.exitFullscreen') : this.translationManager.get('window.fullscreen'), "aria-label": this.isFullscreen ? this.translationManager.get('window.exitFullscreen') : this.translationManager.get('window.fullscreen') }, this.isFullscreen ? h(ArrowsPointingInIcon, null) : h(ArrowsPointingOutIcon, null))), h("button", { class: "header-button", onClick: () => (this.visible = false), "aria-label": this.translationManager.get('window.close') }, h(XMarkIcon, null))))), !this.isKioskMode() && this.showNewChatConfirmation && (h("div", { class: "confirmation-overlay" }, h("div", { class: "confirmation-dialog" }, h("div", { class: "confirmation-content" }, h("h3", { class: "confirmation-title" }, this.translationManager.get('modal.newChatTitle')), h("p", { class: "confirmation-message" }, this.translationManager.get('modal.newChatBody', this.newChatConfirmationMessage)), h("div", { class: "confirmation-buttons" }, h("button", { class: "confirmation-button confirmation-button-cancel", onClick: () => this.hideConfirmationDialog() }, this.translationManager.get('modal.cancel')), h("button", { class: "confirmation-button confirmation-button-confirm", onClick: () => this.confirmNewChat() }, this.translationManager.get('modal.confirm'))))))), h("div", { class: "chat-content" }, this.isLoading && !this.activeSessionId && (h("div", { class: "loading-container" }, h("div", { class: "loading-spinner" }), h("span", { class: "loading-text" }, this.translationManager.get('status.starting')))), h("div", { ref: el => (this.messageListRef = el), class: "messages-container" }, this.messages.length === 0 && this.getWelcomeMessages().length > 0 && (h("div", { class: "welcome-messages" }, this.getWelcomeMessages().map((message, index) => (h("div", { key: `welcome-${index}`, class: "message-row message-row-assistant" }, h("div", { class: "message-bubble message-bubble-assistant" }, h("div", { class: "chat-markdown", innerHTML: renderMarkdownSync(message) }))))))), this.messages.map((message, index) => (h("div", { key: index, class: `message-row ${message.role === 'user' ? 'message-row-user' : 'message-row-assistant'}` }, h("div", { class: `message-bubble ${message.role === 'user' ? 'message-bubble-user' : message.role === 'assistant' ? 'message-bubble-assistant' : 'message-bubble-system'}` }, h("div", { class: "chat-markdown", innerHTML: renderMarkdownSync(message.content) }), message.attachments && message.attachments.length > 0 && (h("div", { class: "message-attachments" }, message.attachments.map((attachment, attachmentIndex) => (h("div", { key: attachmentIndex, class: "flex items-center gap-[0.5em]" }, h("span", { class: "message-attachment-icon" }, h(PaperClipIcon, null)), h("span", { class: "message-attachment-name" }, attachment.name)))))), h("div", { class: "message-timestamp" }, this.formatTime(message.created_at)))))), this.isTyping && (h("div", null, h("div", { class: "typing-indicator" }, h("div", { class: "typing-progress" })), h("div", { class: "typing-text" }, h("span", null, this.typingProgressMessage || this.translationManager.get('status.typing', this.typingIndicatorText)), h("span", { class: "typing-dots loading" }))))), this.messages.length === 0 && this.getStarterQuestions().length > 0 && (h("div", { class: "starter-questions" }, this.getStarterQuestions().map((question, index) => (h("div", { key: `starter-${index}`, class: "starter-question-row" }, h("button", { class: "starter-question", onClick: () => this.handleStarterQuestionClick(question) }, question)))))), this.allowAttachments && this.selectedFiles.length > 0 && (h("div", { class: "selected-files-container" }, h("div", { class: "space-y-[0.25em]" }, this.selectedFiles.map((selectedFile, index) => (h("div", { key: index, class: "selected-file-item" }, h("div", { class: "flex items-center gap-[0.5em]" }, h("span", { class: "selected-file-icon" }, h(PaperClipIcon, null)), h("span", null, selectedFile.file.name), h("span", { class: "selected-file-size" }, "(", this.formatFileSize(selectedFile.file.size), ")"), selectedFile.error && h("span", { class: "selected-file-error" }, selectedFile.error), selectedFile.uploaded && (h("span", { class: "selected-file-success-icon" }, h(CheckDocumentIcon, null)))), h("button", { onClick: () => this.removeSelectedFile(index), class: "selected-file-remove-button", "aria-label": this.translationManager.get('attach.remove') }, h(XIcon, null)))))))), h("div", { class: "input-area" }, h("div", { class: "input-container" }, h("textarea", { ref: el => (this.textareaRef = el), class: "message-textarea", rows: 1, placeholder: this.sessionEnded ? this.translationManager.get('status.chatEnded') : this.translationManager.get('composer.placeholder'), value: this.messageInput, onInput: e => this.handleInputChange(e), onKeyPress: e => this.handleKeyPress(e), disabled: this.isTyping || this.isUploadingFiles || this.isLoading || this.sessionEnded }), this.allowAttachments && (h("input", { ref: el => {
|
|
6716
7655
|
// Unclear why but after removing all attachments this is being set to `null`.
|
|
6717
7656
|
if (el) {
|
|
6718
7657
|
this.fileInputRef = el;
|
|
6719
7658
|
}
|
|
6720
|
-
}, id: "ocs-file-input", type: "file", multiple: true, accept: OcsChat.SUPPORTED_FILE_EXTENSIONS.join(',') + ',text/*', onChange: e => this.handleFileSelect(e), class: "hidden" })), this.allowAttachments && (h("button", { class: "file-attachment-button", onClick: () => { var _a; return (_a = this.fileInputRef) === null || _a === void 0 ? void 0 : _a.click(); }, disabled: this.isTyping || this.isUploadingFiles || this.isLoading, title: this.translationManager.get('attach.add'), "aria-label": this.translationManager.get('attach.add') }, h(PaperClipIcon, null))), h("button", { class: `send-button ${!this.isTyping && !this.isLoading && !!this.messageInput.trim() ? 'send-button-enabled' : 'send-button-disabled'}`, onClick: () => this.sendMessage(this.messageInput), disabled: this.isTyping || this.isUploadingFiles || this.isLoading || !this.messageInput.trim() }, this.isUploadingFiles ? `${this.translationManager.get('status.uploading')}...` : this.translationManager.get('composer.send')))), h("div", { class: "flex items-center justify-center text-[0.8em] font-light w-full text-slate-500 py-[2px]" }, h("p", null, this.translationManager.get('branding.poweredBy'), ' ', h("a", { class: "underline", href: "https://www.dimagi.com", target: "_blank", rel: "noopener noreferrer" }, "Dimagi"))))))));
|
|
7659
|
+
}, id: "ocs-file-input", type: "file", multiple: true, accept: OcsChat.SUPPORTED_FILE_EXTENSIONS.join(',') + ',text/*', onChange: e => this.handleFileSelect(e), class: "hidden" })), this.allowAttachments && (h("button", { class: "file-attachment-button", onClick: () => { var _a; return (_a = this.fileInputRef) === null || _a === void 0 ? void 0 : _a.click(); }, disabled: this.isTyping || this.isUploadingFiles || this.isLoading || this.sessionEnded, title: this.translationManager.get('attach.add'), "aria-label": this.translationManager.get('attach.add') }, h(PaperClipIcon, null))), h("button", { class: `send-button ${!this.isTyping && !this.isLoading && !this.sessionEnded && !!this.messageInput.trim() ? 'send-button-enabled' : 'send-button-disabled'}`, onClick: () => this.sendMessage(this.messageInput), disabled: this.isTyping || this.isUploadingFiles || this.isLoading || this.sessionEnded || !this.messageInput.trim() }, this.isUploadingFiles ? `${this.translationManager.get('status.uploading')}...` : this.translationManager.get('composer.send')))), h("div", { class: "flex items-center justify-center text-[0.8em] font-light w-full text-slate-500 py-[2px]" }, h("p", null, this.translationManager.get('branding.poweredBy'), ' ', h("a", { class: "underline", href: "https://www.dimagi.com", target: "_blank", rel: "noopener noreferrer" }, "Dimagi"))))))));
|
|
6721
7660
|
}
|
|
6722
7661
|
get host() { return getElement(this); }
|
|
6723
7662
|
static get watchers() { return {
|