File manager - Edit - /usr/local/CyberCP/public/snappymail/snappymail/v/2.38.2/static/js/libs.js
Back
(doc=>{ Array.prototype.unique = function() { return this.filter((v, i, a) => a.indexOf(v) === i); }; Array.prototype.validUnique = function(fn) { return this.filter((v, i, a) => (fn ? fn(v) : v) && a.indexOf(v) === i); }; // full = Monday, December 12, 2022 at 12:16:21 PM Central European Standard Time // long = December 12, 2022 at 12:16:21 PM GMT+1 // medium = Dec 12, 2022, 12:16:21 PM // short = 12/12/22, 12:16 PM let formats = { // LT : {timeStyle: 'short'}, // Issue in Safari LT : {hour: 'numeric', minute: 'numeric'}, LLL : {dateStyle: 'long', timeStyle: 'short'} }; // Format momentjs/PHP date formats to Intl.DateTimeFormat // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat Date.prototype.format = function (options, UTC, hourCycle) { if (typeof options == 'string') { if (formats[options]) { options = formats[options]; } else { console.log('Date.format('+options+')'); options = {}; } } if (hourCycle) { options.hourCycle = hourCycle; } let el = doc.documentElement; return this.toLocaleString(el.dataset.dateLang || el.lang, options); }; Element.prototype.closestWithin = function(selector, parent) { const el = this.closest(selector); return (el && el !== parent && parent.contains(el)) ? el : null; }; Element.fromHTML = string => { const template = doc.createElement('template'); template.innerHTML = string.trim(); return template.content.firstChild; }; /** * https://github.com/tc39/proposal-regex-escaping */ if (!RegExp.escape){ RegExp.escape = s => String(s).replace(/[\\^$*+?.()|[\]{}]/g, '\\$&'); } /** * Every time the function is executed, * it will delay the execution with the given amount of milliseconds. */ if (!Function.prototype.debounce) { Function.prototype.debounce = function(ms) { let func = this, timer; return function(...args) { timer && clearTimeout(timer); timer = setTimeout(()=>{ func.apply(this, args); timer = 0; }, ms); }; }; } /** * No matter how many times the event is executed, * the function will be executed only once, after the given amount of milliseconds. */ if (!Function.prototype.throttle) { Function.prototype.throttle = function(ms) { let func = this, timer; return function(...args) { timer = timer || setTimeout(()=>{ func.apply(this, args); timer = 0; }, ms); }; }; } })(document); /** * Modified version of https://github.com/Bernardo-Castilho/dragdroptouch * This is to only support Firefox Mobile. * Because touchstart must call preventDefault() to prevent scrolling * but then it doesn't work native in Chrome on Android */ (doc => { let ua = navigator.userAgent.toLowerCase(); // Chrome on mobile supports drag & drop if (ua.includes('mobile') && ua.includes('gecko/')) { let opt = { passive: false, capture: false }, dropEffect = 'move', effectAllowed = 'all', data = {}, dataTransfer, dragSource, isDragging, allowDrop, lastTarget, lastTouch, holdInterval, img; /* class DataTransferItem { get kind() { return 'string'; } } */ /** https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer */ class DataTransfer { get dropEffect() { return dropEffect; } set dropEffect(value) { dropEffect = value; } get effectAllowed() { return effectAllowed; } set effectAllowed(value) { effectAllowed = value; } get files() { return []; } get items() { return []; } // DataTransferItemList get types() { return Object.keys(data); } clearData(type) { if (type != null) { delete data[type]; } else { data = {}; } } getData(type) { return data[type] || ''; } setData(type, value) { data[type] = value; } constructor() { this.setDragImage = setDragImage; } } const htmlDrag = b => doc.documentElement.classList.toggle('firefox-drag', b), setDragImage = (src, xOffset, yOffset) => { img?.remove(); if (src) { // create drag image from custom element or drag source img = src.cloneNode(true); copyStyle(src, img); img._x = xOffset ?? src.clientWidth / 2; img._y = yOffset ?? src.clientHeight / 2; } }, // clear all members reset = () => { if (dragSource) { clearInterval(holdInterval); // dispose of drag image element img?.remove(); isDragging && dispatchEvent(lastTouch, 'dragend', dragSource); img = dragSource = lastTouch = lastTarget = dataTransfer = holdInterval = null; isDragging = allowDrop = false; htmlDrag(false); } }, // get point for a touch event getPoint = e => { e = e.touches ? e.touches[0] : e; return { x: e.clientX, y: e.clientY }; }, touchend = e => { if (dragSource) { // finish dragging allowDrop && 'touchcancel' !== e.type && dispatchEvent(lastTouch, 'drop', lastTarget); reset(); } }, // get the element at a given touch event getTarget = pt => { let el = doc.elementFromPoint(pt.x, pt.y); while (el && getComputedStyle(el).pointerEvents == 'none') { el = el.parentElement; } return el; }, // move the drag image element moveImage = pt => { requestAnimationFrame(() => { if (img) { img.style.left = Math.round(pt.x - img._x) + 'px'; img.style.top = Math.round(pt.y - img._y) + 'px'; } }); }, copyStyle = (src, dst) => { // remove potentially troublesome attributes ['id','class','style','draggable'].forEach(att => dst.removeAttribute(att)); // copy canvas content if (src instanceof HTMLCanvasElement) { let cSrc = src, cDst = dst; cDst.width = cSrc.width; cDst.height = cSrc.height; cDst.getContext('2d').drawImage(cSrc, 0, 0); } // copy style (without transitions) let cs = getComputedStyle(src); Object.entries(cs).forEach(([key, value]) => key.includes('transition') || (dst.style[key] = value)); dst.style.pointerEvents = 'none'; // and repeat for all children let i = src.children.length; while (i--) copyStyle(src.children[i], dst.children[i]); }, // return false when cancelled dispatchEvent = (e, type, target) => { if (e && target) { let evt = new Event(type, {bubbles:true,cancelable:true}); evt.button = 0; evt.buttons = 1; // copy event properties into new event ['altKey','ctrlKey','metaKey','shiftKey'].forEach(k => evt[k] = e[k]); let src = e.touches ? e.touches[0] : e; ['pageX','pageY','clientX','clientY','screenX','screenY','offsetX','offsetY'].forEach(k => evt[k] = src[k]); if (dragSource) { evt.dataTransfer = dataTransfer; } return target.dispatchEvent(evt); } return false; }; /* doc.addEventListener('pointerdown', e => { doc.addEventListener('pointermove', e => { e.clientX }); doc.setPointerCapture(e.pointerId); }); doc.addEventListener('pointerup', e => { doc.releasePointerCapture(e.pointerId); }); */ doc.addEventListener('touchstart', e => { // clear all variables reset(); // ignore events that have been handled or that involve more than one touch if (e && !e.defaultPrevented && e.touches && e.touches.length < 2) { // get nearest draggable element dragSource = e.target.closest('[draggable]'); if (dragSource) { // get ready to start dragging lastTouch = e; // dragSource.style.userSelect = 'none'; // 1000 ms to wait, chrome on android triggers dragstart in 600 holdInterval = setTimeout(() => { // start dragging dataTransfer = new DataTransfer(); if ((isDragging = dispatchEvent(e, 'dragstart', dragSource))) { htmlDrag(true); let pt = getPoint(e); // create drag image from custom element or drag source img || setDragImage(dragSource); let style = img.style; style.top = style.left = '-9999px'; style.position = 'fixed'; style.pointerEvents = 'none'; style.zIndex = '999999999'; // add image to document moveImage(pt); doc.body.append(img); dispatchEvent(e, 'dragenter', getTarget(pt)); } else { reset(); } }, 1000); } } }, opt); doc.addEventListener('touchmove', e => { if (isDragging) { // continue dragging let pt = getPoint(e), target = getTarget(pt); lastTouch = e; if (target != lastTarget) { dispatchEvent(e, 'dragleave', lastTarget); dispatchEvent(e, 'dragenter', target); lastTarget = target; } moveImage(pt); allowDrop = !dispatchEvent(e, 'dragover', target); } else { reset(); } }, opt); doc.addEventListener('touchend', touchend); doc.addEventListener('touchcancel', touchend); } })(document); (win => { let scope = {}, _scope = 'all'; const doc = document, // On Mac we use ⌘ else the Ctrl key meta = /Mac OS X/.test(navigator.userAgent) ? 'meta' : 'ctrl', _scopes = { all: {} }, toArray = v => Array.isArray(v) ? v : v.split(/\s*,\s*/), exec = (event, cmd) => { try { // call the handler and stop the event if neccessary if (!event.defaultPrevented && cmd(event) === false) { event.preventDefault(); event.stopPropagation(); } } catch (e) { console.error(e); } }, shortcuts = { on: () => doc.addEventListener('keydown', keydown), off: () => doc.removeEventListener('keydown', keydown), add: (keys, modifiers, scopes, method) => { if (null == method) { method = scopes; scopes = 'all'; } toArray(scopes).forEach(scope => { if (!_scopes[scope]) { _scopes[scope] = {}; } toArray(keys).forEach(key => { key = key.toLowerCase(); if (!_scopes[scope][key]) { _scopes[scope][key] = {}; } modifiers = toArray(modifiers) .map(key => 'meta' == key ? meta : key) .unique().sort().join('+'); if (!_scopes[scope][key][modifiers]) { _scopes[scope][key][modifiers] = []; } _scopes[scope][key][modifiers].push(method); }); }); }, setScope: value => { _scope = value || 'all'; scope = _scopes[_scope] || {}; console.log('Shortcuts scope set to: ' + _scope); }, getScope: () => _scope, getMetaKey: () => 'meta' === meta ? '⌘' : 'Ctrl' }, keydown = event => { let key = (event.key || '').toLowerCase().replace(' ','space'), modifiers = ['alt','ctrl','meta','shift'].filter(v => event[v+'Key']).join('+'); scope[key]?.[modifiers]?.forEach(cmd => exec(event, cmd)); !event.defaultPrevented && _scope !== 'all' && _scopes.all[key]?.[modifiers]?.forEach(cmd => exec(event, cmd)); }; win.shortcuts = shortcuts; shortcuts.on(); })(this); /*!! * Hasher <http://github.com/millermedeiros/hasher> * @author Miller Medeiros * @version 1.1.2 (2012/10/31 03:19 PM) * Released under the MIT License */ (global => { //-------------------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------------------- const _hashValRegexp = /#(.*)$/, _hashRegexp = /^[#/]+/, _hashTrim = /^\/+/g, _trimHash = hash => hash?.replace(_hashTrim, '') || '', _getWindowHash = () => { //parsed full URL instead of getting window.location.hash because Firefox decode hash value (and all the other browsers don't) var result = _hashValRegexp.exec( location.href ); return result?.[1] ? decodeURIComponent(result[1]) : ''; }, _registerChange = newHash => { if (_hash !== newHash) { var oldHash = _hash; _hash = newHash; //should come before event dispatch to make sure user can get proper value inside event handler _dispatch(_trimHash(newHash), _trimHash(oldHash)); } }, _setHash = (path, replace) => { path = path ? '/' + path.replace(_hashRegexp, '') : path; if (path !== _hash){ // we should store raw value _registerChange(path); if (path === _hash) { path = '#' + encodeURI(path) // we check if path is still === _hash to avoid error in // case of multiple consecutive redirects [issue #39] replace ? location.replace(path) : (location.hash = path); } } }, _dispatch = (...args) => hasher.active && _bindings.forEach(callback => callback(...args)), //-------------------------------------------------------------------------------------- // Public (API) //-------------------------------------------------------------------------------------- hasher = /** @lends hasher */ { clear : () => { _bindings = []; hasher.active = true; }, /** * Signal dispatched when hash value changes. * - pass current hash as 1st parameter to listeners and previous hash value as 2nd parameter. * @type signals.Signal */ active : true, add : callback => _bindings.push(callback), /** * Start listening/dispatching changes in the hash/history. * <ul> * <li>hasher won't dispatch CHANGE events by manually typing a new value or pressing the back/forward buttons before calling this method.</li> * </ul> */ init : () => _dispatch(_trimHash(_hash)), /** * Set Hash value, generating a new history record. * @param {...string} path Hash value without '#'. * @example hasher.setHash('lorem/ipsum/dolor') -> '#/lorem/ipsum/dolor' */ setHash : path => _setHash(path), /** * Set Hash value without keeping previous hash on the history record. * @param {...string} path Hash value without '#'. * @example hasher.replaceHash('lorem/ipsum/dolor') -> '#/lorem/ipsum/dolor' */ replaceHash : path => _setHash(path, true) }; var _hash = _getWindowHash(), _bindings = []; addEventListener('hashchange', () => _registerChange(_getWindowHash())); global.hasher = hasher; })(this); /** @license * Crossroads.js <http://millermedeiros.github.com/crossroads.js> * Released under the MIT license * Author: Miller Medeiros * Version: 0.7.1 - Build: 93 (2012/02/02 09:29 AM) */ (global => { const isFunction = obj => typeof obj === 'function'; // Crossroads -------- //==================== global.Crossroads = class Crossroads { constructor() { this._routes = []; } addRoute(pattern, callback) { var route = new Route(pattern, callback, this); this._routes.push(route); return route; } parse(request) { request = request || ''; var i = 0, routes = this._routes, n = routes.length, route; //should be decrement loop since higher priorities are added at the end of array while (n--) { route = routes[n]; if ((!i || route.greedy) && route.match(request)) { route.callback?.(...route._getParamsArray(request)); ++i; } } } } // Route -------------- //===================== class Route { constructor(pattern, callback, router) { var isRegexPattern = pattern instanceof RegExp; Object.assign(this, { greedy: false, rules: {}, _router: router, _pattern: pattern, _paramsIds: isRegexPattern ? null : captureVals(PARAMS_REGEXP, pattern), _optionalParamsIds: isRegexPattern ? null : captureVals(OPTIONAL_PARAMS_REGEXP, pattern), _matchRegexp: isRegexPattern ? pattern : compilePattern(pattern), callback: isFunction(callback) ? callback : null }); } match(request) { // validate params even if regexp. var values = this._getParamsObject(request); return this._matchRegexp.test(request) && 0 == Object.entries(this.rules).filter(([key, validationRule]) => { var val = values[key], isValid = false; if (key === 'normalize_' || (val == null && this._optionalParamsIds?.includes(key))) { isValid = true; } else if (validationRule instanceof RegExp) { isValid = validationRule.test(val); } else if (Array.isArray(validationRule)) { isValid = validationRule.includes(val); } else if (isFunction(validationRule)) { isValid = validationRule(val, request, values); } // fail silently if validationRule is from an unsupported type return !isValid; }).length; } _getParamsObject(request) { var values = getParamValues(request, this._matchRegexp) || [], n = values.length; if (this._paramsIds) { while (n--) { values[this._paramsIds[n]] = values[n]; } } return values; } _getParamsArray(request) { var norm = this.rules.normalize_; return isFunction(norm) ? norm(request, this._getParamsObject(request)) : getParamValues(request, this._matchRegexp); } } // Pattern Lexer ------ //===================== const ESCAPE_CHARS_REGEXP = /[\\.+*?^$[\](){}/'#]/g, //match chars that should be escaped on string regexp UNNECESSARY_SLASHES_REGEXP = /\/$/g, //trailing slash OPTIONAL_SLASHES_REGEXP = /([:}]|\w(?=\/))\/?(:)/g, //slash between `::` or `}:` or `\w:`. $1 = before, $2 = after REQUIRED_SLASHES_REGEXP = /([:}])\/?(\{)/g, //used to insert slash between `:{` and `}{` REQUIRED_PARAMS_REGEXP = /\{([^}]+)\}/g, //match everything between `{ }` OPTIONAL_PARAMS_REGEXP = /:([^:]+):/g, //match everything between `: :` PARAMS_REGEXP = /(?:\{|:)([^}:]+)(?:\}|:)/g, //capture everything between `{ }` or `: :` //used to save params during compile (avoid escaping things that //shouldn't be escaped). SAVE_REQUIRED_PARAMS = '__CR_RP__', SAVE_OPTIONAL_PARAMS = '__CR_OP__', SAVE_REQUIRED_SLASHES = '__CR_RS__', SAVE_OPTIONAL_SLASHES = '__CR_OS__', SAVED_REQUIRED_REGEXP = new RegExp(SAVE_REQUIRED_PARAMS, 'g'), SAVED_OPTIONAL_REGEXP = new RegExp(SAVE_OPTIONAL_PARAMS, 'g'), SAVED_OPTIONAL_SLASHES_REGEXP = new RegExp(SAVE_OPTIONAL_SLASHES, 'g'), SAVED_REQUIRED_SLASHES_REGEXP = new RegExp(SAVE_REQUIRED_SLASHES, 'g'), captureVals = (regex, pattern) => { var vals = [], match; while ((match = regex.exec(pattern))) { vals.push(match[1]); } return vals; }, getParamValues = (request, regexp) => { var vals = regexp.exec(request); vals?.shift(); return vals; }, compilePattern = pattern => { return new RegExp('^' + (pattern ? pattern // tokenize, save chars that shouldn't be escaped .replace(UNNECESSARY_SLASHES_REGEXP, '') .replace(OPTIONAL_SLASHES_REGEXP, '$1'+ SAVE_OPTIONAL_SLASHES +'$2') .replace(REQUIRED_SLASHES_REGEXP, '$1'+ SAVE_REQUIRED_SLASHES +'$2') .replace(OPTIONAL_PARAMS_REGEXP, SAVE_OPTIONAL_PARAMS) .replace(REQUIRED_PARAMS_REGEXP, SAVE_REQUIRED_PARAMS) .replace(ESCAPE_CHARS_REGEXP, '\\$&') // untokenize .replace(SAVED_OPTIONAL_SLASHES_REGEXP, '\\/?') .replace(SAVED_REQUIRED_SLASHES_REGEXP, '\\/') .replace(SAVED_OPTIONAL_REGEXP, '([^\\/]+)?/?') .replace(SAVED_REQUIRED_REGEXP, '([^\\/]+)') : '' ) + '/?$'); //trailing slash is optional }; })(this); /* RainLoop Webmail (c) RainLoop Team | MIT */ (doc => { const defined = v => undefined !== v, /** * @param {*} aItems * @param {Function} fFileCallback * @param {number=} iLimit = 20 */ getDataFromFiles = (aItems, fFileCallback, iLimit) => { if (aItems?.length) { let oFile, iCount = 0, bCallLimit = false ; [...aItems].forEach(oItem => { if (oItem) { if (iLimit && iLimit < ++iCount) { if (!bCallLimit) { bCallLimit = true; // fLimitCallback(iLimit); } } else { oFile = getDataFromFile(oItem); oFile && fFileCallback(oFile); } } }); } }, addEventListeners = (element, obj) => Object.entries(obj).forEach(([key, value]) => element.addEventListener(key, value)), /** * @param {*} oFile * @return {Object} */ getDataFromFile = oFile => { return oFile.size ? { fileName: (oFile.name || '').replace(/^.*\/([^/]*)$/, '$1'), size: oFile.size, file: oFile } : null; // Folder }, eventContainsFiles = oEvent => oEvent.dataTransfer.types.includes('Files'); class Queue extends Array { push(fn, ...args) { super.push([fn, args]); this.call(); } call() { if (!this.running) { this.running = true; let f; while ((f = this.shift())) f[0](...f[1]); this.running = false; } } } /** * @constructor * @param {Object=} options */ class Jua { constructor(options) { let timer, el = options.clickElement; const self = this, timerStart = fn => { timerStop(); timer = setTimeout(fn, 200); }, timerStop = () => { timer && clearTimeout(timer); timer = 0; }; self.oEvents = { onSelect: null, onStart: null, onComplete: null, onProgress: null, onDragEnter: null, onDragLeave: null, onBodyDragEnter: null, onBodyDragLeave: null }; self.oXhrs = {}; self.oUids = {}; self.options = Object.assign({ action: '', name: 'uploader', limit: 0, // clickElement: // dragAndDropElement: }, options || {}); self.oQueue = new Queue(); // clickElement if (el) { el.style.position = 'relative'; el.style.overflow = 'hidden'; if ('inline' === el.style.display) { el.style.display = 'inline-block'; } self.generateNewInput(el); } el = options.dragAndDropElement; if (el) { addEventListeners(doc, { dragover: oEvent => { if (eventContainsFiles(oEvent)) { timerStop(); if (el.contains(oEvent.target)) { oEvent.dataTransfer.dropEffect = 'copy'; oEvent.stopPropagation(); } else { oEvent.dataTransfer.dropEffect = 'none'; } oEvent.preventDefault(); } }, dragenter: oEvent => { if (eventContainsFiles(oEvent)) { timerStop(); oEvent.preventDefault(); self.runEvent('onBodyDragEnter', oEvent); if (el.contains(oEvent.target)) { timerStop(); self.runEvent('onDragEnter', el, oEvent); } } }, dragleave: oEvent => { if (eventContainsFiles(oEvent)) { let oRelatedTarget = doc.elementFromPoint(oEvent.clientX, oEvent.clientY); if (!oRelatedTarget || !el.contains(oRelatedTarget)) { self.runEvent('onDragLeave', el, oEvent); } timerStart(() => self.runEvent('onBodyDragLeave', oEvent)) } }, drop: oEvent => { if (eventContainsFiles(oEvent)) { timerStop(); oEvent.preventDefault(); if (el.contains(oEvent.target)) { getDataFromFiles( oEvent.files || oEvent.dataTransfer.files, oFile => { if (oFile) { self.addFile(oFile); } }, self.options.limit ); } } self.runEvent('onDragLeave', oEvent); self.runEvent('onBodyDragLeave', oEvent); } }); } } /** * @param {string} sName * @param {Function} fFunc */ on(sName, fFunc) { this.oEvents[sName] = fFunc; return this; } /** * @param {string} sName */ runEvent(sName, ...aArgs) { this.oEvents[sName]?.apply(null, aArgs); } /** * @param {string} sName */ getEvent(sName) { return this.oEvents[sName] || null; } /** * @param {Object} oFileInfo */ addFile(oFileInfo) { const sUid = 'jua-uid-' + Jua.randomId(16) + '-' + (Date.now().toString()), fOnSelect = this.getEvent('onSelect'); if (oFileInfo && (!fOnSelect || (false !== fOnSelect(sUid, oFileInfo)))) { this.oUids[sUid] = true; this.oQueue.push((...args) => this.uploadTask(...args), sUid, oFileInfo); } else { this.cancel(sUid); } } /** * @param {string} sUid * @param {?} oFileInfo */ uploadTask(sUid, oFileInfo) { if (false === this.oUids[sUid] || !oFileInfo || !oFileInfo.file) { return false; } try { const self = this, oXhr = new XMLHttpRequest(), oFormData = new FormData(), sAction = this.options.action, fStartFunction = this.getEvent('onStart'), fProgressFunction = this.getEvent('onProgress') ; oXhr.open('POST', sAction, true); if (fProgressFunction && oXhr.upload) { oXhr.upload.onprogress = oEvent => { if (oEvent && oEvent.lengthComputable && defined(oEvent.loaded) && defined(oEvent.total)) { fProgressFunction(sUid, oEvent.loaded, oEvent.total); } }; } oXhr.onreadystatechange = () => { if (4 === oXhr.readyState) { delete self.oXhrs[sUid]; let bResult = false, oResult = null; if (200 === oXhr.status) { try { oResult = JSON.parse(oXhr.responseText); bResult = true; } catch (e) { console.error(e); } } this.getEvent('onComplete')(sUid, bResult, oResult); } }; fStartFunction && fStartFunction(sUid); oFormData.append(this.options.name, oFileInfo.file); oXhr.send(oFormData); this.oXhrs[sUid] = oXhr; return true; } catch (oError) { console.error(oError) } return false; } generateNewInput(oClickElement) { if (oClickElement) { const self = this, limit = self.options.limit, oInput = doc.createElement('input'), onClick = ()=>oInput.click(); oInput.type = 'file'; oInput.tabIndex = -1; oInput.style.display = 'none'; oInput.multiple = 1 != limit; oClickElement.addEventListener('click', onClick); oInput.addEventListener('input', () => { const fFileCallback = oFile => { self.addFile(oFile); setTimeout(() => { oInput.remove(); oClickElement.removeEventListener('click', onClick); self.generateNewInput(oClickElement); }, 10); }; if (oInput.files?.length) { getDataFromFiles(oInput.files, fFileCallback, limit); } else { fFileCallback({ fileName: oInput.value.split(/\\\//).pop(), size: null, file : null }); } }); } } /** * @param {string} sUid */ cancel(sUid) { this.oUids[sUid] = false; if (this.oXhrs[sUid]) { try { this.oXhrs[sUid].abort && this.oXhrs[sUid].abort(); } catch (oError) { console.error(oError); } delete this.oXhrs[sUid]; } } } Jua.randomId = len => { let arr = new Uint8Array((len || 32) / 2); crypto.getRandomValues(arr); return arr.map(dec => dec.toString(16).padStart(2,'0')).join(''); } this.Jua = Jua; })(document); /*! * Native JavaScript for Bootstrap v3.0.10 (https://thednp.github.io/bootstrap.native/) * Copyright 2015-2020 © dnp_theme * Licensed under MIT (https://github.com/thednp/bootstrap.native/blob/master/LICENSE) */ (doc => { const setFocus = element => element.focus ? element.focus() : element.setActive(), isArrow = e => 'ArrowUp' === e.key || 'ArrowDown' === e.key; this.BSN = { Dropdown: function(toggleBtn) { let menu, menuItems = []; const self = this, parent = toggleBtn.parentNode, preventEmptyAnchor = e => { const t = e.target; ('#' === (t.href || t.parentNode?.href)?.slice(-1)) && e.preventDefault(); }, open = bool => { menu?.classList.toggle('show', bool); parent.classList.toggle('show', bool); toggleBtn.setAttribute('aria-expanded', bool); toggleBtn.open = bool; if (bool) { toggleBtn.removeEventListener('click',clickHandler); } else { setTimeout(() => toggleBtn.addEventListener('click',clickHandler), 1); } }, toggleEvents = () => { const action = (toggleBtn.open ? 'add' : 'remove') + 'EventListener'; doc[action]('click',dismissHandler); doc[action]('keydown',preventScroll); doc[action]('keyup',keyHandler); doc[action]('focus',dismissHandler); }, dismissHandler = e => { const eventTarget = e.target; if ((!menu.contains(eventTarget) && !toggleBtn.contains(eventTarget)) || e.type !== 'focus') { self.hide(); preventEmptyAnchor(e); } }, clickHandler = e => { self.show(); preventEmptyAnchor(e); }, preventScroll = e => isArrow(e) && e.preventDefault(), keyHandler = e => { if ('Escape' === e.key) { self.toggle(); } else if (isArrow(e)) { let activeItem = doc.activeElement, isMenuButton = activeItem === toggleBtn, idx = isMenuButton ? 0 : menuItems.indexOf(activeItem); if (parent.contains(activeItem)) { if (!isMenuButton) { idx = 'ArrowUp' === e.key ? (idx > 1 ? idx-1 : 0) : (idx < menuItems.length-1 ? idx+1 : idx); } menuItems[idx] && setFocus(menuItems[idx]); } else { console.log('activeElement not in menu'); } } }; self.show = () => { menu = parent.querySelector('.dropdown-menu'); menuItems = [...menu.querySelectorAll('A')].filter(item => 'none' != item.parentNode.style.display); !('tabindex' in menu) && menu.setAttribute('tabindex', '0'); open(true); setTimeout(() => { setFocus( menu.getElementsByTagName('INPUT')[0] || toggleBtn ); toggleEvents(); },1); }; self.hide = () => { open(false); toggleEvents(); setFocus(toggleBtn); }; self.toggle = () => toggleBtn.open ? self.hide() : self.show(); open(false); toggleBtn.Dropdown = self; } }; })(document); /*! * Knockout JavaScript library v3.5.1-sm * (c) The Knockout.js team - http://knockoutjs.com/ * License: MIT (http://www.opensource.org/licenses/mit-license.php) */ (R=>{function L(a,b){return a===b&&a!==Object(a)}function ca(a,b){var d;return()=>{d||(d=setTimeout(()=>{d=0;a()},b))}}function da(a,b){var d;return()=>{clearTimeout(d);d=setTimeout(a,b)}}function ea(a,b){b?.dispose?.()}function fa(a,b){var d=this.Lb,e=d[x];e.X||(this.Ma&&this.va[b]?(d.kb(b,a,this.va[b]),this.va[b]=null,--this.Ma):e.v[b]||d.kb(b,a,e.A?{S:a}:d.Cb(a)),a.ea&&a.Gb())}var J=R.document,M={},c="undefined"!==typeof M?M:{};c.U=(a,b)=>{a=a.split(".");for(var d=c,e=0,g=a.length-1;e<g;e++)d= d[a[e]];d[a[g]]=b};c.g={extend:(a,b)=>b?Object.assign(a,b):a,K:(a,b)=>a&&Object.entries(a).forEach(d=>b(d[0],d[1])),Qa:a=>[...a.childNodes].forEach(b=>c.removeNode(b)),Wb:a=>{a=[...a];var b=(a[0]?.ownerDocument||J).createElement("div");a.forEach(d=>b.append(c.ha(d)));return b},ua:(a,b)=>Array.prototype.map.call(a,b?d=>c.ha(d.cloneNode(!0)):d=>d.cloneNode(!0)),pa:(a,b)=>{c.g.Qa(a);b&&a.append(...b)},xa:(a,b)=>{if(a.length){for(b=8===b.nodeType&&b.parentNode||b;a.length&&a[0].parentNode!==b;)a.splice(0, 1);for(;1<a.length&&a[a.length-1].parentNode!==b;)--a.length;if(1<a.length){b=a[0];var d=a[a.length-1];for(a.length=0;b!==d;)a.push(b),b=b.nextSibling;a.push(d)}}return a},Bb:a=>null==a?"":a.trim?a.trim():a.toString().replace(/^[\s\xa0]+|[\s\xa0]+$/g,""),Pa:a=>a.ownerDocument.documentElement.contains(1!==a.nodeType?a.parentNode:a),Db:(a,b)=>{if(!a?.nodeType)throw Error("element must be a DOM node when calling triggerEvent");a.dispatchEvent(new Event(b))},h:a=>c.W(a)?a():a,Za:(a,b)=>a.textContent= c.g.h(b)};c.U("utils",c.g);c.U("unwrap",c.g.h);(()=>{let a=0,b="__ko__"+Date.now(),d=new WeakMap;c.g.l={get:(e,g)=>(d.get(e)||{})[g],set:(e,g,l)=>{d.has(e)?d.get(e)[g]=l:d.set(e,{[g]:l});return l},Ra(e,g,l){return this.get(e,g)||this.set(e,g,l)},clear:e=>d.delete(e),Z:()=>a++ +b}})();c.g.N=(()=>{var a=c.g.l.Z(),b={1:1,8:1,9:1},d={1:1,9:1};const e=(f,h)=>{var k=c.g.l.get(f,a);h&&!k&&(k=new Set,c.g.l.set(f,a,k));return k},g=f=>{var h=e(f);h&&(new Set(h)).forEach(k=>k(f));c.g.l.clear(f);d[f.nodeType]&& l(f.childNodes,!0)},l=(f,h)=>{for(var k=[],m,p=0;p<f.length;p++)if(!h||8===f[p].nodeType)if(g(k[k.length]=m=f[p]),f[p]!==m)for(;p--&&!k.includes(f[p]););};return{addDisposeCallback:(f,h)=>{if("function"!=typeof h)throw Error("Callback must be a function");e(f,1).add(h)},Ya:(f,h)=>{var k=e(f);k&&(k.delete(h),k.size||c.g.l.set(f,a,null))},ha:f=>{c.u.I(()=>{b[f.nodeType]&&(g(f),d[f.nodeType]&&l(f.getElementsByTagName("*")))});return f},removeNode:f=>{c.ha(f);f.parentNode&&f.parentNode.removeChild(f)}}})(); c.ha=c.g.N.ha;c.removeNode=c.g.N.removeNode;c.U("utils.domNodeDisposal",c.g.N);c.extenders={debounce:(a,b)=>a.Da(d=>da(d,b)),rateLimit:(a,b)=>a.Da(d=>ca(d,b)),notify:(a,b)=>{a.ka="always"==b?null:L}};class ha{constructor(a,b,d){this.S=a;this.eb=b;this.za=d;this.Ha=!1;this.H=this.da=null}dispose(){this.Ha||(this.H&&c.g.N.Ya(this.da,this.H),this.Ha=!0,this.za(),this.S=this.eb=this.za=this.da=this.H=null)}s(a){this.da=a;c.g.N.addDisposeCallback(a,this.H=this.dispose.bind(this))}}c.P=function(){Object.setPrototypeOf(this, N);N.init(this)};var N={init:a=>{a.R=new Map;a.R.set("change",new Set);a.jb=1},subscribe:function(a,b,d){var e=this;d=d||"change";var g=new ha(e,b?a.bind(b):a,()=>{e.R.get(d).delete(g);e.Ia?.(d)});e.Ja?.(d);e.R.has(d)||e.R.set(d,new Set);e.R.get(d).add(g);return g},B(a,b){b=b||"change";"change"===b&&this.Ea();if(this.na(b)){b="change"===b&&this.Eb||new Set(this.R.get(b));try{c.u.nb(),b.forEach(d=>{d.Ha||d.eb(a)})}finally{c.u.end()}}},ya(){return this.jb},Rb(a){return this.ya()!==a},Ea(){++this.jb}, Da(a){var b=this,d=c.W(b),e,g,l,f,h;b.ra||(b.ra=b.B,b.B=(m,p)=>{p&&"change"!==p?"beforeChange"===p?b.gb(m):b.ra(m,p):b.hb(m)});var k=a(()=>{b.ea=!1;d&&f===b&&(f=b.fb?b.fb():b());var m=g||h&&b.Ba(l,f);h=g=e=!1;m&&b.ra(l=f)});b.hb=(m,p)=>{p&&b.ea||(h=!p);b.Eb=new Set(b.R.get("change"));b.ea=e=!0;f=m;k()};b.gb=m=>{e||(l=m,b.ra(m,"beforeChange"))};b.ib=()=>{h=!0};b.Gb=()=>{b.Ba(l,b.L(!0))&&(g=!0)}},na(a){return(this.R.get(a)||[]).size},Ba(a,b){return!this.ka||!this.ka(a,b)},toString:()=>"[object Object]", extend:function(a){var b=this;a&&c.g.K(a,(d,e)=>{d=c.extenders[d];"function"==typeof d&&(b=d(b,e)||b)});return b}};c.P.fn=Object.setPrototypeOf(N,Function.prototype);c.Vb=a=>"function"==typeof a?.subscribe&&"function"==typeof a.B;(()=>{let a=[],b,d=0;c.u={nb:e=>{a.push(b);b=e},end:()=>b=a.pop(),zb:e=>{if(b){if(!c.Vb(e))throw Error("Only subscribable things can act as dependencies");b.Jb.call(b.Kb,e,e.Fb||(e.Fb=++d))}},I(e,g,l){try{return a.push(b),b=void 0,e.apply(g,l||[])}finally{b=a.pop()}},ma:()=> b?.o.ma(),Ca:()=>b?.Ca,o:()=>b?.o}})();const A=Symbol("_latestValue");c.$=a=>{function b(){if(0<arguments.length)return b.Ba(b[A],arguments[0])&&(b.cb(),b[A]=arguments[0],b.valueHasMutated()),this;c.u.zb(b);return b[A]}b[A]=a;Object.defineProperty(b,"length",{get:()=>b[A]?.length});c.P.fn.init(b);return Object.setPrototypeOf(b,E)};var E={toJSON:function(){let a=this[A];return a?.toJSON?.()||a},ka:L,L(){return this[A]},valueHasMutated:function(){this.B(this[A],"spectate");this.B(this[A])},cb(){this.B(this[A], "beforeChange")}};Object.setPrototypeOf(E,c.P.fn);var D=c.$.Zb="__ko_proto__";E[D]=c.$;c.W=a=>{if((a="function"==typeof a&&a[D])&&a!==E[D]&&a!==c.o.fn[D])throw Error("Invalid object that looks like an observable; possibly from another Knockout instance");return!!a};c.vb=a=>"function"==typeof a&&(a[D]===E[D]||a[D]===c.o.fn[D]&&a.Sb);c.U("observable",c.$);c.U("isObservable",c.W);c.U("observable.fn",E);c.observableArray=a=>{a=a||[];if(!Array.isArray(a))throw Error("The argument passed when initializing an observable array must be an array, or null, or undefined."); return Object.setPrototypeOf(c.$(a),c.observableArray.fn).extend({trackArrayChanges:!0})};const S=Symbol("IS_OBSERVABLE_ARRAY");c.observableArray.fn=Object.setPrototypeOf({[S]:1,remove:function(a){for(var b=this.L(),d=!1,e="function"!=typeof a||c.W(a)?f=>f===a:a,g=b.length;g--;){var l=b[g];if(e(l)){if(b[g]!==l)throw Error("Array modified during remove; cannot remove item");d||this.cb();d=!0;b.splice(g,1)}}d&&this.valueHasMutated()}},c.$.fn);Object.getOwnPropertyNames(Array.prototype).forEach(a=>{"function"=== typeof Array.prototype[a]&&"constructor"!=a&&("copyWithin fill pop push reverse shift sort splice unshift".split(" ").includes(a)?c.observableArray.fn[a]=function(...b){var d=this.L();this.cb();this.pb(d,a,b);b=d[a](...b);this.valueHasMutated();return b===d?this:b}:c.observableArray.fn[a]=function(...b){return this()[a](...b)})});c.isObservableArray=a=>!(!a||!a[S]);c.extenders.trackArrayChanges=(a,b)=>{function d(){if(k){var q=[].concat(a.L()||[]);if(a.na("arrayChange")){if(!l||1<k)l=c.g.qb(m,q,a.Ka); var r=l}m=q;l=null;k=0;r?.length&&a.B(r,"arrayChange")}}function e(){g?d():(g=!0,h=a.subscribe(()=>++k,null,"spectate"),m=[].concat(a.L()||[]),l=null,f=a.subscribe(d))}a.Ka={};"object"==typeof b&&c.g.extend(a.Ka,b);a.Ka.sparse=!0;if(!a.pb){var g=!1,l=null,f,h,k=0,m,p=a.Ja,n=a.Ia;a.Ja=q=>{p?.call(a,q);"arrayChange"===q&&e()};a.Ia=q=>{n?.call(a,q);"arrayChange"!==q||a.na("arrayChange")||(f?.dispose(),h?.dispose(),h=f=null,g=!1,m=void 0)};a.pb=(q,r,u)=>{if(g&&!k){var t=[],z=q.length,w=u.length,y=0,B= (ia,ja,ka)=>t[t.length]={status:ia,value:ja,index:ka};switch(r){case "push":y=z;case "unshift":for(q=0;q<w;++q)B("added",u[q],y+q);break;case "pop":y=z-1;case "shift":z&&B("deleted",q[y],y);break;case "splice":y=Math.min(Math.max(0,0>u[0]?z+u[0]:u[0]),z);z=1===w?z:Math.min(y+(u[1]||0),z);w=y+w-2;r=Math.max(z,w);for(var v=[],F=[],C=2;y<r;++y,++C)y<z&&F.push(B("deleted",q[y],y)),y<w&&v.push(B("added",u[C],y));c.g.tb(F,v);break;default:return}l=t}}}};var x=Symbol("_state");c.o=(a,b)=>{function d(){if(0< arguments.length){if("function"!==typeof e)throw Error("Cannot write a value to a ko.computed unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters.");e(...arguments);return this}g.X||c.u.zb(d);(g.V||g.A&&d.oa())&&d.T();return g.J}"object"===typeof a?b=a:(b=b||{},a&&(b.read=a));if("function"!=typeof b.read)throw Error("Pass a function that returns the value of the ko.computed");var e=b.write,g={J:void 0,Y:!0,V:!0,Aa:!1,ab:!1,X:!1,Xa:!1,A:!1,yb:b.read, s:b.s||null,ia:b.ia,Oa:null,v:{},G:0,bc:null};d[x]=g;d.Sb="function"===typeof e;c.P.fn.init(d);Object.setPrototypeOf(d,K);b.pure&&(g.Xa=!0,g.A=!0,c.g.extend(d,la));g.s&&(g.ab=!0,g.s.nodeType||(g.s=null));g.A||d.T();g.s&&d.isActive()&&c.g.N.addDisposeCallback(g.s,g.Oa=()=>{d.dispose()});return d};var K={ka:L,ma(){return this[x].G},Pb(){var a=[];c.g.K(this[x].v,(b,d)=>a[d.fa]=d.S);return a},Sa(a){if(!this[x].G)return!1;var b=this.Pb();return b.includes(a)||!!b.find(d=>d.Sa&&d.Sa(a))},kb(a,b,d){if(this[x].Xa&& b===this)throw Error("A 'pure' computed must not be called recursively");this[x].v[a]=d;d.fa=this[x].G++;d.ga=b.ya()},oa(){var a,b=this[x].v;for(a in b)if(Object.prototype.hasOwnProperty.call(b,a)){var d=b[a];if(this.qa&&d.S.ea||d.S.Rb(d.ga))return!0}},dc(){this[x].Aa||this.qa?.(!1)},isActive(){var a=this[x];return a.V||0<a.G},ec(){this.ea?this[x].V&&(this[x].Y=!0):this.sb()},Cb(a){return a.subscribe(this.sb,this)},sb(){this.qa?this.qa(!0):this.T(!0)},T(a){var b=this[x],d=b.ia,e=!1;if(!b.Aa&&!b.X){if(b.s&& !c.g.Pa(b.s)||d?.()){if(!b.ab){this.dispose();return}}else b.ab=!1;try{b.Aa=!0,e=this.Nb(a)}finally{b.Aa=!1}return e}},Nb(a){var b=this[x],d=b.Xa?void 0:!b.G;var e={Lb:this,va:b.v,Ma:b.G};c.u.nb({Kb:e,Jb:fa,o:this,Ca:d});b.v={};b.G=0;a:{try{var g=b.yb();break a}finally{c.u.end(),e.Ma&&!b.A&&c.g.K(e.va,ea),b.Y=b.V=!1}g=void 0}b.G?e=this.Ba(b.J,g):(this.dispose(),e=!0);e&&(b.A?this.Ea():this.B(b.J,"beforeChange"),b.J=g,this.B(b.J,"spectate"),!b.A&&a&&this.B(b.J),this.ib&&this.ib());d&&this.B(b.J,"awake"); return e},L(a){var b=this[x];(b.V&&(a||!b.G)||b.A&&this.oa())&&this.T();return b.J},Da(a){var b=this;c.P.fn.Da.call(b,a);b.fb=()=>{b[x].A||(b[x].Y?b.T():b[x].V=!1);return b[x].J};b.qa=d=>{b.gb(b[x].J);b[x].V=!0;d&&(b[x].Y=!0);b.hb(b,!d)}},dispose:function(){var a=this[x];!a.A&&a.v&&c.g.K(a.v,(b,d)=>d.dispose?.());a.s&&a.Oa&&c.g.N.Ya(a.s,a.Oa);a.v=void 0;a.G=0;a.X=!0;a.Y=!1;a.V=!1;a.A=!1;a.s=void 0;a.ia=void 0;a.yb=void 0}},la={Ja(a){var b=this,d=b[x];if(!d.X&&d.A&&"change"==a){d.A=!1;if(d.Y||b.oa())d.v= null,d.G=0,b.T()&&b.Ea();else{var e=[];c.g.K(d.v,(g,l)=>e[l.fa]=g);e.forEach((g,l)=>{var f=d.v[g],h=b.Cb(f.S);h.fa=l;h.ga=f.ga;d.v[g]=h});b.oa()&&b.T()&&b.Ea()}d.X||b.B(d.J,"awake")}},Ia(a){var b=this[x];b.X||"change"!=a||this.na("change")||(c.g.K(b.v,(d,e)=>{e.dispose&&(b.v[d]={S:e.S,fa:e.fa,ga:e.ga},e.dispose())}),b.A=!0,this.B(void 0,"asleep"))},ya(){var a=this[x];a.A&&(a.Y||this.oa())&&this.T();return c.P.fn.ya.call(this)}};Object.setPrototypeOf(K,c.P.fn);var O=c.$.Zb;K[O]=c.o;c.o.fn=K;c.U("computed", c.o);c.isComputed=a=>"function"==typeof a&&a[O]===K[O];c.xb=a=>{if("function"===typeof a)return c.o(a,{pure:!0});a={...a,pure:!0};return c.o(a)};c.C={M:a=>{switch(a.nodeName){case "OPTION":return!0===a.__ko__hasDomDataOptionValue__?c.g.l.get(a,c.i.options.Wa):a.value;case "SELECT":return 0<=a.selectedIndex?c.C.M(a.options[a.selectedIndex]):void 0;default:return a.value}},Fa:(a,b)=>{switch(a.nodeName){case "OPTION":"string"===typeof b?(c.g.l.set(a,c.i.options.Wa,void 0),delete a.__ko__hasDomDataOptionValue__, a.value=b):(c.g.l.set(a,c.i.options.Wa,b),a.__ko__hasDomDataOptionValue__=!0,a.value="number"===typeof b?b:"");break;case "SELECT":for(var d=-1,e=""===(b??""),g=a.options.length,l;g--;)if(l=c.C.M(a.options[g]),l==b||""===l&&e){d=g;break}if(0<=d||e&&1<a.size)a.selectedIndex=d;break;default:a.value=b??""}}};c.la=(()=>{var a=RegExp("\"(?:\\\\.|[^\"])*\"|'(?:\\\\.|[^'])*'|`(?:\\\\.|[^`])*`|/\\*(?:[^*]|\\*+[^*/])*\\*+/|//.*\n|/(?:\\\\.|[^/])+/w*|[^\\s:,/][^,\"'`{}()/:[\\]]*[^\\s,\"'`{}()/:[\\]]|[^\\s]", "g"),b=/[\])"'A-Za-z0-9_$]+$/,d={"in":1,"return":1,"typeof":1};return{Yb:e=>{e=c.g.Bb(e);123===e.charCodeAt(0)&&(e=e.slice(1,-1));e+="\n,";var g=[],l=e.match(a),f=[],h=0;if(1<l.length){for(var k=0,m;m=l[k++];){var p=m.charCodeAt(0);if(44===p){if(0>=h){n&&f.length&&g.push("'"+n+"':()=>("+f.join("")+")");var n=h=0;f=[];continue}}else if(58===p){if(!h&&!n&&1===f.length){n=f.pop();continue}}else if(47===p&&1<m.length&&(47===m.charCodeAt(1)||42===m.charCodeAt(1)))continue;else 47===p&&k&&1<m.length?(p= l[k-1].match(b))&&!d[p[0]]&&(e=e.slice(e.indexOf(m)+1),l=e.match(a),k=-1,m="/"):40===p||123===p||91===p?++h:41===p||125===p||93===p?--h:n||f.length||34!==p&&39!==p||(m=m.slice(1,-1));f.push(m)}if(0<h)throw Error("Unbalanced parentheses, braces, or brackets");}g.push("'$data':()=>$data");return g.join(",")},cc:(e,g)=>-1<e.findIndex(l=>l.key==g),Ga:(e,g,l,f,h,k)=>{g&&c.W(g)?!c.vb(g)||k&&g.L()===h||g(h):(console.error(`"${f}" should be observable in ${e.outerHTML.replace(/>.+/,">")}`),l.get("$data")[f]= h)}}})();(()=>{function a(f){return 8==f.nodeType&&e.test(f.nodeValue)}function b(f){return 8==f.nodeType&&g.test(f.nodeValue)}function d(f,h){for(var k=f,m=1,p=[];k=k.nextSibling;){if(b(k)&&(c.g.l.set(k,l,!0),!--m))return p;p.push(k);a(k)&&++m}if(!h)throw Error("Cannot find closing comment tag to match: "+f.nodeValue);return null}var e=/^\s*ko(?:\s+([\s\S]+))?\s*$/,g=/^\s*\/ko\s*$/,l="__ko_matchedEndComment__";c.m={aa:{},childNodes:f=>a(f)?d(f):f.childNodes,ja:f=>{a(f)?(f=d(f))&&[...f].forEach(h=> c.removeNode(h)):c.g.Qa(f)},pa:(f,h)=>{a(f)?(c.m.ja(f),f.after(...h)):c.g.pa(f,h)},prepend:(f,h)=>{a(f)?f.nextSibling.before(h):f.prepend(h)},Ub:(f,h,k)=>{k?k.after(h):c.m.prepend(f,h)},firstChild:f=>{if(a(f))return f=f.nextSibling,!f||b(f)?null:f;let h=f.firstChild;if(h&&b(h))throw Error("Found invalid end comment, as the first child of "+f);return h},nextSibling:f=>{if(a(f)){var h=d(f,void 0);f=h?(h.length?h[h.length-1]:f).nextSibling:null}if((h=f.nextSibling)&&b(h)){if(b(h)&&!c.g.l.get(h,l))throw Error("Found end comment without a matching opening comment, as child of "+ f);return null}return h},Qb:a,ac:f=>(f=f.nodeValue.match(e))?f[1]:null}})();const T=new Map;c.ob=new class{Xb(a){switch(a.nodeType){case 1:return null!=a.getAttribute("data-bind");case 8:return c.m.Qb(a)}return!1}Ob(a,b){a:{switch(a.nodeType){case 1:var d=a.getAttribute("data-bind");break a;case 8:d=c.m.ac(a);break a}d=null}if(d)try{let g=T.get(d);if(!g){var e="with($data){return{"+c.la.Yb(d)+"}}";g=new Function("$context","$root","$parent","$data","$element",e);T.set(d,g)}return g(b,b.$root,b.$parent, b.$data||{},a)}catch(g){throw g.message="Unable to parse bindings.\nBindings value: "+d+"\nMessage: "+g.message,g;}return null}};const G=Symbol("_subscribable"),H=Symbol("_ancestorBindingInfo"),U=Symbol("_dataDependency"),V={},I=c.g.l.Z();c.i={};c.ba=class{constructor(a,b,d,e){var g=this,l=a===V,f=l?void 0:a,h="function"==typeof f&&!c.W(f),k=e?.dataDependency;a=()=>{var p=h?f():f;p=c.g.h(p);b?(c.g.extend(g,b),H in b&&(g[H]=b[H])):g.$root=p;g[G]=m;l?p=g.$data:g.$data=p;d?.(g,b,p);if(b?.[G]&&!c.u.o().Sa(b[G]))b[G](); k&&(g[U]=k);return g.$data};if(e?.exportDependencies)a();else{var m=c.xb(a);m.L();m.isActive()?m.ka=null:g[G]=void 0}}createChildContext(a,b){return new c.ba(a,this,(d,e)=>{d.$parent=e.$data;b.extend?.(d)},b)}extend(a,b){return new c.ba(V,this,d=>c.g.extend(d,"function"==typeof a?a(d):a),b)}};const W=a=>{a=c.g.l.get(a,I);var b=a?.D;b&&(a.D=null,b.wb())};class ma{constructor(a,b,d){this.H=a;this.da=b;this.ta=new Set;this.F=!1;b.D||c.g.N.addDisposeCallback(a,W);d?.D&&(d.D.ta.add(a),this.za=d)}wb(){this.za?.D?.Mb(this.H)}Mb(a){this.ta.delete(a); this.ta.size||this.rb?.()}rb(){this.F=!0;this.da.D&&!this.ta.size&&(this.da.D=null,c.g.N.Ya(this.H,W),c.j.notify(this.H,c.j.ca),this.wb())}}c.j={F:"childrenComplete",ca:"descendantsComplete",subscribe:(a,b,d,e,g)=>{var l=c.g.l.Ra(a,I,{});l.wa||(l.wa=new c.P);g?.notifyImmediately&&l.Va[b]&&c.u.I(d,e,[a]);return l.wa.subscribe(d,e,b)},notify:(a,b)=>{var d=c.g.l.get(a,I);if(d&&(d.Va[b]=!0,d.wa?.B(a,b),b==c.j.F))if(d.D)d.D.rb();else if(void 0===d.D&&d.wa?.na(c.j.ca))throw Error("descendantsComplete event not supported for bindings on this node"); },$a:(a,b)=>{var d=c.g.l.Ra(a,I,{});d.D||(d.D=new ma(a,d,b[H]));return b[H]==d?b:b.extend(e=>{e[H]=d})}};const Y=(a,b)=>{for(var d,e=c.m.firstChild(b);d=e;)e=c.m.nextSibling(d),X(a,d);c.j.notify(b,c.j.F)},X=(a,b)=>{var d=a;if(1===b.nodeType||c.ob.Xb(b))d=Z(b,null,a);d&&!b.matches?.("SCRIPT,TEXTAREA,TEMPLATE")&&Y(d,b)},na=a=>{var b=[],d={},e=[],g=l=>{if(!d[l]){var f=c.i[l];f&&(f.after&&(e.push(l),f.after.forEach(h=>{if(a[h]){if(e.includes(h))throw Error("Cannot combine the following bindings, because they have a cyclic dependency: "+ e.join(", "));g(h)}}),e.length--),b.push({key:l,ub:f}));d[l]=!0}};c.g.K(a,g);return b},Z=(a,b,d)=>{var e=c.g.l.Ra(a,I,{}),g=e.Hb;if(!b){if(g)throw Error("You cannot apply bindings multiple times to the same element.");e.Hb=!0}g||(e.context=d);e.Va||(e.Va={});if(b&&"function"!==typeof b)var l=b;else{var f=c.o(()=>{if(l=b?b(d,a):c.ob.Ob(a,d))d[G]?.(),d[U]?.();return l},{s:a});l&&f.isActive()||(f=null)}var h=d,k;if(l){var m=f?n=>()=>f()[n]():n=>l[n],p={get:n=>l[n]&&m(n)(),has:n=>n in l};c.j.F in l&& c.j.subscribe(a,c.j.F,()=>{var n=l[c.j.F]();if(n){var q=c.m.childNodes(a);q.length&&n(q,c.dataFor(q[0]))}});c.j.ca in l&&(h=c.j.$a(a,d),c.j.subscribe(a,c.j.ca,()=>{var n=l[c.j.ca]();n&&c.m.firstChild(a)&&n(a)}));na(l).forEach(n=>{var q=n.ub.init,r=n.ub.update,u=n.key;if(8===a.nodeType&&!c.m.aa[u])throw Error("The binding '"+u+"' cannot be used with comment nodes");try{"function"==typeof q&&c.u.I(()=>{var t=q(a,m(u),p,h.$data,h);if(t&&t.controlsDescendantBindings){if(void 0!==k)throw Error("Multiple bindings ("+ k+" and "+u+") are trying to control descendant bindings of the same element. You cannot use these bindings together on the same element.");k=u}}),"function"==typeof r&&c.o(()=>r(a,m(u),p,h.$data,h),{s:a})}catch(t){throw t.message='Unable to process binding "'+u+": "+l[u]+'"\nMessage: '+t.message,t;}})}return void 0===k&&h};c.$b=a=>c.g.l.get(a,I)?.context;const P=a=>a&&a instanceof c.ba?a:new c.ba(a);c.applyBindingAccessorsToNode=(a,b,d)=>Z(a,b,P(d));c.mb=(a,b)=>{1!==b.nodeType&&8!==b.nodeType||Y(P(a), b)};c.Ib=(a,b)=>X(P(a),b);c.dataFor=a=>([1,8].includes(a?.nodeType)&&c.$b(a))?.$data;c.U("bindingHandlers",c.i);(()=>{var a=Object.create(null),b=new Map;c.components={get:(l,f)=>{if(b.has(l))f(b.get(l));else{var h=a[l];h?h.subscribe(f):(h=a[l]=new c.P,h.subscribe(f),g(l,k=>{b.set(l,k);delete a[l];h.B(k)}))}},register:(l,f)=>{if(!f)throw Error("Invalid configuration for "+l);if(d[l])throw Error("Component "+l+" is already registered");d[l]=f}};var d=Object.create(null),e=(l,f)=>{throw Error(`Component '${l}': ${f}`); },g=(l,f)=>{var h={},k=d[l]||{},m=k.template;k=k.viewModel;if(m){m.element||e(l,"Unknown template value: "+m);m=m.element;var p=J.getElementById(m);p||e(l,"Cannot find element with ID "+m);p.matches("TEMPLATE")||e(l,"Template Source Element not a <template>");h.template=c.g.ua(p.content.childNodes)}k&&("function"!==typeof k.createViewModel&&e(l,"Unknown viewModel value: "+k),h.createViewModel=k.createViewModel);f(h.template&&h.createViewModel?h:null)}})();(()=>{var a=0;c.i.component={init:(b,d,e, g,l)=>{var f,h,k,m=()=>{var p=f&&f.dispose;"function"===typeof p&&p.call(f);k&&k.dispose();h=f=k=null};c.m.ja(b);c.g.N.addDisposeCallback(b,m);c.o(()=>{var p=c.g.h(d());if("string"!==typeof p){var n=c.g.h(p.params);p=c.g.h(p.name)}if(!p)throw Error("No component name specified");var q=c.j.$a(b,l),r=h=++a;c.components.get(p,u=>{if(h===r){m();if(!u)throw Error("Unknown component '"+p+"'");var t=u.template;if(!t)throw Error("Component '"+p+"' has no template");c.m.pa(b,c.g.ua(t));f=u.createViewModel(n, {element:b});c.mb(q.createChildContext(f,{}),b)}})},{s:b});return{controlsDescendantBindings:!0}}};c.m.aa.component=!0})();c.i.attr={update:(a,b)=>{b=c.g.h(b())||{};c.g.K(b,function(d,e){e=c.g.h(e);var g=d.indexOf(":");g="lookupNamespaceURI"in a&&0<g&&a.lookupNamespaceURI(d.slice(0,g));!1===e||null==e?g?a.removeAttributeNS(g,d):a.removeAttribute(d):(e=e.toString(),g?a.setAttributeNS(g,d,e):a.setAttribute(d,e))})}};(()=>{c.i.checked={after:["value","attr"],init:function(a,b,d){var e="checkbox"==a.type, g="radio"==a.type;if(e||g){const n=c.xb(()=>{if(d.has("checkedValue"))return c.g.h(d.get("checkedValue"));if(m)return d.has("value")?c.g.h(d.get("value")):a.value});var l=()=>{if(!c.u.Ca()){var q=a.checked,r=n();if(q||!g&&!c.u.ma()){var u=c.u.I(b);if(h){var t=k?u.L():u,z=p;p=r;z!==r?q&&(t.push(r),t.remove(z)):q?t.push(r):t.remove(r);k&&c.vb(u)&&u(t)}else e&&(void 0===r?r=q:q||(r=void 0)),c.la.Ga(a,u,d,"checked",r,!0)}}},f=b(),h=e&&c.g.h(f)instanceof Array,k=!(h&&f.push&&f.splice),m=g||h,p=h?n():void 0; c.o(l,null,{s:a});a.addEventListener("click",l);c.o(()=>{var q=c.g.h(b()),r=n();h?(a.checked=q.includes(r),p=r):a.checked=e&&void 0===r?!!q:n()===q},null,{s:a});f=void 0}}};c.i.checkedValue={update:function(a,b){a.value=c.g.h(b())}}})();var Q=(a,b,d)=>b&&b.split(/\s+/).forEach(e=>a.classList.toggle(e,d));c.i.css={update:(a,b)=>{b=c.g.h(b());"object"==typeof b?c.g.K(b,(d,e)=>{e=c.g.h(e);Q(a,d,!!e)}):(b=c.g.Bb(b),Q(a,a.__ko__cssValue,!1),a.__ko__cssValue=b,Q(a,b,!0))}};c.i.enable={update:(a,b)=>{(b= c.g.h(b()))&&a.disabled?a.removeAttribute("disabled"):b||a.disabled||(a.disabled=!0)}};c.i.disable={update:(a,b)=>c.i.enable.update(a,()=>!c.g.h(b()))};c.i.event={init:function(a,b,d,e,g){c.g.K(b()||{},l=>{"string"==typeof l&&a.addEventListener(l,(...f)=>{var h=b()[l];if(h)try{e=g.$data;var k=h.apply(e,[e,...f])}finally{!0!==k&&f[0].preventDefault()}})})}};const aa=a=>()=>{var b=a(),d=c.W(b)?b.L():b;if(!d||Array.isArray(d))return{foreach:b};c.g.h(b);return{foreach:d.data}};c.i.foreach={init:(a,b)=> c.i.template.init(a,aa(b)),update:(a,b,d,e,g)=>c.i.template.update(a,aa(b),d,e,g)};c.m.aa.foreach=!0;c.i.hasfocus={init:(a,b,d)=>{var e=l=>{a.__ko_hasfocusUpdating=!0;l=a.ownerDocument.activeElement===a;c.la.Ga(a,b(),d,"hasfocus",l,!0);a.__ko_hasfocusLastValue=l;a.__ko_hasfocusUpdating=!1},g=e.bind(null,!0);e=e.bind(null,!1);a.addEventListener("focus",g);a.addEventListener("focusin",g);a.addEventListener("blur",e);a.addEventListener("focusout",e);a.__ko_hasfocusLastValue=!1},update:(a,b)=>{b=!!c.g.h(b()); a.__ko_hasfocusUpdating||a.__ko_hasfocusLastValue===b||(b?a.focus():a.blur())}};c.i.html={init:()=>({controlsDescendantBindings:!0}),update:(a,b)=>{c.g.Qa(a);b=c.g.h(b());if(null!=b){const d=J.createElement("template");d.innerHTML="string"!=typeof b?b.toString():b;a.appendChild(d.content)}}};(()=>{function a(b,d,e){c.i[b]={init:(g,l,f,h,k)=>{var m,p={};d&&(p={exportDependencies:!0});var n=f.has(c.j.ca);c.o(()=>{var q=c.g.h(l()),r=!e!==!q,u=!m;n&&(k=c.j.$a(g,k));if(r){p.dataDependency=c.u.o();var t= d?k.createChildContext("function"==typeof q?q:l,p):c.u.ma()?k.extend(null,p):k}u&&c.u.ma()&&(m=c.g.ua(c.m.childNodes(g),!0));r?(u||c.m.pa(g,c.g.ua(m)),c.mb(t,g)):(c.m.ja(g),c.j.notify(g,c.j.F))},{s:g});return{controlsDescendantBindings:!0}}};c.m.aa[b]=!0}a("if");a("ifnot",!1,!0);a("with",!0)})();var ba={};c.i.options={init:a=>{if(!a.matches("SELECT"))throw Error("options binding applies only to SELECT elements");let b=a.length;for(;b--;)a.remove(b);return{controlsDescendantBindings:!0}},update:(a, b,d)=>{var e=a.multiple,g=0!=a.length&&e?a.scrollTop:null,l=c.g.h(b()),f=[];b=()=>Array.from(a.options).filter(n=>n.selected);var h=(n,q,r)=>{var u=typeof q;return"function"==u?q(n):"string"==u?n[q]:r},k=(n,q)=>{f.length&&(n=f.includes(c.C.M(q[0])),q[0].selected=n,p&&!n&&c.u.I(c.g.Db,null,[a,"change"]))};e?f=b().map(c.C.M):0<=a.selectedIndex&&f.push(c.C.M(a.options[a.selectedIndex]));if(l){Array.isArray(l)||(l=[l]);var m=l.filter(n=>n??1)}var p=!1;l=k;d.has("optionsAfterRender")&&"function"==typeof d.get("optionsAfterRender")&& (l=(n,q)=>{k(n,q);c.u.I(d.get("optionsAfterRender"),null,[q[0],n!==ba?n:void 0])});c.g.Ab(a,m,(n,q,r)=>{r.length&&(f=r[0].selected?[c.C.M(r[0])]:[],p=!0);q=a.ownerDocument.createElement("option");n===ba?(c.g.Za(q),c.C.Fa(q,void 0)):(r=h(n,d.get("optionsValue"),n),c.C.Fa(q,c.g.h(r)),n=h(n,d.get("optionsText"),r),c.g.Za(q,n));return[q]},{},l);m=f.length;(e?m&&b().length<m:m&&0<=a.selectedIndex?c.C.M(a.options[a.selectedIndex])!==f[0]:m||0<=a.selectedIndex)&&c.u.I(c.g.Db,null,[a,"change"]);c.u.Ca()&& c.j.notify(a,c.j.F);g&&20<Math.abs(g-a.scrollTop)&&(a.scrollTop=g)}};c.i.options.Wa=c.g.l.Z();c.i.style={update:(a,b)=>{c.g.K(c.g.h(b()||{}),(d,e)=>{e=c.g.h(e);if(null==e||!1===e)e="";if(/^--/.test(d))a.style.setProperty(d,e);else{d=d.replace(/-(\w)/g,(l,f)=>f.toUpperCase());var g=a.style[d];a.style[d]=e;e===g||a.style[d]!=g||isNaN(e)||(a.style[d]=e+"px")}})}};c.i.submit={init:(a,b,d,e,g)=>{if("function"!=typeof b())throw Error("The value for a submit binding must be a function");a.addEventListener("submit", l=>{var f=b();try{var h=f.call(g.$data,a)}finally{!0!==h&&l.preventDefault()}})}};c.i.text={init:()=>({controlsDescendantBindings:!0}),update:(a,b)=>{8===a.nodeType&&(a.text||a.after(a.text=J.createTextNode("")),a=a.text);c.g.Za(a,b())}};c.m.aa.text=!0;c.i.textInput={init:(a,b,d)=>{var e=a.value,g,l,f=()=>{clearTimeout(g);l=g=void 0;var k=a.value;a.checkValidity()&&e!==k&&(e=k,c.la.Ga(a,b(),d,"textInput",k))},h=()=>{var k=c.g.h(b())??"";void 0!==l&&k===l?setTimeout(h,4):a.value!==k&&(a.value=k,e= a.value)};a.addEventListener("input",f);a.addEventListener("change",f);c.o(h,{s:a})}};c.i.value={init:(a,b,d)=>{var e=a.matches("SELECT"),g=a.matches("INPUT");if(!g||"checkbox"!=a.type&&"radio"!=a.type){var l=new Set,f=d.get("valueUpdate"),h=null,k=()=>{h=null;var n=b(),q=c.C.M(a);c.la.Ga(a,n,d,"value",q)};f&&("string"==typeof f?l.add(f):f.forEach(n=>l.add(n)),l.delete("change"));l.forEach(n=>{var q=k;(n||"").startsWith("after")&&(q=()=>{h=c.C.M(a);setTimeout(k,0)},n=n.slice(5));a.addEventListener(n, q)});var m=g&&"file"==a.type?()=>{var n=c.g.h(b());null==n||""===n?a.value="":c.u.I(k)}:()=>{var n=c.g.h(b()),q=c.C.M(a);if(null!==h&&n===h)setTimeout(m,0);else if(n!==q||void 0===q)e?(c.C.Fa(a,n),n!==c.C.M(a)&&c.u.I(k)):c.C.Fa(a,n)};if(e){var p;c.j.subscribe(a,c.j.F,()=>{p?d.get("valueAllowUnset")?m():k():(a.addEventListener("change",k),p=c.o(m,{s:a}))},null,{notifyImmediately:!0})}else a.addEventListener("change",k),c.o(m,{s:a})}else c.applyBindingAccessorsToNode(a,{checkedValue:b})},update:()=> {}};c.i.visible={update:(a,b)=>{b=c.g.h(b());var d="none"!=a.style.display;b&&!d?a.style.display="":d&&!b&&(a.style.display="none")}};c.i.hidden={update:(a,b)=>a.hidden=!!c.g.h(b())};(function(a){c.i[a]={init:function(b,d,e,g,l){return c.i.event.init.call(this,b,()=>({[a]:d()}),e,g,l)}}})("click");(()=>{let a=c.g.l.Z();class b{constructor(e){this.Na=e}Ua(...e){let g=this.Na;if(!e.length)return c.g.l.get(g,a)||(11===this.H?g.content:1===this.H?g:void 0);c.g.l.set(g,a,e[0])}}class d extends b{constructor(e){super(e); e&&(this.H=e.matches("TEMPLATE")&&e.content?e.content.nodeType:1)}}c.bb={Na:d,lb:b}})();(()=>{const a=(h,k,m)=>{var p;for(k=c.m.nextSibling(k);h&&(p=h)!==k;)h=c.m.nextSibling(p),m(p,h)},b=(h,k)=>{if(h.length){var m=h[0],p=m.parentNode;a(m,h[h.length-1],n=>(1===n.nodeType||8===n.nodeType)&&c.Ib(k,n));c.g.xa(h,p)}},d=(h,k,m,p)=>{var n=(h&&(h.nodeType?h:0<h.length?h[0]:null)||m||{}).ownerDocument;if("string"==typeof m){n=n||J;n=n.getElementById(m);if(!n)throw Error("Cannot find template with ID "+m); m=new c.bb.Na(n)}else if([1,8].includes(m.nodeType))m=new c.bb.lb(m);else throw Error("Unknown template type: "+m);m=(m=m.Ua?m.Ua():null)?[...m.cloneNode(!0).childNodes]:null;if(!Array.isArray(m)||0<m.length&&"number"!=typeof m[0].nodeType)throw Error("Template engine must return an array of DOM nodes");k&&(c.m.pa(h,m),b(m,p),c.j.notify(h,c.j.F));return m},e=(h,k,m)=>c.W(h)?h():"function"===typeof h?h(k,m):h,g=(h,k,m,p)=>{m=m||{};if(p){var n=p.nodeType?p:0<p.length?p[0]:null;return c.o(()=>{var q= k instanceof c.ba?k:new c.ba(k,null,null,{exportDependencies:!0}),r=e(h,q.$data,q);d(p,!0,r,q,m)},{ia:()=>!n||!c.g.Pa(n),s:n})}console.log("no targetNodeOrNodeArray")},l=(h,k,m,p,n)=>{var q,r=(w,y)=>{q=n.createChildContext(w,{extend:B=>B.$index=y});w=e(h,w,q);return d(p,!1,w,q,m)},u=(w,y)=>{b(y,q);q=null},t=(w,y)=>{c.u.I(c.g.Ab,null,[p,w,r,m,u,y]);c.j.notify(p,c.j.F)};if(c.isObservableArray(k)){t(k.L());var z=k.subscribe(w=>{t(k(),w)},null,"arrayChange");z.s(p);return z}return c.o(()=>{var w=c.g.h(k)|| [];Array.isArray(w)||(w=[w]);t(w)},{s:p})},f=c.g.l.Z();c.i.template={init:(h,k)=>{k=c.g.h(k());if("string"==typeof k||"name"in k)c.m.ja(h);else if(k=c.m.childNodes(h),k.length)k=c.g.Wb(k),(new c.bb.lb(h)).Ua(k);else throw Error("Anonymous template defined, but no template content was provided");return{controlsDescendantBindings:!0}},update:(h,k,m,p,n)=>{p=k();k=c.g.h(p);m=null;"string"==typeof k?k={}:p="name"in k?k.name:h;var q=!!p;"foreach"in k?m=l(p,q&&k.foreach||[],k,h,n):q?(m=n,"data"in k&&(m= n.createChildContext(k.data,{exportDependencies:!0})),m=g(p,m,k,h)):c.m.ja(h);n=m;c.g.l.get(h,f)?.dispose?.();c.g.l.set(h,f,!n||n.isActive&&!n.isActive()?void 0:n)}};c.m.aa.template=!0})();c.g.tb=(a,b,d)=>{var e=0,g,l=b.length;l&&a.every(f=>{g=b.findIndex(h=>f.value===h.value);0<=g&&(f.moved=b[g].index,b[g].moved=f.index,b.splice(g,1),e=g=0,--l);e+=l;return l&&(!d||e<d)})};c.g.qb=(()=>{var a=(b,d,e,g,l)=>{for(var f=Math.min,h=Math.max,k=[],m=-1,p=b.length,n,q=d.length,r=q-p||1,u=p+q+1,t,z,w;++m<= p;)for(z=t,k.push(t=[]),w=f(q,m+r),n=h(0,m-1);n<=w;n++)t[n]=n?m?b[m-1]===d[n-1]?z[n-1]:f(z[n]||u,t[n-1]||u)+1:n+1:m+1;f=[];h=[];r=[];m=p;for(n=q;m||n;)q=k[m][n]-1,n&&q===k[m][n-1]?h.push(f[f.length]={status:e,value:d[--n],index:n}):m&&q===k[m-1][n]?r.push(f[f.length]={status:g,value:b[--m],index:m}):(--n,--m,l.sparse||f.push({status:"retained",value:d[n]}));c.g.tb(r,h,10*p);return f.reverse()};return(b,d,e)=>{b=b||[];d=d||[];return b.length<d.length?a(b,d,"added","deleted",e):a(d,b,"deleted","added", e)}})();(()=>{function a(e,g,l,f,h){var k=[],m=c.o(()=>{var p=g(l,h,c.g.xa(k,e))||[];if(0<k.length){var n=k.nodeType?[k]:k;if(0<n.length){var q=n[0],r=q.parentNode;p.forEach(u=>r.insertBefore(u,q));n.forEach(u=>c.removeNode(u))}f&&c.u.I(f,null,[l,p,h])}k.length=0;k.push(...p)},{s:e,ia:()=>!!k.find(c.g.Pa)});return{O:k,La:m.isActive()?m:void 0}}var b=c.g.l.Z(),d=c.g.l.Z();c.g.Ab=(e,g,l,f,h,k)=>{g=g||[];Array.isArray(g)||(g=[g]);var m=c.g.l.get(e,b),p=[],n=0,q=0,r=[],u=[],t,z=v=>{t={sa:v,Ta:c.$(q++)}; p.push(t)},w=v=>{t=m[v];t.Ta(q++);c.g.xa(t.O,e);p.push(t)};if(m){if(!k||m&&m._countWaitingForRemove)k=c.g.qb(Array.prototype.map.call(m,C=>C.sa),g,{sparse:!0});let v,F;for(k.forEach(C=>{v=C.moved;F=C.index;switch(C.status){case "deleted":for(;n<F;)w(n++);void 0===v&&(t=m[n],t.La&&(t.La.dispose(),t.La=void 0),c.g.xa(t.O,e).length&&t&&r.push.apply(r,t.O));n++;break;case "added":for(;q<F;)w(n++);void 0!==v?(u.push(p.length),w(v)):z(C.value)}});q<g.length;)w(n++);p._countWaitingForRemove=0}else g.forEach(z); c.g.l.set(e,b,p);r.forEach(c.removeNode);var y=v=>{c.m.Ub(e,v,B);B=v};k=e.ownerDocument.activeElement;if(u.length)for(;null!=(g=u.shift());){for(t=p[g];g--;)if(f=p[g].O,f?.length){var B=f[f.length-1];break}t.O.forEach(y)}p.forEach(v=>{v.O||c.g.extend(v,a(e,l,v.sa,h,v.Ta));v.O.forEach(y);!v.Tb&&h&&(h(v.sa,v.O,v.Ta),v.Tb=!0,B=v.O[v.O.length-1])});e.ownerDocument.activeElement!=k&&k?.focus();[].forEach(v=>v&&(v.sa=d))}})();R.ko=M})(this); "use strict"; (() => { // source/node/TreeIterator.ts var SHOW_ELEMENT = 1; var SHOW_TEXT = 4; var SHOW_ELEMENT_OR_TEXT = 5; // source/node/TreeWalker.ts var FILTER_ACCEPT = NodeFilter.FILTER_ACCEPT; TreeWalker.prototype.previousPONode = function() { const root = this.root; let current = this.currentNode; let node; while (true) { node = current.lastChild; while (!node && current) { if (current === root) { break; } node = current.previousSibling; if (!node) { current = current.parentNode; } } if (!node) { return null; } const nodeType = node.nodeType; const nodeFilterType = nodeType === Node.ELEMENT_NODE ? NodeFilter.SHOW_ELEMENT : nodeType === Node.TEXT_NODE ? NodeFilter.SHOW_TEXT : 0; if (!!(nodeFilterType & this.whatToShow) && FILTER_ACCEPT === this.filter.acceptNode(node)) { this.currentNode = node; return node; } current = node; } }; var createTreeWalker = (root, whatToShow, filter) => document.createTreeWalker( root, whatToShow, { acceptNode: (node) => !filter || filter(node) ? FILTER_ACCEPT : NodeFilter.FILTER_SKIP } ); // source/Constants.ts var ELEMENT_NODE = 1; var TEXT_NODE = 3; var DOCUMENT_FRAGMENT_NODE = 11; var ZWS = "\u200B"; var ua = navigator.userAgent; var isMac = /Mac OS X/.test(ua); var isWin = /Windows NT/.test(ua); var isIOS = /iP(?:ad|hone)/.test(ua) || isMac && !!navigator.maxTouchPoints; var isAndroid = /Android/.test(ua); var isWebKit = /WebKit\//.test(ua); var ctrlKey = isMac || isIOS ? "Meta-" : "Ctrl-"; var cantFocusEmptyTextNodes = isWebKit; var notWS = /[^ \t\r\n]/; // source/node/Category.ts var inlineNodeNames = /^(?:#text|A(?:BBR|CRONYM)?|B(?:R|D[IO])?|C(?:ITE|ODE)|D(?:ATA|EL|FN)|EM|FONT|HR|I(?:MG|NS)?|KBD|Q|R(?:P|T|UBY)|S(?:AMP|MALL|PAN|TR(?:IKE|ONG)|U[BP])?|TIME|U|VAR|WBR)$/; var leafNodeNames = /* @__PURE__ */ new Set(["BR", "HR", "IMG"]); var UNKNOWN = 0; var INLINE = 1; var BLOCK = 2; var CONTAINER = 3; var cache = /* @__PURE__ */ new WeakMap(); var resetNodeCategoryCache = () => { cache = /* @__PURE__ */ new WeakMap(); }; var isLeaf = (node) => leafNodeNames.has(node.nodeName); var getNodeCategory = (node) => { switch (node.nodeType) { case TEXT_NODE: return INLINE; case ELEMENT_NODE: case DOCUMENT_FRAGMENT_NODE: if (cache.has(node)) { return cache.get(node); } break; default: return UNKNOWN; } let nodeCategory; if (!Array.from(node.childNodes).every(isInline)) { nodeCategory = CONTAINER; } else if (inlineNodeNames.test(node.nodeName)) { nodeCategory = INLINE; } else { nodeCategory = BLOCK; } cache.set(node, nodeCategory); return nodeCategory; }; var isInline = (node) => getNodeCategory(node) === INLINE; var isBlock = (node) => getNodeCategory(node) === BLOCK; var isContainer = (node) => getNodeCategory(node) === CONTAINER; // source/node/Node.ts var createElement = (tag, props, children) => { const el = document.createElement(tag); if (props instanceof Array) { children = props; props = null; } setAttributes(el, props); children && el.append(...children); return el; }; var areAlike = (node, node2) => { if (isLeaf(node)) { return false; } if (node.nodeType !== node2.nodeType || node.nodeName !== node2.nodeName) { return false; } if (node instanceof HTMLElement && node2 instanceof HTMLElement) { return node.nodeName !== "A" && node.className === node2.className && node.style.cssText === node2.style.cssText; } return true; }; var hasTagAttributes = (node, tag, attributes) => { if (node.nodeName !== tag) { return false; } for (const attr in attributes) { if (!("getAttribute" in node) || node.getAttribute(attr) !== attributes[attr]) { return false; } } return true; }; var getNearest = (node, root, tag, attributes) => { while (node && node !== root) { if (hasTagAttributes(node, tag, attributes)) { return node; } node = node.parentNode; } return null; }; var getNodeBeforeOffset = (node, offset) => { let children = node.childNodes; while (offset && node instanceof Element) { node = children[offset - 1]; children = node.childNodes; offset = children.length; } return node; }; var getNodeAfterOffset = (node, offset) => { let returnNode = node; if (returnNode instanceof Element) { const children = returnNode.childNodes; if (offset < children.length) { returnNode = children[offset]; } else { while (returnNode && !returnNode.nextSibling) { returnNode = returnNode.parentNode; } if (returnNode) { returnNode = returnNode.nextSibling; } } } return returnNode; }; var getLength = (node) => node instanceof Element || node instanceof DocumentFragment ? node.childNodes.length : node instanceof CharacterData ? node.length : 0; var empty = (node) => { const frag = document.createDocumentFragment(); frag.append(...node.childNodes); return frag; }; var detach = (node) => { node.parentNode?.removeChild(node); return node; }; var replaceWith = (node, node2) => node.parentNode?.replaceChild(node2, node); var getClosest = (node, root, selector) => { node = (node && !node.closest ? node.parentElement : node)?.closest(selector); return node && root.contains(node) ? node : null; }; var setAttributes = (node, props) => { props && Object.entries(props).forEach(([k, v]) => { if (null == v) { node.removeAttribute(k); } else if ("style" === k && typeof v === "object") { Object.entries(v).forEach(([k2, v2]) => node.style[k2] = v2); } else { node.setAttribute(k, v); } }); }; // source/node/Whitespace.ts var notWSTextNode = (node) => node instanceof Element ? node.nodeName === "BR" : ( // okay if data is 'undefined' here. notWS.test(node.data) ); var isLineBreak = (br) => { let block = br.parentNode; while (isInline(block)) { block = block.parentNode; } const walker = createTreeWalker( block, SHOW_ELEMENT_OR_TEXT, notWSTextNode ); walker.currentNode = br; return !!walker.nextNode(); }; var removeZWS = (root, keepNode) => { const walker = createTreeWalker(root, SHOW_TEXT); let textNode; let index; while (textNode = walker.nextNode()) { while ((index = textNode.data.indexOf(ZWS)) > -1 && // eslint-disable-next-line no-unmodified-loop-condition (!keepNode || textNode.parentNode !== keepNode)) { if (textNode.length === 1) { let node = textNode; let parent = node.parentNode; while (parent) { parent.removeChild(node); walker.currentNode = parent; if (!isInline(parent) || getLength(parent)) { break; } node = parent; parent = node.parentNode; } break; } else { textNode.deleteData(index, 1); } } } }; // source/range/Boundaries.ts var START_TO_START = 0; var END_TO_END = 2; var isNodeContainedInRange = (range, node, partial) => { if (partial) { return range.intersectsNode(node); } else { const nodeRange = document.createRange(); nodeRange.selectNode(node); const nodeStartAfterStart = range.compareBoundaryPoints(START_TO_START, nodeRange) < 1; const nodeEndBeforeEnd = range.compareBoundaryPoints(END_TO_END, nodeRange) > -1; return nodeStartAfterStart && nodeEndBeforeEnd; } }; var moveRangeBoundariesDownTree = (range) => { let { startContainer, startOffset, endContainer, endOffset } = range; while (!(startContainer instanceof Text)) { let child = startContainer.childNodes[startOffset]; if (!child || isLeaf(child)) { if (startOffset) { child = startContainer.childNodes[startOffset - 1]; if (child instanceof Text) { let textChild = child; let prev; while (!textChild.length && (prev = textChild.previousSibling) && prev instanceof Text) { textChild.remove(); textChild = prev; } startContainer = textChild; startOffset = textChild.length; } } break; } startContainer = child; startOffset = 0; } if (endOffset) { while (!(endContainer instanceof Text)) { const child = endContainer.childNodes[endOffset - 1]; if (!child || isLeaf(child)) { if (child && child.nodeName === "BR" && !isLineBreak(child)) { --endOffset; continue; } break; } endContainer = child; endOffset = getLength(endContainer); } } else { while (!(endContainer instanceof Text)) { const child = endContainer.firstChild; if (!child || isLeaf(child)) { break; } endContainer = child; } } range.setStart(startContainer, startOffset); range.setEnd(endContainer, endOffset); }; var moveRangeBoundariesUpTree = (range, startMax, endMax, root) => { let startContainer = range.startContainer; let startOffset = range.startOffset; let endContainer = range.endContainer; let endOffset = range.endOffset; let parent; if (!startMax) { startMax = range.commonAncestorContainer; } if (!endMax) { endMax = startMax; } while (!startOffset && startContainer !== startMax && startContainer !== root) { parent = startContainer.parentNode; startOffset = Array.from(parent.childNodes).indexOf( startContainer ); startContainer = parent; } while (true) { if (endContainer === endMax || endContainer === root) { break; } if (endContainer.nodeType !== TEXT_NODE && endContainer.childNodes[endOffset] && endContainer.childNodes[endOffset].nodeName === "BR" && !isLineBreak(endContainer.childNodes[endOffset])) { ++endOffset; } if (endOffset !== getLength(endContainer)) { break; } parent = endContainer.parentNode; endOffset = Array.from(parent.childNodes).indexOf(endContainer) + 1; endContainer = parent; } range.setStart(startContainer, startOffset); range.setEnd(endContainer, endOffset); }; var moveRangeBoundaryOutOf = (range, tag, root) => { let parent = getClosest(range.endContainer, root, tag); if (parent && (parent = parent.parentNode)) { const clone = range.cloneRange(); moveRangeBoundariesUpTree(clone, parent, parent, root); if (clone.endContainer === parent) { range.setStart(clone.endContainer, clone.endOffset); range.setEnd(clone.endContainer, clone.endOffset); } } return range; }; // source/Clean.ts var styleToSemantic = { "font-weight": { regexp: /^bold|^700/i, replace() { return createElement("B"); } }, "font-style": { regexp: /^italic/i, replace() { return createElement("I"); } }, "font-family": { regexp: notWS, replace(classNames, family) { return createElement("SPAN", { class: classNames.fontFamily, style: "font-family:" + family }); } }, "font-size": { regexp: notWS, replace(classNames, size) { return createElement("SPAN", { class: classNames.fontSize, style: "font-size:" + size }); } }, "text-decoration": { regexp: /^underline/i, replace() { return createElement("U"); } } }; var replaceStyles = (node, _, config) => { const style = node.style; let newTreeBottom; let newTreeTop; for (const attr in styleToSemantic) { const converter = styleToSemantic[attr]; const css = style.getPropertyValue(attr); if (css && converter.regexp.test(css)) { const el = converter.replace(config.classNames, css); if (el.nodeName === node.nodeName && el.className === node.className) { continue; } if (!newTreeTop) { newTreeTop = el; } if (newTreeBottom) { newTreeBottom.append(el); } newTreeBottom = el; node.style.removeProperty(attr); } } if (newTreeTop && newTreeBottom) { newTreeBottom.append(empty(node)); if (node.style.cssText) { node.append(newTreeTop); } else { replaceWith(node, newTreeTop); } } return newTreeBottom || node; }; var replaceWithTag = (tag) => { return (node, parent) => { const el = createElement(tag); const attributes = node.attributes; for (let i = 0, l = attributes.length; i < l; ++i) { const attribute = attributes[i]; el.setAttribute(attribute.name, attribute.value); } parent.replaceChild(el, node); el.append(empty(node)); return el; }; }; var fontSizes = { "1": "x-small", "2": "small", "3": "medium", "4": "large", "5": "x-large", "6": "xx-large", "7": "xxx-large", "-1": "smaller", "+1": "larger" }; var stylesRewriters = { STRONG: replaceWithTag("B"), EM: replaceWithTag("I"), INS: replaceWithTag("U"), STRIKE: replaceWithTag("S"), SPAN: replaceStyles, FONT: (node, parent, config) => { const font = node; const face = font.face; const size = font.size; let color = font.color; let newTag = createElement("SPAN"); let css = newTag.style; newTag.style.cssText = node.style.cssText; if (face) { css.fontFamily = face; } if (size) { css.fontSize = fontSizes[size]; } if (color && /^#?([\dA-F]{3}){1,2}$/i.test(color)) { if (color.charAt(0) !== "#") { color = "#" + color; } css.color = color; } replaceWith(node, newTag); newTag.append(empty(node)); return newTag; }, TT: (node, parent, config) => { const el = createElement("SPAN", { class: config.classNames.fontFamily, style: 'font-family:menlo,consolas,"courier new",monospace' }); replaceWith(node, el); el.append(empty(node)); return el; } }; var allowedBlock = /^(?:A(?:DDRESS|RTICLE|SIDE|UDIO)|BLOCKQUOTE|CAPTION|D(?:[DLT]|IV)|F(?:IGURE|IGCAPTION|OOTER)|H[1-6]|HEADER|L(?:ABEL|EGEND|I)|O(?:L|UTPUT)|P(?:RE)?|SECTION|T(?:ABLE|BODY|D|FOOT|H|HEAD|R)|COL(?:GROUP)?|UL)$/; var blacklist = /* @__PURE__ */ new Set(["HEAD", "META", "STYLE"]); var cleanTree = (node, config, preserveWS) => { const children = node.childNodes; let nonInlineParent = node; while (isInline(nonInlineParent)) { nonInlineParent = nonInlineParent.parentNode; } const walker = createTreeWalker( nonInlineParent, SHOW_ELEMENT_OR_TEXT ); let i = children.length; while (i--) { let child = children[i]; const nodeName = child.nodeName; if (child instanceof HTMLElement) { const childLength = child.childNodes.length; if (stylesRewriters[nodeName]) { child = stylesRewriters[nodeName](child, node, config); } else if (blacklist.has(nodeName)) { child.remove(); continue; } else if (!allowedBlock.test(nodeName) && !isInline(child)) { i += childLength; replaceWith(child, empty(child)); continue; } if (childLength) { cleanTree(child, config, preserveWS || nodeName === "PRE"); } } } return node; }; var removeEmptyInlines = (node) => { const children = node.childNodes; let l = children.length; while (l--) { const child = children[l]; if (child instanceof Element && !isLeaf(child)) { removeEmptyInlines(child); if (isInline(child) && !child.firstChild) { node.removeChild(child); } } else if (child instanceof Text && !child.length) { node.removeChild(child); } } }; var cleanupBRs = (node) => { const brs = node.querySelectorAll("BR:last-child"); let l = brs.length; while (l--) { const br = brs[l]; if (!isLineBreak(br)) { br.remove(); } } }; var escapeHTML = (text) => { return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """); }; // source/node/MergeSplit.ts var fixCursor = (node) => { if ((node instanceof Element || node instanceof DocumentFragment) && !isInline(node) && !node.children.length && !node.textContent.length) { node.appendChild(createElement("BR")); } return node; }; var fixContainer = (container) => { let wrapper = null; [...container.childNodes].forEach((child) => { if (isInline(child)) { wrapper || (wrapper = createElement("DIV")); wrapper.append(child); } else if (wrapper) { (wrapper.children.length || wrapper.textContent.trim().length) && container.insertBefore(wrapper, child); wrapper = null; } }); wrapper && container.append(wrapper); return container; }; var split = (node, offset, stopNode, root) => { if (node instanceof Text && node !== stopNode) { if (typeof offset !== "number") { throw new Error("Offset must be a number to split text node!"); } if (!node.parentNode) { throw new Error("Cannot split text node with no parent!"); } return split(node.parentNode, node.splitText(offset), stopNode, root); } let nodeAfterSplit = typeof offset === "number" ? offset < node.childNodes.length ? node.childNodes[offset] : null : offset; const parent = node.parentNode; if (!parent || node === stopNode || !(node instanceof Element)) { return nodeAfterSplit; } const clone = node.cloneNode(false); while (nodeAfterSplit) { const next = nodeAfterSplit.nextSibling; clone.append(nodeAfterSplit); nodeAfterSplit = next; } if (node instanceof HTMLOListElement && getClosest(node, root, "BLOCKQUOTE")) { clone.start = (+node.start || 1) + node.childNodes.length - 1; } fixCursor(node); fixCursor(clone); node.after(clone); return split(parent, clone, stopNode, root); }; var _mergeInlines = (node, fakeRange) => { const children = node.childNodes; let l = children.length; const frags = []; while (l--) { const child = children[l]; const prev = l ? children[l - 1] : null; if (prev && isInline(child) && areAlike(child, prev)) { if (fakeRange.startContainer === child) { fakeRange.startContainer = prev; fakeRange.startOffset += getLength(prev); } if (fakeRange.endContainer === child) { fakeRange.endContainer = prev; fakeRange.endOffset += getLength(prev); } if (fakeRange.startContainer === node) { if (fakeRange.startOffset > l) { --fakeRange.startOffset; } else if (fakeRange.startOffset === l) { fakeRange.startContainer = prev; fakeRange.startOffset = getLength(prev); } } if (fakeRange.endContainer === node) { if (fakeRange.endOffset > l) { --fakeRange.endOffset; } else if (fakeRange.endOffset === l) { fakeRange.endContainer = prev; fakeRange.endOffset = getLength(prev); } } detach(child); if (child instanceof Text) { prev.appendData(child.data); } else { frags.push(empty(child)); } } else if (child instanceof Element) { let frag; while (frag = frags.pop()) { child.append(frag); } _mergeInlines(child, fakeRange); } } }; var mergeInlines = (node, range) => { const element = node instanceof Text ? node.parentNode : node; if (element instanceof Element) { const fakeRange = { startContainer: range.startContainer, startOffset: range.startOffset, endContainer: range.endContainer, endOffset: range.endOffset }; _mergeInlines(element, fakeRange); range.setStart(fakeRange.startContainer, fakeRange.startOffset); range.setEnd(fakeRange.endContainer, fakeRange.endOffset); } }; var mergeWithBlock = (block, next, range, root) => { let container = next; let parent; let offset; while ((parent = container.parentNode) && parent !== root && parent instanceof Element && parent.childNodes.length === 1) { container = parent; } detach(container); offset = block.childNodes.length; const last = block.lastChild; if (last && last.nodeName === "BR") { last.remove(); --offset; } block.append(empty(next)); range.setStart(block, offset); range.collapse(true); mergeInlines(block, range); }; var mergeContainers = (node, root) => { const prev = node.previousSibling; const first = node.firstChild; const isListItem = node.nodeName === "LI"; if (isListItem && (!first || !/^[OU]L$/.test(first.nodeName))) { return; } if (prev && areAlike(prev, node)) { if (!isContainer(prev)) { if (!isListItem) { return; } const block = createElement("DIV"); block.append(empty(prev)); prev.append(block); } detach(node); const needsFix = !isContainer(node); prev.append(empty(node)); needsFix && fixContainer(prev); first && mergeContainers(first, root); } else if (isListItem) { const block = createElement("DIV"); node.insertBefore(block, first); fixCursor(block); } }; // source/node/Block.ts var getBlockWalker = (node, root) => { const walker = createTreeWalker(root, SHOW_ELEMENT, isBlock); walker.currentNode = node; return walker; }; var getPreviousBlock = (node, root) => { const block = getBlockWalker(node, root).previousNode(); return block !== root ? block : null; }; var getNextBlock = (node, root) => { const block = getBlockWalker(node, root).nextNode(); return block !== root ? block : null; }; var isEmptyBlock = (block) => { return !block.textContent && !block.querySelector("IMG"); }; // source/range/Block.ts var getStartBlockOfRange = (range, root) => { const container = range.startContainer; let block; if (isInline(container)) { block = getPreviousBlock(container, root); } else if (container !== root && container instanceof HTMLElement && isBlock(container)) { block = container; } else { const node = getNodeBeforeOffset(container, range.startOffset); block = getNextBlock(node, root); } return block && isNodeContainedInRange(range, block, true) ? block : null; }; var getEndBlockOfRange = (range, root) => { const container = range.endContainer; let block; if (isInline(container)) { block = getPreviousBlock(container, root); } else if (container !== root && container instanceof HTMLElement && isBlock(container)) { block = container; } else { let node = getNodeAfterOffset(container, range.endOffset); if (!node || !root.contains(node)) { node = root; let child; while (child = node.lastChild) { node = child; } } block = getPreviousBlock(node, root); } return block && isNodeContainedInRange(range, block, true) ? block : null; }; var isContent = (node) => { return node instanceof Text ? notWS.test(node.data) : node.nodeName === "IMG"; }; var rangeDoesStartAtBlockBoundary = (range, root) => { const startContainer = range.startContainer; const startOffset = range.startOffset; let nodeAfterCursor; if (startContainer instanceof Text) { const text = startContainer.data; for (let i = startOffset; i > 0; --i) { if (text.charAt(i - 1) !== ZWS) { return false; } } nodeAfterCursor = startContainer; } else { nodeAfterCursor = getNodeAfterOffset(startContainer, startOffset); if (nodeAfterCursor && !root.contains(nodeAfterCursor)) { nodeAfterCursor = null; } if (!nodeAfterCursor) { nodeAfterCursor = getNodeBeforeOffset(startContainer, startOffset); if (nodeAfterCursor instanceof Text && nodeAfterCursor.length) { return false; } } } const block = getStartBlockOfRange(range, root); if (!block) { return false; } const contentWalker = createTreeWalker( block, SHOW_ELEMENT_OR_TEXT, isContent ); contentWalker.currentNode = nodeAfterCursor; return !contentWalker.previousNode(); }; var rangeDoesEndAtBlockBoundary = (range, root) => { const endContainer = range.endContainer; const endOffset = range.endOffset; let currentNode; if (endContainer instanceof Text) { const text = endContainer.data; const length = text.length; for (let i = endOffset; i < length; ++i) { if (text.charAt(i) !== ZWS) { return false; } } currentNode = endContainer; } else { currentNode = getNodeBeforeOffset(endContainer, endOffset); } const block = getEndBlockOfRange(range, root); if (!block) { return false; } const contentWalker = createTreeWalker( block, SHOW_ELEMENT_OR_TEXT, isContent ); contentWalker.currentNode = currentNode; return !contentWalker.nextNode(); }; var expandRangeToBlockBoundaries = (range, root) => { const start = getStartBlockOfRange(range, root); const end = getEndBlockOfRange(range, root); let parent; if (start && end) { parent = start.parentNode; range.setStart(parent, Array.from(parent.childNodes).indexOf(start)); parent = end.parentNode; range.setEnd(parent, Array.from(parent.childNodes).indexOf(end) + 1); } }; // source/range/InsertDelete.ts function createRange(startContainer, startOffset, endContainer, endOffset) { const range = document.createRange(); range.setStart(startContainer, startOffset); if (endContainer && typeof endOffset === "number") { range.setEnd(endContainer, endOffset); } else { range.setEnd(startContainer, startOffset); } return range; } var insertNodeInRange = (range, node) => { let { startContainer, startOffset, endContainer, endOffset } = range; let children; if (startContainer instanceof Text) { const parent = startContainer.parentNode; children = parent.childNodes; if (startOffset === startContainer.length) { startOffset = Array.from(children).indexOf(startContainer) + 1; if (range.collapsed) { endContainer = parent; endOffset = startOffset; } } else { if (startOffset) { const afterSplit = startContainer.splitText(startOffset); if (endContainer === startContainer) { endOffset -= startOffset; endContainer = afterSplit; } else if (endContainer === parent) { ++endOffset; } startContainer = afterSplit; } startOffset = Array.from(children).indexOf( startContainer ); } startContainer = parent; } else { children = startContainer.childNodes; } const childCount = children.length; if (startOffset === childCount) { startContainer.append(node); } else { startContainer.insertBefore(node, children[startOffset]); } if (startContainer === endContainer) { endOffset += children.length - childCount; } range.setStart(startContainer, startOffset); range.setEnd(endContainer, endOffset); }; var extractContentsOfRange = (range, common, root) => { const frag = document.createDocumentFragment(); if (range.collapsed) { return frag; } if (!common) { common = range.commonAncestorContainer; } if (common instanceof Text) { common = common.parentNode; } const startContainer = range.startContainer; const startOffset = range.startOffset; let endContainer = split(range.endContainer, range.endOffset, common, root); let endOffset = 0; let node = split(startContainer, startOffset, common, root); while (node && node !== endContainer) { const next = node.nextSibling; frag.append(node); node = next; } node = endContainer && endContainer.previousSibling; if (node && node instanceof Text && endContainer instanceof Text) { endOffset = node.length; node.appendData(endContainer.data); detach(endContainer); endContainer = node; } range.setStart(startContainer, startOffset); if (endContainer) { range.setEnd(endContainer, endOffset); } else { range.setEnd(common, common.childNodes.length); } fixCursor(common); return frag; }; var getAdjacentInlineNode = (iterator, method, node) => { iterator.currentNode = node; let nextNode; while (nextNode = iterator[method]()) { if (nextNode instanceof Text || isLeaf(nextNode)) { return nextNode; } if (!isInline(nextNode)) { return null; } } return null; }; var deleteContentsOfRange = (range, root) => { const startBlock = getStartBlockOfRange(range, root); let endBlock = getEndBlockOfRange(range, root); const needsMerge = startBlock !== endBlock; if (startBlock && endBlock) { moveRangeBoundariesDownTree(range); moveRangeBoundariesUpTree(range, startBlock, endBlock, root); } const frag = extractContentsOfRange(range, null, root); moveRangeBoundariesDownTree(range); if (needsMerge) { endBlock = getEndBlockOfRange(range, root); if (startBlock && endBlock && startBlock !== endBlock) { mergeWithBlock(startBlock, endBlock, range, root); } } if (startBlock) { fixCursor(startBlock); } const child = root.firstChild; if (!child || child.nodeName === "BR") { fixCursor(root); if (root.firstChild) { range.selectNodeContents(root.firstChild); } } range.collapse(true); const startContainer = range.startContainer; const startOffset = range.startOffset; const iterator = createTreeWalker(root, SHOW_ELEMENT_OR_TEXT); let afterNode = startContainer; let afterOffset = startOffset; if (!(afterNode instanceof Text) || afterOffset === afterNode.length) { afterNode = getAdjacentInlineNode(iterator, "nextNode", afterNode); afterOffset = 0; } let beforeNode = startContainer; let beforeOffset = startOffset - 1; if (!(beforeNode instanceof Text) || beforeOffset === -1) { beforeNode = getAdjacentInlineNode( iterator, "previousPONode", afterNode || (startContainer instanceof Text ? startContainer : startContainer.childNodes[startOffset] || startContainer) ); if (beforeNode instanceof Text) { beforeOffset = beforeNode.length; } } let node = null; let offset = 0; if (afterNode instanceof Text && afterNode.data.charAt(afterOffset) === " " && rangeDoesStartAtBlockBoundary(range, root)) { node = afterNode; offset = afterOffset; } else if (beforeNode instanceof Text && beforeNode.data.charAt(beforeOffset) === " ") { if (afterNode instanceof Text && afterNode.data.charAt(afterOffset) === " " || rangeDoesEndAtBlockBoundary(range, root)) { node = beforeNode; offset = beforeOffset; } } if (node) { node.replaceData(offset, 1, "\xA0"); } range.setStart(startContainer, startOffset); range.collapse(true); return frag; }; var insertTreeFragmentIntoRange = (range, frag, root) => { const firstInFragIsInline = frag.firstChild && isInline(frag.firstChild); let node; fixContainer(frag); node = frag; while (node = getNextBlock(node, root)) { fixCursor(node); } if (!range.collapsed) { deleteContentsOfRange(range, root); } moveRangeBoundariesDownTree(range); range.collapse(false); const stopPoint = getClosest(range.endContainer, root, "BLOCKQUOTE") || root; let block = getStartBlockOfRange(range, root); let blockContentsAfterSplit = null; const firstBlockInFrag = getNextBlock(frag, frag); const replaceBlock = !firstInFragIsInline && !!block && isEmptyBlock(block); if (block && firstBlockInFrag && !replaceBlock && // Don't merge table cells or PRE elements into block !getClosest(firstBlockInFrag, frag, "PRE") && !getClosest(firstBlockInFrag, frag, "TABLE")) { moveRangeBoundariesUpTree(range, block, block, root); range.collapse(true); let container = range.endContainer; let offset = range.endOffset; cleanupBRs(block); if (isInline(container)) { const nodeAfterSplit = split( container, offset, getPreviousBlock(container, root) || root, root ); container = nodeAfterSplit.parentNode; offset = Array.from(container.childNodes).indexOf( nodeAfterSplit ); } if ( /*isBlock( container ) && */ offset !== getLength(container) ) { blockContentsAfterSplit = document.createDocumentFragment(); while (node = container.childNodes[offset]) { blockContentsAfterSplit.append(node); } } mergeWithBlock(container, firstBlockInFrag, range, root); offset = Array.from(container.parentNode.childNodes).indexOf( container ) + 1; container = container.parentNode; range.setEnd(container, offset); } if (getLength(frag)) { if (replaceBlock && block) { range.setEndBefore(block); range.collapse(false); detach(block); } moveRangeBoundariesUpTree(range, stopPoint, stopPoint, root); let nodeAfterSplit = split( range.endContainer, range.endOffset, stopPoint, root ); const nodeBeforeSplit = nodeAfterSplit ? nodeAfterSplit.previousSibling : stopPoint.lastChild; stopPoint.insertBefore(frag, nodeAfterSplit); if (nodeAfterSplit) { range.setEndBefore(nodeAfterSplit); } else { range.setEnd(stopPoint, getLength(stopPoint)); } block = getEndBlockOfRange(range, root); moveRangeBoundariesDownTree(range); const container = range.endContainer; const offset = range.endOffset; if (nodeAfterSplit && isContainer(nodeAfterSplit)) { mergeContainers(nodeAfterSplit, root); } nodeAfterSplit = nodeBeforeSplit && nodeBeforeSplit.nextSibling; if (nodeAfterSplit && isContainer(nodeAfterSplit)) { mergeContainers(nodeAfterSplit, root); } range.setEnd(container, offset); } if (blockContentsAfterSplit && block) { const tempRange = range.cloneRange(); fixCursor(blockContentsAfterSplit); mergeWithBlock(block, blockContentsAfterSplit, tempRange, root); range.setEnd(tempRange.endContainer, tempRange.endOffset); } moveRangeBoundariesDownTree(range); }; // source/range/Contents.ts var getTextContentsOfRange = (range) => { if (range.collapsed) { return ""; } const startContainer = range.startContainer; const endContainer = range.endContainer; const walker = createTreeWalker( range.commonAncestorContainer, SHOW_ELEMENT_OR_TEXT, (node2) => { return isNodeContainedInRange(range, node2, true); } ); walker.currentNode = startContainer; let node = startContainer; let textContent = ""; let addedTextInBlock = false; let value; if (!(node instanceof Element) && !(node instanceof Text) || FILTER_ACCEPT !== walker.filter.acceptNode(node)) { node = walker.nextNode(); } while (node) { if (node instanceof Text) { value = node.data; if (value && /\S/.test(value)) { if (node === endContainer) { value = value.slice(0, range.endOffset); } if (node === startContainer) { value = value.slice(range.startOffset); } textContent += value; addedTextInBlock = true; } } else if (node.nodeName === "BR" || addedTextInBlock && !isInline(node)) { textContent += "\n"; addedTextInBlock = false; } node = walker.nextNode(); } textContent = textContent.replace(/ /g, " "); return textContent; }; // source/Clipboard.ts var indexOf = Array.prototype.indexOf; var extractRangeToClipboard = (event, range, root, removeRangeFromDocument, toCleanHTML, toPlainText, plainTextOnly) => { const clipboardData = event.clipboardData; if (!clipboardData) { return false; } let text = toPlainText ? "" : getTextContentsOfRange(range); const startBlock = getStartBlockOfRange(range, root); const endBlock = getEndBlockOfRange(range, root); let copyRoot = root; if (startBlock === endBlock && startBlock?.contains(range.commonAncestorContainer)) { copyRoot = startBlock; } let contents; if (removeRangeFromDocument) { contents = deleteContentsOfRange(range, root); } else { range = range.cloneRange(); moveRangeBoundariesDownTree(range); moveRangeBoundariesUpTree(range, copyRoot, copyRoot, root); contents = range.cloneContents(); } let parent = range.commonAncestorContainer; if (parent instanceof Text) { parent = parent.parentNode; } while (parent && parent !== copyRoot) { const newContents = parent.cloneNode(false); newContents.append(contents); contents = newContents; parent = parent.parentNode; } let html; if (contents.childNodes.length === 1 && contents.childNodes[0] instanceof Text) { text = contents.childNodes[0].data.replace(/ /g, " "); plainTextOnly = true; } else { const node = createElement("DIV"); node.append(contents); html = node.innerHTML; if (toCleanHTML) { html = toCleanHTML(html); } } if (toPlainText && html !== void 0) { text = toPlainText(html); } if (isWin) { text = text.replace(/\r?\n/g, "\r\n"); } if (!plainTextOnly && html && text !== html) { html = "<!-- squire -->" + html; clipboardData.setData("text/html", html); } clipboardData.setData("text/plain", text); event.preventDefault(); return true; }; var _onCut = function(event) { const range = this.getSelection(); const root = this._root; if (range.collapsed) { event.preventDefault(); return; } this.saveUndoState(range); const handled = extractRangeToClipboard( event, range, root, true, this._config.willCutCopy, this._config.toPlainText, false ); if (!handled) { setTimeout(() => { try { this._ensureBottomLine(); } catch (error) { this._config.didError(error); } }, 0); } this.setSelection(range); }; var _onCopy = function(event) { extractRangeToClipboard( event, this.getSelection(), this._root, false, this._config.willCutCopy, this._config.toPlainText, false ); }; var _monitorShiftKey = function(event) { this._isShiftDown = event.shiftKey; }; var _onPaste = function(event) { const clipboardData = event.clipboardData; const items = clipboardData.items; const choosePlain = this._isShiftDown; let hasRTF = false; let hasImage = false; let plainItem = null; let htmlItem = null; let l = items.length; while (l--) { const item = items[l]; const type = item.type; if (type === "text/html") { htmlItem = item; } else if (type === "text/plain" || type === "text/uri-list") { plainItem = item; } else if (type === "text/rtf") { hasRTF = true; } else if (/^image\/.*/.test(type)) { hasImage = true; } } if (hasImage && !(hasRTF && htmlItem)) { event.preventDefault(); this.fireEvent("pasteImage", { clipboardData }); return; } event.preventDefault(); if (htmlItem && (!choosePlain || !plainItem)) { htmlItem.getAsString((html) => { this.insertHTML(html, true); }); } else if (plainItem) { plainItem.getAsString((text) => { let isLink = false; const range = this.getSelection(); if (!range.collapsed && notWS.test(range.toString())) { const match = this.linkRegExp.exec(text); isLink = !!match && match[0].length === text.length; } if (isLink) { this.makeLink(text); } else { this.insertPlainText(text, true); } }); } }; var _onDrop = function(event) { if (!event.dataTransfer) { return; } const types = event.dataTransfer.types; let l = types.length; let hasPlain = false; let hasHTML = false; while (l--) { switch (types[l]) { case "text/plain": hasPlain = true; break; case "text/html": hasHTML = true; break; default: return; } } if (hasHTML || hasPlain && this.saveUndoState) { this.saveUndoState(); } }; // source/keyboard/KeyHelpers.ts var afterDelete = (self, range) => { try { if (!range) { range = self.getSelection(); } let node = range.startContainer; if (node instanceof Text) { node = node.parentNode; } let parent = node; while (isInline(parent) && (!parent.textContent || parent.textContent === ZWS)) { node = parent; parent = node.parentNode; } if (node !== parent) { range.setStart( parent, Array.from(parent.childNodes).indexOf(node) ); range.collapse(true); parent.removeChild(node); if (!isBlock(parent)) { parent = getPreviousBlock(parent, self._root) || self._root; } fixCursor(parent); moveRangeBoundariesDownTree(range); } if (node === self._root && (node = node.firstChild) && node.nodeName === "BR") { detach(node); } self._ensureBottomLine(); self.setSelection(range); self._updatePath(range, true); } catch (error) { self._config.didError(error); } }; var detachUneditableNode = (node, root) => { let parent; while (parent = node.parentNode) { if (parent === root || parent.isContentEditable) { break; } node = parent; } detach(node); }; var linkifyText = (self, textNode, offset) => { if (getClosest(textNode, self._root, "A")) { return; } const data = textNode.data || ""; const searchFrom = Math.max( data.lastIndexOf(" ", offset - 1), data.lastIndexOf("\xA0", offset - 1) ) + 1; const searchText = data.slice(searchFrom, offset); const match = self.linkRegExp.exec(searchText); if (match) { const selection = self.getSelection(); self._docWasChanged(); self._recordUndoState(selection); self._getRangeAndRemoveBookmark(selection); const index = searchFrom + match.index; const endIndex = index + match[0].length; const needsSelectionUpdate = selection.startContainer === textNode; const newSelectionOffset = selection.startOffset - endIndex; if (index) { textNode = textNode.splitText(index); } const defaultAttributes = self._config.tagAttributes.a; const link = createElement( "A", Object.assign( { href: match[1] ? /^(?:ht|f)tps?:/i.test(match[1]) ? match[1] : "http://" + match[1] : "mailto:" + match[0] }, defaultAttributes ) ); link.textContent = data.slice(index, endIndex); textNode.parentNode.insertBefore(link, textNode); textNode.data = data.slice(endIndex); if (needsSelectionUpdate) { selection.setStart(textNode, newSelectionOffset); selection.setEnd(textNode, newSelectionOffset); } self.setSelection(selection); } }; // source/keyboard/Backspace.ts var Backspace = (self, event, range) => { const root = self._root; self._removeZWS(); self.saveUndoState(range); if (!range.collapsed) { event.preventDefault(); deleteContentsOfRange(range, root); afterDelete(self, range); } else if (rangeDoesStartAtBlockBoundary(range, root)) { event.preventDefault(); const startBlock = getStartBlockOfRange(range, root); if (!startBlock) { return; } let current = startBlock; fixContainer(current.parentNode); const previous = getPreviousBlock(current, root); if (previous) { if (!previous.isContentEditable) { detachUneditableNode(previous, root); return; } mergeWithBlock(previous, current, range, root); current = previous.parentNode; while (current !== root && !current.nextSibling) { current = current.parentNode; } if (current !== root && (current = current.nextSibling)) { mergeContainers(current, root); } self.setSelection(range); } else if (current) { if (getClosest(current, root, "UL") || getClosest(current, root, "OL")) { self.decreaseListLevel(range); return; } else if (getClosest(current, root, "BLOCKQUOTE")) { self.removeQuote(range); return; } self.setSelection(range); self._updatePath(range, true); } } else { moveRangeBoundariesDownTree(range); const text = range.startContainer; const offset = range.startOffset; const a = text.parentNode; if (text instanceof Text && a instanceof HTMLAnchorElement && offset && a.href.includes(text.data)) { text.deleteData(offset - 1, 1); self.setSelection(range); self.removeLink(); event.preventDefault(); } else { self.setSelection(range); setTimeout(() => { afterDelete(self); }, 0); } } }; // source/keyboard/Delete.ts var Delete = (self, event, range) => { const root = self._root; let current; let next; let originalRange; let cursorContainer; let cursorOffset; let nodeAfterCursor; self._removeZWS(); self.saveUndoState(range); if (!range.collapsed) { event.preventDefault(); deleteContentsOfRange(range, root); afterDelete(self, range); } else if (rangeDoesEndAtBlockBoundary(range, root)) { event.preventDefault(); current = getStartBlockOfRange(range, root); if (!current) { return; } fixContainer(current.parentNode); next = getNextBlock(current, root); if (next) { if (!next.isContentEditable) { detachUneditableNode(next, root); return; } mergeWithBlock(current, next, range, root); next = current.parentNode; while (next !== root && !next.nextSibling) { next = next.parentNode; } if (next !== root && (next = next.nextSibling)) { mergeContainers(next, root); } self.setSelection(range); self._updatePath(range, true); } } else { originalRange = range.cloneRange(); moveRangeBoundariesUpTree(range, root, root, root); cursorContainer = range.endContainer; cursorOffset = range.endOffset; if (cursorContainer instanceof Element) { nodeAfterCursor = cursorContainer.childNodes[cursorOffset]; if (nodeAfterCursor && nodeAfterCursor.nodeName === "IMG") { event.preventDefault(); detach(nodeAfterCursor); moveRangeBoundariesDownTree(range); afterDelete(self, range); return; } } self.setSelection(originalRange); setTimeout(() => { afterDelete(self); }, 0); } }; // source/keyboard/Tab.ts var Tab = (self, event, range) => { const root = self._root; self._removeZWS(); if (range.collapsed && rangeDoesStartAtBlockBoundary(range, root)) { getClosest(range.startContainer, root, "UL,OL,BLOCKQUOTE") && self.changeIndentationLevel("increase") && event.preventDefault(); } }; var ShiftTab = (self, event, range) => { const root = self._root; self._removeZWS(); if (range.collapsed && rangeDoesStartAtBlockBoundary(range, root)) { decreaseLevel(self, range, range.startContainer) && event.preventDefault(); } }; // source/keyboard/Space.ts var Space = (self, event, range) => { let node; const root = self._root; self._recordUndoState(range); self._getRangeAndRemoveBookmark(range); if (!range.collapsed) { deleteContentsOfRange(range, root); self._ensureBottomLine(); self.setSelection(range); self._updatePath(range, true); } else if (rangeDoesEndAtBlockBoundary(range, root)) { const block = getStartBlockOfRange(range, root); if (block && block.nodeName !== "PRE") { const text = block.textContent?.trimEnd().replace(ZWS, ""); if (text === "*" || text === "1.") { event.preventDefault(); self.insertPlainText(" ", false); self._docWasChanged(); self.saveUndoState(range); const walker = createTreeWalker(block, SHOW_TEXT); let textNode; while (textNode = walker.nextNode()) { detach(textNode); } if (text === "*") { self.makeUnorderedList(); } else { self.makeOrderedList(); } return; } } } node = range.endContainer; if (range.endOffset === getLength(node)) { do { if (node.nodeName === "A") { range.setStartAfter(node); break; } } while (!node.nextSibling && (node = node.parentNode) && node !== root); } if (self._config.addLinks) { const linkRange = range.cloneRange(); moveRangeBoundariesDownTree(linkRange); const textNode = linkRange.startContainer; const offset = linkRange.startOffset; setTimeout(() => { linkifyText(self, textNode, offset); }, 0); } self.setSelection(range); }; // source/keyboard/KeyHandlers.ts var _onKey = function(event) { if (event.defaultPrevented || event.isComposing) { return; } let key = event.key; let modifiers = ""; const code = event.code; if (/^Digit\d$/.test(code)) { key = code.slice(-1); } if (key !== "Backspace" && key !== "Delete") { if (event.altKey) { modifiers += "Alt-"; } if (event.ctrlKey) { modifiers += "Ctrl-"; } if (event.metaKey) { modifiers += "Meta-"; } if (event.shiftKey) { modifiers += "Shift-"; } } if (isWin && event.shiftKey && key === "Delete") { modifiers += "Shift-"; } key = modifiers + key; const range = this.getSelection(); if (this._keyHandlers[key]) { this._keyHandlers[key](this, event, range); } else if (!range.collapsed && !event.ctrlKey && !event.metaKey && key.length === 1) { this.saveUndoState(range); deleteContentsOfRange(range, this._root); this._ensureBottomLine(); this.setSelection(range); this._updatePath(range, true); } }; var keyHandlers = { "Backspace": Backspace, "Delete": Delete, "Tab": Tab, "Shift-Tab": ShiftTab, " ": Space, "ArrowLeft"(self) { self._removeZWS(); }, "ArrowRight"(self, event, range) { self._removeZWS(); const root = self.getRoot(); if (rangeDoesEndAtBlockBoundary(range, root)) { moveRangeBoundariesDownTree(range); let node = range.endContainer; do { if (node.nodeName === "CODE") { let next = node.nextSibling; if (!(next instanceof Text)) { const textNode = document.createTextNode("\xA0"); node.parentNode.insertBefore(textNode, next); next = textNode; } range.setStart(next, 1); self.setSelection(range); event.preventDefault(); break; } } while (!node.nextSibling && (node = node.parentNode) && node !== root); } } }; if (!isMac && !isIOS) { keyHandlers.PageUp = (self) => { self.moveCursorToStart(); }; keyHandlers.PageDown = (self) => { self.moveCursorToEnd(); }; } var mapKeyToFormat = (tag, remove) => { remove = remove || null; return (self, event) => { event.preventDefault(); const range = self.getSelection(); if (self.hasFormat(tag, null, range)) { self.changeFormat(null, { tag }, range); } else { self.changeFormat({ tag }, remove, range); } }; }; keyHandlers[ctrlKey + "b"] = mapKeyToFormat("B"); keyHandlers[ctrlKey + "i"] = mapKeyToFormat("I"); keyHandlers[ctrlKey + "u"] = mapKeyToFormat("U"); keyHandlers[ctrlKey + "Shift-7"] = mapKeyToFormat("S"); keyHandlers[ctrlKey + "Shift-5"] = mapKeyToFormat("SUB", { tag: "SUP" }); keyHandlers[ctrlKey + "Shift-6"] = mapKeyToFormat("SUP", { tag: "SUB" }); keyHandlers[ctrlKey + "Shift-8"] = (self, event) => { event.preventDefault(); const path = self.getPath(); if (!/(?:^|>)UL/.test(path)) { self.makeUnorderedList(); } else { self.removeList(); } }; keyHandlers[ctrlKey + "Shift-9"] = (self, event) => { event.preventDefault(); const path = self.getPath(); if (!/(?:^|>)OL/.test(path)) { self.makeOrderedList(); } else { self.removeList(); } }; keyHandlers[ctrlKey + "["] = (self, event) => { event.preventDefault(); const path = self.getPath(); if (/(?:^|>)BLOCKQUOTE/.test(path) || !/(?:^|>)[OU]L/.test(path)) { self.decreaseQuoteLevel(); } else { self.decreaseListLevel(); } }; keyHandlers[ctrlKey + "]"] = (self, event) => { event.preventDefault(); const path = self.getPath(); if (/(?:^|>)BLOCKQUOTE/.test(path) || !/(?:^|>)[OU]L/.test(path)) { self.increaseQuoteLevel(); } else { self.increaseListLevel(); } }; keyHandlers[ctrlKey + "d"] = (self, event) => { event.preventDefault(); self.toggleCode(); }; keyHandlers[ctrlKey + "z"] = (self, event) => { event.preventDefault(); self.undo(); }; keyHandlers[ctrlKey + "y"] = // Depending on platform, the Shift may cause the key to come through as // upper case, but sometimes not. Just add both as shortcuts — the browser // will only ever fire one or the other. keyHandlers[ctrlKey + "Shift-z"] = keyHandlers[ctrlKey + "Shift-Z"] = (self, event) => { event.preventDefault(); self.redo(); }; // source/Editor.ts var Squire = class { constructor(root, config) { /** * Subscribing to these events won't automatically add a listener to the * document node, since these events are fired in a custom manner by the * editor code. */ this.customEvents = /* @__PURE__ */ new Set([ "pathChange", "select", "input", "pasteImage", "undoStateChange" ]); // --- this.startSelectionId = "squire-selection-start"; this.endSelectionId = "squire-selection-end"; /* linkRegExp = new RegExp( // Only look on boundaries '\\b(?:' + // Capture group 1: URLs '(' + // Add links to URLS // Starts with: '(?:' + // http(s):// or ftp:// '(?:ht|f)tps?:\\/\\/' + // or '|' + // www. 'www\\d{0,3}[.]' + // or '|' + // foo90.com/ '[a-z0-9][a-z0-9.\\-]*[.][a-z]{2,}\\/' + ')' + // Then we get one or more: '(?:' + // Run of non-spaces, non ()<> '[^\\s()<>]+' + // or '|' + // balanced parentheses (one level deep only) '\\([^\\s()<>]+\\)' + ')+' + // And we finish with '(?:' + // Not a space or punctuation character '[^\\s?&`!()\\[\\]{};:\'".,<>«»“”‘’]' + // or '|' + // Balanced parentheses. '\\([^\\s()<>]+\\)' + ')' + // Capture group 2: Emails ')|(' + // Add links to emails '[\\w\\-.%+]+@(?:[\\w\\-]+\\.)+[a-z]{2,}\\b' + // Allow query parameters in the mailto: style '(?:' + '[?][^&?\\s]+=[^\\s?&`!()\\[\\]{};:\'".,<>«»“”‘’]+' + '(?:&[^&?\\s]+=[^\\s?&`!()\\[\\]{};:\'".,<>«»“”‘’]+)*' + ')?' + '))', 'i' ); */ this.linkRegExp = /\b(?:((?:(?:ht|f)tps?:\/\/|www\d{0,3}[.]|[a-z0-9][a-z0-9.\-]*[.][a-z]{2,}\/)(?:[^\s()<>]+|\([^\s()<>]+\))+(?:[^\s?&`!()\[\]{};:'".,<>«»“”‘’]|\([^\s()<>]+\)))|([\w\-.%+]+@(?:[\w\-]+\.)+[a-z]{2,}\b(?:[?][^&?\s]+=[^\s?&`!()\[\]{};:'".,<>«»“”‘’]+(?:&[^&?\s]+=[^\s?&`!()\[\]{};:'".,<>«»“”‘’]+)*)?))/i; this.tagAfterSplit = { DT: "DD", DD: "DT", LI: "LI", PRE: "PRE" }; this._root = root; this._config = this._makeConfig(config); this._isFocused = false; this._lastSelection = createRange(root, 0); this._willRestoreSelection = false; this._mayHaveZWS = false; this._lastAnchorNode = null; this._lastFocusNode = null; this._path = ""; this._events = /* @__PURE__ */ new Map(); this._undoIndex = -1; this._undoStack = []; this._undoStackLength = 0; this._isInUndoState = false; this._ignoreChange = false; this._ignoreAllChanges = false; this.addEventListener("selectionchange", this._updatePathOnEvent); this.addEventListener("blur", this._enableRestoreSelection); this.addEventListener("mousedown", this._disableRestoreSelection); this.addEventListener("touchstart", this._disableRestoreSelection); this.addEventListener("focus", this._restoreSelection); this.addEventListener("blur", this._removeZWS); this._isShiftDown = false; this.addEventListener("cut", _onCut); this.addEventListener("copy", _onCopy); this.addEventListener("paste", _onPaste); this.addEventListener("drop", _onDrop); this.addEventListener( "keydown", _monitorShiftKey ); this.addEventListener("keyup", _monitorShiftKey); this.addEventListener("keydown", _onKey); this._keyHandlers = Object.create(keyHandlers); const mutation = new MutationObserver(() => this._docWasChanged()); mutation.observe(root, { childList: true, attributes: true, characterData: true, subtree: true }); this._mutation = mutation; root.setAttribute("contenteditable", "true"); this.addEventListener( "beforeinput", this._beforeInput ); this.setHTML(""); } destroy() { this._events.forEach((_, type) => { this.removeEventListener(type); }); this._mutation.disconnect(); this._undoIndex = -1; this._undoStack = []; this._undoStackLength = 0; } _makeConfig(userConfig) { const config = { blockTag: "DIV", classNames: { color: "color", fontFamily: "font", fontSize: "size", highlight: "highlight" }, undo: { documentSizeThreshold: -1, // -1 means no threshold undoLimit: -1 // -1 means no limit }, addLinks: true, willCutCopy: null, toPlainText: null, sanitizeToDOMFragment: (html) => { const frag = DOMPurify.sanitize(html, { ALLOW_UNKNOWN_PROTOCOLS: true, WHOLE_DOCUMENT: false, RETURN_DOM: true, RETURN_DOM_FRAGMENT: true, FORCE_BODY: false }); return frag ? document.importNode(frag, true) : document.createDocumentFragment(); }, didError: (error) => console.log(error) }; if (userConfig) { Object.assign(config, userConfig); config.blockTag = config.blockTag.toUpperCase(); } return config; } setKeyHandler(key, fn) { this._keyHandlers[key] = fn; return this; } _beforeInput(event) { switch (event.inputType) { case "insertLineBreak": event.preventDefault(); this.splitBlock(true); break; case "insertParagraph": event.preventDefault(); this.splitBlock(false); break; case "insertOrderedList": event.preventDefault(); this.makeOrderedList(); break; case "insertUnoderedList": event.preventDefault(); this.makeUnorderedList(); break; case "historyUndo": event.preventDefault(); this.undo(); break; case "historyRedo": event.preventDefault(); this.redo(); break; case "formatBold": event.preventDefault(); this.bold(); break; case "formaItalic": event.preventDefault(); this.italic(); break; case "formatUnderline": event.preventDefault(); this.underline(); break; case "formatStrikeThrough": event.preventDefault(); this.strikethrough(); break; case "formatSuperscript": event.preventDefault(); this.superscript(); break; case "formatSubscript": event.preventDefault(); this.subscript(); break; case "formatJustifyFull": case "formatJustifyCenter": case "formatJustifyRight": case "formatJustifyLeft": { event.preventDefault(); let alignment = event.inputType.slice(13).toLowerCase(); if (alignment === "full") { alignment = "justify"; } this.setTextAlignment(alignment); break; } case "formatRemove": event.preventDefault(); this.setStyle(); break; case "formatSetBlockTextDirection": { event.preventDefault(); let dir = event.data; if (dir === "null") { dir = null; } this.setTextDirection(dir); break; } case "formatBackColor": event.preventDefault(); this.setStyle({ backgroundColor: event.data }); break; case "formatFontColor": event.preventDefault(); this.setStyle({ color: event.data }); break; case "formatFontName": event.preventDefault(); this.setStyle({ fontFamily: event.data }); break; } } // --- Events handleEvent(event) { this.fireEvent(event.type, event); } fireEvent(type, detail) { let handlers = this._events.get(type); if (/^(?:focus|blur)/.test(type)) { const isFocused = this._root === document.activeElement; if (type === "focus") { if (!isFocused || this._isFocused) { return this; } this._isFocused = true; } else { if (isFocused || !this._isFocused) { return this; } this._isFocused = false; } } if (handlers) { const event = detail instanceof Event ? detail : new CustomEvent(type, { detail }); handlers = handlers.slice(); for (const handler of handlers) { try { if ("handleEvent" in handler) { handler.handleEvent(event); } else { handler.call(this, event); } } catch (error) { this._config.didError(error); } } } return this; } addEventListener(type, fn) { let handlers = this._events.get(type); let target = this._root; if (!handlers) { handlers = []; this._events.set(type, handlers); if (!this.customEvents.has(type)) { if (type === "selectionchange") { target = document; } target.addEventListener(type, this, true); } } handlers.push(fn); return this; } removeEventListener(type, fn) { const handlers = this._events.get(type); let target = this._root; if (handlers) { if (fn) { let l = handlers.length; while (l--) { if (handlers[l] === fn) { handlers.splice(l, 1); } } } else { handlers.length = 0; } if (!handlers.length) { this._events.delete(type); if (!this.customEvents.has(type)) { if (type === "selectionchange") { target = document; } target.removeEventListener(type, this, true); } } } return this; } // --- Focus focus() { this._root.focus({ preventScroll: true }); return this; } blur() { this._root.blur(); return this; } // --- Selection and bookmarking _enableRestoreSelection() { this._willRestoreSelection = true; } _disableRestoreSelection() { this._willRestoreSelection = false; } _restoreSelection() { if (this._willRestoreSelection) { this.setSelection(this._lastSelection); } } // --- _removeZWS() { if (!this._mayHaveZWS) { return; } removeZWS(this._root); this._mayHaveZWS = false; } _saveRangeToBookmark(range) { let startNode = createElement("INPUT", { id: this.startSelectionId, type: "hidden" }); let endNode = createElement("INPUT", { id: this.endSelectionId, type: "hidden" }); let temp; insertNodeInRange(range, startNode); range.collapse(false); insertNodeInRange(range, endNode); if (startNode.compareDocumentPosition(endNode) & Node.DOCUMENT_POSITION_PRECEDING) { startNode.id = this.endSelectionId; endNode.id = this.startSelectionId; temp = startNode; startNode = endNode; endNode = temp; } range.setStartAfter(startNode); range.setEndBefore(endNode); } _getRangeAndRemoveBookmark(range) { const root = this._root; const start = root.querySelector("#" + this.startSelectionId); const end = root.querySelector("#" + this.endSelectionId); if (start && end) { let startContainer = start.parentNode; let endContainer = end.parentNode; const startOffset = Array.from(startContainer.childNodes).indexOf( start ); let endOffset = Array.from(endContainer.childNodes).indexOf(end); if (startContainer === endContainer) { --endOffset; } start.remove(); end.remove(); if (!range) { range = document.createRange(); } range.setStart(startContainer, startOffset); range.setEnd(endContainer, endOffset); mergeInlines(startContainer, range); if (startContainer !== endContainer) { mergeInlines(endContainer, range); } if (range.collapsed) { startContainer = range.startContainer; if (startContainer instanceof Text) { endContainer = startContainer.childNodes[range.startOffset]; if (!endContainer || !(endContainer instanceof Text)) { endContainer = startContainer.childNodes[range.startOffset - 1]; } if (endContainer && endContainer instanceof Text) { range.setStart(endContainer, 0); range.collapse(true); } } } } return range || null; } getSelection() { const selection = window.getSelection(); const root = this._root; let range = null; if (this._isFocused && selection && selection.rangeCount) { range = selection.getRangeAt(0).cloneRange(); const startContainer = range.startContainer; const endContainer = range.endContainer; if (startContainer && isLeaf(startContainer)) { range.setStartBefore(startContainer); } if (endContainer && isLeaf(endContainer)) { range.setEndBefore(endContainer); } } if (range && root.contains(range.commonAncestorContainer)) { this._lastSelection = range; } else { range = this._lastSelection; if (!document.contains(range.commonAncestorContainer)) { range = null; } } return range || createRange(root.firstElementChild || root, 0); } setSelection(range) { this._lastSelection = range; if (!this._isFocused) { this._enableRestoreSelection(); } else { const selection = window.getSelection(); if (selection) { if ("setBaseAndExtent" in Selection.prototype) { selection.setBaseAndExtent( range.startContainer, range.startOffset, range.endContainer, range.endOffset ); } else { selection.removeAllRanges(); selection.addRange(range); } } } return this; } // --- _moveCursorTo(toStart) { const root = this._root; const range = createRange(root, toStart ? 0 : root.childNodes.length); moveRangeBoundariesDownTree(range); this.setSelection(range); return this; } moveCursorToStart() { return this._moveCursorTo(true); } moveCursorToEnd() { return this._moveCursorTo(false); } // --- getCursorPosition() { const range = this.getSelection(); let rect = range.getBoundingClientRect(); if (rect && !rect.top) { this._ignoreChange = true; const node = createElement("SPAN"); node.textContent = ZWS; insertNodeInRange(range, node); rect = node.getBoundingClientRect(); const parent = node.parentNode; parent.removeChild(node); mergeInlines(parent, range); } return rect; } // --- Path getPath() { return this._path; } _updatePathOnEvent() { if (this._isFocused) { this._updatePath(this.getSelection()); } } _updatePath(range, force) { const anchor = range.startContainer; const focus = range.endContainer; let newPath; if (force || anchor !== this._lastAnchorNode || focus !== this._lastFocusNode) { this._lastAnchorNode = anchor; this._lastFocusNode = focus; newPath = anchor && focus ? anchor === focus ? this._getPath(focus) : "(selection)" : ""; if (this._path !== newPath || anchor !== focus) { this._path = newPath; this.fireEvent("pathChange", { path: newPath }); } } this.fireEvent(range.collapsed ? "cursor" : "select", { range }); } _getPath(node) { const root = this._root; const config = this._config; let path = ""; if (node && node !== root) { const parent = node.parentNode; path = parent ? this._getPath(parent) : ""; if (node instanceof HTMLElement) { const id = node.id; const classList = node.classList; const classNames = Array.from(classList).sort(); const dir = node.dir; const styleNames = config.classNames; path += (path ? ">" : "") + node.nodeName; if (id) { path += "#" + id; } if (classNames.length) { path += "."; path += classNames.join("."); } if (dir) { path += "[dir=" + dir + "]"; } if (classList.contains(styleNames.highlight)) { path += "[backgroundColor=" + node.style.backgroundColor.replace(/ /g, "") + "]"; } if (classList.contains(styleNames.color)) { path += "[color=" + node.style.color.replace(/ /g, "") + "]"; } if (classList.contains(styleNames.fontFamily)) { path += "[fontFamily=" + node.style.fontFamily.replace(/ /g, "") + "]"; } if (classList.contains(styleNames.fontSize)) { path += "[fontSize=" + node.style.fontSize + "]"; } } } return path; } // --- History modifyDocument(modificationFn) { const mutation = this._mutation; if (mutation) { if (mutation.takeRecords().length) { this._docWasChanged(); } mutation.disconnect(); } this._ignoreAllChanges = true; modificationFn(); this._ignoreAllChanges = false; if (mutation) { mutation.observe(this._root, { childList: true, attributes: true, characterData: true, subtree: true }); this._ignoreChange = false; } return this; } _docWasChanged() { resetNodeCategoryCache(); this._mayHaveZWS = true; if (this._ignoreAllChanges) { return; } if (this._ignoreChange) { this._ignoreChange = false; return; } if (this._isInUndoState) { this._isInUndoState = false; this.fireEvent("undoStateChange", { canUndo: true, canRedo: false }); } this.fireEvent("input"); } /** * Leaves bookmark. */ _recordUndoState(range, replace) { const isInUndoState = this._isInUndoState; if (!isInUndoState || replace) { let undoIndex = this._undoIndex + 1; const undoStack = this._undoStack; const undoConfig = this._config.undo; const undoThreshold = undoConfig.documentSizeThreshold; const undoLimit = undoConfig.undoLimit; if (undoIndex < this._undoStackLength) { undoStack.length = this._undoStackLength = undoIndex; } if (range) { this._saveRangeToBookmark(range); } if (isInUndoState) { return this; } const html = this._getRawHTML(); if (replace) { --undoIndex; } if (undoThreshold > -1 && html.length * 2 > undoThreshold) { if (undoLimit > -1 && undoIndex > undoLimit) { undoStack.splice(0, undoIndex - undoLimit); undoIndex = undoLimit; this._undoStackLength = undoLimit; } } undoStack[undoIndex] = html; this._undoIndex = undoIndex; ++this._undoStackLength; this._isInUndoState = true; } return this; } saveUndoState(range) { range || (range = this.getSelection()); this._recordUndoState(range, this._isInUndoState); this._getRangeAndRemoveBookmark(range); return this; } undo() { if (this._undoIndex !== 0 || !this._isInUndoState) { this._recordUndoState(this.getSelection(), false); --this._undoIndex; this._setRawHTML(this._undoStack[this._undoIndex]); const range = this._getRangeAndRemoveBookmark(); if (range) { this.setSelection(range); } this._isInUndoState = true; this.fireEvent("undoStateChange", { canUndo: this._undoIndex !== 0, canRedo: true }); this.fireEvent("input"); } return this.focus(); } redo() { const undoIndex = this._undoIndex; const undoStackLength = this._undoStackLength; if (undoIndex + 1 < undoStackLength && this._isInUndoState) { ++this._undoIndex; this._setRawHTML(this._undoStack[this._undoIndex]); const range = this._getRangeAndRemoveBookmark(); if (range) { this.setSelection(range); } this.fireEvent("undoStateChange", { canUndo: true, canRedo: undoIndex + 2 < undoStackLength }); this.fireEvent("input"); } return this.focus(); } // --- Get and set data getRoot() { return this._root; } _getRawHTML() { return this._root.innerHTML; } _setRawHTML(html) { if (html !== void 0) { const root = this._root; let node = root; root.innerHTML = html; do { fixCursor(node); } while (node = getNextBlock(node, root)); this._ignoreChange = true; } return this; } getHTML(withBookmark) { let range; if (withBookmark) { range = this.getSelection(); this._saveRangeToBookmark(range); } const html = this._getRawHTML().replace(/\u200B/g, ""); if (withBookmark) { this._getRangeAndRemoveBookmark(range); } return html; } setHTML(html) { const frag = this._config.sanitizeToDOMFragment(html, this); const root = this._root; cleanTree(frag, this._config); cleanupBRs(frag); fixContainer(frag); let node = frag; let child = node.firstChild; if (!child || child.nodeName === "BR") { const block = this.createDefaultBlock(); if (child) { node.replaceChild(block, child); } else { node.append(block); } } else { while (node = getNextBlock(node, root)) { fixCursor(node); } } this._ignoreChange = true; while (child = root.lastChild) { root.removeChild(child); } root.append(frag); this._undoIndex = -1; this._undoStack.length = 0; this._undoStackLength = 0; this._isInUndoState = false; const range = this._getRangeAndRemoveBookmark() || createRange(root.firstElementChild || root, 0); this.saveUndoState(range); this.setSelection(range); this._updatePath(range, true); return this; } /** * Insert HTML at the cursor location. If the selection is not collapsed * insertTreeFragmentIntoRange will delete the selection so that it is * replaced by the html being inserted. */ insertHTML(html, isPaste) { const config = this._config; let frag = config.sanitizeToDOMFragment(html, this); const range = this.getSelection(); this.saveUndoState(range); try { const root = this._root; if (config.addLinks) { this.addDetectedLinks(frag, frag); } cleanTree(frag, this._config); cleanupBRs(frag); removeEmptyInlines(frag); frag.normalize(); let node = frag; while (node = getNextBlock(node, frag)) { fixCursor(node); } let doInsert = true; if (isPaste) { const event = new CustomEvent("willPaste", { cancelable: true, detail: { html, fragment: frag } }); this.fireEvent("willPaste", event); frag = event.detail.fragment; doInsert = !event.defaultPrevented; } if (doInsert) { insertTreeFragmentIntoRange(range, frag, root); range.collapse(false); moveRangeBoundaryOutOf(range, "A", root); this._ensureBottomLine(); } this.setSelection(range); this._updatePath(range, true); if (isPaste) { this.focus(); } } catch (error) { this._config.didError(error); } return this; } insertElement(el, range) { if (!range) { range = this.getSelection(); } range.collapse(true); if (isInline(el)) { insertNodeInRange(range, el); range.setStartAfter(el); } else { const root = this._root; const startNode = getStartBlockOfRange( range, root ); let splitNode = startNode || root; let nodeAfterSplit = null; while (splitNode !== root && !splitNode.nextSibling) { splitNode = splitNode.parentNode; } if (splitNode !== root) { const parent = splitNode.parentNode; nodeAfterSplit = split( parent, splitNode.nextSibling, root, root ); } if (startNode && isEmptyBlock(startNode)) { detach(startNode); } root.insertBefore(el, nodeAfterSplit); const blankLine = this.createDefaultBlock(); root.insertBefore(blankLine, nodeAfterSplit); range.setStart(blankLine, 0); range.setEnd(blankLine, 0); moveRangeBoundariesDownTree(range); } this.focus(); this.setSelection(range); this._updatePath(range); return this; } insertImage(src, attributes) { const img = createElement( "IMG", Object.assign( { src }, attributes ) ); this.insertElement(img); return img; } insertPlainText(plainText, isPaste) { const range = this.getSelection(); if (range.collapsed && getClosest(range.startContainer, this._root, "PRE")) { const startContainer = range.startContainer; let offset = range.startOffset; let textNode; if (!startContainer || !(startContainer instanceof Text)) { const text = document.createTextNode(""); startContainer.insertBefore( text, startContainer.childNodes[offset] ); textNode = text; offset = 0; } else { textNode = startContainer; } let doInsert = true; if (isPaste) { const event = new CustomEvent("willPaste", { cancelable: true, detail: { text: plainText } }); this.fireEvent("willPaste", event); plainText = event.detail.text; doInsert = !event.defaultPrevented; } if (doInsert) { textNode.insertData(offset, plainText); range.setStart(textNode, offset + plainText.length); range.collapse(true); } this.setSelection(range); return this; } const lines = plainText.split("\n"); const config = this._config; const tag = config.blockTag; const closeBlock = "</" + tag + ">"; const openBlock = "<" + tag + ">"; for (let i = 0, l = lines.length; i < l; ++i) { let line = lines[i]; line = escapeHTML(line).replace(/ (?=(?: |$))/g, " "); if (i) { line = openBlock + (line || "<BR>") + closeBlock; } lines[i] = line; } return this.insertHTML(lines.join(""), isPaste); } getSelectedText(range) { return getTextContentsOfRange(range || this.getSelection()); } // --- Inline formatting /** * Extracts the font-family and font-size (if any) of the element * holding the cursor. If there's a selection, returns an empty object. */ getFontInfo(range) { const fontInfo = { color: void 0, backgroundColor: void 0, fontFamily: void 0, fontSize: void 0 }; if (!range) { range = this.getSelection(); } moveRangeBoundariesDownTree(range); let seenAttributes = 0; let element = range.commonAncestorContainer; if (range.collapsed || element instanceof Text) { if (element instanceof Text) { element = element.parentNode; } while (seenAttributes < 4 && element) { const style = element.style; if (style) { const color = style.color; if (!fontInfo.color && color) { fontInfo.color = color; ++seenAttributes; } const backgroundColor = style.backgroundColor; if (!fontInfo.backgroundColor && backgroundColor) { fontInfo.backgroundColor = backgroundColor; ++seenAttributes; } const fontFamily = style.fontFamily; if (!fontInfo.fontFamily && fontFamily) { fontInfo.fontFamily = fontFamily; ++seenAttributes; } const fontSize = style.fontSize; if (!fontInfo.fontSize && fontSize) { fontInfo.fontSize = fontSize; ++seenAttributes; } } element = element.parentNode; } } return fontInfo; } /** * Looks for matching tag and attributes, so won't work if <strong> * instead of <b> etc. */ hasFormat(tag, attributes, range) { tag = tag.toUpperCase(); attributes || (attributes = {}); range || (range = this.getSelection()); if (!range.collapsed && range.startContainer instanceof Text && range.startOffset === range.startContainer.length && range.startContainer.nextSibling) { range.setStartBefore(range.startContainer.nextSibling); } if (!range.collapsed && range.endContainer instanceof Text && range.endOffset === 0 && range.endContainer.previousSibling) { range.setEndAfter(range.endContainer.previousSibling); } const root = this._root; const common = range.commonAncestorContainer; if (getNearest(common, root, tag, attributes)) { return true; } if (common instanceof Text) { return false; } const walker = createTreeWalker( common, SHOW_TEXT, (node2) => isNodeContainedInRange(range, node2, true) ); let seenNode = false; let node; while (node = walker.nextNode()) { if (!getNearest(node, root, tag, attributes)) { return false; } seenNode = true; } return seenNode; } changeFormat(add, remove, range, partial) { if (!range) { range = this.getSelection(); } this.saveUndoState(range); if (remove) { range = this._removeFormat( remove.tag.toUpperCase(), remove.attributes || {}, range, partial ); } if (add) { range = this._addFormat( add.tag.toUpperCase(), add.attributes || {}, range ); } this.setSelection(range); this._updatePath(range, true); return this.focus(); } _addFormat(tag, attributes, range) { const root = this._root; if (range.collapsed) { const el = fixCursor(createElement(tag, attributes)); insertNodeInRange(range, el); const focusNode = el.firstChild || el; const focusOffset = focusNode instanceof Text ? focusNode.length : 0; range.setStart(focusNode, focusOffset); range.collapse(true); let block = el; while (isInline(block)) { block = block.parentNode; } removeZWS(block, el); } else { const walker = createTreeWalker( range.commonAncestorContainer, SHOW_ELEMENT_OR_TEXT, (node) => { return (node instanceof Text || node.nodeName === "BR" || node.nodeName === "IMG") && isNodeContainedInRange(range, node, true); } ); let { startContainer, startOffset, endContainer, endOffset } = range; walker.currentNode = startContainer; if (!(startContainer instanceof Element) && !(startContainer instanceof Text) || FILTER_ACCEPT !== walker.filter.acceptNode(startContainer)) { const next = walker.nextNode(); if (!next) { return range; } startContainer = next; startOffset = 0; } do { let node = walker.currentNode; const needsFormat = !getNearest(node, root, tag, attributes); if (needsFormat) { if (node === endContainer && node.length > endOffset) { node.splitText(endOffset); } if (node === startContainer && startOffset) { node = node.splitText(startOffset); if (endContainer === startContainer) { endContainer = node; endOffset -= startOffset; } else if (endContainer === startContainer.parentNode) { ++endOffset; } startContainer = node; startOffset = 0; } const el = createElement(tag, attributes); replaceWith(node, el); el.append(node); } } while (walker.nextNode()); range = createRange( startContainer, startOffset, endContainer, endOffset ); } return range; } _removeFormat(tag, attributes, range, partial) { this._saveRangeToBookmark(range); let fixer; if (range.collapsed) { if (cantFocusEmptyTextNodes) { fixer = document.createTextNode(ZWS); } else { fixer = document.createTextNode(""); } insertNodeInRange(range, fixer); } let root = range.commonAncestorContainer; while (isInline(root)) { root = root.parentNode; } const startContainer = range.startContainer; const startOffset = range.startOffset; const endContainer = range.endContainer; const endOffset = range.endOffset; const toWrap = []; const examineNode = (node, exemplar) => { if (isNodeContainedInRange(range, node, false)) { return; } let child; let next; if (!isNodeContainedInRange(range, node, true)) { if (!(node instanceof HTMLInputElement) && (!(node instanceof Text) || node.data)) { toWrap.push([exemplar, node]); } return; } if (node instanceof Text) { if (node === endContainer && endOffset !== node.length) { toWrap.push([exemplar, node.splitText(endOffset)]); } if (node === startContainer && startOffset) { node.splitText(startOffset); toWrap.push([exemplar, node]); } } else { for (child = node.firstChild; child; child = next) { next = child.nextSibling; examineNode(child, exemplar); } } }; const formatTags = Array.from( root.getElementsByTagName(tag) ).filter((el) => { return isNodeContainedInRange(range, el, true) && hasTagAttributes(el, tag, attributes); }); partial || formatTags.forEach((node) => examineNode(node, node)); toWrap.forEach(([el, node]) => { el = el.cloneNode(false); replaceWith(node, el); el.append(node); }); formatTags.forEach((el) => replaceWith(el, empty(el))); if (cantFocusEmptyTextNodes && fixer) { fixer = fixer.parentNode; let block = fixer; while (block && isInline(block)) { block = block.parentNode; } block && removeZWS(block, fixer); } this._getRangeAndRemoveBookmark(range); fixer && range.collapse(false); mergeInlines(root, range); return range; } // --- bold() { this.toggleTag("B"); } italic() { this.toggleTag("I"); } underline() { this.toggleTag("U"); } strikethrough() { this.toggleTag("S"); } subscript() { this.toggleTag("SUB", "SUP"); } superscript() { this.toggleTag("SUP", "SUB"); } // --- makeLink(url, attributes) { const range = this.getSelection(); if (range.collapsed) { let protocolEnd = url.indexOf(":") + 1; if (protocolEnd) { while (url[protocolEnd] === "/") { ++protocolEnd; } } insertNodeInRange( range, document.createTextNode(url.slice(protocolEnd)) ); } attributes = Object.assign( { href: url }, attributes ); return this.changeFormat( { tag: "A", attributes }, { tag: "A" }, range ); } removeLink() { return this.changeFormat( null, { tag: "A" }, this.getSelection(), true ); } addDetectedLinks(searchInNode, root) { const walker = createTreeWalker( searchInNode, SHOW_TEXT, (node2) => !getClosest(node2, root || this._root, "A") ); const linkRegExp = this.linkRegExp; let node; while (node = walker.nextNode()) { const parent = node.parentNode; let data = node.data; let match; while (match = linkRegExp.exec(data)) { const index = match.index; const endIndex = index + match[0].length; if (index) { parent.insertBefore( document.createTextNode(data.slice(0, index)), node ); } const child = createElement( "A", { href: match[1] ? /^(?:ht|f)tps?:/i.test(match[1]) ? match[1] : "http://" + match[1] : "mailto:" + match[0] } ); child.textContent = data.slice(index, endIndex); parent.insertBefore(child, node); node.data = data = data.slice(endIndex); } } return this; } // --- Block formatting _ensureBottomLine() { const root = this._root; const last = root.lastElementChild; if (!last || last.nodeName !== this._config.blockTag || !isBlock(last)) { root.append(this.createDefaultBlock()); } } createDefaultBlock(children) { const config = this._config; return fixCursor( createElement(config.blockTag, null, children) ); } splitBlock(lineBreakOnly, range) { if (!range) { range = this.getSelection(); } const root = this._root; let block; let parent; let node; let nodeAfterSplit; this._recordUndoState(range); this._removeZWS(); this._getRangeAndRemoveBookmark(range); if (!range.collapsed) { deleteContentsOfRange(range, root); } if (this._config.addLinks) { moveRangeBoundariesDownTree(range); const textNode = range.startContainer; const offset2 = range.startOffset; setTimeout(() => { linkifyText(this, textNode, offset2); }, 0); } block = getStartBlockOfRange(range, root); if (block && (parent = getClosest(block, root, "PRE"))) { moveRangeBoundariesDownTree(range); node = range.startContainer; const offset2 = range.startOffset; if (!(node instanceof Text)) { node = document.createTextNode(""); parent.insertBefore(node, parent.firstChild); } if (!lineBreakOnly && node instanceof Text && (node.data.charAt(offset2 - 1) === "\n" || rangeDoesStartAtBlockBoundary(range, root)) && (node.data.charAt(offset2) === "\n" || rangeDoesEndAtBlockBoundary(range, root))) { node.deleteData(offset2 && offset2 - 1, offset2 ? 2 : 1); nodeAfterSplit = split( node, offset2 && offset2 - 1, root, root ); node = nodeAfterSplit.previousSibling; if (!node.textContent) { detach(node); } node = this.createDefaultBlock(); nodeAfterSplit.parentNode.insertBefore(node, nodeAfterSplit); if (!nodeAfterSplit.textContent) { detach(nodeAfterSplit); } range.setStart(node, 0); } else { node.insertData(offset2, "\n"); fixCursor(parent); if (node.length === offset2 + 1) { range.setStartAfter(node); } else { range.setStart(node, offset2 + 1); } } range.collapse(true); this.setSelection(range); this._updatePath(range, true); this._docWasChanged(); return this; } if (!block || lineBreakOnly || /^T[HD]$/.test(block.nodeName)) { moveRangeBoundaryOutOf(range, "A", root); insertNodeInRange(range, createElement("BR")); range.collapse(false); this.setSelection(range); this._updatePath(range, true); return this; } if (parent = getClosest(block, root, "LI")) { block = parent; } if (isEmptyBlock(block)) { if (getClosest(block, root, "UL") || getClosest(block, root, "OL")) { this.decreaseListLevel(range); return this; } else if (getClosest(block, root, "BLOCKQUOTE")) { this.replaceWithBlankLine(range); return this; } } node = range.startContainer; const offset = range.startOffset; let splitTag = this.tagAfterSplit[block.nodeName] || this._config.blockTag; nodeAfterSplit = split( node, offset, block.parentNode, this._root ); if (!hasTagAttributes(nodeAfterSplit, splitTag)) { block = createElement(splitTag); if (nodeAfterSplit.dir) { block.dir = nodeAfterSplit.dir; } replaceWith(nodeAfterSplit, block); block.append(empty(nodeAfterSplit)); nodeAfterSplit = block; } removeZWS(block); removeEmptyInlines(block); fixCursor(block); while (nodeAfterSplit instanceof Element) { let child = nodeAfterSplit.firstChild; let next; if (nodeAfterSplit.nodeName === "A" && (!nodeAfterSplit.textContent || nodeAfterSplit.textContent === ZWS)) { child = document.createTextNode(""); replaceWith(nodeAfterSplit, child); nodeAfterSplit = child; break; } while (child && child instanceof Text && !child.length) { next = child.nextSibling; if (!next || next.nodeName === "BR") { break; } detach(child); child = next; } if (!child || child.nodeName === "BR" || child instanceof Text) { break; } nodeAfterSplit = child; } range = createRange(nodeAfterSplit, 0); this.setSelection(range); this._updatePath(range, true); return this; } forEachBlock(fn, mutates, range) { if (!range) { range = this.getSelection(); } if (mutates) { this.saveUndoState(range); } const root = this._root; let start = getStartBlockOfRange(range, root); const end = getEndBlockOfRange(range, root); if (start && end) { do { if (fn(start) || start === end) { break; } } while (start = getNextBlock(start, root)); } if (mutates) { this.setSelection(range); this._updatePath(range, true); } return this; } modifyBlocks(modify, range) { if (!range) { range = this.getSelection(); } this._recordUndoState(range, this._isInUndoState); const root = this._root; expandRangeToBlockBoundaries(range, root); moveRangeBoundariesUpTree(range, root, root, root); const frag = extractContentsOfRange(range, root, root); if (!range.collapsed) { let node = range.endContainer; if (node === root) { range.collapse(false); } else { while (node.parentNode !== root) { node = node.parentNode; } range.setStartBefore(node); range.collapse(true); } } insertNodeInRange(range, modify.call(this, frag)); if (range.endOffset < range.endContainer.childNodes.length) { mergeContainers( range.endContainer.childNodes[range.endOffset], root ); } mergeContainers( range.startContainer.childNodes[range.startOffset], root ); this._getRangeAndRemoveBookmark(range); this.setSelection(range); this._updatePath(range, true); return this; } // --- setTextAlignment(alignment) { this.forEachBlock((block) => { const className = block.className.split(/\s+/).filter((klass) => { return !!klass && !/^align/.test(klass); }).join(" "); if (alignment) { block.className = className + " align-" + alignment; block.style.textAlign = alignment; } else { block.className = className; block.style.textAlign = ""; } }, true); return this.focus(); } setTextDirection(direction) { this.forEachBlock((block) => { if (direction) { block.dir = direction; } else { block.removeAttribute("dir"); } }, true); return this.focus(); } // --- _getListSelection(range, root) { let list = range.commonAncestorContainer; let startLi = range.startContainer; let endLi = range.endContainer; while (list && list !== root && !/^[OU]L$/.test(list.nodeName)) { list = list.parentNode; } if (!list || list === root) { return null; } if (startLi === list) { startLi = startLi.childNodes[range.startOffset]; } if (endLi === list) { endLi = endLi.childNodes[range.endOffset]; } while (startLi && startLi.parentNode !== list) { startLi = startLi.parentNode; } while (endLi && endLi.parentNode !== list) { endLi = endLi.parentNode; } return [list, startLi, endLi]; } increaseListLevel(range) { if (!range) { range = this.getSelection(); } const root = this._root; const listSelection = this._getListSelection(range, root); if (!listSelection) { return this.focus(); } let [list, startLi, endLi] = listSelection; if (!startLi || startLi === list.firstChild) { return this.focus(); } this._recordUndoState(range, this._isInUndoState); const type = list.nodeName; let newParent = startLi.previousSibling; let next; if (newParent.nodeName !== type) { newParent = createElement(type); list.insertBefore(newParent, startLi); } do { next = startLi === endLi ? null : startLi.nextSibling; newParent.append(startLi); } while (startLi = next); next = newParent.nextSibling; if (next) { mergeContainers(next, root); } this._getRangeAndRemoveBookmark(range); this.setSelection(range); this._updatePath(range, true); return this.focus(); } decreaseListLevel(range) { if (!range) { range = this.getSelection(); } const root = this._root; const listSelection = this._getListSelection(range, root); if (!listSelection) { return this.focus(); } let [list, startLi, endLi] = listSelection; if (!startLi) { startLi = list.firstChild; } if (!endLi) { endLi = list.lastChild; } this._recordUndoState(range, this._isInUndoState); let next; let insertBefore = null; if (startLi) { let newParent = list.parentNode; insertBefore = !endLi.nextSibling ? list.nextSibling : split(list, endLi.nextSibling, newParent, root); if (newParent !== root && newParent.nodeName === "LI") { newParent = newParent.parentNode; while (insertBefore) { next = insertBefore.nextSibling; endLi.append(insertBefore); insertBefore = next; } insertBefore = list.parentNode.nextSibling; } const makeNotList = !/^[OU]L$/.test(newParent.nodeName); do { next = startLi === endLi ? null : startLi.nextSibling; list.removeChild(startLi); if (makeNotList && startLi.nodeName === "LI") { startLi = this.createDefaultBlock([empty(startLi)]); } newParent.insertBefore(startLi, insertBefore); } while (startLi = next); } if (!list.firstChild) { detach(list); } if (insertBefore) { mergeContainers(insertBefore, root); } this._getRangeAndRemoveBookmark(range); this.setSelection(range); this._updatePath(range, true); return this.focus(); } _makeList(frag, type) { const walker = getBlockWalker(frag, this._root); let node; while (node = walker.nextNode()) { if (node.parentNode instanceof HTMLLIElement) { node = node.parentNode; walker.currentNode = node.lastChild; } if (!(node instanceof HTMLLIElement)) { const newLi = createElement("LI"); if (node.dir) { newLi.dir = node.dir; } const prev = node.previousSibling; if (prev && prev.nodeName === type) { prev.append(newLi); detach(node); } else { replaceWith(node, createElement(type, null, [newLi])); } newLi.append(empty(node)); walker.currentNode = newLi; } else { node = node.parentNode; const tag = node.nodeName; if (tag !== type && /^[OU]L$/.test(tag)) { replaceWith( node, createElement(type, null, [empty(node)]) ); } } } return frag; } makeUnorderedList() { this.modifyBlocks((frag) => this._makeList(frag, "UL")); return this.focus(); } makeOrderedList() { this.modifyBlocks((frag) => this._makeList(frag, "OL")); return this.focus(); } removeList() { this.modifyBlocks((frag) => { const lists = frag.querySelectorAll("UL, OL"); const items = frag.querySelectorAll("LI"); const root = this._root; for (let i = 0, l = lists.length; i < l; ++i) { const list = lists[i]; const listFrag = empty(list); fixContainer(listFrag); replaceWith(list, listFrag); } for (let i = 0, l = items.length; i < l; ++i) { const item = items[i]; if (isBlock(item)) { replaceWith(item, this.createDefaultBlock([empty(item)])); } else { fixContainer(item); replaceWith(item, empty(item)); } } return frag; }); return this.focus(); } // --- increaseQuoteLevel(range) { this.modifyBlocks( (frag) => createElement( "BLOCKQUOTE", null, [frag] ), range ); return this.focus(); } decreaseQuoteLevel(range) { this.modifyBlocks((frag) => { Array.from(frag.querySelectorAll("blockquote")).filter((el) => { return !getClosest(el.parentNode, frag, "BLOCKQUOTE"); }).forEach((el) => { replaceWith(el, empty(el)); }); return frag; }, range); return this.focus(); } removeQuote(range) { this.modifyBlocks((frag) => { Array.from(frag.querySelectorAll("blockquote")).forEach( (el) => { replaceWith(el, empty(el)); } ); return frag; }, range); return this.focus(); } replaceWithBlankLine(range) { this.modifyBlocks( () => this.createDefaultBlock([ createElement("INPUT", { id: this.startSelectionId, type: "hidden" }), createElement("INPUT", { id: this.endSelectionId, type: "hidden" }) ]), range ); return this.focus(); } // --- code() { const range = this.getSelection(); if (range.collapsed || isContainer(range.commonAncestorContainer)) { this.modifyBlocks((frag) => { const root = this._root; const output = document.createDocumentFragment(); const blockWalker = getBlockWalker(frag, root); let node; while (node = blockWalker.nextNode()) { let nodes = node.querySelectorAll("BR"); const brBreaksLine = []; let l = nodes.length; for (let i = 0; i < l; ++i) { brBreaksLine[i] = isLineBreak(nodes[i]); } while (l--) { const br = nodes[l]; if (!brBreaksLine[l]) { detach(br); } else { replaceWith(br, document.createTextNode("\n")); } } nodes = node.querySelectorAll("CODE"); l = nodes.length; while (l--) { replaceWith(nodes[l], empty(nodes[l])); } if (output.childNodes.length) { output.append(document.createTextNode("\n")); } output.append(empty(node)); } const textWalker = createTreeWalker(output, SHOW_TEXT); while (node = textWalker.nextNode()) { node.data = node.data.replace(/ /g, " "); } output.normalize(); return fixCursor( createElement("PRE", null, [ output ]) ); }, range); this.focus(); } else { this.changeFormat( { tag: "CODE", attributes: null }, null, range ); } return this; } removeCode() { const range = this.getSelection(); const ancestor = range.commonAncestorContainer; const inPre = getClosest(ancestor, this._root, "PRE"); if (inPre) { this.modifyBlocks((frag) => { const root = this._root; const pres = frag.querySelectorAll("PRE"); let l = pres.length; while (l--) { const pre = pres[l]; const walker = createTreeWalker(pre, SHOW_TEXT); let node; while (node = walker.nextNode()) { let value = node.data; value = value.replace(/ (?= )/g, "\xA0"); const contents = document.createDocumentFragment(); let index; while ((index = value.indexOf("\n")) > -1) { contents.append( document.createTextNode(value.slice(0, index)) ); contents.append(createElement("BR")); value = value.slice(index + 1); } node.parentNode.insertBefore(contents, node); node.data = value; } fixContainer(pre); replaceWith(pre, empty(pre)); } return frag; }, range); this.focus(); } else { this.changeFormat(null, { tag: "CODE" }, range); } return this; } toggleCode() { if (this.hasFormat("PRE") || this.hasFormat("CODE")) { this.removeCode(); } else { this.code(); } return this; } /** * SnappyMail */ changeIndentationLevel(direction) { let parent = this.getSelectionClosest("UL,OL,BLOCKQUOTE"); if (parent || "increase" === direction) { direction += !parent || "BLOCKQUOTE" === parent.nodeName ? "Quote" : "List"; return this[direction + "Level"](); } } getSelectionClosest(selector) { return getClosest(this.getSelection().commonAncestorContainer, this._root, selector); } setAttribute(name, value) { let range = this.getSelection(); let start = range?.startContainer || {}; let end = range?.endContainer || {}; if ("dir" == name || start instanceof Text && 0 === range.startOffset && start === end && end.length === range.endOffset) { this._recordUndoState(range); setAttributes(start.parentNode, { [name]: value }); this._docWasChanged(); } else if (null == value) { this._recordUndoState(range); let node = getClosest(range.commonAncestorContainer, this._root, "*"); range.collapsed ? setAttributes(node, { [name]: value }) : node.querySelectorAll("*").forEach((el) => setAttributes(el, { [name]: value })); this._docWasChanged(); } else { this.changeFormat({ tag: "SPAN", attributes: { [name]: value } }, null, range); } return this.focus(); } setStyle(style) { this.setAttribute("style", style); } toggleTag(name, remove) { let range = this.getSelection(); if (this.hasFormat(name, null, range)) { this.changeFormat(null, { tag: name }, range); } else { this.changeFormat({ tag: name }, remove ? { tag: remove } : null, range); } } setRange(range) { this.setSelection(range); this._updatePath(range, true); } setConfig(config) { this._config = mergeObjects({ addLinks: true }, config, true); return this; } }; // source/Legacy.ts window.Squire = Squire; })(); /** * Modified version of https://github.com/mathiasbynens/punycode.js */ (() => { 'use strict'; const /** Highest positive signed 32-bit float value */ maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1 /** Bootstring parameters */ base = 36, tMin = 1, tMax = 26, skew = 38, damp = 700, initialBias = 72, initialN = 128, // 0x80 delimiter = '-', // '\x2D' /** Regular expressions */ regexPunycode = /^xn--/, regexNonASCII = /[^\0-\x7F]/, // Note: U+007F DEL is excluded too. regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g, // RFC 3490 separators /** Error messages */ errors = { 'overflow': 'Overflow: input needs wider integers to process', 'not-basic': 'Illegal input >= 0x80 (not a basic code point)', 'invalid-input': 'Invalid input' }, /** Convenience shortcuts */ baseMinusTMin = base - tMin, floor = Math.floor, stringFromCharCode = String.fromCharCode, /*--------------------------------------------------------------------------*/ /** * A generic error utility function. * @private * @param {String} type The error type. * @returns {Error} Throws a `RangeError` with the applicable error message. */ error = type => { throw new RangeError(errors[type]) }, /** * A simple `Array#map`-like wrapper to work with domain name strings or email * addresses. * @private * @param {String} domain The domain name or email address. * @param {Function} callback The function that gets called for every * character. * @returns {String} A new string of characters returned by the callback * function. */ mapDomain = (domain, callback) => { // In email addresses, only the domain name should be punycoded. // Leave the local part (i.e. everything up to `@`) intact. const parts = (domain || '').split('@'); parts.push( parts.pop() .split(regexSeparators) .map(label => callback(label)) .join('.') ); return parts.join('@'); }, /** * Creates an array containing the numeric code points of each Unicode * character in the string. While JavaScript uses UCS-2 internally, * this function will convert a pair of surrogate halves (each of which * UCS-2 exposes as separate characters) into a single code point, * matching UTF-16. * @see <https://mathiasbynens.be/notes/javascript-encoding> * @name decode * @param {String} string The Unicode input string (UCS-2). * @returns {Array} The new array of code points. */ ucs2decode = string => { const output = []; let counter = 0; const length = string.length; while (counter < length) { const value = string.charCodeAt(counter++); if (value >= 0xD800 && value <= 0xDBFF && counter < length) { // It's a high surrogate, and there is a next character. const extra = string.charCodeAt(counter++); if ((extra & 0xFC00) == 0xDC00) { // Low surrogate. output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); } else { // It's an unmatched surrogate; only append this code unit, in case the // next code unit is the high surrogate of a surrogate pair. output.push(value); counter--; } } else { output.push(value); } } return output; }, /** * Converts a basic code point into a digit/integer. * @see `digitToBasic()` * @private * @param {Number} codePoint The basic numeric code point value. * @returns {Number} The numeric value of a basic code point (for use in * representing integers) in the range `0` to `base - 1`, or `base` if * the code point does not represent a value. */ basicToDigit = codePoint => { if (codePoint >= 0x30 && codePoint < 0x3A) { return 26 + (codePoint - 0x30); } if (codePoint >= 0x41 && codePoint < 0x5B) { return codePoint - 0x41; } if (codePoint >= 0x61 && codePoint < 0x7B) { return codePoint - 0x61; } return base; }, /** * Converts a digit/integer into a basic code point. * @see `basicToDigit()` * @private * @param {Number} digit The numeric value of a basic code point. * @returns {Number} The basic code point whose value (when used for * representing integers) is `digit`, which needs to be in the range * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is * used; else, the lowercase form is used. The behavior is undefined * if `flag` is non-zero and `digit` has no uppercase form. */ digitToBasic = (digit, flag) => // 0..25 map to ASCII a..z or A..Z // 26..35 map to ASCII 0..9 digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5), /** * Bias adaptation function as per section 3.4 of RFC 3492. * https://tools.ietf.org/html/rfc3492#section-3.4 * @private */ adapt = (delta, numPoints, firstTime) => { let k = 0; delta = firstTime ? floor(delta / damp) : delta >> 1; delta += floor(delta / numPoints); for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) { delta = floor(delta / baseMinusTMin); } return floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); }, /** * Converts a Punycode string of ASCII-only symbols to a string of Unicode * symbols. * @memberOf punycode * @param {String} input The Punycode string of ASCII-only symbols. * @returns {String} The resulting string of Unicode symbols. */ decode = input => { // Don't use UCS-2. const output = []; const inputLength = input.length; let i = 0; let n = initialN; let bias = initialBias; // Handle the basic code points: let `basic` be the number of input code // points before the last delimiter, or `0` if there is none, then copy // the first basic code points to the output. let basic = input.lastIndexOf(delimiter); if (basic < 0) { basic = 0; } for (let j = 0; j < basic; ++j) { // if it's not a basic code point if (input.charCodeAt(j) >= 0x80) { error('not-basic'); } output.push(input.charCodeAt(j)); } // Main decoding loop: start just after the last delimiter if any basic code // points were copied; start at the beginning otherwise. for (let index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) { // `index` is the index of the next character to be consumed. // Decode a generalized variable-length integer into `delta`, // which gets added to `i`. The overflow checking is easier // if we increase `i` as we go, then subtract off its starting // value at the end to obtain `delta`. const oldi = i; for (let w = 1, k = base; /* no condition */; k += base) { if (index >= inputLength) { error('invalid-input'); } const digit = basicToDigit(input.charCodeAt(index++)); if (digit >= base) { error('invalid-input'); } if (digit > floor((maxInt - i) / w)) { error('overflow'); } i += digit * w; const t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); if (digit < t) { break; } const baseMinusT = base - t; if (w > floor(maxInt / baseMinusT)) { error('overflow'); } w *= baseMinusT; } const out = output.length + 1; bias = adapt(i - oldi, out, oldi == 0); // `i` was supposed to wrap around from `out` to `0`, // incrementing `n` each time, so we'll fix that now: if (floor(i / out) > maxInt - n) { error('overflow'); } n += floor(i / out); i %= out; // Insert `n` at position `i` of the output. output.splice(i++, 0, n); } return String.fromCodePoint(...output); }, /** * Converts a string of Unicode symbols (e.g. a domain name label) to a * Punycode string of ASCII-only symbols. * @memberOf punycode * @param {String} input The string of Unicode symbols. * @returns {String} The resulting Punycode string of ASCII-only symbols. */ encode = input => { const output = []; // Convert the input in UCS-2 to an array of Unicode code points. input = ucs2decode(input); // Cache the length. const inputLength = input.length; // Initialize the state. let n = initialN; let delta = 0; let bias = initialBias; // Handle the basic code points. for (const currentValue of input) { if (currentValue < 0x80) { output.push(stringFromCharCode(currentValue)); } } const basicLength = output.length; let handledCPCount = basicLength; // `handledCPCount` is the number of code points that have been handled; // `basicLength` is the number of basic code points. // Finish the basic string with a delimiter unless it's empty. if (basicLength) { output.push(delimiter); } // Main encoding loop: while (handledCPCount < inputLength) { // All non-basic code points < n have been handled already. Find the next // larger one: let m = maxInt; for (const currentValue of input) { if (currentValue >= n && currentValue < m) { m = currentValue; } } // Increase `delta` enough to advance the decoder's <n,i> state to <m,0>, // but guard against overflow. const handledCPCountPlusOne = handledCPCount + 1; if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { error('overflow'); } delta += (m - n) * handledCPCountPlusOne; n = m; for (const currentValue of input) { if (currentValue < n && ++delta > maxInt) { error('overflow'); } if (currentValue === n) { // Represent delta as a generalized variable-length integer. let q = delta; for (let k = base; /* no condition */; k += base) { const t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); if (q < t) { break; } const qMinusT = q - t; const baseMinusT = base - t; output.push( stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)) ); q = floor(qMinusT / baseMinusT); } output.push(stringFromCharCode(digitToBasic(q, 0))); bias = adapt(delta, handledCPCountPlusOne, handledCPCount === basicLength); delta = 0; ++handledCPCount; } } ++delta; ++n; } return output.join(''); }; /*--------------------------------------------------------------------------*/ /** Define the public API */ window.IDN = { /** * A string representing the current Punycode.js version number. * @memberOf punycode * @type String */ version: '2.3.1', /** * Converts a Punycode string representing a domain name or an email address * to Unicode. Only the Punycoded parts of the input will be converted, i.e. * it doesn't matter if you call it on a string that has already been * converted to Unicode. * @memberOf punycode * @param {String} input The Punycoded domain name or email address to * convert to Unicode. * @returns {String} The Unicode representation of the given Punycode * string. */ toUnicode: input => mapDomain( input, string => regexPunycode.test(string) ? decode(string.slice(4).toLowerCase()) : string ), /** * Converts a Unicode string representing a domain name or an email address to * Punycode. Only the non-ASCII parts of the domain name will be converted, * i.e. it doesn't matter if you call it with a domain that's already in * ASCII. * @memberOf punycode * @param {String} input The domain name or email address to convert, as a * Unicode string. * @returns {String} The Punycode representation of the given domain name or * email address. */ toASCII: input => mapDomain( input, string => (regexNonASCII.test(string) ? 'xn--' + encode(string) : string).toLowerCase() ) }; })(); /** * https://github.com/mixmark-io/turndown * v7.2.0 modified by SnappyMail to be ES2020 */ const TurndownService = (() => { const repeat = (character, count) => Array(count + 1).join(character), trimLeadingNewlines = string => string.replace(/^\n*/, ''), trimTrailingNewlines = string => { // avoid match-at-end regexp bottleneck, see #370 var indexEnd = string.length; while (indexEnd > 0 && string[indexEnd - 1] === '\n') indexEnd--; return string.substring(0, indexEnd) }, blockElements = [ 'ADDRESS', 'ARTICLE', 'ASIDE', 'AUDIO', 'BLOCKQUOTE', 'BODY', 'CANVAS', 'CENTER', 'DD', 'DIR', 'DIV', 'DL', 'DT', 'FIELDSET', 'FIGCAPTION', 'FIGURE', 'FOOTER', 'FORM', 'FRAMESET', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HEADER', 'HGROUP', 'HR', 'HTML', 'ISINDEX', 'LI', 'MAIN', 'MENU', 'NAV', 'NOFRAMES', 'NOSCRIPT', 'OL', 'OUTPUT', 'P', 'PRE', 'SECTION', 'TABLE', 'TBODY', 'TD', 'TFOOT', 'TH', 'THEAD', 'TR', 'UL' ], isBlock = node => is(node, blockElements), voidElements = [ 'AREA', 'BASE', 'BR', 'COL', 'COMMAND', 'EMBED', 'HR', 'IMG', 'INPUT', 'KEYGEN', 'LINK', 'META', 'PARAM', 'SOURCE', 'TRACK', 'WBR' ], isVoid = node => is(node, voidElements), hasVoid = node => has(node, voidElements), meaningfulWhenBlankElements = [ 'A', 'TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TH', 'TD', 'IFRAME', 'SCRIPT', 'AUDIO', 'VIDEO' ], isMeaningfulWhenBlank = node => is(node, meaningfulWhenBlankElements), hasMeaningfulWhenBlank = node => has(node, meaningfulWhenBlankElements), is = (node, tagNames) => tagNames.indexOf(node.nodeName) >= 0, has = (node, tagNames) => ( node.getElementsByTagName && tagNames.some(tagName => node.getElementsByTagName(tagName).length) ), rules = { paragraph: { filter: 'p', replacement: content => '\n\n' + content + '\n\n' }, lineBreak: { filter: 'br', replacement: (content, node, options) => options.br + '\n' }, heading: { filter: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'], replacement: (content, node, options) => { var hLevel = Number(node.nodeName.charAt(1)); if (options.headingStyle === 'setext' && hLevel < 3) { var underline = repeat((hLevel === 1 ? '=' : '-'), content.length); return '\n\n' + content + '\n' + underline + '\n\n' } return '\n\n' + repeat('#', hLevel) + ' ' + content + '\n\n' } }, blockquote: { filter: 'blockquote', replacement: (content) => { content = content.replace(/^\n+|\n+$/g, ''); content = content.replace(/^/gm, '> '); return '\n\n' + content + '\n\n' } }, list: { filter: ['ul', 'ol'], replacement: (content, node) => { var parent = node.parentNode; if (parent.nodeName === 'LI' && parent.lastElementChild === node) { return '\n' + content } return '\n\n' + content + '\n\n' } }, listItem: { filter: 'li', replacement: (content, node, options) => { content = content .replace(/^\n+/, '') // remove leading newlines .replace(/\n+$/, '\n') // replace trailing newlines with just a single one .replace(/\n/gm, '\n '); // indent var prefix = options.bulletListMarker + ' '; var parent = node.parentNode; if (parent.nodeName === 'OL') { var start = parent.getAttribute('start'); var index = Array.prototype.indexOf.call(parent.children, node); prefix = (start ? Number(start) + index : index + 1) + '. '; } return ( prefix + content + (node.nextSibling && !/\n$/.test(content) ? '\n' : '') ) } }, indentedCodeBlock: { filter: (node, options) => ( options.codeBlockStyle === 'indented' && node.nodeName === 'PRE' && node.firstChild && node.firstChild.nodeName === 'CODE' ) , replacement: (content, node) => ( '\n\n ' + node.firstChild.textContent.replace(/\n/g, '\n ') + '\n\n' ) }, fencedCodeBlock: { filter: (node, options) => ( options.codeBlockStyle === 'fenced' && node.nodeName === 'PRE' && node.firstChild && node.firstChild.nodeName === 'CODE' ) , replacement: (content, node, options) => { var className = node.firstChild.getAttribute('class') || ''; var language = (className.match(/language-(\S+)/) || [null, ''])[1]; var code = node.firstChild.textContent; var fenceChar = options.fence.charAt(0); var fenceSize = 3; var fenceInCodeRegex = new RegExp('^' + fenceChar + '{3,}', 'gm'); var match; while ((match = fenceInCodeRegex.exec(code))) { if (match[0].length >= fenceSize) { fenceSize = match[0].length + 1; } } var fence = repeat(fenceChar, fenceSize); return ( '\n\n' + fence + language + '\n' + code.replace(/\n$/, '') + '\n' + fence + '\n\n' ) } }, horizontalRule: { filter: 'hr', replacement: (content, node, options) => '\n\n' + options.hr + '\n\n' }, inlineLink: { filter: (node, options) => ( options.linkStyle === 'inlined' && node.nodeName === 'A' && node.getAttribute('href') ) , replacement: (content, node) => { var href = node.getAttribute('href'); if (href) href = href.replace(/([()])/g, '\\$1'); var title = cleanAttribute(node.getAttribute('title')); if (title) title = ' "' + title.replace(/"/g, '\\"') + '"'; return '[' + content + '](' + href + title + ')' } }, referenceLink: { filter: (node, options) => ( options.linkStyle === 'referenced' && node.nodeName === 'A' && node.getAttribute('href') ) , replacement(content, node, options) { var href = node.getAttribute('href'); var title = cleanAttribute(node.getAttribute('title')); if (title) title = ' "' + title + '"'; var replacement; var reference; switch (options.linkReferenceStyle) { case 'collapsed': replacement = '[' + content + '][]'; reference = '[' + content + ']: ' + href + title; break case 'shortcut': replacement = '[' + content + ']'; reference = '[' + content + ']: ' + href + title; break default: var id = this.references.length + 1; replacement = '[' + content + '][' + id + ']'; reference = '[' + id + ']: ' + href + title; } this.references.push(reference); return replacement }, references: [], append() { var references = ''; if (this.references.length) { references = '\n\n' + this.references.join('\n') + '\n\n'; this.references = []; // Reset references } return references } }, emphasis: { filter: ['em', 'i'], replacement: (content, node, options) => { if (!content.trim()) return '' return options.emDelimiter + content + options.emDelimiter } }, strong: { filter: ['strong', 'b'], replacement: (content, node, options) => { if (!content.trim()) return '' return options.strongDelimiter + content + options.strongDelimiter } }, code: { filter: (node) => { var hasSiblings = node.previousSibling || node.nextSibling; var isCodeBlock = node.parentNode.nodeName === 'PRE' && !hasSiblings; return node.nodeName === 'CODE' && !isCodeBlock }, replacement: (content) => { if (!content) return '' content = content.replace(/\r?\n|\r/g, ' '); var extraSpace = /^`|^ .*?[^ ].* $|`$/.test(content) ? ' ' : ''; var delimiter = '`'; var matches = content.match(/`+/gm) || []; while (matches.indexOf(delimiter) !== -1) delimiter = delimiter + '`'; return delimiter + extraSpace + content + extraSpace + delimiter } }, image: { filter: 'img', replacement: (content, node) => { var alt = cleanAttribute(node.getAttribute('alt')); var src = node.getAttribute('src') || ''; var title = cleanAttribute(node.getAttribute('title')); var titlePart = title ? ' "' + title + '"' : ''; return src ? '![' + alt + ']' + '(' + src + titlePart + ')' : '' } }, style: { filter: 'style', replacement: () => '' } }, cleanAttribute = (attribute) => attribute ? attribute.replace(/(\n+\s*)+/g, '\n') : ''; /** * Manages a collection of rules used to convert HTML to Markdown */ class Rules { constructor(options) { this.options = options; this._keep = []; this._remove = []; this.blankRule = { replacement: options.blankReplacement }; this.keepReplacement = options.keepReplacement; this.defaultRule = { replacement: options.defaultReplacement }; this.array = []; for (var key in options.rules) this.array.push(options.rules[key]); } add(key, rule) { this.array.unshift(rule); } keep(filter) { this._keep.unshift({ filter: filter, replacement: this.keepReplacement }); } remove(filter) { this._remove.unshift({ filter: filter, replacement: () => '' }); } forNode(node) { if (node.isBlank) return this.blankRule return findRule(this.array, node, this.options) || findRule(this._keep, node, this.options) || findRule(this._remove, node, this.options) || this.defaultRule } forEach(fn) { for (var i = 0; i < this.array.length; i++) fn(this.array[i], i); } } const findRule = (rules, node, options) => { for (var i = 0; i < rules.length; i++) { var rule = rules[i]; if (filterValue(rule, node, options)) return rule } return void 0 }, filterValue = (rule, node, options) => { var filter = rule.filter; if (typeof filter === 'string') { return (filter === node.nodeName.toLowerCase()) } else if (Array.isArray(filter)) { return (filter.indexOf(node.nodeName.toLowerCase()) > -1) } else if (typeof filter === 'function') { return !!filter.call(rule, node, options) } throw new TypeError('`filter` needs to be a string, array, or function') }, /** * The collapseWhitespace function is adapted from collapse-whitespace * by Luc Thevenard. * * The MIT License (MIT) * * Copyright (c) 2014 Luc Thevenard <lucthevenard@gmail.com> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ /** * collapseWhitespace(options) removes extraneous whitespace from an the given element. * * @param {Object} options */ collapseWhitespace = (options) => { var element = options.element; var isBlock = options.isBlock; var isVoid = options.isVoid; var isPre = options.isPre || ((node) => node.nodeName === 'PRE'); if (!element.firstChild || isPre(element)) return var prevText = null; var keepLeadingWs = false; var prev = null; var node = next(prev, element, isPre); while (node !== element) { if (node.nodeType === 3 || node.nodeType === 4) { // Node.TEXT_NODE or Node.CDATA_SECTION_NODE var text = node.data.replace(/[ \r\n\t]+/g, ' '); if ((!prevText || / $/.test(prevText.data)) && !keepLeadingWs && text[0] === ' ') { text = text.substr(1); } // `text` might be empty at this point. if (!text) { node = remove(node); continue } node.data = text; prevText = node; } else if (node.nodeType === 1) { // Node.ELEMENT_NODE if (isBlock(node) || node.nodeName === 'BR') { if (prevText) { prevText.data = prevText.data.replace(/ $/, ''); } prevText = null; keepLeadingWs = false; } else if (isVoid(node) || isPre(node)) { // Avoid trimming space around non-block, non-BR void elements and inline PRE. prevText = null; keepLeadingWs = true; } else if (prevText) { // Drop protection if set previously. keepLeadingWs = false; } } else { node = remove(node); continue } var nextNode = next(prev, node, isPre); prev = node; node = nextNode; } if (prevText) { prevText.data = prevText.data.replace(/ $/, ''); if (!prevText.data) { remove(prevText); } } }, /** * remove(node) removes the given node from the DOM and returns the * next node in the sequence. * * @param {Node} node * @return {Node} node */ remove = (node) => { var next = node.nextSibling || node.parentNode; node.parentNode.removeChild(node); return next }, /** * next(prev, current, isPre) returns the next node in the sequence, given the * current and previous nodes. * * @param {Node} prev * @param {Node} current * @param {Function} isPre * @return {Node} */ next = (prev, current, isPre) => { if ((prev && prev.parentNode === current) || isPre(current)) { return current.nextSibling || current.parentNode } return current.firstChild || current.nextSibling || current.parentNode }, /* * Parsing HTML strings */ RootNode = (input, options) => { var root; if (typeof input === 'string') { var doc = htmlParser().parseFromString( // DOM parsers arrange elements in the <head> and <body>. // Wrapping in a custom element ensures elements are reliably arranged in // a single element. '<x-turndown id="turndown-root">' + input + '</x-turndown>', 'text/html' ); root = doc.getElementById('turndown-root'); } else { root = input.cloneNode(true); } collapseWhitespace({ element: root, isBlock: isBlock, isVoid: isVoid, isPre: options.preformattedCode ? isPreOrCode : null }); return root }; var _htmlParser; const htmlParser = () => { _htmlParser = _htmlParser || new DOMParser(); return _htmlParser }, isPreOrCode = (node) => node.nodeName === 'PRE' || node.nodeName === 'CODE', Node = (node, options) => { node.isBlock = isBlock(node); node.isCode = node.nodeName === 'CODE' || node.parentNode.isCode; node.isBlank = isBlank(node); node.flankingWhitespace = flankingWhitespace(node, options); return node }, isBlank = (node) => ( !isVoid(node) && !isMeaningfulWhenBlank(node) && /^\s*$/i.test(node.textContent) && !hasVoid(node) && !hasMeaningfulWhenBlank(node) ) , flankingWhitespace = (node, options) => { if (node.isBlock || (options.preformattedCode && node.isCode)) { return { leading: '', trailing: '' } } var edges = edgeWhitespace(node.textContent); // abandon leading ASCII WS if left-flanked by ASCII WS if (edges.leadingAscii && isFlankedByWhitespace('left', node, options)) { edges.leading = edges.leadingNonAscii; } // abandon trailing ASCII WS if right-flanked by ASCII WS if (edges.trailingAscii && isFlankedByWhitespace('right', node, options)) { edges.trailing = edges.trailingNonAscii; } return { leading: edges.leading, trailing: edges.trailing } }, edgeWhitespace = (string) => { var m = string.match(/^(([ \t\r\n]*)(\s*))(?:(?=\S)[\s\S]*\S)?((\s*?)([ \t\r\n]*))$/); return { leading: m[1], // whole string for whitespace-only strings leadingAscii: m[2], leadingNonAscii: m[3], trailing: m[4], // empty for whitespace-only strings trailingNonAscii: m[5], trailingAscii: m[6] } }, isFlankedByWhitespace = (side, node, options) => { var sibling; var regExp; var isFlanked; if (side === 'left') { sibling = node.previousSibling; regExp = / $/; } else { sibling = node.nextSibling; regExp = /^ /; } if (sibling) { if (sibling.nodeType === 3) { isFlanked = regExp.test(sibling.nodeValue); } else if (options.preformattedCode && sibling.nodeName === 'CODE') { isFlanked = false; } else if (sibling.nodeType === 1 && !isBlock(sibling)) { isFlanked = regExp.test(sibling.textContent); } } return isFlanked }, reduce = Array.prototype.reduce, escapes = [ [/\\/g, '\\\\'], [/\*/g, '\\*'], [/^-/g, '\\-'], [/^\+ /g, '\\+ '], [/^(=+)/g, '\\$1'], [/^(#{1,6}) /g, '\\$1 '], [/`/g, '\\`'], [/^~~~/g, '\\~~~'], [/\[/g, '\\['], [/\]/g, '\\]'], [/^>/g, '\\>'], [/_/g, '\\_'], [/^(\d+)\. /g, '$1\\. '] ]; class TurndownService { constructor(options) { this.options = Object.assign({ rules: rules, headingStyle: 'setext', hr: '* * *', bulletListMarker: '*', codeBlockStyle: 'indented', fence: '```', emDelimiter: '_', strongDelimiter: '**', linkStyle: 'inlined', linkReferenceStyle: 'full', br: ' ', preformattedCode: false, blankReplacement: (content, node) => node.isBlock ? '\n\n' : '', keepReplacement: (content, node) => node.isBlock ? '\n\n' + node.outerHTML + '\n\n' : node.outerHTML, defaultReplacement: (content, node) => node.isBlock ? '\n\n' + content + '\n\n' : content }, options); this.rules = new Rules(this.options); } /** * The entry point for converting a string or DOM node to Markdown * @public * @param {String|HTMLElement} input The string or DOM node to convert * @returns A Markdown representation of the input * @type String */ turndown(input) { if (!canConvert(input)) { throw new TypeError( input + ' is not a string, or an element/document/fragment node.' ) } if (input === '') return '' var output = this.process(RootNode(input, this.options)); return this.postProcess(output) } /** * Add one or more plugins * @public * @param {Function|Array} plugin The plugin or array of plugins to add * @returns The Turndown instance for chaining * @type Object */ use(plugin) { if (Array.isArray(plugin)) { for (var i = 0; i < plugin.length; i++) this.use(plugin[i]); } else if (typeof plugin === 'function') { plugin(this); } else { throw new TypeError('plugin must be a Function or an Array of Functions') } return this } /** * Adds a rule * @public * @param {String} key The unique key of the rule * @param {Object} rule The rule * @returns The Turndown instance for chaining * @type Object */ addRule(key, rule) { this.rules.add(key, rule); return this } /** * Keep a node (as HTML) that matches the filter * @public * @param {String|Array|Function} filter The unique key of the rule * @returns The Turndown instance for chaining * @type Object */ keep(filter) { this.rules.keep(filter); return this } /** * Remove a node that matches the filter * @public * @param {String|Array|Function} filter The unique key of the rule * @returns The Turndown instance for chaining * @type Object */ remove(filter) { this.rules.remove(filter); return this } /** * Escapes Markdown syntax * @public * @param {String} string The string to escape * @returns A string with Markdown syntax escaped * @type String */ escape(string) { return escapes.reduce((accumulator, escape) => accumulator.replace(escape[0], escape[1]), string) } /** * Reduces a DOM node down to its Markdown string equivalent * @private * @param {HTMLElement} parentNode The node to convert * @returns A Markdown representation of the node * @type String */ process(parentNode) { var self = this; return reduce.call(parentNode.childNodes, (output, node) => { node = Node(node, self.options); var replacement = ''; if (node.nodeType === 3) { replacement = node.isCode ? node.nodeValue : self.escape(node.nodeValue); } else if (node.nodeType === 1) { replacement = self.replacementForNode(node); } return join(output, replacement) }, '') } /** * Appends strings as each rule requires and trims the output * @private * @param {String} output The conversion output * @returns A trimmed version of the ouput * @type String */ postProcess(output) { var self = this; this.rules.forEach((rule) => { if (typeof rule.append === 'function') { output = join(output, rule.append(self.options)); } }); return output.replace(/^[\t\r\n]+/, '').replace(/[\t\r\n\s]+$/, '') } /** * Converts an element node to its Markdown equivalent * @private * @param {HTMLElement} node The node to convert * @returns A Markdown representation of the node * @type String */ replacementForNode(node) { var rule = this.rules.forNode(node); var content = this.process(node); var whitespace = node.flankingWhitespace; if (whitespace.leading || whitespace.trailing) content = content.trim(); return ( whitespace.leading + rule.replacement(content, node, this.options) + whitespace.trailing ) } } /** * Joins replacement to the current output with appropriate number of new lines * @private * @param {String} output The current conversion output * @param {String} replacement The string to append to the output * @returns Joined output * @type String */ const join = (output, replacement) => { var s1 = trimTrailingNewlines(output); var s2 = trimLeadingNewlines(replacement); var nls = Math.max(output.length - s1.length, replacement.length - s2.length); var separator = '\n\n'.substring(0, nls); return s1 + separator + s2 }, /** * Determines whether an input can be converted * @private * @param {String|HTMLElement} input Describe this parameter * @returns Describe what it returns * @type String|Object|Array|Boolean|Number */ canConvert = (input) => ( input != null && ( typeof input === 'string' || (input.nodeType && ( input.nodeType === 1 || input.nodeType === 9 || input.nodeType === 11 )) ) ) ; return TurndownService; })(); /* eslint max-len: 0 */ (doc => { const i18n = (str, def) => rl.i18n(str) || def, ctrlKey = shortcuts.getMetaKey() + ' + ', createElement = name => doc.createElement(name), tpl = createElement('template'), trimLines = html => html.trim().replace(/^(<div>\s*<br\s*\/?>\s*<\/div>)+/, '').trim(), htmlToPlain = html => rl.Utils.htmlToPlain(html).trim(), plainToHtml = text => rl.Utils.plainToHtml(text), forEachObjectValue = (obj, fn) => Object.values(obj).forEach(fn), SquireDefaultConfig = { /* addLinks: true // allow_smart_html_links */ sanitizeToDOMFragment: (html, isPaste/*, squire*/) => { html = (html||'') .replace(/<\/?(BODY|HTML)[^>]*>/gi,'') .replace(/<!--[^>]+-->/g,'') .replace(/<span[^>]*>\s*<\/span>/gi,'') .trim(); tpl.innerHTML = isPaste ? rl.Utils.cleanHtml(html).html : html; return tpl.content; } }; class SquireUI { constructor(container) { const clr = createElement('input'), doClr = name => input => { // https://github.com/the-djmaze/snappymail/issues/826 clr.style.left = (input.offsetLeft + input.parentNode.offsetLeft) + 'px'; clr.style.width = input.offsetWidth + 'px'; clr.value = ''; clr.onchange = () => squire.setStyle({[name]:clr.value}); // Chrome 110+ https://github.com/the-djmaze/snappymail/issues/1199 // clr.oninput = () => squire.setStyle({[name]:clr.value}); setTimeout(()=>clr.click(),1); }, actions = { mode: { plain: { // html: '〈〉', // cmd: () => this.setMode('plain' == this.mode ? 'wysiwyg' : 'plain'), select: [ [i18n('SETTINGS_GENERAL/EDITOR_HTML'),'wysiwyg'], [i18n('SETTINGS_GENERAL/EDITOR_PLAIN'),'plain'] ], cmd: s => this.setMode('plain' == s.value ? 'plain' : 'wysiwyg') } }, font: { fontFamily: { select: { 'sans-serif': { Arial: "'Nimbus Sans L', 'Liberation sans', 'Arial Unicode MS', Arial, Helvetica, Garuda, Utkal, FreeSans, sans-serif", Tahoma: "'Luxi Sans', Tahoma, Loma, Geneva, Meera, sans-serif", Trebuchet: "'DejaVu Sans Condensed', Trebuchet, 'Trebuchet MS', sans-serif", Lucida: "'Lucida Sans Unicode', 'Lucida Sans', 'DejaVu Sans', 'Bitstream Vera Sans', 'DejaVu LGC Sans', sans-serif", Verdana: "'DejaVu Sans', Verdana, Geneva, 'Bitstream Vera Sans', 'DejaVu LGC Sans', sans-serif" }, monospace: { Courier: "'Liberation Mono', 'Courier New', FreeMono, Courier, monospace", Lucida: "'DejaVu Sans Mono', 'DejaVu LGC Sans Mono', 'Bitstream Vera Sans Mono', 'Lucida Console', Monaco, monospace" }, sans: { Times: "'Nimbus Roman No9 L', 'Times New Roman', Times, FreeSerif, serif", Palatino: "'Bitstream Charter', 'Palatino Linotype', Palatino, Palladio, 'URW Palladio L', 'Book Antiqua', Times, serif", Georgia: "'URW Palladio L', Georgia, Times, serif" } }, cmd: s => squire.setStyle({ fontFamily: s.value }) }, fontSize: { select: [[i18n('GLOBAL/DEFAULT'),''],'11px','13px','16px','20px','24px','30px'], defaultValueIndex: 0, cmd: s => squire.setStyle({ fontSize: s.value }) // TODO: maybe consider using https://developer.mozilla.org/en-US/docs/Web/CSS/font-size#values // example: // select: ['','xx-small', 'x-small',' small',' medium', 'large', 'x-large', 'xx-large', 'xxx-large'], // defaultValueIndex: 0, }, // dir: { // select: [ // [i18n('EDITOR/DIR_LTR', 'LTR'),'ltr'], // [i18n('EDITOR/DIR_RTL', 'RTL'),'rtl'], // [i18n('EDITOR/DIR_AUTO', 'Auto'),'auto'], // ['',''], // ], // cmd: s => { // squire.setAttribute('dir', s.value || null); // // squire.setStyle({ 'unicode-bidi': 'plaintext' }); // } // } }, dir: { dir_ltr: { html: '⁋', cmd: () => squire.setTextDirection('ltr') }, dir_rtl: { html: '¶', cmd: () => squire.setTextDirection('rtl') } }, colors: { textColor: { html: 'A<sub>▾</sub>', cmd: doClr('color') }, backgroundColor: { html: '🎨', /* ▧ */ cmd: doClr('backgroundColor') }, }, inline: { bold: { html: 'B', cmd: () => this.doAction('bold'), key: 'B', matches: 'B,STRONG' }, italic: { html: 'I', cmd: () => this.doAction('italic'), key: 'I', matches: 'I' }, underline: { html: '<u>U</u>', cmd: () => this.doAction('underline'), key: 'U', matches: 'U' }, strike: { html: '<s>S</s>', cmd: () => this.doAction('strikethrough'), key: 'Shift + 7', matches: 'S' }, sub: { html: 'Xₙ', cmd: () => this.doAction('subscript'), key: 'Shift + 5', matches: 'SUB' }, sup: { html: 'Xⁿ', cmd: () => this.doAction('superscript'), key: 'Shift + 6', matches: 'SUP' } }, block: { ol: { html: '#', cmd: () => this.doList('OL'), key: 'Shift + 8', matches: 'OL' }, ul: { html: '⋮', cmd: () => this.doList('UL'), key: 'Shift + 9', matches: 'UL' }, quote: { html: '"', cmd: () => { let parent = squire.getSelectionClosest('UL,OL,BLOCKQUOTE')?.nodeName; ('BLOCKQUOTE' == parent) ? squire.decreaseQuoteLevel() : squire.increaseQuoteLevel(); }, matches: 'BLOCKQUOTE' }, indentDecrease: { html: '⇤', cmd: () => squire.changeIndentationLevel('decrease'), key: ']' }, indentIncrease: { html: '⇥', cmd: () => squire.changeIndentationLevel('increase'), key: '[' } }, targets: { link: { html: '🔗', cmd: () => { let node = squire.getSelectionClosest('A'), url = prompt("Link", node?.href || "https://"); if (url != null) { url.length ? squire.makeLink(url) : (node && squire.removeLink()); } }, matches: 'A' }, imageUrl: { html: '🖼️', cmd: () => { let node = squire.getSelectionClosest('IMG'), src = prompt("Image", node?.src || "https://"); src?.length ? squire.insertImage(src) : node?.remove(); }, matches: 'IMG' }, imageUpload: { html: '📂️', cmd: () => browseImage.click(), matches: 'IMG' } }, /* table: { // TODO }, */ changes: { undo: { html: '↶', cmd: () => squire.undo(), key: 'Z' }, redo: { html: '↷', cmd: () => squire.redo(), key: 'Y' }, source: { html: '👁', cmd: btn => { this.setMode('source' == this.mode ? 'wysiwyg' : 'source'); btn.classList.toggle('active', 'source' == this.mode); } } }, clear: { removeStyle: { html: '⎚', cmd: () => squire.setStyle() } } }, plain = createElement('textarea'), wysiwyg = createElement('div'), toolbar = createElement('div'), browseImage = createElement('input'), squire = new Squire(wysiwyg, SquireDefaultConfig); clr.type = 'color'; toolbar.append(clr); // Chrome https://github.com/the-djmaze/snappymail/issues/1199 let clrid = 'squire-colors', colorlist = doc.getElementById(clrid), add = hex => colorlist.append(new Option(hex)); if (!colorlist) { colorlist = createElement('datalist'); colorlist.id = clrid; // Color blind safe Tableau 10 by Maureen Stone add('#4E79A7'); add('#F28E2B'); add('#E15759'); add('#76B7B2'); add('#59A14F'); add('#EDC948'); add('#B07AA1'); add('#FF9DA7'); add('#9C755F'); add('#BAB0AC'); doc.body.append(colorlist); } clr.setAttribute('list', clrid); browseImage.type = 'file'; browseImage.accept = 'image/*'; browseImage.style.display = 'none'; browseImage.onchange = () => { if (browseImage.files.length) { let reader = new FileReader(); reader.readAsDataURL(browseImage.files[0]); reader.onloadend = () => reader.result && squire.insertImage(reader.result); } } plain.className = 'squire-plain'; wysiwyg.className = 'squire-wysiwyg'; wysiwyg.dir = 'auto'; this.mode = ''; // 'plain' | 'wysiwyg' this.container = container; this.squire = squire; this.plain = plain; this.wysiwyg = wysiwyg; dispatchEvent(new CustomEvent('squire-toolbar', {detail:{squire:this,actions:actions}})); toolbar.className = 'squire-toolbar btn-toolbar'; let group, action/*, touchTap*/; for (group in actions) { let toolgroup = createElement('div'); toolgroup.className = 'btn-group'; toolgroup.id = 'squire-toolgroup-'+group; for (action in actions[group]) { let cfg = actions[group][action], input, ev = 'click'; if (cfg.input) { input = createElement('input'); input.type = cfg.input; ev = 'change'; } else if (cfg.select) { input = createElement('select'); input.className = 'btn'; if (Array.isArray(cfg.select)) { cfg.select.forEach(value => { value = Array.isArray(value) ? value : [value, value]; var option = new Option(value[0], value[1]); option.style[action] = value[1]; input.append(option); }); } else { input.add(new Option(i18n('GLOBAL/DEFAULT'), '')); Object.entries(cfg.select).forEach(([label, options]) => { let group = createElement('optgroup'); group.label = label; Object.entries(options).forEach(([text, value]) => { var option = new Option(text, value); option.style[action] = value; group.append(option); }); input.add(group); }); } ev = 'input'; } else { input = createElement('button'); input.type = 'button'; input.className = 'btn'; input.innerHTML = cfg.html; input.action_cmd = cfg.cmd; /* input.addEventListener('pointerdown', () => touchTap = input, {passive:true}); input.addEventListener('pointermove', () => touchTap = null, {passive:true}); input.addEventListener('pointercancel', () => touchTap = null); input.addEventListener('pointerup', e => { if (touchTap === input) { e.preventDefault(); cfg.cmd(input); } touchTap = null; }); */ } input.addEventListener(ev, () => cfg.cmd(input)); cfg.hint = i18n('EDITOR/' + action.toUpperCase()); if (cfg.hint) { input.title = cfg.key ? cfg.hint + ' (' + ctrlKey + cfg.key + ')' : cfg.hint; } else if (cfg.key) { input.title = ctrlKey + cfg.key; } input.dataset.action = action; input.tabIndex = -1; cfg.input = input; toolgroup.append(input); } toolgroup.children.length && toolbar.append(toolgroup); } this.modeSelect = actions.mode.plain.input; let changes = actions.changes; changes.undo.input.disabled = changes.redo.input.disabled = true; squire.addEventListener('undoStateChange', e => { changes.undo.input.disabled = !e.detail.canUndo; changes.redo.input.disabled = !e.detail.canRedo; }); squire.addEventListener('pasteImage', e => { const items = e.detail.clipboardData.items; let l = items.length; while (l--) { const item = items[l]; if (/^image\/(png|jpeg|webp)/.test(item.type)) { let reader = new FileReader(); reader.onload = event => { let img = createElement("img"), canvas = createElement("canvas"), ctx = canvas.getContext('2d'); img.onload = ()=>{ ctx.drawImage(img, 0, 0); let width = img.width, height = img.height; if (width > height) { // Landscape if (width > 1024) { height = height * 1024 / width; width = 1024; } } else if (height > 1024) { // Portrait width = width * 1024 / height; height = 1024; } canvas.width = width; canvas.height = height; ctx.drawImage(img, 0, 0, width, height); squire.insertHTML('<img alt="" style="width:100%;max-width:'+width+'px" src="'+canvas.toDataURL()+'">', true); }; img.src = event.target.result; } reader.readAsDataURL(item.getAsFile()); break; } } }); actions.font.fontSize.input.selectedIndex = actions.font.fontSize.defaultValueIndex; // squire.addEventListener('focus', () => shortcuts.off()); // squire.addEventListener('blur', () => shortcuts.on()); container.append(toolbar, wysiwyg, plain); /** * @param {string} fontName * @return {string} */ const normalizeFontName = (fontName) => fontName.trim().replace(/(^["']*|["']*$)/g, '').trim().toLowerCase(); /** @type {string[]} - lower cased array of available font families*/ const fontFamiliesLowerCase = Object.values(actions.font.fontFamily.input.options).map(option => option.value.toLowerCase()); /** * A theme might have CSS like div.squire-wysiwyg[contenteditable="true"] { * font-family: 'Times New Roman', Times, serif; } * so let's find the best match squire.getRoot()'s font * it will also help to properly handle generic font names like 'sans-serif' * @type {number} */ let defaultFontFamilyIndex = 0; const squireRootFonts = getComputedStyle(squire.getRoot()).fontFamily.split(',').map(normalizeFontName); fontFamiliesLowerCase.some((family, index) => { const matchFound = family.split(',').some(availableFontName => { const normalizedFontName = normalizeFontName(availableFontName); return squireRootFonts.some(squireFontName => squireFontName === normalizedFontName); }); if (matchFound) { defaultFontFamilyIndex = index; } return matchFound; }); /** * Instead of comparing whole 'font-family' strings, * we are going to look for individual font names, because we might be * editing a Draft started in another email client for example * * @type {Object.<string,number>} */ const fontNamesMap = {}; /** * @param {string} fontFamily * @param {number} index */ const processFontFamilyString = (fontFamily, index) => { fontFamily.split(',').forEach(fontName => { const key = normalizeFontName(fontName); if (fontNamesMap[key] === undefined) { fontNamesMap[key] = index; } }); }; // first deal with the default font family processFontFamilyString(fontFamiliesLowerCase[defaultFontFamilyIndex], defaultFontFamilyIndex); // and now with the rest of the font families fontFamiliesLowerCase.forEach((fontFamily, index) => { if (index !== defaultFontFamilyIndex) { processFontFamilyString(fontFamily, index); } }); // ----- squire.addEventListener('pathChange', () => { const squireRoot = squire.getRoot(); let range = squire.getSelection(), collapsed = range.collapsed, elm = collapsed ? range.endContainer : range?.commonAncestorContainer; if (elm && !(elm instanceof Element)) { elm = elm.parentElement; } forEachObjectValue(actions, entries => { forEachObjectValue(entries, cfg => { // Check if selection has a matching parent or contains a matching element cfg.matches && cfg.input.classList.toggle('active', !!(elm && ( (!collapsed && [...elm.querySelectorAll(cfg.matches)].some(node => range.intersectsNode(node))) || elm.closestWithin(cfg.matches, squireRoot) ))); }); }); if (elm) { // try to find font-family and/or font-size and set "select" elements' values let sizeSelectedIndex = actions.font.fontSize.defaultValueIndex; let familySelectedIndex = defaultFontFamilyIndex; let familyFound = false; let sizeFound = false; do { if (!familyFound && elm.style.fontFamily) { familyFound = true; familySelectedIndex = -1; // show empty select if we don't know the font const fontNames = elm.style.fontFamily.split(','); for (let i = 0; i < fontNames.length; i++) { const index = fontNamesMap[normalizeFontName(fontNames[i])]; if (index !== undefined) { familySelectedIndex = index; break; } } } if (!sizeFound && elm.style.fontSize) { sizeFound = true; // -1 is ok because it will just show a black <select> sizeSelectedIndex = actions.font.fontSize.select.indexOf(elm.style.fontSize); } elm = elm.parentElement; } while ((!familyFound || !sizeFound) && elm && elm !== squireRoot); actions.font.fontFamily.input.selectedIndex = familySelectedIndex; actions.font.fontSize.input.selectedIndex = sizeSelectedIndex; } }); /* squire.addEventListener('cursor', e => { console.dir({cursor:e.detail.range}); }); squire.addEventListener('select', e => { console.dir({select:e.detail.range}); }); */ } doAction(name) { this.squire[name](); this.squire.focus(); } doList(type) { let parent = this.squire.getSelectionClosest('UL,OL')?.nodeName, fn = {UL:'makeUnorderedList',OL:'makeOrderedList'}; (parent == type) ? this.squire.removeList() : this.squire[fn[type]](); } /* testPresenceinSelection(format, validation) { return validation.test(this.squire.getPath()) || this.squire.hasFormat(format); } */ setMode(mode) { if (this.mode != mode) { let cl = this.container.classList, source = 'source' == this.mode; cl.remove('squire-mode-'+this.mode); if ('plain' == mode) { this.plain.value = htmlToPlain(source ? this.plain.value : this.squire.getHTML(), true); } else if ('source' == mode) { this.plain.value = this.squire.getHTML(); } else { this.setData(source ? this.plain.value : plainToHtml(this.plain.value, true)); mode = 'wysiwyg'; } this.mode = mode; // 'wysiwyg' or 'plain' cl.add('squire-mode-'+mode); this.onModeChange?.(); setTimeout(()=>this.focus(),1); } this.modeSelect.selectedIndex = 'plain' == this.mode ? 1 : 0; } on(type, fn) { if ('mode' == type) { this.onModeChange = fn; } else { this.squire.addEventListener(type, fn); this.plain.addEventListener(type, fn); } } execCommand(cmd, cfg) { if ('insertSignature' == cmd) { cfg = Object.assign({ clearCache: false, isHtml: false, insertBefore: false, signature: '' }, cfg); if (cfg.clearCache) { this._prev_txt_sig = null; } else try { const signature = cfg.isHtml ? htmlToPlain(cfg.signature) : cfg.signature; if ('plain' === this.mode) { let text = this.plain.value, prevSignature = this._prev_txt_sig; if (prevSignature) { text = text.replace(prevSignature, '').trim(); } this.plain.value = cfg.insertBefore ? '\n\n' + signature + '\n\n' + text : text + '\n\n' + signature; } else { const squire = this.squire, root = squire.getRoot(), br = createElement('br'), div = createElement('div'); div.className = 'rl-signature'; div.innerHTML = cfg.isHtml ? cfg.signature : plainToHtml(cfg.signature); root.querySelectorAll('div.rl-signature').forEach(node => node.remove()); cfg.insertBefore ? root.prepend(div) : root.append(div); // Move cursor above signature div.before(br); div.before(br.cloneNode()); // squire._docWasChanged(); } this._prev_txt_sig = signature; } catch (e) { console.error(e); } } } getData() { return 'source' == this.mode ? this.plain.value : trimLines(this.squire.getHTML()); } setData(html) { // this.plain.value = html; const squire = this.squire; squire.setHTML(trimLines(html)); const node = squire.getRoot(), range = squire.getSelection(); range.setStart(node, 0); range.setEnd(node, 0); squire.setSelection( range ); } getPlainData() { return this.plain.value; } setPlainData(text) { this.plain.value = text; } blur() { this.squire.blur(); } focus() { if ('plain' == this.mode) { this.plain.focus(); this.plain.setSelectionRange(0, 0); } else { this.squire.focus(); } } } this.SquireUI = SquireUI; })(document);
| ver. 1.4 |
Github
|
.
| PHP 8.2.28 | Generation time: 0.02 |
proxy
|
phpinfo
|
Settings