import 'webext-base-css/webext-base.css';
import './options.css';
import React from 'dom-chef';
import cache from 'webext-storage-cache';
import domify from 'doma';
import select from 'select-dom';
import delegate from 'delegate-it';
import fitTextarea from 'fit-textarea';
import * as indentTextarea from 'indent-textarea';
import {perDomainOptions} from './options-storage';
function moveDisabledFeaturesToTop(): void {
const container = select('.js-features')!;
for (const unchecked of select.all('.feature [type=checkbox]:not(:checked)', container).reverse()) {
// .reverse() needed to preserve alphabetical order while prepending
container.prepend(unchecked.closest('.feature')!);
}
}
function buildFeatureCheckbox({id, description, screenshot}: FeatureMeta): HTMLElement {
const descriptionElement = domify.one(description)!;
descriptionElement.className = 'description';
return (
);
}
async function clearCacheHandler(event: Event): Promise {
await cache.clear();
const button = event.target as HTMLButtonElement;
const initialText = button.textContent;
button.textContent = 'Cache cleared!';
button.disabled = true;
setTimeout(() => {
button.textContent = initialText;
button.disabled = false;
}, 2000);
}
function featuresFilterHandler(event: Event): void {
const keywords = (event.currentTarget as HTMLInputElement).value.toLowerCase()
.replace(/\W/g, ' ')
.split(/\s+/)
.filter(Boolean); // Ignore empty strings
for (const feature of select.all('.feature')) {
feature.hidden = !keywords.every(word => feature.dataset.text!.includes(word));
}
}
async function highlightNewFeatures(): Promise {
const {featuresAlreadySeen} = await browser.storage.local.get({featuresAlreadySeen: {}});
const isFirstVisit = Object.keys(featuresAlreadySeen).length === 0;
const tenDaysAgo = Date.now() - (10 * 24 * 60 * 60 * 1000);
for (const feature of select.all('.feature [type=checkbox]')) {
if (!(feature.id in featuresAlreadySeen)) {
featuresAlreadySeen[feature.id] = isFirstVisit ? tenDaysAgo : Date.now();
}
if (featuresAlreadySeen[feature.id] > tenDaysAgo) {
feature.parentElement!.classList.add('feature-new');
}
}
void browser.storage.local.set({featuresAlreadySeen});
}
async function generateDom(): Promise {
// Generate list
select('.js-features')!.append(...__featuresMeta__.map(buildFeatureCheckbox));
// Update list from saved options
await perDomainOptions.syncForm('form');
// Decorate list
moveDisabledFeaturesToTop();
void highlightNewFeatures();
// Move debugging tools higher when side-loaded
if (process.env.NODE_ENV === 'development') {
select('#debugging-position')!.replaceWith(select('#debugging')!);
}
}
function addEventListeners(): void {
// Update domain-dependent page content when the domain is changed
select('.js-options-sync-selector')?.addEventListener('change', ({currentTarget: dropdown}) => {
select('#personal-token-link')!.host = (dropdown as HTMLSelectElement).value;
});
// Refresh page when permissions are changed (because the dropdown selector needs to be regenerated)
browser.permissions.onRemoved.addListener(() => location.reload());
browser.permissions.onAdded.addListener(() => location.reload());
// Improve textareas editing
fitTextarea.watch('textarea');
indentTextarea.watch('textarea');
// Filter feature list
select('#filter-features')!.addEventListener('input', featuresFilterHandler);
// Add cache clearer
select('#clear-cache')!.addEventListener('click', clearCacheHandler);
// Ensure all links open in a new tab #3181
delegate(document, '[href^="http"]', 'click', (event: delegate.Event) => {
event.preventDefault();
window.open(event.delegateTarget.href);
});
}
async function init(): Promise {
await generateDom();
addEventListeners();
}
void init();