parent
60f59e9f06
commit
e54178a43c
|
@ -342,6 +342,19 @@
|
||||||
"message": "Disable",
|
"message": "Disable",
|
||||||
"description": "Label for the button to disable a style"
|
"description": "Label for the button to disable a style"
|
||||||
},
|
},
|
||||||
|
"draftTitle": {
|
||||||
|
"message": "Draft recovery, created $date$",
|
||||||
|
"placeholders": {
|
||||||
|
"date": {
|
||||||
|
"content": "$1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Title of the modal displayed in the editor when an unsaved draft is found, the $date$ looks like '1 hour ago' in user's current UI language"
|
||||||
|
},
|
||||||
|
"draftAction": {
|
||||||
|
"message": "Choose 'Yes' to load this draft or 'No' to discard it.",
|
||||||
|
"description": "Displayed in the editor after the browser/extension crashed"
|
||||||
|
},
|
||||||
"dragDropMessage": {
|
"dragDropMessage": {
|
||||||
"message": "Drop your backup file anywhere on this page to import.",
|
"message": "Drop your backup file anywhere on this page to import.",
|
||||||
"description": "Drag'n'drop message"
|
"description": "Drag'n'drop message"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/* global API msg */// msg.js
|
/* global API msg */// msg.js
|
||||||
/* global addAPI bgReady */// common.js
|
/* global addAPI bgReady */// common.js
|
||||||
/* global createWorker */// worker-util.js
|
/* global createWorker */// worker-util.js
|
||||||
|
/* global db */
|
||||||
/* global prefs */
|
/* global prefs */
|
||||||
/* global styleMan */
|
/* global styleMan */
|
||||||
/* global syncMan */
|
/* global syncMan */
|
||||||
|
@ -37,6 +38,11 @@ addAPI(/** @namespace API */ {
|
||||||
},
|
},
|
||||||
}))(),
|
}))(),
|
||||||
|
|
||||||
|
/** @type IDBObjectStore */
|
||||||
|
drafts: new Proxy({}, {
|
||||||
|
get: (_, cmd) => (...args) => db.exec.call('drafts', cmd, ...args),
|
||||||
|
}),
|
||||||
|
|
||||||
styles: styleMan,
|
styles: styleMan,
|
||||||
sync: syncMan,
|
sync: syncMan,
|
||||||
updater: updateMan,
|
updater: updateMan,
|
||||||
|
|
|
@ -2,17 +2,15 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/* exported createChromeStorageDB */
|
/* exported createChromeStorageDB */
|
||||||
function createChromeStorageDB() {
|
function createChromeStorageDB(PREFIX) {
|
||||||
let INC;
|
let INC;
|
||||||
|
|
||||||
const PREFIX = 'style-';
|
return {
|
||||||
const METHODS = {
|
|
||||||
|
|
||||||
delete(id) {
|
delete(id) {
|
||||||
return chromeLocal.remove(PREFIX + id);
|
return chromeLocal.remove(PREFIX + id);
|
||||||
},
|
},
|
||||||
|
|
||||||
// FIXME: we don't use this method at all. Should we remove this?
|
|
||||||
get(id) {
|
get(id) {
|
||||||
return chromeLocal.getValue(PREFIX + id);
|
return chromeLocal.getValue(PREFIX + id);
|
||||||
},
|
},
|
||||||
|
@ -59,8 +57,4 @@ function createChromeStorageDB() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return function dbExecChromeStorage(method, ...args) {
|
|
||||||
return METHODS[method](...args);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,12 +52,18 @@ const db = (() => {
|
||||||
console.warn('Failed to access indexedDB. Switched to storage API.', err);
|
console.warn('Failed to access indexedDB. Switched to storage API.', err);
|
||||||
}
|
}
|
||||||
await require(['/background/db-chrome-storage']); /* global createChromeStorageDB */
|
await require(['/background/db-chrome-storage']); /* global createChromeStorageDB */
|
||||||
return createChromeStorageDB();
|
const BASES = {};
|
||||||
|
return function dbExecChromeStorage(method, ...args) {
|
||||||
|
const prefix = Object(this) instanceof String ? `${this}-` : 'style-';
|
||||||
|
const baseApi = BASES[prefix] || (BASES[prefix] = createChromeStorageDB(prefix));
|
||||||
|
return baseApi[method](...args);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function dbExecIndexedDB(method, ...args) {
|
async function dbExecIndexedDB(method, ...args) {
|
||||||
const mode = method.startsWith('get') ? 'readonly' : 'readwrite';
|
const mode = method.startsWith('get') ? 'readonly' : 'readwrite';
|
||||||
const store = (await open()).transaction([STORE], mode).objectStore(STORE);
|
const dbName = Object(this) instanceof String ? `${this}` : DATABASE;
|
||||||
|
const store = (await open(dbName)).transaction([STORE], mode).objectStore(STORE);
|
||||||
const fn = method === 'putMany' ? putMany : storeRequest;
|
const fn = method === 'putMany' ? putMany : storeRequest;
|
||||||
return fn(store, method, ...args);
|
return fn(store, method, ...args);
|
||||||
}
|
}
|
||||||
|
@ -75,9 +81,9 @@ const db = (() => {
|
||||||
return Promise.all(items.map(item => storeRequest(store, 'put', item)));
|
return Promise.all(items.map(item => storeRequest(store, 'put', item)));
|
||||||
}
|
}
|
||||||
|
|
||||||
function open() {
|
function open(name) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const request = indexedDB.open(DATABASE, 2);
|
const request = indexedDB.open(name, 2);
|
||||||
request.onsuccess = () => resolve(request.result);
|
request.onsuccess = () => resolve(request.result);
|
||||||
request.onerror = reject;
|
request.onerror = reject;
|
||||||
request.onupgradeneeded = create;
|
request.onupgradeneeded = create;
|
||||||
|
|
|
@ -61,7 +61,13 @@ const styleMan = (() => {
|
||||||
let ready = init();
|
let ready = init();
|
||||||
let order = {};
|
let order = {};
|
||||||
|
|
||||||
chrome.runtime.onConnect.addListener(handleLivePreview);
|
chrome.runtime.onConnect.addListener(port => {
|
||||||
|
if (port.name === 'livePreview') {
|
||||||
|
handleLivePreview(port);
|
||||||
|
} else if (port.name.startsWith('draft:')) {
|
||||||
|
handleDraft(port);
|
||||||
|
}
|
||||||
|
});
|
||||||
// function handleColorScheme() {
|
// function handleColorScheme() {
|
||||||
colorScheme.onChange(() => {
|
colorScheme.onChange(() => {
|
||||||
for (const {style: data} of dataMap.values()) {
|
for (const {style: data} of dataMap.values()) {
|
||||||
|
@ -106,6 +112,7 @@ const styleMan = (() => {
|
||||||
// Must be called after the style is deleted from dataMap
|
// Must be called after the style is deleted from dataMap
|
||||||
API.usw.revoke(id);
|
API.usw.revoke(id);
|
||||||
}
|
}
|
||||||
|
API.drafts.delete(id);
|
||||||
await msg.broadcast({
|
await msg.broadcast({
|
||||||
method: 'styleDeleted',
|
method: 'styleDeleted',
|
||||||
style: {id},
|
style: {id},
|
||||||
|
@ -371,10 +378,12 @@ const styleMan = (() => {
|
||||||
style);
|
style);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleDraft(port) {
|
||||||
|
const id = port.name.split(':').pop();
|
||||||
|
port.onDisconnect.addListener(() => API.drafts.delete(Number(id) || id));
|
||||||
|
}
|
||||||
|
|
||||||
function handleLivePreview(port) {
|
function handleLivePreview(port) {
|
||||||
if (port.name !== 'livePreview') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let id;
|
let id;
|
||||||
port.onMessage.addListener(style => {
|
port.onMessage.addListener(style => {
|
||||||
if (!id) id = style.id;
|
if (!id) id = style.id;
|
||||||
|
|
35
edit/base.js
35
edit/base.js
|
@ -339,9 +339,15 @@ baseInit.ready.then(() => {
|
||||||
function DirtyReporter() {
|
function DirtyReporter() {
|
||||||
const data = new Map();
|
const data = new Map();
|
||||||
const listeners = new Set();
|
const listeners = new Set();
|
||||||
|
const dataListeners = new Set();
|
||||||
const notifyChange = wasDirty => {
|
const notifyChange = wasDirty => {
|
||||||
if (wasDirty !== (data.size > 0)) {
|
const isDirty = data.size > 0;
|
||||||
listeners.forEach(cb => cb());
|
const flipped = isDirty !== wasDirty;
|
||||||
|
if (flipped) {
|
||||||
|
listeners.forEach(cb => cb(isDirty));
|
||||||
|
}
|
||||||
|
if (flipped || isDirty) {
|
||||||
|
dataListeners.forEach(cb => cb(isDirty));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
/** @namespace DirtyReporter */
|
/** @namespace DirtyReporter */
|
||||||
|
@ -358,17 +364,19 @@ function DirtyReporter() {
|
||||||
saved.newValue = value;
|
saved.newValue = value;
|
||||||
saved.type = 'modify';
|
saved.type = 'modify';
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
notifyChange(wasDirty);
|
notifyChange(wasDirty);
|
||||||
},
|
},
|
||||||
clear(obj) {
|
clear(...objs) {
|
||||||
const wasDirty = data.size > 0;
|
if (data.size && (
|
||||||
if (obj === undefined) {
|
objs.length
|
||||||
data.clear();
|
? objs.map(data.delete, data).includes(true)
|
||||||
} else {
|
: (data.clear(), true)
|
||||||
data.delete(obj);
|
)) {
|
||||||
|
notifyChange(true);
|
||||||
}
|
}
|
||||||
notifyChange(wasDirty);
|
|
||||||
},
|
},
|
||||||
has(key) {
|
has(key) {
|
||||||
return data.has(key);
|
return data.has(key);
|
||||||
|
@ -382,6 +390,8 @@ function DirtyReporter() {
|
||||||
if (!saved) {
|
if (!saved) {
|
||||||
if (oldValue !== newValue) {
|
if (oldValue !== newValue) {
|
||||||
data.set(obj, {type: 'modify', savedValue: oldValue, newValue});
|
data.set(obj, {type: 'modify', savedValue: oldValue, newValue});
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} else if (saved.type === 'modify') {
|
} else if (saved.type === 'modify') {
|
||||||
if (saved.savedValue === newValue) {
|
if (saved.savedValue === newValue) {
|
||||||
|
@ -391,12 +401,17 @@ function DirtyReporter() {
|
||||||
}
|
}
|
||||||
} else if (saved.type === 'add') {
|
} else if (saved.type === 'add') {
|
||||||
saved.newValue = newValue;
|
saved.newValue = newValue;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
notifyChange(wasDirty);
|
notifyChange(wasDirty);
|
||||||
},
|
},
|
||||||
onChange(cb, add = true) {
|
onChange(cb, add = true) {
|
||||||
listeners[add ? 'add' : 'delete'](cb);
|
listeners[add ? 'add' : 'delete'](cb);
|
||||||
},
|
},
|
||||||
|
onDataChange(cb, add = true) {
|
||||||
|
dataListeners[add ? 'add' : 'delete'](cb);
|
||||||
|
},
|
||||||
remove(obj, value) {
|
remove(obj, value) {
|
||||||
const wasDirty = data.size > 0;
|
const wasDirty = data.size > 0;
|
||||||
const saved = data.get(obj);
|
const saved = data.get(obj);
|
||||||
|
@ -406,6 +421,8 @@ function DirtyReporter() {
|
||||||
data.delete(obj);
|
data.delete(obj);
|
||||||
} else if (saved.type === 'modify') {
|
} else if (saved.type === 'modify') {
|
||||||
saved.type = 'remove';
|
saved.type = 'remove';
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
notifyChange(wasDirty);
|
notifyChange(wasDirty);
|
||||||
},
|
},
|
||||||
|
|
69
edit/drafts.js
Normal file
69
edit/drafts.js
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
/* global messageBoxProxy */// dom.js
|
||||||
|
/* global API */// msg.js
|
||||||
|
/* global clamp debounce */// toolbox.js
|
||||||
|
/* global editor */
|
||||||
|
/* global prefs */
|
||||||
|
/* global t */// localization.js
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
(async function AutosaveDrafts() {
|
||||||
|
const makeId = () => editor.style.id || 'new';
|
||||||
|
let delay;
|
||||||
|
let port;
|
||||||
|
connectPort();
|
||||||
|
|
||||||
|
const draft = await API.drafts.get(makeId());
|
||||||
|
if (draft && draft.isUsercss === editor.isUsercss) {
|
||||||
|
const date = makeRelativeDate(draft.date);
|
||||||
|
if (await messageBoxProxy.confirm(t('draftAction'), 'danger', t('draftTitle', date))) {
|
||||||
|
await editor.replaceStyle(draft.style, draft);
|
||||||
|
} else {
|
||||||
|
API.drafts.delete(makeId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.dirty.onChange(isDirty => isDirty ? connectPort() : port.disconnect());
|
||||||
|
editor.dirty.onDataChange(isDirty => debounce(updateDraft, isDirty ? delay : 0));
|
||||||
|
|
||||||
|
prefs.subscribe('editor.autosaveDraft', (key, val) => {
|
||||||
|
delay = clamp(val * 1000 | 0, 1000, 2 ** 32 - 1);
|
||||||
|
const t = debounce.timers.get(updateDraft);
|
||||||
|
if (t != null) debounce(updateDraft, t ? delay : 0);
|
||||||
|
}, {runNow: true});
|
||||||
|
|
||||||
|
function connectPort() {
|
||||||
|
port = chrome.runtime.connect({name: 'draft:' + makeId()});
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeRelativeDate(date) {
|
||||||
|
let delta = (Date.now() - date) / 1000;
|
||||||
|
if (delta >= 0 && Intl.RelativeTimeFormat) {
|
||||||
|
for (const [span, unit, frac = 1] of [
|
||||||
|
[60, 'second', 0],
|
||||||
|
[60, 'minute', 0],
|
||||||
|
[24, 'hour'],
|
||||||
|
[7, 'day'],
|
||||||
|
[4, 'week'],
|
||||||
|
[12, 'month'],
|
||||||
|
[1e99, 'year'],
|
||||||
|
]) {
|
||||||
|
if (delta < span) {
|
||||||
|
return new Intl.RelativeTimeFormat({style: 'short'}).format(-delta.toFixed(frac), unit);
|
||||||
|
}
|
||||||
|
delta /= span;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return date.toLocaleString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDraft(isDirty = editor.dirty.isDirty()) {
|
||||||
|
if (!isDirty) return;
|
||||||
|
API.drafts.put({
|
||||||
|
date: Date.now(),
|
||||||
|
id: makeId(),
|
||||||
|
isUsercss: editor.isUsercss,
|
||||||
|
style: editor.getValue(true),
|
||||||
|
si: editor.makeScrollInfo(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})();
|
25
edit/edit.js
25
edit/edit.js
|
@ -58,6 +58,7 @@ baseInit.ready.then(async () => {
|
||||||
|
|
||||||
require([
|
require([
|
||||||
'/edit/autocomplete',
|
'/edit/autocomplete',
|
||||||
|
'/edit/drafts',
|
||||||
'/edit/global-search',
|
'/edit/global-search',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
@ -139,16 +140,7 @@ window.on('beforeunload', e => {
|
||||||
prefs.set('windowPosition', pos);
|
prefs.set('windowPosition', pos);
|
||||||
}
|
}
|
||||||
sessionStore.windowPos = JSON.stringify(pos || {});
|
sessionStore.windowPos = JSON.stringify(pos || {});
|
||||||
sessionStore['editorScrollInfo' + editor.style.id] = JSON.stringify({
|
sessionStore['editorScrollInfo' + editor.style.id] = JSON.stringify(editor.makeScrollInfo());
|
||||||
scrollY: window.scrollY,
|
|
||||||
cms: editor.getEditors().map(cm => /** @namespace EditorScrollInfo */({
|
|
||||||
bookmarks: (cm.state.sublimeBookmarks || []).map(b => b.find()),
|
|
||||||
focus: cm.hasFocus(),
|
|
||||||
height: cm.display.wrapper.style.height.replace('100vh', ''),
|
|
||||||
parentHeight: cm.display.wrapper.parentElement.offsetHeight,
|
|
||||||
sel: cm.isClean() && [cm.doc.sel.ranges, cm.doc.sel.primIndex],
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
const activeElement = document.activeElement;
|
const activeElement = document.activeElement;
|
||||||
if (activeElement) {
|
if (activeElement) {
|
||||||
// blurring triggers 'change' or 'input' event if needed
|
// blurring triggers 'change' or 'input' event if needed
|
||||||
|
@ -195,6 +187,19 @@ window.on('beforeunload', e => {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
makeScrollInfo() {
|
||||||
|
return {
|
||||||
|
scrollY: window.scrollY,
|
||||||
|
cms: editor.getEditors().map(cm => /** @namespace EditorScrollInfo */({
|
||||||
|
bookmarks: (cm.state.sublimeBookmarks || []).map(b => b.find()),
|
||||||
|
focus: cm.hasFocus(),
|
||||||
|
height: cm.display.wrapper.style.height.replace('100vh', ''),
|
||||||
|
parentHeight: cm.display.wrapper.parentElement.offsetHeight,
|
||||||
|
sel: [cm.doc.sel.ranges, cm.doc.sel.primIndex],
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
async save() {
|
async save() {
|
||||||
if (dirty.isDirty()) {
|
if (dirty.isDirty()) {
|
||||||
editor.saving = true;
|
editor.saving = true;
|
||||||
|
|
|
@ -86,15 +86,21 @@ function SectionsEditor() {
|
||||||
: null;
|
: null;
|
||||||
},
|
},
|
||||||
|
|
||||||
async replaceStyle(newStyle) {
|
async replaceStyle(newStyle, draft) {
|
||||||
const sameCode = styleSectionsEqual(newStyle, getModel());
|
const sameCode = styleSectionsEqual(newStyle, getModel());
|
||||||
if (!sameCode && !await messageBoxProxy.confirm(t('styleUpdateDiscardChanges'))) {
|
if (!sameCode && !draft && !await messageBoxProxy.confirm(t('styleUpdateDiscardChanges'))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dirty.clear();
|
if (!draft) {
|
||||||
|
dirty.clear();
|
||||||
|
}
|
||||||
// FIXME: avoid recreating all editors?
|
// FIXME: avoid recreating all editors?
|
||||||
if (!sameCode) {
|
if (!sameCode) {
|
||||||
await initSections(newStyle.sections, {replace: true});
|
await initSections(newStyle.sections, {
|
||||||
|
keepDirty: draft,
|
||||||
|
replace: true,
|
||||||
|
si: draft && draft.si,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
editor.useSavedStyle(newStyle);
|
editor.useSavedStyle(newStyle);
|
||||||
updateLivePreview();
|
updateLivePreview();
|
||||||
|
@ -468,14 +474,15 @@ function SectionsEditor() {
|
||||||
async function initSections(src, {
|
async function initSections(src, {
|
||||||
focusOn = 0,
|
focusOn = 0,
|
||||||
replace = false,
|
replace = false,
|
||||||
keepDirty = false, // used by import
|
keepDirty = false,
|
||||||
|
si = editor.scrollInfo,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
|
editor.ready = false;
|
||||||
if (replace) {
|
if (replace) {
|
||||||
sections.forEach(s => s.remove(true));
|
sections.forEach(s => s.remove(true));
|
||||||
sections.length = 0;
|
sections.length = 0;
|
||||||
container.textContent = '';
|
container.textContent = '';
|
||||||
}
|
}
|
||||||
let si = editor.scrollInfo;
|
|
||||||
if (si && si.cms && si.cms.length === src.length) {
|
if (si && si.cms && si.cms.length === src.length) {
|
||||||
si.scrollY2 = si.scrollY + window.innerHeight;
|
si.scrollY2 = si.scrollY + window.innerHeight;
|
||||||
container.style.height = si.scrollY2 + 'px';
|
container.style.height = si.scrollY2 + 'px';
|
||||||
|
@ -503,9 +510,12 @@ function SectionsEditor() {
|
||||||
if (!keepDirty) dirty.clear();
|
if (!keepDirty) dirty.clear();
|
||||||
if (i === focusOn) sections[i].cm.focus();
|
if (i === focusOn) sections[i].cm.focus();
|
||||||
}
|
}
|
||||||
if (!si) requestAnimationFrame(fitToAvailableSpace);
|
if (!si || si.cms.every(cm => !cm.height)) {
|
||||||
|
requestAnimationFrame(fitToAvailableSpace);
|
||||||
|
}
|
||||||
container.style.removeProperty('height');
|
container.style.removeProperty('height');
|
||||||
setGlobalProgress();
|
setGlobalProgress();
|
||||||
|
editor.ready = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {EditorSection} section */
|
/** @param {EditorSection} section */
|
||||||
|
|
|
@ -57,7 +57,13 @@ function SourceEditor() {
|
||||||
closestVisible: () => cm,
|
closestVisible: () => cm,
|
||||||
getEditors: () => [cm],
|
getEditors: () => [cm],
|
||||||
getEditorTitle: () => '',
|
getEditorTitle: () => '',
|
||||||
getValue: () => cm.getValue(),
|
getValue: asObject => asObject
|
||||||
|
? {
|
||||||
|
customName: style.customName,
|
||||||
|
enabled: style.enabled,
|
||||||
|
sourceCode: cm.getValue(),
|
||||||
|
}
|
||||||
|
: cm.getValue(),
|
||||||
getSearchableInputs: () => [],
|
getSearchableInputs: () => [],
|
||||||
prevEditor: nextPrevSection.bind(null, -1),
|
prevEditor: nextPrevSection.bind(null, -1),
|
||||||
nextEditor: nextPrevSection.bind(null, 1),
|
nextEditor: nextPrevSection.bind(null, 1),
|
||||||
|
@ -195,7 +201,7 @@ function SourceEditor() {
|
||||||
cm.setPreprocessor((style.usercssData || {}).preprocessor);
|
cm.setPreprocessor((style.usercssData || {}).preprocessor);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function replaceStyle(newStyle) {
|
async function replaceStyle(newStyle, draft) {
|
||||||
dirty.clear('name');
|
dirty.clear('name');
|
||||||
const sameCode = newStyle.sourceCode === cm.getValue();
|
const sameCode = newStyle.sourceCode === cm.getValue();
|
||||||
if (sameCode) {
|
if (sameCode) {
|
||||||
|
@ -207,19 +213,26 @@ function SourceEditor() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await messageBoxProxy.confirm(t('styleUpdateDiscardChanges'))) {
|
if (draft || await messageBoxProxy.confirm(t('styleUpdateDiscardChanges'))) {
|
||||||
editor.useSavedStyle(newStyle);
|
editor.useSavedStyle(newStyle);
|
||||||
if (!sameCode) {
|
if (!sameCode) {
|
||||||
const cursor = cm.getCursor();
|
const si0 = draft && draft.si.cms[0];
|
||||||
|
const cursor = !si0 && cm.getCursor();
|
||||||
cm.setValue(style.sourceCode);
|
cm.setValue(style.sourceCode);
|
||||||
cm.setCursor(cursor);
|
if (si0) {
|
||||||
|
editor.applyScrollInfo(cm, si0);
|
||||||
|
} else {
|
||||||
|
cm.setCursor(cursor);
|
||||||
|
}
|
||||||
savedGeneration = cm.changeGeneration();
|
savedGeneration = cm.changeGeneration();
|
||||||
}
|
}
|
||||||
if (sameCode) {
|
if (sameCode) {
|
||||||
// the code is same but the environment is changed
|
// the code is same but the environment is changed
|
||||||
updateLivePreview();
|
updateLivePreview();
|
||||||
}
|
}
|
||||||
dirty.clear();
|
if (!draft) {
|
||||||
|
dirty.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
/* global $ $create animateElement focusAccessibility moveFocus */// dom.js
|
/* global $ $create animateElement focusAccessibility moveFocus */// dom.js
|
||||||
|
/* global clamp */// toolbox.js
|
||||||
/* global t */// localization.js
|
/* global t */// localization.js
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
@ -84,10 +85,6 @@ messageBox.show = async ({
|
||||||
messageBox._resolve = resolve;
|
messageBox._resolve = resolve;
|
||||||
});
|
});
|
||||||
|
|
||||||
function clamp(value, min, max) {
|
|
||||||
return Math.min(Math.max(value, min), max);
|
|
||||||
}
|
|
||||||
|
|
||||||
function initOwnListeners() {
|
function initOwnListeners() {
|
||||||
let listening = false;
|
let listening = false;
|
||||||
let offsetX = 0;
|
let offsetX = 0;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* global $$ $ $create focusAccessibility getEventKeyName moveFocus */// dom.js
|
/* global $$ $ $create focusAccessibility getEventKeyName moveFocus */// dom.js
|
||||||
/* global debounce */// toolbox.js
|
/* global clamp debounce */// toolbox.js
|
||||||
/* global t */// localization.js
|
/* global t */// localization.js
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
const key = isSelect ? 'selectedIndex' : 'valueAsNumber';
|
const key = isSelect ? 'selectedIndex' : 'valueAsNumber';
|
||||||
const old = el[key];
|
const old = el[key];
|
||||||
const rawVal = old + Math.sign(event.deltaY) * (el.step || 1);
|
const rawVal = old + Math.sign(event.deltaY) * (el.step || 1);
|
||||||
el[key] = Math.max(el.min || 0, Math.min(el.max || el.length - 1, rawVal));
|
el[key] = clamp(rawVal, el.min || 0, el.max || el.length - 1);
|
||||||
if (el[key] !== old) {
|
if (el[key] !== old) {
|
||||||
el.dispatchEvent(new Event('change', {bubbles: true}));
|
el.dispatchEvent(new Event('change', {bubbles: true}));
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,6 +99,7 @@
|
||||||
'editor.selectByTokens': true,
|
'editor.selectByTokens': true,
|
||||||
|
|
||||||
'editor.appliesToLineWidget': true, // show applies-to line widget on the editor
|
'editor.appliesToLineWidget': true, // show applies-to line widget on the editor
|
||||||
|
'editor.autosaveDraft': 10, // seconds
|
||||||
'editor.livePreview': true,
|
'editor.livePreview': true,
|
||||||
|
|
||||||
// show CSS colors as clickable colored rectangles
|
// show CSS colors as clickable colored rectangles
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
CHROME_POPUP_BORDER_BUG
|
CHROME_POPUP_BORDER_BUG
|
||||||
RX_META
|
RX_META
|
||||||
capitalize
|
capitalize
|
||||||
|
clamp
|
||||||
closeCurrentTab
|
closeCurrentTab
|
||||||
deepEqual
|
deepEqual
|
||||||
download
|
download
|
||||||
|
@ -132,6 +133,10 @@ if (FIREFOX || OPERA || VIVALDI) {
|
||||||
// (detecting FF57 by the feature it added, not navigator.ua which may be spoofed in about:config)
|
// (detecting FF57 by the feature it added, not navigator.ua which may be spoofed in about:config)
|
||||||
const openerTabIdSupported = (!FIREFOX || window.AbortController) && chrome.windows != null;
|
const openerTabIdSupported = (!FIREFOX || window.AbortController) && chrome.windows != null;
|
||||||
|
|
||||||
|
function clamp(value, min, max) {
|
||||||
|
return Math.min(Math.max(value, min), max);
|
||||||
|
}
|
||||||
|
|
||||||
function getOwnTab() {
|
function getOwnTab() {
|
||||||
return browser.tabs.getCurrent();
|
return browser.tabs.getCurrent();
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
OPERA
|
OPERA
|
||||||
URLS
|
URLS
|
||||||
capitalize
|
capitalize
|
||||||
|
clamp
|
||||||
ignoreChromeError
|
ignoreChromeError
|
||||||
openURL
|
openURL
|
||||||
*/// toolbox.js
|
*/// toolbox.js
|
||||||
|
@ -288,7 +289,7 @@ function enforceInputRange(element) {
|
||||||
if (type === 'input' && element.checkValidity()) {
|
if (type === 'input' && element.checkValidity()) {
|
||||||
doNotify();
|
doNotify();
|
||||||
} else if (type === 'change' && !element.checkValidity()) {
|
} else if (type === 'change' && !element.checkValidity()) {
|
||||||
element.value = Math.max(min, Math.min(max, Number(element.value)));
|
element.value = clamp(Number(element.value), min, max);
|
||||||
doNotify();
|
doNotify();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
FIREFOX
|
FIREFOX
|
||||||
URLS
|
URLS
|
||||||
capitalize
|
capitalize
|
||||||
|
clamp
|
||||||
getActiveTab
|
getActiveTab
|
||||||
isEmptyObj
|
isEmptyObj
|
||||||
*/// toolbox.js
|
*/// toolbox.js
|
||||||
|
@ -66,7 +67,7 @@ function onRuntimeMessage(msg) {
|
||||||
|
|
||||||
function setPopupWidth(_key, width) {
|
function setPopupWidth(_key, width) {
|
||||||
document.body.style.width =
|
document.body.style.width =
|
||||||
Math.max(200, Math.min(800, width)) + 'px';
|
clamp(width, 200, 800) + 'px';
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleSideBorders(_key, state) {
|
function toggleSideBorders(_key, state) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user