This commit is contained in:
derv82 2017-12-04 20:10:52 -08:00
commit f580c10e85
42 changed files with 1209 additions and 886 deletions

View File

@ -55,7 +55,8 @@ globals:
animateElement: false animateElement: false
$: false $: false
$$: false $$: false
$element: false $create: false
$createLink: false
# prefs.js # prefs.js
prefs: false prefs: false
setupLivePrefs: false setupLivePrefs: false
@ -236,7 +237,7 @@ rules:
one-var: [0] one-var: [0]
operator-assignment: [2, always] operator-assignment: [2, always]
operator-linebreak: [2, after, overrides: {"?": ignore, ":": ignore, "&&": ignore, "||": ignore}] operator-linebreak: [2, after, overrides: {"?": ignore, ":": ignore, "&&": ignore, "||": ignore}]
padded-blocks: [2, never] padded-blocks: [0]
prefer-numeric-literals: [2] prefer-numeric-literals: [2]
prefer-rest-params: [0] prefer-rest-params: [0]
prefer-const: [1, {destructuring: any, ignoreReadBeforeAssign: true}] prefer-const: [1, {destructuring: any, ignoreReadBeforeAssign: true}]

View File

@ -377,6 +377,10 @@
"message": "Open this style on userstyles.org to customize via 'Advanced Style Settings'", "message": "Open this style on userstyles.org to customize via 'Advanced Style Settings'",
"description": "Tooltip for a button that opens style on userstyles.org for customizing" "description": "Tooltip for a button that opens style on userstyles.org for customizing"
}, },
"filteredStylesAllHidden": {
"message": "Currently applied filters match no styles",
"description": "Text shown when no styles match currently applied filter in the style manager"
},
"findStylesForSite": { "findStylesForSite": {
"message": "Find styles", "message": "Find styles",
"description": "Text for a link that gets a list of styles for the current site" "description": "Text for a link that gets a list of styles for the current site"
@ -774,6 +778,10 @@
"message": "Invalid regexps skipped", "message": "Invalid regexps skipped",
"description": "RegExp test report: label for the invalid expressions" "description": "RegExp test report: label for the invalid expressions"
}, },
"styleRegexpTestNote": {
"message": "Note: use a single \\ for escaping in the regexp input field, which will be automatically converted to \\\\ in the style code as per specification for quoted strings in CSS.",
"description": "RegExp test report: a note displayed at the bottom of the dialog"
},
"styleRegexpPartialExplanation": { "styleRegexpPartialExplanation": {
"message": "This style uses partially matching regexps in violation of <a href='https://developer.mozilla.org/docs/Web/CSS/@document'>CSS4 @document specification</a> which requires a full URL match. The affected CSS sections were not applied to the page. This style was probably created in Stylish-for-Chrome which incorrectly checks 'regexp()' rules since the very first version (known bug)." "message": "This style uses partially matching regexps in violation of <a href='https://developer.mozilla.org/docs/Web/CSS/@document'>CSS4 @document specification</a> which requires a full URL match. The affected CSS sections were not applied to the page. This style was probably created in Stylish-for-Chrome which incorrectly checks 'regexp()' rules since the very first version (known bug)."
}, },

View File

@ -36,7 +36,7 @@
"description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)" "description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)"
}, },
"installButton": { "installButton": {
"message": "安裝", "message": "安裝樣式",
"description": "Label for install button" "description": "Label for install button"
}, },
"styleMetaErrorCheckbox": { "styleMetaErrorCheckbox": {
@ -47,6 +47,10 @@
"message": "無效的 JSON 格式", "message": "無效的 JSON 格式",
"description": "Setting linter config with invalid JSON" "description": "Setting linter config with invalid JSON"
}, },
"popupHotkeysTooltip": {
"message": "點選以檢視可用的快捷鍵",
"description": "Tooltip displayed when hovering the right edge of the extension popup"
},
"optionsBadgeNormal": { "optionsBadgeNormal": {
"message": "背景顏色", "message": "背景顏色",
"description": "" "description": ""
@ -330,6 +334,10 @@
"message": "顯示生效的樣式數目", "message": "顯示生效的樣式數目",
"description": "Label (must be very short) for the checkbox in the toolbar button context menu controlling toolbar badge text." "description": "Label (must be very short) for the checkbox in the toolbar button context menu controlling toolbar badge text."
}, },
"popupHotkeysInfo": {
"message": "<1>-<9>、<0>,數字鍵盤也可以 — 切換第 N 個樣式0 是切換到第 10 個)\n<A>-<Z> 切換以該字母為名稱第一個字的樣式\n<Shift> 開啟編輯器而非切換\n<Numpad +> 啟用列出的樣式\n<Numpad > 停用列出的樣式\n<Numpad *> 與 <`> (倒引號)— 切換初始啟用的樣式;不要在彈出式視窗開啟時套用到後來啟用的樣式,這樣您就可以在測試完後復原初始選擇:僅停用全部,然後切換。<Numpad > <Numpad *>\n更多資訊請見 wiki",
"description": "NOTE1: preserve < and > symbols so that <hotkey> is styled as a key.\nNOTE2: the last line is displayed as a text of the link to the wiki page.\nNOTE3: this is the list of hotkeys displayed after clicking the right edge of the extension popup."
},
"cm_lineWrapping": { "cm_lineWrapping": {
"message": "自動換行", "message": "自動換行",
"description": "Label for the checkbox controlling word wrap option for the style editor." "description": "Label for the checkbox controlling word wrap option for the style editor."
@ -934,7 +942,7 @@
"description": "Label for the checkbox in the style editor." "description": "Label for the checkbox in the style editor."
}, },
"installButtonReinstall": { "installButtonReinstall": {
"message": "重新安裝", "message": "重新安裝樣式",
"description": "Label for reinstall button" "description": "Label for reinstall button"
}, },
"linterInvalidConfigError": { "linterInvalidConfigError": {
@ -1036,7 +1044,7 @@
"description": "" "description": ""
}, },
"installButtonUpdate": { "installButtonUpdate": {
"message": "更新", "message": "更新樣式",
"description": "Label for update button" "description": "Label for update button"
}, },
"backupButtons": { "backupButtons": {
@ -1052,7 +1060,7 @@
"description": "Label for the button to go to the edit style page" "description": "Label for the button to go to the edit style page"
}, },
"installButtonInstalled": { "installButtonInstalled": {
"message": "已安裝", "message": "樣式已安裝",
"description": "Text displayed when the style is successfully installed" "description": "Text displayed when the style is successfully installed"
}, },
"author": { "author": {

View File

@ -238,6 +238,9 @@ function getStyles(options) {
cachedStyles.byId.clear(); cachedStyles.byId.clear();
for (const style of cachedStyles.list) { for (const style of cachedStyles.list) {
cachedStyles.byId.set(style.id, style); cachedStyles.byId.set(style.id, style);
if (!style.name) {
style.name = 'ID: ' + style.id;
}
} }
cachedStyles.mutex.inProgress = false; cachedStyles.mutex.inProgress = false;

View File

@ -56,26 +56,26 @@ var updater = {
'ignoreDigest' option is set on the second manual individual update check on the manage page. 'ignoreDigest' option is set on the second manual individual update check on the manage page.
*/ */
const maybeUpdate = style.usercssData ? maybeUpdateUsercss : maybeUpdateUSO; return Promise.resolve(style)
return (ignoreDigest ? Promise.resolve() : calcStyleDigest(style)) .then([calcStyleDigest][!ignoreDigest ? 0 : 'skip'])
.then(checkIfEdited) .then([checkIfEdited][!ignoreDigest ? 0 : 'skip'])
.then(maybeUpdate) .then([maybeUpdateUSO, maybeUpdateUsercss][style.usercssData ? 1 : 0])
.then(maybeValidate)
.then(maybeSave) .then(maybeSave)
.then(saved => { .then(reportSuccess)
.catch(reportFailure);
function reportSuccess(saved) {
observer(updater.UPDATED, saved); observer(updater.UPDATED, saved);
updater.log(updater.UPDATED + ` #${saved.id} ${saved.name}`); updater.log(updater.UPDATED + ` #${saved.id} ${saved.name}`);
}) }
.catch(err => {
function reportFailure(err) {
observer(updater.SKIPPED, style, err); observer(updater.SKIPPED, style, err);
err = err === 0 ? 'server unreachable' : err; err = err === 0 ? 'server unreachable' : err;
updater.log(updater.SKIPPED + ` (${err}) #${style.id} ${style.name}`); updater.log(updater.SKIPPED + ` (${err}) #${style.id} ${style.name}`);
}); }
function checkIfEdited(digest) { function checkIfEdited(digest) {
if (ignoreDigest) {
return;
}
if (style.originalDigest && style.originalDigest !== digest) { if (style.originalDigest && style.originalDigest !== digest) {
return Promise.reject(updater.EDITED); return Promise.reject(updater.EDITED);
} }
@ -95,6 +95,7 @@ var updater = {
} }
function maybeUpdateUsercss() { function maybeUpdateUsercss() {
// TODO: when sourceCode is > 100kB use http range request(s) for version check
return download(style.updateUrl).then(text => { return download(style.updateUrl).then(text => {
const json = usercss.buildMeta(text); const json = usercss.buildMeta(text);
const {usercssData: {version}} = style; const {usercssData: {version}} = style;
@ -104,6 +105,8 @@ var updater = {
// re-install is invalid in a soft upgrade // re-install is invalid in a soft upgrade
if (!ignoreDigest) { if (!ignoreDigest) {
return Promise.reject(updater.SAME_VERSION); return Promise.reject(updater.SAME_VERSION);
} else if (text === style.sourceCode) {
return Promise.reject(updater.SAME_CODE);
} }
break; break;
case 1: case 1:
@ -114,38 +117,31 @@ var updater = {
}); });
} }
function maybeValidate(json) { function maybeSave(json = {}) {
if (json.usercssData) {
// usercss is already validated while building // usercss is already validated while building
return json; if (!json.usercssData && !styleJSONseemsValid(json)) {
}
if (!styleJSONseemsValid(json)) {
return Promise.reject(updater.ERROR_JSON); return Promise.reject(updater.ERROR_JSON);
} }
return json;
}
function maybeSave(json) {
json.id = style.id; json.id = style.id;
json.updateDate = Date.now(); json.updateDate = Date.now();
json.reason = 'update';
// keep current state
delete json.enabled;
// keep local name customizations
delete json.name;
if (styleSectionsEqual(json, style)) { if (styleSectionsEqual(json, style)) {
// JSONs may have different order of items even if sections are effectively equal // update digest even if save === false as there might be just a space added etc.
// so we'll update the digest anyway
// always update digest even if (save === false)
saveStyle(Object.assign(json, {reason: 'update-digest'})); saveStyle(Object.assign(json, {reason: 'update-digest'}));
return Promise.reject(updater.SAME_CODE); return Promise.reject(updater.SAME_CODE);
} else if (!style.originalDigest && !ignoreDigest) { } else if (!style.originalDigest && !ignoreDigest) {
return Promise.reject(updater.MAYBE_EDITED); return Promise.reject(updater.MAYBE_EDITED);
} }
if (!save) {
return json; return !save ? json :
} json.usercssData
json.reason = 'update'; ? usercssHelper.save(json)
if (json.usercssData) { : saveStyle(json);
return usercssHelper.save(json);
}
json.name = null; // keep local name customizations
return saveStyle(json);
} }
function styleJSONseemsValid(json) { function styleJSONseemsValid(json) {

View File

@ -386,6 +386,7 @@
let restorationCounter = 0; let restorationCounter = 0;
let observing = false; let observing = false;
let sorting = false; let sorting = false;
let timer;
// allow any types of elements between ours, except for the following: // allow any types of elements between ours, except for the following:
const ORDERED_TAGS = ['head', 'body', 'style', 'link']; const ORDERED_TAGS = ['head', 'body', 'style', 'link'];
@ -395,6 +396,10 @@
function init() { function init() {
docRootObserver = new MutationObserver(sortStyleElements); docRootObserver = new MutationObserver(sortStyleElements);
Object.assign(docRootObserver, {start, stop}); Object.assign(docRootObserver, {start, stop});
if (!chrome.app) {
// compensate for Firefox missing a step when loading the tab in background
setTimeout(sortStyleElements);
}
} }
function start({sort = false} = {}) { function start({sort = false} = {}) {
if (sort && sortStyleMap()) { if (sort && sortStyleMap()) {
@ -453,8 +458,8 @@
if (sorting) { if (sorting) {
sorting = false; sorting = false;
docRootObserver.takeRecords(); docRootObserver.takeRecords();
setTimeout(start); clearTimeout(timer);
//docRootObserver.start(); timer = setTimeout(start);
} }
} }
function isMovable(el) { function isMovable(el) {

View File

@ -3,6 +3,9 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="global.css">
<link rel="stylesheet" href="edit/edit.css">
<style id="firefox-transitions-bug-suppressor"> <style id="firefox-transitions-bug-suppressor">
/* restrict to FF */ /* restrict to FF */
@supports (-moz-appearance:none) { @supports (-moz-appearance:none) {
@ -21,7 +24,6 @@
<script src="js/script-loader.js"></script> <script src="js/script-loader.js"></script>
<script src="js/moz-parser.js"></script> <script src="js/moz-parser.js"></script>
<script src="content/apply.js"></script> <script src="content/apply.js"></script>
<link rel="stylesheet" href="edit/edit.css">
<script src="edit/lint.js"></script> <script src="edit/lint.js"></script>
<script src="edit/util.js"></script> <script src="edit/util.js"></script>
<script src="edit/regexp-tester.js"></script> <script src="edit/regexp-tester.js"></script>
@ -70,17 +72,19 @@
<script src="edit/codemirror-default.js"></script> <script src="edit/codemirror-default.js"></script>
<link rel="stylesheet" href="/edit/codemirror-default.css"> <link rel="stylesheet" href="/edit/codemirror-default.css">
<link id="cm-theme" rel="stylesheet">
<template data-id="appliesTo"> <template data-id="appliesTo">
<li> <li>
<div class="select-resizer">
<select name="applies-type" class="applies-type style-contributor"> <select name="applies-type" class="applies-type style-contributor">
<option value="url" i18n-text="appliesUrlOption"></option> <option value="url" i18n-text="appliesUrlOption"></option>
<option value="url-prefix" i18n-text="appliesUrlPrefixOption"></option> <option value="url-prefix" i18n-text="appliesUrlPrefixOption"></option>
<option value="domain" i18n-text="appliesDomainOption"></option> <option value="domain" i18n-text="appliesDomainOption"></option>
<option value="regexp" i18n-text="appliesRegexpOption"></option> <option value="regexp" i18n-text="appliesRegexpOption"></option>
</select> </select>
<input name="applies-value" class="applies-value style-contributor"> <svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
</div>
<input name="applies-value" class="applies-value style-contributor" spellcheck="false">
<button class="remove-applies-to" i18n-text="appliesRemove"></button> <button class="remove-applies-to" i18n-text="appliesRemove"></button>
<button class="add-applies-to" i18n-text="appliesAdd"></button> <button class="add-applies-to" i18n-text="appliesAdd"></button>
</li> </li>
@ -157,7 +161,7 @@
<thead> <thead>
<tr> <tr>
<th><input i18n-placeholder="helpKeyMapHotkey" type="search" class="can-close-on-esc"></th> <th><input i18n-placeholder="helpKeyMapHotkey" type="search" class="can-close-on-esc"></th>
<th><input i18n-placeholder="helpKeyMapCommand" type="search" class="can-close-on-esc"></th> <th><input i18n-placeholder="helpKeyMapCommand" type="search" class="can-close-on-esc" spellcheck="false"></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -179,8 +183,10 @@
<a id="url" target="_blank"><svg class="svg-icon"><use xlink:href="#svg-icon-external-link"/></svg></a> <a id="url" target="_blank"><svg class="svg-icon"><use xlink:href="#svg-icon-external-link"/></svg></a>
</div> </div>
<div id="basic-info-enabled"> <div id="basic-info-enabled">
<label id="enabled-label" i18n-text="styleEnabledLabel">
<input type="checkbox" id="enabled" class="style-contributor"> <input type="checkbox" id="enabled" class="style-contributor">
<label for="enabled" id="enabled-label" i18n-text="styleEnabledLabel"></label><!-- <svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label><!--
--><svg id="toggle-style-help" class="svg-icon info"> --><svg id="toggle-style-help" class="svg-icon info">
<use xlink:href="#svg-icon-help"/> <use xlink:href="#svg-icon-help"/>
</svg> </svg>
@ -201,30 +207,40 @@
<details id="options" data-pref="editor.options.expanded"> <details id="options" data-pref="editor.options.expanded">
<summary><h2 id="options-heading" i18n-text="optionsHeading"></h2></summary> <summary><h2 id="options-heading" i18n-text="optionsHeading"></h2></summary>
<div class="option"> <div class="option">
<label id="lineWrapping-label" i18n-text="cm_lineWrapping">
<input id="editor.lineWrapping" type="checkbox"> <input id="editor.lineWrapping" type="checkbox">
<label id="lineWrapping-label" for="editor.lineWrapping" i18n-text="cm_lineWrapping"></label> <svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
</div> </div>
<div class="option"> <div class="option">
<label id="smartIndent-label" i18n-text="cm_smartIndent">
<input id="editor.smartIndent" type="checkbox"> <input id="editor.smartIndent" type="checkbox">
<label id="smartIndent-label" for="editor.smartIndent" i18n-text="cm_smartIndent"></label> <svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
</div> </div>
<div class="option"> <div class="option">
<label id="indentWithTabs-label" i18n-text="cm_indentWithTabs">
<input id="editor.indentWithTabs" type="checkbox"> <input id="editor.indentWithTabs" type="checkbox">
<label id="indentWithTabs-label" for="editor.indentWithTabs" i18n-text="cm_indentWithTabs"></label> <svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
</div> </div>
<div class="option"> <div class="option">
<label i18n-text="cm_autoCloseBrackets" i18n-title="cm_autoCloseBracketsTooltip">
<input id="editor.autoCloseBrackets" type="checkbox"> <input id="editor.autoCloseBrackets" type="checkbox">
<label for="editor.autoCloseBrackets" <svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
i18n-text="cm_autoCloseBrackets" </label>
i18n-title="cm_autoCloseBracketsTooltip"></label>
</div> </div>
<div class="option"> <div class="option">
<label i18n-text="cm_autocompleteOnTyping">
<input id="editor.autocompleteOnTyping" type="checkbox"> <input id="editor.autocompleteOnTyping" type="checkbox">
<label for="editor.autocompleteOnTyping" i18n-text="cm_autocompleteOnTyping"></label> <svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
</div> </div>
<div class="option"> <div class="option">
<label i18n-text="cm_colorpicker">
<input id="editor.colorpicker" type="checkbox"> <input id="editor.colorpicker" type="checkbox">
<label for="editor.colorpicker" i18n-text="cm_colorpicker"></label> <svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
<span class="svg-inline-wrapper" i18n-title="shortcutsNote"> <span class="svg-inline-wrapper" i18n-title="shortcutsNote">
<svg id="colorpicker-settings" class="svg-icon settings"> <svg id="colorpicker-settings" class="svg-icon settings">
<use xlink:href="#svg-icon-settings"/> <use xlink:href="#svg-icon-settings"/>
@ -232,10 +248,10 @@
</span> </span>
</div> </div>
<div class="option usercss-only"> <div class="option usercss-only">
<label i18n-text="appliesLineWidgetLabel" i18n-title="appliesLineWidgetWarning">
<input id="editor.appliesToLineWidget" type="checkbox"> <input id="editor.appliesToLineWidget" type="checkbox">
<label for="editor.appliesToLineWidget" <svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
i18n-text="appliesLineWidgetLabel" </label>
i18n-title="appliesLineWidgetWarning"></label>
</div> </div>
<div class="option aligned"> <div class="option aligned">
<label id="tabSize-label" for="editor.tabSize" i18n-text="cm_tabSize"></label> <label id="tabSize-label" for="editor.tabSize" i18n-text="cm_tabSize"></label>
@ -243,30 +259,42 @@
</div> </div>
<div class="option aligned"> <div class="option aligned">
<label id="keyMap-label" for="editor.keyMap" i18n-text="cm_keyMap"></label> <label id="keyMap-label" for="editor.keyMap" i18n-text="cm_keyMap"></label>
<div class="select-resizer">
<select id="editor.keyMap"></select> <select id="editor.keyMap"></select>
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
</div>
<span class="svg-inline-wrapper"> <span class="svg-inline-wrapper">
<svg id="keyMap-help" class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg> <svg id="keyMap-help" class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
</span> </span>
</div> </div>
<div class="option aligned"> <div class="option aligned">
<label id="theme-label" for="editor.theme" i18n-text="cm_theme"></label> <label id="theme-label" for="editor.theme" i18n-text="cm_theme"></label>
<div class="select-resizer">
<select id="editor.theme"></select> <select id="editor.theme"></select>
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
</div>
</div> </div>
<div class="option aligned"> <div class="option aligned">
<label id="highlight-label" for="editor.matchHighlight" i18n-text="cm_matchHighlight"></label> <label id="highlight-label" for="editor.matchHighlight" i18n-text="cm_matchHighlight"></label>
<div class="select-resizer">
<select id="editor.matchHighlight"> <select id="editor.matchHighlight">
<option i18n-text="cm_matchHighlightToken" value="token"> <option i18n-text="cm_matchHighlightToken" value="token">
<option i18n-text="cm_matchHighlightSelection" value="selection"> <option i18n-text="cm_matchHighlightSelection" value="selection">
<option i18n-text="genericDisabledLabel" value=""> <option i18n-text="genericDisabledLabel" value="">
</select> </select>
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
</div>
</div> </div>
<div class="option aligned"> <div class="option aligned">
<label id="linter-label" for="editor.linter" i18n-text="cm_linter"></label> <label id="linter-label" for="editor.linter" i18n-text="cm_linter"></label>
<div class="select-resizer">
<select id="editor.linter"> <select id="editor.linter">
<option value="csslint" selected>CSSLint</option> <option value="csslint" selected>CSSLint</option>
<option value="stylelint">Stylelint</option> <option value="stylelint">Stylelint</option>
<option value="" i18n-text="genericDisabledLabel"></option> <option value="" i18n-text="genericDisabledLabel"></option>
</select> </select>
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
</div>
<span class="svg-inline-wrapper" i18n-title="linterConfigTooltip"> <span class="svg-inline-wrapper" i18n-title="linterConfigTooltip">
<svg id="linter-settings" class="svg-icon settings"> <svg id="linter-settings" class="svg-icon settings">
<use xlink:href="#svg-icon-settings"/> <use xlink:href="#svg-icon-settings"/>
@ -299,18 +327,31 @@
</div> </div>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none"> <svg xmlns="http://www.w3.org/2000/svg" style="display: none">
<symbol id="svg-icon-external-link" viewBox="0 0 8 8"> <symbol id="svg-icon-external-link" viewBox="0 0 8 8">
<path d="M0 0v8h8v-2h-1v1h-6v-6h1v-1h-2zm4 0l1.5 1.5-2.5 2.5 1 1 2.5-2.5 1.5 1.5v-4h-4z"></path> <path d="M0 0v8h8v-2h-1v1h-6v-6h1v-1h-2zm4 0l1.5 1.5-2.5 2.5 1 1 2.5-2.5 1.5 1.5v-4h-4z"></path>
</symbol> </symbol>
<symbol id="svg-icon-help" viewBox="0 0 14 16" i18n-alt="helpAlt"> <symbol id="svg-icon-help" viewBox="0 0 14 16" i18n-alt="helpAlt">
<path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path> <path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path>
</symbol> </symbol>
<symbol id="svg-icon-close" viewBox="0 0 12 16"> <symbol id="svg-icon-close" viewBox="0 0 12 16">
<path fill-rule="evenodd" d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48z"></path> <path fill-rule="evenodd" d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48z"></path>
</symbol> </symbol>
<symbol id="svg-icon-settings" viewBox="0 0 16 16"> <symbol id="svg-icon-settings" viewBox="0 0 16 16">
<path d="M8,0C7.6,0,7.3,0,6.9,0.1v2.2C6.1,2.5,5.4,2.8,4.8,3.2L3.2,1.6c-0.6,0.4-1.1,1-1.6,1.6l1.6,1.6C2.8,5.4,2.5,6.1,2.3,6.9H0.1C0,7.3,0,7.6,0,8c0,0.4,0,0.7,0.1,1.1h2.2c0.1,0.8,0.4,1.5,0.9,2.1l-1.6,1.6c0.4,0.6,1,1.1,1.6,1.6l1.6-1.6c0.6,0.4,1.4,0.7,2.1,0.9v2.2C7.3,16,7.6,16,8,16c0.4,0,0.7,0,1.1-0.1v-2.2c0.8-0.1,1.5-0.4,2.1-0.9l1.6,1.6c0.6-0.4,1.1-1,1.6-1.6l-1.6-1.6c0.4-0.6,0.7-1.4,0.9-2.1h2.2C16,8.7,16,8.4,16,8c0-0.4,0-0.7-0.1-1.1h-2.2c-0.1-0.8-0.4-1.5-0.9-2.1l1.6-1.6c-0.4-0.6-1-1.1-1.6-1.6l-1.6,1.6c-0.6-0.4-1.4-0.7-2.1-0.9V0.1C8.7,0,8.4,0,8,0z M8,4.3c2.1,0,3.7,1.7,3.7,3.7c0,0,0,0,0,0c0,2.1-1.7,3.7-3.7,3.7c0,0,0,0,0,0c-2.1,0-3.7-1.7-3.7-3.7c0,0,0,0,0,0C4.3,5.9,5.9,4.3,8,4.3C8,4.3,8,4.3,8,4.3z"/> <path d="M8,0C7.6,0,7.3,0,6.9,0.1v2.2C6.1,2.5,5.4,2.8,4.8,3.2L3.2,1.6c-0.6,0.4-1.1,1-1.6,1.6l1.6,1.6C2.8,5.4,2.5,6.1,2.3,6.9H0.1C0,7.3,0,7.6,0,8c0,0.4,0,0.7,0.1,1.1h2.2c0.1,0.8,0.4,1.5,0.9,2.1l-1.6,1.6c0.4,0.6,1,1.1,1.6,1.6l1.6-1.6c0.6,0.4,1.4,0.7,2.1,0.9v2.2C7.3,16,7.6,16,8,16c0.4,0,0.7,0,1.1-0.1v-2.2c0.8-0.1,1.5-0.4,2.1-0.9l1.6,1.6c0.6-0.4,1.1-1,1.6-1.6l-1.6-1.6c0.4-0.6,0.7-1.4,0.9-2.1h2.2C16,8.7,16,8.4,16,8c0-0.4,0-0.7-0.1-1.1h-2.2c-0.1-0.8-0.4-1.5-0.9-2.1l1.6-1.6c-0.4-0.6-1-1.1-1.6-1.6l-1.6,1.6c-0.6-0.4-1.4-0.7-2.1-0.9V0.1C8.7,0,8.4,0,8,0z M8,4.3c2.1,0,3.7,1.7,3.7,3.7c0,0,0,0,0,0c0,2.1-1.7,3.7-3.7,3.7c0,0,0,0,0,0c-2.1,0-3.7-1.7-3.7-3.7c0,0,0,0,0,0C4.3,5.9,5.9,4.3,8,4.3C8,4.3,8,4.3,8,4.3z"/>
</symbol> </symbol>
<symbol id="svg-icon-select-arrow" viewBox="0 0 1792 1792">
<path fill-rule="evenodd" d="M1408 704q0 26-19 45l-448 448q-19 19-45 19t-45-19l-448-448q-19-19-19-45t19-45 45-19h896q26 0 45 19t19 45z"/>
</symbol>
<symbol id="svg-icon-checked" viewBox="0 0 1000 1000">
<path fill-rule="evenodd" d="M983.2,184.3L853,69.8c-4-3.5-9.3-5.3-14.5-5c-5.3,0.4-10.3,2.8-13.8,6.8L352.3,609.2L184.4,386.9c-3.2-4.2-8-7-13.2-7.8c-5.3-0.8-10.6,0.6-14.9,3.9L18,487.5c-8.8,6.7-10.6,19.3-3.9,28.1L325,927.2c3.6,4.8,9.3,7.7,15.3,8c0.2,0,0.5,0,0.7,0c5.8,0,11.3-2.5,15.1-6.8L985,212.6C992.3,204.3,991.5,191.6,983.2,184.3z"/>
</symbol>
</svg> </svg>
</body> </body>

View File

@ -1,79 +1,12 @@
/* global regExpTester debounce messageBox CodeMirror */ /* global regExpTester debounce messageBox CodeMirror template */
'use strict'; 'use strict';
function templateCache(cache) {
function clone(id) {
if (typeof cache[id] === 'function') {
cache[id] = cache[id]();
}
return cache[id].cloneNode(true);
}
return {clone};
}
function createAppliesToLineWidget(cm) { function createAppliesToLineWidget(cm) {
const APPLIES_TYPE = [
[t('appliesUrlOption'), 'url'],
[t('appliesUrlPrefixOption'), 'url-prefix'],
[t('appliesDomainOption'), 'domain'],
[t('appliesRegexpOption'), 'regexp']
];
const THROTTLE_DELAY = 400; const THROTTLE_DELAY = 400;
let TPL, EVENTS, CLICK_ROUTE;
let widgets = []; let widgets = [];
let fromLine, toLine, styleVariables; let fromLine, toLine, styleVariables;
let initialized = false; let initialized = false;
const template = templateCache({
container: () =>
$element({className: 'applies-to', appendChild: [
$element({tag: 'label', appendChild: t('appliesLabel')}),
$element({
tag: 'ul',
className: 'applies-to-list'
})
]}),
listItem: () =>
$element({tag: 'li', appendChild: [
$element({
tag: 'select',
className: 'applies-type',
appendChild: APPLIES_TYPE.map(([label, value]) => $element({
tag: 'option',
value: value,
textContent: label
}))
}),
$element({
tag: 'input',
className: 'applies-value'
}),
$element({
tag: 'button',
type: 'button',
className: 'applies-to-regexp-test',
textContent: t('styleRegexpTestButton')
}),
$element({
tag: 'button',
type: 'button',
className: 'applies-to-remove',
textContent: t('appliesRemove')
}),
$element({
tag: 'button',
type: 'button',
className: 'applies-to-add',
textContent: t('appliesAdd')
})
]}),
appliesToEverything: () =>
$element({
tag: 'li',
className: 'applies-to-everything',
textContent: t('appliesToEverything')
})
});
return {toggle}; return {toggle};
function toggle(newState = !initialized) { function toggle(newState = !initialized) {
@ -90,7 +23,120 @@ function createAppliesToLineWidget(cm) {
function init() { function init() {
initialized = true; initialized = true;
styleVariables = $element({tag: 'style'}); TPL = {
container:
$create('div.applies-to', [
$create('label', t('appliesLabel')),
$create('ul.applies-to-list'),
]),
listItem:
$create('li.applies-to-item', [
$create('select.applies-type', [
$create('option', {value: 'url'}, t('appliesUrlOption')),
$create('option', {value: 'url-prefix'}, t('appliesUrlPrefixOption')),
$create('option', {value: 'domain'}, t('appliesDomainOption')),
$create('option', {value: 'regexp'}, t('appliesRegexpOption')),
]),
$create('input.applies-value', {spellcheck: false}),
$create('button.test-regexp', t('styleRegexpTestButton')),
$create('button.remove-applies-to', t('appliesRemove')),
$create('button.add-applies-to', t('appliesAdd')),
]),
appliesToEverything:
$create('li.applies-to-everything', t('appliesToEverything')),
};
CLICK_ROUTE = {
'.test-regexp': (item, apply) => {
regExpTester.toggle();
regExpTester.update([apply.value.text]);
},
'.remove-applies-to': (item, apply) => {
const applies = item.closest('.applies-to').__applies;
const i = applies.indexOf(apply);
let repl;
let from;
let to;
if (applies.length < 2) {
messageBox({
contents: t('appliesRemoveError'),
buttons: [t('confirmClose')]
});
return;
}
if (i === 0) {
from = apply.mark.find().from;
to = applies[i + 1].mark.find().from;
repl = '';
} else if (i === applies.length - 1) {
from = applies[i - 1].mark.find().to;
to = apply.mark.find().to;
repl = '';
} else {
from = applies[i - 1].mark.find().to;
to = applies[i + 1].mark.find().from;
repl = ', ';
}
cm.replaceRange(repl, from, to, 'appliesTo');
clearApply(apply);
item.remove();
applies.splice(i, 1);
},
'.add-applies-to': (item, apply) => {
const applies = this.closest('.applies-to').__applies;
const i = applies.indexOf(apply);
const pos = apply.mark.find().to;
const text = `, ${apply.type.text}("")`;
cm.replaceRange(text, pos, pos, 'appliesTo');
const newApply = createApply(
cm.indexFromPos(pos) + 2,
apply.type.text,
'',
true
);
setupApplyMarkers(newApply);
applies.splice(i + 1, 0, newApply);
item.insertAdjacentElement('afterend', buildChildren(applies, newApply));
},
};
EVENTS = {
onchange({target}) {
const typeElement = target.closest('.applies-type');
if (typeElement) {
const item = target.closest('.applies-to-item');
const apply = item.__apply;
changeItem(apply, 'type', typeElement.value);
item.dataset.type = apply.type.text;
}
},
oninput({target}) {
if (target.matches('.applies-value')) {
const apply = target.closest('.applies-to-item').__apply;
debounce(changeItem, THROTTLE_DELAY, apply, 'value', target.value);
}
},
onfocus({target}) {
if (target.matches('.test-regexp')) {
const apply = target.closest('.applies-to-item').__apply;
updateRegexpTest(apply);
}
},
onclick({target}) {
for (const selector in CLICK_ROUTE) {
const routed = target.closest(selector);
if (routed) {
const item = routed.closest('.applies-to-item');
CLICK_ROUTE[selector].call(routed, item, item.__apply);
return;
}
}
}
};
styleVariables = $create('style');
fromLine = 0; fromLine = 0;
toLine = cm.doc.size; toLine = cm.doc.size;
@ -175,7 +221,7 @@ function createAppliesToLineWidget(cm) {
inOp = true; inOp = true;
cm.startOperation(); cm.startOperation();
} }
cm.operation(doUpdate); doUpdate();
} }
if (inOp) { if (inOp) {
cm.endOperation(); cm.endOperation();
@ -245,26 +291,29 @@ function createAppliesToLineWidget(cm) {
let i = 0; let i = 0;
let itemHeight; let itemHeight;
for (const section of findAppliesTo(start, end)) { for (const section of findAppliesTo(start, end)) {
while (removed[i] && removed[i].line.lineNo() < section.pos.line) { let removedWidget = removed[i];
clearWidget(removed[i++]); while (removedWidget && removedWidget.line.lineNo() < section.pos.line) {
clearWidget(removed[i]);
removedWidget = removed[++i];
} }
for (const a of section.applies) { for (const a of section.applies) {
setupApplyMarkers(a, lineIndexes); setupApplyMarkers(a, lineIndexes);
} }
if (removed[i] && removed[i].line.lineNo() === section.pos.line) { if (removedWidget && removedWidget.line.lineNo() === section.pos.line) {
// reuse old widget // reuse old widget
removed[i].section.applies.forEach(apply => { removedWidget.section.applies.forEach(apply => {
apply.type.mark.clear(); apply.type.mark.clear();
apply.value.mark.clear(); apply.value.mark.clear();
}); });
removed[i].section = section; removedWidget.section = section;
const newNode = buildElement(section); const newNode = buildElement(section);
if (removed[i].node.parentNode) { const removedNode = removedWidget.node;
removed[i].node.parentNode.replaceChild(newNode, removed[i].node); if (removedNode.parentNode) {
removedNode.parentNode.replaceChild(newNode, removedNode);
} }
removed[i].node = newNode; removedWidget.node = newNode;
removed[i].changed(); removedWidget.changed();
yield removed[i]; yield removedWidget;
i++; i++;
continue; continue;
} }
@ -337,138 +386,71 @@ function createAppliesToLineWidget(cm) {
} }
function buildElement({applies}) { function buildElement({applies}) {
const el = template.clone('container'); const container = TPL.container.cloneNode(true);
const appliesToList = $('.applies-to-list', el); const list = $('.applies-to-list', container);
applies.map(makeLi) for (const apply of applies) {
.forEach(item => appliesToList.appendChild(item)); list.appendChild(buildChildren(applies, apply));
if (!appliesToList.childNodes.length) {
appliesToList.appendChild(template.clone('appliesToEverything'));
} }
if (!list.children[0]) {
list.appendChild(TPL.appliesToEverything.cloneNode(true));
}
return Object.assign(container, EVENTS, {__applies: applies});
}
function buildChildren(applies, apply) {
const el = TPL.listItem.cloneNode(true);
el.dataset.type = apply.type.text;
el.__apply = apply;
$('.applies-type', el).value = apply.type.text;
$('.applies-value', el).value = apply.value.text;
return el; return el;
function makeLi(apply) {
const el = template.clone('listItem');
el.dataset.type = apply.type.text;
el.addEventListener('change', e => {
if (e.target.classList.contains('applies-type')) {
el.dataset.type = apply.type.text;
} }
});
const typeInput = $('.applies-type', el); function changeItem(apply, part, newText) {
typeInput.value = apply.type.text; if (!apply) {
typeInput.onchange = function () {
applyChange(apply.type, this.value);
};
const valueInput = $('.applies-value', el);
valueInput.value = apply.value.text;
valueInput.oninput = function () {
debounce(applyChange, THROTTLE_DELAY, apply.value, this.value);
};
valueInput.onfocus = updateRegexpTest;
const regexpTestButton = $('.applies-to-regexp-test', el);
regexpTestButton.onclick = () => {
regExpTester.toggle();
regExpTester.update([apply.value.text]);
};
const removeButton = $('.applies-to-remove', el);
removeButton.onclick = function () {
const i = applies.indexOf(apply);
let repl;
let from;
let to;
if (applies.length < 2) {
messageBox({
contents: chrome.i18n.getMessage('appliesRemoveError'),
buttons: [t('confirmClose')]
});
return; return;
} }
if (i === 0) { part = apply[part];
from = apply.mark.find().from; const range = part.mark.find();
to = applies[i + 1].mark.find().from; part.mark.clear();
repl = ''; newText = newText.replace(/\\/g, '\\\\');
} else if (i === applies.length - 1) {
from = applies[i - 1].mark.find().to;
to = apply.mark.find().to;
repl = '';
} else {
from = applies[i - 1].mark.find().to;
to = applies[i + 1].mark.find().from;
repl = ', ';
}
cm.replaceRange(repl, from, to, 'appliesTo');
clearApply(apply);
this.closest('li').remove();
applies.splice(i, 1);
};
const addButton = $('.applies-to-add', el);
addButton.onclick = function () {
const i = applies.indexOf(apply);
const pos = apply.mark.find().to;
const text = `, ${apply.type.text}("")`;
cm.replaceRange(text, pos, pos, 'appliesTo');
const newApply = createApply(
cm.indexFromPos(pos) + 2,
apply.type.text,
'',
true
);
setupApplyMarkers(newApply);
applies.splice(i + 1, 0, newApply);
this.closest('li').insertAdjacentElement('afterend', makeLi(newApply));
};
return el;
function updateRegexpTest() {
if (apply.type.text === 'regexp') {
const re = apply.value.text.trim();
if (re) {
regExpTester.update([re]);
} else {
regExpTester.update([]);
}
}
}
function applyChange(input, newText) {
const range = input.mark.find();
input.mark.clear();
cm.replaceRange(newText, range.from, range.to, 'appliesTo'); cm.replaceRange(newText, range.from, range.to, 'appliesTo');
input.mark = cm.markText( part.mark = cm.markText(
range.from, range.from,
cm.findPosH(range.from, newText.length, 'char'), cm.findPosH(range.from, newText.length, 'char'),
{clearWhenEmpty: false} {clearWhenEmpty: false}
); );
input.text = newText; part.text = newText;
if (input === apply.type) { if (part === apply.type) {
const range = apply.mark.find(); const range = apply.mark.find();
apply.mark.clear(); apply.mark.clear();
apply.mark = cm.markText( apply.mark = cm.markText(
input.mark.find().from, part.mark.find().from,
range.to, range.to,
{clearWhenEmpty: false} {clearWhenEmpty: false}
); );
} }
updateRegexpTest(); updateRegexpTest(apply);
} }
function updateRegexpTest(apply) {
if (apply.type.text === 'regexp') {
const rx = apply.value.text.trim();
regExpTester.update(rx ? [rx] : {});
} }
} }
function createApply(pos, typeText, valueText, isQuoted = false) { function createApply(pos, typeText, valueText, isQuoted = false) {
typeText = typeText.toLowerCase();
const start = pos; const start = pos;
const typeStart = start; const typeStart = start;
const typeEnd = typeStart + typeText.length; const typeEnd = typeStart + typeText.length;
const valueStart = typeEnd + 1 + Number(isQuoted); const valueStart = typeEnd + 1 + Number(isQuoted);
const valueEnd = valueStart + valueText.length; const valueEnd = valueStart + valueText.length;
const end = valueEnd + Number(isQuoted) + 1; const end = valueEnd + Number(isQuoted) + 1;
const hasSingleEscapes = /([^\\]|^)\\([^\\]|$)/.test(valueText);
return { return {
start, start,
type: { type: {
@ -477,7 +459,7 @@ function createAppliesToLineWidget(cm) {
end: typeEnd, end: typeEnd,
}, },
value: { value: {
text: valueText, text: hasSingleEscapes ? valueText : valueText.replace(/\\\\/g, '\\'),
start: valueStart, start: valueStart,
end: valueEnd, end: valueEnd,
}, },
@ -487,8 +469,12 @@ function createAppliesToLineWidget(cm) {
function *findAppliesTo(posStart, posEnd) { function *findAppliesTo(posStart, posEnd) {
const text = cm.getValue(); const text = cm.getValue();
const re = /^[\t ]*@-moz-document\s+/mg; const re = /^[\t ]*@-moz-document[\s\n]+/gm;
const applyRe = /(url|url-prefix|domain|regexp)\(((['"])(?:\\\\|\\\n|\\\3|[^\n])*?\3|[^)\n]*)\)[\s,]*/iyg; const applyRe = new RegExp([
/(?:\/\*[^*]*\*\/[\s\n]*)*/,
/(url|url-prefix|domain|regexp)/,
/\(((['"])(?:\\\\|\\\n|\\\3|[^\n])*?\3|[^)\n]*)\)\s*(,\s*)?/,
].map(rx => rx.source).join(''), 'giy');
let match; let match;
re.lastIndex = posStart; re.lastIndex = posStart;
while ((match = re.exec(text))) { while ((match = re.exec(text))) {

View File

@ -29,12 +29,16 @@ function beautify(event) {
optionHtml('border: none;', 'newline_between_properties', true) + optionHtml('border: none;', 'newline_between_properties', true) +
optionHtml('display: block;', 'newline_before_close_brace', true) + optionHtml('display: block;', 'newline_before_close_brace', true) +
optionHtml('}', 'newline_between_rules') + optionHtml('}', 'newline_between_rules') +
`<label style="display: block; clear: both;"><input data-option="indent_conditional" type="checkbox" `<label style="display: block; clear: both;">
${options.indent_conditional !== false ? 'checked' : ''}>` + <input data-option="indent_conditional" type="checkbox"
${options.indent_conditional !== false ? 'checked' : ''}>
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>` +
t('styleBeautifyIndentConditional') + '</label>' + t('styleBeautifyIndentConditional') + '</label>' +
'</div>' + '</div>' +
'<div><button role="undo"></button></div>'); '<div><button role="undo"></button></div>');
$('#help-popup').className = 'wide';
const undoButton = $('#help-popup button[role="undo"]'); const undoButton = $('#help-popup button[role="undo"]');
undoButton.textContent = t(scope.length === 1 ? 'undo' : 'undoGlobal'); undoButton.textContent = t(scope.length === 1 ? 'undo' : 'undoGlobal');
undoButton.addEventListener('click', () => { undoButton.addEventListener('click', () => {
@ -87,10 +91,17 @@ function beautify(event) {
const value = options[optionName]; const value = options[optionName];
return '<div newline="' + value.toString() + '">' + return '<div newline="' + value.toString() + '">' +
'<span' + (indent ? ' indent' : '') + '>' + label + '</span>' + '<span' + (indent ? ' indent' : '') + '>' + label + '</span>' +
'<div class="select-resizer">' +
'<select data-option="' + optionName + '">' + '<select data-option="' + optionName + '">' +
'<option' + (value ? '' : ' selected') + '>&nbsp;</option>' + '<option' + (value ? '' : ' selected') + '>&nbsp;</option>' +
'<option' + (value ? ' selected' : '') + '>\\n</option>' + '<option' + (value ? ' selected' : '') + '>\\n</option>' +
'</select></div>'; '</select>' +
'<svg class="svg-icon select-arrow" viewBox="0 0 1792 1792">' +
'<path fill-rule="evenodd" d="M1408 704q0 26-19 45l-448 448q-19 19-45 ' +
'19t-45-19l-448-448q-19-19-19-45t19-45 45-19h896q26 0 45 19t19 45z"/>' +
'</svg>' +
'</div>' +
'</div>';
} }
} }
} }

View File

@ -115,10 +115,8 @@ onDOMready().then(() => {
if (option.type === 'checkbox') { if (option.type === 'checkbox') {
option = (option.labels || [])[0] || option.nextElementSibling || option; option = (option.labels || [])[0] || option.nextElementSibling || option;
} }
progress = document.body.appendChild($element({ progress = document.body.appendChild(
className: 'set-option-progress', $create('.set-option-progress', {targetElement: option}));
targetElement: option,
}));
} }
} }
if (progress) { if (progress) {
@ -222,12 +220,7 @@ onDOMready().then(() => {
break; break;
} }
// avoid flicker: wait for the second stylesheet to load, then apply the theme // avoid flicker: wait for the second stylesheet to load, then apply the theme
document.head.appendChild($element({ document.head.appendChild($create('link#cm-theme2', {rel: 'stylesheet', href: url}));
tag: 'link',
id: 'cm-theme2',
rel: 'stylesheet',
href: url
}));
setTimeout(() => { setTimeout(() => {
CodeMirror.setOption(option, value); CodeMirror.setOption(option, value);
themeLink.remove(); themeLink.remove();
@ -287,7 +280,7 @@ onDOMready().then(() => {
function optionsFromArray(parent, options) { function optionsFromArray(parent, options) {
const fragment = document.createDocumentFragment(); const fragment = document.createDocumentFragment();
for (const opt of options) { for (const opt of options) {
fragment.appendChild($element({tag: 'option', textContent: opt})); fragment.appendChild($create('option', opt));
} }
parent.appendChild(fragment); parent.appendChild(fragment);
} }
@ -342,16 +335,16 @@ onDOMready().then(() => {
customizeOpenDialog(activeCM, template.find, function (query) { customizeOpenDialog(activeCM, template.find, function (query) {
this(query); this(query);
searchState = activeCM.state.search; searchState = activeCM.state.search;
if (editors.length === 1 || !searchState.query) { const searchOthers = editors.length > 1 && searchState.query;
return;
}
editors.forEach(cm => { editors.forEach(cm => {
if (cm !== activeCM) { if (cm !== activeCM) {
cm.execCommand('clearSearch'); cm.execCommand('clearSearch');
if (searchOthers) {
updateState(cm, searchState); updateState(cm, searchState);
} }
}
}); });
if (CodeMirror.cmpPos(searchState.posFrom, searchState.posTo) === 0) { if (searchOthers && CodeMirror.cmpPos(searchState.posFrom, searchState.posTo) === 0) {
findNext(activeCM); findNext(activeCM);
} }
}); });

View File

@ -69,8 +69,7 @@ var initColorpicker = () => {
} }
function configureColorpicker() { function configureColorpicker() {
const input = $element({ const input = $create('input', {
tag: 'input',
type: 'search', type: 'search',
spellcheck: false, spellcheck: false,
value: prefs.get('editor.colorpicker.hotkey'), value: prefs.get('editor.colorpicker.hotkey'),

View File

@ -27,6 +27,20 @@ body {
display: none !important; display: none !important;
} }
/************ checkbox & select************/
#options > div[class="option"] {
margin-bottom: 4px;
}
#basic-info-enabled {
margin-top: 2px;
}
label {
padding-left: 16px;
position: relative;
}
/************ header ************/ /************ header ************/
#header { #header {
width: 280px; width: 280px;
@ -61,9 +75,7 @@ body {
margin-top: 0.1rem; margin-top: 0.1rem;
min-height: 1.4rem; min-height: 1.4rem;
} }
input[type="checkbox"] {
margin-left: 0.1rem;
}
/* basic info */ /* basic info */
#basic-info { #basic-info {
margin-bottom: 1rem; margin-bottom: 1rem;
@ -136,14 +148,21 @@ input:invalid {
margin-left: -13px; margin-left: -13px;
cursor: pointer; cursor: pointer;
outline: none; outline: none;
margin-top: 8px;
margin-bottom: 8px;
} }
#header summary h2 { #header summary h2 {
display: inline-block; display: inline-block;
border-bottom: 1px dotted transparent; border-bottom: 1px dotted transparent;
margin-top: .1em;
margin-bottom: .1em;
} }
#header summary h2:hover {
#header summary:hover h2 {
border-color: #bbb; border-color: #bbb;
} }
#header summary svg { #header summary svg {
margin-top: -3px; margin-top: -3px;
} }
@ -159,8 +178,9 @@ input:invalid {
text-align: left; text-align: left;
padding-left: .25em; padding-left: .25em;
} }
#options .option.aligned > * { #options .option.aligned > label {
padding-right: 0.25rem; padding: .1rem .25rem 0 0;
vertical-align: middle;
} }
.set-option-progress { .set-option-progress {
position: absolute; position: absolute;
@ -306,7 +326,7 @@ body[data-match-highlight="selection"] .CodeMirror-selection-highlight-scrollbar
align-items: center; align-items: center;
margin-bottom: 0.35rem; margin-bottom: 0.35rem;
} }
.applies-to li > *:not(button) { .applies-to li > *:not(button):not(.select-resizer) {
flex: auto; flex: auto;
min-height: 1.4rem; min-height: 1.4rem;
margin-left: 0.35rem; margin-left: 0.35rem;
@ -396,9 +416,12 @@ body[data-match-highlight="selection"] .CodeMirror-selection-highlight-scrollbar
padding: 0.5rem; padding: 0.5rem;
z-index: 99; z-index: 99;
} }
#help-popup.big,
#help-popup.wide {
max-width: 100%;
}
#help-popup.big { #help-popup.big {
box-shadow: rgba(0, 0, 0, 0.45) 0px 0px 0px 100000px !important; box-shadow: rgba(0, 0, 0, 0.45) 0px 0px 0px 100000px !important;
max-width: 100%;
left: calc(280px - 3rem); left: calc(280px - 3rem);
} }
#help-popup.big .CodeMirror { #help-popup.big .CodeMirror {
@ -526,10 +549,11 @@ body[data-match-highlight="selection"] .CodeMirror-selection-highlight-scrollbar
/************ CSS beautifier ************/ /************ CSS beautifier ************/
.beautify-options { .beautify-options {
white-space: nowrap; white-space: nowrap;
font-family: monospace;
} }
.beautify-options div { .beautify-options div {
float: left; float: left;
display: flex;
align-items: center;
} }
.beautify-options div[newline="true"] + div { .beautify-options div[newline="true"] + div {
clear: left; clear: left;
@ -537,18 +561,30 @@ body[data-match-highlight="selection"] .CodeMirror-selection-highlight-scrollbar
.beautify-options div[newline="true"] + div span[indent] { .beautify-options div[newline="true"] + div span[indent] {
padding-left: 2rem; padding-left: 2rem;
} }
.beautify-options > label {
top: 1em;
margin: 1ex 0;
}
.firefox .beautify-options > label input {
top: 1px;
}
.beautify-options:after { .beautify-options:after {
clear: both; clear: both;
display: block; display: block;
content: " "; content: "";
height: 1rem; height: 1rem;
} }
.beautify-options span { .beautify-options span {
font-weight: bold; font-weight: bold;
font-family: monospace;
} }
.beautify-options select { .beautify-options select {
border: none; border: none;
background-color: rgba(0, 0, 0, 0.05); background: linear-gradient(90deg, rgba(0, 0, 0, .05) 18px, rgba(0, 0, 0, .02) 24px);
font-family: monospace;
font-weight: bold;
padding-left: 4px;
margin-left: 4px;
} }
/************ single editor **************/ /************ single editor **************/
@ -671,12 +707,6 @@ html:not(.usercss) .usercss-only,
#options h2 { #options h2 {
margin: 0 0 .5em; margin: 0 0 .5em;
} }
#options .aligned > *:not(.svg-inline-wrapper) {
margin: 1px 0 0 0; /* workaround the flowing-padding column bug in webkit */
padding-right: 0.4rem;
vertical-align: baseline;
min-height: 1.4rem;
}
.option label { .option label {
line-height: 1.25rem; line-height: 1.25rem;
margin: 0; margin: 0;

View File

@ -92,14 +92,36 @@ function preinit() {
}); });
// preload the theme so that CodeMirror can calculate its metrics in DOMContentLoaded->setupLivePrefs() // preload the theme so that CodeMirror can calculate its metrics in DOMContentLoaded->setupLivePrefs()
new MutationObserver((mutations, observer) => { document.head.appendChild(
const themeElement = $('#cm-theme'); $create('link#cm-theme', {
if (themeElement) { rel: 'stylesheet',
themeElement.href = prefs.get('editor.theme') === 'default' ? '' href: prefs.get('editor.theme') === 'default' ? '' :
: 'vendor/codemirror/theme/' + prefs.get('editor.theme') + '.css'; 'vendor/codemirror/theme/' + prefs.get('editor.theme') + '.css'
observer.disconnect(); }));
// forcefully break long labels in aligned options to prevent the entire block layout from breaking
onDOMready().then(() => new Promise(requestAnimationFrame)).then(() => {
const maxWidth2ndChild = $$('#options .aligned > :nth-child(2)')
.sort((a, b) => b.offsetWidth - a.offsetWidth)[0].offsetWidth;
const widthFor1stChild = $('#options').offsetWidth - maxWidth2ndChild;
if (widthFor1stChild > 50) {
for (const el of $$('#options .aligned > :nth-child(1)')) {
if (el.offsetWidth > widthFor1stChild) {
el.style.cssText = 'word-break: break-all; hyphens: auto;';
} }
}).observe(document, {subtree: true, childList: true}); }
} else {
const width = $('#options').clientWidth;
document.head.appendChild($create('style', `
#options .aligned > nth-child(1) {
max-width: 70px;
}
#options .aligned > nth-child(2) {
max-width: ${width - 70}px;
}
`));
}
});
if (chrome.windows) { if (chrome.windows) {
queryTabs({currentWindow: true}).then(tabs => { queryTabs({currentWindow: true}).then(tabs => {
@ -387,7 +409,7 @@ function save() {
saveStyleSafe({ saveStyleSafe({
id: styleId, id: styleId,
name: name, name: $('#name').value.trim(),
enabled: $('#enabled').checked, enabled: $('#enabled').checked,
reason: 'editSave', reason: 'editSave',
sections: getSectionsHashes() sections: getSectionsHashes()
@ -457,22 +479,20 @@ function toMozillaFormat() {
function fromMozillaFormat() { function fromMozillaFormat() {
const popup = showCodeMirrorPopup(t('styleFromMozillaFormatPrompt'), const popup = showCodeMirrorPopup(t('styleFromMozillaFormatPrompt'),
$element({appendChild: [ $create([
$element({ $create('button', {
tag: 'button',
name: 'import-append', name: 'import-append',
textContent: t('importAppendLabel'), textContent: t('importAppendLabel'),
title: 'Ctrl-Enter:\n' + t('importAppendTooltip'), title: 'Ctrl-Enter:\n' + t('importAppendTooltip'),
onclick: doImport, onclick: doImport,
}), }),
$element({ $create('button', {
tag: 'button',
name: 'import-replace', name: 'import-replace',
textContent: t('importReplaceLabel'), textContent: t('importReplaceLabel'),
title: 'Ctrl-Shift-Enter:\n' + t('importReplaceTooltip'), title: 'Ctrl-Shift-Enter:\n' + t('importReplaceTooltip'),
onclick: () => doImport({replaceOldStyle: true}), onclick: () => doImport({replaceOldStyle: true}),
}), }),
]})); ]));
const contents = $('.contents', popup); const contents = $('.contents', popup);
contents.insertBefore(popup.codebox.display.wrapper, contents.firstElementChild); contents.insertBefore(popup.codebox.display.wrapper, contents.firstElementChild);
popup.codebox.focus(); popup.codebox.focus();
@ -522,10 +542,8 @@ function fromMozillaFormat() {
} }
function showError(errors) { function showError(errors) {
showHelp(t('styleFromMozillaFormatError'), $element({ showHelp(t('styleFromMozillaFormatError'),
tag: 'pre', $create('pre', Array.isArray(errors) ? errors.join('\n') : errors));
textContent: Array.isArray(errors) ? errors.join('\n') : errors,
}));
} }
} }
@ -547,7 +565,7 @@ function showToggleStyleHelp() {
function showHelp(title = '', body) { function showHelp(title = '', body) {
const div = $('#help-popup'); const div = $('#help-popup');
div.classList.remove('big'); div.className = '';
const contents = $('.contents', div); const contents = $('.contents', div);
contents.textContent = ''; contents.textContent = '';
if (body) { if (body) {
@ -617,7 +635,7 @@ function showCodeMirrorPopup(title, html, options) {
function setGlobalProgress(done, total) { function setGlobalProgress(done, total) {
const progressElement = $('#global-progress') || const progressElement = $('#global-progress') ||
total && document.body.appendChild($element({id: 'global-progress'})); total && document.body.appendChild($create('#global-progress'));
if (total) { if (total) {
const progress = (done / Math.max(done, total) * 100).toFixed(1); const progress = (done / Math.max(done, total) * 100).toFixed(1);
progressElement.style.borderLeftWidth = progress + 'vw'; progressElement.style.borderLeftWidth = progress + 'vw';

View File

@ -1,7 +1,6 @@
/* global CodeMirror messageBox */ /* global CodeMirror messageBox */
/* global editors makeSectionVisible showCodeMirrorPopup showHelp */ /* global editors makeSectionVisible showCodeMirrorPopup showHelp */
/* global loadScript require CSSLint stylelint */ /* global loadScript require CSSLint stylelint */
/* global makeLink */
'use strict'; 'use strict';
onDOMready().then(loadLinterAssets); onDOMready().then(loadLinterAssets);
@ -231,9 +230,7 @@ function updateLinter({immediately, linter = linterConfig.getName()} = {}) {
cm.options.gutters = guttersOption; cm.options.gutters = guttersOption;
const el = $('.' + GUTTERS_CLASS, cm.display.gutters); const el = $('.' + GUTTERS_CLASS, cm.display.gutters);
if (linter && !el) { if (linter && !el) {
cm.display.gutters.appendChild($element({ cm.display.gutters.appendChild($create('.CodeMirror-gutter ' + GUTTERS_CLASS));
className: 'CodeMirror-gutter ' + GUTTERS_CLASS
}));
} else if (!linter && el) { } else if (!linter && el) {
el.remove(); el.remove();
} }
@ -281,9 +278,8 @@ function updateLintReportInternal(scope, {postponeNewIssues} = {}) {
const newMarkers = lintState.stylusMarkers = new Map(); const newMarkers = lintState.stylusMarkers = new Map();
const oldText = (lintState.body || {}).textContentCached || ''; const oldText = (lintState.body || {}).textContentCached || '';
const activeLine = cm.getCursor().line; const activeLine = cm.getCursor().line;
const body = !(lintState.marked || {}).length ? {} : $element({ const body = !(lintState.marked || {}).length ? {} :
tag: 'tbody', $create('tbody', lintState.marked.map(mark => {
appendChild: lintState.marked.map(mark => {
const info = mark.__annotation; const info = mark.__annotation;
const {line, ch} = info.from; const {line, ch} = info.from;
const isActiveLine = line === activeLine; const isActiveLine = line === activeLine;
@ -294,27 +290,15 @@ function updateLintReportInternal(scope, {postponeNewIssues} = {}) {
oldMarkers.delete(pos); oldMarkers.delete(pos);
} }
newMarkers.set(pos, message); newMarkers.set(pos, message);
return $element({ return $create(`tr.${info.severity}`, [
tag: 'tr', $create('td', {attributes: {role: 'severity'}, dataset: {rule: info.rule}},
className: info.severity, $create('.CodeMirror-lint-marker-' + info.severity, info.severity)),
appendChild: [ $create('td', {attributes: {role: 'line'}}, line + 1),
$element({ $create('td', {attributes: {role: 'sep'}}, ':'),
tag: 'td', $create('td', {attributes: {role: 'col'}}, ch + 1),
attributes: {role: 'severity'}, $create('td', {attributes: {role: 'message'}, title}, message),
dataset: {rule: info.rule}, ]);
appendChild: $element({ }));
className: 'CodeMirror-lint-marker-' + info.severity,
textContent: info.severity,
}),
}),
$element({tag: 'td', attributes: {role: 'line'}, textContent: line + 1}),
$element({tag: 'td', attributes: {role: 'sep'}, textContent: ':'}),
$element({tag: 'td', attributes: {role: 'col'}, textContent: ch + 1}),
$element({tag: 'td', attributes: {role: 'message'}, textContent: message, title}),
],
});
})
});
body.textContentCached = body.textContent || ''; body.textContentCached = body.textContent || '';
lintState.body = body.textContentCached && body; lintState.body = body.textContentCached && body;
result.changed |= oldText !== body.textContentCached; result.changed |= oldText !== body.textContentCached;
@ -340,14 +324,10 @@ function renderLintReport(someBlockChanged) {
if (!body) { if (!body) {
return; return;
} }
const newBlock = $element({ const newBlock = $create('table', {cm}, [
tag: 'table', $create('caption', label + ' ' + (index + 1)),
appendChild: [
$element({tag: 'caption', textContent: label + ' ' + (index + 1)}),
body, body,
], ]);
cm,
});
newContent.appendChild(newBlock); newContent.appendChild(newBlock);
issueCount += newBlock.rows.length; issueCount += newBlock.rows.length;
@ -386,38 +366,36 @@ function showLintHelp() {
? 'https://stylelint.io/user-guide/rules/' ? 'https://stylelint.io/user-guide/rules/'
// some CSSLint rules do not have a url // some CSSLint rules do not have a url
: 'https://github.com/CSSLint/csslint/issues/535'; : 'https://github.com/CSSLint/csslint/issues/535';
let headerLink, template; let headerLink, template, csslintRules;
if (linter === 'csslint') { if (linter === 'csslint') {
headerLink = makeLink('https://github.com/CSSLint/csslint/wiki/Rules-by-ID', 'CSSLint'); headerLink = $createLink('https://github.com/CSSLint/csslint/wiki/Rules-by-ID', 'CSSLint');
template = ruleID => { template = ruleID => {
const rule = linterConfig.allRuleIds.csslint.find(rule => rule.id === ruleID); const rule = csslintRules.find(rule => rule.id === ruleID);
return rule && return rule &&
$element({tag: 'li', appendChild: [ $create('li', [
$element({tag: 'b', appendChild: makeLink(rule.url || baseUrl, rule.name)}), $create('b', $createLink(rule.url || baseUrl, rule.name)),
$element({tag: 'br'}), $create('br'),
rule.desc, rule.desc,
]}); ]);
}; };
} else { } else {
headerLink = makeLink(baseUrl, 'stylelint'); headerLink = $createLink(baseUrl, 'stylelint');
template = rule => template = rule =>
$element({ $create('li',
tag: 'li', rule === 'CssSyntaxError' ? rule : $createLink(baseUrl + rule, rule));
appendChild: makeLink(baseUrl + rule, rule),
});
} }
const header = t('linterIssuesHelp', '\x01').split('\x01'); const header = t('linterIssuesHelp', '\x01').split('\x01');
const activeRules = new Set($$('#lint td[role="severity"]').map(el => el.dataset.rule)); const activeRules = new Set($$('#lint td[role="severity"]').map(el => el.dataset.rule));
return showHelp(t('linterIssues'), Promise.resolve(linter !== 'csslint' || linterConfig.invokeWorker({action: 'getAllRuleInfos'}))
$element({appendChild: [ .then(data => {
csslintRules = data;
showHelp(t('linterIssues'),
$create([
header[0], headerLink, header[1], header[0], headerLink, header[1],
$element({ $create('ul.rules', [...activeRules.values()].map(template)),
tag: 'ul', ])
className: 'rules',
appendChild: [...activeRules.values()].map(template),
}),
]})
); );
});
} }
function showLinterErrorMessage(title, contents, popup) { function showLinterErrorMessage(title, contents, popup) {
@ -465,41 +443,21 @@ function setupLinterPopup(config) {
}); });
function makeFooter() { function makeFooter() {
const makeButton = (className, onclick, text, options = {}) => return $create('div', [
$element(Object.assign(options, { $create('p', [
className,
onclick,
tag: 'button',
type: 'button',
textContent: t(text),
}));
return $element({
appendChild: [
$element({
tag: 'p',
appendChild: [
t('linterRulesLink') + ' ', t('linterRulesLink') + ' ',
$element({ $createLink(
tag: 'a', linter === 'stylelint'
target: '_blank',
href: linter === 'stylelint'
? 'https://stylelint.io/user-guide/rules/' ? 'https://stylelint.io/user-guide/rules/'
: 'https://github.com/CSSLint/csslint/wiki/Rules-by-ID', : 'https://github.com/CSSLint/csslint/wiki/Rules-by-ID',
textContent: linterTitle linterTitle),
}), linter === 'csslint' ? ' ' + t('linterCSSLintSettings') : '',
linter === 'csslint' ? ' ' + t('linterCSSLintSettings') : '' ]),
] $create('button.save', {onclick: save, title: 'Ctrl-Enter'}, t('styleSaveLabel')),
}), $create('button.cancel', {onclick: cancel}, t('confirmClose')),
makeButton('save', save, 'styleSaveLabel', {title: 'Ctrl-Enter'}), $create('button.reset', {onclick: reset, title: t('linterResetMessage')}, t('genericResetLabel')),
makeButton('cancel', cancel, 'confirmClose'), $create('span.saved-message', t('genericSavedMessage')),
makeButton('reset', reset, 'genericResetLabel', {title: t('linterResetMessage')}), ]);
$element({
tag: 'span',
className: 'saved-message',
textContent: t('genericSavedMessage')
})
]
});
} }
function save(event) { function save(event) {
@ -516,9 +474,7 @@ function setupLinterPopup(config) {
if (invalid.length) { if (invalid.length) {
showLinterErrorMessage(linter, [ showLinterErrorMessage(linter, [
t('linterInvalidConfigError'), t('linterInvalidConfigError'),
$element({tag: 'ul', appendChild: invalid.map(name => $create('ul', invalid.map(name => $create('li', name))),
$element({tag: 'li', textContent: name})),
}),
], popup); ], popup);
return; return;
} }

View File

@ -123,7 +123,8 @@
const rx = query instanceof RegExp && query; const rx = query instanceof RegExp && query;
const sel = this.getSelection(); const sel = this.getSelection();
// current query differs from the selected text => remove the overlay // current query differs from the selected text => remove the overlay
if (sel && (rx && !rx.test(sel) || sel.toLowerCase() !== query)) { if (sel && rx && !rx.test(sel) || sel.toLowerCase() !== originalToken.toLowerCase()) {
helper.query = helper.originalToken = sel;
return; return;
} }
// if token under cursor has changed => remove the overlay // if token under cursor has changed => remove the overlay
@ -160,6 +161,8 @@
// in case the original addon won't highlight anything we need to actually remove the overlays // in case the original addon won't highlight anything we need to actually remove the overlays
// by setting a timer that runs in the next event loop cycle and can be canceled in this cycle // by setting a timer that runs in the next event loop cycle and can be canceled in this cycle
hookTimer: setTimeout(removeOverlayIfExpired, 0, this, state), hookTimer: setTimeout(removeOverlayIfExpired, 0, this, state),
originalToken,
query,
}; };
// fool the original addon so it won't invoke state.matchesonscroll.clear() // fool the original addon so it won't invoke state.matchesonscroll.clear()
state.matchesonscroll = null; state.matchesonscroll = null;

View File

@ -25,17 +25,17 @@ var regExpTester = (() => {
} }
} }
function isShowed() { function isShown() {
return Boolean($('.regexp-report')); return Boolean($('.regexp-report'));
} }
function toggle(state = !isShowed()) { function toggle(state = !isShown()) {
if (state && !isShowed()) { if (state && !isShown()) {
if (!isInit) { if (!isInit) {
init(); init();
} }
showHelp('', $element({className: 'regexp-report'})); showHelp('', $create('.regexp-report'));
} else if (!state && isShowed()) { } else if (!state && isShown()) {
if (isInit) { if (isInit) {
uninit(); uninit();
} }
@ -45,7 +45,7 @@ var regExpTester = (() => {
} }
function update(newRegexps) { function update(newRegexps) {
if (!isShowed()) { if (!isShown()) {
if (isInit) { if (isInit) {
uninit(); uninit();
} }
@ -108,19 +108,19 @@ var regExpTester = (() => {
const faviconUrl = url.startsWith(URLS.ownOrigin) const faviconUrl = url.startsWith(URLS.ownOrigin)
? OWN_ICON ? OWN_ICON
: GET_FAVICON_URL + new URL(url).hostname; : GET_FAVICON_URL + new URL(url).hostname;
const icon = $element({tag: 'img', src: faviconUrl}); const icon = $create('img', {src: faviconUrl});
if (match.text.length === url.length) { if (match.text.length === url.length) {
full.push($element({appendChild: [ full.push($create('div', [
icon, icon,
url, url,
]})); ]));
} else { } else {
partial.push($element({appendChild: [ partial.push($create('div', [
icon, icon,
url.substr(0, match.pos), url.substr(0, match.pos),
$element({tag: 'mark', textContent: match.text}), $create('mark', match.text),
url.substr(match.pos + match.text.length), url.substr(match.pos + match.text.length),
]})); ]));
} }
} }
if (full.length) { if (full.length) {
@ -131,33 +131,28 @@ var regExpTester = (() => {
} }
} }
// render stats // render stats
const report = $element({className: 'regexp-report'}); const report = $create('.regexp-report');
const br = $element({tag: 'br'}); const br = $create('br');
for (const type in stats) { for (const type in stats) {
// top level groups: full, partial, none, invalid // top level groups: full, partial, none, invalid
const {label, data} = stats[type]; const {label, data} = stats[type];
if (!data.length) { if (!data.length) {
continue; continue;
} }
const block = report.appendChild($element({ const block = report.appendChild(
tag: 'details', $create('details', {open: true, dataset: {type}}, [
open: true, $create('summary', label),
dataset: {type}, ]));
appendChild: $element({tag: 'summary', appendChild: label}),
}));
// 2nd level: regexp text // 2nd level: regexp text
for (const {text, urls} of data) { for (const {text, urls} of data) {
if (urls) { if (urls) {
// type is partial or full // type is partial or full
block.appendChild($element({ block.appendChild(
tag: 'details', $create('details', {open: true}, [
open: true, $create('summary', text),
appendChild: [
$element({tag: 'summary', textContent: text}),
// 3rd level: tab urls // 3rd level: tab urls
...urls, ...urls,
], ]));
}));
} else { } else {
// type is none or invalid // type is none or invalid
block.appendChild(document.createTextNode(text)); block.appendChild(document.createTextNode(text));
@ -165,9 +160,14 @@ var regExpTester = (() => {
} }
} }
} }
report.appendChild(
$create('p.regexp-report-note',
t('styleRegexpTestNote')
.split(/(\\+)/)
.map(s => (s.startsWith('\\') ? $create('code', s) : s))));
showHelp(t('styleRegexpTestTitle'), report); showHelp(t('styleRegexpTestTitle'), report);
$('.regexp-report').onclick = event => { report.onclick = event => {
const target = event.target.closest('a, .regexp-report div'); const target = event.target.closest('a, .regexp-report div');
if (target) { if (target) {
openURL({ openURL({

View File

@ -390,11 +390,11 @@ function getSections() {
function getSectionsHashes() { function getSectionsHashes() {
const sections = []; const sections = [];
getSections().forEach(div => { for (const div of getSections()) {
const meta = {urls: [], urlPrefixes: [], domains: [], regexps: []}; const meta = {urls: [], urlPrefixes: [], domains: [], regexps: []};
for (const li of $('.applies-to-list', div).childNodes) { for (const li of $('.applies-to-list', div).childNodes) {
if (li.className === template.appliesToEverything.className) { if (li.className === template.appliesToEverything.className) {
return; break;
} }
const type = $('[name=applies-type]', li).value; const type = $('[name=applies-type]', li).value;
const value = $('[name=applies-value]', li).value; const value = $('[name=applies-value]', li).value;
@ -404,11 +404,11 @@ function getSectionsHashes() {
} }
const code = div.CodeMirror.getValue(); const code = div.CodeMirror.getValue();
if (/^\s*$/.test(code) && Object.keys(meta).length === 0) { if (/^\s*$/.test(code) && Object.keys(meta).length === 0) {
return; continue;
} }
meta.code = code; meta.code = code;
sections.push(meta); sections.push(meta);
}); }
return sections; return sections;
} }

View File

@ -64,7 +64,7 @@ function showKeyMapHelp() {
if (index > offset) { if (index > offset) {
cell.appendChild(document.createTextNode(text.substring(offset, index))); cell.appendChild(document.createTextNode(text.substring(offset, index)));
} }
cell.appendChild($element({tag: 'mark', textContent: match})); cell.appendChild($create('mark', match));
offset = index + match.length; offset = index + match.length;
}); });
if (offset + 1 !== text.length) { if (offset + 1 !== text.length) {

View File

@ -1,7 +1,7 @@
/* global CodeMirror dirtyReporter initLint */ /* global CodeMirror dirtyReporter initLint */
/* global showToggleStyleHelp goBackToManage updateLintReportIfEnabled */ /* global showToggleStyleHelp goBackToManage updateLintReportIfEnabled */
/* global editors linterConfig updateLinter regExpTester mozParser */ /* global editors linterConfig updateLinter regExpTester mozParser */
/* global makeLink createAppliesToLineWidget messageBox */ /* global createAppliesToLineWidget messageBox */
'use strict'; 'use strict';
function createSourceEditor(style) { function createSourceEditor(style) {
@ -12,9 +12,7 @@ function createSourceEditor(style) {
$('#name').disabled = true; $('#name').disabled = true;
$('#mozilla-format-container').remove(); $('#mozilla-format-container').remove();
$('#sections').textContent = ''; $('#sections').textContent = '';
$('#sections').appendChild( $('#sections').appendChild($create('.single-editor'));
$element({className: 'single-editor'})
);
const dirty = dirtyReporter(); const dirty = dirtyReporter();
dirty.onChange(() => { dirty.onChange(() => {
@ -236,15 +234,12 @@ function createSourceEditor(style) {
return; return;
} }
const contents = Array.isArray(err) ? const contents = Array.isArray(err) ?
$element({tag: 'pre', textContent: err.join('\n')}) : $create('pre', err.join('\n')) :
[String(err)]; [String(err)];
if (Number.isInteger(err.index)) { if (Number.isInteger(err.index)) {
const pos = cm.posFromIndex(err.index); const pos = cm.posFromIndex(err.index);
contents[0] += ` (line ${pos.line + 1} col ${pos.ch + 1})`; contents[0] += ` (line ${pos.line + 1} col ${pos.ch + 1})`;
contents.push($element({ contents.push($create('pre', drawLinePointer(pos)));
tag: 'pre',
textContent: drawLinePointer(pos)
}));
} }
messageBox.alert(contents); messageBox.alert(contents);
}); });

95
global.css Normal file
View File

@ -0,0 +1,95 @@
.svg-icon.checked {
position: absolute;
height: 8px;
width: 8px;
display: none;
fill: #000;
margin: 2px 0 0 2px;
}
input[type="checkbox"]:not(.slider):checked + .svg-icon.checked {
display: inline-flex;
transition: fill .1s;
}
input[type="checkbox"]:not(.slider) {
position: absolute;
left: 0;
top: 0;
-moz-appearance: none;
-webkit-appearance: none;
pointer-events: none;
border: 1px solid hsl(0, 0%, 46%);
height: 12px;
width: 12px;
display: inline-flex;
border-radius: 2px;
background-color: hsla(0, 0%, 0%, .1);
outline: none;
margin: 0;
transition: background-color .1s, border-color .1s;
}
input[type="checkbox"]:not(.slider):hover {
border-color: hsl(0, 0%, 32%);
background-color: hsl(0, 0%, 82%);
}
input[type="checkbox"]:not(.slider):checked + .svg-icon.checked {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
}
label {
transition: color .1s;
}
select {
-moz-appearance: none;
-webkit-appearance: none;
height: 22px;
background-color: transparent;
border: 1px solid hsl(0, 0%, 66%);
padding: 0 20px 0 6px;
transition: color .5s;
}
.firefox select {
padding: 0 20px 0 2px;
}
.select-resizer {
display: inline-flex!important;
cursor: default;
position: relative;
}
.svg-icon.select-arrow {
pointer-events: none;
cursor: default;
display: inline-flex;
height: 14px;
width: 14px;
fill: #000;
position: absolute;
top: 4px;
right: 4px;
transition: fill .5s;
}
@supports (-moz-appearance: none) {
.moz-appearance-bug .svg-icon.checked {
display: none !important;
}
.moz-appearance-bug input[type="checkbox"] {
-moz-appearance: checkbox !important;
}
.moz-appearance-bug button {
padding-left: .75ex;
padding-right: .75ex;
}
}

View File

@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge"> <meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Loading...</title> <title>Loading...</title>
<link rel="stylesheet" href="global.css">
<link rel="stylesheet" href="/install-usercss/install-usercss.css"> <link rel="stylesheet" href="/install-usercss/install-usercss.css">
<script src="/js/messaging.js"></script> <script src="/js/messaging.js"></script>
<script src="/js/prefs.js"></script> <script src="/js/prefs.js"></script>
@ -61,10 +62,12 @@
<button class="install" i18n-text="installButton"></button> <button class="install" i18n-text="installButton"></button>
<label class="set-update-url"> <label class="set-update-url">
<input type="checkbox"> <input type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
<span></span> <span></span>
</label> </label>
<label class="live-reload"> <label class="live-reload">
<input type="checkbox"> <input type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
<span i18n-text="liveReloadLabel"></span> <span i18n-text="liveReloadLabel"></span>
</label> </label>
</div> </div>
@ -86,6 +89,14 @@
<div class="warnings"></div> <div class="warnings"></div>
</div> </div>
</div> </div>
<script src="/install-usercss/install-usercss.js"></script> <script src="/install-usercss/install-usercss.js"></script>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none !important;">
<symbol id="svg-icon-checked" viewBox="0 0 1000 1000">
<path fill-rule="evenodd" d="M983.2,184.3L853,69.8c-4-3.5-9.3-5.3-14.5-5c-5.3,0.4-10.3,2.8-13.8,6.8L352.3,609.2L184.4,386.9c-3.2-4.2-8-7-13.2-7.8c-5.3-0.8-10.6,0.6-14.9,3.9L18,487.5c-8.8,6.7-10.6,19.3-3.9,28.1L325,927.2c3.6,4.8,9.3,7.7,15.3,8c0.2,0,0.5,0,0.7,0c5.8,0,11.3-2.5,15.1-6.8L985,212.6C992.3,204.3,991.5,191.6,983.2,184.3z"/>
</symbol>
</svg>
</body> </body>
</html> </html>

View File

@ -239,6 +239,10 @@ li {
user-select: auto; user-select: auto;
} }
label {
padding-left: 16px;
position: relative;
}
/* spinner: https://github.com/loadingio/css-spinner */ /* spinner: https://github.com/loadingio/css-spinner */
@keyframes lds-spinner { @keyframes lds-spinner {
@ -353,58 +357,6 @@ li {
-webkit-animation-delay: 0s; -webkit-animation-delay: 0s;
animation-delay: 0s; animation-delay: 0s;
} }
/* https://github.com/lukehaas/css-loaders */
/*
.spinner {
--color: currentColor;
--background-color: currentColor;
font-size: 10px;
margin: 50px auto;
text-indent: -9999em;
width: 200px;
height: 200px;
border-radius: 50%;
background: #ffffff;
background: -moz-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
background: -webkit-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
background: -o-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
background: -ms-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
background: linear-gradient(to right, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
position: relative;
-webkit-animation: load3 1.4s infinite linear;
animation: load3 1.4s infinite linear;
-webkit-transform: translateZ(0);
-ms-transform: translateZ(0);
transform: translateZ(0);
}
.spinner:before {
width: 50%;
height: 50%;
background: #ffffff;
border-radius: 100% 0 0 0;
position: absolute;
top: 0;
left: 0;
content: '';
}
.spinner:after {
background: #0dc5c1;
width: 75%;
height: 75%;
border-radius: 50%;
content: '';
margin: auto;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
*/
@-webkit-keyframes load3 { @-webkit-keyframes load3 {
0% { 0% {
-webkit-transform: rotate(0deg); -webkit-transform: rotate(0deg);

View File

@ -1,4 +1,4 @@
/* global CodeMirror semverCompare makeLink closeCurrentTab */ /* global CodeMirror semverCompare closeCurrentTab */
/* global messageBox download chromeLocal */ /* global messageBox download chromeLocal */
'use strict'; 'use strict';
@ -44,11 +44,8 @@
setTimeout(() => { setTimeout(() => {
if (!installed) { if (!installed) {
const div = $element({}); $('.header').appendChild($create('.lds-spinner',
$('.header').appendChild($element({ new Array(12).fill($create('div')).map(e => e.cloneNode())));
className: 'lds-spinner',
appendChild: new Array(12).fill(div).map(e => e.cloneNode()),
}));
} }
}, 200); }, 200);
@ -101,8 +98,7 @@
$('.applies-to').textContent = ''; $('.applies-to').textContent = '';
getAppliesTo(style).forEach(pattern => getAppliesTo(style).forEach(pattern =>
$('.applies-to').appendChild($element({tag: 'li', textContent: pattern})) $('.applies-to').appendChild($create('li', pattern)));
);
$('.external-link').textContent = ''; $('.external-link').textContent = '';
const externalLink = makeExternalLink(); const externalLink = makeExternalLink();
@ -125,46 +121,35 @@
const [, name, email, url] = match; const [, name, email, url] = match;
const frag = document.createDocumentFragment(); const frag = document.createDocumentFragment();
if (email) { if (email) {
frag.appendChild(makeLink(`mailto:${email}`, name)); frag.appendChild($createLink(`mailto:${email}`, name));
} else { } else {
frag.appendChild($element({ frag.appendChild($create('span', name));
tag: 'span',
textContent: name
}));
} }
if (url) { if (url) {
frag.appendChild(makeLink( frag.appendChild($createLink(url,
url, $create('SVG:svg.svg-icon', {viewBox: '0 0 20 20'},
$element({ $create('SVG:path', {
tag: 'svg#svg',
viewBox: '0 0 20 20',
class: 'svg-icon',
appendChild: $element({
tag: 'svg#path',
d: 'M4,4h5v2H6v8h8v-3h2v5H4V4z M11,3h6v6l-2-2l-4,4L9,9l4-4L11,3z' d: 'M4,4h5v2H6v8h8v-3h2v5H4V4z M11,3h6v6l-2-2l-4,4L9,9l4-4L11,3z'
}) }))
})
)); ));
} }
return frag; return frag;
} }
function makeExternalLink() { function makeExternalLink() {
const urls = []; const urls = [
if (data.homepageURL) { data.homepageURL && [data.homepageURL, t('externalHomepage')],
urls.push([data.homepageURL, t('externalHomepage')]); data.supportURL && [data.supportURL, t('externalSupport')],
} ];
if (data.supportURL) { return (data.homepageURL || data.supportURL) && (
urls.push([data.supportURL, t('externalSupport')]); $create('div', [
} $create('h3', t('externalLink')),
if (urls.length) { $create('ul', urls.map(args => args &&
return $element({appendChild: [ $create('li',
$element({tag: 'h3', textContent: t('externalLink')}), $createLink(...args)
$element({tag: 'ul', appendChild: urls.map(args => )
$element({tag: 'li', appendChild: makeLink(...args)}) ))
)}) ]));
]});
}
} }
function installButtonClass() { function installButtonClass() {
@ -220,8 +205,8 @@
function initSourceCode(sourceCode) { function initSourceCode(sourceCode) {
cm.setValue(sourceCode); cm.setValue(sourceCode);
cm.refresh(); cm.refresh();
sendMessage({method: 'buildUsercss', sourceCode, checkDup: true}) BG.usercssHelper.build(BG.deepCopy({sourceCode, checkDup: true}))
.then(init) .then(r => init(deepCopy(r)))
.catch(err => { .catch(err => {
$('.header').classList.add('meta-init-error'); $('.header').classList.add('meta-init-error');
showError(err); showError(err);
@ -229,10 +214,43 @@
} }
function buildWarning(err) { function buildWarning(err) {
return $element({className: 'warning', appendChild: [ const contents = Array.isArray(err) ?
$create('pre', err.join('\n')) :
[err && err.message || err || 'Unknown error'];
if (Number.isInteger(err.index)) {
const pos = cm.posFromIndex(err.index);
contents[0] = `${pos.line + 1}:${pos.ch + 1} ` + contents[0];
contents.push($create('pre', drawLinePointer(pos)));
setTimeout(() => {
cm.scrollIntoView({line: pos.line + 1, ch: pos.ch}, window.innerHeight / 4);
cm.setCursor(pos.line, pos.ch + 1);
cm.focus();
});
}
return $create('.warning', [
t('parseUsercssError'), t('parseUsercssError'),
$element({tag: 'pre', textContent: String(err)}) '\n',
]}); ...contents,
]);
}
function drawLinePointer(pos) {
const SIZE = 60;
const line = cm.getLine(pos.line);
const numTabs = pos.ch + 1 - line.slice(0, pos.ch + 1).replace(/\t/g, '').length;
const pointer = ' '.repeat(pos.ch) + '^';
const start = Math.max(Math.min(pos.ch - SIZE / 2, line.length - SIZE), 0);
const end = Math.min(Math.max(pos.ch + SIZE / 2, SIZE), line.length);
const leftPad = start !== 0 ? '...' : '';
const rightPad = end !== line.length ? '...' : '';
return (
leftPad +
line.slice(start, end).replace(/\t/g, ' '.repeat(cm.options.tabSize)) +
rightPad +
'\n' +
' '.repeat(leftPad.length + numTabs * cm.options.tabSize) +
pointer.slice(start, end)
);
} }
function init({style, dup}) { function init({style, dup}) {
@ -245,7 +263,7 @@
// update UI // update UI
if (versionTest < 0) { if (versionTest < 0) {
$('.actions').parentNode.insertBefore( $('.actions').parentNode.insertBefore(
$element({className: 'warning', textContent: t('versionInvalidOlder')}), $create('.warning', t('versionInvalidOlder')),
$('.actions') $('.actions')
); );
} }
@ -258,9 +276,10 @@
data.version, data.version,
])) ]))
).then(ok => ok && ).then(ok => ok &&
sendMessage(Object.assign(style, {method: 'saveUsercss', reason: 'update'})) BG.usercssHelper.save(BG.deepCopy(Object.assign(style, {reason: 'update'})))
.then(install) .then(r => install(deepCopy(r)))
.catch(err => messageBox.alert(t('styleInstallFailed', err)))); .catch(err => messageBox.alert(t('styleInstallFailed', err)))
);
}; };
// set updateUrl // set updateUrl

View File

@ -73,8 +73,7 @@ if (!chrome.app && chrome.windows) {
} }
const iconset = ['', 'light/'][prefs.get('iconset')] || ''; const iconset = ['', 'light/'][prefs.get('iconset')] || '';
for (const size of [38, 32, 19, 16]) { for (const size of [38, 32, 19, 16]) {
document.head.appendChild($element({ document.head.appendChild($create('link', {
tag: 'link',
rel: 'icon', rel: 'icon',
href: `/images/icon/${iconset}${size}.png`, href: `/images/icon/${iconset}${size}.png`,
sizes: size + 'x' + size, sizes: size + 'x' + size,
@ -167,47 +166,103 @@ function $$(selector, base = document) {
} }
function $element(opt) { function $create(selector = 'div', properties, children) {
// tag: string, default 'div', may include namespace like 'ns#tag' /*
// appendChild: element/string or an array of elements/strings $create('tag#id.class.class', ?[children])
// dataset: object $create('tag#id.class.class', ?textContentOrChildNode)
// any DOM property: assigned as is $create('tag#id.class.class', {properties}, ?[children])
const [ns, tag] = opt.tag && opt.tag.includes('#') $create('tag#id.class.class', {properties}, ?textContentOrChildNode)
? opt.tag.split('#') tag is 'div' by default, #id and .class are optional
: [null, opt.tag];
$create([children])
$create({propertiesAndOptions})
$create({propertiesAndOptions}, ?[children])
tag: string, default 'div'
appendChild: element/string or an array of elements/strings
dataset: object
any DOM property: assigned as is
tag may include namespace like 'ns:tag'
*/
let ns, tag, opt;
if (typeof selector === 'string') {
if (Array.isArray(properties) ||
properties instanceof Node ||
typeof properties !== 'object') {
opt = {};
children = properties;
} else {
opt = properties || {};
}
const idStart = (selector.indexOf('#') + 1 || selector.length + 1) - 1;
const classStart = (selector.indexOf('.') + 1 || selector.length + 1) - 1;
const id = selector.slice(idStart + 1, classStart);
if (id) {
opt.id = id;
}
const cls = selector.slice(classStart + 1);
if (cls) {
opt[selector.includes(':') ? 'class' : 'className'] =
cls.includes('.') ? cls.replace(/\./g, ' ') : cls;
}
tag = selector.slice(0, Math.min(idStart, classStart));
} else if (Array.isArray(selector)) {
tag = 'div';
opt = {};
children = selector;
} else {
opt = selector;
tag = opt.tag;
delete opt.tag;
children = opt.appendChild || properties;
delete opt.appendChild;
}
if (tag && tag.includes(':')) {
([ns, tag] = tag.split(':'));
}
const element = ns const element = ns
? document.createElementNS(ns === 'SVG' || ns === 'svg' ? 'http://www.w3.org/2000/svg' : ns, tag) ? document.createElementNS(ns === 'SVG' || ns === 'svg' ? 'http://www.w3.org/2000/svg' : ns, tag)
: document.createElement(tag || 'div'); : document.createElement(tag || 'div');
const children = Array.isArray(opt.appendChild) ? opt.appendChild : [opt.appendChild];
for (const child of children) { for (const child of Array.isArray(children) ? children : [children]) {
if (child) { if (child) {
element.appendChild(child instanceof Node ? child : document.createTextNode(child)); element.appendChild(child instanceof Node ? child : document.createTextNode(child));
} }
} }
delete opt.appendChild;
delete opt.tag;
if (opt.dataset) { if (opt.dataset) {
Object.assign(element.dataset, opt.dataset); Object.assign(element.dataset, opt.dataset);
delete opt.dataset; delete opt.dataset;
} }
if (opt.attributes) { if (opt.attributes) {
for (const attr in opt.attributes) { for (const attr in opt.attributes) {
element.setAttribute(attr, opt.attributes[attr]); element.setAttribute(attr, opt.attributes[attr]);
} }
delete opt.attributes; delete opt.attributes;
} }
if (ns) { if (ns) {
for (const attr in opt) { for (const attr in opt) {
element.setAttributeNS(null, attr, opt[attr]); const i = attr.indexOf(':') + 1;
const attrNS = i && `http://www.w3.org/1999/${attr.slice(0, i - 1)}`;
element.setAttributeNS(attrNS || null, attr, opt[attr]);
} }
} else { } else {
Object.assign(element, opt); Object.assign(element, opt);
} }
return element; return element;
} }
function makeLink(href = '', content) { function $createLink(href = '', content) {
const opt = { const opt = {
tag: 'a', tag: 'a',
target: '_blank', target: '_blank',
@ -217,9 +272,9 @@ function makeLink(href = '', content) {
Object.assign(opt, href); Object.assign(opt, href);
} else { } else {
opt.href = href; opt.href = href;
opt.appendChild = content;
} }
return $element(opt); opt.appendChild = opt.appendChild || content;
return $create(opt);
} }

View File

@ -17,7 +17,7 @@ if (!CHROME && !chrome.browserAction.openPopup) {
FIREFOX = 50; FIREFOX = 50;
browser.runtime.getBrowserInfo().then(info => { browser.runtime.getBrowserInfo().then(info => {
FIREFOX = parseFloat(info.version); FIREFOX = parseFloat(info.version);
document.documentElement.classList.toggle('moz-appearance-bug', FIREFOX && FIREFOX < 54); document.documentElement.classList.add('moz-appearance-bug', FIREFOX && FIREFOX < 54);
}); });
} }
@ -62,7 +62,6 @@ if (BG && !BG.getStyles && BG !== window) {
} }
if (!BG || BG !== window) { if (!BG || BG !== window) {
document.documentElement.classList.toggle('firefox', FIREFOX); document.documentElement.classList.toggle('firefox', FIREFOX);
document.documentElement.classList.toggle('moz-appearance-bug', FIREFOX && FIREFOX < 54);
document.documentElement.classList.toggle('opera', OPERA); document.documentElement.classList.toggle('opera', OPERA);
// TODO: remove once our manifest's minimum_chrome_version is 50+ // TODO: remove once our manifest's minimum_chrome_version is 50+
// Chrome 49 doesn't report own extension pages in webNavigation apparently // Chrome 49 doesn't report own extension pages in webNavigation apparently

View File

@ -23,10 +23,11 @@ var mozParser = (() => {
const section = {code: '', start: {line, col}}; const section = {code: '', start: {line, col}};
// move last comment before @-moz-document inside the section // move last comment before @-moz-document inside the section
if (!/\/\*[\s\n]*AGENT_SHEET[\s\n]*\*\//.test(lastCmt)) { if (!/\/\*[\s\n]*AGENT_SHEET[\s\n]*\*\//.test(lastCmt)) {
if (lastCmt) {
section.code = lastCmt + '\n'; section.code = lastCmt + '\n';
const indent = outerText.match(/^\s*/)[0];
outerText = outerText.slice(0, -lastCmt.length); outerText = outerText.slice(0, -lastCmt.length);
outerText = indent + outerText.trim(); }
outerText = outerText.match(/^\s*/)[0] + outerText.trim();
} }
if (outerText.trim()) { if (outerText.trim()) {
lastSection.code = outerText; lastSection.code = outerText;

View File

@ -4,6 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title i18n-text="manageTitle"></title> <title i18n-text="manageTitle"></title>
<link rel="stylesheet" href="global.css">
<link rel="stylesheet" href="manage/manage.css"> <link rel="stylesheet" href="manage/manage.css">
<link rel="stylesheet" href="msgbox/msgbox.css"> <link rel="stylesheet" href="msgbox/msgbox.css">
<link rel="stylesheet" href="options/onoffswitch.css"> <link rel="stylesheet" href="options/onoffswitch.css">
@ -54,7 +55,10 @@
<template data-id="styleCompact"> <template data-id="styleCompact">
<div class="entry"> <div class="entry">
<h2 class="style-name"> <h2 class="style-name">
<div class="checkmate">
<input class="checker" type="checkbox" i18n-title="toggleStyle"> <input class="checker" type="checkbox" i18n-title="toggleStyle">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</div>
<a class="style-name-link"></a> <a class="style-name-link"></a>
</h2> </h2>
<p class="actions"> <p class="actions">
@ -156,50 +160,85 @@
</head> </head>
<body id="stylus-manage" i18n-dragndrop-hint="dragDropMessage"> <body id="stylus-manage" i18n-dragndrop-hint="dragDropMessage">
<div id="header"> <div id="header">
<h1 id="manage-heading" i18n-text="manageHeading"></h1> <h1 id="manage-heading" i18n-text="manageHeading"></h1>
<fieldset> <fieldset>
<legend id="filters"> <legend id="filters">
<span i18n-text="manageFilters"></span><span id="filters-stats"></span> <span i18n-text="manageFilters"></span><span id="filters-stats"></span>
</legend> </legend>
<div class="filter-selection">
<label> <label>
<div class="checkmate">
<input id="manage.onlyEnabled" type="checkbox" <input id="manage.onlyEnabled" type="checkbox"
data-filter=".enabled" data-filter=".enabled"
data-filter-hide=".disabled"> data-filter-hide=".disabled">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</div>
</label>
<div class="select-resizer">
<select id="manage.onlyEnabled.invert"> <select id="manage.onlyEnabled.invert">
<option i18n-text="manageOnlyEnabled" value="false"></option> <option i18n-text="manageOnlyEnabled" value="false"></option>
<option i18n-text="manageOnlyDisabled" value="true"></option> <option i18n-text="manageOnlyDisabled" value="true"></option>
</select> </select>
</label> <svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
</div>
</div>
<div class="filter-selection">
<label> <label>
<div class="checkmate">
<input id="manage.onlyLocal" type="checkbox" <input id="manage.onlyLocal" type="checkbox"
data-filter=":not(.updatable):not(.update-done)" data-filter=":not(.updatable):not(.update-done)"
data-filter-hide=".updatable, .update-done"> data-filter-hide=".updatable, .update-done">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</div>
</label>
<div class="select-resizer">
<select id="manage.onlyLocal.invert" i18n-title="manageOnlyLocalTooltip"> <select id="manage.onlyLocal.invert" i18n-title="manageOnlyLocalTooltip">
<option i18n-text="manageOnlyLocal" value="false"></option> <option i18n-text="manageOnlyLocal" value="false"></option>
<option i18n-text="manageOnlyExternal" value="true"></option> <option i18n-text="manageOnlyExternal" value="true"></option>
</select> </select>
</label> <svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
</div>
</div>
<div class="filter-selection">
<label> <label>
<div class="checkmate">
<input id="manage.onlyUsercss" type="checkbox" <input id="manage.onlyUsercss" type="checkbox"
data-filter=".usercss" data-filter=".usercss"
data-filter-hide=":not(.usercss)"> data-filter-hide=":not(.usercss)">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</div>
</label>
<div class="select-resizer">
<select id="manage.onlyUsercss.invert"> <select id="manage.onlyUsercss.invert">
<option i18n-text="manageOnlyUsercss" value="false"></option> <option i18n-text="manageOnlyUsercss" value="false"></option>
<option i18n-text="manageOnlyNonUsercss" value="true"></option> <option i18n-text="manageOnlyNonUsercss" value="true"></option>
</select> </select>
</label> <svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
</div>
</div>
<label id="onlyUpdates" class="hidden"> <label id="onlyUpdates" class="hidden">
<input type="checkbox" <input type="checkbox"
data-filter=".can-update, .update-problem, .update-done" data-filter=".can-update, .update-problem, .update-done"
data-filter-hide=":not(.updatable):not(.update-done), .no-update:not(.update-problem)"> data-filter-hide=":not(.updatable):not(.update-done), .no-update:not(.update-problem)">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
<span i18n-text="manageOnlyUpdates"></span> <span i18n-text="manageOnlyUpdates"></span>
</label> </label>
<input id="search" type="search" i18n-placeholder="searchStyles" spellcheck="false" <input id="search" type="search" i18n-placeholder="searchStyles" spellcheck="false"
i18n-title="searchStylesTooltip" i18n-title="searchStylesTooltip"
data-filter=":not(.not-matching)" data-filter=":not(.not-matching)"
data-filter-hide=".not-matching"> data-filter-hide=".not-matching">
</fieldset> </fieldset>
<p class="nowrap"> <p class="nowrap">
<button id="check-all-updates" i18n-text="checkAllUpdates"><span id="update-progress"></span></button> <button id="check-all-updates" i18n-text="checkAllUpdates"><span id="update-progress"></span></button>
<span id="update-history" i18n-title="genericHistoryLabel"> <span id="update-history" i18n-title="genericHistoryLabel">
@ -208,17 +247,20 @@
</svg> </svg>
</span> </span>
</p> </p>
<p> <p>
<button id="apply-all-updates" class="hidden" i18n-text="applyAllUpdates"></button> <button id="apply-all-updates" class="hidden" i18n-text="applyAllUpdates"></button>
<span id="update-all-no-updates" class="hidden" i18n-text="updateAllCheckSucceededNoUpdate"></span> <span id="update-all-no-updates" class="hidden" i18n-text="updateAllCheckSucceededNoUpdate"></span>
<button id="check-all-updates-force" class="hidden" i18n-text="checkAllUpdatesForce"></button> <button id="check-all-updates-force" class="hidden" i18n-text="checkAllUpdatesForce"></button>
</p> </p>
<p> <p>
<a href="edit.html"> <a href="edit.html">
<button id="add-style-label" i18n-text="addStyleLabel"></button> <button id="add-style-label" i18n-text="addStyleLabel"></button>
</a> </a>
<label id="newStyleAsUsercss-wrapper" class="nobreak"> <label id="newStyleAsUsercss-wrapper" class="nobreak">
<input type="checkbox" id="newStyleAsUsercss"> <input type="checkbox" id="newStyleAsUsercss">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
<span i18n-text="manageNewStyleAsUsercss" i18n-title="optionsAdvancedNewStyleAsUsercss"></span> <span i18n-text="manageNewStyleAsUsercss" i18n-title="optionsAdvancedNewStyleAsUsercss"></span>
<a id="usercss-wiki" <a id="usercss-wiki"
href="https://github.com/openstyles/stylus/wiki/Usercss" href="https://github.com/openstyles/stylus/wiki/Usercss"
@ -229,25 +271,38 @@
</a> </a>
</label> </label>
</p> </p>
<details id="options" data-pref="manage.options.expanded"> <details id="options" data-pref="manage.options.expanded">
<summary><h2 id="options-heading" i18n-text="optionsHeading"></h2></summary> <summary><h2 id="options-heading" i18n-text="optionsHeading"></h2></summary>
<label><input id="manage.newUI" type="checkbox"><span i18n-text="manageNewUI"></span></label>
<label>
<input id="manage.newUI" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
<span i18n-text="manageNewUI"></span>
</label>
<div id="newUIoptions"> <div id="newUIoptions">
<div> <div>
<label for="manage.newUI.favicons" i18n-text="manageFavicons">
<input id="manage.newUI.favicons" type="checkbox"> <input id="manage.newUI.favicons" type="checkbox">
<label for="manage.newUI.favicons" i18n-text="manageFavicons"></label> <svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
<svg class="svg-icon info" viewBox="0 0 14 16" i18n-alt="helpAlt" data-toggle-on-click="#faviconsHelp"> <svg class="svg-icon info" viewBox="0 0 14 16" i18n-alt="helpAlt" data-toggle-on-click="#faviconsHelp">
<path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path> <path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path>
</svg> </svg>
<div id="faviconsHelp" class="hidden" i18n-text="manageFaviconsHelp"> <div id="faviconsHelp" class="hidden" i18n-text="manageFaviconsHelp">
<div> <div>
<label for="manage.newUI.faviconsGray" i18n-text="manageFaviconsGray">
<input id="manage.newUI.faviconsGray" type="checkbox"> <input id="manage.newUI.faviconsGray" type="checkbox">
<label for="manage.newUI.faviconsGray" i18n-text="manageFaviconsGray"></label> <svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
</div> </div>
</div> </div>
</div> </div>
<label><input id="manage.newUI.targets" type="number" min="1" max="99"><span i18n-text="manageMaxTargets"></span></label> <label><input id="manage.newUI.targets" type="number" min="1" max="99"><span i18n-text="manageMaxTargets"></span></label>
</div> </div>
<p> <p>
<button id="manage-options-button" i18n-text="openOptionsManage"></button> <button id="manage-options-button" i18n-text="openOptionsManage"></button>
<button id="manage-shortcuts-button" class="chromium-only" <button id="manage-shortcuts-button" class="chromium-only"
@ -258,7 +313,9 @@
i18n-title="editorStylesButton" i18n-title="editorStylesButton"
target="_blank"><button i18n-text="cm_theme"></button></a> target="_blank"><button i18n-text="cm_theme"></button></a>
</p> </p>
</details> </details>
<div id="backup"> <div id="backup">
<h2 id="backup-title" i18n-text="backupButtons"></h2> <h2 id="backup-title" i18n-text="backupButtons"></h2>
<span id="backup-message" i18n-text="backupMessage"></span> <span id="backup-message" i18n-text="backupMessage"></span>
@ -267,13 +324,26 @@
<button id="unfile-all-styles" i18n-text="retrieveBckp"></button> <button id="unfile-all-styles" i18n-text="retrieveBckp"></button>
</p> </p>
</div> </div>
<p id="manage-text" i18n-html="manageText"></p> <p id="manage-text" i18n-html="manageText"></p>
</div> </div>
<div id="installed"></div> <div id="installed"></div>
<script src="manage/import-export.js"></script> <script src="manage/import-export.js"></script>
<script src="msgbox/msgbox.js"></script> <script src="msgbox/msgbox.js"></script>
<script src="manage/incremental-search.js" async></script> <script src="manage/incremental-search.js" async></script>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none !important;">
<symbol id="svg-icon-checked" viewBox="0 0 1000 1000">
<path fill-rule="evenodd" d="M983.2,184.3L853,69.8c-4-3.5-9.3-5.3-14.5-5c-5.3,0.4-10.3,2.8-13.8,6.8L352.3,609.2L184.4,386.9c-3.2-4.2-8-7-13.2-7.8c-5.3-0.8-10.6,0.6-14.9,3.9L18,487.5c-8.8,6.7-10.6,19.3-3.9,28.1L325,927.2c3.6,4.8,9.3,7.7,15.3,8c0.2,0,0.5,0,0.7,0c5.8,0,11.3-2.5,15.1-6.8L985,212.6C992.3,204.3,991.5,191.6,983.2,184.3z"/>
</symbol>
<symbol id="svg-icon-select-arrow" viewBox="0 0 1792 1792">
<path fill-rule="evenodd" d="M1408 704q0 26-19 45l-448 448q-19 19-45 19t-45-19l-448-448q-19-19-19-45t19-45 45-19h896q26 0 45 19t19 45z"/>
</symbol>
</svg>
</body> </body>
</html> </html>

View File

@ -1,8 +1,9 @@
/* global messageBox makeLink */ /* global messageBox */
'use strict'; 'use strict';
function configDialog(style) { function configDialog(style) {
const varsHash = deepCopy(style.usercssData.vars) || {}; const data = style.usercssData;
const varsHash = deepCopy(data.vars) || {};
const varNames = Object.keys(varsHash); const varNames = Object.keys(varsHash);
const vars = varNames.map(name => varsHash[name]); const vars = varNames.map(name => varsHash[name]);
const elements = []; const elements = [];
@ -12,21 +13,12 @@ function configDialog(style) {
renderValues(); renderValues();
return messageBox({ return messageBox({
title: `${style.name} v${style.usercssData.version}`, title: `${style.name} v${data.version}`,
className: 'config-dialog', className: 'config-dialog',
contents: [ contents: [
$element({ $create('.config-heading', data.supportURL &&
className: 'config-heading', $createLink({className: '.external-support', href: data.supportURL}, t('externalFeedback'))),
appendChild: style.usercssData.supportURL && makeLink({ $create('.config-body', elements)
className: 'external-support',
href: style.usercssData.supportURL,
textContent: t('externalFeedback')
})
}),
$element({
className: 'config-body',
appendChild: elements
})
], ],
buttons: [ buttons: [
t('confirmSave'), t('confirmSave'),
@ -71,19 +63,17 @@ function configDialog(style) {
continue; continue;
} }
invalid.push(['*' + va.name, ': ', ...error].map(e => invalid.push(['*' + va.name, ': ', ...error].map(e =>
e[0] === '*' && $element({tag: 'b', textContent: e.slice(1)}) || e)); e[0] === '*' && $create('b', e.slice(1)) || e));
if (bgva) { if (bgva) {
styleVars[va.name].value = deepCopy(bgva); styleVars[va.name].value = deepCopy(bgva);
} }
} }
if (invalid.length) { if (invalid.length) {
messageBox.alert([ messageBox.alert([
$element({textContent: t('usercssConfigIncomplete'), style: 'max-width: 34em'}), $create('div', {style: 'max-width: 34em'}, t('usercssConfigIncomplete')),
$element({ $create('ol', {style: 'text-align: left'},
tag: 'ol', invalid.map(msg =>
style: 'text-align: left', $create({tag: 'li', appendChild: msg}))),
appendChild: invalid.map(msg => $element({tag: 'li', appendChild: msg})),
}),
]); ]);
} }
return numValid && BG.usercssHelper.save(style); return numValid && BG.usercssHelper.save(style);
@ -91,30 +81,28 @@ function configDialog(style) {
function buildConfigForm() { function buildConfigForm() {
for (const va of vars) { for (const va of vars) {
let appendChild; let children;
switch (va.type) { switch (va.type) {
case 'color': case 'color':
appendChild = [$element({ va.inputColor = $create('.color-swatch', {va, onclick: showColorpicker});
className: 'cm-colorview', children = [
appendChild: va.inputColor = $element({ $create('.cm-colorview', [
va, va.inputColor,
className: 'color-swatch', ]),
onclick: showColorpicker, ];
})
})];
break; break;
case 'checkbox': case 'checkbox':
va.input = $element({tag: 'input', type: 'checkbox', className: 'slider'}); va.input = $create('input.slider', {type: 'checkbox'});
va.input.onchange = () => { va.input.onchange = () => {
va.dirty = true; va.dirty = true;
va.value = String(Number(va.input.checked)); va.value = String(Number(va.input.checked));
}; };
appendChild = [ children = [
$element({tag: 'span', className: 'onoffswitch', appendChild: [ $create('span.onoffswitch', [
va.input, va.input,
$element({tag: 'span'}) $create('span'),
]}) ])
]; ];
break; break;
@ -122,36 +110,33 @@ function configDialog(style) {
case 'dropdown': case 'dropdown':
case 'image': case 'image':
// TODO: a image picker input? // TODO: a image picker input?
va.input = $element({ va.input = $create('.select-resizer', [
tag: 'select', $create('select', va.options.map(o =>
appendChild: va.options.map(o => $element({ $create('option', {value: o.name}, o.label))),
tag: 'option', value: o.name, textContent: o.label $create('SVG:svg.svg-icon.select-arrow',
})) $create('SVG:use', {'xlink:href': '#svg-icon-select-arrow'})),
}); ]);
va.input.onchange = () => { va.input.onchange = () => {
va.dirty = true; va.dirty = true;
va.value = va.input.value; va.value = va.input.value;
}; };
appendChild = [va.input]; children = [va.input];
break; break;
default: default:
va.input = $element({tag: 'input', type: 'text'}); va.input = $create('input', {type: 'text'});
va.input.oninput = () => { va.input.oninput = () => {
va.dirty = true; va.dirty = true;
va.value = va.input.value; va.value = va.input.value;
}; };
appendChild = [va.input]; children = [va.input];
break; break;
} }
elements.push($element({ elements.push(
tag: 'label', $create(`label.config-${va.type}`, [
className: `config-${va.type}`, $create('span', va.label),
appendChild: [ ...children,
$element({tag: 'span', appendChild: va.label}), ]));
...appendChild,
],
}));
} }
} }

View File

@ -260,6 +260,9 @@ function reapplyFilter(container = installed) {
}; };
} }
} }
if (fullPass) {
showFiltersStats({immediately: true});
}
} }
function findInsertionPoint(entry) { function findInsertionPoint(entry) {
@ -295,9 +298,9 @@ function reapplyFilter(container = installed) {
} }
function showFiltersStats({immediately} = {}) { function showFiltersStats() {
if (!immediately) { if (!BG.cachedStyles.list) {
debounce(showFiltersStats, 100, {immediately: true}); debounce(showFiltersStats, 100);
return; return;
} }
$('#filters').classList.toggle('active', filtersSelector.hide !== ''); $('#filters').classList.toggle('active', filtersSelector.hide !== '');
@ -309,6 +312,8 @@ function showFiltersStats({immediately} = {}) {
filtersSelector.numShown = numShown; filtersSelector.numShown = numShown;
filtersSelector.numTotal = numTotal; filtersSelector.numTotal = numTotal;
$('#filters-stats').textContent = t('filteredStyles', [numShown, numTotal]); $('#filters-stats').textContent = t('filteredStyles', [numShown, numTotal]);
document.body.classList.toggle('all-styles-hidden-by-filters',
!numShown && numTotal && filtersSelector.hide);
} }
} }

View File

@ -175,18 +175,16 @@ function importFromString(jsonString) {
.map(kind => { .map(kind => {
const {ids, names, legend} = stats[kind]; const {ids, names, legend} = stats[kind];
const listItemsWithId = (name, i) => const listItemsWithId = (name, i) =>
$element({dataset: {id: ids[i]}, textContent: name}); $create('div', {dataset: {id: ids[i]}}, name);
const listItems = name => const listItems = name =>
$element({textContent: name}); $create('div', name);
const block = const block =
$element({tag: 'details', dataset: {id: kind}, appendChild: [ $create('details', {dataset: {id: kind}}, [
$element({tag: 'summary', appendChild: $create('summary',
$element({tag: 'b', textContent: names.length + ' ' + t(legend)}) $create('b', names.length + ' ' + t(legend))),
}), $create('small',
$element({tag: 'small', appendChild: names.map(ids ? listItemsWithId : listItems)),
names.map(ids ? listItemsWithId : listItems) ]);
}),
]});
return block; return block;
}); });
scrollTo(0, 0); scrollTo(0, 0);
@ -308,8 +306,7 @@ $('#file-all-styles').onclick = () => {
const text = JSON.stringify(styles, null, '\t'); const text = JSON.stringify(styles, null, '\t');
const blob = new Blob([text], {type: 'application/json'}); const blob = new Blob([text], {type: 'application/json'});
const objectURL = URL.createObjectURL(blob); const objectURL = URL.createObjectURL(blob);
let link = $element({ let link = $create('a', {
tag:'a',
href: objectURL, href: objectURL,
type: 'application/json', type: 'application/json',
download: generateFileName(), download: generateFileName(),
@ -319,8 +316,7 @@ $('#file-all-styles').onclick = () => {
link.dispatchEvent(new MouseEvent('click')); link.dispatchEvent(new MouseEvent('click'));
setTimeout(() => URL.revokeObjectURL(objectURL)); setTimeout(() => URL.revokeObjectURL(objectURL));
} else { } else {
const iframe = document.body.appendChild($element({ const iframe = document.body.appendChild($create('iframe', {
tag: 'iframe',
style: 'width: 0; height: 0; position: fixed; opacity: 0;'.replace(/;/g, '!important;'), style: 'width: 0; height: 0; position: fixed; opacity: 0;'.replace(/;/g, '!important;'),
})); }));
doTimeout() doTimeout()

View File

@ -5,8 +5,7 @@ onDOMready().then(() => {
let prevText, focusedLink, focusedEntry; let prevText, focusedLink, focusedEntry;
let prevTime = performance.now(); let prevTime = performance.now();
let focusedName = ''; let focusedName = '';
const input = $element({ const input = $create('textarea', {
tag: 'textarea',
spellcheck: false, spellcheck: false,
oninput: incrementalSearch, oninput: incrementalSearch,
}); });

View File

@ -15,6 +15,26 @@ body {
height: 100%; height: 100%;
} }
body.all-styles-hidden-by-filters:before,
body.all-styles-hidden-by-filters:after {
position: absolute;
left: calc(3rem + var(--header-width));
color: hsla(180, 40%, 45%, .3);
animation: fadein 3s;
}
body.all-styles-hidden-by-filters:before {
content: "\2190"; /* left arrow */
font-size: 3rem;
top: 3.5rem;
}
body.all-styles-hidden-by-filters:after {
font-size: 1.5rem;
position: absolute;
top: 3rem;
}
a, .disabled a:hover { a, .disabled a:hover {
color: #000; color: #000;
transition: color .5s; transition: color .5s;
@ -74,6 +94,10 @@ select {
fill: #aaa; fill: #aaa;
} }
#usercss-wiki svg {
margin-top: -2px;
}
.nobreak { .nobreak {
white-space: nowrap; white-space: nowrap;
} }
@ -250,16 +274,17 @@ label.nobreak input {
margin-left: -13px; margin-left: -13px;
cursor: pointer; cursor: pointer;
outline: none; outline: none;
margin-bottom: 8px;
} }
#header summary h2 { #header summary h2 {
display: inline-block; display: inline-block;
border-bottom: 1px dotted transparent; border-bottom: 1px dotted transparent;
margin-top: .2em; margin-top: .1em;
margin-bottom: .4em; margin-bottom: .1em;
} }
#header summary h2:hover { #header summary:hover h2 {
border-color: #bbb; border-color: #bbb;
} }
@ -289,11 +314,107 @@ label.nobreak input {
display: table-cell; display: table-cell;
vertical-align: middle; vertical-align: middle;
} }
/************ checkbox & select************/
.newUI .checker { .newUI .checker {
margin: 0;
}
#newUIoptions > div {
margin: 4px 0;
}
.filter-selection {
position: relative; position: relative;
top: 1px; }
margin: 0 1ex 0 0;
#header label {
padding-left: 16px;
position: relative;
}
#header .filter-selection label {
display: flex;
align-items: center;
height: 18px;
width: 100%;
padding: 0 0 0 4px;
}
#header .filter-selection label .checkmate {
vertical-align: middle;
margin-top: -2px;
}
.firefox #header .filter-selection label .checkmate {
margin: 0;
}
.newUI #newUIoptions > label {
padding-left: 0;
}
.filter-selection select {
height: 18px;
outline: none;
border: none;
max-width: 100%;
padding-left: 4px;
padding-right: 14px;
}
.firefox .filter-selection select {
padding-left: 0;
}
.filter-selection .select-arrow {
margin-top: 2px;
top: 0;
right: 0;
}
.select-resizer {
cursor: default;
border: none;
max-width: calc(100% - 2em);
position: absolute;
top: 0;
left: 16px;
}
.firefox .select-resizer {
left: 16px;
}
fieldset > label,
fieldset > .filter-selection {
transition: background-color .25s;
}
fieldset > label:hover,
fieldset > .filter-selection:hover {
background-color: hsla(0, 0%, 50%, .2);
}
.checkmate {
position: relative;
height: 12px;
width: 12px;
display: inline-flex;
}
.entry .checkmate {
vertical-align: middle;
margin: -2px 1ex 0 0;
}
.newUI .entry .svg-icon.checked,
.newUI .entry:hover .svg-icon.checked {
fill: #000;
}
.newUI .entry input[type="checkbox"]:not(.slider) {
pointer-events: all;
} }
.newUI .style-name { .newUI .style-name {
@ -638,13 +759,19 @@ fieldset {
max-width: 250px; max-width: 250px;
} }
fieldset > *:not(legend) { fieldset > input,
fieldset > label {
display: flex; display: flex;
align-items: center; align-items: center;
} }
fieldset > label:hover { #header fieldset > label {
background-color: hsla(0, 0%, 50%, .1); padding-left: 20px;
}
#header fieldset > label input[type="checkbox"]:not(.slider),
#header fieldset > label input[type="checkbox"]:not(.slider):checked + .svg-icon.checked{
left: 4px;
} }
#filters { #filters {
@ -665,12 +792,6 @@ fieldset > label:hover {
content: ": "; content: ": ";
} }
fieldset select {
border: none;
max-width: calc(100% - 2em);
background-color: transparent;
}
#search { #search {
width: calc(100% - 4px); width: calc(100% - 4px);
margin: 0.25rem 4px 0; margin: 0.25rem 4px 0;
@ -752,6 +873,10 @@ fieldset select {
align-items: center; align-items: center;
} }
.config-dialog .select-resizer {
position: static;
}
.config-dialog label:first-child { .config-dialog label:first-child {
padding-top: 0; padding-top: 0;
} }
@ -788,10 +913,13 @@ fieldset select {
vertical-align: middle; vertical-align: middle;
} }
.config-dialog .select-resizer,
.config-dialog select { .config-dialog select {
width: auto; width: auto;
min-width: var(--onoffswitch-width); min-width: var(--onoffswitch-width);
max-width: 124px; max-width: 124px;
left: auto;
position: relative;
} }
.config-dialog .onoffswitch { .config-dialog .onoffswitch {
@ -808,7 +936,7 @@ fieldset select {
flex-shrink: 0; flex-shrink: 0;
} }
.config-dialog label > :last-child:not(.onoffswitch) > :not(:last-child) { .config-dialog label > :last-child:not(.onoffswitch):not(.select-resizer) > :not(:last-child) {
margin-right: 4px; margin-right: 4px;
} }
@ -898,6 +1026,17 @@ fieldset select {
flex-direction: column; flex-direction: column;
} }
body.all-styles-hidden-by-filters:before {
content: "\2191"; /* up arrow */
top: calc(50% + 2.75rem);
left: 2rem;
}
body.all-styles-hidden-by-filters:after {
top: calc(50% + 4rem);
left: 3.75rem;
}
#header { #header {
height: auto; height: auto;
position: static; position: static;
@ -972,12 +1111,3 @@ fieldset select {
word-break: break-all; word-break: break-all;
} }
} }
@supports (-moz-appearance:none) {
/* fixes for Firefox quirks */
#newStyleAsUsercss-wrapper svg {
margin-top: -2px;
}
}

View File

@ -84,14 +84,17 @@ function initGlobalEvents() {
switchUI({styleOnly: true}); switchUI({styleOnly: true});
// translate CSS manually // translate CSS manually
document.head.appendChild($element({tag: 'style', textContent: ` document.head.appendChild($create('style', `
.disabled h2::after { .disabled h2::after {
content: "${t('genericDisabledLabel')}"; content: "${t('genericDisabledLabel')}";
} }
#update-all-no-updates[data-skipped-edited="true"]:after { #update-all-no-updates[data-skipped-edited="true"]:after {
content: " ${t('updateAllCheckSucceededSomeEdited')}"; content: " ${t('updateAllCheckSucceededSomeEdited')}";
} }
`})); body.all-styles-hidden-by-filters:after {
content: "${t('filteredStylesAllHidden')}";
}
`));
} }

View File

@ -122,9 +122,9 @@ function reportUpdateState(state, style, details) {
const edited = details === BG.updater.EDITED || details === BG.updater.MAYBE_EDITED; const edited = details === BG.updater.EDITED || details === BG.updater.MAYBE_EDITED;
entry.dataset.details = details; entry.dataset.details = details;
if (!details) { if (!details) {
details = t('updateCheckFailServerUnreachable'); details = t('updateCheckFailServerUnreachable') + '\n' + style.updateUrl;
} else if (typeof details === 'number') { } else if (typeof details === 'number') {
details = t('updateCheckFailBadResponseCode', [details]); details = t('updateCheckFailBadResponseCode', [details]) + '\n' + style.updateUrl;
} else if (details === BG.updater.EDITED) { } else if (details === BG.updater.EDITED) {
details = t('updateCheckSkippedLocallyEdited') + '\n' + t('updateCheckManualUpdateHint'); details = t('updateCheckSkippedLocallyEdited') + '\n' + t('updateCheckManualUpdateHint');
} else if (details === BG.updater.MAYBE_EDITED) { } else if (details === BG.updater.MAYBE_EDITED) {
@ -174,10 +174,7 @@ function showUpdateHistory() {
BG.chromeLocal.getValue('updateLog').then((lines = []) => { BG.chromeLocal.getValue('updateLog').then((lines = []) => {
messageBox({ messageBox({
title: t('updateCheckHistory'), title: t('updateCheckHistory'),
contents: $element({ contents: $create('.update-history-log', lines.join('\n')),
className: 'update-history-log',
textContent: lines.join('\n'),
}),
buttons: [t('confirmOK')], buttons: [t('confirmOK')],
onshow: () => ($('#message-box-contents').scrollTop = 1e9), onshow: () => ($('#message-box-contents').scrollTop = 1e9),
}); });

View File

@ -1,6 +1,6 @@
{ {
"name": "Stylus", "name": "Stylus",
"version": "1.1.7", "version": "1.1.7.2",
"minimum_chrome_version": "49", "minimum_chrome_version": "49",
"description": "__MSG_description__", "description": "__MSG_description__",
"homepage_url": "http://add0n.com/stylus.html", "homepage_url": "http://add0n.com/stylus.html",

View File

@ -28,7 +28,7 @@
} }
#message-box.fadeout { #message-box.fadeout {
animation: fadeout .5s ease-in-out; animation: fadeout .25s ease-in-out;
} }
#message-box.center { #message-box.center {

View File

@ -58,27 +58,25 @@ function messageBox({
removeSelf(); removeSelf();
} }
const id = 'message-box'; const id = 'message-box';
messageBox.element = $element({id, className, appendChild: [ messageBox.element =
$element({appendChild: [ $create({id, className}, [
$element({id: `${id}-title`, textContent: title}), $create([
$element({id: `${id}-close-icon`, appendChild: $create(`#${id}-title`, title),
$element({tag: 'SVG#svg', class: 'svg-icon', viewBox: '0 0 20 20', appendChild: $create(`#${id}-close-icon`, {onclick: messageBox.listeners.closeIcon},
$element({tag: 'SVG#path', d: 'M11.69,10l4.55,4.55-1.69,1.69L10,11.69,' + $create('SVG:svg.svg-icon', {viewBox: '0 0 20 20'},
$create('SVG:path', {d: 'M11.69,10l4.55,4.55-1.69,1.69L10,11.69,' +
'5.45,16.23,3.77,14.55,8.31,10,3.77,5.45,5.45,3.77,10,8.31l4.55-4.55,1.69,1.69Z', '5.45,16.23,3.77,14.55,8.31,10,3.77,5.45,5.45,3.77,10,8.31l4.55-4.55,1.69,1.69Z',
}) }))),
}), $create(`#${id}-contents`, tHTML(contents)),
onclick: messageBox.listeners.closeIcon}), $create(`#${id}-buttons`,
$element({id: `${id}-contents`, appendChild: tHTML(contents)}), buttons.map((content, buttonIndex) => content &&
$element({id: `${id}-buttons`, appendChild: $create('button', {
buttons.map((content, buttonIndex) => content && $element({
tag: 'button',
buttonIndex, buttonIndex,
textContent: content.textContent || content, textContent: content.textContent || content,
onclick: content.onclick || messageBox.listeners.button, onclick: content.onclick || messageBox.listeners.button,
})) }))),
}), ]),
]}), ]);
]});
} }
function bindGlobalListeners() { function bindGlobalListeners() {

View File

@ -3,6 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="global.css">
<link rel="stylesheet" href="popup/popup.css"> <link rel="stylesheet" href="popup/popup.css">
<link rel="stylesheet" href="popup/search-results.css"> <link rel="stylesheet" href="popup/search-results.css">
@ -27,9 +28,7 @@
<div class="main-controls"> <div class="main-controls">
<label class="style-name"> <label class="style-name">
<input class="checker" type="checkbox"> <input class="checker" type="checkbox">
<svg class="svg-icon checked" viewBox="0 0 1000 1000"> <svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
<path fill-rule="evenodd" d="M983.2,184.3L853,69.8c-4-3.5-9.3-5.3-14.5-5c-5.3,0.4-10.3,2.8-13.8,6.8L352.3,609.2L184.4,386.9c-3.2-4.2-8-7-13.2-7.8c-5.3-0.8-10.6,0.6-14.9,3.9L18,487.5c-8.8,6.7-10.6,19.3-3.9,28.1L325,927.2c3.6,4.8,9.3,7.7,15.3,8c0.2,0,0.5,0,0.7,0c5.8,0,11.3-2.5,15.1-6.8L985,212.6C992.3,204.3,991.5,191.6,983.2,184.3z"/>
</svg>
</label> </label>
</div> </div>
<div class="actions"> <div class="actions">
@ -141,11 +140,10 @@
<div class="actions"> <div class="actions">
<div id="disable-all-wrapper"> <div id="disable-all-wrapper">
<div class="main-controls"> <div class="main-controls">
<label id="disableAll-label" i18n-text="disableAllStyles">
<input id="disableAll" type="checkbox"> <input id="disableAll" type="checkbox">
<svg class="svg-icon checked" viewBox="0 0 1000 1000"> <svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
<path fill-rule="evenodd" d="M983.2,184.3L853,69.8c-4-3.5-9.3-5.3-14.5-5c-5.3,0.4-10.3,2.8-13.8,6.8L352.3,609.2L184.4,386.9c-3.2-4.2-8-7-13.2-7.8c-5.3-0.8-10.6,0.6-14.9,3.9L18,487.5c-8.8,6.7-10.6,19.3-3.9,28.1L325,927.2c3.6,4.8,9.3,7.7,15.3,8c0.2,0,0.5,0,0.7,0c5.8,0,11.3-2.5,15.1-6.8L985,212.6C992.3,204.3,991.5,191.6,983.2,184.3z"/> </label>
</svg>
<label id="disableAll-label" for="disableAll" i18n-text="disableAllStyles"></label>
</div> </div>
</div> </div>
<div id="searchResults" class="hidden"> <div id="searchResults" class="hidden">
@ -193,5 +191,12 @@
i18n-title="shortcutsNote"></button> i18n-title="shortcutsNote"></button>
</div> </div>
</div> </div>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none !important;">
<symbol id="svg-icon-checked" viewBox="0 0 1000 1000">
<path fill-rule="evenodd" d="M983.2,184.3L853,69.8c-4-3.5-9.3-5.3-14.5-5c-5.3,0.4-10.3,2.8-13.8,6.8L352.3,609.2L184.4,386.9c-3.2-4.2-8-7-13.2-7.8c-5.3-0.8-10.6,0.6-14.9,3.9L18,487.5c-8.8,6.7-10.6,19.3-3.9,28.1L325,927.2c3.6,4.8,9.3,7.7,15.3,8c0.2,0,0.5,0,0.7,0c5.8,0,11.3-2.5,15.1-6.8L985,212.6C992.3,204.3,991.5,191.6,983.2,184.3z"/>
</symbol>
</svg>
</body> </body>
</html> </html>

View File

@ -142,13 +142,13 @@ window.addEventListener('showStyles:done', function _() {
line line
.split(/(<.*?>)/) .split(/(<.*?>)/)
.map(s => (!s.startsWith('<') ? s : .map(s => (!s.startsWith('<') ? s :
$element({tag: 'mark', textContent: s.slice(1, -1)}))); $create('mark', s.slice(1, -1))));
const linesToElements = text => const linesToElements = text =>
text text
.trim() .trim()
.split('\n') .split('\n')
.map((line, i, array) => .map((line, i, array) =>
$element(i < array.length - 1 ? { $create(i < array.length - 1 ? {
tag: 'p', tag: 'p',
appendChild: keysToElements(line), appendChild: keysToElements(line),
} : { } : {
@ -159,9 +159,9 @@ window.addEventListener('showStyles:done', function _() {
})); }));
[ [
linesToElements(t('popupHotkeysInfo')), linesToElements(t('popupHotkeysInfo')),
$element({tag: 'button', textContent: t('confirmOK')}), $create('button', t('confirmOK')),
].forEach(child => { ].forEach(child => {
container.appendChild($element({appendChild: child})); container.appendChild($create('div', child));
}); });
} }
} }

View File

@ -22,112 +22,57 @@ body > div:not(#installed) {
.firefox .chromium-only { .firefox .chromium-only {
display: none; display: none;
} }
/************ checkbox ************/
/* there's a bug in FF 52 ESR so until we find a better workaround we won't style checkboxes in FF */ .style-name:hover input[type="checkbox"]:checked {
html.moz-appearance-bug .svg-icon.checked {
display: none;
}
html.moz-appearance-bug input[type="checkbox"] {
position: absolute;
left: 8px;
top: 0;
bottom: 0;
margin: auto;
}
html.moz-appearance-bug #disableAll {
left: -1px;
}
html.moz-appearance-bug #disableAll-label {
padding-left: 16px !important;
}
html:not(.moz-appearance-bug) .svg-icon.checked {
position: absolute;
height: 8px;
width: 8px;
display: none;
fill: #000;
margin: 2px 0 0 2px;
}
html:not(.moz-appearance-bug) input[type="checkbox"]:checked + .svg-icon.checked {
display: inline-flex;
transition: fill .25s;
}
html:not(.moz-appearance-bug) input[type="checkbox"] {
-moz-appearance: none;
-webkit-appearance: none;
border: 1px solid hsl(0, 0%, 46%);
height: 12px;
width: 12px;
display: inline-flex;
border-radius: 2px;
background-color: hsl(0, 0%, 94%);
outline: none;
margin: 0;
transition: background-color .25s, border-color .25s;
}
html:not(.moz-appearance-bug) input[type="checkbox"]:checked:hover,
html:not(.moz-appearance-bug) .style-name:hover input[type="checkbox"]:checked {
border-color: hsl(0, 0%, 32%); border-color: hsl(0, 0%, 32%);
background-color: hsl(0, 0%, 82%); background-color: hsl(0, 0%, 82%);
} }
html:not(.moz-appearance-bug) input[type="checkbox"]:hover, .style-name:hover input[type="checkbox"] {
html:not(.moz-appearance-bug) .style-name:hover input[type="checkbox"],
html:not(.moz-appearance-bug) .entry.disabled .style-name:hover input[type="checkbox"] {
border-color: hsl(0, 0%, 32%); border-color: hsl(0, 0%, 32%);
background-color: hsl(0, 0%, 82%); background-color: hsl(0, 0%, 82%);
} }
html:not(.moz-appearance-bug) #installed.disabled + .actions #disableAll:checked + .svg-icon.checked { #disable-all-wrapper input[type="checkbox"]:not(.slider):checked + .svg-icon.checked {
fill: hsl(0, 68%, 42%);
}
html:not(.moz-appearance-bug) #installed.disabled + .actions #disableAll:checked {
border-color: hsl(0, 68%, 50%);
}
html:not(.moz-appearance-bug) #installed.disabled + .actions #disableAll:checked:hover + .svg-icon.checked {
fill: #fff;
}
html:not(.moz-appearance-bug) #disableAll:hover {
border-color: hsl(0, 68%, 50%);
background-color: hsl(20, 70%, 75%);
}
html:not(.moz-appearance-bug) #disableAll:hover + .svg-icon + label {
color: hsl(0, 68%, 42%);
}
html:not(.moz-appearance-bug) #installed.disabled + .actions #disableAll:checked:hover {
border-color: hsl(0, 50%, 56%);
background-color: hsl(0, 50%, 56%);
}
html:not(.moz-appearance-bug) .style-name .checker,
html:not(.moz-appearance-bug) .style-name .svg-icon.checked {
position: absolute;
top: 7px;
left: 9px;
pointer-events: none;
}
html:not(.moz-appearance-bug) #disable-all-wrapper .main-controls .svg-icon.checked {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
pointer-events: none; pointer-events: none;
} }
label { #installed.disabled + .actions #disableAll:checked + .svg-icon.checked {
transition: color .25s; fill: hsl(0, 68%, 42%);
}
#installed.disabled + .actions #disableAll:checked {
border-color: hsl(0, 68%, 50%);
}
#installed.disabled + .actions #disableAll:checked:hover + .svg-icon.checked {
fill: #fff;
}
#disableAll:hover {
border-color: hsl(0, 68%, 50%);
background-color: hsl(20, 70%, 75%);
}
#disableAll-label:hover {
color: hsl(0, 68%, 42%);
}
#installed.disabled + .actions #disableAll:checked:hover {
border-color: hsl(0, 50%, 56%);
background-color: hsl(0, 50%, 56%);
}
#installed .style-name .checker,
#installed .style-name .svg-icon.checked {
position: absolute;
top: 7px;
left: 9px;
pointer-events: none;
} }
#disable-all-wrapper { #disable-all-wrapper {
@ -140,18 +85,14 @@ label {
} }
#disable-all-wrapper .main-controls label { #disable-all-wrapper .main-controls label {
padding-left: 16px;
position: relative;
transition: color .25s;
font-size: 12px; font-size: 12px;
padding-left: 4px;
} }
#no-styles { #no-styles {
margin: 0 .75em; font-style: italic;
font-weight: bold;
font-style: normal;
}
#no-styles:before {
content: none;
} }
.checker { .checker {
@ -222,6 +163,10 @@ html[style] .entry {
padding: 0 16px 0 0; padding: 0 16px 0 0;
} }
#no-styles.entry {
padding: 0 14px;
}
html[style] .entry:nth-child(-n+10):before, html[style] .entry:nth-child(-n+10):before,
html[style] .entry:nth-child(10):before { html[style] .entry:nth-child(10):before {
right: 7px; right: 7px;

View File

@ -929,7 +929,7 @@
((cm.display.renderedView || [])[0] || {}).text || cm.display.lineDiv; ((cm.display.renderedView || [])[0] || {}).text || cm.display.lineDiv;
for (let el = start; el; el = el.parentElement) { for (let el = start; el; el = el.parentElement) {
const bgColor = getComputedStyle(el).backgroundColor; const bgColor = getComputedStyle(el).backgroundColor;
const [r, g, b, a = 255] = bgColor.match(/\d+/g).map(Number); const [r, g, b, a = 255] = (bgColor.match(/\d+/g) || []).map(Number);
if (!a) { if (!a) {
continue; continue;
} }

View File

@ -10994,6 +10994,11 @@ self.onmessage = ({data: {action = 'run', code, config}}) => {
self.postMessage(CSSLint.getRules().map(rule => rule.id)); self.postMessage(CSSLint.getRules().map(rule => rule.id));
return; return;
case 'getAllRuleInfos':
// the functions are non-tranferable
self.postMessage(CSSLint.getRules().map(rule => JSON.parse(JSON.stringify(rule))));
return;
case 'run': case 'run':
Object.defineProperty(config, 'errors', {get: () => 0, set: () => 0}); Object.defineProperty(config, 'errors', {get: () => 0, set: () => 0});
config['uso-vars'] = 1; config['uso-vars'] = 1;