"use strict";
// ==UserScript==
// @name KameSame Open Framework - Settings module
// @namespace timberpile
// @description Settings module for KameSame Open Framework
// @version 0.3
// @copyright 2022+, Robin Findley, Timberpile
// @license MIT http://opensource.org/licenses/MIT
// ==/UserScript==
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
((async (global) => {
var _KSOFSettings_instances, _KSOFSettings_openDialog, _KSOFSettings_settingChanged;
const ksof = global.ksof;
const backgroundFuncs = () => {
return {
open: () => {
const anchor = installAnchor();
let bkgd = anchor.find('> #ksofs_bkgd');
if (bkgd.length === 0) {
bkgd = $('<div id="ksofs_bkgd" refcnt="0"></div>');
anchor.prepend(bkgd);
}
const refcnt = Number(bkgd.attr('refcnt'));
bkgd.attr('refcnt', refcnt + 1);
},
close: () => {
const bkgd = $('#ksof_ds > #ksofs_bkgd');
if (bkgd.length === 0)
return;
const refcnt = Number(bkgd.attr('refcnt'));
if (refcnt <= 0)
return;
bkgd.attr('refcnt', refcnt - 1);
},
};
};
//########################################################################
//------------------------------
// Constructor
//------------------------------
class KSOFSettings {
constructor(config) {
_KSOFSettings_instances.add(this);
_KSOFSettings_openDialog.set(this, void 0);
this.cfg = config;
this.configList = {};
__classPrivateFieldSet(this, _KSOFSettings_openDialog, $(), "f");
this.background = backgroundFuncs();
}
//------------------------------
// Open the settings dialog.
//------------------------------
static save(context) {
const scriptId = ((typeof context === 'string') ? context : context.cfg.scriptId);
const settings = ksof.settings[scriptId];
if (!settings)
return Promise.resolve('');
return ksof.fileCache.save(`ksof.settings.${scriptId}`, settings);
}
save() {
return KSOFSettings.save(this);
}
//------------------------------
// Open the settings dialog.
//------------------------------
static async load(context, defaults) {
const scriptId = ((typeof context === 'string') ? context : context.cfg.scriptId);
const finish = (settings) => {
if (defaults)
ksof.settings[scriptId] = deepMerge(defaults, settings);
else
ksof.settings[scriptId] = settings;
return ksof.settings[scriptId];
};
try {
const settings = await ksof.fileCache.load(`ksof.settings.${scriptId}`);
return finish(settings);
}
catch (error) {
return finish.call(null, {});
}
}
load(defaults) {
return KSOFSettings.load(this, defaults);
}
//------------------------------
// Save button handler.
//------------------------------
saveBtn() {
const scriptId = this.cfg.scriptId;
const settings = ksof.settings[scriptId];
if (settings) {
const activeTabs = __classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").find('.ui-tabs-active').toArray()
.map((tab) => { return `#${tab.attributes.getNamedItem('id')?.value || ''}`; });
if (activeTabs.length > 0)
settings.ksofActiveTabs = activeTabs;
}
if (this.cfg.autosave === undefined || this.cfg.autosave === true) {
this.save();
}
if (this.cfg.onSave) {
this.cfg.onSave(ksof.settings[this.cfg.scriptId]);
}
ksof.trigger('ksof.settings.save');
this.keepSettings = true;
__classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").dialog('close');
}
//------------------------------
// Cancel button handler.
//------------------------------
cancel() {
__classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").dialog('close');
if (typeof this.cfg.onCancel === 'function')
this.cfg.onCancel(ksof.settings[this.cfg.scriptId]);
}
//------------------------------
// Open the settings dialog.
//------------------------------
open() {
if (!ready)
return;
if (__classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").length > 0)
return;
installAnchor();
if (this.cfg.background !== false)
this.background.open();
__classPrivateFieldSet(this, _KSOFSettings_openDialog, $(`<div id="ksofs_${this.cfg.scriptId}" class="ksof_settings" style="display:none"></div>`), "f");
__classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").html(configToHTML(this));
const resize = (event, ui) => {
const isNarrow = __classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").hasClass('narrow');
ui;
if (isNarrow && ui.size.width >= 510) {
__classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").removeClass('narrow');
}
else if (!isNarrow && ui.size.width < 490) {
__classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").addClass('narrow');
}
};
const tabActivated = () => {
const wrapper = $(__classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").dialog('widget'));
if ((wrapper.outerHeight() || 0) + wrapper.position().top > document.body.clientHeight) {
__classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").dialog('option', 'maxHeight', document.body.clientHeight);
}
};
let width = 500;
if (window.innerWidth < 510) {
width = 280;
__classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").addClass('narrow');
}
__classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").dialog({
title: this.cfg.title,
buttons: [
{
text: 'Save',
click: this.saveBtn.bind(this),
},
{
text: 'Cancel',
click: this.cancel.bind(this),
},
],
width,
maxHeight: document.body.clientHeight,
modal: false,
autoOpen: false,
appendTo: '#ksof_ds',
resize: resize.bind(this),
close: () => {
this.close(false);
},
});
$(__classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").dialog('widget')).css('position', 'fixed');
__classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").parent().addClass('ksof_settings_dialog');
$('.ksof_stabs').tabs({ activate: tabActivated.bind(null) });
const settings = ksof.settings[this.cfg.scriptId];
if (settings && settings.ksofActiveTabs instanceof Array) {
const activeTabs = settings.ksofActiveTabs;
for (let tabIndex = 0; tabIndex < activeTabs.length; tabIndex++) {
const tab = $(activeTabs[tabIndex]);
tab.closest('.ui-tabs').tabs({ active: tab.index() });
}
}
const toggleMulti = (e) => {
if (e.button != 0)
return true;
const multi = $(e.currentTarget);
const scroll = e.currentTarget.scrollTop;
e.target.selected = !e.target.selected;
setTimeout(() => {
e.currentTarget.scrollTop = scroll;
multi.focus(); // TODO what should this do? it's deprecated
}, 0);
return __classPrivateFieldGet(this, _KSOFSettings_instances, "m", _KSOFSettings_settingChanged).call(this, e);
};
const settingButtonClicked = (e) => {
const name = e.target.attributes.name.value;
const _item = this.configList[name];
if (_item.type == 'button') {
const item = _item;
item.onClick.call(e, name, item, __classPrivateFieldGet(this, _KSOFSettings_instances, "m", _KSOFSettings_settingChanged).bind(this, e));
}
};
__classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").dialog('open');
__classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").find('.setting[multiple]').on('mousedown', toggleMulti.bind(this));
__classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").find('.setting').on('change', __classPrivateFieldGet(this, _KSOFSettings_instances, "m", _KSOFSettings_settingChanged).bind(this));
__classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").find('form').on('submit', () => { return false; });
__classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").find('button.setting').on('click', settingButtonClicked.bind(this));
if (typeof this.cfg.preOpen === 'function')
this.cfg.preOpen(__classPrivateFieldGet(this, _KSOFSettings_openDialog, "f"));
this.reversions = deepMerge({}, ksof.settings[this.cfg.scriptId]);
this.refresh();
}
//------------------------------
// Close and destroy the dialog.
//------------------------------
close(keepSettings) {
if (!this.keepSettings && keepSettings !== true) {
// Revert settings
ksof.settings[this.cfg.scriptId] = deepMerge({}, this.reversions || {});
delete this.reversions;
}
delete this.keepSettings;
__classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").dialog('destroy');
__classPrivateFieldSet(this, _KSOFSettings_openDialog, $(), "f");
if (this.cfg.background !== false)
this.background.close();
if (typeof this.cfg.onClose === 'function')
this.cfg.onClose(ksof.settings[this.cfg.scriptId]);
}
//------------------------------
// Update the dialog to reflect changed settings.
//------------------------------
refresh() {
const scriptId = this.cfg.scriptId;
const settings = ksof.settings[scriptId];
for (const name in this.configList) {
const elem = __classPrivateFieldGet(this, _KSOFSettings_openDialog, "f").find(`#${scriptId}_${name}`);
const _config = this.configList[name];
const value = getValue(this, settings, name);
if (_config.type == 'dropdown') {
elem.find(`option[name="${value}"]`).prop('selected', true);
}
else if (_config.type == 'list') {
const config = _config;
if (config.multi === true) {
elem.find('option').each((i, e) => {
const optionName = e.getAttribute('name') || `#${e.index}`;
e.selected = value[optionName];
});
}
else {
elem.find(`option[name="${value}"]`).prop('selected', true);
}
}
else if (_config.type == 'checkbox') {
elem.prop('checked', value);
}
else {
elem.val(value);
}
}
if (typeof this.cfg.onRefresh === 'function')
this.cfg.onRefresh(ksof.settings[this.cfg.scriptId]);
}
}
_KSOFSettings_openDialog = new WeakMap(), _KSOFSettings_instances = new WeakSet(), _KSOFSettings_settingChanged = function _KSOFSettings_settingChanged(event) {
const elem = $(event.currentTarget);
const name = elem.attr('name');
if (!name)
return false;
const _item = this.configList[name];
// Extract the value
let value;
if (_item.type == 'dropdown') {
value = elem.find(':checked').attr('name');
}
else if (_item.type == 'list') {
const item = _item;
if (item.multi === true) {
value = {};
elem.find('option').each((i, e) => {
const optionName = e.getAttribute('name') || `#${e.index}`;
value[optionName] = e.selected;
});
}
else {
value = elem.find(':checked').attr('name');
}
}
else if (_item.type == 'input') {
const item = _item;
if (item.subtype === 'number') {
value = Number(elem.val());
}
}
else if (_item.type == 'checkbox') {
value = elem.is(':checked');
}
else if (_item.type == 'number') {
value = Number(elem.val());
}
else {
value = elem.val();
}
// Validation
let valid = { valid: true, msg: '' };
{
const item = _item;
if (item.validate) {
const _valid = item.validate.call(event.target, value, item);
if (typeof _valid === 'boolean')
valid = { valid: _valid, msg: '' };
else if (typeof _valid === 'string')
valid = { valid: false, msg: _valid };
}
}
if (_item.type == 'number') {
const item = _item;
if (item.min && Number(value) < item.min) {
valid.valid = false;
if (valid.msg.length === 0) {
if (typeof item.max === 'number')
valid.msg = `Must be between ${item.min} and ${item.max}`;
else
valid.msg = `Must be ${item.min} or higher`;
}
}
else if (item.max && Number(value) > item.max) {
valid.valid = false;
if (valid.msg.length === 0) {
if (typeof item.min === 'number')
valid.msg = `Must be between ${item.min} and ${item.max}`;
else
valid.msg = `Must be ${item.max} or lower`;
}
}
}
else if (_item.type == 'text') {
const item = _item;
if (item.match !== undefined && value.match(item.match) === null) {
valid.valid = false;
if (valid.msg.length === 0)
// valid.msg = item.error_msg || 'Invalid value' // TODO no item has a error_msg?
valid.msg = 'Invalid value';
}
}
// Style for valid/invalid
const parent = elem.closest('.right');
parent.find('.note').remove();
if (typeof valid.msg === 'string' && valid.msg.length > 0)
parent.append(`<div class="note${valid.valid ? '' : ' error'}">${valid.msg}</div>`);
if (!valid.valid) {
elem.addClass('invalid');
}
else {
elem.removeClass('invalid');
}
const scriptId = this.cfg.scriptId;
const settings = ksof.settings[scriptId];
if (valid.valid) {
const item = _item;
// if (item.no_save !== true) set_value(this, settings, name, value) // TODO what is no_save supposed to do?
setValue(this, settings, name, value);
if (item.onChange)
item.onChange.call(event.target, name, value, item);
if (this.cfg.onChange)
this.cfg.onChange.call(event.target, name, value, item);
if (item.refreshOnChange === true)
this.refresh();
}
return false;
};
const createSettings = () => {
const settingsObj = (config) => {
return new KSOFSettings(config);
};
settingsObj.save = (context) => { return KSOFSettings.save(context); };
settingsObj.load = (context, defaults) => { return KSOFSettings.load(context, defaults); };
settingsObj.background = backgroundFuncs();
return settingsObj;
};
ksof.Settings = createSettings();
ksof.settings = {};
//########################################################################
let ready = false;
//========================================================================
const deepMerge = (...objects) => {
const merged = {};
const recursiveMerge = (dest, src) => {
for (const prop in src) {
if (typeof src[prop] === 'object' && src[prop] !== null) {
const srcProp = src[prop];
if (Array.isArray(srcProp)) {
dest[prop] = srcProp.slice();
}
else {
dest[prop] = dest[prop] || {};
recursiveMerge(dest[prop], srcProp);
}
}
else {
dest[prop] = src[prop];
}
}
return dest;
};
for (const obj in objects) {
recursiveMerge(merged, objects[obj]);
}
return merged;
};
//------------------------------
// Convert a config object to html dialog.
//------------------------------
/* eslint-disable no-case-declarations */
const configToHTML = (context) => {
context.configList = {};
if (!ksof.settings) {
return '';
}
const assemblePages = (id, tabs, pages) => { return `<div id="${id}" class="ksof_stabs"><ul>${tabs.join('')}</ul>${pages.join('')}</div>`; };
const wrapRow = (html, full, hoverTip) => { return `<div class="row${full ? ' full' : ''}"${toTitle(hoverTip)}>${html}</div>`; };
const wrapLeft = (html) => { return `<div class="left">${html}</div>`; };
const wrapRight = (html) => { return `<div class="right">${html}</div>`; };
const escapeText = (text) => {
return text.replace(/[<>]/g, (ch) => {
if (ch == '<')
return '<';
if (ch == '>')
return '>';
return '';
});
};
const escapeAttr = (text) => { return text.replace(/"/g, '"'); };
const toTitle = (tip) => { if (!tip)
return ''; return ` title="${tip.replace(/"/g, '"')}"`; };
const parseItem = (name, _item, passback) => {
if (typeof _item.type !== 'string')
return '';
const id = `${context.cfg.scriptId}_${name}`;
let cname, html = '', childPassback, nonPage = '';
const makeLabel = (item) => {
if (typeof item.label !== 'string')
return '';
return wrapLeft(`<label for="${id}">${item.label}</label>`);
};
const _type = _item.type;
if (_type == 'tabset') {
const item = _item;
childPassback = {};
for (cname in item.content) {
nonPage += parseItem(cname, item.content[cname], childPassback);
}
if (childPassback.tabs && childPassback.pages) {
html = assemblePages(id, childPassback.tabs, childPassback.pages);
}
}
else if (_type == 'page') {
const item = _item;
if (typeof item.content !== 'object')
item.content = {};
if (!passback.tabs) {
passback.tabs = [];
}
if (!passback.pages) {
passback.pages = [];
}
passback.tabs.push(`<li id="${id}_tab"${toTitle(item.hoverTip)}><a href="#${id}">${item.label}</a></li>`);
childPassback = {};
for (cname in item.content)
nonPage += parseItem(cname, item.content[cname], childPassback);
if (childPassback.tabs && childPassback.pages)
html = assemblePages(id, childPassback.tabs, childPassback.pages);
passback.pages.push(`<div id="${id}">${html}${nonPage}</div>`);
passback.isPage = true;
html = '';
}
else if (_type == 'group') {
const item = _item;
if (typeof item.content !== 'object')
item.content = {};
childPassback = {};
for (cname in item.content)
nonPage += parseItem(cname, item.content[cname], childPassback);
if (childPassback.tabs && childPassback.pages)
html = assemblePages(id, childPassback.tabs, childPassback.pages);
html = `<fieldset id="${id}" class="ksof_group"><legend>${item.label}</legend>${html}${nonPage}</fieldset>`;
}
else if (_type == 'dropdown') {
const item = _item;
context.configList[name] = item;
let value = getValue(context, base, name);
if (value === undefined) {
if (item.default !== undefined) {
value = item.default;
}
else {
value = Object.keys(item.content)[0];
}
setValue(context, base, name, value);
}
html = `<select id="${id}" name="${name}" class="setting"${toTitle(item.hoverTip)}>`;
for (cname in item.content)
html += `<option name="${cname}">${escapeText(item.content[cname])}</option>`;
html += '</select>';
html = makeLabel(item) + wrapRight(html);
html = wrapRow(html, item.fullWidth, item.hoverTip);
}
else if (_type == 'list') {
const item = _item;
context.configList[name] = item;
let value = getValue(context, base, name);
if (value === undefined) {
if (item.default !== undefined) {
value = item.default;
}
else {
if (item.multi === true) {
value = {};
Object.keys(item.content).forEach((key) => {
value[key] = false;
});
}
else {
value = Object.keys(item.content)[0];
}
}
setValue(context, base, name, value);
}
let attribs = ` size="${item.size || Object.keys(item.content).length || 4}"`;
if (item.multi === true)
attribs += ' multiple';
html = `<select id="${id}" name="${name}" class="setting list"${attribs}${toTitle(item.hoverTip)}>`;
for (cname in item.content)
html += `<option name="${cname}">${escapeText(item.content[cname])}</option>`;
html += '</select>';
html = makeLabel(item) + wrapRight(html);
html = wrapRow(html, item.fullWidth, item.hoverTip);
}
else if (_type == 'checkbox') {
const item = _item;
context.configList[name] = item;
html = makeLabel(item);
let value = getValue(context, base, name);
if (value === undefined) {
value = (item.default || false);
setValue(context, base, name, value);
}
html += wrapRight(`<input id="${id}" class="setting" type="checkbox" name="${name}">`);
html = wrapRow(html, item.fullWidth, item.hoverTip);
}
else if (_type == 'input') {
const item = _item;
const itype = item.subtype || 'text';
context.configList[name] = item;
html += makeLabel(item);
let value = getValue(context, base, name);
if (value === undefined) {
const isNumber = (item.subtype === 'number');
value = (item.default || (isNumber ? 0 : ''));
setValue(context, base, name, value);
}
html += wrapRight(`<input id="${id}" class="setting" type="${itype}" name="${name}"${(item.placeholder ? ` placeholder="${escapeAttr(item.placeholder)}"` : '')}>`);
html = wrapRow(html, item.fullWidth, item.hoverTip);
}
else if (_type == 'number') {
const item = _item;
const itype = item.type;
context.configList[name] = item;
html += makeLabel(item);
let value = getValue(context, base, name);
if (value === undefined) {
const isNumber = (item.type === 'number');
value = (item.default || (isNumber ? 0 : ''));
setValue(context, base, name, value);
}
html += wrapRight(`<input id="${id}" class="setting" type="${itype}" name="${name}"${(item.placeholder ? ` placeholder="${escapeAttr(item.placeholder)}"` : '')}>`);
html = wrapRow(html, item.fullWidth, item.hoverTip);
}
else if (_type == 'text') {
const item = _item;
const itype = item.type;
context.configList[name] = item;
html += makeLabel(item);
let value = getValue(context, base, name);
if (value === undefined) {
value = (item.default || '');
setValue(context, base, name, value);
}
html += wrapRight(`<input id="${id}" class="setting" type="${itype}" name="${name}"${(item.placeholder ? ` placeholder="${escapeAttr(item.placeholder)}"` : '')}>`);
html = wrapRow(html, item.fullWidth, item.hoverTip);
}
else if (_type == 'color') {
const item = _item;
context.configList[name] = item;
html += makeLabel(item);
let value = getValue(context, base, name);
if (value === undefined) {
value = (item.default || '#000000');
setValue(context, base, name, value);
}
html += wrapRight(`<input id="${id}" class="setting" type="color" name="${name}">`);
html = wrapRow(html, item.fullWidth, item.hoverTip);
}
else if (_type == 'button') {
const item = _item;
context.configList[name] = item;
html += makeLabel(item);
const text = escapeText(item.text || 'Click');
html += wrapRight(`<button type="button" class="setting" name="${name}">${text}</button>`);
html = wrapRow(html, item.fullWidth, item.hoverTip);
}
else if (_type == 'divider') {
html += '<hr>';
}
else if (_type == 'section') {
const item = _item;
html += `<section>${item.label || ''}</section>`;
}
else if (_type == 'html') {
const item = _item;
html += makeLabel(item);
html += item.html;
switch (item.wrapper) {
case 'row':
html = wrapRow(html, undefined, item.hoverTip);
break;
case 'left':
html = wrapLeft(html);
break;
case 'right':
html = wrapRight(html);
break;
}
}
return html;
};
let base = ksof.settings[context.cfg.scriptId];
if (base === undefined)
ksof.settings[context.cfg.scriptId] = base = {};
let html = '';
const childPassback = {};
const id = `${context.cfg.scriptId}_dialog`;
for (const name in context.cfg.content) {
html += parseItem(name, context.cfg.content[name], childPassback);
}
if (childPassback.tabs && childPassback.pages)
html = assemblePages(id, childPassback.tabs, childPassback.pages) + html;
return `<form>${html}</form>`;
};
const getValue = (context, base, name) => {
const item = context.configList[name];
const evaluate = (item.path !== undefined);
const path = (item.path || name);
try {
if (!evaluate)
return base[path];
return eval(path.replace(/@/g, 'base.'));
}
catch (e) {
return;
}
};
const setValue = (context, base, name, value) => {
const item = context.configList[name];
const evaluate = (item.path !== undefined);
const path = (item.path || name);
try {
if (!evaluate)
return base[path] = value;
let depth = 0;
let newPath = '';
let param = '';
let c;
for (let idx = 0; idx < path.length; idx++) {
c = path[idx];
if (c === '[') {
if (depth++ === 0) {
newPath += '[';
param = '';
}
else {
param += '[';
}
}
else if (c === ']') {
if (--depth === 0) {
newPath += `${JSON.stringify(eval(param))}]`;
}
else {
param += ']';
}
}
else {
if (c === '@')
c = 'base.';
if (depth === 0)
newPath += c;
else
param += c;
}
}
eval(`${newPath}=value`);
}
catch (e) {
return;
}
};
const installAnchor = () => {
let anchor = $('#ksof_ds');
if (anchor.length === 0) {
anchor = $('<div id="ksof_ds"></div></div>');
$('body').prepend(anchor);
$('#ksof_ds').on('keydown keyup keypress', '.ksof_settings_dialog', (e) => {
// Stop keys from bubbling beyond the background overlay.
e.stopPropagation();
});
}
return anchor;
};
//------------------------------
// Load jquery UI and the appropriate CSS based on location.
//------------------------------
const cssUrl = ksof.supportFiles['jqui_ksmain.css'];
ksof.include('Jquery');
await ksof.ready('document, Jquery');
await Promise.all([
ksof.loadScript(ksof.supportFiles['jquery_ui.js'], true /* cache */),
ksof.loadCSS(cssUrl, true /* cache */),
]);
ready = true;
// Workaround... https://community.wanikani.com/t/19984/55
try {
const temp = $.fn;
delete temp.autocomplete;
}
catch (e) {
// do nothing
}
// Notify listeners that we are ready.
// Delay guarantees include() callbacks are called before ready() callbacks.
setTimeout(() => { ksof.setState('ksof.Settings', 'ready'); }, 0);
})(window));