live preview in editor
* refreshAllTabs was extracted * ...and fixed to use each frame's url when getting the styles
This commit is contained in:
parent
0c2c86a8de
commit
989df35b05
|
@ -749,6 +749,14 @@
|
||||||
"message": "Number of styles active for the current site",
|
"message": "Number of styles active for the current site",
|
||||||
"description": "Label for the checkbox controlling toolbar badge text."
|
"description": "Label for the checkbox controlling toolbar badge text."
|
||||||
},
|
},
|
||||||
|
"previewLabel": {
|
||||||
|
"message": "Live preview",
|
||||||
|
"description": "Label for the checkbox in style editor to enable live preview while editing."
|
||||||
|
},
|
||||||
|
"previewTooltip": {
|
||||||
|
"message": "Temporarily applies the changes without saving.\nSave the style to make the changes permanent.",
|
||||||
|
"description": "Tooltip for the checkbox in style editor to enable live preview while editing."
|
||||||
|
},
|
||||||
"replace": {
|
"replace": {
|
||||||
"message": "Replace",
|
"message": "Replace",
|
||||||
"description": "Label before the replace input field in the editor shown on Ctrl-H"
|
"description": "Label before the replace input field in the editor shown on Ctrl-H"
|
||||||
|
@ -927,7 +935,7 @@
|
||||||
"description": "Label for the enabled state of styles"
|
"description": "Label for the enabled state of styles"
|
||||||
},
|
},
|
||||||
"styleEnabledToggleHint": {
|
"styleEnabledToggleHint": {
|
||||||
"message": "Press Alt-Enter to toggle enabled/disabled state and save the style",
|
"message": "Press Alt-Enter to toggle the enabled/disabled state",
|
||||||
"description": "Help text for the '[x] enable' checkbox in the editor"
|
"description": "Help text for the '[x] enable' checkbox in the editor"
|
||||||
},
|
},
|
||||||
"styleInstall": {
|
"styleInstall": {
|
||||||
|
|
|
@ -21,7 +21,6 @@ window.API_METHODS = Object.assign(window.API_METHODS || {}, {
|
||||||
detectSloppyRegexps,
|
detectSloppyRegexps,
|
||||||
openEditor,
|
openEditor,
|
||||||
updateIcon,
|
updateIcon,
|
||||||
refreshAllTabs,
|
|
||||||
|
|
||||||
closeTab: (msg, sender, respond) => {
|
closeTab: (msg, sender, respond) => {
|
||||||
chrome.tabs.remove(msg.tabId || sender.tab.id, () => {
|
chrome.tabs.remove(msg.tabId || sender.tab.id, () => {
|
||||||
|
@ -306,40 +305,6 @@ function webNavUsercssInstallerFF(data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function refreshAllTabs(msg, sender = {}) {
|
|
||||||
return Promise.all([
|
|
||||||
sender.tab || getActiveTab(),
|
|
||||||
queryTabs(),
|
|
||||||
]).then(([ownTab, tabs]) => new Promise(resolve => {
|
|
||||||
if (FIREFOX) tabs = tabs.filter(tab => tab.width);
|
|
||||||
const last = tabs.length - 1;
|
|
||||||
for (let i = 0; i < last; i++) {
|
|
||||||
refreshTab(tabs[i], ownTab);
|
|
||||||
}
|
|
||||||
if (tabs.length) {
|
|
||||||
refreshTab(tabs[last], ownTab, resolve);
|
|
||||||
} else {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
function refreshTab(tab, ownTab, resolve) {
|
|
||||||
const {id: tabId, url: matchUrl} = tab;
|
|
||||||
chrome.webNavigation.getAllFrames({tabId}, (frames = []) => {
|
|
||||||
ignoreChromeError();
|
|
||||||
for (const {frameId} of frames[0] ? frames : [{frameId: 0}]) {
|
|
||||||
getStyles({matchUrl, enabled: true, asHash: true}).then(styles => {
|
|
||||||
const message = {method: 'styleReplaceAll', tabId, frameId, styles};
|
|
||||||
invokeOrPostpone(tab.active, sendMessage, message, ignoreChromeError);
|
|
||||||
if (!frameId) setTimeout(updateIcon, 0, {tab, styles});
|
|
||||||
if (resolve) resolve();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function updateIcon({tab, styles}) {
|
function updateIcon({tab, styles}) {
|
||||||
if (tab.id < 0) {
|
if (tab.id < 0) {
|
||||||
return;
|
return;
|
||||||
|
|
228
background/refresh-all-tabs.js
Normal file
228
background/refresh-all-tabs.js
Normal file
|
@ -0,0 +1,228 @@
|
||||||
|
/*
|
||||||
|
global API_METHODS cachedStyles
|
||||||
|
global getStyles filterStyles invalidateCache normalizeStyleSections
|
||||||
|
global updateIcon
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
(() => {
|
||||||
|
const previewFromTabs = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When style id and state is provided, only that style is propagated.
|
||||||
|
* Otherwise all styles are replaced and the toolbar icon is updated.
|
||||||
|
* @param {Object} [msg]
|
||||||
|
* @param {{id:Number, enabled?:Boolean, sections?: (Array|String)}} [msg.style] -
|
||||||
|
* style to propagate
|
||||||
|
* @param {Boolean} [msg.codeIsUpdated]
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
API_METHODS.refreshAllTabs = (msg = {}) =>
|
||||||
|
Promise.all([
|
||||||
|
queryTabs(),
|
||||||
|
maybeParseUsercss(msg),
|
||||||
|
getStyles(),
|
||||||
|
]).then(([tabs, style]) =>
|
||||||
|
new Promise(resolve => {
|
||||||
|
if (style) msg.style.sections = normalizeStyleSections(style);
|
||||||
|
run(tabs, msg, resolve);
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
function run(tabs, msg, resolve) {
|
||||||
|
const {style, codeIsUpdated} = msg;
|
||||||
|
|
||||||
|
// the style was updated/saved so we need to remove the old copy of the original style
|
||||||
|
if (msg.method === 'styleUpdated' && msg.reason !== 'editPreview') {
|
||||||
|
for (const [tabId, original] of previewFromTabs.entries()) {
|
||||||
|
if (style.id === original.id) {
|
||||||
|
previewFromTabs.delete(tabId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!previewFromTabs.size) {
|
||||||
|
unregisterTabListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!style) {
|
||||||
|
msg = {method: 'styleReplaceAll'};
|
||||||
|
|
||||||
|
// simple style update:
|
||||||
|
// * if disabled, apply.js will remove the element
|
||||||
|
// * if toggled and code is unchanged, apply.js will toggle the element
|
||||||
|
} else if (!style.enabled || codeIsUpdated === false) {
|
||||||
|
msg = {
|
||||||
|
method: 'styleUpdated',
|
||||||
|
reason: msg.reason,
|
||||||
|
style: {
|
||||||
|
id: style.id,
|
||||||
|
enabled: style.enabled,
|
||||||
|
},
|
||||||
|
codeIsUpdated,
|
||||||
|
};
|
||||||
|
|
||||||
|
// live preview puts the code in cachedStyles, saves the original in previewFromTabs,
|
||||||
|
// and if preview is being disabled, but the style is already deleted, we bail out
|
||||||
|
} else if (msg.reason === 'editPreview' && !updateCache(msg)) {
|
||||||
|
return;
|
||||||
|
|
||||||
|
// live preview normal operation, the new code is already in cachedStyles
|
||||||
|
} else {
|
||||||
|
msg.method = 'styleApply';
|
||||||
|
msg.style = {id: msg.style.id};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tabs || !tabs.length) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const last = tabs[tabs.length - 1];
|
||||||
|
for (const tab of tabs) {
|
||||||
|
if (FIREFOX && !tab.width) continue;
|
||||||
|
chrome.webNavigation.getAllFrames({tabId: tab.id}, frames =>
|
||||||
|
refreshFrame(tab, frames, msg, tab === last && resolve));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshFrame(tab, frames, msg, resolve) {
|
||||||
|
ignoreChromeError();
|
||||||
|
if (!frames || !frames.length) {
|
||||||
|
frames = [{
|
||||||
|
frameId: 0,
|
||||||
|
url: tab.url,
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
msg.tabId = tab.id;
|
||||||
|
const styleId = msg.style && msg.style.id;
|
||||||
|
|
||||||
|
for (const frame of frames) {
|
||||||
|
|
||||||
|
const styles = filterStyles({
|
||||||
|
matchUrl: getFrameUrl(frame, frames),
|
||||||
|
asHash: true,
|
||||||
|
id: styleId,
|
||||||
|
});
|
||||||
|
|
||||||
|
msg = Object.assign({}, msg);
|
||||||
|
msg.frameId = frame.frameId;
|
||||||
|
|
||||||
|
if (msg.method !== 'styleUpdated') {
|
||||||
|
msg.styles = styles;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.method === 'styleApply' && !styles.length) {
|
||||||
|
// remove the style from a previously matching frame
|
||||||
|
invokeOrPostpone(tab.active, sendMessage, {
|
||||||
|
method: 'styleUpdated',
|
||||||
|
reason: 'editPreview',
|
||||||
|
style: {
|
||||||
|
id: styleId,
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
tabId: tab.id,
|
||||||
|
frameId: frame.frameId,
|
||||||
|
}, ignoreChromeError);
|
||||||
|
} else {
|
||||||
|
invokeOrPostpone(tab.active, sendMessage, msg, ignoreChromeError);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.method === 'styleReplaceAll' && !frame.frameId) {
|
||||||
|
setTimeout(updateIcon, 0, {
|
||||||
|
tab,
|
||||||
|
styles,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resolve) resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getFrameUrl(frame, frames) {
|
||||||
|
while (frame.url === 'about:blank' && frame.frameId > 0) {
|
||||||
|
for (const f of frames) {
|
||||||
|
if (f.frameId === frame.parentFrameId) {
|
||||||
|
frame.url = f.url;
|
||||||
|
frame = f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (frame || frames[0]).url;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function maybeParseUsercss({style}) {
|
||||||
|
if (style && typeof style.sections === 'string') {
|
||||||
|
return API_METHODS.parseUsercss({sourceCode: style.sections});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function updateCache(msg) {
|
||||||
|
const {style, tabId, restoring} = msg;
|
||||||
|
const spoofed = !restoring && previewFromTabs.get(tabId);
|
||||||
|
const original = cachedStyles.byId.get(style.id);
|
||||||
|
|
||||||
|
if (style.sections && !restoring) {
|
||||||
|
if (!previewFromTabs.size) {
|
||||||
|
registerTabListeners();
|
||||||
|
}
|
||||||
|
if (!spoofed) {
|
||||||
|
previewFromTabs.set(tabId, Object.assign({}, original));
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
previewFromTabs.delete(tabId);
|
||||||
|
if (!previewFromTabs.size) {
|
||||||
|
unregisterTabListeners();
|
||||||
|
}
|
||||||
|
if (!original) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!restoring) {
|
||||||
|
msg.style = spoofed || original;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
invalidateCache({updated: msg.style});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function registerTabListeners() {
|
||||||
|
chrome.tabs.onRemoved.addListener(onTabRemoved);
|
||||||
|
chrome.tabs.onReplaced.addListener(onTabReplaced);
|
||||||
|
chrome.webNavigation.onCommitted.addListener(onTabNavigated);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function unregisterTabListeners() {
|
||||||
|
chrome.tabs.onRemoved.removeListener(onTabRemoved);
|
||||||
|
chrome.tabs.onReplaced.removeListener(onTabReplaced);
|
||||||
|
chrome.webNavigation.onCommitted.removeListener(onTabNavigated);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function onTabRemoved(tabId) {
|
||||||
|
const style = previewFromTabs.get(tabId);
|
||||||
|
if (style) {
|
||||||
|
API_METHODS.refreshAllTabs({
|
||||||
|
style,
|
||||||
|
tabId,
|
||||||
|
reason: 'editPreview',
|
||||||
|
restoring: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function onTabReplaced(addedTabId, removedTabId) {
|
||||||
|
onTabRemoved(removedTabId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function onTabNavigated({tabId}) {
|
||||||
|
onTabRemoved(tabId);
|
||||||
|
}
|
||||||
|
})();
|
|
@ -7,7 +7,8 @@
|
||||||
API_METHODS.saveUsercssUnsafe = style => save(style, true);
|
API_METHODS.saveUsercssUnsafe = style => save(style, true);
|
||||||
API_METHODS.buildUsercss = build;
|
API_METHODS.buildUsercss = build;
|
||||||
API_METHODS.installUsercss = install;
|
API_METHODS.installUsercss = install;
|
||||||
API_METHODS.findUsercss = findUsercss;
|
API_METHODS.parseUsercss = parse;
|
||||||
|
API_METHODS.findUsercss = find;
|
||||||
|
|
||||||
const TEMP_CODE_PREFIX = 'tempUsercssCode';
|
const TEMP_CODE_PREFIX = 'tempUsercssCode';
|
||||||
const TEMP_CODE_CLEANUP_DELAY = 60e3;
|
const TEMP_CODE_CLEANUP_DELAY = 60e3;
|
||||||
|
@ -49,34 +50,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the source and find the duplication
|
|
||||||
function build({sourceCode, checkDup = false}) {
|
|
||||||
return buildMeta({sourceCode})
|
|
||||||
.then(usercss.buildCode)
|
|
||||||
.then(style => ({
|
|
||||||
style,
|
|
||||||
dup: checkDup && findUsercss(style),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
function save(style, allowErrors = false) {
|
|
||||||
// restore if stripped by getStyleWithNoCode
|
|
||||||
if (typeof style.sourceCode !== 'string') {
|
|
||||||
style.sourceCode = cachedStyles.byId.get(style.id).sourceCode;
|
|
||||||
}
|
|
||||||
return buildMeta(style)
|
|
||||||
.then(assignVars)
|
|
||||||
.then(style => usercss.buildCode(style, allowErrors))
|
|
||||||
.then(result =>
|
|
||||||
allowErrors ?
|
|
||||||
saveStyle(result.style).then(style => ({style, errors: result.errors})) :
|
|
||||||
saveStyle(result));
|
|
||||||
|
|
||||||
function assignVars(style) {
|
function assignVars(style) {
|
||||||
if (style.reason === 'config' && style.id) {
|
if (style.reason === 'config' && style.id) {
|
||||||
return style;
|
return style;
|
||||||
}
|
}
|
||||||
const dup = findUsercss(style);
|
const dup = find(style);
|
||||||
if (dup) {
|
if (dup) {
|
||||||
style.id = dup.id;
|
style.id = dup.id;
|
||||||
if (style.reason !== 'config') {
|
if (style.reason !== 'config') {
|
||||||
|
@ -86,13 +64,41 @@
|
||||||
}
|
}
|
||||||
return style;
|
return style;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse the source and find the duplication
|
||||||
|
function build({sourceCode, checkDup = false}) {
|
||||||
|
return buildMeta({sourceCode})
|
||||||
|
.then(usercss.buildCode)
|
||||||
|
.then(style => ({
|
||||||
|
style,
|
||||||
|
dup: checkDup && find(style),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the source, apply customizations, report fatal/syntax errors
|
||||||
|
function parse(style, allowErrors = false) {
|
||||||
|
// restore if stripped by getStyleWithNoCode
|
||||||
|
if (typeof style.sourceCode !== 'string') {
|
||||||
|
style.sourceCode = cachedStyles.byId.get(style.id).sourceCode;
|
||||||
|
}
|
||||||
|
return buildMeta(style)
|
||||||
|
.then(assignVars)
|
||||||
|
.then(style => usercss.buildCode(style, allowErrors));
|
||||||
|
}
|
||||||
|
|
||||||
|
function save(style, allowErrors = false) {
|
||||||
|
return parse(style, allowErrors)
|
||||||
|
.then(result =>
|
||||||
|
allowErrors ?
|
||||||
|
saveStyle(result.style).then(style => ({style, errors: result.errors})) :
|
||||||
|
saveStyle(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Style|{name:string, namespace:string}} styleOrData
|
* @param {Style|{name:string, namespace:string}} styleOrData
|
||||||
* @returns {Style}
|
* @returns {Style}
|
||||||
*/
|
*/
|
||||||
function findUsercss(styleOrData) {
|
function find(styleOrData) {
|
||||||
if (styleOrData.id) return cachedStyles.byId.get(styleOrData.id);
|
if (styleOrData.id) return cachedStyles.byId.get(styleOrData.id);
|
||||||
const {name, namespace} = styleOrData.usercssData || styleOrData;
|
const {name, namespace} = styleOrData.usercssData || styleOrData;
|
||||||
for (const dup of cachedStyles.list) {
|
for (const dup of cachedStyles.list) {
|
||||||
|
|
|
@ -519,7 +519,7 @@
|
||||||
function moveAfter(el, expected) {
|
function moveAfter(el, expected) {
|
||||||
if (!sorting) {
|
if (!sorting) {
|
||||||
sorting = true;
|
sorting = true;
|
||||||
if (observer) observer.stop();
|
stop();
|
||||||
}
|
}
|
||||||
expected.insertAdjacentElement('afterend', el);
|
expected.insertAdjacentElement('afterend', el);
|
||||||
if (el.disabled !== disableAll) {
|
if (el.disabled !== disableAll) {
|
||||||
|
|
10
edit.html
10
edit.html
|
@ -249,13 +249,15 @@
|
||||||
<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">
|
<label id="enabled-label" i18n-text="styleEnabledLabel" i18n-title="styleEnabledToggleHint">
|
||||||
<input type="checkbox" id="enabled" class="style-contributor">
|
<input type="checkbox" id="enabled" class="style-contributor">
|
||||||
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
||||||
</label>
|
</label>
|
||||||
<a href="#" id="toggle-style-help" class="svg-inline-wrapper">
|
<label id="preview-label" i18n-text="previewLabel" i18n-title="previewTooltip" class="hidden">
|
||||||
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
|
<input type="checkbox" id="editor.livePreview">
|
||||||
</a>
|
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
||||||
|
</label>
|
||||||
|
<span id="preview-errors" class="hidden">!</span>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section id="actions">
|
<section id="actions">
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
/*
|
/*
|
||||||
global CodeMirror linterConfig loadScript
|
global CodeMirror linterConfig loadScript
|
||||||
global editors editor styleId
|
global editors editor styleId ownTabId
|
||||||
global save toggleStyle setupAutocomplete makeSectionVisible getSectionForChild
|
global save toggleStyle setupAutocomplete makeSectionVisible getSectionForChild
|
||||||
|
global getSectionsHashes
|
||||||
|
global messageBox
|
||||||
*/
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
@ -41,8 +43,9 @@ onDOMscriptReady('/codemirror.js').then(() => {
|
||||||
addEventListener('showHotkeyInTooltip', showHotkeyInTooltip);
|
addEventListener('showHotkeyInTooltip', showHotkeyInTooltip);
|
||||||
showHotkeyInTooltip();
|
showHotkeyInTooltip();
|
||||||
|
|
||||||
// N.B. the event listener should be registered before setupLivePrefs()
|
// N.B. the onchange event listeners should be registered before setupLivePrefs()
|
||||||
$('#options').addEventListener('change', onOptionElementChanged);
|
$('#options').addEventListener('change', onOptionElementChanged);
|
||||||
|
setupLivePreview();
|
||||||
buildThemeElement();
|
buildThemeElement();
|
||||||
buildKeymapElement();
|
buildKeymapElement();
|
||||||
setupLivePrefs();
|
setupLivePrefs();
|
||||||
|
@ -531,4 +534,49 @@ onDOMscriptReady('/codemirror.js').then(() => {
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setupLivePreview() {
|
||||||
|
if (!prefs.get('editor.livePreview') && !editors.length) {
|
||||||
|
setTimeout(setupLivePreview);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$('#editor.livePreview').onchange = function () {
|
||||||
|
const previewing = this.checked;
|
||||||
|
editors.forEach(cm => cm[previewing ? 'on' : 'off']('changes', updatePreview));
|
||||||
|
const addRemove = previewing ? 'addEventListener' : 'removeEventListener';
|
||||||
|
$('#enabled')[addRemove]('change', updatePreview);
|
||||||
|
$('#sections')[addRemove]('change', updatePreview);
|
||||||
|
if (!previewing || document.body.classList.contains('dirty')) {
|
||||||
|
updatePreview(null, previewing);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
CodeMirror.defineInitHook(cm => {
|
||||||
|
if (prefs.get('editor.livePreview')) {
|
||||||
|
cm.on('changes', updatePreview);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePreview(data, previewing) {
|
||||||
|
if (previewing !== true && previewing !== false) {
|
||||||
|
if (data instanceof Event && !event.target.matches('.style-contributor')) return;
|
||||||
|
debounce(updatePreview, data && data.id === 'enabled' ? 0 : 400, null, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const errors = $('#preview-errors');
|
||||||
|
API.refreshAllTabs({
|
||||||
|
reason: 'editPreview',
|
||||||
|
tabId: ownTabId,
|
||||||
|
style: {
|
||||||
|
id: styleId,
|
||||||
|
enabled: $('#enabled').checked,
|
||||||
|
sections: previewing && (editor ? editors[0].getValue() : getSectionsHashes()),
|
||||||
|
},
|
||||||
|
}).then(() => {
|
||||||
|
errors.classList.add('hidden');
|
||||||
|
}).catch(err => {
|
||||||
|
errors.classList.remove('hidden');
|
||||||
|
errors.onclick = () => messageBox.alert(String(err));
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -41,10 +41,6 @@ body {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#basic-info-enabled {
|
|
||||||
margin-top: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
label {
|
||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -102,6 +98,43 @@ label {
|
||||||
#url:not([href^="http"]) {
|
#url:not([href^="http"]) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#basic-info-enabled {
|
||||||
|
margin-top: 2px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
line-height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#basic-info-enabled > * {
|
||||||
|
margin-right: 1em;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#basic-info-enabled > :last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#basic-info-enabled input,
|
||||||
|
#basic-info-enabled svg {
|
||||||
|
margin: auto 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#basic-info-enabled svg {
|
||||||
|
left: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#preview-errors {
|
||||||
|
background-color: red;
|
||||||
|
color: white;
|
||||||
|
padding: 0 6px;
|
||||||
|
border-radius: 9px;
|
||||||
|
margin-left: -.5em;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.svg-icon {
|
.svg-icon {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
@ -117,9 +150,6 @@ label {
|
||||||
#mozilla-format-heading .svg-inline-wrapper {
|
#mozilla-format-heading .svg-inline-wrapper {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
#basic-info-enabled .svg-inline-wrapper {
|
|
||||||
margin-left: .1rem;
|
|
||||||
}
|
|
||||||
#colorpicker-settings.svg-inline-wrapper {
|
#colorpicker-settings.svg-inline-wrapper {
|
||||||
margin: -2px 0 0 .1rem;
|
margin: -2px 0 0 .1rem;
|
||||||
}
|
}
|
||||||
|
@ -155,10 +185,6 @@ input:invalid {
|
||||||
}
|
}
|
||||||
#enabled {
|
#enabled {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
#enabled-label {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
}
|
||||||
/* collapsibles */
|
/* collapsibles */
|
||||||
#header summary {
|
#header summary {
|
||||||
|
|
|
@ -16,6 +16,7 @@ let dirty = {};
|
||||||
// array of all CodeMirror instances
|
// array of all CodeMirror instances
|
||||||
const editors = [];
|
const editors = [];
|
||||||
let saveSizeOnClose;
|
let saveSizeOnClose;
|
||||||
|
let ownTabId;
|
||||||
|
|
||||||
// direct & reverse mapping of @-moz-document keywords and internal property names
|
// direct & reverse mapping of @-moz-document keywords and internal property names
|
||||||
const propertyToCss = {urls: 'url', urlPrefixes: 'url-prefix', domains: 'domain', regexps: 'regexp'};
|
const propertyToCss = {urls: 'url', urlPrefixes: 'url-prefix', domains: 'domain', regexps: 'regexp'};
|
||||||
|
@ -38,6 +39,8 @@ Promise.all([
|
||||||
$('#name').placeholder = t(usercss ? 'usercssEditorNamePlaceholder' : 'styleMissingName');
|
$('#name').placeholder = t(usercss ? 'usercssEditorNamePlaceholder' : 'styleMissingName');
|
||||||
$('#name').title = usercss ? t('usercssReplaceTemplateName') : '';
|
$('#name').title = usercss ? t('usercssReplaceTemplateName') : '';
|
||||||
|
|
||||||
|
$('#preview-label').classList.toggle('hidden', !styleId);
|
||||||
|
|
||||||
$('#beautify').onclick = beautify;
|
$('#beautify').onclick = beautify;
|
||||||
$('#lint').addEventListener('scroll', hideLintHeaderOnScroll, {passive: true});
|
$('#lint').addEventListener('scroll', hideLintHeaderOnScroll, {passive: true});
|
||||||
window.addEventListener('resize', () => debounce(rememberWindowSize, 100));
|
window.addEventListener('resize', () => debounce(rememberWindowSize, 100));
|
||||||
|
@ -110,7 +113,7 @@ function preinit() {
|
||||||
}
|
}
|
||||||
|
|
||||||
getOwnTab().then(tab => {
|
getOwnTab().then(tab => {
|
||||||
const ownTabId = tab.id;
|
ownTabId = tab.id;
|
||||||
|
|
||||||
// use browser history back when 'back to manage' is clicked
|
// use browser history back when 'back to manage' is clicked
|
||||||
if (sessionStorageHash('manageStylesHistory').value[ownTabId] === location.href) {
|
if (sessionStorageHash('manageStylesHistory').value[ownTabId] === location.href) {
|
||||||
|
@ -153,6 +156,7 @@ function onRuntimeMessage(request) {
|
||||||
switch (request.method) {
|
switch (request.method) {
|
||||||
case 'styleUpdated':
|
case 'styleUpdated':
|
||||||
if (styleId && styleId === request.style.id &&
|
if (styleId && styleId === request.style.id &&
|
||||||
|
request.reason !== 'editPreview' &&
|
||||||
request.reason !== 'editSave' &&
|
request.reason !== 'editSave' &&
|
||||||
request.reason !== 'config') {
|
request.reason !== 'config') {
|
||||||
// code-less style from notifyAllTabs
|
// code-less style from notifyAllTabs
|
||||||
|
@ -258,7 +262,6 @@ function initHooks() {
|
||||||
node.addEventListener('change', onChange);
|
node.addEventListener('change', onChange);
|
||||||
node.addEventListener('input', onChange);
|
node.addEventListener('input', onChange);
|
||||||
});
|
});
|
||||||
$('#toggle-style-help').addEventListener('click', showToggleStyleHelp);
|
|
||||||
$('#to-mozilla').addEventListener('click', showMozillaFormat, false);
|
$('#to-mozilla').addEventListener('click', showMozillaFormat, false);
|
||||||
$('#to-mozilla-help').addEventListener('click', showToMozillaHelp, false);
|
$('#to-mozilla-help').addEventListener('click', showToMozillaHelp, false);
|
||||||
$('#from-mozilla').addEventListener('click', fromMozillaFormat);
|
$('#from-mozilla').addEventListener('click', fromMozillaFormat);
|
||||||
|
@ -365,6 +368,7 @@ function save() {
|
||||||
$('#heading').textContent = t('editStyleHeading');
|
$('#heading').textContent = t('editStyleHeading');
|
||||||
}
|
}
|
||||||
updateTitle();
|
updateTitle();
|
||||||
|
$('#preview-label').classList.remove('hidden');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
/* global CodeMirror dirtyReporter initLint */
|
/*
|
||||||
/* global showToggleStyleHelp goBackToManage updateLintReportIfEnabled */
|
global editors styleId: true
|
||||||
/* global editors linterConfig updateLinter regExpTester sectionsToMozFormat */
|
global CodeMirror dirtyReporter
|
||||||
/* global createAppliesToLineWidget messageBox */
|
global updateLintReportIfEnabled initLint linterConfig updateLinter
|
||||||
|
global createAppliesToLineWidget messageBox
|
||||||
|
global sectionsToMozFormat
|
||||||
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
function createSourceEditor(style) {
|
function createSourceEditor(style) {
|
||||||
|
@ -9,7 +12,6 @@ function createSourceEditor(style) {
|
||||||
$('#save-button').disabled = true;
|
$('#save-button').disabled = true;
|
||||||
$('#mozilla-format-container').remove();
|
$('#mozilla-format-container').remove();
|
||||||
$('#save-button').onclick = save;
|
$('#save-button').onclick = save;
|
||||||
$('#toggle-style-help').onclick = showToggleStyleHelp;
|
|
||||||
$('#header').addEventListener('wheel', headerOnScroll, {passive: true});
|
$('#header').addEventListener('wheel', headerOnScroll, {passive: true});
|
||||||
$('#sections').textContent = '';
|
$('#sections').textContent = '';
|
||||||
$('#sections').appendChild($create('.single-editor'));
|
$('#sections').appendChild($create('.single-editor'));
|
||||||
|
@ -176,6 +178,8 @@ function createSourceEditor(style) {
|
||||||
}
|
}
|
||||||
sessionStorage.justEditedStyleId = newStyle.id;
|
sessionStorage.justEditedStyleId = newStyle.id;
|
||||||
style = newStyle;
|
style = newStyle;
|
||||||
|
styleId = style.id;
|
||||||
|
$('#preview-label').classList.remove('hidden');
|
||||||
updateMeta();
|
updateMeta();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,7 +144,8 @@ var API = (() => {
|
||||||
|
|
||||||
function notifyAllTabs(msg) {
|
function notifyAllTabs(msg) {
|
||||||
const originalMessage = msg;
|
const originalMessage = msg;
|
||||||
if (msg.method === 'styleUpdated' || msg.method === 'styleAdded') {
|
const styleUpdated = msg.method === 'styleUpdated';
|
||||||
|
if (styleUpdated || msg.method === 'styleAdded') {
|
||||||
// apply/popup/manage use only meta for these two methods,
|
// apply/popup/manage use only meta for these two methods,
|
||||||
// editor may need the full code but can fetch it directly,
|
// editor may need the full code but can fetch it directly,
|
||||||
// so we send just the meta to avoid spamming lots of tabs with huge styles
|
// so we send just the meta to avoid spamming lots of tabs with huge styles
|
||||||
|
@ -167,7 +168,8 @@ function notifyAllTabs(msg) {
|
||||||
if (affectsTabs || affectsIcon) {
|
if (affectsTabs || affectsIcon) {
|
||||||
const notifyTab = tab => {
|
const notifyTab = tab => {
|
||||||
// own pages will be notified via runtime.sendMessage later
|
// own pages will be notified via runtime.sendMessage later
|
||||||
if ((affectsTabs || URLS.optionsUI.includes(tab.url))
|
if (!styleUpdated
|
||||||
|
&& (affectsTabs || URLS.optionsUI.includes(tab.url))
|
||||||
&& !(affectsSelf && tab.url.startsWith(URLS.ownOrigin))
|
&& !(affectsSelf && tab.url.startsWith(URLS.ownOrigin))
|
||||||
// skip lazy-loaded aka unloaded tabs that seem to start loading on message in FF
|
// skip lazy-loaded aka unloaded tabs that seem to start loading on message in FF
|
||||||
&& (!FIREFOX || tab.width)) {
|
&& (!FIREFOX || tab.width)) {
|
||||||
|
@ -198,6 +200,10 @@ function notifyAllTabs(msg) {
|
||||||
if (typeof applyOnMessage !== 'undefined') {
|
if (typeof applyOnMessage !== 'undefined') {
|
||||||
applyOnMessage(originalMessage);
|
applyOnMessage(originalMessage);
|
||||||
}
|
}
|
||||||
|
// propagate saved style state/code efficiently
|
||||||
|
if (styleUpdated) {
|
||||||
|
API.refreshAllTabs(msg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -404,7 +410,7 @@ const debounce = Object.assign((fn, delay, ...args) => {
|
||||||
|
|
||||||
function deepCopy(obj) {
|
function deepCopy(obj) {
|
||||||
if (!obj || typeof obj !== 'object') return obj;
|
if (!obj || typeof obj !== 'object') return obj;
|
||||||
// N.B. a copy should be an explicitly literal
|
// N.B. the copy should be an explicit literal
|
||||||
if (Array.isArray(obj)) {
|
if (Array.isArray(obj)) {
|
||||||
const copy = [];
|
const copy = [];
|
||||||
for (const v of obj) {
|
for (const v of obj) {
|
||||||
|
|
|
@ -68,6 +68,7 @@ var prefs = new function Prefs() {
|
||||||
'editor.contextDelete': contextDeleteMissing(), // "Delete" item in context menu
|
'editor.contextDelete': contextDeleteMissing(), // "Delete" item in context menu
|
||||||
|
|
||||||
'editor.appliesToLineWidget': true, // show applies-to line widget on the editor
|
'editor.appliesToLineWidget': true, // show applies-to line widget on the editor
|
||||||
|
'editor.livePreview': true,
|
||||||
|
|
||||||
// show CSS colors as clickable colored rectangles
|
// show CSS colors as clickable colored rectangles
|
||||||
'editor.colorpicker': true,
|
'editor.colorpicker': true,
|
||||||
|
|
|
@ -515,6 +515,7 @@ Object.assign(handleEvent, {
|
||||||
|
|
||||||
|
|
||||||
function handleUpdate(style, {reason, method} = {}) {
|
function handleUpdate(style, {reason, method} = {}) {
|
||||||
|
if (reason === 'editPreview') return;
|
||||||
let entry;
|
let entry;
|
||||||
let oldEntry = $(ENTRY_ID_PREFIX + style.id);
|
let oldEntry = $(ENTRY_ID_PREFIX + style.id);
|
||||||
if (oldEntry && method === 'styleUpdated') {
|
if (oldEntry && method === 'styleUpdated') {
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
"background/style-via-api.js",
|
"background/style-via-api.js",
|
||||||
"background/search-db.js",
|
"background/search-db.js",
|
||||||
"background/update.js",
|
"background/update.js",
|
||||||
|
"background/refresh-all-tabs.js",
|
||||||
"vendor/node-semver/semver.js",
|
"vendor/node-semver/semver.js",
|
||||||
"vendor-overwrites/colorpicker/colorconverter.js"
|
"vendor-overwrites/colorpicker/colorconverter.js"
|
||||||
]
|
]
|
||||||
|
|
|
@ -32,6 +32,7 @@ function onRuntimeMessage(msg) {
|
||||||
switch (msg.method) {
|
switch (msg.method) {
|
||||||
case 'styleAdded':
|
case 'styleAdded':
|
||||||
case 'styleUpdated':
|
case 'styleUpdated':
|
||||||
|
if (msg.reason === 'editPreview') return;
|
||||||
handleUpdate(msg.style);
|
handleUpdate(msg.style);
|
||||||
break;
|
break;
|
||||||
case 'styleDeleted':
|
case 'styleDeleted':
|
||||||
|
|
Loading…
Reference in New Issue
Block a user