123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231 |
- /*******************************************************************************
- ηMatrix - a browser extension to black/white list requests.
- Copyright (C) 2014-2019 Raymond Hill
- Copyright (C) 2019-2022 Alessio Vanni
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program. If not, see {http://www.gnu.org/licenses/}.
- Home: https://gitlab.com/vannilla/ematrix
- uMatrix Home: https://github.com/gorhill/uMatrix
- */
- 'use strict';
- // This file should always be included at the end of the `body` tag, so as
- // to ensure all i18n targets are already loaded.
- (function() {
- // https://github.com/gorhill/uBlock/issues/2084
- // Anything else than <a>, <b>, <code>, <em>, <i>, <input>, and
- // <span> will be rendered as plain text. For <input>, only the
- // type attribute is allowed. For <a>, only href attribute must
- // be present, and it MUST starts with `https://`, and includes no
- // single- or double-quotes. No HTML entities are allowed, there
- // is code to handle existing HTML entities already present in
- // translation files until they are all gone.
- //
- // ηMatrix:
- // We're not going to remove anything, but rather going to make
- // full use of HTML tags and HTML entities in translations. Of
- // course, this check for safe tags is going to stay and will be
- // used to check the source text. The above comment is kept just
- // in case.
- let reSafeTags =
- /^([\s\S]*?)<(b|blockquote|code|em|i|kbd|span|sup)>(.+?)<\/\2>([\s\S]*)$/;
- let reSafeInput = /^([\s\S]*?)<(input type="[^"]+")>(.*?)([\s\S]*)$/;
- let reInput = /^input type=(['"])([a-z]+)\1$/;
- let reSafeLink =
- /^([\s\S]*?)<(a href=['"]https?:\/\/[^'" <>]+['"])>(.+?)<\/a>([\s\S]*)$/;
- let reLink = /^a href=(['"])(https?:\/\/[^'"]+)\1$/;
- let safeTextToTagNode = function (text) {
- let matches;
- let node;
- if (text.lastIndexOf('a ', 0) === 0) {
- matches = reLink.exec(text);
- if (matches === null) {
- return null;
- }
- node = document.createElement('a');
- node.setAttribute('href', matches[2]);
- return node;
- }
- if (text.lastIndexOf('input ', 0) === 0) {
- matches = reInput.exec(text);
- if (matches === null) {
- return null;
- }
- node = document.createElement('input');
- node.setAttribute('type', matches[2]);
- return node;
- }
- // Firefox extension validator warns if using a variable as
- // argument for document.createElement().
- // ηMatrix: is it important for us?
- // ηMatrix (4.4.3 onwards): let's just use the variable and
- // hope for the best, no need to have a redundant switch.
- /*
- switch (text) {
- case 'b':
- return document.createElement('b');
- case 'blockquote':
- return document.createElement('blockquote');
- case 'code':
- return document.createElement('code');
- case 'em':
- return document.createElement('em');
- case 'i':
- return document.createElement('i');
- case 'kbd':
- return document.createElement('kbd');
- case 'span':
- return document.createElement('span');
- case 'sup':
- return document.createElement('sup');
- default:
- break;
- }
- */
- return document.createElement(text);
- };
- let safeTextToTextNode = function (text) {
- if (text.indexOf('&') !== -1) {
- text = text
- .replace(/“/g, '“')
- .replace(/”/g, '”')
- .replace(/‘/g, '‘')
- .replace(/’/g, '’')
- .replace(/</g, '<')
- .replace(/>/g, '>')
- .replace(/'/g, '\'');
- }
- return document.createTextNode(text);
- };
- let safeTextToDOM = function (text, parent) {
- if (text === '') {
- return;
- }
- if (text.indexOf('<') === -1) {
- return parent.appendChild(safeTextToTextNode(text));
- }
- // Using the raw <p> element is not allowed for security reason,
- // but it's good for formatting content, so here it's substituted
- // for a safer equivalent (for the extension.)
- text = text
- .replace(/^<p>|<\/p>/g, '')
- .replace(/<p>/g, '\n\n');
- let matches;
- let matches1 = reSafeTags.exec(text);
- let matches2 = reSafeLink.exec(text);
- if (matches1 !== null && matches2 !== null) {
- matches = matches1.index < matches2.index ? matches1 : matches2;
- } else if (matches1 !== null) {
- matches = matches1;
- } else if (matches2 !== null) {
- matches = matches2;
- } else {
- matches = reSafeInput.exec(text);
- }
- if (matches === null) {
- parent.appendChild(safeTextToTextNode(text));
- return;
- }
- safeTextToDOM(matches[1], parent);
- let node = safeTextToTagNode(matches[2]) || parent;
- safeTextToDOM(matches[3], node);
- parent.appendChild(node);
- safeTextToDOM(matches[4], parent);
- };
- // Helper to deal with the i18n'ing of HTML files.
- vAPI.i18n.render = function (context) {
- let docu = document;
- let root = context || docu;
- let i, elem, text;
- let elems = root.querySelectorAll('[data-i18n]');
- let n = elems.length;
- for (i=0; i<n; ++i) {
- elem = elems[i];
- text = vAPI.i18n(elem.getAttribute('data-i18n'));
- if (!text) {
- continue;
- }
- // TODO: remove once it's all replaced with <input type="...">
- if (text.indexOf('{') !== -1) {
- text =
- text.replace(/\{\{input:([^}]+)\}\}/g, '<input type="$1">');
- }
- safeTextToDOM(text, elem);
- }
- uDom('[title]', context).forEach(function (elem) {
- let title = vAPI.i18n(elem.attr('title'));
- if (title) {
- elem.attr('title', title);
- }
- });
- uDom('[placeholder]', context).forEach(function (elem) {
- elem.attr('placeholder', vAPI.i18n(elem.attr('placeholder')));
- });
- uDom('[data-i18n-tip]', context).forEach(function (elem) {
- elem.attr('data-tip',
- vAPI.i18n(elem.attr('data-i18n-tip'))
- .replace(/<br>/g, '\n')
- .replace(/\n{3,}/g, '\n\n'));
- });
- };
- vAPI.i18n.render();
- vAPI.i18n.renderElapsedTimeToString = function (tstamp) {
- let value = (Date.now() - tstamp) / 60000;
- if (value < 2) {
- return vAPI.i18n('elapsedOneMinuteAgo');
- }
- if (value < 60) {
- return vAPI
- .i18n('elapsedManyMinutesAgo')
- .replace('{{value}}', Math.floor(value).toLocaleString());
- }
- value /= 60;
- if (value < 2) {
- return vAPI.i18n('elapsedOneHourAgo');
- }
- if (value < 24) {
- return vAPI
- .i18n('elapsedManyHoursAgo')
- .replace('{{value}}', Math.floor(value).toLocaleString());
- }
- value /= 24;
- if (value < 2) {
- return vAPI.i18n('elapsedOneDayAgo');
- }
- return vAPI
- .i18n('elapsedManyDaysAgo')
- .replace('{{value}}', Math.floor(value).toLocaleString());
- };
- })();
|