Merge branch 'master' into dev-chrome-49
This commit is contained in:
commit
37dca280f2
|
@ -1373,6 +1373,10 @@
|
|||
"message": "Stylus can access file:// URLs only if you enable the corresponding checkbox for Stylus extension on chrome://extensions page.",
|
||||
"description": "Note in the toolbar popup for file:// URLs"
|
||||
},
|
||||
"InaccessibleFileHint": {
|
||||
"message": "Stylus can not access some file types (e.g. pdf & json files).",
|
||||
"description": "Note in the toolbar popup for some file types that cannot be accessed"
|
||||
},
|
||||
"updateAllCheckSucceededNoUpdate": {
|
||||
"message": "No updates found.",
|
||||
"description": "Text that displays when an update all check completed and no updates are available"
|
||||
|
@ -1477,6 +1481,9 @@
|
|||
"connectingDropbox": {
|
||||
"message": "Connecting Dropbox..."
|
||||
},
|
||||
"connectingDropboxNotAllowed": {
|
||||
"message": "Connecting to Dropbox is only available in apps installed directly from the webstore"
|
||||
},
|
||||
"gettingStyles": {
|
||||
"message": "Getting all styles..."
|
||||
},
|
||||
|
|
|
@ -56,7 +56,7 @@ window.API_METHODS = Object.assign(window.API_METHODS || {}, {
|
|||
return browser.runtime.openOptionsPage()
|
||||
.then(() => new Promise(resolve => setTimeout(resolve, 100)))
|
||||
.then(() => msg.broadcastExtension({method: 'optionsCustomizeHotkeys'}));
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-var
|
||||
|
@ -150,6 +150,9 @@ chrome.runtime.onInstalled.addListener(({reason}) => {
|
|||
});
|
||||
// themes may change
|
||||
delete localStorage.codeMirrorThemes;
|
||||
// save install type: "admin", "development", "normal", "sideload" or "other"
|
||||
// "normal" = addon installed from webstore
|
||||
chrome.management.getSelf(info => localStorage.installType = info.installType);
|
||||
});
|
||||
|
||||
// *************************************************************************
|
||||
|
|
|
@ -75,6 +75,7 @@
|
|||
* @returns {Promise<{style, dup:Boolean?}>}
|
||||
*/
|
||||
function build({
|
||||
styleId,
|
||||
sourceCode,
|
||||
checkDup,
|
||||
metaOnly,
|
||||
|
@ -83,7 +84,8 @@
|
|||
}) {
|
||||
return usercss.buildMeta(sourceCode)
|
||||
.then(style => {
|
||||
const findDup = checkDup || assignVars ? find(style) : null;
|
||||
const findDup = checkDup || assignVars ?
|
||||
find(styleId ? {id: styleId} : style) : Promise.resolve();
|
||||
return Promise.all([
|
||||
metaOnly ? style : doBuild(style, findDup),
|
||||
findDup
|
||||
|
|
|
@ -65,10 +65,9 @@ const APPLY = (() => {
|
|||
// Since it's easy to spoof the browser version in pre-Quantum FF we're checking
|
||||
// for getPreventDefault which got removed in FF59 https://bugzil.la/691151
|
||||
const EVENT_NAME = chrome.runtime.id;
|
||||
const usePageScript = CHROME || isOwnPage || Event.prototype.getPreventDefault ?
|
||||
Promise.resolve(false) : injectPageScript();
|
||||
let ready;
|
||||
return (el, content) =>
|
||||
usePageScript.then(ok => {
|
||||
checkPageScript().then(ok => {
|
||||
if (!ok) {
|
||||
const disabled = el.disabled;
|
||||
el.textContent = content;
|
||||
|
@ -83,9 +82,19 @@ const APPLY = (() => {
|
|||
}
|
||||
});
|
||||
|
||||
function checkPageScript() {
|
||||
if (!ready) {
|
||||
ready = CHROME || isOwnPage || Event.prototype.getPreventDefault ?
|
||||
Promise.resolve(false) : injectPageScript();
|
||||
}
|
||||
return ready;
|
||||
}
|
||||
|
||||
function injectPageScript() {
|
||||
const scriptContent = EVENT_NAME => {
|
||||
document.currentScript.remove();
|
||||
const available = checkStyleApplied();
|
||||
if (available) {
|
||||
window.addEventListener(EVENT_NAME, function handler(e) {
|
||||
const {method, id, content} = e.detail;
|
||||
if (method === 'setStyleContent') {
|
||||
|
@ -100,16 +109,41 @@ const APPLY = (() => {
|
|||
window.removeEventListener(EVENT_NAME, handler);
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
window.dispatchEvent(new CustomEvent(EVENT_NAME, {detail: {
|
||||
method: 'init',
|
||||
available
|
||||
}}));
|
||||
|
||||
function checkStyleApplied() {
|
||||
const style = document.createElement('style');
|
||||
style.textContent = ':root{--stylus-applied:1}';
|
||||
document.documentElement.appendChild(style);
|
||||
const applied = getComputedStyle(document.documentElement)
|
||||
.getPropertyValue('--stylus-applied');
|
||||
style.remove();
|
||||
return Boolean(applied);
|
||||
}
|
||||
};
|
||||
const code = `(${scriptContent})(${JSON.stringify(EVENT_NAME)})`;
|
||||
const src = `data:application/javascript;base64,${btoa(code)}`;
|
||||
const script = document.createElement('script');
|
||||
const {resolve, promise} = deferred();
|
||||
script.src = src;
|
||||
script.onload = () => resolve(true);
|
||||
script.onerror = () => resolve(false);
|
||||
document.documentElement.appendChild(script);
|
||||
return promise;
|
||||
window.addEventListener(EVENT_NAME, handleInit);
|
||||
(document.head || document.documentElement).appendChild(script);
|
||||
return promise.then(result => {
|
||||
script.remove();
|
||||
window.removeEventListener(EVENT_NAME, handleInit);
|
||||
return result;
|
||||
});
|
||||
|
||||
function handleInit(e) {
|
||||
if (e.detail.method === 'init') {
|
||||
resolve(e.detail.available);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,6 @@
|
|||
<link href="vendor/codemirror/addon/search/matchesonscrollbar.css" rel="stylesheet">
|
||||
<script src="vendor/codemirror/addon/search/matchesonscrollbar.js"></script>
|
||||
<script src="vendor/codemirror/addon/scroll/annotatescrollbar.js"></script>
|
||||
<script src="vendor/codemirror/addon/search/match-highlighter.js"></script>
|
||||
<script src="vendor/codemirror/addon/search/searchcursor.js"></script>
|
||||
|
||||
<script src="vendor/codemirror/addon/comment/comment.js"></script>
|
||||
|
@ -62,6 +61,8 @@
|
|||
<script src="vendor-overwrites/colorpicker/colorpicker.js"></script>
|
||||
<script src="vendor-overwrites/colorpicker/colorview.js"></script>
|
||||
|
||||
<script src="vendor-overwrites/codemirror-addon/match-highlighter.js"></script>
|
||||
|
||||
<script src="js/polyfill.js"></script>
|
||||
<script src="js/promisify.js"></script>
|
||||
<script src="js/dom.js"></script>
|
||||
|
@ -78,8 +79,6 @@
|
|||
<link href="edit/global-search.css" rel="stylesheet">
|
||||
<script src="edit/global-search.js"></script>
|
||||
|
||||
<script src="edit/match-highlighter-helper.js"></script>
|
||||
|
||||
<link href="edit/codemirror-default.css" rel="stylesheet">
|
||||
<script src="edit/codemirror-default.js"></script>
|
||||
|
||||
|
|
|
@ -37,14 +37,6 @@
|
|||
.cm-uso-variable {
|
||||
font-weight: bold;
|
||||
}
|
||||
.cm-searching.cm-matchhighlight {
|
||||
/* tokens found by manual search should not animate by cm-matchhighlight */
|
||||
animation-name: search-and-match-highlighter !important;
|
||||
}
|
||||
@keyframes search-and-match-highlighter {
|
||||
from { background-color: rgba(255, 255, 0, .4); } /* search color */
|
||||
to { background-color: rgba(100, 255, 100, .4); } /* sarch + highlight */
|
||||
}
|
||||
|
||||
.CodeMirror-activeline .applies-to:before {
|
||||
background-color: hsla(214, 100%, 90%, 0.15);
|
||||
|
|
|
@ -230,7 +230,7 @@
|
|||
|
||||
// editor commands
|
||||
for (const name of ['save', 'toggleStyle', 'nextEditor', 'prevEditor']) {
|
||||
CodeMirror.commands[name] = () => editor[name]();
|
||||
CodeMirror.commands[name] = (...args) => editor[name](...args);
|
||||
}
|
||||
|
||||
// CodeMirror convenience commands
|
||||
|
|
|
@ -32,12 +32,14 @@ const cmFactory = (() => {
|
|||
if (value === 'token') {
|
||||
cm.setOption('highlightSelectionMatches', {
|
||||
showToken: /[#.\-\w]/,
|
||||
annotateScrollbar: true
|
||||
annotateScrollbar: true,
|
||||
onUpdate: updateMatchHighlightCount
|
||||
});
|
||||
} else if (value === 'selection') {
|
||||
cm.setOption('highlightSelectionMatches', {
|
||||
showToken: false,
|
||||
annotateScrollbar: true
|
||||
annotateScrollbar: true,
|
||||
onUpdate: updateMatchHighlightCount
|
||||
});
|
||||
} else {
|
||||
cm.setOption('highlightSelectionMatches', null);
|
||||
|
@ -80,6 +82,10 @@ const cmFactory = (() => {
|
|||
});
|
||||
return {create, destroy, setOption};
|
||||
|
||||
function updateMatchHighlightCount(cm, state) {
|
||||
cm.display.wrapper.dataset.matchHighlightCount = state.matchesonscroll.matches.length;
|
||||
}
|
||||
|
||||
function configureMouseFn(cm, repeat) {
|
||||
return repeat === 'double' ?
|
||||
{unit: selectTokenOnDoubleclick} :
|
||||
|
|
|
@ -364,14 +364,14 @@ input:invalid {
|
|||
.resize-grip-enabled .CodeMirror-scrollbar-filler {
|
||||
bottom: 7px; /* make space for resize-grip */
|
||||
}
|
||||
body[data-match-highlight="token"] .cm-matchhighlight-approved .cm-matchhighlight,
|
||||
body[data-match-highlight="token"] .CodeMirror-selection-highlight-scrollbar {
|
||||
body:not(.find-open) .cm-matchhighlight,
|
||||
body:not(.find-open) .CodeMirror-selection-highlight-scrollbar {
|
||||
animation: fadein-match-highlighter 1s cubic-bezier(.97,.01,.42,.98);
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
body[data-match-highlight="selection"] .cm-matchhighlight-approved .cm-matchhighlight,
|
||||
body[data-match-highlight="selection"] .CodeMirror-selection-highlight-scrollbar {
|
||||
background-color: rgba(1, 151, 193, 0.1);
|
||||
body:not(.find-open) [data-match-highlight-count="1"] .cm-matchhighlight,
|
||||
body:not(.find-open) [data-match-highlight-count="1"] .CodeMirror-selection-highlight-scrollbar {
|
||||
animation: none;
|
||||
}
|
||||
@-webkit-keyframes highlight {
|
||||
from {
|
||||
|
|
28
edit/edit.js
28
edit/edit.js
|
@ -174,11 +174,29 @@ preinit();
|
|||
$('#beautify').onclick = () => beautify(editor.getEditors());
|
||||
$('#lint').addEventListener('scroll', hideLintHeaderOnScroll, {passive: true});
|
||||
window.addEventListener('resize', () => debounce(rememberWindowSize, 100));
|
||||
editor = usercss ? createSourceEditor(style) : createSectionsEditor(style);
|
||||
if (editor.ready) {
|
||||
return editor.ready();
|
||||
}
|
||||
editor = (usercss ? createSourceEditor : createSectionsEditor)({
|
||||
style,
|
||||
onTitleChanged: updateTitle
|
||||
});
|
||||
editor.dirty.onChange(updateDirty);
|
||||
return Promise.resolve(editor.ready && editor.ready())
|
||||
.then(updateDirty);
|
||||
});
|
||||
}
|
||||
|
||||
function updateTitle() {
|
||||
if (editor) {
|
||||
const styleName = editor.getStyle().name;
|
||||
const isDirty = editor.dirty.isDirty();
|
||||
document.title = (isDirty ? '* ' : '') + styleName;
|
||||
}
|
||||
}
|
||||
|
||||
function updateDirty() {
|
||||
const isDirty = editor.dirty.isDirty();
|
||||
document.body.classList.toggle('dirty', isDirty);
|
||||
$('#save-button').disabled = !isDirty;
|
||||
updateTitle();
|
||||
}
|
||||
})();
|
||||
|
||||
|
@ -306,7 +324,7 @@ function beforeUnload(e) {
|
|||
// refocus if unloading was canceled
|
||||
setTimeout(() => activeElement.focus());
|
||||
}
|
||||
if (editor && editor.isDirty()) {
|
||||
if (editor && editor.dirty.isDirty()) {
|
||||
// neither confirm() nor custom messages work in modern browsers but just in case
|
||||
e.returnValue = t('styleChangesNotSaved');
|
||||
}
|
||||
|
|
|
@ -181,17 +181,33 @@
|
|||
opacity: 1;
|
||||
}
|
||||
|
||||
/*********** CodeMirror ****************/
|
||||
|
||||
.search-target-editor {
|
||||
outline: 1px solid darkorange;
|
||||
/*********** CM search highlight restyling, which shouldn't need color variables ****************/
|
||||
body.find-open .search-target-editor {
|
||||
outline-color: darkorange !important;
|
||||
}
|
||||
|
||||
#stylus .search-target-match {
|
||||
body.find-open .cm-searching {
|
||||
background-color: rgba(255, 255, 0, .4);
|
||||
}
|
||||
|
||||
body.find-open .cm-searching.search-target-match {
|
||||
background-color: darkorange;
|
||||
color: black;
|
||||
}
|
||||
|
||||
body.find-open .CodeMirror-search-match {
|
||||
background: gold;
|
||||
border-top: 1px solid orange;
|
||||
border-bottom: 1px solid orange;
|
||||
}
|
||||
|
||||
/* hide default CM search highlight styling */
|
||||
body .cm-searching,
|
||||
body .CodeMirror-search-match {
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
#search-replace-dialog {
|
||||
left: 0;
|
||||
|
|
|
@ -752,8 +752,14 @@ onDOMready().then(() => {
|
|||
function makeTargetVisible(element) {
|
||||
const old = $('.' + TARGET_CLASS);
|
||||
if (old !== element) {
|
||||
if (old) old.classList.remove(TARGET_CLASS);
|
||||
if (element) element.classList.add(TARGET_CLASS);
|
||||
if (old) {
|
||||
old.classList.remove(TARGET_CLASS);
|
||||
document.body.classList.remove('find-open');
|
||||
}
|
||||
if (element) {
|
||||
element.classList.add(TARGET_CLASS);
|
||||
document.body.classList.add('find-open');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,18 +33,18 @@ function createLinterHelpDialog(getIssues) {
|
|||
};
|
||||
} else {
|
||||
headerLink = $createLink(baseUrl, 'stylelint');
|
||||
template = ({rule}) =>
|
||||
template = rule =>
|
||||
$create('li',
|
||||
rule === 'CssSyntaxError' ? rule : $createLink(baseUrl + rule, rule));
|
||||
}
|
||||
const header = t('linterIssuesHelp', '\x01').split('\x01');
|
||||
const activeRules = getIssues();
|
||||
const activeRules = new Set([...getIssues()].map(issue => issue.rule));
|
||||
Promise.resolve(linter === 'csslint' && prepareCsslintRules())
|
||||
.then(() =>
|
||||
showHelp(t('linterIssues'),
|
||||
$create([
|
||||
header[0], headerLink, header[1],
|
||||
$create('ul.rules', [...activeRules.values()].map(template)),
|
||||
$create('ul.rules', [...activeRules].map(template)),
|
||||
])
|
||||
)
|
||||
);
|
||||
|
|
|
@ -12,7 +12,7 @@ function createMetaCompiler(cm) {
|
|||
if (_cm !== cm) {
|
||||
return;
|
||||
}
|
||||
const match = text.match(/\/\*\s*==userstyle==[\s\S]*?==\/userstyle==\s*\*\//i);
|
||||
const match = text.match(/\/\*\!?\s*==userstyle==[\s\S]*?==\/userstyle==\s*\*\//i);
|
||||
if (!match) {
|
||||
return [];
|
||||
}
|
||||
|
|
|
@ -1,223 +0,0 @@
|
|||
/* global CodeMirror prefs */
|
||||
'use strict';
|
||||
|
||||
(() => {
|
||||
/*
|
||||
The original match-highlighter addon always recreates the highlight overlay
|
||||
even if the token under cursor hasn't changed, which is terribly ineffective
|
||||
(the entire view is re-rendered) and makes our animated token highlight effect
|
||||
restart on every cursor movement.
|
||||
|
||||
Invocation sequence of our hooks:
|
||||
|
||||
1. removeOverlayForHighlighter()
|
||||
The original addon removes the overlay unconditionally
|
||||
so this hook saves the state if the token hasn't changed.
|
||||
|
||||
2. addOverlayForHighlighter()
|
||||
Restores the saved state instead of creating a new overlay,
|
||||
installs a hook to count occurrences.
|
||||
|
||||
3. matchesOnScrollbar()
|
||||
Saves the query regexp passed from the original addon in our helper object,
|
||||
and in case removeOverlayForHighlighter() decided to keep the overlay
|
||||
only rewrites the regexp without invoking the original constructor.
|
||||
*/
|
||||
|
||||
const HL_APPROVED = 'cm-matchhighlight-approved';
|
||||
const SEARCH_MATCH_TOKEN_NAME = 'searching';
|
||||
|
||||
const originalAddOverlay = CodeMirror.prototype.addOverlay;
|
||||
const originalRemoveOverlay = CodeMirror.prototype.removeOverlay;
|
||||
const originalMatchesOnScrollbar = CodeMirror.prototype.showMatchesOnScrollbar;
|
||||
const originalSetOption = CodeMirror.prototype.setOption;
|
||||
let originalGetOption;
|
||||
|
||||
CodeMirror.prototype.addOverlay = addOverlay;
|
||||
CodeMirror.prototype.removeOverlay = removeOverlay;
|
||||
CodeMirror.prototype.showMatchesOnScrollbar = matchesOnScrollbar;
|
||||
CodeMirror.prototype.setOption = setOption;
|
||||
|
||||
let enabled = Boolean(prefs.get('editor.matchHighlight'));
|
||||
|
||||
return;
|
||||
|
||||
function setOption(option, value) {
|
||||
enabled = option === 'highlightSelectionMatches' ? value : enabled;
|
||||
return originalSetOption.apply(this, arguments);
|
||||
}
|
||||
|
||||
function shouldIntercept(overlay) {
|
||||
const hlState = this.state.matchHighlighter || {};
|
||||
return overlay === hlState.overlay && (hlState.options || {}).showToken;
|
||||
}
|
||||
|
||||
function addOverlay() {
|
||||
return enabled && shouldIntercept.apply(this, arguments) &&
|
||||
addOverlayForHighlighter.apply(this, arguments) ||
|
||||
originalAddOverlay.apply(this, arguments);
|
||||
}
|
||||
|
||||
function removeOverlay() {
|
||||
return enabled && shouldIntercept.apply(this, arguments) &&
|
||||
removeOverlayForHighlighter.apply(this, arguments) ||
|
||||
originalRemoveOverlay.apply(this, arguments);
|
||||
}
|
||||
|
||||
function addOverlayForHighlighter(overlay) {
|
||||
const state = this.state.matchHighlighter || {};
|
||||
const helper = state.highlightHelper = state.highlightHelper || {};
|
||||
|
||||
helper.rewriteScrollbarQuery = true;
|
||||
|
||||
// since we're here the original addon decided there's something to highlight,
|
||||
// so we cancel removeOverlayIfExpired() scheduled in our removeOverlay hook
|
||||
clearTimeout(helper.hookTimer);
|
||||
|
||||
// the original addon just removed its overlays, which was intercepted by removeOverlayForHighlighter,
|
||||
// which decided to restore it and saved the previous overlays in our helper object,
|
||||
// so here we are now, restoring them
|
||||
if (helper.skipMatchesOnScrollbar) {
|
||||
state.matchesonscroll = helper.matchesonscroll;
|
||||
state.overlay = helper.overlay;
|
||||
return true;
|
||||
}
|
||||
|
||||
// hook the newly created overlay's token() to count the occurrences
|
||||
if (overlay.token !== tokenHook) {
|
||||
overlay.highlightHelper = {
|
||||
token: overlay.token,
|
||||
occurrences: 0,
|
||||
};
|
||||
overlay.token = tokenHook;
|
||||
}
|
||||
|
||||
// speed up rendering of scrollbar marks 4 times: we don't need ultimate precision there
|
||||
// so for the duration of this event loop cycle we spoof the "lineWrapping" option
|
||||
// and restore it in the next event loop cycle
|
||||
if (this.options.lineWrapping && CodeMirror.prototype.getOption !== spoofLineWrappingOption) {
|
||||
originalGetOption = CodeMirror.prototype.getOption;
|
||||
CodeMirror.prototype.getOption = spoofLineWrappingOption;
|
||||
setTimeout(() => (CodeMirror.prototype.getOption = originalGetOption));
|
||||
}
|
||||
}
|
||||
|
||||
function spoofLineWrappingOption(option) {
|
||||
return option !== 'lineWrapping' && originalGetOption.apply(this, arguments);
|
||||
}
|
||||
|
||||
function tokenHook(stream) {
|
||||
// we don't highlight a single match in case 'editor.matchHighlight' option is 'token'
|
||||
// so this hook counts the occurrences and toggles HL_APPROVED class on CM's wrapper element
|
||||
const style = this.highlightHelper.token.call(this, stream);
|
||||
if (style !== 'matchhighlight') {
|
||||
return style;
|
||||
}
|
||||
|
||||
const tokens = stream.lineOracle.baseTokens;
|
||||
const tokenIndex = tokens.indexOf(stream.pos, 1);
|
||||
if (tokenIndex > 0) {
|
||||
const tokenStart = tokenIndex > 2 ? tokens[tokenIndex - 2] : 0;
|
||||
const token = tokenStart === stream.start && tokens[tokenIndex + 1];
|
||||
const index = token && token.indexOf(SEARCH_MATCH_TOKEN_NAME);
|
||||
if (token && index >= 0 &&
|
||||
(token[index - 1] || ' ') === ' ' &&
|
||||
(token[index + SEARCH_MATCH_TOKEN_NAME.length] || ' ') === ' ') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const num = ++this.highlightHelper.occurrences;
|
||||
if (num === 1) {
|
||||
stream.lineOracle.doc.cm.display.wrapper.classList.remove(HL_APPROVED);
|
||||
} else if (num === 2) {
|
||||
stream.lineOracle.doc.cm.display.wrapper.classList.add(HL_APPROVED);
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
function removeOverlayForHighlighter() {
|
||||
const state = this.state.matchHighlighter || {};
|
||||
const helper = state.highlightHelper;
|
||||
const {query, originalToken} = helper || state.matchesonscroll || {};
|
||||
// no current query means nothing to preserve => remove the overlay
|
||||
if (!query || !originalToken) {
|
||||
return;
|
||||
}
|
||||
const sel = this.getSelection();
|
||||
// current query differs from the selected text => remove the overlay
|
||||
if (sel && sel.toLowerCase() !== originalToken.toLowerCase()) {
|
||||
helper.query = helper.originalToken = sel;
|
||||
return;
|
||||
}
|
||||
// if token under cursor has changed => remove the overlay
|
||||
if (!sel) {
|
||||
const {line, ch} = this.getCursor();
|
||||
const queryLen = originalToken.length;
|
||||
const start = Math.max(0, ch - queryLen + 1);
|
||||
const end = ch + queryLen;
|
||||
const string = this.getLine(line);
|
||||
const area = string.slice(start, end);
|
||||
const i = area.indexOf(query);
|
||||
const startInArea = i < 0 ? NaN : i;
|
||||
if (isNaN(startInArea) || start + startInArea > ch ||
|
||||
state.options.showToken.test(string[start + startInArea - 1] || '') ||
|
||||
state.options.showToken.test(string[start + startInArea + queryLen] || '')) {
|
||||
// pass the displayed instance back to the original code to remove it
|
||||
state.matchesonscroll = state.matchesonscroll || helper && helper.matchesonscroll;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// since the same token is under cursor we prevent the highlighter from rerunning
|
||||
// by saving current overlays in a helper object so that it's restored in addOverlayForHighlighter()
|
||||
state.highlightHelper = {
|
||||
overlay: state.overlay,
|
||||
matchesonscroll: state.matchesonscroll || (helper || {}).matchesonscroll,
|
||||
// instruct our matchesOnScrollbar hook to preserve current scrollbar annotations
|
||||
skipMatchesOnScrollbar: true,
|
||||
// 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
|
||||
hookTimer: setTimeout(removeOverlayIfExpired, 0, this, state),
|
||||
originalToken,
|
||||
query,
|
||||
};
|
||||
// fool the original addon so it won't invoke state.matchesonscroll.clear()
|
||||
state.matchesonscroll = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
function removeOverlayIfExpired(self, state) {
|
||||
const {overlay, matchesonscroll} = state.highlightHelper || {};
|
||||
if (overlay) {
|
||||
originalRemoveOverlay.call(self, overlay);
|
||||
}
|
||||
if (matchesonscroll) {
|
||||
matchesonscroll.clear();
|
||||
}
|
||||
state.highlightHelper = null;
|
||||
}
|
||||
|
||||
function matchesOnScrollbar(query, ...args) {
|
||||
if (!enabled) {
|
||||
return originalMatchesOnScrollbar.call(this, query, ...args);
|
||||
}
|
||||
const state = this.state.matchHighlighter;
|
||||
const helper = state.highlightHelper = state.highlightHelper || {};
|
||||
// rewrite the \btoken\b regexp so it matches .token and #token and --token
|
||||
if (helper.rewriteScrollbarQuery && /^\\b.*?\\b$/.test(query.source)) {
|
||||
helper.rewriteScrollbarQuery = undefined;
|
||||
helper.originalToken = query.source.slice(2, -2);
|
||||
const notToken = '(?!' + state.options.showToken.source + ').';
|
||||
query = new RegExp(`(^|${notToken})` + helper.originalToken + `(${notToken}|$)`);
|
||||
}
|
||||
// save the query for future use in removeOverlayForHighlighter
|
||||
helper.query = query;
|
||||
// if removeOverlayForHighlighter() decided to keep the overlay
|
||||
if (helper.skipMatchesOnScrollbar) {
|
||||
helper.skipMatchesOnScrollbar = undefined;
|
||||
return;
|
||||
} else {
|
||||
return originalMatchesOnScrollbar.call(this, query, ...args);
|
||||
}
|
||||
}
|
||||
})();
|
|
@ -6,10 +6,9 @@
|
|||
/* exported createSectionsEditor */
|
||||
'use strict';
|
||||
|
||||
function createSectionsEditor(style) {
|
||||
function createSectionsEditor({style, onTitleChanged}) {
|
||||
let INC_ID = 0; // an increment id that is used by various object to track the order
|
||||
const dirty = dirtyReporter();
|
||||
dirty.onChange(updateTitle);
|
||||
|
||||
const container = $('#sections');
|
||||
const sections = [];
|
||||
|
@ -18,7 +17,7 @@ function createSectionsEditor(style) {
|
|||
nameEl.addEventListener('input', () => {
|
||||
dirty.modify('name', style.name, nameEl.value);
|
||||
style.name = nameEl.value;
|
||||
updateTitle();
|
||||
onTitleChanged();
|
||||
});
|
||||
|
||||
const enabledEl = $('#enabled');
|
||||
|
@ -64,7 +63,7 @@ function createSectionsEditor(style) {
|
|||
return {
|
||||
ready: () => initializing,
|
||||
replaceStyle,
|
||||
isDirty: dirty.isDirty,
|
||||
dirty,
|
||||
getStyle: () => style,
|
||||
getEditors,
|
||||
scrollToEditor,
|
||||
|
@ -201,35 +200,27 @@ function createSectionsEditor(style) {
|
|||
}
|
||||
|
||||
function nextEditor(cm, cycle = true) {
|
||||
if (!cycle) {
|
||||
for (const section of sections) {
|
||||
if (section.isRemoved()) {
|
||||
continue;
|
||||
}
|
||||
if (cm === section.cm) {
|
||||
if (!cycle && findLast(sections, s => !s.isRemoved()).cm === cm) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return nextPrevEditor(cm, 1);
|
||||
}
|
||||
|
||||
function prevEditor(cm, cycle = true) {
|
||||
if (!cycle) {
|
||||
for (let i = sections.length - 1; i >= 0; i--) {
|
||||
if (sections[i].isRemoved()) {
|
||||
continue;
|
||||
}
|
||||
if (cm === sections[i].cm) {
|
||||
if (!cycle && sections.find(s => !s.isRemoved()).cm === cm) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return nextPrevEditor(cm, -1);
|
||||
}
|
||||
|
||||
function findLast(arr, match) {
|
||||
for (let i = arr.length - 1; i >= 0; i--) {
|
||||
if (match(arr[i])) {
|
||||
return arr[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function nextPrevEditor(cm, direction) {
|
||||
const editors = getEditors();
|
||||
cm = editors[(editors.indexOf(cm) + direction + editors.length) % editors.length];
|
||||
|
@ -421,7 +412,7 @@ function createSectionsEditor(style) {
|
|||
nameEl.value = style.name || '';
|
||||
enabledEl.checked = style.enabled !== false;
|
||||
$('#url').href = style.url || '';
|
||||
updateTitle();
|
||||
onTitleChanged();
|
||||
}
|
||||
|
||||
function updateLivePreview() {
|
||||
|
@ -432,14 +423,6 @@ function createSectionsEditor(style) {
|
|||
livePreview.update(getModel());
|
||||
}
|
||||
|
||||
function updateTitle() {
|
||||
const name = style.name;
|
||||
const clean = !dirty.isDirty();
|
||||
const title = !style.id ? t('addStyleTitle') : name;
|
||||
document.title = (clean ? '' : '* ') + title;
|
||||
$('#save-button').disabled = clean;
|
||||
}
|
||||
|
||||
function initSection({
|
||||
sections: originalSections,
|
||||
total = originalSections.length,
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
/* exported createSourceEditor */
|
||||
'use strict';
|
||||
|
||||
function createSourceEditor(style) {
|
||||
function createSourceEditor({style, onTitleChanged}) {
|
||||
$('#name').disabled = true;
|
||||
$('#save-button').disabled = true;
|
||||
$('#mozilla-format-container').remove();
|
||||
|
@ -16,12 +16,6 @@ function createSourceEditor(style) {
|
|||
$('#sections').appendChild($create('.single-editor'));
|
||||
|
||||
const dirty = dirtyReporter();
|
||||
dirty.onChange(() => {
|
||||
const isDirty = dirty.isDirty();
|
||||
document.body.classList.toggle('dirty', isDirty);
|
||||
$('#save-button').disabled = !isDirty;
|
||||
updateTitle();
|
||||
});
|
||||
|
||||
// normalize style
|
||||
if (!style.id) setupNewStyle(style);
|
||||
|
@ -82,6 +76,7 @@ function createSourceEditor(style) {
|
|||
|
||||
function preprocess(style) {
|
||||
return API.buildUsercss({
|
||||
styleId: style.id,
|
||||
sourceCode: style.sourceCode,
|
||||
assignVars: true
|
||||
})
|
||||
|
@ -170,18 +165,10 @@ function createSourceEditor(style) {
|
|||
$('#name').value = style.name;
|
||||
$('#enabled').checked = style.enabled;
|
||||
$('#url').href = style.url;
|
||||
updateTitle();
|
||||
onTitleChanged();
|
||||
return cm.setPreprocessor((style.usercssData || {}).preprocessor);
|
||||
}
|
||||
|
||||
function updateTitle() {
|
||||
const newTitle = (dirty.isDirty() ? '* ' : '') +
|
||||
(style.id ? style.name : t('addStyleTitle'));
|
||||
if (document.title !== newTitle) {
|
||||
document.title = newTitle;
|
||||
}
|
||||
}
|
||||
|
||||
function replaceStyle(newStyle, codeIsUpdated) {
|
||||
const sameCode = newStyle.sourceCode === cm.getValue();
|
||||
if (sameCode) {
|
||||
|
@ -384,7 +371,7 @@ function createSourceEditor(style) {
|
|||
|
||||
return {
|
||||
replaceStyle,
|
||||
isDirty: dirty.isDirty,
|
||||
dirty,
|
||||
getStyle: () => style,
|
||||
getEditors: () => [cm],
|
||||
scrollToEditor: () => {},
|
||||
|
|
|
@ -10,7 +10,7 @@ const usercss = (() => {
|
|||
// updateURL: 'updateUrl',
|
||||
name: undefined,
|
||||
};
|
||||
const RX_META = /\/\*\s*==userstyle==[\s\S]*?==\/userstyle==\s*\*\//i;
|
||||
const RX_META = /\/\*\!?\s*==userstyle==[\s\S]*?==\/userstyle==\s*\*\//i;
|
||||
const ERR_ARGS_IS_LIST = new Set(['missingMandatory', 'missingChar']);
|
||||
return {buildMeta, buildCode, assignVars};
|
||||
|
||||
|
|
|
@ -148,6 +148,9 @@ function reportUpdateState({updated, style, error, STATES}) {
|
|||
error = t('updateCheckSkippedLocallyEdited') + '\n' + t('updateCheckManualUpdateHint');
|
||||
} else if (error === STATES.MAYBE_EDITED) {
|
||||
error = t('updateCheckSkippedMaybeLocallyEdited') + '\n' + t('updateCheckManualUpdateHint');
|
||||
} else if (typeof error === 'object' && error.message) {
|
||||
// UserCSS meta errors provide an object
|
||||
error = error.message;
|
||||
}
|
||||
const message = same ? t('updateCheckSucceededNoUpdate') : error;
|
||||
newClasses.set('no-update', true);
|
||||
|
|
|
@ -80,7 +80,6 @@
|
|||
<template data-id="unreachableInfo">
|
||||
<div class="blocked-info">
|
||||
<label i18n-text="unreachableContentScript"></label>
|
||||
<p i18n-text="unreachableFileHint"></p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -554,7 +554,8 @@ body.blocked .actions > .main-controls {
|
|||
}
|
||||
|
||||
.blocked-info {
|
||||
hyphens: auto;
|
||||
hyphens: none;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.blocked-info label {
|
||||
|
|
|
@ -130,6 +130,10 @@ function initPopup() {
|
|||
return;
|
||||
}
|
||||
const info = template.unreachableInfo;
|
||||
if (!FIREFOX) {
|
||||
// Chrome "Allow access to file URLs" in chrome://extensions message
|
||||
info.appendChild($create('p', t('unreachableFileHint')));
|
||||
}
|
||||
if (FIREFOX && tabURL.startsWith(URLS.browserWebStore)) {
|
||||
$('label', info).textContent = t('unreachableAMO');
|
||||
const note = (FIREFOX < 59 ? t('unreachableAMOHintOldFF') : t('unreachableAMOHint')) +
|
||||
|
@ -137,9 +141,11 @@ function initPopup() {
|
|||
const renderToken = s => s[0] === '<' ? $create('b', tWordBreak(s.slice(1, -1))) : s;
|
||||
const renderLine = line => $create('p', line.split(/(<.*?>)/).map(renderToken));
|
||||
const noteNode = $create('fragment', note.split('\n').map(renderLine));
|
||||
const target = $('p', info);
|
||||
target.parentNode.insertBefore(noteNode, target);
|
||||
target.remove();
|
||||
info.appendChild(noteNode);
|
||||
}
|
||||
// Inaccessible locally hosted file type, e.g. JSON, PDF, etc.
|
||||
if (tabURL.length - tabURL.lastIndexOf(".") <= 5) {
|
||||
info.appendChild($create('p', t('InaccessibleFileHint')));
|
||||
}
|
||||
document.body.classList.add('unreachable');
|
||||
document.body.insertBefore(info, document.body.firstChild);
|
||||
|
|
|
@ -689,6 +689,7 @@ window.addEventListener('showStyles:done', function _() {
|
|||
'/api/v1/styles/subcategory' +
|
||||
'?search=' + encodeURIComponent(category) +
|
||||
'&page=' + searchCurrentPage +
|
||||
'&per_page=10' +
|
||||
'&country=NA';
|
||||
|
||||
const cacheKey = category + '/' + searchCurrentPage;
|
||||
|
|
|
@ -46,8 +46,11 @@ function uploadFileDropbox(client, stylesText) {
|
|||
}
|
||||
|
||||
$('#sync-dropbox-export').onclick = () => {
|
||||
const mode = localStorage.installType;
|
||||
const title = t('syncDropboxStyles');
|
||||
messageProgressBar({title: title, text: t('connectingDropbox')});
|
||||
const text = mode === 'normal' ? t('connectingDropbox') : t('connectingDropboxNotAllowed');
|
||||
messageProgressBar({title, text});
|
||||
if (mode !== 'normal') return;
|
||||
|
||||
hasDropboxAccessToken()
|
||||
.then(token => token || requestDropboxAccessToken())
|
||||
|
@ -116,8 +119,11 @@ $('#sync-dropbox-export').onclick = () => {
|
|||
};
|
||||
|
||||
$('#sync-dropbox-import').onclick = () => {
|
||||
const mode = localStorage.installType;
|
||||
const title = t('retrieveDropboxSync');
|
||||
messageProgressBar({title: title, text: t('connectingDropbox')});
|
||||
const text = mode === 'normal' ? t('connectingDropbox') : t('connectingDropboxNotAllowed');
|
||||
messageProgressBar({title, text});
|
||||
if (mode !== 'normal') return;
|
||||
|
||||
hasDropboxAccessToken()
|
||||
.then(token => token || requestDropboxAccessToken())
|
||||
|
|
|
@ -402,10 +402,6 @@
|
|||
if (!ch) {
|
||||
break;
|
||||
} else if (ch === '/' && peek() === '*') { /* css comment */
|
||||
if (isAfterNewline) {
|
||||
print.newLine();
|
||||
}
|
||||
|
||||
print.text(eatComment());
|
||||
if (peek() !== ';') print.newLine();
|
||||
} else if (ch === '/' && peek() === '/') { // single line comment
|
||||
|
|
|
@ -36,7 +36,8 @@
|
|||
wordsOnly: false,
|
||||
annotateScrollbar: false,
|
||||
showToken: false,
|
||||
trim: true
|
||||
trim: true,
|
||||
onUpdate: () => {}
|
||||
}
|
||||
|
||||
function State(options) {
|
||||
|
@ -46,6 +47,7 @@
|
|||
this.overlay = this.timeout = null;
|
||||
this.matchesonscroll = null;
|
||||
this.active = false;
|
||||
this.query = null;
|
||||
}
|
||||
|
||||
CodeMirror.defineOption("highlightSelectionMatches", false, function(cm, val, old) {
|
||||
|
@ -88,12 +90,24 @@
|
|||
|
||||
function addOverlay(cm, query, hasBoundary, style) {
|
||||
var state = cm.state.matchHighlighter;
|
||||
if (state.query === query) {
|
||||
return;
|
||||
}
|
||||
removeOverlay(cm);
|
||||
state.query = query;
|
||||
cm.addOverlay(state.overlay = makeOverlay(query, hasBoundary, style));
|
||||
if (state.options.annotateScrollbar && cm.showMatchesOnScrollbar) {
|
||||
var searchFor = hasBoundary ? new RegExp("\\b" + query.replace(/[\\\[.+*?(){|^$]/g, "\\$&") + "\\b") : query;
|
||||
var searchFor = hasBoundary ?
|
||||
new RegExp(
|
||||
(/[a-z]/i.test(query[0]) ? "\\b" : "") +
|
||||
query.replace(/[\\\[.+*?(){|^$]/g, "\\$&") +
|
||||
(/[a-z]/i.test(query[query.length - 1]) ? "\\b" : ""),
|
||||
"m"
|
||||
) : query;
|
||||
state.matchesonscroll = cm.showMatchesOnScrollbar(searchFor, false,
|
||||
{className: "CodeMirror-selection-highlight-scrollbar"});
|
||||
}
|
||||
state.options.onUpdate(cm, state);
|
||||
}
|
||||
|
||||
function removeOverlay(cm) {
|
||||
|
@ -106,19 +120,22 @@
|
|||
state.matchesonscroll = null;
|
||||
}
|
||||
}
|
||||
state.query = null;
|
||||
}
|
||||
|
||||
function highlightMatches(cm) {
|
||||
cm.operation(function() {
|
||||
var state = cm.state.matchHighlighter;
|
||||
removeOverlay(cm);
|
||||
if (!cm.somethingSelected() && state.options.showToken) {
|
||||
var re = state.options.showToken === true ? /[\w$]/ : state.options.showToken;
|
||||
var cur = cm.getCursor(), line = cm.getLine(cur.line), start = cur.ch, end = start;
|
||||
while (start && re.test(line.charAt(start - 1))) --start;
|
||||
while (end < line.length && re.test(line.charAt(end))) ++end;
|
||||
if (start < end)
|
||||
if (start < end) {
|
||||
addOverlay(cm, line.slice(start, end), re, state.options.style);
|
||||
} else {
|
||||
removeOverlay(cm);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var from = cm.getCursor("from"), to = cm.getCursor("to");
|
||||
|
@ -126,8 +143,11 @@
|
|||
if (state.options.wordsOnly && !isWord(cm, from, to)) return;
|
||||
var selection = cm.getRange(from, to)
|
||||
if (state.options.trim) selection = selection.replace(/^\s+|\s+$/g, "")
|
||||
if (selection.length >= state.options.minChars)
|
||||
if (selection.length >= state.options.minChars) {
|
||||
addOverlay(cm, selection, false, state.options.style);
|
||||
} else {
|
||||
removeOverlay(cm);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -4777,7 +4777,7 @@ self.parserlib = (() => {
|
|||
return result;
|
||||
}
|
||||
|
||||
_expr(inFunction) {
|
||||
_expr(inFunction, endToken = Tokens.RPAREN) {
|
||||
const stream = this._tokenStream;
|
||||
const values = [];
|
||||
|
||||
|
@ -4786,7 +4786,7 @@ self.parserlib = (() => {
|
|||
if (!value && !values.length) return null;
|
||||
|
||||
// get everything inside the parens and let validateProperty handle that
|
||||
if (!value && inFunction && stream.peek() !== Tokens.RPAREN) {
|
||||
if (!value && inFunction && stream.peek() !== endToken) {
|
||||
stream.get();
|
||||
value = new PropertyValuePart(stream._token);
|
||||
} else if (!value) {
|
||||
|
@ -4914,8 +4914,9 @@ self.parserlib = (() => {
|
|||
inFunction && Tokens.LBRACE,
|
||||
])) {
|
||||
const token = stream._token;
|
||||
token.expr = this._expr(inFunction);
|
||||
stream.mustMatch(Tokens.type(token.endChar));
|
||||
const endToken = Tokens.type(token.endChar);
|
||||
token.expr = this._expr(inFunction, endToken);
|
||||
stream.mustMatch(endToken);
|
||||
return finalize(token, token.value + (token.expr || '') + token.endChar);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user