From 17b91e51c343ad68deb1c31118d516866c1f2a3e Mon Sep 17 00:00:00 2001 From: Yue Yang Date: Thu, 19 May 2022 10:18:43 +0800 Subject: [PATCH] feat: custom theme switching (#4741) * feat: custom theme switching Signed-off-by: Yue Yang * chore: handle reset-custom-theme Signed-off-by: Yue Yang * fix: unregisterTheme Signed-off-by: Yue Yang * fix: format with ts-standard Signed-off-by: Yue Yang * chore: reset extra formats Signed-off-by: Yue Yang * chore: simplify array types Signed-off-by: Yue Yang * Update lsplugin.core.js * fix: sync upstream Signed-off-by: Yue Yang * chore: add `no mode` themes into panel Signed-off-by: Yue Yang * fix: ci Signed-off-by: Yue Yang * fix: remove first
Signed-off-by: Yue Yang * fix: lockfile Signed-off-by: Yue Yang * chore: update Signed-off-by: Yue Yang * chore: set-theme! => set-theme-mode! Signed-off-by: Yue Yang * fix: selectTheme Signed-off-by: Yue Yang * perf: reduce unnecessary logic Signed-off-by: Yue Yang --- libs/src/LSPlugin.core.ts | 163 +++++++++++++++------- libs/src/LSPlugin.ts | 53 ++++--- libs/src/LSPlugin.user.ts | 7 +- libs/src/helpers.ts | 15 +- resources/js/lsplugin.core.js | 2 +- src/main/frontend/components/plugins.cljs | 117 ++++++++++------ src/main/frontend/components/plugins.css | 11 +- src/main/frontend/components/theme.cljs | 1 + src/main/frontend/handler/plugin.cljs | 24 ++-- src/main/frontend/state.cljs | 44 +++--- src/main/frontend/ui.cljs | 6 + src/main/logseq/api.cljs | 2 +- 12 files changed, 281 insertions(+), 164 deletions(-) diff --git a/libs/src/LSPlugin.core.ts b/libs/src/LSPlugin.core.ts index f0a3425bd8c..69ece824b99 100644 --- a/libs/src/LSPlugin.core.ts +++ b/libs/src/LSPlugin.core.ts @@ -3,7 +3,6 @@ import { deepMerge, setupInjectedStyle, genID, - setupInjectedTheme, setupInjectedUI, deferred, invokeHostExportedApi, @@ -19,6 +18,7 @@ import { IS_DEV, cleanInjectedScripts, safeSnakeCase, + injectTheme, } from './helpers' import * as pluginHelpers from './helpers' import Debug from 'debug' @@ -34,11 +34,13 @@ import { } from './LSPlugin.caller' import { ILSPluginThemeManager, + LegacyTheme, LSPluginPkgConfig, SettingSchemaDesc, StyleOptions, StyleString, - ThemeOptions, + Theme, + ThemeMode, UIContainerAttrs, UIOptions, } from './LSPlugin' @@ -173,10 +175,13 @@ class PluginLogger extends EventEmitter<'change'> { } interface UserPreferences { - theme: ThemeOptions + theme: LegacyTheme + themes: { + mode: ThemeMode + light: Theme + dark: Theme + } externals: string[] // external plugin locations - - [key: string]: any } interface PluginLocalOptions { @@ -310,7 +315,7 @@ function initProviderHandlers(pluginLocal: PluginLocal) { let themed = false // provider:theme - pluginLocal.on(_('theme'), (theme: ThemeOptions) => { + pluginLocal.on(_('theme'), (theme: Theme) => { pluginLocal.themeMgr.registerTheme(pluginLocal.id, theme) if (!themed) { @@ -697,7 +702,7 @@ class PluginLocal extends EventEmitter< this._options.entry = entry } - async _loadConfigThemes(themes: ThemeOptions[]) { + async _loadConfigThemes(themes: Theme[]) { themes.forEach((options) => { if (!options.url) return @@ -1123,6 +1128,7 @@ class LSPluginCore | 'unregistered' | 'theme-changed' | 'theme-selected' + | 'reset-custom-theme' | 'settings-changed' | 'unlink-plugin' | 'beforereload' @@ -1133,19 +1139,24 @@ class LSPluginCore private _isRegistering = false private _readyIndicator?: DeferredActor private readonly _hostMountedActor: DeferredActor = deferred() - private readonly _userPreferences: Partial = {} - private readonly _registeredThemes = new Map< - PluginLocalIdentity, - ThemeOptions[] - >() + private readonly _userPreferences: UserPreferences = { + theme: null, + themes: { + mode: 'light', + light: null, + dark: null, + }, + externals: [], + } + private readonly _registeredThemes = new Map() private readonly _registeredPlugins = new Map< PluginLocalIdentity, PluginLocal >() private _currentTheme: { - dis: () => void pid: PluginLocalIdentity - opt: ThemeOptions + opt: Theme | LegacyTheme + eject: () => void } /** @@ -1182,12 +1193,25 @@ class LSPluginCore } } + /** + * Activate the user preferences. + * + * Steps: + * + * 1. Load the custom theme. + * + * @memberof LSPluginCore + */ async activateUserPreferences() { - const { theme } = this._userPreferences - - // 0. theme - if (theme) { - await this.selectTheme(theme, false) + const { theme: legacyTheme, themes } = this._userPreferences + const currentTheme = themes[themes.mode] + + // If there is currently a theme that has been set + if (currentTheme) { + await this.selectTheme(currentTheme, { effect: false }) + } else if (legacyTheme) { + // Otherwise compatible with older versions + await this.selectTheme(legacyTheme, { effect: false }) } } @@ -1238,7 +1262,7 @@ class LSPluginCore await this.loadUserPreferences() - const externals = new Set(this._userPreferences.externals || []) + const externals = new Set(this._userPreferences.externals) if (initial) { plugins = plugins.concat( @@ -1349,8 +1373,8 @@ class LSPluginCore this.emit('unregistered', identity) } - const externals = this._userPreferences.externals || [] - if (externals.length > 0 && unregisteredExternals.length > 0) { + const externals = this._userPreferences.externals + if (externals.length && unregisteredExternals.length) { await this.saveUserPreferences({ externals: externals.filter((it) => { return !unregisteredExternals.includes(it) @@ -1472,18 +1496,15 @@ class LSPluginCore return this._isRegistering } - get themes(): Map { + get themes() { return this._registeredThemes } - async registerTheme( - id: PluginLocalIdentity, - opt: ThemeOptions - ): Promise { - debug('registered Theme #', id, opt) + async registerTheme(id: PluginLocalIdentity, opt: Theme): Promise { + debug('Register theme #', id, opt) if (!id) return - let themes: ThemeOptions[] = this._registeredThemes.get(id)! + let themes: Theme[] = this._registeredThemes.get(id)! if (!themes) { this._registeredThemes.set(id, (themes = [])) } @@ -1492,41 +1513,81 @@ class LSPluginCore this.emit('theme-changed', this.themes, { id, ...opt }) } - async selectTheme(opt?: ThemeOptions, effect = true): Promise { - // clear current + async selectTheme( + theme: Theme | LegacyTheme, + options: { + effect?: boolean + emit?: boolean + } = {} + ) { + const { effect, emit } = Object.assign( + {}, + { effect: true, emit: true }, + options + ) + + // Clear current theme before injecting. if (this._currentTheme) { - this._currentTheme.dis?.() + this._currentTheme.eject() } - const disInjectedTheme = setupInjectedTheme(opt?.url) - this.emit('theme-selected', opt) - effect && (await this.saveUserPreferences({ theme: opt?.url ? opt : null })) - if (opt?.url) { + // Detect if it is the default theme (no url). + if (!theme.url) { + this._currentTheme = null + } else { + const ejectTheme = injectTheme(theme.url) + this._currentTheme = { - dis: () => { - disInjectedTheme() - effect && this.saveUserPreferences({ theme: null }) - }, - opt, - pid: opt.pid, + pid: theme.pid, + opt: theme, + eject: ejectTheme, } } + + if (effect) { + await this.saveUserPreferences( + theme.mode + ? { + themes: { + ...this._userPreferences.themes, + mode: theme.mode, + [theme.mode]: theme, + }, + } + : { theme: theme } + ) + } + + if (emit) { + this.emit('theme-selected', theme) + } } - async unregisterTheme( - id: PluginLocalIdentity, - effect: boolean = true - ): Promise { - debug('unregistered Theme #', id) + async unregisterTheme(id: PluginLocalIdentity, effect = true) { + debug('Unregister theme #', id) + + if (!this._registeredThemes.has(id)) { + return + } - if (!this._registeredThemes.has(id)) return this._registeredThemes.delete(id) this.emit('theme-changed', this.themes, { id }) if (effect && this._currentTheme?.pid === id) { - this._currentTheme.dis?.() + this._currentTheme.eject() this._currentTheme = null - // reset current theme - this.emit('theme-selected', null) + + const { theme, themes } = this._userPreferences + await this.saveUserPreferences({ + theme: theme?.pid === id ? null : theme, + themes: { + ...themes, + light: themes.light?.pid === id ? null : themes.light, + dark: themes.dark?.pid === id ? null : themes.dark, + }, + }) + + // Reset current theme if it is unregistered + this.emit('reset-custom-theme', this._userPreferences.themes) } } } diff --git a/libs/src/LSPlugin.ts b/libs/src/LSPlugin.ts index a2aa214d669..a93664d4508 100644 --- a/libs/src/LSPlugin.ts +++ b/libs/src/LSPlugin.ts @@ -1,18 +1,24 @@ -import EventEmitter from 'eventemitter3' import * as CSS from 'csstype' + +import EventEmitter from 'eventemitter3' import { LSPluginCaller } from './LSPlugin.caller' -import { LSPluginFileStorage } from './modules/LSPlugin.Storage' import { LSPluginExperiments } from './modules/LSPlugin.Experiments' +import { LSPluginFileStorage } from './modules/LSPlugin.Storage' export type PluginLocalIdentity = string -export type ThemeOptions = { +export type ThemeMode = 'light' | 'dark' + +export interface LegacyTheme { name: string url: string description?: string - mode?: 'dark' | 'light' + mode?: ThemeMode + pid: PluginLocalIdentity +} - [key: string]: any +export interface Theme extends LegacyTheme { + mode: ThemeMode } export type StyleString = string @@ -64,7 +70,7 @@ export interface LSPluginPkgConfig { entry: string // alias of main title: string mode: 'shadow' | 'iframe' - themes: Array + themes: Theme[] icon: string [key: string]: any @@ -122,7 +128,7 @@ export interface AppInfo { * User's app configurations */ export interface AppUserConfigs { - preferredThemeMode: 'dark' | 'light' + preferredThemeMode: ThemeMode preferredFormat: 'markdown' | 'org' preferredDateFormat: string preferredStartOfWeek: string @@ -382,7 +388,7 @@ export interface IAppProxy { content: string, status?: 'success' | 'warning' | 'error' | string ) => void - + setZoomFactor: (factor: number) => void setFullScreen: (flag: boolean | 'toggle') => void setLeftSidebarVisible: (flag: boolean | 'toggle') => void @@ -614,9 +620,17 @@ export interface IEditorProxy extends Record { getAllPages: (repo?: string) => Promise - prependBlockInPage: (page: PageIdentity, content: string, opts?: Partial<{ properties: {} }>) => Promise + prependBlockInPage: ( + page: PageIdentity, + content: string, + opts?: Partial<{ properties: {} }> + ) => Promise - appendBlockInPage: (page: PageIdentity, content: string, opts?: Partial<{ properties: {} }>) => Promise + appendBlockInPage: ( + page: PageIdentity, + content: string, + opts?: Partial<{ properties: {} }> + ) => Promise getPreviousSiblingBlock: ( srcBlock: BlockIdentity @@ -756,9 +770,7 @@ export interface IAssetsProxy { * @added 0.0.2 * @param exts */ - listFilesOfCurrentGraph( - exts: string | string[] - ): Promise<{ + listFilesOfCurrentGraph(exts: string | string[]): Promise<{ path: string size: number accessTime: number @@ -768,14 +780,17 @@ export interface IAssetsProxy { }> } -export interface ILSPluginThemeManager extends EventEmitter { - themes: Map> +export interface ILSPluginThemeManager { + get themes(): Map - registerTheme(id: PluginLocalIdentity, opt: ThemeOptions): Promise + registerTheme(id: PluginLocalIdentity, opt: Theme): Promise - unregisterTheme(id: PluginLocalIdentity): Promise + unregisterTheme(id: PluginLocalIdentity, effect?: boolean): Promise - selectTheme(opt?: ThemeOptions): Promise + selectTheme( + opt: Theme | LegacyTheme, + options: { effect?: boolean; emit?: boolean } + ): Promise } export type LSPluginUserEvents = 'ui:visible:changed' | 'settings:changed' @@ -837,7 +852,7 @@ export interface ILSPluginUser extends EventEmitter { /** * Set the theme for the main Logseq app */ - provideTheme(theme: ThemeOptions): this + provideTheme(theme: Theme): this /** * Inject custom css for the main Logseq app diff --git a/libs/src/LSPlugin.user.ts b/libs/src/LSPlugin.user.ts index 8fa4b8a50e7..6dcd3ae9674 100644 --- a/libs/src/LSPlugin.user.ts +++ b/libs/src/LSPlugin.user.ts @@ -15,7 +15,7 @@ import { SlashCommandAction, BlockCommandCallback, StyleString, - ThemeOptions, + Theme, UIOptions, IHookEvent, BlockIdentity, @@ -318,7 +318,8 @@ const KEY_MAIN_UI = 0 */ export class LSPluginUser extends EventEmitter - implements ILSPluginUser { + implements ILSPluginUser +{ // @ts-ignore private _version: string = LIB_VERSION private _debugTag: string = '' @@ -436,7 +437,7 @@ export class LSPluginUser return this } - provideTheme(theme: ThemeOptions) { + provideTheme(theme: Theme) { this.caller.call('provider:theme', theme) return this } diff --git a/libs/src/helpers.ts b/libs/src/helpers.ts index db75ab061d7..6a90e332214 100644 --- a/libs/src/helpers.ts +++ b/libs/src/helpers.ts @@ -416,26 +416,21 @@ export function transformableEvent(target: HTMLElement, e: Event) { return obj } -let injectedThemeEffect: any = null - -export function setupInjectedTheme(url?: string) { - injectedThemeEffect?.call() - - if (!url) return - +export function injectTheme(url: string) { const link = document.createElement('link') link.rel = 'stylesheet' link.href = url document.head.appendChild(link) - return (injectedThemeEffect = () => { + const ejectTheme = () => { try { document.head.removeChild(link) } catch (e) { console.error(e) } - injectedThemeEffect = null - }) + } + + return ejectTheme } export function mergeSettingsWithSchema( diff --git a/resources/js/lsplugin.core.js b/resources/js/lsplugin.core.js index 492982f3755..e1abc0f0d13 100644 --- a/resources/js/lsplugin.core.js +++ b/resources/js/lsplugin.core.js @@ -1,2 +1,2 @@ /*! For license information please see lsplugin.core.js.LICENSE.txt */ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.LSPlugin=t():e.LSPlugin=t()}(self,(function(){return(()=>{var e={227:(e,t,n)=>{var r=n(155);t.formatArgs=function(t){if(t[0]=(this.useColors?"%c":"")+this.namespace+(this.useColors?" %c":" ")+t[0]+(this.useColors?"%c ":" ")+"+"+e.exports.humanize(this.diff),!this.useColors)return;const n="color: "+this.color;t.splice(1,0,n,"color: inherit");let r=0,i=0;t[0].replace(/%[a-zA-Z%]/g,(e=>{"%%"!==e&&(r++,"%c"===e&&(i=r))})),t.splice(i,0,n)},t.save=function(e){try{e?t.storage.setItem("debug",e):t.storage.removeItem("debug")}catch(e){}},t.load=function(){let e;try{e=t.storage.getItem("debug")}catch(e){}return!e&&void 0!==r&&"env"in r&&(e=r.env.DEBUG),e},t.useColors=function(){return!("undefined"==typeof window||!window.process||"renderer"!==window.process.type&&!window.process.__nwjs)||("undefined"==typeof navigator||!navigator.userAgent||!navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/))&&("undefined"!=typeof document&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||"undefined"!=typeof window&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/))},t.storage=function(){try{return localStorage}catch(e){}}(),t.destroy=(()=>{let e=!1;return()=>{e||(e=!0,console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`."))}})(),t.colors=["#0000CC","#0000FF","#0033CC","#0033FF","#0066CC","#0066FF","#0099CC","#0099FF","#00CC00","#00CC33","#00CC66","#00CC99","#00CCCC","#00CCFF","#3300CC","#3300FF","#3333CC","#3333FF","#3366CC","#3366FF","#3399CC","#3399FF","#33CC00","#33CC33","#33CC66","#33CC99","#33CCCC","#33CCFF","#6600CC","#6600FF","#6633CC","#6633FF","#66CC00","#66CC33","#9900CC","#9900FF","#9933CC","#9933FF","#99CC00","#99CC33","#CC0000","#CC0033","#CC0066","#CC0099","#CC00CC","#CC00FF","#CC3300","#CC3333","#CC3366","#CC3399","#CC33CC","#CC33FF","#CC6600","#CC6633","#CC9900","#CC9933","#CCCC00","#CCCC33","#FF0000","#FF0033","#FF0066","#FF0099","#FF00CC","#FF00FF","#FF3300","#FF3333","#FF3366","#FF3399","#FF33CC","#FF33FF","#FF6600","#FF6633","#FF9900","#FF9933","#FFCC00","#FFCC33"],t.log=console.debug||console.log||(()=>{}),e.exports=n(447)(t);const{formatters:i}=e.exports;i.j=function(e){try{return JSON.stringify(e)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}}},447:(e,t,n)=>{e.exports=function(e){function t(e){let n,i=null;function o(...e){if(!o.enabled)return;const r=o,i=Number(new Date),s=i-(n||i);r.diff=s,r.prev=n,r.curr=i,n=i,e[0]=t.coerce(e[0]),"string"!=typeof e[0]&&e.unshift("%O");let a=0;e[0]=e[0].replace(/%([a-zA-Z%])/g,((n,i)=>{if("%%"===n)return"%";a++;const o=t.formatters[i];if("function"==typeof o){const t=e[a];n=o.call(r,t),e.splice(a,1),a--}return n})),t.formatArgs.call(r,e),(r.log||t.log).apply(r,e)}return o.namespace=e,o.useColors=t.useColors(),o.color=t.selectColor(e),o.extend=r,o.destroy=t.destroy,Object.defineProperty(o,"enabled",{enumerable:!0,configurable:!1,get:()=>null===i?t.enabled(e):i,set:e=>{i=e}}),"function"==typeof t.init&&t.init(o),o}function r(e,n){const r=t(this.namespace+(void 0===n?":":n)+e);return r.log=this.log,r}function i(e){return e.toString().substring(2,e.toString().length-2).replace(/\.\*\?$/,"*")}return t.debug=t,t.default=t,t.coerce=function(e){return e instanceof Error?e.stack||e.message:e},t.disable=function(){const e=[...t.names.map(i),...t.skips.map(i).map((e=>"-"+e))].join(",");return t.enable(""),e},t.enable=function(e){let n;t.save(e),t.names=[],t.skips=[];const r=("string"==typeof e?e:"").split(/[\s,]+/),i=r.length;for(n=0;n{t[n]=e[n]})),t.names=[],t.skips=[],t.formatters={},t.selectColor=function(e){let n=0;for(let t=0;t1?n-1:0),i=1;i/gm),U=s(/^data-[\-\w.\u00B7-\uFFFF]/),$=s(/^aria-[\-\w]+$/),z=s(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),H=s(/^(?:\w+script|data):/i),B=s(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),W="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};function q(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t0&&void 0!==arguments[0]?arguments[0]:G(),n=function(t){return e(t)};if(n.version="2.3.1",n.removed=[],!t||!t.document||9!==t.document.nodeType)return n.isSupported=!1,n;var r=t.document,i=t.document,s=t.DocumentFragment,a=t.HTMLTemplateElement,c=t.Node,l=t.Element,u=t.NodeFilter,d=t.NamedNodeMap,x=void 0===d?t.NamedNodeMap||t.MozNamedAttrMap:d,Z=t.Text,K=t.Comment,V=t.DOMParser,Y=t.trustedTypes,X=l.prototype,Q=O(X,"cloneNode"),ee=O(X,"nextSibling"),te=O(X,"childNodes"),ne=O(X,"parentNode");if("function"==typeof a){var re=i.createElement("template");re.content&&re.content.ownerDocument&&(i=re.content.ownerDocument)}var ie=J(Y,r),oe=ie&&De?ie.createHTML(""):"",se=i,ae=se.implementation,ce=se.createNodeIterator,le=se.createDocumentFragment,ue=se.getElementsByTagName,de=r.importNode,he={};try{he=C(i).documentMode?i.documentMode:{}}catch(e){}var pe={};n.isSupported="function"==typeof ne&&ae&&void 0!==ae.createHTMLDocument&&9!==he;var fe=D,ge=N,me=U,ye=$,_e=H,ve=B,be=z,we=null,xe=S({},[].concat(q(A),q(E),q(T),q(k),q(I))),Se=null,Ce=S({},[].concat(q(F),q(P),q(R),q(M))),Oe=null,Ae=null,Ee=!0,Te=!0,je=!1,ke=!1,Le=!1,Ie=!1,Fe=!1,Pe=!1,Re=!1,Me=!0,De=!1,Ne=!0,Ue=!0,$e=!1,ze={},He=null,Be=S({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]),We=null,qe=S({},["audio","video","img","source","image","track"]),Ge=null,Je=S({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),Ze="http://www.w3.org/1998/Math/MathML",Ke="http://www.w3.org/2000/svg",Ve="http://www.w3.org/1999/xhtml",Ye=Ve,Xe=!1,Qe=null,et=i.createElement("form"),tt=function(e){Qe&&Qe===e||(e&&"object"===(void 0===e?"undefined":W(e))||(e={}),e=C(e),we="ALLOWED_TAGS"in e?S({},e.ALLOWED_TAGS):xe,Se="ALLOWED_ATTR"in e?S({},e.ALLOWED_ATTR):Ce,Ge="ADD_URI_SAFE_ATTR"in e?S(C(Je),e.ADD_URI_SAFE_ATTR):Je,We="ADD_DATA_URI_TAGS"in e?S(C(qe),e.ADD_DATA_URI_TAGS):qe,He="FORBID_CONTENTS"in e?S({},e.FORBID_CONTENTS):Be,Oe="FORBID_TAGS"in e?S({},e.FORBID_TAGS):{},Ae="FORBID_ATTR"in e?S({},e.FORBID_ATTR):{},ze="USE_PROFILES"in e&&e.USE_PROFILES,Ee=!1!==e.ALLOW_ARIA_ATTR,Te=!1!==e.ALLOW_DATA_ATTR,je=e.ALLOW_UNKNOWN_PROTOCOLS||!1,ke=e.SAFE_FOR_TEMPLATES||!1,Le=e.WHOLE_DOCUMENT||!1,Pe=e.RETURN_DOM||!1,Re=e.RETURN_DOM_FRAGMENT||!1,Me=!1!==e.RETURN_DOM_IMPORT,De=e.RETURN_TRUSTED_TYPE||!1,Fe=e.FORCE_BODY||!1,Ne=!1!==e.SANITIZE_DOM,Ue=!1!==e.KEEP_CONTENT,$e=e.IN_PLACE||!1,be=e.ALLOWED_URI_REGEXP||be,Ye=e.NAMESPACE||Ve,ke&&(Te=!1),Re&&(Pe=!0),ze&&(we=S({},[].concat(q(I))),Se=[],!0===ze.html&&(S(we,A),S(Se,F)),!0===ze.svg&&(S(we,E),S(Se,P),S(Se,M)),!0===ze.svgFilters&&(S(we,T),S(Se,P),S(Se,M)),!0===ze.mathMl&&(S(we,k),S(Se,R),S(Se,M))),e.ADD_TAGS&&(we===xe&&(we=C(we)),S(we,e.ADD_TAGS)),e.ADD_ATTR&&(Se===Ce&&(Se=C(Se)),S(Se,e.ADD_ATTR)),e.ADD_URI_SAFE_ATTR&&S(Ge,e.ADD_URI_SAFE_ATTR),e.FORBID_CONTENTS&&(He===Be&&(He=C(He)),S(He,e.FORBID_CONTENTS)),Ue&&(we["#text"]=!0),Le&&S(we,["html","head","body"]),we.table&&(S(we,["tbody"]),delete Oe.tbody),o&&o(e),Qe=e)},nt=S({},["mi","mo","mn","ms","mtext"]),rt=S({},["foreignobject","desc","title","annotation-xml"]),it=S({},E);S(it,T),S(it,j);var ot=S({},k);S(ot,L);var st=function(e){var t=ne(e);t&&t.tagName||(t={namespaceURI:Ve,tagName:"template"});var n=g(e.tagName),r=g(t.tagName);if(e.namespaceURI===Ke)return t.namespaceURI===Ve?"svg"===n:t.namespaceURI===Ze?"svg"===n&&("annotation-xml"===r||nt[r]):Boolean(it[n]);if(e.namespaceURI===Ze)return t.namespaceURI===Ve?"math"===n:t.namespaceURI===Ke?"math"===n&&rt[r]:Boolean(ot[n]);if(e.namespaceURI===Ve){if(t.namespaceURI===Ke&&!rt[r])return!1;if(t.namespaceURI===Ze&&!nt[r])return!1;var i=S({},["title","style","font","a","script"]);return!ot[n]&&(i[n]||!it[n])}return!1},at=function(e){f(n.removed,{element:e});try{e.parentNode.removeChild(e)}catch(t){try{e.outerHTML=oe}catch(t){e.remove()}}},ct=function(e,t){try{f(n.removed,{attribute:t.getAttributeNode(e),from:t})}catch(e){f(n.removed,{attribute:null,from:t})}if(t.removeAttribute(e),"is"===e&&!Se[e])if(Pe||Re)try{at(t)}catch(e){}else try{t.setAttribute(e,"")}catch(e){}},lt=function(e){var t=void 0,n=void 0;if(Fe)e=""+e;else{var r=m(e,/^[\r\n\t ]+/);n=r&&r[0]}var o=ie?ie.createHTML(e):e;if(Ye===Ve)try{t=(new V).parseFromString(o,"text/html")}catch(e){}if(!t||!t.documentElement){t=ae.createDocument(Ye,"template",null);try{t.documentElement.innerHTML=Xe?"":o}catch(e){}}var s=t.body||t.documentElement;return e&&n&&s.insertBefore(i.createTextNode(n),s.childNodes[0]||null),Ye===Ve?ue.call(t,Le?"html":"body")[0]:Le?t.documentElement:s},ut=function(e){return ce.call(e.ownerDocument||e,e,u.SHOW_ELEMENT|u.SHOW_COMMENT|u.SHOW_TEXT,null,!1)},dt=function(e){return!(e instanceof Z||e instanceof K||"string"==typeof e.nodeName&&"string"==typeof e.textContent&&"function"==typeof e.removeChild&&e.attributes instanceof x&&"function"==typeof e.removeAttribute&&"function"==typeof e.setAttribute&&"string"==typeof e.namespaceURI&&"function"==typeof e.insertBefore)},ht=function(e){return"object"===(void 0===c?"undefined":W(c))?e instanceof c:e&&"object"===(void 0===e?"undefined":W(e))&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName},pt=function(e,t,r){pe[e]&&h(pe[e],(function(e){e.call(n,t,r,Qe)}))},ft=function(e){var t=void 0;if(pt("beforeSanitizeElements",e,null),dt(e))return at(e),!0;if(m(e.nodeName,/[\u0080-\uFFFF]/))return at(e),!0;var r=g(e.nodeName);if(pt("uponSanitizeElement",e,{tagName:r,allowedTags:we}),!ht(e.firstElementChild)&&(!ht(e.content)||!ht(e.content.firstElementChild))&&b(/<[/\w]/g,e.innerHTML)&&b(/<[/\w]/g,e.textContent))return at(e),!0;if("select"===r&&b(/