2017-03-26 02:30:59 +00:00
|
|
|
'use strict';
|
|
|
|
|
2012-04-16 01:56:12 +00:00
|
|
|
function t(key, params) {
|
2020-11-18 11:17:15 +00:00
|
|
|
const s = chrome.i18n.getMessage(key, params);
|
|
|
|
if (!s) throw `Missing string "${key}"`;
|
2017-03-26 02:30:59 +00:00
|
|
|
return s;
|
2012-04-16 01:56:12 +00:00
|
|
|
}
|
2017-03-26 02:30:59 +00:00
|
|
|
|
2020-11-18 11:17:15 +00:00
|
|
|
Object.assign(t, {
|
|
|
|
template: {},
|
|
|
|
DOMParser: new DOMParser(),
|
|
|
|
ALLOWED_TAGS: 'a,b,code,i,sub,sup,wbr'.split(','),
|
|
|
|
RX_WORD_BREAK: new RegExp([
|
|
|
|
'(',
|
|
|
|
/[\d\w\u007B-\uFFFF]{10}/,
|
|
|
|
'|',
|
|
|
|
/[\d\w\u007B-\uFFFF]{5,10}[!-/]/,
|
|
|
|
'|',
|
|
|
|
/((?!\s)\W){10}/,
|
|
|
|
')',
|
|
|
|
/(?!\b|\s|$)/,
|
|
|
|
].map(rx => rx.source || rx).join(''), 'g'),
|
|
|
|
|
|
|
|
HTML(html) {
|
|
|
|
return typeof html !== 'string'
|
|
|
|
? html
|
|
|
|
: /<\w+/.test(html) // check for html tags
|
|
|
|
? t.createHtml(html.replace(/>\n\s*</g, '><').trim())
|
|
|
|
: document.createTextNode(html);
|
|
|
|
},
|
|
|
|
|
|
|
|
NodeList(nodes) {
|
|
|
|
const PREFIX = 'i18n-';
|
|
|
|
for (let n = nodes.length; --n >= 0;) {
|
|
|
|
const node = nodes[n];
|
|
|
|
if (node.nodeType !== Node.ELEMENT_NODE) {
|
2017-03-26 02:30:59 +00:00
|
|
|
continue;
|
|
|
|
}
|
2020-11-18 11:17:15 +00:00
|
|
|
if (node.localName === 'template') {
|
|
|
|
t.createTemplate(node);
|
|
|
|
continue;
|
2017-03-26 02:30:59 +00:00
|
|
|
}
|
2020-11-18 11:17:15 +00:00
|
|
|
for (let a = node.attributes.length; --a >= 0;) {
|
|
|
|
const attr = node.attributes[a];
|
|
|
|
const name = attr.nodeName;
|
|
|
|
if (!name.startsWith(PREFIX)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const type = name.substr(PREFIX.length);
|
|
|
|
const value = t(attr.value);
|
|
|
|
let toInsert, before;
|
|
|
|
switch (type) {
|
|
|
|
case 'word-break':
|
|
|
|
// we already know that: hasWordBreak
|
|
|
|
break;
|
|
|
|
case 'text':
|
|
|
|
before = node.firstChild;
|
|
|
|
// fallthrough to text-append
|
|
|
|
case 'text-append':
|
|
|
|
toInsert = t.createText(value);
|
|
|
|
break;
|
|
|
|
case 'html': {
|
|
|
|
toInsert = t.createHtml(value);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
node.setAttribute(type, value);
|
|
|
|
}
|
|
|
|
t.stopObserver();
|
|
|
|
if (toInsert) {
|
|
|
|
node.insertBefore(toInsert, before || null);
|
|
|
|
}
|
|
|
|
node.removeAttribute(name);
|
2017-12-07 20:21:27 +00:00
|
|
|
}
|
2017-03-26 02:30:59 +00:00
|
|
|
}
|
2020-11-18 11:17:15 +00:00
|
|
|
},
|
2017-12-07 20:21:27 +00:00
|
|
|
|
2020-11-18 11:17:15 +00:00
|
|
|
/** Adds soft hyphens every 10 characters to ensure the long words break before breaking the layout */
|
|
|
|
breakWord(text) {
|
|
|
|
return text.length <= 10 ? text :
|
|
|
|
text.replace(t.RX_WORD_BREAK, '$&\u00AD');
|
|
|
|
},
|
|
|
|
|
|
|
|
createTemplate(node) {
|
2017-12-07 20:21:27 +00:00
|
|
|
const elements = node.content.querySelectorAll('*');
|
2020-11-18 11:17:15 +00:00
|
|
|
t.NodeList(elements);
|
|
|
|
t.template[node.dataset.id] = elements[0];
|
2017-12-07 20:21:27 +00:00
|
|
|
// compress inter-tag whitespace to reduce number of DOM nodes by 25%
|
|
|
|
const walker = document.createTreeWalker(elements[0], NodeFilter.SHOW_TEXT);
|
|
|
|
const toRemove = [];
|
|
|
|
while (walker.nextNode()) {
|
|
|
|
const textNode = walker.currentNode;
|
2020-11-18 11:17:15 +00:00
|
|
|
if (!/[\xA0\S]/.test(textNode.nodeValue)) { // allow \xA0 to keep
|
2017-12-07 20:21:27 +00:00
|
|
|
toRemove.push(textNode);
|
|
|
|
}
|
|
|
|
}
|
2020-11-18 11:17:15 +00:00
|
|
|
t.stopObserver();
|
2017-12-07 20:21:27 +00:00
|
|
|
toRemove.forEach(el => el.remove());
|
2020-11-18 11:17:15 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
createText(str) {
|
|
|
|
return document.createTextNode(t.breakWord(str));
|
|
|
|
},
|
|
|
|
|
|
|
|
createHtml(str, trusted) {
|
|
|
|
const root = t.DOMParser.parseFromString(str, 'text/html').body;
|
|
|
|
if (!trusted) {
|
|
|
|
t.sanitizeHtml(root);
|
|
|
|
} else if (str.includes('i18n-')) {
|
|
|
|
t.NodeList(root.getElementsByTagName('*'));
|
|
|
|
}
|
2017-12-07 20:21:27 +00:00
|
|
|
const bin = document.createDocumentFragment();
|
2020-11-18 11:17:15 +00:00
|
|
|
while (root.firstChild) {
|
|
|
|
bin.appendChild(root.firstChild);
|
2017-12-07 20:21:27 +00:00
|
|
|
}
|
|
|
|
return bin;
|
2020-11-18 11:17:15 +00:00
|
|
|
},
|
2015-03-27 10:30:07 +00:00
|
|
|
|
2020-11-18 11:17:15 +00:00
|
|
|
sanitizeHtml(root) {
|
|
|
|
const toRemove = [];
|
|
|
|
const walker = document.createTreeWalker(root);
|
|
|
|
for (let n; (n = walker.nextNode());) {
|
|
|
|
if (n.nodeType === Node.TEXT_NODE) {
|
|
|
|
n.nodeValue = t.breakWord(n.nodeValue);
|
|
|
|
} else if (t.ALLOWED_TAGS.includes(n.localName)) {
|
|
|
|
for (const attr of n.attributes) {
|
|
|
|
if (n.localName !== 'a' || attr.localName !== 'href' || !/^https?:/.test(n.href)) {
|
|
|
|
n.removeAttribute(attr.name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
toRemove.push(n);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (const n of toRemove) {
|
|
|
|
const parent = n.parentNode;
|
|
|
|
if (parent) parent.removeChild(n); // not using .remove() as there may be a non-element
|
|
|
|
}
|
|
|
|
},
|
2015-03-27 10:30:07 +00:00
|
|
|
|
2020-11-18 11:17:15 +00:00
|
|
|
formatDate(date) {
|
|
|
|
if (!date) {
|
|
|
|
return '';
|
|
|
|
}
|
2017-09-12 12:08:09 +00:00
|
|
|
try {
|
2020-11-18 11:17:15 +00:00
|
|
|
const newDate = new Date(Number(date) || date);
|
|
|
|
const string = newDate.toLocaleDateString([chrome.i18n.getUILanguage(), 'en'], {
|
|
|
|
day: '2-digit',
|
|
|
|
month: 'short',
|
|
|
|
year: newDate.getYear() === new Date().getYear() ? undefined : '2-digit',
|
|
|
|
});
|
|
|
|
return string === 'Invalid Date' ? '' : string;
|
|
|
|
} catch (e) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
(() => {
|
|
|
|
const observer = new MutationObserver(process);
|
|
|
|
let observing = false;
|
|
|
|
Object.assign(t, {
|
|
|
|
stopObserver() {
|
|
|
|
if (observing) {
|
|
|
|
observing = false;
|
|
|
|
observer.disconnect();
|
2017-12-07 20:21:27 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
2020-11-18 11:17:15 +00:00
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
process(observer.takeRecords());
|
|
|
|
t.stopObserver();
|
|
|
|
}, {once: true});
|
2017-12-07 20:21:27 +00:00
|
|
|
|
2020-11-18 11:17:15 +00:00
|
|
|
t.NodeList(document.getElementsByTagName('*'));
|
|
|
|
start();
|
2017-03-23 17:49:23 +00:00
|
|
|
|
2017-12-07 20:21:27 +00:00
|
|
|
function process(mutations) {
|
2020-11-18 11:17:15 +00:00
|
|
|
mutations.forEach(m => t.NodeList(m.addedNodes));
|
|
|
|
start();
|
2017-12-07 20:21:27 +00:00
|
|
|
}
|
|
|
|
|
2020-11-18 11:17:15 +00:00
|
|
|
function start() {
|
|
|
|
if (!observing) {
|
|
|
|
observing = true;
|
|
|
|
observer.observe(document, {subtree: true, childList: true});
|
2017-04-17 15:54:39 +00:00
|
|
|
}
|
2017-12-07 20:21:27 +00:00
|
|
|
}
|
2020-11-18 11:17:15 +00:00
|
|
|
})();
|