stylus/popup/popup-exclusions.js
2018-10-10 23:05:20 +08:00

192 lines
5.9 KiB
JavaScript

/*
global messageBox t $create getActiveTab tryRegExp animateElement $ API
*/
'use strict';
/* exported popupExclusions */
const popupExclusions = (() => {
// return matches on url ending to prevent duplicates in the exclusion list
// e.g. http://test.com and http://test.com/* are equivalent
// this function would return ['', '/*']
function exclusionExists(array, value) {
const match = [];
['', '*', '/', '/*'].forEach(ending => {
if (array.includes(value + ending)) {
match.push(ending);
}
});
return match;
}
/* Modal in Popup.html */
function processURL(url) {
const results = [];
const protocol = url.match(/\w+:\/\//);
// remove ending '/', protocol, hash & search strings
const parts = url.replace(/\/$/, '').replace(/(\w+:\/\/|[#?].*$)/g, '').split('/');
const domain = parts[0].split('.');
/*
Domain: a.b.com
Domain: b.com
Prefix: https://a.b.com
Prefix: https://a.b.com/current
Prefix: https://a.b.com/current/page
*/
while (parts.length > 1) {
results.push([t('excludedPrefix'), protocol + parts.join('/')]);
parts.pop();
}
while (domain.length > 1) {
results.push([t('excludedDomain'), domain.join('.')]);
domain.shift();
}
return results.reverse();
}
function shortenURL(text) {
const len = text.length;
let prefix = '\u2026';
// account for URL that end with a '/'
let index = (text.endsWith('/') ? text.substring(0, len - 1) : text).lastIndexOf('/');
if (index < 0 || len - index < 2) {
index = 0;
prefix = '';
}
return prefix + text.substring(index, len);
}
function createOption(option) {
// ["Domain/Prefix", "{url}"]
return $create('option', {
value: option[1],
title: option[1],
textContent: `${option[0]}: ${shortenURL(option[1])}`
});
}
function getMultiOptions({select, selectedOnly} = {}) {
return [...select.children].reduce((acc, opt) => {
if (selectedOnly && opt.selected || !selectedOnly) {
acc.push(opt.value);
}
return acc;
}, []);
}
function updatePopupContent(url) {
const options = processURL(url);
const renderBin = document.createDocumentFragment();
options.map(option => renderBin.appendChild(createOption(option)));
$('#popup-exclusions').textContent = '';
$('#popup-exclusions').appendChild(renderBin);
}
function getIframeURLs(style) {
getActiveTab().then(tab => {
if (tab && tab.status === 'complete') {
chrome.webNavigation.getAllFrames({
tabId: tab.id
}, frames => {
const urls = frames.reduce((acc, frame) => [...acc, ...processURL(frame.url)], []);
updateSelections(style, urls);
});
}
});
}
function selectExclusions(exclusions) {
const select = $('#exclude select');
const excludes = Object.keys(exclusions || {});
[...select.children].forEach(option => {
if (exclusionExists(excludes, option.value).length) {
option.selected = true;
}
});
}
function updateSelections(style, newOptions = []) {
const wrap = $('#exclude');
const select = $('select', wrap);
if (newOptions.length) {
const currentOptions = [...select.children].map(opt => opt.value);
newOptions.forEach(opt => {
if (!currentOptions.includes(opt[1])) {
select.appendChild(createOption(opt));
// newOptions may have duplicates (e.g. multiple iframes from same source)
currentOptions.push(opt[1]);
}
});
select.size = select.children.length;
// hide select, then calculate & adjust height
select.style.height = '0';
document.body.style.height = `${select.scrollHeight + wrap.offsetHeight}px`;
select.style.height = '';
}
selectExclusions(style.exclusions);
}
function isExcluded(matchUrl, exclusions = {}) {
const values = Object.values(exclusions);
return values.length && values.some(exclude => tryRegExp(exclude).test(matchUrl));
}
function openPopupDialog(entry, tabURL) {
const style = entry.styleMeta;
updateSelections(style, updatePopupContent(tabURL));
getIframeURLs(style);
const box = $('#exclude');
box.dataset.display = true;
box.style.cssText = '';
$('strong', box).textContent = style.name;
$('[data-cmd="ok"]', box).focus();
$('[data-cmd="ok"]', box).onclick = () => confirm(true);
$('[data-cmd="cancel"]', box).onclick = () => confirm(false);
window.onkeydown = event => {
const keyCode = event.keyCode || event.which;
if (!event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey
&& (keyCode === 13 || keyCode === 27)) {
event.preventDefault();
confirm(keyCode === 13);
}
};
function confirm(ok) {
window.onkeydown = null;
animateElement(box, {
className: 'lights-on',
onComplete: () => (box.dataset.display = false),
});
document.body.style.height = '';
const excluded = isExcluded(tabURL, style.exclusions);
if (ok) {
handlePopupSave(style);
entry.styleMeta = style;
entry.classList.toggle('excluded', excluded);
}
}
return Promise.resolve();
}
function handlePopupSave(style) {
if (!Array.isArray(style.exclusions)) {
style.exclusions = [];
}
const current = new Set(style.exclusions);
const select = $('#popup-exclusions', messageBox.element);
const all = getMultiOptions({select});
const selected = new Set(getMultiOptions({select, selectedOnly: true}));
for (const value of all) {
if (current.has(value) && !selected.has(value)) {
const index = style.exclusions.indexOf(value);
style.exclusions.splice(index, 1);
}
if (!current.has(value) && selected.has(value)) {
style.exclusions.push(value);
}
}
API.setStyleExclusions(style.id, style.exclusions);
}
return {openPopupDialog, selectExclusions, isExcluded};
})();