Files
2026-03-12 11:29:26 -04:00

7007 lines
174 KiB
JavaScript

(function () {
"use strict";
/* malevic@0.19.1 - Jan 8, 2022 */
function m$1(tagOrComponent, props, ...children) {
props = props || {};
if (typeof tagOrComponent === "string") {
const tag = tagOrComponent;
return { type: tag, props, children };
}
if (typeof tagOrComponent === "function") {
const component = tagOrComponent;
return { type: component, props, children };
}
throw new Error("Unsupported spec type");
}
/* malevic@0.19.1 - Jan 8, 2022 */
function createPluginsStore() {
const plugins = [];
return {
add(plugin) {
plugins.push(plugin);
return this;
},
apply(props) {
let result;
let plugin;
const usedPlugins = new Set();
for (let i = plugins.length - 1; i >= 0; i--) {
plugin = plugins[i];
if (usedPlugins.has(plugin)) {
continue;
}
result = plugin(props);
if (result != null) {
return result;
}
usedPlugins.add(plugin);
}
return null;
},
delete(plugin) {
for (let i = plugins.length - 1; i >= 0; i--) {
if (plugins[i] === plugin) {
plugins.splice(i, 1);
break;
}
}
return this;
},
empty() {
return plugins.length === 0;
},
};
}
function iterateComponentPlugins(type, pairs, iterator) {
pairs
.filter(([key]) => type[key])
.forEach(([key, plugins]) => {
return type[key].forEach((plugin) => iterator(plugins, plugin));
});
}
function addComponentPlugins(type, pairs) {
iterateComponentPlugins(type, pairs, (plugins, plugin) =>
plugins.add(plugin)
);
}
function deleteComponentPlugins(type, pairs) {
iterateComponentPlugins(type, pairs, (plugins, plugin) =>
plugins.delete(plugin)
);
}
function createPluginsAPI(key) {
const api = {
add(type, plugin) {
if (!type[key]) {
type[key] = [];
}
type[key].push(plugin);
return api;
},
};
return api;
}
const XHTML_NS = "http://www.w3.org/1999/xhtml";
const SVG_NS = "http://www.w3.org/2000/svg";
const PLUGINS_CREATE_ELEMENT = Symbol();
const pluginsCreateElement = createPluginsStore();
function createElement(spec, parent) {
const result = pluginsCreateElement.apply({ spec, parent });
if (result) {
return result;
}
const tag = spec.type;
if (tag === "svg") {
return document.createElementNS(SVG_NS, "svg");
}
const namespace = parent.namespaceURI;
if (namespace === XHTML_NS || namespace == null) {
return document.createElement(tag);
}
return document.createElementNS(namespace, tag);
}
function classes$1(...args) {
const classes = [];
args
.filter((c) => Boolean(c))
.forEach((c) => {
if (typeof c === "string") {
classes.push(c);
} else if (typeof c === "object") {
classes.push(...Object.keys(c).filter((key) => Boolean(c[key])));
}
});
return classes.join(" ");
}
function setInlineCSSPropertyValue(element, prop, $value) {
if ($value != null && $value !== "") {
let value = String($value);
let important = "";
if (value.endsWith("!important")) {
value = value.substring(0, value.length - 10);
important = "important";
}
element.style.setProperty(prop, value, important);
} else {
element.style.removeProperty(prop);
}
}
function isObject(value) {
return value != null && typeof value === "object";
}
const eventListeners = new WeakMap();
function addEventListener$1(element, event, listener) {
let listeners;
if (eventListeners.has(element)) {
listeners = eventListeners.get(element);
} else {
listeners = new Map();
eventListeners.set(element, listeners);
}
if (listeners.get(event) !== listener) {
if (listeners.has(event)) {
element.removeEventListener(event, listeners.get(event));
}
element.addEventListener(event, listener);
listeners.set(event, listener);
}
}
function removeEventListener(element, event) {
if (!eventListeners.has(element)) {
return;
}
const listeners = eventListeners.get(element);
element.removeEventListener(event, listeners.get(event));
listeners.delete(event);
}
function setClassObject(element, classObj) {
const cls = Array.isArray(classObj)
? classes$1(...classObj)
: classes$1(classObj);
if (cls) {
element.setAttribute("class", cls);
} else {
element.removeAttribute("class");
}
}
function mergeValues(obj, old) {
const values = new Map();
const newProps = new Set(Object.keys(obj));
const oldProps = Object.keys(old);
oldProps
.filter((prop) => !newProps.has(prop))
.forEach((prop) => values.set(prop, null));
newProps.forEach((prop) => values.set(prop, obj[prop]));
return values;
}
function setStyleObject(element, styleObj, prev) {
let prevObj;
if (isObject(prev)) {
prevObj = prev;
} else {
prevObj = {};
element.removeAttribute("style");
}
const declarations = mergeValues(styleObj, prevObj);
declarations.forEach(($value, prop) =>
setInlineCSSPropertyValue(element, prop, $value)
);
}
function setEventListener(element, event, listener) {
if (typeof listener === "function") {
addEventListener$1(element, event, listener);
} else {
removeEventListener(element, event);
}
}
const specialAttrs = new Set([
"key",
"oncreate",
"onupdate",
"onrender",
"onremove",
]);
const PLUGINS_SET_ATTRIBUTE = Symbol();
const pluginsSetAttribute = createPluginsStore();
function getPropertyValue(obj, prop) {
return obj && obj.hasOwnProperty(prop) ? obj[prop] : null;
}
function syncAttrs(element, attrs, prev) {
const values = mergeValues(attrs, prev || {});
values.forEach((value, attr) => {
if (!pluginsSetAttribute.empty()) {
const result = pluginsSetAttribute.apply({
element,
attr,
value,
get prev() {
return getPropertyValue(prev, attr);
},
});
if (result != null) {
return;
}
}
if (attr === "class" && isObject(value)) {
setClassObject(element, value);
} else if (attr === "style" && isObject(value)) {
const prevValue = getPropertyValue(prev, attr);
setStyleObject(element, value, prevValue);
} else if (attr.startsWith("on")) {
const event = attr.substring(2);
setEventListener(element, event, value);
} else if (specialAttrs.has(attr));
else if (value == null || value === false) {
element.removeAttribute(attr);
} else {
element.setAttribute(attr, value === true ? "" : String(value));
}
});
}
class LinkedList {
constructor(...items) {
this.nexts = new WeakMap();
this.prevs = new WeakMap();
this.first = null;
this.last = null;
items.forEach((item) => this.push(item));
}
empty() {
return this.first == null;
}
push(item) {
if (this.empty()) {
this.first = item;
this.last = item;
} else {
this.nexts.set(this.last, item);
this.prevs.set(item, this.last);
this.last = item;
}
}
insertBefore(newItem, refItem) {
const prev = this.before(refItem);
this.prevs.set(newItem, prev);
this.nexts.set(newItem, refItem);
this.prevs.set(refItem, newItem);
prev && this.nexts.set(prev, newItem);
refItem === this.first && (this.first = newItem);
}
delete(item) {
const prev = this.before(item);
const next = this.after(item);
prev && this.nexts.set(prev, next);
next && this.prevs.set(next, prev);
item === this.first && (this.first = next);
item === this.last && (this.last = prev);
}
before(item) {
return this.prevs.get(item) || null;
}
after(item) {
return this.nexts.get(item) || null;
}
loop(iterator) {
if (this.empty()) {
return;
}
let current = this.first;
do {
if (iterator(current)) {
break;
}
} while ((current = this.after(current)));
}
copy() {
const list = new LinkedList();
this.loop((item) => {
list.push(item);
return false;
});
return list;
}
forEach(iterator) {
this.loop((item) => {
iterator(item);
return false;
});
}
find(iterator) {
let result = null;
this.loop((item) => {
if (iterator(item)) {
result = item;
return true;
}
return false;
});
return result;
}
map(iterator) {
const results = [];
this.loop((item) => {
results.push(iterator(item));
return false;
});
return results;
}
}
function matchChildren(vnode, old) {
const oldChildren = old.children();
const oldChildrenByKey = new Map();
const oldChildrenWithoutKey = [];
oldChildren.forEach((v) => {
const key = v.key();
if (key == null) {
oldChildrenWithoutKey.push(v);
} else {
oldChildrenByKey.set(key, v);
}
});
const children = vnode.children();
const matches = [];
const unmatched = new Set(oldChildren);
const keys = new Set();
children.forEach((v) => {
let match = null;
let guess = null;
const key = v.key();
if (key != null) {
if (keys.has(key)) {
throw new Error("Duplicate key");
}
keys.add(key);
if (oldChildrenByKey.has(key)) {
guess = oldChildrenByKey.get(key);
}
} else if (oldChildrenWithoutKey.length > 0) {
guess = oldChildrenWithoutKey.shift();
}
if (v.matches(guess)) {
match = guess;
}
matches.push([v, match]);
if (match) {
unmatched.delete(match);
}
});
return { matches, unmatched };
}
function execute(vnode, old, vdom) {
const didMatch = vnode && old && vnode.matches(old);
if (didMatch && vnode.parent() === old.parent()) {
vdom.replaceVNode(old, vnode);
} else if (vnode) {
vdom.addVNode(vnode);
}
const context = vdom.getVNodeContext(vnode);
const oldContext = vdom.getVNodeContext(old);
if (old && !didMatch) {
old.detach(oldContext);
old.children().forEach((v) => execute(null, v, vdom));
old.detached(oldContext);
}
if (vnode && !didMatch) {
vnode.attach(context);
vnode.children().forEach((v) => execute(v, null, vdom));
vnode.attached(context);
}
if (didMatch) {
const result = vnode.update(old, context);
if (result !== vdom.LEAVE) {
const { matches, unmatched } = matchChildren(vnode, old);
unmatched.forEach((v) => execute(null, v, vdom));
matches.forEach(([v, o]) => execute(v, o, vdom));
vnode.updated(context);
}
}
}
function m(tagOrComponent, props, ...children) {
props = props || {};
if (typeof tagOrComponent === "string") {
const tag = tagOrComponent;
return { type: tag, props, children };
}
if (typeof tagOrComponent === "function") {
const component = tagOrComponent;
return { type: component, props, children };
}
throw new Error("Unsupported spec type");
}
function isSpec(x) {
return isObject(x) && x.type != null && x.nodeType == null;
}
function isNodeSpec(x) {
return isSpec(x) && typeof x.type === "string";
}
function isComponentSpec(x) {
return isSpec(x) && typeof x.type === "function";
}
class VNodeBase {
constructor(parent) {
this.parentVNode = parent;
}
key() {
return null;
}
parent(vnode) {
if (vnode) {
this.parentVNode = vnode;
return;
}
return this.parentVNode;
}
children() {
return [];
}
attach(context) {}
detach(context) {}
update(old, context) {
return null;
}
attached(context) {}
detached(context) {}
updated(context) {}
}
function nodeMatchesSpec(node, spec) {
return node instanceof Element && spec.type === node.tagName.toLowerCase();
}
const refinedElements = new WeakMap();
function markElementAsRefined(element, vdom) {
let refined;
if (refinedElements.has(vdom)) {
refined = refinedElements.get(vdom);
} else {
refined = new WeakSet();
refinedElements.set(vdom, refined);
}
refined.add(element);
}
function isElementRefined(element, vdom) {
return refinedElements.has(vdom) && refinedElements.get(vdom).has(element);
}
class ElementVNode extends VNodeBase {
constructor(spec, parent) {
super(parent);
this.spec = spec;
}
matches(other) {
return (
other instanceof ElementVNode && this.spec.type === other.spec.type
);
}
key() {
return this.spec.props.key;
}
children() {
return [this.child];
}
getExistingElement(context) {
const parent = context.parent;
const existing = context.node;
let element;
if (nodeMatchesSpec(existing, this.spec)) {
element = existing;
} else if (
!isElementRefined(parent, context.vdom) &&
context.vdom.isDOMNodeCaptured(parent)
) {
const sibling = context.sibling;
const guess = sibling
? sibling.nextElementSibling
: parent.firstElementChild;
if (guess && !context.vdom.isDOMNodeCaptured(guess)) {
if (nodeMatchesSpec(guess, this.spec)) {
element = guess;
} else {
parent.removeChild(guess);
}
}
}
return element;
}
attach(context) {
let element;
const existing = this.getExistingElement(context);
if (existing) {
element = existing;
} else {
element = createElement(this.spec, context.parent);
markElementAsRefined(element, context.vdom);
}
syncAttrs(element, this.spec.props, null);
this.child = createDOMVNode(element, this.spec.children, this, false);
}
update(prev, context) {
const prevContext = context.vdom.getVNodeContext(prev);
const element = prevContext.node;
syncAttrs(element, this.spec.props, prev.spec.props);
this.child = createDOMVNode(element, this.spec.children, this, false);
}
attached(context) {
const { oncreate, onrender } = this.spec.props;
if (oncreate) {
oncreate(context.node);
}
if (onrender) {
onrender(context.node);
}
}
detached(context) {
const { onremove } = this.spec.props;
if (onremove) {
onremove(context.node);
}
}
updated(context) {
const { onupdate, onrender } = this.spec.props;
if (onupdate) {
onupdate(context.node);
}
if (onrender) {
onrender(context.node);
}
}
}
const symbols = {
CREATED: Symbol(),
REMOVED: Symbol(),
UPDATED: Symbol(),
RENDERED: Symbol(),
ACTIVE: Symbol(),
DEFAULTS_ASSIGNED: Symbol(),
};
const domPlugins = [
[PLUGINS_CREATE_ELEMENT, pluginsCreateElement],
[PLUGINS_SET_ATTRIBUTE, pluginsSetAttribute],
];
class ComponentVNode extends VNodeBase {
constructor(spec, parent) {
super(parent);
this.lock = false;
this.spec = spec;
this.prev = null;
this.store = {};
this.store[symbols.ACTIVE] = this;
}
matches(other) {
return (
other instanceof ComponentVNode && this.spec.type === other.spec.type
);
}
key() {
return this.spec.props.key;
}
children() {
return [this.child];
}
createContext(context) {
const { parent } = context;
const { spec, prev, store } = this;
return {
spec,
prev,
store,
get node() {
return context.node;
},
get nodes() {
return context.nodes;
},
parent,
onCreate: (fn) => (store[symbols.CREATED] = fn),
onUpdate: (fn) => (store[symbols.UPDATED] = fn),
onRemove: (fn) => (store[symbols.REMOVED] = fn),
onRender: (fn) => (store[symbols.RENDERED] = fn),
refresh: () => {
const activeVNode = store[symbols.ACTIVE];
activeVNode.refresh(context);
},
leave: () => context.vdom.LEAVE,
getStore: (defaults) => {
if (defaults && !store[symbols.DEFAULTS_ASSIGNED]) {
Object.entries(defaults).forEach(([prop, value]) => {
store[prop] = value;
});
store[symbols.DEFAULTS_ASSIGNED] = true;
}
return store;
},
};
}
unbox(context) {
const Component = this.spec.type;
const props = this.spec.props;
const children = this.spec.children;
this.lock = true;
const prevContext = ComponentVNode.context;
ComponentVNode.context = this.createContext(context);
let unboxed = null;
try {
unboxed = Component(props, ...children);
} finally {
ComponentVNode.context = prevContext;
this.lock = false;
}
return unboxed;
}
refresh(context) {
if (this.lock) {
throw new Error("Calling refresh during unboxing causes infinite loop");
}
this.prev = this.spec;
const latestContext = context.vdom.getVNodeContext(this);
const unboxed = this.unbox(latestContext);
if (unboxed === context.vdom.LEAVE) {
return;
}
const prevChild = this.child;
this.child = createVNode(unboxed, this);
context.vdom.execute(this.child, prevChild);
this.updated(context);
}
addPlugins() {
addComponentPlugins(this.spec.type, domPlugins);
}
deletePlugins() {
deleteComponentPlugins(this.spec.type, domPlugins);
}
attach(context) {
this.addPlugins();
const unboxed = this.unbox(context);
const childSpec = unboxed === context.vdom.LEAVE ? null : unboxed;
this.child = createVNode(childSpec, this);
}
update(prev, context) {
this.store = prev.store;
this.prev = prev.spec;
this.store[symbols.ACTIVE] = this;
const prevContext = context.vdom.getVNodeContext(prev);
this.addPlugins();
const unboxed = this.unbox(prevContext);
let result = null;
if (unboxed === context.vdom.LEAVE) {
result = unboxed;
this.child = prev.child;
context.vdom.adoptVNode(this.child, this);
} else {
this.child = createVNode(unboxed, this);
}
return result;
}
handle(event, context) {
const fn = this.store[event];
if (fn) {
const nodes = context.nodes.length === 0 ? [null] : context.nodes;
fn(...nodes);
}
}
attached(context) {
this.deletePlugins();
this.handle(symbols.CREATED, context);
this.handle(symbols.RENDERED, context);
}
detached(context) {
this.handle(symbols.REMOVED, context);
}
updated(context) {
this.deletePlugins();
this.handle(symbols.UPDATED, context);
this.handle(symbols.RENDERED, context);
}
}
ComponentVNode.context = null;
function getComponentContext() {
return ComponentVNode.context;
}
class TextVNode extends VNodeBase {
constructor(text, parent) {
super(parent);
this.text = text;
}
matches(other) {
return other instanceof TextVNode;
}
children() {
return [this.child];
}
getExistingNode(context) {
const { parent } = context;
let node;
if (context.node instanceof Text) {
node = context.node;
} else if (
!isElementRefined(parent, context.vdom) &&
context.vdom.isDOMNodeCaptured(parent)
) {
const sibling = context.sibling;
const guess = sibling ? sibling.nextSibling : parent.firstChild;
if (
guess &&
!context.vdom.isDOMNodeCaptured(guess) &&
guess instanceof Text
) {
node = guess;
}
}
return node;
}
attach(context) {
const existing = this.getExistingNode(context);
let node;
if (existing) {
node = existing;
node.textContent = this.text;
} else {
node = document.createTextNode(this.text);
}
this.child = createVNode(node, this);
}
update(prev, context) {
const prevContext = context.vdom.getVNodeContext(prev);
const { node } = prevContext;
if (this.text !== prev.text) {
node.textContent = this.text;
}
this.child = createVNode(node, this);
}
}
class InlineFunctionVNode extends VNodeBase {
constructor(fn, parent) {
super(parent);
this.fn = fn;
}
matches(other) {
return other instanceof InlineFunctionVNode;
}
children() {
return [this.child];
}
call(context) {
const fn = this.fn;
const inlineFnContext = {
parent: context.parent,
get node() {
return context.node;
},
get nodes() {
return context.nodes;
},
};
const result = fn(inlineFnContext);
this.child = createVNode(result, this);
}
attach(context) {
this.call(context);
}
update(prev, context) {
const prevContext = context.vdom.getVNodeContext(prev);
this.call(prevContext);
}
}
class NullVNode extends VNodeBase {
matches(other) {
return other instanceof NullVNode;
}
}
class DOMVNode extends VNodeBase {
constructor(node, childSpecs, parent, isNative) {
super(parent);
this.node = node;
this.childSpecs = childSpecs;
this.isNative = isNative;
}
matches(other) {
return other instanceof DOMVNode && this.node === other.node;
}
wrap() {
this.childVNodes = this.childSpecs.map((spec) => createVNode(spec, this));
}
insertNode(context) {
const { parent, sibling } = context;
const shouldInsert = !(
parent === this.node.parentElement &&
sibling === this.node.previousSibling
);
if (shouldInsert) {
const target = sibling ? sibling.nextSibling : parent.firstChild;
parent.insertBefore(this.node, target);
}
}
attach(context) {
this.wrap();
this.insertNode(context);
}
detach(context) {
context.parent.removeChild(this.node);
}
update(prev, context) {
this.wrap();
this.insertNode(context);
}
cleanupDOMChildren(context) {
const element = this.node;
for (let current = element.lastChild; current != null; ) {
if (context.vdom.isDOMNodeCaptured(current)) {
current = current.previousSibling;
} else {
const prev = current.previousSibling;
element.removeChild(current);
current = prev;
}
}
}
refine(context) {
if (!this.isNative) {
this.cleanupDOMChildren(context);
}
const element = this.node;
markElementAsRefined(element, context.vdom);
}
attached(context) {
const { node } = this;
if (
node instanceof Element &&
!isElementRefined(node, context.vdom) &&
context.vdom.isDOMNodeCaptured(node)
) {
this.refine(context);
}
}
children() {
return this.childVNodes;
}
}
function isDOMVNode(v) {
return v instanceof DOMVNode;
}
function createDOMVNode(node, childSpecs, parent, isNative) {
return new DOMVNode(node, childSpecs, parent, isNative);
}
class ArrayVNode extends VNodeBase {
constructor(items, key, parent) {
super(parent);
this.items = items;
this.id = key;
}
matches(other) {
return other instanceof ArrayVNode;
}
key() {
return this.id;
}
children() {
return this.childVNodes;
}
wrap() {
this.childVNodes = this.items.map((spec) => createVNode(spec, this));
}
attach() {
this.wrap();
}
update() {
this.wrap();
}
}
function createVNode(spec, parent) {
if (isNodeSpec(spec)) {
return new ElementVNode(spec, parent);
}
if (isComponentSpec(spec)) {
if (spec.type === Array) {
return new ArrayVNode(spec.children, spec.props.key, parent);
}
return new ComponentVNode(spec, parent);
}
if (typeof spec === "string") {
return new TextVNode(spec, parent);
}
if (spec == null) {
return new NullVNode(parent);
}
if (typeof spec === "function") {
return new InlineFunctionVNode(spec, parent);
}
if (spec instanceof Node) {
return createDOMVNode(spec, [], parent, true);
}
if (Array.isArray(spec)) {
return new ArrayVNode(spec, null, parent);
}
throw new Error("Unable to create virtual node for spec");
}
function createVDOM(rootNode) {
const contexts = new WeakMap();
const hubs = new WeakMap();
const parentNodes = new WeakMap();
const passingLinks = new WeakMap();
const linkedParents = new WeakSet();
const LEAVE = Symbol();
function execute$1(vnode, old) {
execute(vnode, old, vdom);
}
function creatVNodeContext(vnode) {
const parentNode = parentNodes.get(vnode);
contexts.set(vnode, {
parent: parentNode,
get node() {
const linked = passingLinks
.get(vnode)
.find((link) => link.node != null);
return linked ? linked.node : null;
},
get nodes() {
return passingLinks
.get(vnode)
.map((link) => link.node)
.filter((node) => node);
},
get sibling() {
if (parentNode === rootNode.parentElement) {
return passingLinks.get(vnode).first.node.previousSibling;
}
const hub = hubs.get(parentNode);
let current = passingLinks.get(vnode).first;
while ((current = hub.links.before(current))) {
if (current.node) {
return current.node;
}
}
return null;
},
vdom,
});
}
function createRootVNodeLinks(vnode) {
const parentNode =
rootNode.parentElement || document.createDocumentFragment();
const node = rootNode;
const links = new LinkedList({
parentNode,
node,
});
passingLinks.set(vnode, links.copy());
parentNodes.set(vnode, parentNode);
hubs.set(parentNode, {
node: parentNode,
links,
});
}
function createVNodeLinks(vnode) {
const parent = vnode.parent();
const isBranch = linkedParents.has(parent);
const parentNode = isDOMVNode(parent)
? parent.node
: parentNodes.get(parent);
parentNodes.set(vnode, parentNode);
const vnodeLinks = new LinkedList();
passingLinks.set(vnode, vnodeLinks);
if (isBranch) {
const newLink = {
parentNode,
node: null,
};
let current = vnode;
do {
passingLinks.get(current).push(newLink);
current = current.parent();
} while (current && !isDOMVNode(current));
hubs.get(parentNode).links.push(newLink);
} else {
linkedParents.add(parent);
const links = isDOMVNode(parent)
? hubs.get(parentNode).links
: passingLinks.get(parent);
links.forEach((link) => vnodeLinks.push(link));
}
}
function connectDOMVNode(vnode) {
if (isDOMVNode(vnode)) {
const { node } = vnode;
hubs.set(node, {
node,
links: new LinkedList({
parentNode: node,
node: null,
}),
});
passingLinks.get(vnode).forEach((link) => (link.node = node));
}
}
function addVNode(vnode) {
const parent = vnode.parent();
if (parent == null) {
createRootVNodeLinks(vnode);
} else {
createVNodeLinks(vnode);
}
connectDOMVNode(vnode);
creatVNodeContext(vnode);
}
function getVNodeContext(vnode) {
return contexts.get(vnode);
}
function getAncestorsLinks(vnode) {
const parentNode = parentNodes.get(vnode);
const hub = hubs.get(parentNode);
const allLinks = [];
let current = vnode;
while ((current = current.parent()) && !isDOMVNode(current)) {
allLinks.push(passingLinks.get(current));
}
allLinks.push(hub.links);
return allLinks;
}
function replaceVNode(old, vnode) {
if (vnode.parent() == null) {
addVNode(vnode);
return;
}
const oldContext = contexts.get(old);
const { parent: parentNode } = oldContext;
parentNodes.set(vnode, parentNode);
const oldLinks = passingLinks.get(old);
const newLink = {
parentNode,
node: null,
};
getAncestorsLinks(vnode).forEach((links) => {
const nextLink = links.after(oldLinks.last);
oldLinks.forEach((link) => links.delete(link));
if (nextLink) {
links.insertBefore(newLink, nextLink);
} else {
links.push(newLink);
}
});
const vnodeLinks = new LinkedList(newLink);
passingLinks.set(vnode, vnodeLinks);
creatVNodeContext(vnode);
}
function adoptVNode(vnode, parent) {
const vnodeLinks = passingLinks.get(vnode);
const parentLinks = passingLinks.get(parent).copy();
vnode.parent(parent);
getAncestorsLinks(vnode).forEach((links) => {
vnodeLinks.forEach((link) =>
links.insertBefore(link, parentLinks.first)
);
parentLinks.forEach((link) => links.delete(link));
});
}
function isDOMNodeCaptured(node) {
return hubs.has(node) && node !== rootNode.parentElement;
}
const vdom = {
execute: execute$1,
addVNode,
getVNodeContext,
replaceVNode,
adoptVNode,
isDOMNodeCaptured,
LEAVE,
};
return vdom;
}
const roots = new WeakMap();
const vdoms = new WeakMap();
function realize(node, vnode) {
const old = roots.get(node) || null;
roots.set(node, vnode);
let vdom;
if (vdoms.has(node)) {
vdom = vdoms.get(node);
} else {
vdom = createVDOM(node);
vdoms.set(node, vdom);
}
vdom.execute(vnode, old);
return vdom.getVNodeContext(vnode);
}
function render$1(element, spec) {
const vnode = createDOMVNode(
element,
Array.isArray(spec) ? spec : [spec],
null,
false
);
realize(element, vnode);
return element;
}
function sync(node, spec) {
const vnode = createVNode(spec, null);
const context = realize(node, vnode);
const { nodes } = context;
if (nodes.length !== 1 || nodes[0] !== node) {
throw new Error("Spec does not match the node");
}
return nodes[0];
}
function normalize(attrsOrChild, ...otherChildren) {
const attrs =
isObject(attrsOrChild) && !isSpec(attrsOrChild) ? attrsOrChild : null;
const children =
attrs == null ? [attrsOrChild].concat(otherChildren) : otherChildren;
return { attrs, children };
}
function createTagFunction(tag) {
return (attrsOrChild, ...otherChildren) => {
const { attrs, children } = normalize(attrsOrChild, otherChildren);
return m(tag, attrs, children);
};
}
new Proxy(
{},
{
get: (_, tag) => {
return createTagFunction(tag);
},
}
);
const plugins = {
createElement: createPluginsAPI(PLUGINS_CREATE_ELEMENT),
setAttribute: createPluginsAPI(PLUGINS_SET_ATTRIBUTE),
};
const userAgent =
typeof navigator === "undefined"
? "some useragent"
: navigator.userAgent.toLowerCase();
const platform =
typeof navigator === "undefined"
? "some platform"
: navigator.platform.toLowerCase();
const isChromium =
userAgent.includes("chrome") || userAgent.includes("chromium");
const isThunderbird = userAgent.includes("thunderbird");
const isFirefox =
userAgent.includes("firefox") ||
userAgent.includes("librewolf") ||
isThunderbird;
const isVivaldi = userAgent.includes("vivaldi");
const isYaBrowser = userAgent.includes("yabrowser");
const isOpera = userAgent.includes("opr") || userAgent.includes("opera");
const isEdge = userAgent.includes("edg");
userAgent.includes("safari") && !isChromium;
const isWindows = platform.startsWith("win");
const isMacOS = platform.startsWith("mac");
const isMobile = userAgent.includes("mobile");
const chromiumVersion = (() => {
const m = userAgent.match(/chrom[e|ium]\/([^ ]+)/);
if (m && m[1]) {
return m[1];
}
return "";
})();
(() => {
try {
document.querySelector(":defined");
return true;
} catch (err) {
return false;
}
})();
function compareChromeVersions($a, $b) {
const a = $a.split(".").map((x) => parseInt(x));
const b = $b.split(".").map((x) => parseInt(x));
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return a[i] < b[i] ? -1 : 1;
}
}
return 0;
}
globalThis.chrome &&
globalThis.chrome.runtime &&
globalThis.chrome.runtime.getManifest &&
globalThis.chrome.runtime.getManifest().manifest_version === 3;
const isCSSColorSchemePropSupported = (() => {
if (typeof document === "undefined") {
return false;
}
const el = document.createElement("div");
el.setAttribute("style", "color-scheme: dark");
return el.style && el.style.colorScheme === "dark";
})();
const MessageType = {
UI_GET_DATA: "ui-get-data",
UI_SUBSCRIBE_TO_CHANGES: "ui-subscribe-to-changes",
UI_UNSUBSCRIBE_FROM_CHANGES: "ui-unsubscribe-from-changes",
UI_CHANGE_SETTINGS: "ui-change-settings",
UI_SET_THEME: "ui-set-theme",
UI_SET_SHORTCUT: "ui-set-shortcut",
UI_TOGGLE_ACTIVE_TAB: "ui-toggle-active-tab",
UI_MARK_NEWS_AS_READ: "ui-mark-news-as-read",
UI_MARK_NEWS_AS_DISPLAYED: "ui-mark-news-as-displayed",
UI_LOAD_CONFIG: "ui-load-config",
UI_APPLY_DEV_DYNAMIC_THEME_FIXES: "ui-apply-dev-dynamic-theme-fixes",
UI_RESET_DEV_DYNAMIC_THEME_FIXES: "ui-reset-dev-dynamic-theme-fixes",
UI_APPLY_DEV_INVERSION_FIXES: "ui-apply-dev-inversion-fixes",
UI_RESET_DEV_INVERSION_FIXES: "ui-reset-dev-inversion-fixes",
UI_APPLY_DEV_STATIC_THEMES: "ui-apply-dev-static-themes",
UI_RESET_DEV_STATIC_THEMES: "ui-reset-dev-static-themes",
UI_SAVE_FILE: "ui-save-file",
UI_REQUEST_EXPORT_CSS: "ui-request-export-css",
BG_CHANGES: "bg-changes",
BG_ADD_CSS_FILTER: "bg-add-css-filter",
BG_ADD_STATIC_THEME: "bg-add-static-theme",
BG_ADD_SVG_FILTER: "bg-add-svg-filter",
BG_ADD_DYNAMIC_THEME: "bg-add-dynamic-theme",
BG_EXPORT_CSS: "bg-export-css",
BG_UNSUPPORTED_SENDER: "bg-unsupported-sender",
BG_CLEAN_UP: "bg-clean-up",
BG_RELOAD: "bg-reload",
BG_FETCH_RESPONSE: "bg-fetch-response",
BG_UI_UPDATE: "bg-ui-update",
BG_CSS_UPDATE: "bg-css-update",
CS_COLOR_SCHEME_CHANGE: "cs-color-scheme-change",
CS_FRAME_CONNECT: "cs-frame-connect",
CS_FRAME_FORGET: "cs-frame-forget",
CS_FRAME_FREEZE: "cs-frame-freeze",
CS_FRAME_RESUME: "cs-frame-resume",
CS_EXPORT_CSS_RESPONSE: "cs-export-css-response",
CS_FETCH: "cs-fetch",
CS_DARK_THEME_DETECTED: "cs-dark-theme-detected",
CS_DARK_THEME_NOT_DETECTED: "cs-dark-theme-not-detected",
};
class Connector {
constructor() {
this.onChangesReceived = ({ type, data }) => {
if (type === MessageType.BG_CHANGES) {
this.changeSubscribers.forEach((callback) => callback(data));
}
};
this.changeSubscribers = new Set();
}
async sendRequest(type, data) {
return new Promise((resolve, reject) => {
chrome.runtime.sendMessage({ type, data }, ({ data, error }) => {
if (error) {
reject(error);
} else {
resolve(data);
}
});
});
}
async firefoxSendRequestWithResponse(type, data) {
return new Promise((resolve, reject) => {
const dataPort = chrome.runtime.connect({ name: type });
dataPort.onDisconnect.addListener(() => reject());
dataPort.onMessage.addListener(({ data, error }) => {
if (error) {
reject(error);
} else {
resolve(data);
}
dataPort.disconnect();
});
data && dataPort.postMessage({ data });
});
}
async getData() {
if (isFirefox) {
return await this.firefoxSendRequestWithResponse(
MessageType.UI_GET_DATA
);
}
return await this.sendRequest(MessageType.UI_GET_DATA);
}
subscribeToChanges(callback) {
this.changeSubscribers.add(callback);
if (this.changeSubscribers.size === 1) {
chrome.runtime.onMessage.addListener(this.onChangesReceived);
chrome.runtime.sendMessage({
type: MessageType.UI_SUBSCRIBE_TO_CHANGES,
});
}
}
setShortcut(command, shortcut) {
chrome.runtime.sendMessage({
type: MessageType.UI_SET_SHORTCUT,
data: { command, shortcut },
});
}
changeSettings(settings) {
chrome.runtime.sendMessage({
type: MessageType.UI_CHANGE_SETTINGS,
data: settings,
});
}
setTheme(theme) {
chrome.runtime.sendMessage({
type: MessageType.UI_SET_THEME,
data: theme,
});
}
toggleActiveTab() {
chrome.runtime.sendMessage({
type: MessageType.UI_TOGGLE_ACTIVE_TAB,
data: {},
});
}
markNewsAsRead(ids) {
chrome.runtime.sendMessage({
type: MessageType.UI_MARK_NEWS_AS_READ,
data: ids,
});
}
markNewsAsDisplayed(ids) {
chrome.runtime.sendMessage({
type: MessageType.UI_MARK_NEWS_AS_DISPLAYED,
data: ids,
});
}
loadConfig(options) {
chrome.runtime.sendMessage({
type: MessageType.UI_LOAD_CONFIG,
data: options,
});
}
async applyDevDynamicThemeFixes(text) {
if (isFirefox) {
return await this.firefoxSendRequestWithResponse(
MessageType.UI_APPLY_DEV_DYNAMIC_THEME_FIXES,
text
);
}
return await this.sendRequest(
MessageType.UI_APPLY_DEV_DYNAMIC_THEME_FIXES,
text
);
}
resetDevDynamicThemeFixes() {
chrome.runtime.sendMessage({
type: MessageType.UI_RESET_DEV_DYNAMIC_THEME_FIXES,
});
}
async applyDevInversionFixes(text) {
if (isFirefox) {
return await this.firefoxSendRequestWithResponse(
MessageType.UI_APPLY_DEV_INVERSION_FIXES,
text
);
}
return await this.sendRequest(
MessageType.UI_APPLY_DEV_INVERSION_FIXES,
text
);
}
resetDevInversionFixes() {
chrome.runtime.sendMessage({
type: MessageType.UI_RESET_DEV_INVERSION_FIXES,
});
}
async applyDevStaticThemes(text) {
if (isFirefox) {
return await this.firefoxSendRequestWithResponse(
MessageType.UI_APPLY_DEV_STATIC_THEMES,
text
);
}
return await this.sendRequest(
MessageType.UI_APPLY_DEV_STATIC_THEMES,
text
);
}
resetDevStaticThemes() {
chrome.runtime.sendMessage({
type: MessageType.UI_RESET_DEV_STATIC_THEMES,
});
}
disconnect() {
if (this.changeSubscribers.size > 0) {
this.changeSubscribers.clear();
chrome.runtime.onMessage.removeListener(this.onChangesReceived);
chrome.runtime.sendMessage({
type: MessageType.UI_UNSUBSCRIBE_FROM_CHANGES,
});
}
}
}
/* malevic@0.19.1 - Jan 8, 2022 */
function withForms(type) {
plugins.setAttribute.add(type, ({ element, attr, value }) => {
if (attr === "value" && element instanceof HTMLInputElement) {
const text = (element.value = value == null ? "" : value);
element.value = text;
return true;
}
return null;
});
return type;
}
/* malevic@0.19.1 - Jan 8, 2022 */
let currentUseStateFn = null;
function useState(initialState) {
if (!currentUseStateFn) {
throw new Error("`useState()` should be called inside a component");
}
return currentUseStateFn(initialState);
}
function withState(type) {
const Stateful = (props, ...children) => {
const context = getComponentContext();
const useState = (initial) => {
if (!context) {
return { state: initial, setState: null };
}
const { store, refresh } = context;
store.state = store.state || initial;
const setState = (newState) => {
if (lock) {
throw new Error(
"Setting state during unboxing causes infinite loop"
);
}
store.state = Object.assign(Object.assign({}, store.state), newState);
refresh();
};
return {
state: store.state,
setState,
};
};
let lock = true;
const prevUseStateFn = currentUseStateFn;
currentUseStateFn = useState;
let result;
try {
result = type(props, ...children);
} finally {
currentUseStateFn = prevUseStateFn;
lock = false;
}
return result;
};
return Stateful;
}
function classes(...args) {
const classes = [];
args
.filter((c) => Boolean(c))
.forEach((c) => {
if (typeof c === "string") {
classes.push(c);
} else if (typeof c === "object") {
classes.push(...Object.keys(c).filter((key) => Boolean(c[key])));
}
});
return classes.join(" ");
}
function compose(type, ...wrappers) {
return wrappers.reduce((t, w) => w(t), type);
}
function openFile(options, callback) {
const input = document.createElement("input");
input.type = "file";
input.style.display = "none";
if (options.extensions && options.extensions.length > 0) {
input.accept = options.extensions.map((ext) => `.${ext}`).join(",");
}
const reader = new FileReader();
reader.onloadend = () => callback(reader.result);
input.onchange = () => {
if (input.files[0]) {
reader.readAsText(input.files[0]);
document.body.removeChild(input);
}
};
document.body.appendChild(input);
input.click();
}
function saveFile(name, content) {
if (isFirefox) {
const a = document.createElement("a");
a.href = URL.createObjectURL(new Blob([content]));
a.download = name;
a.click();
} else {
chrome.runtime.sendMessage({
type: MessageType.UI_SAVE_FILE,
data: { name, content },
});
}
}
function throttle$1(callback) {
let frameId = null;
return (...args) => {
if (!frameId) {
callback(...args);
frameId = requestAnimationFrame(() => (frameId = null));
}
};
}
function onSwipeStart(startEventObj, startHandler) {
const isTouchEvent =
typeof TouchEvent !== "undefined" && startEventObj instanceof TouchEvent;
const touchId = isTouchEvent
? startEventObj.changedTouches[0].identifier
: null;
const pointerMoveEvent = isTouchEvent ? "touchmove" : "mousemove";
const pointerUpEvent = isTouchEvent ? "touchend" : "mouseup";
if (!isTouchEvent) {
startEventObj.preventDefault();
}
function getSwipeEventObject(e) {
const { clientX, clientY } = isTouchEvent ? getTouch(e) : e;
return { clientX, clientY };
}
const startSE = getSwipeEventObject(startEventObj);
const { move: moveHandler, up: upHandler } = startHandler(
startSE,
startEventObj
);
function getTouch(e) {
return Array.from(e.changedTouches).find(
({ identifier: id }) => id === touchId
);
}
const onPointerMove = throttle$1((e) => {
const se = getSwipeEventObject(e);
moveHandler(se, e);
});
function onPointerUp(e) {
unsubscribe();
const se = getSwipeEventObject(e);
upHandler(se, e);
}
function unsubscribe() {
window.removeEventListener(pointerMoveEvent, onPointerMove);
window.removeEventListener(pointerUpEvent, onPointerUp);
}
window.addEventListener(pointerMoveEvent, onPointerMove, { passive: true });
window.addEventListener(pointerUpEvent, onPointerUp, { passive: true });
}
function createSwipeHandler(startHandler) {
return (e) => onSwipeStart(e, startHandler);
}
async function getFontList() {
return new Promise((resolve) => {
if (!chrome.fontSettings) {
resolve([
"serif",
"sans-serif",
"monospace",
"cursive",
"fantasy",
"system-ui",
]);
return;
}
chrome.fontSettings.getFontList((list) => {
const fonts = list.map((f) => f.fontId);
resolve(fonts);
});
});
}
function toArray(x) {
return Array.isArray(x) ? x : [x];
}
function mergeClass(cls, propsCls) {
const normalized = toArray(cls).concat(toArray(propsCls));
return classes(...normalized);
}
function omitAttrs(omit, attrs) {
const result = {};
Object.keys(attrs).forEach((key) => {
if (omit.indexOf(key) < 0) {
result[key] = attrs[key];
}
});
return result;
}
function isElementHidden(element) {
return element.offsetParent === null;
}
function Button(props, ...children) {
const cls = mergeClass("button", props.class);
const attrs = omitAttrs(["class"], props);
return m$1(
"button",
{ class: cls, ...attrs },
m$1("span", { class: "button__wrapper" }, ...children)
);
}
function CheckBox(props, ...children) {
const cls = mergeClass("checkbox", props.class);
const attrs = omitAttrs(["class", "checked", "onchange"], props);
const check = (domNode) => (domNode.checked = Boolean(props.checked));
return m$1(
"label",
{ class: cls, ...attrs },
m$1("input", {
class: "checkbox__input",
type: "checkbox",
checked: props.checked,
onchange: props.onchange,
onrender: check,
}),
m$1("span", { class: "checkbox__checkmark" }),
m$1("span", { class: "checkbox__content" }, children)
);
}
function hslToRGB({ h, s, l, a = 1 }) {
if (s === 0) {
const [r, b, g] = [l, l, l].map((x) => Math.round(x * 255));
return { r, g, b, a };
}
const c = (1 - Math.abs(2 * l - 1)) * s;
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
const m = l - c / 2;
const [r, g, b] = (
h < 60
? [c, x, 0]
: h < 120
? [x, c, 0]
: h < 180
? [0, c, x]
: h < 240
? [0, x, c]
: h < 300
? [x, 0, c]
: [c, 0, x]
).map((n) => Math.round((n + m) * 255));
return { r, g, b, a };
}
function rgbToHSL({ r: r255, g: g255, b: b255, a = 1 }) {
const r = r255 / 255;
const g = g255 / 255;
const b = b255 / 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
const c = max - min;
const l = (max + min) / 2;
if (c === 0) {
return { h: 0, s: 0, l, a };
}
let h =
(max === r
? ((g - b) / c) % 6
: max === g
? (b - r) / c + 2
: (r - g) / c + 4) * 60;
if (h < 0) {
h += 360;
}
const s = c / (1 - Math.abs(2 * l - 1));
return { h, s, l, a };
}
function toFixed(n, digits = 0) {
const fixed = n.toFixed(digits);
if (digits === 0) {
return fixed;
}
const dot = fixed.indexOf(".");
if (dot >= 0) {
const zerosMatch = fixed.match(/0+$/);
if (zerosMatch) {
if (zerosMatch.index === dot + 1) {
return fixed.substring(0, dot);
}
return fixed.substring(0, zerosMatch.index);
}
}
return fixed;
}
function rgbToHexString({ r, g, b, a }) {
return `#${(a != null && a < 1 ? [r, g, b, Math.round(a * 255)] : [r, g, b])
.map((x) => {
return `${x < 16 ? "0" : ""}${x.toString(16)}`;
})
.join("")}`;
}
function hslToString(hsl) {
const { h, s, l, a } = hsl;
if (a != null && a < 1) {
return `hsla(${toFixed(h)}, ${toFixed(s * 100)}%, ${toFixed(
l * 100
)}%, ${toFixed(a, 2)})`;
}
return `hsl(${toFixed(h)}, ${toFixed(s * 100)}%, ${toFixed(l * 100)}%)`;
}
const rgbMatch = /^rgba?\([^\(\)]+\)$/;
const hslMatch = /^hsla?\([^\(\)]+\)$/;
const hexMatch = /^#[0-9a-f]+$/i;
function parse($color) {
const c = $color.trim().toLowerCase();
if (c.match(rgbMatch)) {
return parseRGB(c);
}
if (c.match(hslMatch)) {
return parseHSL(c);
}
if (c.match(hexMatch)) {
return parseHex(c);
}
if (knownColors.has(c)) {
return getColorByName(c);
}
if (systemColors.has(c)) {
return getSystemColor(c);
}
if ($color === "transparent") {
return { r: 0, g: 0, b: 0, a: 0 };
}
throw new Error(`Unable to parse ${$color}`);
}
function getNumbers($color) {
const numbers = [];
let prevPos = 0;
let isMining = false;
const startIndex = $color.indexOf("(");
$color = $color.substring(startIndex + 1, $color.length - 1);
for (let i = 0; i < $color.length; i++) {
const c = $color[i];
if ((c >= "0" && c <= "9") || c === "." || c === "+" || c === "-") {
isMining = true;
} else if (isMining && (c === " " || c === ",")) {
numbers.push($color.substring(prevPos, i));
isMining = false;
prevPos = i + 1;
} else if (!isMining) {
prevPos = i + 1;
}
}
if (isMining) {
numbers.push($color.substring(prevPos, $color.length));
}
return numbers;
}
function getNumbersFromString(str, range, units) {
const raw = getNumbers(str);
const unitsList = Object.entries(units);
const numbers = raw
.map((r) => r.trim())
.map((r, i) => {
let n;
const unit = unitsList.find(([u]) => r.endsWith(u));
if (unit) {
n =
(parseFloat(r.substring(0, r.length - unit[0].length)) / unit[1]) *
range[i];
} else {
n = parseFloat(r);
}
if (range[i] > 1) {
return Math.round(n);
}
return n;
});
return numbers;
}
const rgbRange = [255, 255, 255, 1];
const rgbUnits = { "%": 100 };
function parseRGB($rgb) {
const [r, g, b, a = 1] = getNumbersFromString($rgb, rgbRange, rgbUnits);
return { r, g, b, a };
}
const hslRange = [360, 1, 1, 1];
const hslUnits = { "%": 100, deg: 360, rad: 2 * Math.PI, turn: 1 };
function parseHSL($hsl) {
const [h, s, l, a = 1] = getNumbersFromString($hsl, hslRange, hslUnits);
return hslToRGB({ h, s, l, a });
}
function parseHex($hex) {
const h = $hex.substring(1);
switch (h.length) {
case 3:
case 4: {
const [r, g, b] = [0, 1, 2].map((i) => parseInt(`${h[i]}${h[i]}`, 16));
const a = h.length === 3 ? 1 : parseInt(`${h[3]}${h[3]}`, 16) / 255;
return { r, g, b, a };
}
case 6:
case 8: {
const [r, g, b] = [0, 2, 4].map((i) =>
parseInt(h.substring(i, i + 2), 16)
);
const a = h.length === 6 ? 1 : parseInt(h.substring(6, 8), 16) / 255;
return { r, g, b, a };
}
}
throw new Error(`Unable to parse ${$hex}`);
}
function getColorByName($color) {
const n = knownColors.get($color);
return {
r: (n >> 16) & 255,
g: (n >> 8) & 255,
b: (n >> 0) & 255,
a: 1,
};
}
function getSystemColor($color) {
const n = systemColors.get($color);
return {
r: (n >> 16) & 255,
g: (n >> 8) & 255,
b: (n >> 0) & 255,
a: 1,
};
}
const knownColors = new Map(
Object.entries({
aliceblue: 0xf0f8ff,
antiquewhite: 0xfaebd7,
aqua: 0x00ffff,
aquamarine: 0x7fffd4,
azure: 0xf0ffff,
beige: 0xf5f5dc,
bisque: 0xffe4c4,
black: 0x000000,
blanchedalmond: 0xffebcd,
blue: 0x0000ff,
blueviolet: 0x8a2be2,
brown: 0xa52a2a,
burlywood: 0xdeb887,
cadetblue: 0x5f9ea0,
chartreuse: 0x7fff00,
chocolate: 0xd2691e,
coral: 0xff7f50,
cornflowerblue: 0x6495ed,
cornsilk: 0xfff8dc,
crimson: 0xdc143c,
cyan: 0x00ffff,
darkblue: 0x00008b,
darkcyan: 0x008b8b,
darkgoldenrod: 0xb8860b,
darkgray: 0xa9a9a9,
darkgrey: 0xa9a9a9,
darkgreen: 0x006400,
darkkhaki: 0xbdb76b,
darkmagenta: 0x8b008b,
darkolivegreen: 0x556b2f,
darkorange: 0xff8c00,
darkorchid: 0x9932cc,
darkred: 0x8b0000,
darksalmon: 0xe9967a,
darkseagreen: 0x8fbc8f,
darkslateblue: 0x483d8b,
darkslategray: 0x2f4f4f,
darkslategrey: 0x2f4f4f,
darkturquoise: 0x00ced1,
darkviolet: 0x9400d3,
deeppink: 0xff1493,
deepskyblue: 0x00bfff,
dimgray: 0x696969,
dimgrey: 0x696969,
dodgerblue: 0x1e90ff,
firebrick: 0xb22222,
floralwhite: 0xfffaf0,
forestgreen: 0x228b22,
fuchsia: 0xff00ff,
gainsboro: 0xdcdcdc,
ghostwhite: 0xf8f8ff,
gold: 0xffd700,
goldenrod: 0xdaa520,
gray: 0x808080,
grey: 0x808080,
green: 0x008000,
greenyellow: 0xadff2f,
honeydew: 0xf0fff0,
hotpink: 0xff69b4,
indianred: 0xcd5c5c,
indigo: 0x4b0082,
ivory: 0xfffff0,
khaki: 0xf0e68c,
lavender: 0xe6e6fa,
lavenderblush: 0xfff0f5,
lawngreen: 0x7cfc00,
lemonchiffon: 0xfffacd,
lightblue: 0xadd8e6,
lightcoral: 0xf08080,
lightcyan: 0xe0ffff,
lightgoldenrodyellow: 0xfafad2,
lightgray: 0xd3d3d3,
lightgrey: 0xd3d3d3,
lightgreen: 0x90ee90,
lightpink: 0xffb6c1,
lightsalmon: 0xffa07a,
lightseagreen: 0x20b2aa,
lightskyblue: 0x87cefa,
lightslategray: 0x778899,
lightslategrey: 0x778899,
lightsteelblue: 0xb0c4de,
lightyellow: 0xffffe0,
lime: 0x00ff00,
limegreen: 0x32cd32,
linen: 0xfaf0e6,
magenta: 0xff00ff,
maroon: 0x800000,
mediumaquamarine: 0x66cdaa,
mediumblue: 0x0000cd,
mediumorchid: 0xba55d3,
mediumpurple: 0x9370db,
mediumseagreen: 0x3cb371,
mediumslateblue: 0x7b68ee,
mediumspringgreen: 0x00fa9a,
mediumturquoise: 0x48d1cc,
mediumvioletred: 0xc71585,
midnightblue: 0x191970,
mintcream: 0xf5fffa,
mistyrose: 0xffe4e1,
moccasin: 0xffe4b5,
navajowhite: 0xffdead,
navy: 0x000080,
oldlace: 0xfdf5e6,
olive: 0x808000,
olivedrab: 0x6b8e23,
orange: 0xffa500,
orangered: 0xff4500,
orchid: 0xda70d6,
palegoldenrod: 0xeee8aa,
palegreen: 0x98fb98,
paleturquoise: 0xafeeee,
palevioletred: 0xdb7093,
papayawhip: 0xffefd5,
peachpuff: 0xffdab9,
peru: 0xcd853f,
pink: 0xffc0cb,
plum: 0xdda0dd,
powderblue: 0xb0e0e6,
purple: 0x800080,
rebeccapurple: 0x663399,
red: 0xff0000,
rosybrown: 0xbc8f8f,
royalblue: 0x4169e1,
saddlebrown: 0x8b4513,
salmon: 0xfa8072,
sandybrown: 0xf4a460,
seagreen: 0x2e8b57,
seashell: 0xfff5ee,
sienna: 0xa0522d,
silver: 0xc0c0c0,
skyblue: 0x87ceeb,
slateblue: 0x6a5acd,
slategray: 0x708090,
slategrey: 0x708090,
snow: 0xfffafa,
springgreen: 0x00ff7f,
steelblue: 0x4682b4,
tan: 0xd2b48c,
teal: 0x008080,
thistle: 0xd8bfd8,
tomato: 0xff6347,
turquoise: 0x40e0d0,
violet: 0xee82ee,
wheat: 0xf5deb3,
white: 0xffffff,
whitesmoke: 0xf5f5f5,
yellow: 0xffff00,
yellowgreen: 0x9acd32,
})
);
const systemColors = new Map(
Object.entries({
ActiveBorder: 0x3b99fc,
ActiveCaption: 0x000000,
AppWorkspace: 0xaaaaaa,
Background: 0x6363ce,
ButtonFace: 0xffffff,
ButtonHighlight: 0xe9e9e9,
ButtonShadow: 0x9fa09f,
ButtonText: 0x000000,
CaptionText: 0x000000,
GrayText: 0x7f7f7f,
Highlight: 0xb2d7ff,
HighlightText: 0x000000,
InactiveBorder: 0xffffff,
InactiveCaption: 0xffffff,
InactiveCaptionText: 0x000000,
InfoBackground: 0xfbfcc5,
InfoText: 0x000000,
Menu: 0xf6f6f6,
MenuText: 0xffffff,
Scrollbar: 0xaaaaaa,
ThreeDDarkShadow: 0x000000,
ThreeDFace: 0xc0c0c0,
ThreeDHighlight: 0xffffff,
ThreeDLightShadow: 0xffffff,
ThreeDShadow: 0x000000,
Window: 0xececec,
WindowFrame: 0xaaaaaa,
WindowText: 0x000000,
"-webkit-focus-ring-color": 0xe59700,
}).map(([key, value]) => [key.toLowerCase(), value])
);
function TextBox(props) {
const cls = mergeClass("textbox", props.class);
const attrs = omitAttrs(["class", "type"], props);
return m$1("input", {
class: cls,
type: "text",
spellcheck: "false",
...attrs,
});
}
function scale(x, inLow, inHigh, outLow, outHigh) {
return ((x - inLow) * (outHigh - outLow)) / (inHigh - inLow) + outLow;
}
function clamp(x, min, max) {
return Math.min(max, Math.max(min, x));
}
const hsbPickerDefaults = {
wasPrevHidden: true,
hueCanvasRendered: false,
activeHSB: null,
activeChangeHandler: null,
hueTouchStartHandler: null,
sbTouchStartHandler: null,
};
function rgbToHSB({ r, g, b }) {
const min = Math.min(r, g, b);
const max = Math.max(r, g, b);
return {
h: rgbToHSL({ r, g, b }).h,
s: max === 0 ? 0 : 1 - min / max,
b: max / 255,
};
}
function hsbToRGB({ h: hue, s: sat, b: br }) {
let c;
if (hue < 60) {
c = [1, hue / 60, 0];
} else if (hue < 120) {
c = [(120 - hue) / 60, 1, 0];
} else if (hue < 180) {
c = [0, 1, (hue - 120) / 60];
} else if (hue < 240) {
c = [0, (240 - hue) / 60, 1];
} else if (hue < 300) {
c = [(hue - 240) / 60, 0, 1];
} else {
c = [1, 0, (360 - hue) / 60];
}
const max = Math.max(...c);
const [r, g, b] = c
.map((v) => v + (max - v) * (1 - sat))
.map((v) => v * br)
.map((v) => Math.round(v * 255));
return { r, g, b, a: 1 };
}
function hsbToString(hsb) {
const rgb = hsbToRGB(hsb);
return rgbToHexString(rgb);
}
function render(canvas, getPixel) {
const { width, height } = canvas;
const context = canvas.getContext("2d");
const imageData = context.getImageData(0, 0, width, height);
const d = imageData.data;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const i = 4 * (y * width + x);
const c = getPixel(x, y);
for (let j = 0; j < 4; j++) {
d[i + j] = c[j];
}
}
}
context.putImageData(imageData, 0, 0);
}
function renderHue(canvas) {
const { height } = canvas;
render(canvas, (_, y) => {
const hue = scale(y, 0, height, 0, 360);
const { r, g, b } = hsbToRGB({ h: hue, s: 1, b: 1 });
return new Uint8ClampedArray([r, g, b, 255]);
});
}
function renderSB(hue, canvas) {
const { width, height } = canvas;
render(canvas, (x, y) => {
const sat = scale(x, 0, width - 1, 0, 1);
const br = scale(y, 0, height - 1, 1, 0);
const { r, g, b } = hsbToRGB({ h: hue, s: sat, b: br });
return new Uint8ClampedArray([r, g, b, 255]);
});
}
function HSBPicker(props) {
const context = getComponentContext();
const store = context.getStore(hsbPickerDefaults);
store.activeChangeHandler = props.onChange;
const prevColor = context.prev && context.prev.props.color;
const prevActiveColor = store.activeHSB
? hsbToString(store.activeHSB)
: null;
const didColorChange =
props.color !== prevColor && props.color !== prevActiveColor;
let activeHSB;
if (didColorChange) {
const rgb = parse(props.color);
activeHSB = rgbToHSB(rgb);
store.activeHSB = activeHSB;
} else {
activeHSB = store.activeHSB;
}
function onSBCanvasRender(canvas) {
if (isElementHidden(canvas)) {
return;
}
const hue = activeHSB.h;
const prevHue = prevColor && rgbToHSB(parse(prevColor)).h;
if (store.wasPrevHidden || hue !== prevHue) {
renderSB(hue, canvas);
}
store.wasPrevHidden = false;
}
function onHueCanvasRender(canvas) {
if (store.hueCanvasRendered || isElementHidden(canvas)) {
return;
}
store.hueCanvasRendered = true;
renderHue(canvas);
}
function createHSBSwipeHandler(getEventHSB) {
return createSwipeHandler((startEvt, startNativeEvt) => {
const rect = startNativeEvt.currentTarget.getBoundingClientRect();
function onPointerMove(e) {
store.activeHSB = getEventHSB({ ...e, rect });
props.onColorPreview(hsbToString(store.activeHSB));
context.refresh();
}
function onPointerUp(e) {
const hsb = getEventHSB({ ...e, rect });
store.activeHSB = hsb;
props.onChange(hsbToString(hsb));
}
store.activeHSB = getEventHSB({ ...startEvt, rect });
context.refresh();
return {
move: onPointerMove,
up: onPointerUp,
};
});
}
const onSBPointerDown = createHSBSwipeHandler(
({ clientX, clientY, rect }) => {
const sat = clamp((clientX - rect.left) / rect.width, 0, 1);
const br = clamp(1 - (clientY - rect.top) / rect.height, 0, 1);
return { ...activeHSB, s: sat, b: br };
}
);
const onHuePointerDown = createHSBSwipeHandler(({ clientY, rect }) => {
const hue = clamp((clientY - rect.top) / rect.height, 0, 1) * 360;
return { ...activeHSB, h: hue };
});
const hueCursorStyle = {
"background-color": hslToString({ h: activeHSB.h, s: 1, l: 0.5, a: 1 }),
left: "0%",
top: `${(activeHSB.h / 360) * 100}%`,
};
const sbCursorStyle = {
"background-color": rgbToHexString(hsbToRGB(activeHSB)),
left: `${activeHSB.s * 100}%`,
top: `${(1 - activeHSB.b) * 100}%`,
};
return m$1(
"span",
{ class: "hsb-picker" },
m$1(
"span",
{
class: "hsb-picker__sb-container",
onmousedown: onSBPointerDown,
onupdate: (el) => {
if (store.sbTouchStartHandler) {
el.removeEventListener("touchstart", store.sbTouchStartHandler);
}
el.addEventListener("touchstart", onSBPointerDown, {
passive: true,
});
store.sbTouchStartHandler = onSBPointerDown;
},
},
m$1("canvas", {
class: "hsb-picker__sb-canvas",
onrender: onSBCanvasRender,
}),
m$1("span", { class: "hsb-picker__sb-cursor", style: sbCursorStyle })
),
m$1(
"span",
{
class: "hsb-picker__hue-container",
onmousedown: onHuePointerDown,
onupdate: (el) => {
if (store.hueTouchStartHandler) {
el.removeEventListener("touchstart", store.hueTouchStartHandler);
}
el.addEventListener("touchstart", onHuePointerDown, {
passive: true,
});
store.hueTouchStartHandler = onHuePointerDown;
},
},
m$1("canvas", {
class: "hsb-picker__hue-canvas",
onrender: onHueCanvasRender,
}),
m$1("span", { class: "hsb-picker__hue-cursor", style: hueCursorStyle })
)
);
}
function isValidColor(color) {
try {
parse(color);
return true;
} catch (err) {
return false;
}
}
const colorPickerFocuses = new WeakMap();
function focusColorPicker(node) {
const focus = colorPickerFocuses.get(node);
focus();
}
function ColorPicker(props) {
const context = getComponentContext();
context.onRender((node) => colorPickerFocuses.set(node, focus));
const store = context.store;
const isColorValid = isValidColor(props.color);
function onColorPreview(previewColor) {
store.previewNode.style.backgroundColor = previewColor;
store.textBoxNode.value = previewColor;
store.textBoxNode.blur();
}
function onColorChange(rawValue) {
const value = rawValue.trim();
if (isValidColor(value)) {
props.onChange(value);
} else {
props.onChange(props.color);
}
}
function focus() {
if (store.isFocused) {
return;
}
store.isFocused = true;
context.refresh();
window.addEventListener("mousedown", onOuterClick);
}
function blur() {
if (!store.isFocused) {
return;
}
window.removeEventListener("mousedown", onOuterClick);
store.isFocused = false;
context.refresh();
}
function toggleFocus() {
if (store.isFocused) {
blur();
} else {
focus();
}
}
function onOuterClick(e) {
if (!e.composedPath().some((el) => el === context.node)) {
blur();
}
}
const textBox = m$1(TextBox, {
class: "color-picker__input",
onrender: (el) => {
store.textBoxNode = el;
store.textBoxNode.value = isColorValid ? props.color : "";
},
onkeypress: (e) => {
const input = e.target;
if (e.key === "Enter") {
const { value } = input;
onColorChange(value);
blur();
onColorPreview(value);
}
},
onfocus: focus,
});
const previewElement = m$1("span", {
class: "color-picker__preview",
onclick: toggleFocus,
onrender: (el) => {
store.previewNode = el;
el.style.backgroundColor = isColorValid ? props.color : "transparent";
},
});
const resetButton = props.canReset
? m$1("span", {
role: "button",
class: "color-picker__reset",
onclick: () => {
props.onReset();
blur();
},
})
: null;
const textBoxLine = m$1(
"span",
{ class: "color-picker__textbox-line" },
textBox,
previewElement,
resetButton
);
const hsbLine = isColorValid
? m$1(
"span",
{ class: "color-picker__hsb-line" },
m$1(HSBPicker, {
color: props.color,
onChange: onColorChange,
onColorPreview: onColorPreview,
})
)
: null;
return m$1(
"span",
{
class: [
"color-picker",
store.isFocused && "color-picker--focused",
props.class,
],
},
m$1("span", { class: "color-picker__wrapper" }, textBoxLine, hsbLine)
);
}
var ColorPicker$1 = Object.assign(ColorPicker, { focus: focusColorPicker });
function DropDown(props) {
const context = getComponentContext();
const store = context.store;
if (context.prev) {
const currOptions = props.options.map((o) => o.id);
const prevOptions = context.prev.props.options.map((o) => o.id);
if (
currOptions.length !== prevOptions.length ||
currOptions.some((o, i) => o !== prevOptions[i])
) {
store.isOpen = false;
}
}
function saveListNode(el) {
store.listNode = el;
}
function saveSelectedNode(el) {
store.selectedNode = el;
}
function onSelectedClick() {
store.isOpen = !store.isOpen;
context.refresh();
if (store.isOpen) {
const onOuterClick = (e) => {
window.removeEventListener("mousedown", onOuterClick, false);
const listRect = store.listNode.getBoundingClientRect();
const ex = e.clientX;
const ey = e.clientY;
if (
ex < listRect.left ||
ex > listRect.right ||
ey < listRect.top ||
ey > listRect.bottom
) {
store.isOpen = false;
context.refresh();
}
};
window.addEventListener("mousedown", onOuterClick, false);
}
}
function createListItem(value) {
return m$1(
"span",
{
class: {
dropdown__list__item: true,
"dropdown__list__item--selected": value.id === props.selected,
[props.class]: props.class != null,
},
onclick: () => {
store.isOpen = false;
context.refresh();
props.onChange(value.id);
},
},
value.content
);
}
const selectedContent = props.options.find(
(value) => value.id === props.selected
).content;
return m$1(
"span",
{
class: {
dropdown: true,
"dropdown--open": store.isOpen,
[props.class]: Boolean(props.class),
},
},
m$1(
"span",
{ class: "dropdown__list", oncreate: saveListNode },
props.options
.slice()
.sort((a, b) =>
a.id === props.selected ? -1 : b.id === props.selected ? 1 : 0
)
.map(createListItem)
),
m$1(
"span",
{
class: "dropdown__selected",
oncreate: saveSelectedNode,
onclick: onSelectedClick,
},
m$1("span", { class: "dropdown__selected__text" }, selectedContent)
)
);
}
function ColorDropDown(props) {
const context = getComponentContext();
const store = context.store;
const labels = {
DEFAULT: "Default",
AUTO: "Auto",
CUSTOM: "Custom",
};
const dropDownOptions = [
props.hasDefaultOption
? { id: "default", content: labels.DEFAULT }
: null,
props.hasAutoOption ? { id: "auto", content: labels.AUTO } : null,
{ id: "custom", content: labels.CUSTOM },
].filter((v) => v);
const selectedDropDownValue =
props.value === ""
? "default"
: props.value === "auto"
? "auto"
: "custom";
function onDropDownChange(value) {
const result = {
default: "",
auto: "auto",
custom: props.colorSuggestion,
}[value];
props.onChange(result);
}
let isPickerVisible;
try {
parse(props.value);
isPickerVisible = true;
} catch (err) {
isPickerVisible = false;
}
const prevValue = context.prev ? context.prev.props.value : null;
const shouldFocusOnPicker =
props.value !== "" &&
props.value !== "auto" &&
prevValue != null &&
(prevValue === "" || prevValue === "auto");
function onRootRender(root) {
if (shouldFocusOnPicker) {
const pickerNode = root.querySelector(".color-dropdown__picker");
ColorPicker$1.focus(pickerNode);
}
}
return m$1(
"span",
{
class: {
"color-dropdown": true,
"color-dropdown--open": store.isOpen,
[props.class]: Boolean(props.class),
},
onrender: onRootRender,
},
m$1(DropDown, {
class: "color-dropdown__options",
options: dropDownOptions,
selected: selectedDropDownValue,
onChange: onDropDownChange,
}),
m$1(ColorPicker$1, {
class: {
"color-dropdown__picker": true,
"color-dropdown__picker--hidden": !isPickerVisible,
},
color: props.value,
onChange: props.onChange,
canReset: true,
onReset: props.onReset,
})
);
}
const DEFAULT_OVERLAY_KEY = Symbol();
const overlayNodes = new Map();
const clickListeners = new WeakMap();
function getOverlayDOMNode(key) {
if (key == null) {
key = DEFAULT_OVERLAY_KEY;
}
if (!overlayNodes.has(key)) {
const node = document.createElement("div");
node.classList.add("overlay");
node.addEventListener("click", (e) => {
if (clickListeners.has(node) && e.currentTarget === node) {
const listener = clickListeners.get(node);
listener();
}
});
overlayNodes.set(key, node);
}
return overlayNodes.get(key);
}
function Overlay(props) {
return getOverlayDOMNode(props.key);
}
function Portal(props, ...content) {
const context = getComponentContext();
context.onRender(() => {
const node = getOverlayDOMNode(props.key);
if (props.onOuterClick) {
clickListeners.set(node, props.onOuterClick);
} else {
clickListeners.delete(node);
}
render$1(node, content);
});
context.onRemove(() => {
const container = getOverlayDOMNode(props.key);
render$1(container, null);
});
return context.leave();
}
var Overlay$1 = Object.assign(Overlay, { Portal });
function MessageBox(props) {
return m$1(
Overlay$1.Portal,
{ key: props.portalKey, onOuterClick: props.onCancel },
m$1(
"div",
{ class: "message-box" },
m$1("label", { class: "message-box__caption" }, props.caption),
m$1(
"div",
{ class: "message-box__buttons" },
m$1(
Button,
{
class: "message-box__button message-box__button-ok",
onclick: props.onOK,
},
"OK"
),
m$1(
Button,
{
class: "message-box__button message-box__button-cancel",
onclick: props.onCancel,
},
"Cancel"
)
)
)
);
}
function MultiSwitch(props, ...children) {
return m$1(
"span",
{ class: ["multi-switch", props.class] },
m$1("span", {
class: "multi-switch__highlight",
style: {
left: `${
(props.options.indexOf(props.value) / props.options.length) * 100
}%`,
width: `${(1 / props.options.length) * 100}%`,
},
}),
props.options.map((option) =>
m$1(
"span",
{
class: {
"multi-switch__option": true,
"multi-switch__option--selected": option === props.value,
},
onclick: () => option !== props.value && props.onChange(option),
},
option
)
),
...children
);
}
function ResetButton$1(props, ...content) {
return m$1(
Button,
{ class: ["nav-button", props.class], onclick: props.onClick },
m$1("span", { class: "nav-button__content" }, ...content)
);
}
function ResetButton(props, ...content) {
return m$1(
Button,
{ class: "reset-button", onclick: props.onClick },
m$1(
"span",
{ class: "reset-button__content" },
m$1("span", { class: "reset-button__icon" }),
...content
)
);
}
function VirtualScroll(props) {
if (props.items.length === 0) {
return props.root;
}
const { store } = getComponentContext();
function renderContent(root, scrollToIndex) {
if (root.clientWidth === 0) {
return;
}
if (store.itemHeight == null) {
const tempItem = {
...props.items[0],
props: {
...props.items[0].props,
oncreate: null,
onupdate: null,
onrender: null,
},
};
const tempNode = render$1(root, tempItem).firstElementChild;
store.itemHeight = tempNode.getBoundingClientRect().height;
}
const { itemHeight } = store;
const wrapper = render$1(
root,
m$1("div", {
style: {
flex: "none",
height: `${props.items.length * itemHeight}px`,
overflow: "hidden",
position: "relative",
},
})
).firstElementChild;
if (scrollToIndex >= 0) {
root.scrollTop = scrollToIndex * itemHeight;
}
const containerHeight =
document.documentElement.clientHeight -
root.getBoundingClientRect().top;
let focusedIndex = -1;
if (document.activeElement) {
let current = document.activeElement;
while (current && current.parentElement !== wrapper) {
current = current.parentElement;
}
if (current) {
focusedIndex = store.nodesIndices.get(current);
}
}
store.nodesIndices = store.nodesIndices || new WeakMap();
const saveNodeIndex = (node, index) =>
store.nodesIndices.set(node, index);
const items = props.items
.map((item, index) => {
return { item, index };
})
.filter(({ index }) => {
const eTop = index * itemHeight;
const eBottom = (index + 1) * itemHeight;
const rTop = root.scrollTop;
const rBottom = root.scrollTop + containerHeight;
const isTopBoundVisible = eTop >= rTop && eTop <= rBottom;
const isBottomBoundVisible = eBottom >= rTop && eBottom <= rBottom;
return (
isTopBoundVisible || isBottomBoundVisible || focusedIndex === index
);
})
.map(({ item, index }) =>
m$1(
"div",
{
key: index,
onrender: (node) => saveNodeIndex(node, index),
style: {
left: "0",
position: "absolute",
top: `${index * itemHeight}px`,
width: "100%",
},
},
item
)
);
render$1(wrapper, items);
}
let rootNode;
let prevScrollTop;
const rootDidMount = props.root.props.oncreate;
const rootDidUpdate = props.root.props.onupdate;
const rootDidRender = props.root.props.onrender;
return {
...props.root,
props: {
...props.root.props,
oncreate: rootDidMount,
onupdate: rootDidUpdate,
onrender: (node) => {
rootNode = node;
rootDidRender && rootDidRender(rootNode);
renderContent(
rootNode,
isNaN(props.scrollToIndex) ? -1 : props.scrollToIndex
);
},
onscroll: () => {
if (rootNode.scrollTop === prevScrollTop) {
return;
}
prevScrollTop = rootNode.scrollTop;
renderContent(rootNode, -1);
},
},
children: [],
};
}
function Select(props) {
const { state, setState } = useState({
isExpanded: false,
focusedIndex: null,
});
const values = Object.keys(props.options);
const { store } = getComponentContext();
const valueNodes = store.valueNodes || (store.valueNodes = new Map());
const nodesValues =
store.nodesValues || (store.nodesValues = new WeakMap());
function onRender(node) {
store.rootNode = node;
}
function onOuterClick(e) {
const r = store.rootNode.getBoundingClientRect();
if (
e.clientX < r.left ||
e.clientX > r.right ||
e.clientY < r.top ||
e.clientY > r.bottom
) {
window.removeEventListener("click", onOuterClick);
collapseList();
}
}
function onTextInput(e) {
const text = e.target.value.toLowerCase().trim();
expandList();
values.some((value) => {
if (value.toLowerCase().indexOf(text) === 0) {
scrollToValue(value);
return true;
}
});
}
function onKeyPress(e) {
const input = e.target;
if (e.key === "Enter") {
const value = input.value;
input.blur();
collapseList();
props.onChange(value);
}
}
function scrollToValue(value) {
setState({ focusedIndex: values.indexOf(value) });
}
function onExpandClick() {
if (state.isExpanded) {
collapseList();
} else {
expandList();
}
}
function expandList() {
setState({ isExpanded: true });
scrollToValue(props.value);
window.addEventListener("click", onOuterClick);
}
function collapseList() {
setState({ isExpanded: false });
}
function onSelectOption(e) {
let current = e.target;
while (current && !nodesValues.has(current)) {
current = current.parentElement;
}
if (current) {
const value = nodesValues.get(current);
props.onChange(value);
}
collapseList();
}
function saveValueNode(value, domNode) {
valueNodes.set(value, domNode);
nodesValues.set(domNode, value);
}
function removeValueNode(value) {
const el = valueNodes.get(value);
valueNodes.delete(value);
nodesValues.delete(el);
}
return m$1(
"span",
{
class: ["select", state.isExpanded && "select--expanded", props.class],
onrender: onRender,
},
m$1(
"span",
{ class: "select__line" },
m$1(TextBox, {
class: "select__textbox",
value: props.value,
oninput: onTextInput,
onkeypress: onKeyPress,
}),
m$1(
Button,
{ class: "select__expand", onclick: onExpandClick },
m$1("span", { class: "select__expand__icon" })
)
),
m$1(VirtualScroll, {
root: m$1("span", {
class: {
select__list: true,
"select__list--expanded": state.isExpanded,
"select__list--short": Object.keys(props.options).length <= 7,
},
onclick: onSelectOption,
}),
items: Object.entries(props.options).map(([value, content]) =>
m$1(
"span",
{
class: "select__option",
data: value,
onrender: (domNode) => saveValueNode(value, domNode),
onremove: () => removeValueNode(value),
},
content
)
),
scrollToIndex: state.focusedIndex,
})
);
}
var Select$1 = withState(Select);
function ShortcutLink(props) {
const cls = mergeClass("shortcut", props.class);
const shortcut = props.shortcuts[props.commandName];
const shortcutMessage = props.textTemplate(shortcut);
let enteringShortcutInProgress = false;
function startEnteringShortcut(node) {
if (enteringShortcutInProgress) {
return;
}
enteringShortcutInProgress = true;
const initialText = node.textContent;
node.textContent = "...⌨";
function onKeyDown(e) {
e.preventDefault();
const ctrl = e.ctrlKey;
const alt = e.altKey;
const command = e.metaKey;
const shift = e.shiftKey;
let key = null;
if (e.code.startsWith("Key")) {
key = e.code.substring(3);
} else if (e.code.startsWith("Digit")) {
key = e.code.substring(5);
}
const shortcut = `${
ctrl ? "Ctrl+" : alt ? "Alt+" : command ? "Command+" : ""
}${shift ? "Shift+" : ""}${key ? key : ""}`;
node.textContent = shortcut;
if ((ctrl || alt || command || shift) && key) {
removeListeners();
props.onSetShortcut(shortcut);
node.blur();
setTimeout(() => {
enteringShortcutInProgress = false;
node.classList.remove("shortcut--edit");
node.textContent = props.textTemplate(shortcut);
}, 500);
}
}
function onBlur() {
removeListeners();
node.classList.remove("shortcut--edit");
node.textContent = initialText;
enteringShortcutInProgress = false;
}
function removeListeners() {
window.removeEventListener("keydown", onKeyDown, true);
window.removeEventListener("blur", onBlur, true);
}
window.addEventListener("keydown", onKeyDown, true);
window.addEventListener("blur", onBlur, true);
node.classList.add("shortcut--edit");
}
function onClick(e) {
e.preventDefault();
if (isFirefox) {
startEnteringShortcut(e.target);
return;
}
if (isEdge) {
chrome.tabs.create({
url: `edge://extensions/shortcuts`,
active: true,
});
return;
}
chrome.tabs.create({
url: `chrome://extensions/configureCommands#command-${chrome.runtime.id}-${props.commandName}`,
active: true,
});
}
function onRender(node) {
node.textContent = shortcutMessage;
}
return m$1("a", {
class: cls,
href: "#",
onclick: onClick,
oncreate: onRender,
});
}
function throttle(callback) {
let pending = false;
let frameId = null;
let lastArgs;
const throttled = (...args) => {
lastArgs = args;
if (frameId) {
pending = true;
} else {
callback(...lastArgs);
frameId = requestAnimationFrame(() => {
frameId = null;
if (pending) {
callback(...lastArgs);
pending = false;
}
});
}
};
const cancel = () => {
cancelAnimationFrame(frameId);
pending = false;
frameId = null;
};
return Object.assign(throttled, { cancel });
}
function stickToStep(x, step) {
const s = Math.round(x / step) * step;
const exp = Math.floor(Math.log10(step));
if (exp >= 0) {
const m = 10 ** exp;
return Math.round(s / m) * m;
}
const m = 10 ** -exp;
return Math.round(s * m) / m;
}
function Slider(props) {
const context = getComponentContext();
const store = context.store;
store.activeProps = props;
function onRootCreate(rootNode) {
rootNode.addEventListener("touchstart", onPointerDown, { passive: true });
}
function saveTrackNode(el) {
store.trackNode = el;
}
function getTrackNode() {
return store.trackNode;
}
function saveThumbNode(el) {
store.thumbNode = el;
}
function getThumbNode() {
return store.thumbNode;
}
function onPointerDown(startEvt) {
if (store.isActive) {
return;
}
const { getClientX, pointerMoveEvent, pointerUpEvent } = (() => {
const isTouchEvent =
typeof TouchEvent !== "undefined" && startEvt instanceof TouchEvent;
const touchId = isTouchEvent
? startEvt.changedTouches[0].identifier
: null;
function getTouch(e) {
const find = (touches) =>
Array.from(touches).find((t) => t.identifier === touchId);
return find(e.changedTouches) || find(e.touches);
}
function getClientX(e) {
const { clientX } = isTouchEvent ? getTouch(e) : e;
return clientX;
}
const pointerMoveEvent = isTouchEvent ? "touchmove" : "mousemove";
const pointerUpEvent = isTouchEvent ? "touchend" : "mouseup";
return { getClientX, pointerMoveEvent, pointerUpEvent };
})();
const dx = (() => {
const thumbRect = getThumbNode().getBoundingClientRect();
const startClientX = getClientX(startEvt);
const isThumbPressed =
startClientX >= thumbRect.left && startClientX <= thumbRect.right;
return isThumbPressed
? thumbRect.left + thumbRect.width / 2 - startClientX
: 0;
})();
function getEventValue(e) {
const { min, max } = store.activeProps;
const clientX = getClientX(e);
const rect = getTrackNode().getBoundingClientRect();
const scaled = scale(clientX + dx, rect.left, rect.right, min, max);
const clamped = clamp(scaled, min, max);
return clamped;
}
function onPointerMove(e) {
const value = getEventValue(e);
store.activeValue = value;
context.refresh();
}
function onPointerUp(e) {
unsubscribe();
const value = getEventValue(e);
store.isActive = false;
context.refresh();
store.activeValue = null;
const { onChange, step } = store.activeProps;
onChange(stickToStep(value, step));
}
function onKeyPress(e) {
if (e.key === "Escape") {
unsubscribe();
store.isActive = false;
store.activeValue = null;
context.refresh();
}
}
function subscribe() {
window.addEventListener(pointerMoveEvent, onPointerMove, {
passive: true,
});
window.addEventListener(pointerUpEvent, onPointerUp, { passive: true });
window.addEventListener("keypress", onKeyPress);
}
function unsubscribe() {
window.removeEventListener(pointerMoveEvent, onPointerMove);
window.removeEventListener(pointerUpEvent, onPointerUp);
window.removeEventListener("keypress", onKeyPress);
}
subscribe();
store.isActive = true;
store.activeValue = getEventValue(startEvt);
context.refresh();
}
function getValue() {
return store.activeValue == null ? props.value : store.activeValue;
}
const percent = scale(getValue(), props.min, props.max, 0, 100);
const thumbPositionStyleValue = `${percent}%`;
const shouldFlipText = percent > 75;
const formattedValue = props.formatValue(
stickToStep(getValue(), props.step)
);
function scaleWheelDelta(delta) {
return scale(delta, 0, -1000, 0, props.max - props.min);
}
const refreshOnWheel = throttle(() => {
store.activeValue = stickToStep(store.wheelValue, props.step);
store.wheelTimeoutId = setTimeout(() => {
const { onChange } = store.activeProps;
onChange(store.activeValue);
store.isActive = false;
store.activeValue = null;
store.wheelValue = null;
}, 400);
context.refresh();
});
function onWheel(event) {
if (store.wheelValue == null) {
store.wheelValue = getValue();
}
store.isActive = true;
clearTimeout(store.wheelTimeoutId);
event.preventDefault();
const accumulatedValue = store.wheelValue + scaleWheelDelta(event.deltaY);
store.wheelValue = clamp(accumulatedValue, props.min, props.max);
refreshOnWheel();
}
return m$1(
"span",
{
class: { slider: true, "slider--active": store.isActive },
oncreate: onRootCreate,
onmousedown: onPointerDown,
onwheel: onWheel,
},
m$1(
"span",
{ class: "slider__track", oncreate: saveTrackNode },
m$1("span", {
class: "slider__track__fill",
style: { width: thumbPositionStyleValue },
})
),
m$1(
"span",
{ class: "slider__thumb-wrapper" },
m$1(
"span",
{
class: "slider__thumb",
oncreate: saveThumbNode,
style: { left: thumbPositionStyleValue },
},
m$1(
"span",
{
class: {
slider__thumb__value: true,
"slider__thumb__value--flip": shouldFlipText,
},
},
formattedValue
)
)
)
);
}
function Tab({ isActive }, ...children) {
const tabCls = {
"tab-panel__tab": true,
"tab-panel__tab--active": isActive,
};
return m$1("div", { class: tabCls }, children);
}
function TabPanel(props) {
const tabsNames = Object.keys(props.tabs);
function isActiveTab(name, index) {
return name == null ? index === 0 : name === props.activeTab;
}
const buttons = tabsNames.map((name, i) => {
const btnCls = {
"tab-panel__button": true,
"tab-panel__button--active": isActiveTab(name, i),
};
return m$1(
Button,
{ class: btnCls, onclick: () => props.onSwitchTab(name) },
props.tabLabels[name]
);
});
const tabs = tabsNames.map((name, i) =>
m$1(Tab, { isActive: isActiveTab(name, i) }, props.tabs[name])
);
return m$1(
"div",
{ class: "tab-panel" },
m$1("div", { class: "tab-panel__tabs" }, tabs)
);
}
function TextList(props) {
const context = getComponentContext();
context.store.indices = context.store.indices || new WeakMap();
function onTextChange(e) {
const index = context.store.indices.get(e.target);
const values = props.values.slice();
const value = e.target.value.trim();
if (values.includes(value)) {
return;
}
if (!value) {
values.splice(index, 1);
} else if (index === values.length) {
values.push(value);
} else {
values.splice(index, 1, value);
}
props.onChange(values);
}
function createTextBox(text, index) {
const saveIndex = (node) => context.store.indices.set(node, index);
return m$1(TextBox, {
class: "text-list__textbox",
value: text,
onrender: saveIndex,
placeholder: props.placeholder,
});
}
let shouldFocus = false;
const node = context.node;
const prevProps = context.prev ? context.prev.props : null;
if (
node &&
props.isFocused &&
(!prevProps ||
!prevProps.isFocused ||
prevProps.values.length < props.values.length)
) {
focusLastNode();
}
function didMount(node) {
context.store.node = node;
if (props.isFocused) {
focusLastNode();
}
}
function focusLastNode() {
const node = context.store.node;
shouldFocus = true;
requestAnimationFrame(() => {
const inputs = node.querySelectorAll(".text-list__textbox");
const last = inputs.item(inputs.length - 1);
last.focus();
});
}
return m$1(VirtualScroll, {
root: m$1("div", {
class: ["text-list", props.class],
onchange: onTextChange,
oncreate: didMount,
}),
items: props.values
.map(createTextBox)
.concat(createTextBox("", props.values.length)),
scrollToIndex: shouldFocus ? props.values.length : -1,
});
}
function getLocalMessage(messageName) {
return chrome.i18n.getMessage(messageName);
}
function getUILanguage() {
let code;
if (
"i18n" in chrome &&
"getUILanguage" in chrome.i18n &&
typeof chrome.i18n.getUILanguage === "function"
) {
code = chrome.i18n.getUILanguage();
} else {
code = navigator.language.split("-")[0];
}
if (code.endsWith("-mac")) {
return code.substring(0, code.length - 4);
}
return code;
}
function parseTime($time) {
const parts = $time.split(":").slice(0, 2);
const lowercased = $time.trim().toLowerCase();
const isAM = lowercased.endsWith("am") || lowercased.endsWith("a.m.");
const isPM = lowercased.endsWith("pm") || lowercased.endsWith("p.m.");
let hours = parts.length > 0 ? parseInt(parts[0]) : 0;
if (isNaN(hours) || hours > 23) {
hours = 0;
}
if (isAM && hours === 12) {
hours = 0;
}
if (isPM && hours < 12) {
hours += 12;
}
let minutes = parts.length > 1 ? parseInt(parts[1]) : 0;
if (isNaN(minutes) || minutes > 59) {
minutes = 0;
}
return [hours, minutes];
}
function isInTimeIntervalUTC(time0, time1, timestamp) {
if (time1 < time0) {
return timestamp <= time1 || time0 <= timestamp;
}
return time0 < timestamp && timestamp < time1;
}
function getDuration(time) {
let duration = 0;
if (time.seconds) {
duration += time.seconds * 1000;
}
if (time.minutes) {
duration += time.minutes * 60 * 1000;
}
if (time.hours) {
duration += time.hours * 60 * 60 * 1000;
}
if (time.days) {
duration += time.days * 24 * 60 * 60 * 1000;
}
return duration;
}
function getSunsetSunriseUTCTime(latitude, longitude, date) {
const dec31 = Date.UTC(date.getUTCFullYear(), 0, 0, 0, 0, 0, 0);
const oneDay = getDuration({ days: 1 });
const dayOfYear = Math.floor((date.getTime() - dec31) / oneDay);
const zenith = 90.83333333333333;
const D2R = Math.PI / 180;
const R2D = 180 / Math.PI;
const lnHour = longitude / 15;
function getTime(isSunrise) {
const t = dayOfYear + ((isSunrise ? 6 : 18) - lnHour) / 24;
const M = 0.9856 * t - 3.289;
let L =
M + 1.916 * Math.sin(M * D2R) + 0.02 * Math.sin(2 * M * D2R) + 282.634;
if (L > 360) {
L -= 360;
} else if (L < 0) {
L += 360;
}
let RA = R2D * Math.atan(0.91764 * Math.tan(L * D2R));
if (RA > 360) {
RA -= 360;
} else if (RA < 0) {
RA += 360;
}
const Lquadrant = Math.floor(L / 90) * 90;
const RAquadrant = Math.floor(RA / 90) * 90;
RA += Lquadrant - RAquadrant;
RA /= 15;
const sinDec = 0.39782 * Math.sin(L * D2R);
const cosDec = Math.cos(Math.asin(sinDec));
const cosH =
(Math.cos(zenith * D2R) - sinDec * Math.sin(latitude * D2R)) /
(cosDec * Math.cos(latitude * D2R));
if (cosH > 1) {
return {
alwaysDay: false,
alwaysNight: true,
time: 0,
};
} else if (cosH < -1) {
return {
alwaysDay: true,
alwaysNight: false,
time: 0,
};
}
const H =
(isSunrise ? 360 - R2D * Math.acos(cosH) : R2D * Math.acos(cosH)) / 15;
const T = H + RA - 0.06571 * t - 6.622;
let UT = T - lnHour;
if (UT > 24) {
UT -= 24;
} else if (UT < 0) {
UT += 24;
}
return {
alwaysDay: false,
alwaysNight: false,
time: Math.round(UT * getDuration({ hours: 1 })),
};
}
const sunriseTime = getTime(true);
const sunsetTime = getTime(false);
if (sunriseTime.alwaysDay || sunsetTime.alwaysDay) {
return {
alwaysDay: true,
};
} else if (sunriseTime.alwaysNight || sunsetTime.alwaysNight) {
return {
alwaysNight: true,
};
}
return {
sunriseTime: sunriseTime.time,
sunsetTime: sunsetTime.time,
};
}
function isNightAtLocation(latitude, longitude, date = new Date()) {
const time = getSunsetSunriseUTCTime(latitude, longitude, date);
if (time.alwaysDay) {
return false;
} else if (time.alwaysNight) {
return true;
}
const sunriseTime = time.sunriseTime;
const sunsetTime = time.sunsetTime;
const currentTime =
date.getUTCHours() * getDuration({ hours: 1 }) +
date.getUTCMinutes() * getDuration({ minutes: 1 }) +
date.getUTCSeconds() * getDuration({ seconds: 1 }) +
date.getUTCMilliseconds();
return isInTimeIntervalUTC(sunsetTime, sunriseTime, currentTime);
}
const is12H = new Date().toLocaleTimeString(getUILanguage()).endsWith("M");
function toLocaleTime($time) {
const [hours, minutes] = parseTime($time);
const mm = `${minutes < 10 ? "0" : ""}${minutes}`;
if (is12H) {
const h = hours === 0 ? "12" : hours > 12 ? hours - 12 : hours;
return `${h}:${mm}${hours < 12 ? "AM" : "PM"}`;
}
return `${hours}:${mm}`;
}
function to24HTime($time) {
const [hours, minutes] = parseTime($time);
const mm = `${minutes < 10 ? "0" : ""}${minutes}`;
return `${hours}:${mm}`;
}
function TimeRangePicker(props) {
function onStartTimeChange($startTime) {
props.onChange([to24HTime($startTime), props.endTime]);
}
function onEndTimeChange($endTime) {
props.onChange([props.startTime, to24HTime($endTime)]);
}
function setStartTime(node) {
node.value = toLocaleTime(props.startTime);
}
function setEndTime(node) {
node.value = toLocaleTime(props.endTime);
}
return m$1(
"span",
{ class: "time-range-picker" },
m$1(TextBox, {
class: "time-range-picker__input time-range-picker__input--start",
placeholder: toLocaleTime("18:00"),
onrender: setStartTime,
onchange: (e) => onStartTimeChange(e.target.value),
onkeypress: (e) => {
if (e.key === "Enter") {
const input = e.target;
input.blur();
onStartTimeChange(input.value);
}
},
}),
m$1(TextBox, {
class: "time-range-picker__input time-range-picker__input--end",
placeholder: toLocaleTime("9:00"),
onrender: setEndTime,
onchange: (e) => onEndTimeChange(e.target.value),
onkeypress: (e) => {
if (e.key === "Enter") {
const input = e.target;
input.blur();
onEndTimeChange(input.value);
}
},
})
);
}
function Toggle(props) {
const { checked, onChange } = props;
const cls = ["toggle", checked ? "toggle--checked" : null, props.class];
const clsOn = {
toggle__btn: true,
toggle__on: true,
"toggle__btn--active": checked,
};
const clsOff = {
toggle__btn: true,
toggle__off: true,
"toggle__btn--active": !checked,
};
return m$1(
"span",
{ class: cls },
m$1(
"span",
{
class: clsOn,
onclick: onChange ? () => !checked && onChange(true) : null,
},
props.labelOn
),
m$1(
"span",
{
class: clsOff,
onclick: onChange ? () => checked && onChange(false) : null,
},
props.labelOff
)
);
}
function Track(props) {
const valueStyle = { width: `${props.value * 100}%` };
const isClickable = props.onChange != null;
function onMouseDown(e) {
const targetNode = e.currentTarget;
const valueNode = targetNode.firstElementChild;
targetNode.classList.add("track--active");
function getValue(clientX) {
const rect = targetNode.getBoundingClientRect();
return (clientX - rect.left) / rect.width;
}
function setWidth(value) {
valueNode.style.width = `${value * 100}%`;
}
function onMouseMove(e) {
const value = getValue(e.clientX);
setWidth(value);
}
function onMouseUp(e) {
const value = getValue(e.clientX);
props.onChange(value);
cleanup();
}
function onKeyPress(e) {
if (e.key === "Escape") {
setWidth(props.value);
cleanup();
}
}
function cleanup() {
window.removeEventListener("mousemove", onMouseMove);
window.removeEventListener("mouseup", onMouseUp);
window.removeEventListener("keypress", onKeyPress);
targetNode.classList.remove("track--active");
}
window.addEventListener("mousemove", onMouseMove);
window.addEventListener("mouseup", onMouseUp);
window.addEventListener("keypress", onKeyPress);
const value = getValue(e.clientX);
setWidth(value);
}
return m$1(
"span",
{
class: {
track: true,
"track--clickable": Boolean(props.onChange),
},
onmousedown: isClickable ? onMouseDown : null,
},
m$1("span", { class: "track__value", style: valueStyle }),
m$1("label", { class: "track__label" }, props.label)
);
}
function UpDown(props) {
const buttonDownCls = {
updown__button: true,
"updown__button--disabled": props.value === props.min,
};
const buttonUpCls = {
updown__button: true,
"updown__button--disabled": props.value === props.max,
};
function normalize(x) {
const s = Math.round(x / props.step) * props.step;
const exp = Math.floor(Math.log10(props.step));
if (exp >= 0) {
const m = 10 ** exp;
return Math.round(s / m) * m;
}
const m = 10 ** -exp;
return Math.round(s * m) / m;
}
function clamp(x) {
return Math.max(props.min, Math.min(props.max, x));
}
function onButtonDownClick() {
props.onChange(clamp(normalize(props.value - props.step)));
}
function onButtonUpClick() {
props.onChange(clamp(normalize(props.value + props.step)));
}
function onTrackValueChange(trackValue) {
props.onChange(
clamp(normalize(trackValue * (props.max - props.min) + props.min))
);
}
const trackValue = (props.value - props.min) / (props.max - props.min);
const valueText =
props.value === props.default
? getLocalMessage("off").toLocaleLowerCase()
: props.value > props.default
? `+${normalize(props.value - props.default)}`
: `-${normalize(props.default - props.value)}`;
return m$1(
"div",
{ class: "updown" },
m$1(
"div",
{ class: "updown__line" },
m$1(
Button,
{ class: buttonDownCls, onclick: onButtonDownClick },
m$1("span", { class: "updown__icon updown__icon-down" })
),
m$1(Track, {
value: trackValue,
label: props.name,
onChange: onTrackValueChange,
}),
m$1(
Button,
{ class: buttonUpCls, onclick: onButtonUpClick },
m$1("span", { class: "updown__icon updown__icon-up" })
)
),
m$1("label", { class: "updown__value-text" }, valueText)
);
}
function isIPV6(url) {
const openingBracketIndex = url.indexOf("[");
if (openingBracketIndex < 0) {
return false;
}
const queryIndex = url.indexOf("?");
if (queryIndex >= 0 && openingBracketIndex > queryIndex) {
return false;
}
return true;
}
const ipV6HostRegex = /\[.*?\](\:\d+)?/;
function compareIPV6(firstURL, secondURL) {
const firstHost = firstURL.match(ipV6HostRegex)[0];
const secondHost = secondURL.match(ipV6HostRegex)[0];
return firstHost === secondHost;
}
function getURLHostOrProtocol($url) {
const url = new URL($url);
if (url.host) {
return url.host;
} else if (url.protocol === "file:") {
return url.pathname;
}
return url.protocol;
}
function isURLInList(url, list) {
for (let i = 0; i < list.length; i++) {
if (isURLMatched(url, list[i])) {
return true;
}
}
return false;
}
function isURLMatched(url, urlTemplate) {
const isFirstIPV6 = isIPV6(url);
const isSecondIPV6 = isIPV6(urlTemplate);
if (isFirstIPV6 && isSecondIPV6) {
return compareIPV6(url, urlTemplate);
} else if (!isFirstIPV6 && !isSecondIPV6) {
const regex = createUrlRegex(urlTemplate);
return Boolean(url.match(regex));
}
return false;
}
function createUrlRegex(urlTemplate) {
urlTemplate = urlTemplate.trim();
const exactBeginning = urlTemplate[0] === "^";
const exactEnding = urlTemplate[urlTemplate.length - 1] === "$";
urlTemplate = urlTemplate
.replace(/^\^/, "")
.replace(/\$$/, "")
.replace(/^.*?\/{2,3}/, "")
.replace(/\?.*$/, "")
.replace(/\/$/, "");
let slashIndex;
let beforeSlash;
let afterSlash;
if ((slashIndex = urlTemplate.indexOf("/")) >= 0) {
beforeSlash = urlTemplate.substring(0, slashIndex);
afterSlash = urlTemplate.replace(/\$/g, "").substring(slashIndex);
} else {
beforeSlash = urlTemplate.replace(/\$/g, "");
}
let result = exactBeginning
? "^(.*?\\:\\/{2,3})?"
: "^(.*?\\:\\/{2,3})?([^/]*?\\.)?";
const hostParts = beforeSlash.split(".");
result += "(";
for (let i = 0; i < hostParts.length; i++) {
if (hostParts[i] === "*") {
hostParts[i] = "[^\\.\\/]+?";
}
}
result += hostParts.join("\\.");
result += ")";
if (afterSlash) {
result += "(";
result += afterSlash.replace("/", "\\/");
result += ")";
}
result += exactEnding ? "(\\/?(\\?[^/]*?)?)$" : "(\\/?.*?)$";
return new RegExp(result, "i");
}
function isPDF(url) {
if (url.includes(".pdf")) {
if (url.includes("?")) {
url = url.substring(0, url.lastIndexOf("?"));
}
if (url.includes("#")) {
url = url.substring(0, url.lastIndexOf("#"));
}
if (
(url.match(/(wikipedia|wikimedia).org/i) &&
url.match(
/(wikipedia|wikimedia)\.org\/.*\/[a-z]+\:[^\:\/]+\.pdf/i
)) ||
(url.match(/timetravel\.mementoweb\.org\/reconstruct/i) &&
url.match(/\.pdf$/i))
) {
return false;
}
if (url.endsWith(".pdf")) {
for (let i = url.length; i > 0; i--) {
if (url[i] === "=") {
return false;
} else if (url[i] === "/") {
return true;
}
}
} else {
return false;
}
}
return false;
}
function isURLEnabled(
url,
userSettings,
{ isProtected, isInDarkList, isDarkThemeDetected }
) {
if (isProtected && !userSettings.enableForProtectedPages) {
return false;
}
if (isThunderbird) {
return true;
}
if (isPDF(url)) {
return userSettings.enableForPDF;
}
const isURLInUserList = isURLInList(url, userSettings.siteList);
const isURLInEnabledList = isURLInList(url, userSettings.siteListEnabled);
if (userSettings.applyToListedOnly) {
return isURLInEnabledList || isURLInUserList;
}
if (isURLInEnabledList) {
return true;
}
if (isInDarkList || (userSettings.detectDarkTheme && isDarkThemeDetected)) {
return false;
}
return !isURLInUserList;
}
function CustomSettingsToggle({ data, actions }) {
const tab = data.activeTab;
const host = getURLHostOrProtocol(tab.url);
const isCustom = data.settings.customThemes.some(({ url }) =>
isURLInList(tab.url, url)
);
const urlText = host
.split(".")
.reduce(
(elements, part, i) =>
elements.concat(m$1("wbr", null), `${i > 0 ? "." : ""}${part}`),
[]
);
}
function ModeToggle({ mode, onChange }) {
return m$1(
"div",
{ class: "mode-toggle" },
m$1(
"div",
{ class: "mode-toggle__line" },
m$1(
Button,
{
class: { "mode-toggle__button--active": mode === 1 },
onclick: () => onChange(1),
},
m$1("span", { class: "icon icon--dark-mode" })
),
m$1(Toggle, {
checked: mode === 1,
labelOn: getLocalMessage("dark"),
labelOff: getLocalMessage("light"),
onChange: (checked) => onChange(checked ? 1 : 0),
}),
m$1(
Button,
{
class: { "mode-toggle__button--active": mode === 0 },
onclick: () => onChange(0),
},
m$1("span", { class: "icon icon--light-mode" })
)
),
m$1("label", { class: "mode-toggle__label" }, getLocalMessage("mode"))
);
}
function FilterSettings({ data, actions }) {
const custom = data.settings.customThemes.find(({ url }) =>
isURLInList(data.activeTab.url, url)
);
const filterConfig = custom ? custom.theme : data.settings.theme;
function setConfig(config) {
if (custom) {
custom.theme = { ...custom.theme, ...config };
actions.changeSettings({ customThemes: data.settings.customThemes });
} else {
actions.setTheme(config);
}
}
const brightness = m$1(UpDown, {
value: filterConfig.brightness,
min: 50,
max: 150,
step: 5,
default: 100,
name: getLocalMessage("brightness"),
onChange: (value) => setConfig({ brightness: value }),
});
const contrast = m$1(UpDown, {
value: filterConfig.contrast,
min: 50,
max: 150,
step: 5,
default: 100,
name: getLocalMessage("contrast"),
onChange: (value) => setConfig({ contrast: value }),
});
const grayscale = m$1(UpDown, {
value: filterConfig.grayscale,
min: 0,
max: 100,
step: 5,
default: 0,
name: getLocalMessage("grayscale"),
onChange: (value) => setConfig({ grayscale: value }),
});
const sepia = m$1(UpDown, {
value: filterConfig.sepia,
min: 0,
max: 100,
step: 5,
default: 0,
name: getLocalMessage("sepia"),
onChange: (value) => setConfig({ sepia: value }),
});
return m$1(
"section",
{ class: "filter-settings" },
m$1(ModeToggle, {
mode: filterConfig.mode,
onChange: (mode) => setConfig({ mode }),
}),
brightness,
contrast,
sepia,
grayscale,
m$1(CustomSettingsToggle, { data: data, actions: actions })
);
}
function SunMoonIcon({ date, latitude, longitude }) {
if (latitude == null || longitude == null) {
return m$1(
"svg",
{ viewBox: "0 0 16 16" },
m$1(
"text",
{
fill: "white",
"font-size": "16",
"font-weight": "bold",
"text-anchor": "middle",
x: "8",
y: "14",
},
"?"
)
);
}
if (isNightAtLocation(latitude, longitude, date)) {
return m$1(
"svg",
{ viewBox: "0 0 16 16" },
m$1("path", {
fill: "white",
stroke: "none",
d: "M 6 3 Q 10 8 6 13 Q 12 13 12 8 Q 12 3 6 3",
})
);
}
return m$1(
"svg",
{ viewBox: "0 0 16 16" },
m$1("circle", {
fill: "white",
stroke: "none",
cx: "8",
cy: "8",
r: "3",
}),
m$1(
"g",
{
fill: "none",
stroke: "white",
"stroke-linecap": "round",
"stroke-width": "1.5",
},
...Array.from({ length: 8 }).map((_, i) => {
const cx = 8;
const cy = 8;
const angle = (i * Math.PI) / 4 + Math.PI / 8;
const pt = [5, 6].map((l) => [
cx + l * Math.cos(angle),
cy + l * Math.sin(angle),
]);
return m$1("line", {
x1: pt[0][0],
y1: pt[0][1],
x2: pt[1][0],
y2: pt[1][1],
});
})
)
);
}
function SystemIcon() {
return m$1(
"svg",
{ viewBox: "0 0 16 16" },
m$1("path", {
fill: "white",
stroke: "none",
d: "M3,3 h10 v7 h-3 v2 h1 v1 h-6 v-1 h1 v-2 h-3 z M4.5,4.5 v4 h7 v-4 z",
})
);
}
function WatchIcon({ hours, minutes }) {
const cx = 8;
const cy = 8.5;
const lenHour = 3;
const lenMinute = 4;
const clockR = 5.5;
const btnSize = 2;
const btnPad = 1.5;
const ah =
(((hours > 11 ? hours - 12 : hours) + minutes / 60) / 12) * Math.PI * 2;
const am = (minutes / 60) * Math.PI * 2;
const hx = cx + lenHour * Math.sin(ah);
const hy = cy - lenHour * Math.cos(ah);
const mx = cx + lenMinute * Math.sin(am);
const my = cy - lenMinute * Math.cos(am);
return m$1(
"svg",
{ viewBox: "0 0 16 16" },
m$1("circle", {
fill: "none",
stroke: "white",
"stroke-width": "1.5",
cx: cx,
cy: cy,
r: clockR,
}),
m$1("line", {
stroke: "white",
"stroke-width": "1.5",
x1: cx,
y1: cy,
x2: hx,
y2: hy,
}),
m$1("line", {
stroke: "white",
"stroke-width": "1.5",
opacity: "0.67",
x1: cx,
y1: cy,
x2: mx,
y2: my,
}),
[30, -30].map((angle) => {
return m$1("path", {
fill: "white",
transform: `rotate(${angle})`,
"transform-origin": `${cx} ${cy}`,
d: `M${cx - btnSize},${
cy - clockR - btnPad
} a${btnSize},${btnSize} 0 0 1 ${2 * btnSize},0 z`,
});
})
);
}
function CheckmarkIcon({ isChecked }) {
return m$1(
"svg",
{ viewBox: "0 0 8 8" },
m$1("path", {
d: isChecked
? "M1,4 l2,2 l4,-4 v1 l-4,4 l-2,-2 Z"
: "M2,2 l4,4 v1 l-4,-4 Z M2,6 l4,-4 v1 l-4,4 Z",
})
);
}
function SiteToggleButton({ data, actions }) {
const tab = data.activeTab;
function onSiteToggleClick() {
if (pdf) {
actions.changeSettings({ enableForPDF: !data.settings.enableForPDF });
} else {
actions.toggleActiveTab();
}
}
const pdf = isPDF(tab.url);
const toggleHasEffect =
data.settings.enableForProtectedPages ||
(!tab.isProtected && !pdf) ||
tab.isInjected;
const isSiteEnabled =
isURLEnabled(tab.url, data.settings, tab) && tab.isInjected;
const host = getURLHostOrProtocol(tab.url);
const urlText = host
.split(".")
.reduce(
(elements, part, i) =>
elements.concat(m$1("wbr", null), `${i > 0 ? "." : ""}${part}`),
[]
);
return m$1(
Button,
{
class: {
"site-toggle": true,
"site-toggle--active": isSiteEnabled,
"site-toggle--disabled": !toggleHasEffect || isThunderbird,
},
onclick: onSiteToggleClick,
},
m$1(
"span",
{ class: "site-toggle__mark" },
m$1(CheckmarkIcon, { isChecked: isSiteEnabled })
),
" ",
m$1("span", { class: "site-toggle__url" }, pdf ? "PDF" : urlText)
);
}
function MoreToggleSettings({ data, actions, isExpanded, onClose }) {
const isSystemAutomation = data.settings.automation === "system";
const locationSettings = data.settings.location;
const values = {
latitude: {
min: -90,
max: 90,
},
longitude: {
min: -180,
max: 180,
},
};
function getLocationString(location) {
if (location == null) {
return "";
}
return `${location}°`;
}
function locationChanged(inputElement, newValue, type) {
if (newValue.trim() === "") {
inputElement.value = "";
actions.changeSettings({
location: {
...locationSettings,
[type]: null,
},
});
return;
}
const min = values[type].min;
const max = values[type].max;
newValue = newValue.replace(",", ".").replace("°", "");
let num = Number(newValue);
if (isNaN(num)) {
num = 0;
} else if (num > max) {
num = max;
} else if (num < min) {
num = min;
}
inputElement.value = getLocationString(num);
actions.changeSettings({
location: {
...locationSettings,
[type]: num,
},
});
}
return m$1(
"div",
{
class: {
"header__app-toggle__more-settings": true,
"header__app-toggle__more-settings--expanded": isExpanded,
},
},
m$1(
"div",
{ class: "header__app-toggle__more-settings__top" },
m$1(
"span",
{ class: "header__app-toggle__more-settings__top__text" },
getLocalMessage("automation")
),
m$1(
"span",
{
class: "header__app-toggle__more-settings__top__close",
role: "button",
onclick: onClose,
},
"\u2715"
)
),
m$1(
"div",
{ class: "header__app-toggle__more-settings__content" },
m$1(
"div",
{ class: "header__app-toggle__more-settings__line" },
m$1(CheckBox, {
checked: data.settings.automation === "time",
onchange: (e) =>
actions.changeSettings({
automation: e.target.checked ? "time" : "",
}),
}),
m$1(TimeRangePicker, {
startTime: data.settings.time.activation,
endTime: data.settings.time.deactivation,
onChange: ([start, end]) =>
actions.changeSettings({
time: { activation: start, deactivation: end },
}),
})
),
m$1(
"p",
{ class: "header__app-toggle__more-settings__description" },
getLocalMessage("set_active_hours")
),
m$1(
"div",
{
class:
"header__app-toggle__more-settings__line header__app-toggle__more-settings__location",
},
m$1(CheckBox, {
checked: data.settings.automation === "location",
onchange: (e) =>
actions.changeSettings({
automation: e.target.checked ? "location" : "",
}),
}),
m$1(TextBox, {
class: "header__app-toggle__more-settings__location__latitude",
placeholder: getLocalMessage("latitude"),
onchange: (e) =>
locationChanged(e.target, e.target.value, "latitude"),
oncreate: (node) =>
(node.value = getLocationString(locationSettings.latitude)),
onkeypress: (e) => {
if (e.key === "Enter") {
e.target.blur();
}
},
}),
m$1(TextBox, {
class: "header__app-toggle__more-settings__location__longitude",
placeholder: getLocalMessage("longitude"),
onchange: (e) =>
locationChanged(e.target, e.target.value, "longitude"),
oncreate: (node) =>
(node.value = getLocationString(locationSettings.longitude)),
onkeypress: (e) => {
if (e.key === "Enter") {
e.target.blur();
}
},
})
),
m$1(
"p",
{ class: "header__app-toggle__more-settings__location-description" },
getLocalMessage("set_location")
),
m$1(
"div",
{
class: [
"header__app-toggle__more-settings__line",
"header__app-toggle__more-settings__system-dark-mode",
],
},
m$1(CheckBox, {
class:
"header__app-toggle__more-settings__system-dark-mode__checkbox",
checked: isSystemAutomation,
onchange: (e) =>
actions.changeSettings({
automation: e.target.checked ? "system" : "",
}),
}),
m$1(
Button,
{
class: {
"header__app-toggle__more-settings__system-dark-mode__button": true,
"header__app-toggle__more-settings__system-dark-mode__button--active":
isSystemAutomation,
},
onclick: () =>
actions.changeSettings({
automation: isSystemAutomation ? "" : "system",
}),
},
getLocalMessage("system_dark_mode")
)
),
m$1(
"p",
{ class: "header__app-toggle__more-settings__description" },
getLocalMessage("system_dark_mode_description")
)
)
);
}
function multiline(...lines) {
return lines.join("\n");
}
function Header({ data, actions, onMoreToggleSettingsClick }) {
function toggleExtension(enabled) {
actions.changeSettings({
enabled,
automation: "",
});
}
const tab = data.activeTab;
const isAutomation = Boolean(data.settings.automation);
const isTimeAutomation = data.settings.automation === "time";
const isLocationAutomation = data.settings.automation === "location";
const now = new Date();
return m$1(
"header",
{ class: "header" },
m$1(
"div",
{ class: "header__control header__site-toggle" },
m$1(SiteToggleButton, { data: data, actions: actions }),
tab.isProtected || !tab.isInjected
? m$1(
"span",
{ class: "header__site-toggle__unable-text" },
getLocalMessage("page_protected")
)
: tab.isInDarkList
? m$1(
"span",
{ class: "header__site-toggle__unable-text" },
getLocalMessage("page_in_dark_list")
)
: m$1(ShortcutLink, {
commandName: "addSite",
shortcuts: data.shortcuts,
textTemplate: (hotkey) =>
getLocalMessage("setup_hotkey_toggle_site"),
onSetShortcut: (shortcut) =>
actions.setShortcut("addSite", shortcut),
})
),
m$1(
"div",
{ class: "header__control header__app-toggle" },
m$1(Toggle, {
checked: data.isEnabled,
labelOn: getLocalMessage("on"),
labelOff: getLocalMessage("off"),
onChange: toggleExtension,
}),
m$1(ShortcutLink, {
commandName: "toggle",
shortcuts: data.shortcuts,
textTemplate: (hotkey) =>
hotkey
? multiline(getLocalMessage("toggle_extension"), hotkey)
: getLocalMessage("setup_hotkey_toggle_extension"),
onSetShortcut: (shortcut) => actions.setShortcut("toggle", shortcut),
}),
m$1("span", {
class: "header__app-toggle__more-button",
onclick: onMoreToggleSettingsClick,
}),
m$1(
"span",
{
class: {
"header__app-toggle__time": true,
"header__app-toggle__time--active": isAutomation,
},
},
isTimeAutomation
? m$1(WatchIcon, {
hours: now.getHours(),
minutes: now.getMinutes(),
})
: isLocationAutomation
? m$1(SunMoonIcon, {
date: now,
latitude: data.settings.location.latitude,
longitude: data.settings.location.longitude,
})
: m$1(SystemIcon, null)
)
)
);
}
function Loader({ complete = false }) {
const context = getComponentContext();
const { state, setState } = useState({
finished: false,
errorOccured: false,
});
if (!state.errorOccured && !complete) {
context.store.loaderTimeoutID = setTimeout(() => {
setState({ errorOccured: true });
context.refresh();
}, 3000);
}
if (complete) {
clearTimeout(context.store.loaderTimeoutID);
}
const labelMessage = state.errorOccured
? "A unknown error has occured, the UI couldn't be loaded"
: getLocalMessage("loading_please_wait");
return m$1(
"div",
{
class: {
loader: true,
"loader--complete": complete,
"loader--transition-end": state.finished,
},
ontransitionend: () => setState({ finished: true }),
},
m$1(
"label",
{
class: {
loader__message: true,
loader__error: state.errorOccured,
},
},
labelMessage
)
);
}
var Loader$1 = withState(Loader);
function AutomationPage(props) {
const isSystemAutomation = props.data.settings.automation === "system";
const locationSettings = props.data.settings.location;
const values = {
latitude: {
min: -90,
max: 90,
},
longitude: {
min: -180,
max: 180,
},
};
function getLocationString(location) {
if (location == null) {
return "";
}
return `${location}°`;
}
function locationChanged(inputElement, newValue, type) {
if (newValue.trim() === "") {
inputElement.value = "";
props.actions.changeSettings({
location: {
...locationSettings,
[type]: null,
},
});
return;
}
const min = values[type].min;
const max = values[type].max;
newValue = newValue.replace(",", ".").replace("°", "");
let num = Number(newValue);
if (isNaN(num)) {
num = 0;
} else if (num > max) {
num = max;
} else if (num < min) {
num = min;
}
inputElement.value = getLocationString(num);
props.actions.changeSettings({
location: {
...locationSettings,
[type]: num,
},
});
}
return m$1(
"div",
{ class: "automation-page" },
m$1(
"div",
{ class: "automation-page__line" },
m$1(CheckBox, {
checked: props.data.settings.automation === "time",
onchange: (e) =>
props.actions.changeSettings({
automation: e.target.checked ? "time" : "",
}),
}),
m$1(TimeRangePicker, {
startTime: props.data.settings.time.activation,
endTime: props.data.settings.time.deactivation,
onChange: ([start, end]) =>
props.actions.changeSettings({
time: { activation: start, deactivation: end },
}),
})
),
m$1(
"p",
{ class: "automation-page__description" },
getLocalMessage("set_active_hours")
),
m$1(
"div",
{ class: "automation-page__line automation-page__location" },
m$1(CheckBox, {
checked: props.data.settings.automation === "location",
onchange: (e) =>
props.actions.changeSettings({
automation: e.target.checked ? "location" : "",
}),
}),
m$1(TextBox, {
class: "automation-page__location__latitude",
placeholder: getLocalMessage("latitude"),
onchange: (e) =>
locationChanged(e.target, e.target.value, "latitude"),
oncreate: (node) =>
(node.value = getLocationString(locationSettings.latitude)),
onkeypress: (e) => {
if (e.key === "Enter") {
e.target.blur();
}
},
}),
m$1(TextBox, {
class: "automation-page__location__longitude",
placeholder: getLocalMessage("longitude"),
onchange: (e) =>
locationChanged(e.target, e.target.value, "longitude"),
oncreate: (node) =>
(node.value = getLocationString(locationSettings.longitude)),
onkeypress: (e) => {
if (e.key === "Enter") {
e.target.blur();
}
},
})
),
m$1(
"p",
{ class: "automation-page__location-description" },
getLocalMessage("set_location")
),
m$1(
"div",
{
class: ["automation-page__line", "automation-page__system-dark-mode"],
},
m$1(CheckBox, {
class: "automation-page__system-dark-mode__checkbox",
checked: isSystemAutomation,
onchange: (e) =>
props.actions.changeSettings({
automation: e.target.checked ? "system" : "",
}),
}),
m$1(
Button,
{
class: {
"automation-page__system-dark-mode__button": true,
"automation-page__system-dark-mode__button--active":
isSystemAutomation,
},
onclick: () =>
props.actions.changeSettings({
automation: isSystemAutomation ? "" : "system",
}),
},
getLocalMessage("system_dark_mode")
)
),
m$1(
"p",
{ class: "automation-page__description" },
getLocalMessage("system_dark_mode_description")
),
m$1(DropDown, {
onChange: (selected) =>
props.actions.changeSettings({ automationBehaviour: selected }),
selected: props.data.settings.automationBehaviour,
options: [
{ id: "OnOff", content: "Toggle on/off" },
{ id: "Scheme", content: "Toggle dark/light" },
],
}),
m$1("p", { class: "automation-page__description" }, "Automation behavior")
);
}
function ControlGroup(props, control, description) {
return m$1(
"span",
{ class: ["control-group", props.class] },
control,
description
);
}
function Control(props, control) {
return m$1(
"span",
{ class: ["control-group__control", props.class] },
control
);
}
function Description(props, description) {
return m$1(
"span",
{ class: ["control-group__description", props.class] },
description
);
}
var ControlGroup$1 = Object.assign(ControlGroup, { Control, Description });
function AppSwitch(props) {
const isOn =
props.data.settings.enabled === true && !props.data.settings.automation;
const isOff =
props.data.settings.enabled === false && !props.data.settings.automation;
const isAutomation = Boolean(props.data.settings.automation);
const isTimeAutomation = props.data.settings.automation === "time";
const isLocationAutomation = props.data.settings.automation === "location";
const now = new Date();
const values = [getLocalMessage("on"), "Auto", getLocalMessage("off")];
const value = isOn ? values[0] : isOff ? values[2] : values[1];
function onSwitchChange(v) {
const index = values.indexOf(v);
if (index === 0) {
props.actions.changeSettings({
enabled: true,
automation: "",
});
} else if (index === 2) {
props.actions.changeSettings({
enabled: false,
automation: "",
});
} else if (index === 1) {
props.actions.changeSettings({
automation: "system",
});
}
}
const descriptionText = isOn
? "Extension is enabled"
: isOff
? "Extension is disabled"
: isTimeAutomation
? "Switches according to specified time"
: isLocationAutomation
? "Switched according to location"
: "Switches according to system dark mode";
const description = m$1(
"span",
{
class: {
"app-switch__description": true,
"app-switch__description--on": props.data.isEnabled,
"app-switch__description--off": !props.data.isEnabled,
},
},
descriptionText
);
return m$1(
ControlGroup$1,
{ class: "app-switch" },
m$1(
ControlGroup$1.Control,
null,
m$1(
MultiSwitch,
{
class: "app-switch__control",
options: values,
value: value,
onChange: onSwitchChange,
},
m$1(
"span",
{
class: {
"app-switch__time": true,
"app-switch__time--active": isAutomation,
},
},
isTimeAutomation
? m$1(WatchIcon, {
hours: now.getHours(),
minutes: now.getMinutes(),
})
: isLocationAutomation
? m$1(SunMoonIcon, {
date: now,
latitude: props.data.settings.location.latitude,
longitude: props.data.settings.location.longitude,
})
: m$1(SystemIcon, null)
)
)
),
m$1(ControlGroup$1.Description, null, description)
);
}
function HelpGroup() {
return m$1(
ControlGroup$1,
null,
m$1(
ControlGroup$1.Control,
null,
m$1(
"a",
{
class: "m-help-button",
href: getHelpURL(),
target: "_blank",
rel: "noopener noreferrer",
},
m$1("span", { class: "m-help-button__text" }, getLocalMessage("help"))
)
)
);
}
function SiteToggleGroup(props) {
const tab = props.data.activeTab;
const isPageEnabled = isURLEnabled(tab.url, props.data.settings, tab);
const { isDarkThemeDetected } = tab;
const descriptionText = isPDF(tab.url)
? isPageEnabled
? "Enabled for PDF files"
: "Disabled for PDF files"
: isDarkThemeDetected
? "Dark theme detected on page"
: isPageEnabled
? "Enabled for current website"
: "Disabled for current website";
const description = m$1(
"span",
{
class: {
"site-toggle-group__description": true,
"site-toggle-group__description--on": isPageEnabled,
"site-toggle-group__description--off": !isPageEnabled,
},
},
descriptionText
);
return m$1(
ControlGroup$1,
{ class: "site-toggle-group" },
m$1(
ControlGroup$1.Control,
{ class: "site-toggle-group__control" },
m$1(SiteToggleButton, { ...props })
),
m$1(ControlGroup$1.Description, null, description)
);
}
function ThemeControl(props, controls) {
return m$1(
"span",
{ class: "theme-control" },
m$1("label", { class: "theme-control__label" }, props.label),
controls
);
}
function BackgroundColorEditor(props) {
return m$1(
ThemeControl,
{ label: "Background" },
m$1(ColorPicker$1, {
color: props.value,
onChange: props.onChange,
canReset: props.canReset,
onReset: props.onReset,
})
);
}
function formatPercent(v) {
return `${v}%`;
}
function Brightness(props) {
return m$1(
ThemeControl,
{ label: getLocalMessage("brightness") },
m$1(Slider, {
value: props.value,
min: 50,
max: 150,
step: 1,
formatValue: formatPercent,
onChange: props.onChange,
})
);
}
function Contrast(props) {
return m$1(
ThemeControl,
{ label: getLocalMessage("contrast") },
m$1(Slider, {
value: props.value,
min: 50,
max: 150,
step: 1,
formatValue: formatPercent,
onChange: props.onChange,
})
);
}
function ColorSchemeDropDown(props) {
function onColorSchemeChange(value) {
props.onChange(value);
}
return m$1(
ThemeControl,
{ label: "Color Scheme" },
m$1(DropDown, {
selected: props.selected,
options: props.values,
onChange: onColorSchemeChange,
})
);
}
function FontPicker(props) {
return m$1(
ThemeControl,
{ label: "Font name" },
m$1(Select$1, {
class: {
"font-picker": true,
"font-picker--disabled": !props.theme.useFont,
},
value: props.theme.fontFamily,
onChange: props.onChange,
options: props.fonts.reduce((map, font) => {
map[font] = m$1("div", { style: { "font-family": font } }, font);
return map;
}, {}),
})
);
}
function Grayscale(props) {
return m$1(
ThemeControl,
{ label: getLocalMessage("grayscale") },
m$1(Slider, {
value: props.value,
min: 0,
max: 100,
step: 1,
formatValue: formatPercent,
onChange: props.onChange,
})
);
}
function ImmediateModify(props) {
const options = [
{ id: true, content: "Yes" },
{ id: false, content: "No" },
];
return m$1(
ThemeControl,
{ label: "Immediate modify" },
m$1(DropDown, {
options: options,
onChange: props.onChange,
selected: props.value,
})
);
}
var ThemeEngines = {
cssFilter: "cssFilter",
svgFilter: "svgFilter",
staticTheme: "staticTheme",
dynamicTheme: "dynamicTheme",
};
function Mode(props) {
function openCSSEditor() {
chrome.windows.create({
type: "panel",
url: isFirefox
? "../stylesheet-editor/index.html"
: "ui/stylesheet-editor/index.html",
width: 600,
height: 600,
});
}
const modes = [
{
id: ThemeEngines.dynamicTheme,
content: getLocalMessage("engine_dynamic"),
},
{ id: ThemeEngines.cssFilter, content: getLocalMessage("engine_filter") },
{
id: ThemeEngines.svgFilter,
content: getLocalMessage("engine_filter_plus"),
},
{
id: ThemeEngines.staticTheme,
content: getLocalMessage("engine_static"),
},
];
return m$1(
ThemeControl,
{ label: "Mode" },
m$1(
"div",
{ class: "mode-control-container" },
m$1(DropDown, {
selected: modes.find((m) => m.id === props.mode).id,
options: modes,
onChange: props.onChange,
}),
m$1("span", {
class: {
"static-edit-button": true,
"static-edit-button--hidden":
props.mode !== ThemeEngines.staticTheme,
},
onclick: openCSSEditor,
})
)
);
}
const DEFAULT_COLORS = {
darkScheme: {
background: "#181a1b",
text: "#e8e6e3",
},
lightScheme: {
background: "#dcdad7",
text: "#181a1b",
},
};
const DEFAULT_THEME = {
mode: 1,
brightness: 100,
contrast: 100,
grayscale: 0,
sepia: 0,
useFont: false,
fontFamily: isMacOS
? "Helvetica Neue"
: isWindows
? "Segoe UI"
: "Open Sans",
textStroke: 0,
engine: ThemeEngines.dynamicTheme,
stylesheet: "",
darkSchemeBackgroundColor: DEFAULT_COLORS.darkScheme.background,
darkSchemeTextColor: DEFAULT_COLORS.darkScheme.text,
lightSchemeBackgroundColor: DEFAULT_COLORS.lightScheme.background,
lightSchemeTextColor: DEFAULT_COLORS.lightScheme.text,
scrollbarColor: isMacOS ? "" : "auto",
selectionColor: "auto",
styleSystemControls: !isCSSColorSchemePropSupported,
lightColorScheme: "Default",
darkColorScheme: "Default",
immediateModify: false,
};
const DEFAULT_SETTINGS = {
enabled: true,
fetchNews: true,
theme: DEFAULT_THEME,
presets: [],
customThemes: [],
siteList: [],
siteListEnabled: [],
applyToListedOnly: false,
changeBrowserTheme: false,
syncSettings: true,
syncSitesFixes: false,
automation: "",
automationBehaviour: "OnOff",
time: {
activation: "18:00",
deactivation: "9:00",
},
location: {
latitude: null,
longitude: null,
},
previewNewDesign: false,
enableForPDF: true,
enableForProtectedPages: false,
enableContextMenus: false,
detectDarkTheme: false,
};
function ResetButtonGroup$1(props) {
function reset() {
props.actions.setTheme(DEFAULT_SETTINGS.theme);
}
return m$1(
ControlGroup$1,
null,
m$1(
ControlGroup$1.Control,
null,
m$1(ResetButton, { onClick: reset }, "Reset to defaults")
),
m$1(
ControlGroup$1.Description,
null,
"Restore current theme values to defaults"
)
);
}
function Scheme(props) {
return m$1(
ThemeControl,
{ label: "Scheme" },
m$1(DropDown, {
selected: props.isDark,
options: [
{ id: true, content: getLocalMessage("dark") },
{ id: false, content: getLocalMessage("light") },
],
onChange: props.onChange,
})
);
}
function ScrollbarEditor(props) {
return m$1(
ThemeControl,
{ label: "Scrollbar" },
m$1(ColorDropDown, {
value: props.value,
colorSuggestion: "#959799",
onChange: props.onChange,
onReset: props.onReset,
hasAutoOption: true,
hasDefaultOption: true,
})
);
}
function SelectionColorEditor(props) {
return m$1(
ThemeControl,
{ label: "Selection" },
m$1(ColorDropDown, {
value: props.value,
colorSuggestion: "#005ccc",
onChange: props.onChange,
onReset: props.onReset,
hasAutoOption: true,
hasDefaultOption: true,
})
);
}
function Sepia(props) {
return m$1(
ThemeControl,
{ label: getLocalMessage("sepia") },
m$1(Slider, {
value: props.value,
min: 0,
max: 100,
step: 1,
formatValue: formatPercent,
onChange: props.onChange,
})
);
}
function StyleSystemControls(props) {
const options = [
{ id: true, content: "Yes" },
{ id: false, content: "No" },
];
return m$1(
ThemeControl,
{ label: "Style system controls" },
m$1(DropDown, {
options: options,
onChange: props.onChange,
selected: props.value,
})
);
}
function TextColorEditor(props) {
return m$1(
ThemeControl,
{ label: "Text" },
m$1(ColorPicker$1, {
color: props.value,
onChange: props.onChange,
canReset: props.canReset,
onReset: props.onReset,
})
);
}
function TextStroke(props) {
return m$1(
ThemeControl,
{ label: "Text stroke" },
m$1(Slider, {
value: props.value,
min: 0,
max: 1,
step: 0.1,
formatValue: String,
onChange: props.onChange,
})
);
}
function UseFont(props) {
const options = [
{ id: true, content: "Yes" },
{ id: false, content: "No" },
];
return m$1(
ThemeControl,
{ label: "Change font" },
m$1(DropDown, {
options: options,
onChange: props.onChange,
selected: props.value,
})
);
}
function hexify(number) {
return (number < 16 ? "0" : "") + number.toString(16);
}
function generateUID() {
if ("randomUUID" in crypto) {
const uuid = crypto.randomUUID();
return (
uuid.substring(0, 8) +
uuid.substring(9, 13) +
uuid.substring(14, 18) +
uuid.substring(19, 23) +
uuid.substring(24)
);
}
return Array.from(crypto.getRandomValues(new Uint8Array(16)))
.map((x) => hexify(x))
.join("");
}
function PresetItem(props) {
const context = getComponentContext();
const store = context.store;
function onRemoveClick(e) {
e.stopPropagation();
store.isConfirmationVisible = true;
context.refresh();
}
function onConfirmRemoveClick() {
const filtered = props.data.settings.presets.filter(
(p) => p.id !== props.preset.id
);
props.actions.changeSettings({ presets: filtered });
}
function onCancelRemoveClick() {
store.isConfirmationVisible = false;
context.refresh();
}
const confirmation = store.isConfirmationVisible
? m$1(MessageBox, {
caption: `Are you sure you want to remove ${props.preset.name}?`,
onOK: onConfirmRemoveClick,
onCancel: onCancelRemoveClick,
})
: null;
return m$1(
"span",
{ class: "theme-preset-picker__preset" },
m$1(
"span",
{ class: "theme-preset-picker__preset__name" },
props.preset.name
),
m$1("span", {
class: "theme-preset-picker__preset__remove-button",
onclick: onRemoveClick,
}),
confirmation
);
}
const MAX_ALLOWED_PRESETS = 3;
function PresetPicker(props) {
const tab = props.data.activeTab;
const host = getURLHostOrProtocol(tab.url);
const preset = props.data.settings.presets.find(({ urls }) =>
isURLInList(tab.url, urls)
);
const custom = props.data.settings.customThemes.find(({ url }) =>
isURLInList(tab.url, url)
);
const selectedPresetId = custom ? "custom" : preset ? preset.id : "default";
const defaultOption = { id: "default", content: "Theme for all websites" };
const addNewPresetOption =
props.data.settings.presets.length < MAX_ALLOWED_PRESETS
? { id: "add-preset", content: "\uff0b Create new theme" }
: null;
const userPresetsOptions = props.data.settings.presets.map((preset) => {
if (preset.id === selectedPresetId) {
return { id: preset.id, content: preset.name };
}
return {
id: preset.id,
content: m$1(PresetItem, { ...props, preset: preset }),
};
});
const customSitePresetOption = {
id: "custom",
content: `${
selectedPresetId === "custom" ? "\u2605" : "\u2606"
} Theme for ${host}`,
};
const dropdownOptions = [
defaultOption,
...userPresetsOptions,
addNewPresetOption,
customSitePresetOption,
].filter(Boolean);
function onPresetChange(id) {
const filteredCustomThemes = props.data.settings.customThemes.filter(
({ url }) => !isURLInList(tab.url, url)
);
const filteredPresets = props.data.settings.presets.map((preset) => {
return {
...preset,
urls: preset.urls.filter(
(template) => !isURLMatched(tab.url, template)
),
};
});
if (id === "default") {
props.actions.changeSettings({
customThemes: filteredCustomThemes,
presets: filteredPresets,
});
} else if (id === "custom") {
const extended = filteredCustomThemes.concat({
url: [host],
theme: { ...props.data.settings.theme },
});
props.actions.changeSettings({
customThemes: extended,
presets: filteredPresets,
});
} else if (id === "add-preset") {
let newPresetName;
for (let i = 0; i <= props.data.settings.presets.length; i++) {
newPresetName = `Theme ${i + 1}`;
if (
props.data.settings.presets.every((p) => p.name !== newPresetName)
) {
break;
}
}
const extended = filteredPresets.concat({
id: `preset-${generateUID()}`,
name: newPresetName,
urls: [host],
theme: { ...props.data.settings.theme },
});
props.actions.changeSettings({
customThemes: filteredCustomThemes,
presets: extended,
});
} else {
const chosenPresetId = id;
const extended = filteredPresets.map((preset) => {
if (preset.id === chosenPresetId) {
return {
...preset,
urls: preset.urls.concat(host),
};
}
return preset;
});
props.actions.changeSettings({
customThemes: filteredCustomThemes,
presets: extended,
});
}
}
return m$1(DropDown, {
class: "theme-preset-picker",
selected: selectedPresetId,
options: dropdownOptions,
onChange: onPresetChange,
});
}
function getCurrentThemePreset(props) {
const custom = props.data.settings.customThemes.find(({ url }) =>
isURLInList(props.data.activeTab.url, url)
);
const preset = custom
? null
: props.data.settings.presets.find(({ urls }) =>
isURLInList(props.data.activeTab.url, urls)
);
let theme = custom
? custom.theme
: preset
? preset.theme
: props.data.settings.theme;
if (props.data.forcedScheme) {
const mode = props.data.forcedScheme === "dark" ? 1 : 0;
theme = { ...theme, mode };
}
function setTheme(config) {
if (custom) {
custom.theme = { ...custom.theme, ...config };
props.actions.changeSettings({
customThemes: props.data.settings.customThemes,
});
} else if (preset) {
preset.theme = { ...preset.theme, ...config };
props.actions.changeSettings({
presets: props.data.settings.presets,
});
} else {
props.actions.setTheme(config);
}
if (
config.mode != null &&
props.data.settings.automation &&
props.data.settings.automationBehaviour === "Scheme"
) {
props.actions.changeSettings({
automation: "",
});
}
}
return {
theme,
change: setTheme,
};
}
function ThemeControls(props) {
const { theme, onChange } = props;
return m$1(
"section",
{ class: "m-section m-theme-controls" },
m$1(Brightness, {
value: theme.brightness,
onChange: (v) => onChange({ brightness: v }),
}),
m$1(Contrast, {
value: theme.contrast,
onChange: (v) => onChange({ contrast: v }),
}),
m$1(Scheme, {
isDark: theme.mode === 1,
onChange: (isDark) => onChange({ mode: isDark ? 1 : 0 }),
}),
m$1(Mode, {
mode: theme.engine,
onChange: (mode) => onChange({ engine: mode }),
})
);
}
function ThemeGroup(props) {
const preset = getCurrentThemePreset(props);
return m$1(
"div",
{ class: "theme-group" },
m$1(
"div",
{ class: "theme-group__presets-wrapper" },
m$1(PresetPicker, { ...props })
),
m$1(
"div",
{ class: "theme-group__controls-wrapper" },
m$1(ThemeControls, { theme: preset.theme, onChange: preset.change }),
m$1(
Button,
{ class: "theme-group__more-button", onclick: props.onThemeNavClick },
"See all options"
)
),
m$1("label", { class: "theme-group__description" }, "Configure theme")
);
}
function SwitchGroup(props) {
return m$1(
Array,
null,
m$1(AppSwitch, { ...props }),
m$1(SiteToggleGroup, { ...props })
);
}
function SettingsNavButton(props) {
return m$1(
ResetButton$1,
{ onClick: props.onClick },
m$1("span", { class: "settings-button-icon" }),
"Settings"
);
}
function MainPage(props) {
return m$1(
Array,
null,
m$1("section", { class: "m-section" }, m$1(SwitchGroup, { ...props })),
m$1("section", { class: "m-section" }, m$1(ThemeGroup, { ...props })),
m$1(
"section",
{ class: "m-section" },
m$1(SettingsNavButton, { onClick: props.onSettingsNavClick }),
m$1(HelpGroup, null)
)
);
}
function isFresh(n) {
try {
const now = Date.now();
const date = new Date(n.date).getTime();
return now - date < getDuration({ days: 30 });
} catch (err) {
return false;
}
}
function NewsLink(props) {}
function NewsSection(props) {
const context = getComponentContext();
const store = context.getStore({
expanded: false,
didNewsSlideIn: false,
});
const { expanded, didNewsSlideIn } = store;
const news = props.data.news;
const latest = news && news.length > 0 ? news[0] : null;
function markLatestAsRead() {
if (latest) {
props.actions.markNewsAsRead([latest.id]);
}
}
function toggleNews() {
if (expanded) {
markLatestAsRead();
}
store.expanded = !expanded;
store.didNewsSlideIn = didNewsSlideIn || !store.expanded;
context.refresh();
}
context.onRender(() => {
if (latest && !latest.read && !store.expanded && !store.didNewsSlideIn) {
setTimeout(toggleNews, 750);
}
});
return m$1(
"div",
{ class: { "news-section": true, "news-section--expanded": expanded } },
latest
? m$1(NewsLink, {
isSmall: true,
news: latest,
onClick: markLatestAsRead,
})
: null,
m$1(
"div",
{ class: "news-section__popover" },
m$1(
"div",
{ class: "news-section__popover__top" },
m$1("div", { class: "news-section__title" }, "What's New"),
m$1(
"span",
{
role: "button",
class: "news-section__close",
onclick: toggleNews,
},
"\u2715"
)
),
latest
? m$1(NewsLink, { news: latest, onClick: markLatestAsRead })
: null
)
);
}
function Page(props, content) {
return m$1(
"div",
{ class: { page: true, "page--active": props.active } },
m$1("div", { class: "page__content" }, content),
props.first
? null
: m$1(
Button,
{ class: "page__back-button", onclick: props.onBackButtonClick },
"Back"
)
);
}
function PageViewer(props, ...pages) {
return m$1(
"div",
{ class: "page-viewer" },
pages.map((pageSpec, i) => {
return {
...pageSpec,
props: {
...pageSpec.props,
active: props.activePage === pageSpec.props.id,
first: i === 0,
onBackButtonClick: props.onBackButtonClick,
},
};
})
);
}
function AutomationButton(props) {
const now = new Date();
return m$1(
ControlGroup$1,
null,
m$1(
ControlGroup$1.Control,
null,
m$1(
ResetButton$1,
{ onClick: props.onClick },
m$1(
"span",
{ class: "automation-button-icon" },
m$1(WatchIcon, { hours: now.getHours(), minutes: now.getMinutes() })
),
"Automation"
)
),
m$1(ControlGroup$1.Description, null, "Configure when app is enabled")
);
}
function getExistingDevToolsObject() {
if (isMobile) {
return new Promise((resolve) => {
chrome.tabs.query({}, (t) => {
for (const tab of t) {
if (tab.url.endsWith("ui/devtools/index.html")) {
resolve(tab);
return;
}
}
resolve(null);
});
});
}
return new Promise((resolve) => {
chrome.windows.getAll(
{
populate: true,
windowTypes: ["popup"],
},
(w) => {
for (const window of w) {
if (window.tabs[0].url.endsWith("ui/devtools/index.html")) {
resolve(window);
return;
}
}
resolve(null);
}
);
});
}
async function openDevTools$1() {
const devToolsObject = await getExistingDevToolsObject();
if (isMobile) {
if (devToolsObject) {
chrome.tabs.update(devToolsObject.id, { active: true });
window.close();
} else {
chrome.tabs.create({
url: "../devtools/index.html",
});
window.close();
}
} else if (devToolsObject) {
chrome.windows.update(devToolsObject.id, { focused: true });
} else {
chrome.windows.create({
type: "popup",
url: isFirefox ? "../devtools/index.html" : "ui/devtools/index.html",
width: 600,
height: 600,
});
}
}
function DevToolsGroup(props) {
const globalThemeEngine = props.data.settings.theme.engine;
const devtoolsData = props.data.devtools;
const hasCustomFixes =
(globalThemeEngine === ThemeEngines.dynamicTheme &&
devtoolsData.hasCustomDynamicFixes) ||
([ThemeEngines.cssFilter, ThemeEngines.svgFilter].includes(
globalThemeEngine
) &&
devtoolsData.hasCustomFilterFixes) ||
(globalThemeEngine === ThemeEngines.staticTheme &&
devtoolsData.hasCustomStaticFixes);
return m$1(
ControlGroup$1,
null,
m$1(
ControlGroup$1.Control,
null,
m$1(
ResetButton$1,
{
onClick: openDevTools$1,
class: {
"dev-tools-button": true,
"dev-tools-button--has-custom-fixes": hasCustomFixes,
},
},
"\uD83D\uDEE0 ",
getLocalMessage("open_dev_tools")
)
),
m$1(ControlGroup$1.Description, null, "Make a fix for a website")
);
}
function ManageSettingsButton(props) {
return m$1(
ControlGroup$1,
null,
m$1(
ControlGroup$1.Control,
null,
m$1(ResetButton$1, { onClick: props.onClick }, "Manage settings")
),
m$1(ControlGroup$1.Description, null, "Reset, export or import settings")
);
}
function SiteListButton(props) {
return m$1(
ControlGroup$1,
null,
m$1(
ControlGroup$1.Control,
null,
m$1(
ResetButton$1,
{ onClick: props.onClick },
m$1("span", { class: "site-list-button-icon" }),
"Site list"
)
),
m$1(
ControlGroup$1.Description,
null,
"Enable or disable on listed websites"
)
);
}
function CheckButton(props) {
return m$1(
ControlGroup$1,
{ class: "check-button" },
m$1(
ControlGroup$1.Control,
null,
m$1(
CheckBox,
{
class: "check-button__checkbox",
checked: props.checked,
onchange: (e) => props.onChange(e.target.checked),
},
props.label
)
),
m$1(
ControlGroup$1.Description,
{ class: "check-button__description" },
props.description
)
);
}
function EnabledByDefaultGroup(props) {
function onEnabledByDefaultChange(checked) {
props.actions.changeSettings({ applyToListedOnly: !checked });
}
return m$1(CheckButton, {
checked: !props.data.settings.applyToListedOnly,
label: "Enable by default",
description: props.data.settings.applyToListedOnly
? "Disabled on all websites by default"
: "Enabled on all websites by default",
onChange: onEnabledByDefaultChange,
});
}
function DetectDarkTheme(props) {
function onDetectDarkThemeChange(checked) {
props.actions.changeSettings({ detectDarkTheme: checked });
}
return m$1(CheckButton, {
checked: props.data.settings.detectDarkTheme,
label: "Detect dark theme",
description: props.data.settings.detectDarkTheme
? `Will not override website's dark theme`
: `Will override website's dark theme`,
onChange: onDetectDarkThemeChange,
});
}
function ChangeBrowserTheme(props) {
function onBrowserThemeChange(checked) {
props.actions.changeSettings({ changeBrowserTheme: checked });
}
return m$1(CheckButton, {
checked: props.data.settings.changeBrowserTheme,
label: "Change browser theme",
description: props.data.settings.changeBrowserTheme
? "Custom browser theme is active"
: "Default browser theme is active",
onChange: onBrowserThemeChange,
});
}
function ContextMenusGroup(props) {
function onContextMenusChange(checked) {
if (checked) {
if (isFirefox) {
props.actions.changeSettings({ enableContextMenus: true });
return;
}
chrome.permissions.request(
{ permissions: ["contextMenus"] },
(hasPermission) => {
if (hasPermission) {
props.actions.changeSettings({ enableContextMenus: true });
} else {
console.warn("User declined contextMenus permission prompt.");
}
}
);
} else {
props.actions.changeSettings({ enableContextMenus: false });
}
}
return isMobile
? null
: m$1(CheckButton, {
checked: props.data.settings.enableContextMenus,
label: "Use context menus",
description: props.data.settings.enableContextMenus
? "Context menu integration is enabled"
: "Context menu integration is disabled",
onChange: onContextMenusChange,
});
}
let appVersion;
function AppVersion() {
if (!appVersion) {
appVersion = chrome.runtime.getManifest().version;
}
return m$1(
"label",
{ class: "darkreader-version" },
"Version 5 Preview (",
appVersion,
")"
);
}
function SettingsPage(props) {
return m$1(
"section",
{ class: "m-section" },
m$1(EnabledByDefaultGroup, { ...props }),
isFirefox ? m$1(ChangeBrowserTheme, { ...props }) : null,
m$1(SiteListButton, { onClick: props.onSiteListNavClick }),
m$1(DevToolsGroup, { ...props }),
m$1(AutomationButton, { onClick: props.onAutomationNavClick }),
m$1(ContextMenusGroup, { ...props }),
m$1(ManageSettingsButton, { onClick: props.onManageSettingsClick }),
m$1(DetectDarkTheme, { ...props }),
m$1(AppVersion, null)
);
}
function SiteList(props) {
const context = getComponentContext();
const store = context.store;
if (!context.prev) {
store.indices = new WeakMap();
store.shouldFocusAtIndex = -1;
store.wasVisible = false;
}
context.onRender((node) => {
const isVisible = node.clientWidth > 0;
const { wasVisible } = store;
store.wasVisible = isVisible;
if (!wasVisible && isVisible) {
store.shouldFocusAtIndex = props.siteList.length;
context.refresh();
}
});
function onTextChange(e) {
const index = store.indices.get(e.target);
const values = props.siteList.slice();
const value = e.target.value.trim();
if (values.includes(value)) {
return;
}
if (!value) {
values.splice(index, 1);
store.shouldFocusAtIndex = index;
} else if (index === values.length) {
values.push(value);
store.shouldFocusAtIndex = index + 1;
} else {
values.splice(index, 1, value);
store.shouldFocusAtIndex = index + 1;
}
props.onChange(values);
}
function removeValue(event) {
const previousSibling = event.target.previousSibling;
const index = store.indices.get(previousSibling);
const filtered = props.siteList.slice();
filtered.splice(index, 1);
store.shouldFocusAtIndex = index;
props.onChange(filtered);
}
function createTextBox(text, index) {
const onRender = (node) => {
store.indices.set(node, index);
if (store.shouldFocusAtIndex === index) {
store.shouldFocusAtIndex = -1;
node.focus();
}
};
return m$1(
"div",
{ class: "site-list__item" },
m$1(TextBox, {
class: "site-list__textbox",
value: text,
onrender: onRender,
placeholder: "google.com/maps",
}),
text
? m$1("span", {
class: "site-list__item__remove",
role: "button",
onclick: removeValue,
})
: null
);
}
return m$1(
"div",
{ class: "site-list" },
m$1(VirtualScroll, {
root: m$1("div", {
class: "site-list__v-scroll-root",
onchange: onTextChange,
}),
items: props.siteList
.map((site, index) => createTextBox(site, index))
.concat(createTextBox("", props.siteList.length)),
scrollToIndex: store.shouldFocusAtIndex,
})
);
}
function SiteListPage(props) {
function onSiteListChange(sites) {
props.actions.changeSettings({ siteList: sites });
}
function onInvertPDFChange(checked) {
props.actions.changeSettings({ enableForPDF: checked });
}
function onEnableForProtectedPages(value) {
props.actions.changeSettings({ enableForProtectedPages: value });
}
const label = props.data.settings.applyToListedOnly
? "Enable on these websites"
: "Disable on these websites";
return m$1(
"div",
{ class: "site-list-page" },
m$1("label", { class: "site-list-page__label" }, label),
m$1(SiteList, {
siteList: props.data.settings.siteList,
onChange: onSiteListChange,
}),
m$1(
"label",
{ class: "site-list-page__description" },
"Enter website name and press Enter"
),
isFirefox
? null
: m$1(CheckButton, {
checked: props.data.settings.enableForPDF,
label: "Enable for PDF files",
description: props.data.settings.enableForPDF
? "Enabled for PDF documents"
: "Disabled for PDF documents",
onChange: onInvertPDFChange,
}),
m$1(CheckButton, {
checked: props.data.settings.enableForProtectedPages,
onChange: onEnableForProtectedPages,
label: "Enable on restricted pages",
description: props.data.settings.enableForProtectedPages
? "You should enable it in browser flags too"
: "Disabled for web store and other pages",
})
);
}
function CollapsiblePanel({}, ...groups) {
const context = getComponentContext();
const store = context.store;
if (store.activeGroup == null) {
store.activeGroup = groups[0].props.id;
}
return m$1(
"div",
{ class: "collapsible-panel" },
...groups.map((spec, i) => {
const activeIndex = groups.findIndex(
(g) => store.activeGroup === g.props.id
);
const collapsed = i !== activeIndex;
const collapseTop = i < activeIndex;
const collapseBottom = i > activeIndex;
const onExpand = () => {
store.activeGroup = spec.props.id;
context.refresh();
};
return {
...spec,
props: {
...spec.props,
collapsed,
collapseBottom,
collapseTop,
onExpand,
},
};
})
);
}
function Group(props, content) {
return m$1(
"div",
{
class: {
"collapsible-panel__group": true,
"collapsible-panel__group--collapsed": props.collapsed,
"collapsible-panel__group--collapse-top": props.collapseTop,
"collapsible-panel__group--collapse-bottom": props.collapseBottom,
},
},
m$1("div", { class: "collapsible-panel__group__content" }, content),
m$1(
"span",
{
role: "button",
class: "collapsible-panel__group__expand-button",
onclick: props.onExpand,
},
props.label
)
);
}
var Collapsible = Object.assign(CollapsiblePanel, { Group });
function MainGroup({ theme, change }) {
return m$1(
Array,
null,
m$1(Brightness, {
value: theme.brightness,
onChange: (v) => change({ brightness: v }),
}),
m$1(Contrast, {
value: theme.contrast,
onChange: (v) => change({ contrast: v }),
}),
m$1(Sepia, { value: theme.sepia, onChange: (v) => change({ sepia: v }) }),
m$1(Grayscale, {
value: theme.grayscale,
onChange: (v) => change({ grayscale: v }),
}),
m$1(Scheme, {
isDark: theme.mode === 1,
onChange: (isDark) => change({ mode: isDark ? 1 : 0 }),
}),
m$1(Mode, {
mode: theme.engine,
onChange: (mode) => change({ engine: mode }),
})
);
}
function ColorsGroup({ theme, change, colorSchemes }) {
const isDarkScheme = theme.mode === 1;
const csProp = isDarkScheme ? "darkColorScheme" : "lightColorScheme";
const bgProp = isDarkScheme
? "darkSchemeBackgroundColor"
: "lightSchemeBackgroundColor";
const fgProp = isDarkScheme
? "darkSchemeTextColor"
: "lightSchemeTextColor";
const defaultSchemeColors = isDarkScheme
? DEFAULT_COLORS.darkScheme
: DEFAULT_COLORS.lightScheme;
const defaultMatrixValues = {
brightness: DEFAULT_THEME.brightness,
contrast: DEFAULT_THEME.contrast,
sepia: DEFAULT_THEME.sepia,
grayscale: DEFAULT_THEME.grayscale,
};
const currentColorScheme = isDarkScheme
? theme.darkColorScheme
: theme.lightColorScheme;
const currentSchemeColors = isDarkScheme
? colorSchemes.dark
: colorSchemes.light;
const sortedColorSchemeValues = Object.keys(currentSchemeColors)
.sort((a, b) => a.localeCompare(b))
.map((value) => {
return { id: value, content: value };
});
function onColorSchemeChange(newColor) {
change({ [csProp]: newColor });
change({ [bgProp]: currentSchemeColors[newColor].backgroundColor });
change({ [fgProp]: currentSchemeColors[newColor].textColor });
}
return m$1(
Array,
null,
m$1(BackgroundColorEditor, {
value:
theme[bgProp] === "auto"
? defaultSchemeColors.background
: theme[bgProp],
onChange: (v) =>
change({ [bgProp]: v, ...defaultMatrixValues, [csProp]: "Default" }),
canReset: theme[bgProp] !== defaultSchemeColors.background,
onReset: () =>
change({
[bgProp]: DEFAULT_SETTINGS.theme[bgProp],
[csProp]: "Default",
}),
}),
m$1(TextColorEditor, {
value:
theme[fgProp] === "auto" ? defaultSchemeColors.text : theme[fgProp],
onChange: (v) =>
change({ [fgProp]: v, ...defaultMatrixValues, [csProp]: "Default" }),
canReset: theme[fgProp] !== defaultSchemeColors.text,
onReset: () =>
change({
[fgProp]: DEFAULT_SETTINGS.theme[fgProp],
[csProp]: "Default",
}),
}),
m$1(ScrollbarEditor, {
value: theme.scrollbarColor,
onChange: (v) => change({ scrollbarColor: v }),
onReset: () =>
change({ scrollbarColor: DEFAULT_SETTINGS.theme.scrollbarColor }),
}),
m$1(SelectionColorEditor, {
value: theme.selectionColor,
onChange: (v) => change({ selectionColor: v }),
onReset: () =>
change({ selectionColor: DEFAULT_SETTINGS.theme.selectionColor }),
}),
m$1(ColorSchemeDropDown, {
selected: currentColorScheme,
values: sortedColorSchemeValues,
onChange: (v) => onColorSchemeChange(v),
})
);
}
function FontGroup({ theme, fonts, change }) {
return m$1(
Array,
null,
m$1(UseFont, {
value: theme.useFont,
onChange: (useFont) => change({ useFont }),
}),
m$1(FontPicker, {
theme: theme,
fonts: fonts,
onChange: (fontFamily) => change({ fontFamily }),
}),
m$1(TextStroke, {
value: theme.textStroke,
onChange: (textStroke) => change({ textStroke }),
}),
m$1(StyleSystemControls, {
value: theme.styleSystemControls,
onChange: (styleSystemControls) => change({ styleSystemControls }),
}),
m$1(ImmediateModify, {
value: theme.immediateModify,
onChange: (immediateModify) => change({ immediateModify }),
})
);
}
function ThemePage(props) {
const { theme, change } = getCurrentThemePreset(props);
return m$1(
"section",
{ class: "m-section theme-page" },
m$1(PresetPicker, { ...props }),
m$1(
Collapsible,
null,
m$1(
Collapsible.Group,
{ id: "main", label: "Brightness, contrast, mode" },
m$1(MainGroup, { theme: theme, change: change })
),
m$1(
Collapsible.Group,
{ id: "colors", label: "Colors" },
m$1(ColorsGroup, {
theme: theme,
change: change,
colorSchemes: props.data.colorScheme,
})
),
m$1(
Collapsible.Group,
{ id: "font", label: "Font & more" },
m$1(FontGroup, { theme: theme, fonts: props.fonts, change: change })
)
),
m$1(ResetButtonGroup$1, { ...props })
);
}
function ResetButtonGroup(props) {
const context = getComponentContext();
function showDialog() {
context.store.isDialogVisible = true;
context.refresh();
}
function hideDialog() {
context.store.isDialogVisible = false;
context.refresh();
}
function reset() {
context.store.isDialogVisible = false;
props.actions.changeSettings(DEFAULT_SETTINGS);
}
const dialog = context.store.isDialogVisible
? m$1(MessageBox, {
caption:
"Are you sure you want to remove all your settings? You cannot restore them later",
onOK: reset,
onCancel: hideDialog,
})
: null;
return m$1(
ControlGroup$1,
null,
m$1(
ControlGroup$1.Control,
null,
m$1(ResetButton, { onClick: showDialog }, "Reset settings", dialog)
),
m$1(ControlGroup$1.Description, null, "Restore settings to defaults")
);
}
function ImportButton(props) {
const context = getComponentContext();
function getValidatedObject(source, compare) {
const result = {};
if (
source == null ||
typeof source !== "object" ||
Array.isArray(source)
) {
return null;
}
Object.keys(source).forEach((key) => {
const value = source[key];
if (value == null || compare[key] == null) {
return;
}
const array1 = Array.isArray(value);
const array2 = Array.isArray(compare[key]);
if (array1 || array2) {
if (array1 && array2) {
result[key] = value;
}
} else if (
typeof value === "object" &&
typeof compare[key] === "object"
) {
result[key] = getValidatedObject(value, compare[key]);
} else if (typeof value === typeof compare[key]) {
result[key] = value;
}
});
return result;
}
function showDialog() {
context.store.isDialogVisible = true;
context.refresh();
}
function hideDialog() {
context.store.isDialogVisible = false;
context.refresh();
}
const dialog =
context && context.store.isDialogVisible
? m$1(MessageBox, {
caption: "The given file has incorrect JSON.",
onOK: hideDialog,
onCancel: hideDialog,
})
: null;
function importSettings() {
openFile({ extensions: ["json"] }, (result) => {
try {
const content = JSON.parse(result);
const result2 = getValidatedObject(content, DEFAULT_SETTINGS);
props.actions.changeSettings({ ...result2 });
} catch (err) {
console.error(err);
showDialog();
}
});
}
return m$1(
ControlGroup$1,
null,
m$1(
ControlGroup$1.Control,
null,
m$1(
Button,
{ onclick: importSettings, class: "settings-button" },
"Import Settings",
dialog
)
),
m$1(ControlGroup$1.Description, null, "Open settings from a JSON file")
);
}
function ExportButton(props) {
function exportSettings() {
saveFile("Settings.json", JSON.stringify(props.data.settings, null, 4));
}
return m$1(
ControlGroup$1,
null,
m$1(
ControlGroup$1.Control,
null,
m$1(
Button,
{ onclick: exportSettings, class: "settings-button" },
"Export Settings"
)
),
m$1(ControlGroup$1.Description, null, "Save settings to a JSON file")
);
}
function SyncSettings(props) {
function onSyncSettingsChange(checked) {
props.actions.changeSettings({ syncSettings: checked });
}
return m$1(CheckButton, {
checked: props.data.settings.syncSettings,
label: "Enable settings sync",
description: props.data.settings.syncSettings
? "Synchronized across devices"
: "Not synchronized across devices",
onChange: onSyncSettingsChange,
});
}
function ExportTheme() {
const listener = ({ type, data }, sender) => {
if (type === MessageType.CS_EXPORT_CSS_RESPONSE) {
const url = getURLHostOrProtocol(sender.tab.url).replace(
/[^a-z0-1\-]/g,
"-"
);
saveFile(`DarkMode-${url}.css`, data);
chrome.runtime.onMessage.removeListener(listener);
}
};
function exportCSS() {
chrome.runtime.onMessage.addListener(listener);
chrome.runtime.sendMessage({ type: MessageType.UI_REQUEST_EXPORT_CSS });
}
return m$1(
ControlGroup$1,
null,
m$1(
ControlGroup$1.Control,
null,
m$1(
Button,
{ onclick: exportCSS, class: "settings-button" },
"Export Dynamic Theme"
)
),
m$1(ControlGroup$1.Description, null, "Save generated CSS to a file")
);
}
function SyncConfigButton(props) {
function syncConfig(syncSitesFixes) {
props.actions.changeSettings({ syncSitesFixes });
props.actions.loadConfig({ local: !syncSitesFixes });
}
return m$1(CheckButton, {
checked: props.data.settings.syncSitesFixes,
label: "Synchronize sites fixes",
description: "Load the latest sites fixes from a remote server",
onChange: syncConfig,
});
}
function FetchNews(props) {
function onFetchNewsChange(checked) {
props.actions.changeSettings({ fetchNews: checked });
}
return 0;
}
function ManageSettingsPage(props) {
const custom = props.data.settings.customThemes.find(({ url }) =>
isURLInList(props.data.activeTab.url, url)
);
const engine = custom
? custom.theme.engine
: props.data.settings.theme.engine;
return m$1(
"section",
{ class: "m-section" },
m$1(SyncSettings, { ...props }),
m$1(SyncConfigButton, { ...props }),
m$1(FetchNews, { ...props }),
m$1(ImportButton, { ...props }),
m$1(ExportButton, { ...props }),
engine === ThemeEngines.dynamicTheme ? m$1(ExportTheme, null) : null,
m$1(ResetButtonGroup, { ...props })
);
}
function Logo() {
// return m$1("a", { class: "m-logo", href: "", target: "_blank", rel: "noopener noreferrer" }, "");
}
function Pages(props) {
const context = getComponentContext();
const store = context.store;
if (store.activePage == null) {
store.activePage = "main";
}
function onThemeNavClick() {
store.activePage = "theme";
context.refresh();
}
function onSettingsNavClick() {
store.activePage = "settings";
context.refresh();
}
function onAutomationNavClick() {
store.activePage = "automation";
context.refresh();
}
function onManageSettingsClick() {
store.activePage = "manage-settings";
context.refresh();
}
function onSiteListNavClick() {
store.activePage = "site-list";
context.refresh();
}
function onBackClick() {
const activePage = store.activePage;
const settingsPageSubpages = [
"automation",
"manage-settings",
"site-list",
];
if (settingsPageSubpages.includes(activePage)) {
store.activePage = "settings";
} else {
store.activePage = "main";
}
context.refresh();
}
return m$1(
PageViewer,
{ activePage: store.activePage, onBackButtonClick: onBackClick },
m$1(
Page,
{ id: "main" },
m$1(MainPage, {
...props,
onThemeNavClick: onThemeNavClick,
onSettingsNavClick: onSettingsNavClick,
})
),
m$1(Page, { id: "theme" }, m$1(ThemePage, { ...props })),
m$1(
Page,
{ id: "settings" },
m$1(SettingsPage, {
...props,
onAutomationNavClick: onAutomationNavClick,
onManageSettingsClick: onManageSettingsClick,
onSiteListNavClick: onSiteListNavClick,
})
),
m$1(Page, { id: "site-list" }, m$1(SiteListPage, { ...props })),
m$1(Page, { id: "automation" }, m$1(AutomationPage, { ...props })),
m$1(
Page,
{ id: "manage-settings" },
m$1(ManageSettingsPage, { ...props })
)
);
}
function DonateGroup() {
return m$1(
"div",
{ class: "m-donate-group" },
m$1(
"a",
{
class: "m-donate-button",
href: DONATE_URL,
target: "_blank",
rel: "noopener noreferrer",
},
m$1("span", { class: "m-donate-button__icon" }),
m$1(
"span",
{ class: "m-donate-button__text" },
getLocalMessage("donate")
)
),
m$1(
"label",
{ class: "m-donate-description" },
"This project is sponsored by you"
)
);
}
function Body$2(props) {
const context = getComponentContext();
context.onCreate(() => {
if (isMobile) {
window.addEventListener("contextmenu", (e) => e.preventDefault());
}
});
context.onRemove(() => {
document.documentElement.classList.remove("preview");
});
return m$1(
"body",
null,
m$1("section", { class: "m-section" }, m$1(Logo, null)),
m$1(
"section",
{ class: "m-section pages-section" },
m$1(Pages, { ...props })
),
m$1("section", { class: "m-section" }, m$1(DonateGroup, null)),
m$1(NewsSection, { ...props }),
m$1(Overlay$1, null)
);
}
const engineNames = [
[ThemeEngines.cssFilter, getLocalMessage("engine_filter")],
[ThemeEngines.svgFilter, getLocalMessage("engine_filter_plus")],
[ThemeEngines.staticTheme, getLocalMessage("engine_static")],
[ThemeEngines.dynamicTheme, getLocalMessage("engine_dynamic")],
];
function openCSSEditor() {
chrome.windows.create({
type: "panel",
url: isFirefox
? "../stylesheet-editor/index.html"
: "ui/stylesheet-editor/index.html",
width: 600,
height: 600,
});
}
function EngineSwitch({ engine, onChange }) {
return m$1(
"div",
{ class: "engine-switch" },
m$1(MultiSwitch, {
value: engineNames.find(([code]) => code === engine)[1],
options: engineNames.map(([, name]) => name),
onChange: (value) =>
onChange(engineNames.find(([, name]) => name === value)[0]),
}),
m$1("span", {
class: {
"engine-switch__css-edit-button": true,
"engine-switch__css-edit-button_active":
engine === ThemeEngines.staticTheme,
},
onclick: openCSSEditor,
}),
m$1(
"label",
{ class: "engine-switch__description" },
getLocalMessage("theme_generation_mode")
)
);
}
function FontSettings({ config, fonts, onChange }) {
return m$1(
"section",
{ class: "font-settings" },
m$1(
"div",
{ class: "font-settings__font-select-container" },
m$1(
"div",
{ class: "font-settings__font-select-container__line" },
m$1(CheckBox, {
checked: config.useFont,
onchange: (e) => onChange({ useFont: e.target.checked }),
}),
m$1(Select$1, {
value: config.fontFamily,
onChange: (value) => onChange({ fontFamily: value }),
options: fonts.reduce((map, font) => {
map[font] = m$1("div", { style: { "font-family": font } }, font);
return map;
}, {}),
})
),
m$1(
"label",
{ class: "font-settings__font-select-container__label" },
getLocalMessage("select_font")
)
),
m$1(UpDown, {
value: config.textStroke,
min: 0,
max: 1,
step: 0.1,
default: 0,
name: getLocalMessage("text_stroke"),
onChange: (value) => onChange({ textStroke: value }),
})
);
}
function compileMarkdown(markdown) {
return markdown
.split("**")
.map((text, i) => (i % 2 ? m$1("strong", null, text) : text));
}
function MoreSettings({ data, actions, fonts }) {
const tab = data.activeTab;
const custom = data.settings.customThemes.find(({ url }) =>
isURLInList(tab.url, url)
);
const filterConfig = custom ? custom.theme : data.settings.theme;
function setConfig(config) {
if (custom) {
custom.theme = { ...custom.theme, ...config };
actions.changeSettings({ customThemes: data.settings.customThemes });
} else {
actions.setTheme(config);
}
}
return m$1(
"section",
{ class: "more-settings" },
m$1(
"div",
{ class: "more-settings__section" },
m$1(FontSettings, {
config: filterConfig,
fonts: fonts,
onChange: setConfig,
})
),
m$1(
"div",
{ class: "more-settings__section" },
isFirefox
? null
: m$1(
"p",
{ class: "more-settings__description" },
compileMarkdown(getLocalMessage("try_experimental_theme_engines"))
),
m$1(EngineSwitch, {
engine: filterConfig.engine,
onChange: (engine) => setConfig({ engine }),
})
),
m$1(
"div",
{ class: "more-settings__section" },
m$1(CustomSettingsToggle, { data: data, actions: actions }),
tab.isProtected
? m$1(
"p",
{
class:
"more-settings__description more-settings__description--warning",
},
getLocalMessage("page_protected").replace(/\n/g, " ")
)
: tab.isInDarkList
? m$1(
"p",
{
class:
"more-settings__description more-settings__description--warning",
},
getLocalMessage("page_in_dark_list").replace(/\n/g, " ")
)
: m$1(
"p",
{ class: "more-settings__description" },
getLocalMessage("only_for_description")
)
),
isFirefox
? m$1(
"div",
{ class: "more-settings__section" },
m$1(Toggle, {
checked: data.settings.changeBrowserTheme,
labelOn: getLocalMessage("custom_browser_theme_on"),
labelOff: getLocalMessage("custom_browser_theme_off"),
onChange: (checked) =>
actions.changeSettings({ changeBrowserTheme: checked }),
}),
m$1(
"p",
{ class: "more-settings__description" },
getLocalMessage("change_browser_theme")
)
)
: null
);
}
function NewsGroup({ news, expanded, onNewsOpen, onClose }) {
return 0;
}
function NewsButton({ active, count, onClick }) {
return m$1(
Button,
{
class: { "news-button": true, "news-button--active": active },
href: "#news",
"data-count": count > 0 && !active ? count : null,
onclick: (e) => {
e.currentTarget.blur();
onClick();
},
},
getLocalMessage("news")
);
}
function SiteListSettings({ data, actions, isFocused }) {
function isSiteUrlValid(value) {
return /^([^\.\s]+?\.?)+$/.test(value);
}
return m$1(
"section",
{ class: "site-list-settings" },
m$1(Toggle, {
class: "site-list-settings__toggle",
checked: data.settings.applyToListedOnly,
labelOn: getLocalMessage("invert_listed_only"),
labelOff: getLocalMessage("not_invert_listed"),
onChange: (value) =>
actions.changeSettings({ applyToListedOnly: value }),
}),
m$1(TextList, {
class: "site-list-settings__text-list",
placeholder: "google.com/maps",
values: data.settings.siteList,
isFocused: isFocused,
onChange: (values) => {
if (values.every(isSiteUrlValid)) {
actions.changeSettings({ siteList: values });
}
},
}),
m$1(ShortcutLink, {
class: "site-list-settings__shortcut",
commandName: "addSite",
shortcuts: data.shortcuts,
textTemplate: (hotkey) =>
hotkey
? `${getLocalMessage("add_site_to_list")}: ${hotkey}`
: getLocalMessage("setup_add_site_hotkey"),
onSetShortcut: (shortcut) => actions.setShortcut("addSite", shortcut),
})
);
}
function openDevTools() {
chrome.windows.create({
type: "panel",
url: isFirefox ? "../devtools/index.html" : "ui/devtools/index.html",
width: 600,
height: 600,
});
}
function Body(props) {
const context = getComponentContext();
const { state, setState } = useState({
activeTab: "Filter",
newsOpen: false,
didNewsSlideIn: false,
moreToggleSettingsOpen: false,
});
if (!props.data.isReady) {
return m$1("body", null, m$1(Loader$1, { complete: false }));
}
if (isMobile || props.data.settings.previewNewDesign) {
return m$1(Body$2, { ...props, fonts: props.fonts });
}
const unreadNews = props.data.news.filter(({ read }) => !read);
const latestNews = props.data.news.length > 0 ? props.data.news[0] : null;
const isFirstNewsUnread = latestNews && !latestNews.read;
context.onRender(() => {
if (
props.data.settings.fetchNews &&
isFirstNewsUnread &&
!state.newsOpen &&
!state.didNewsSlideIn
) {
setTimeout(toggleNews, 750);
}
});
function toggleNews() {
if (state.newsOpen && unreadNews.length > 0) {
props.actions.markNewsAsRead(unreadNews.map(({ id }) => id));
}
setState({
newsOpen: !state.newsOpen,
didNewsSlideIn: state.didNewsSlideIn || !state.newsOpen,
});
}
function onNewsOpen(...news) {
const unread = news.filter(({ read }) => !read);
if (unread.length > 0) {
props.actions.markNewsAsRead(unread.map(({ id }) => id));
}
}
let displayedNewsCount = unreadNews.length;
if (unreadNews.length > 0) {
const latest = new Date(unreadNews[0].date);
const today = new Date();
const newsWereLongTimeAgo =
latest.getTime() < today.getTime() - getDuration({ days: 14 });
if (newsWereLongTimeAgo) {
displayedNewsCount = 0;
}
}
const globalThemeEngine = props.data.settings.theme.engine;
const devtoolsData = props.data.devtools;
const hasCustomFixes =
(globalThemeEngine === ThemeEngines.dynamicTheme &&
devtoolsData.hasCustomDynamicFixes) ||
([ThemeEngines.cssFilter, ThemeEngines.svgFilter].includes(
globalThemeEngine
) &&
devtoolsData.hasCustomFilterFixes) ||
(globalThemeEngine === ThemeEngines.staticTheme &&
devtoolsData.hasCustomStaticFixes);
function toggleMoreToggleSettings() {
setState({ moreToggleSettingsOpen: !state.moreToggleSettingsOpen });
}
return m$1(
"body",
{ class: { "ext-disabled": !props.data.isEnabled } },
m$1(Loader$1, { complete: true }),
m$1(Header, {
data: props.data,
actions: props.actions,
onMoreToggleSettingsClick: toggleMoreToggleSettings,
}),
m$1(TabPanel, {
activeTab: state.activeTab,
onSwitchTab: (tab) => setState({ activeTab: tab }),
tabs: isThunderbird
? {
Filter: m$1(FilterSettings, {
data: props.data,
actions: props.actions,
}),
More: m$1(MoreSettings, {
data: props.data,
actions: props.actions,
fonts: props.fonts,
}),
}
: {
Filter: m$1(FilterSettings, {
data: props.data,
actions: props.actions,
}),
"Site list": m$1(SiteListSettings, {
data: props.data,
actions: props.actions,
isFocused: state.activeTab === "Site list",
}),
More: m$1(MoreSettings, {
data: props.data,
actions: props.actions,
fonts: props.fonts,
}),
},
tabLabels: {
Filter: getLocalMessage("filter"),
"Site list": getLocalMessage("site_list"),
More: getLocalMessage("more"),
},
})
);
}
var Body$1 = compose(Body, withState, withForms);
function popupHasBuiltInBorders() {
return Boolean(
chromiumVersion &&
!isVivaldi &&
!isYaBrowser &&
!isOpera &&
isWindows &&
compareChromeVersions(chromiumVersion, "62.0.3167.0") < 0
);
}
function popupHasBuiltInHorizontalBorders() {
return Boolean(
chromiumVersion &&
!isVivaldi &&
!isYaBrowser &&
!isEdge &&
!isOpera &&
((isWindows &&
compareChromeVersions(chromiumVersion, "62.0.3167.0") >= 0 &&
compareChromeVersions(chromiumVersion, "74.0.0.0") < 0) ||
(isMacOS &&
compareChromeVersions(chromiumVersion, "67.0.3373.0") >= 0 &&
compareChromeVersions(chromiumVersion, "73.0.3661.0") < 0))
);
}
function fixNotClosingPopupOnNavigation() {
document.addEventListener("click", (e) => {
if (e.defaultPrevented || e.button === 2) {
return;
}
let target = e.target;
while (target && !(target instanceof HTMLAnchorElement)) {
target = target.parentElement;
}
if (target && target.hasAttribute("href")) {
chrome.tabs.create({ url: target.getAttribute("href") });
e.preventDefault();
if (!isThunderbird) {
window.close();
}
}
});
}
function renderBody(data, fonts, actions) {
if (data.settings.previewNewDesign) {
if (!document.documentElement.classList.contains("preview")) {
document.documentElement.classList.add("preview");
}
} else if (document.documentElement.classList.contains("preview")) {
document.documentElement.classList.remove("preview");
}
if (data.news && data.news.length > 0) {
const latest = data.news[0];
if (latest && !latest.displayed) {
actions.markNewsAsDisplayed([latest.id]);
}
}
sync(
document.body,
m$1(Body$1, { data: data, actions: actions, fonts: fonts })
);
}
async function start() {
const connector = new Connector();
window.addEventListener("unload", () => connector.disconnect());
const [data, fonts] = await Promise.all([
connector.getData(),
getFontList(),
]);
renderBody(data, fonts, connector);
connector.subscribeToChanges((data) => renderBody(data, fonts, connector));
}
addEventListener("load", start);
document.documentElement.classList.toggle("mobile", isMobile);
document.documentElement.classList.toggle("firefox", isFirefox);
document.documentElement.classList.toggle(
"built-in-borders",
popupHasBuiltInBorders()
);
document.documentElement.classList.toggle(
"built-in-horizontal-borders",
popupHasBuiltInHorizontalBorders()
);
if (isFirefox) {
fixNotClosingPopupOnNavigation();
}
})();