| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387 |
- /* global $ $create messageBoxProxy waitForSheet */// dom.js
- /* global msg API */// msg.js
- /* global CodeMirror */
- /* global SectionsEditor */
- /* global SourceEditor */
- /* global baseInit */
- /* global clipString createHotkeyInput helpPopup */// util.js
- /* global closeCurrentTab deepEqual sessionStore tryJSONparse */// toolbox.js
- /* global cmFactory */
- /* global editor */
- /* global linterMan */
- /* global prefs */
- /* global t */// localization.js
- /* global StyleSettings */// settings.js
- 'use strict';
- //#region init
- baseInit.ready.then(async () => {
- await waitForSheet();
- (editor.isUsercss ? SourceEditor : SectionsEditor)();
- StyleSettings(editor);
- await editor.ready;
- editor.ready = true;
- editor.dirty.onChange(editor.updateDirty);
- prefs.subscribe('editor.linter', (key, value) => {
- document.body.classList.toggle('linter-disabled', value === '');
- linterMan.run();
- });
- // enabling after init to prevent flash of validation failure on an empty name
- $('#name').required = !editor.isUsercss;
- $('#save-button').onclick = editor.save;
- const elSec = $('#sections-list');
- // editor.toc.expanded pref isn't saved in compact-layout so prefs.subscribe won't work
- if (elSec.open) editor.updateToc();
- // and we also toggle `open` directly in other places e.g. in detectLayout()
- new MutationObserver(() => elSec.open && editor.updateToc())
- .observe(elSec, {attributes: true, attributeFilter: ['open']});
- $('#toc').onclick = e =>
- editor.jumpToEditor([...$('#toc').children].indexOf(e.target));
- $('#keyMap-help').onclick = () =>
- require(['/edit/show-keymap-help'], () => showKeymapHelp()); /* global showKeymapHelp */
- $('#linter-settings').onclick = () =>
- require(['/edit/linter-dialogs'], () => linterMan.showLintConfig());
- $('#lint-help').onclick = () =>
- require(['/edit/linter-dialogs'], () => linterMan.showLintHelp());
- require([
- '/edit/autocomplete',
- '/edit/global-search',
- ]);
- });
- //#endregion
- //#region events
- const IGNORE_UPDATE_REASONS = [
- 'editPreview',
- 'editPreviewEnd',
- 'editSave',
- // https://github.com/openstyles/stylus/issues/807 is closed without fix
- // 'config,
- ];
- msg.onExtension(request => {
- const {style} = request;
- switch (request.method) {
- case 'styleUpdated':
- if (editor.style.id === style.id && !IGNORE_UPDATE_REASONS.includes(request.reason)) {
- if (request.reason === 'toggle') {
- editor.emit('styleToggled', request.style);
- } else {
- API.styles.get(request.style.id)
- .then(style => {
- editor.emit('styleChange', style, request.reason);
- });
- }
- }
- break;
- case 'styleDeleted':
- if (editor.style.id === style.id) {
- closeCurrentTab();
- }
- break;
- case 'editDeleteText':
- document.execCommand('delete');
- break;
- }
- });
- window.on('beforeunload', e => {
- let pos;
- if (editor.isWindowed &&
- document.visibilityState === 'visible' &&
- prefs.get('openEditInWindow') &&
- ( // only if not maximized
- screenX > 0 || outerWidth < screen.availWidth ||
- screenY > 0 || outerHeight < screen.availHeight ||
- screenX <= -10 || outerWidth >= screen.availWidth + 10 ||
- screenY <= -10 || outerHeight >= screen.availHeight + 10
- )
- ) {
- pos = {
- left: screenX,
- top: screenY,
- width: outerWidth,
- height: outerHeight,
- };
- prefs.set('windowPosition', pos);
- }
- sessionStore.windowPos = JSON.stringify(pos || {});
- sessionStore['editorScrollInfo' + editor.style.id] = JSON.stringify({
- 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;
- if (activeElement) {
- // blurring triggers 'change' or 'input' event if needed
- activeElement.blur();
- // refocus if unloading was canceled
- setTimeout(() => activeElement.focus());
- }
- if (editor.dirty.isDirty()) {
- // neither confirm() nor custom messages work in modern browsers but just in case
- e.returnValue = t('styleChangesNotSaved');
- }
- });
- //#endregion
- //#region editor methods
- (() => {
- const toc = [];
- const {dirty} = editor;
- let {style} = editor;
- let wasDirty = false;
- Object.defineProperties(editor, {
- scrollInfo: {
- get: () => style.id && tryJSONparse(sessionStore['editorScrollInfo' + style.id]) || {},
- },
- style: {
- get: () => style,
- set: val => (style = val),
- },
- });
- /** @namespace Editor */
- Object.assign(editor, {
- applyScrollInfo(cm, si = (editor.scrollInfo.cms || [])[0]) {
- if (si && si.sel) {
- const bmOpts = {sublimeBookmark: true, clearWhenEmpty: false}; // copied from sublime.js
- cm.operation(() => {
- cm.setSelections(...si.sel, {scroll: false});
- cm.scrollIntoView(cm.getCursor(), si.parentHeight / 2);
- cm.state.sublimeBookmarks = si.bookmarks.map(b => cm.markText(b.from, b.to, bmOpts));
- });
- }
- },
- toggleStyle(enabled = style.enabled) {
- $('#enabled').checked = enabled;
- editor.updateEnabledness(enabled);
- },
- updateDirty() {
- const isDirty = dirty.isDirty();
- if (wasDirty !== isDirty) {
- wasDirty = isDirty;
- document.body.classList.toggle('dirty', isDirty);
- $('#save-button').disabled = !isDirty;
- }
- editor.updateTitle();
- },
- updateEnabledness(enabled) {
- dirty.modify('enabled', style.enabled, enabled);
- style.enabled = enabled;
- editor.updateLivePreview();
- },
- updateName(isUserInput) {
- if (!editor) return;
- if (isUserInput) {
- const {value} = $('#name');
- dirty.modify('name', style[editor.nameTarget] || style.name, value);
- style[editor.nameTarget] = value;
- }
- editor.updateTitle();
- },
- updateToc(added = editor.sections) {
- if (!toc.el) {
- toc.el = $('#toc');
- toc.elDetails = toc.el.closest('details');
- }
- if (!toc.elDetails.open) return;
- const {sections} = editor;
- const first = sections.indexOf(added[0]);
- const elFirst = toc.el.children[first];
- if (first >= 0 && (!added.focus || !elFirst)) {
- for (let el = elFirst, i = first; i < sections.length; i++) {
- const entry = sections[i].tocEntry;
- if (!deepEqual(entry, toc[i])) {
- if (!el) el = toc.el.appendChild($create('li', {tabIndex: 0}));
- el.tabIndex = entry.removed ? -1 : 0;
- toc[i] = Object.assign({}, entry);
- const s = el.textContent = clipString(entry.label) || (
- entry.target == null
- ? t('appliesToEverything')
- : clipString(entry.target) + (entry.numTargets > 1 ? ', ...' : ''));
- if (s.length > 30) el.title = s;
- }
- el = el.nextElementSibling;
- }
- }
- while (toc.length > sections.length) {
- toc.el.lastElementChild.remove();
- toc.length--;
- }
- if (added.focus) {
- const cls = 'current';
- const old = $('.' + cls, toc.el);
- const el = elFirst || toc.el.children[first];
- if (old && old !== el) old.classList.remove(cls);
- el.classList.add(cls);
- }
- },
- });
- })();
- //#endregion
- //#region editor livePreview
- editor.livePreview = (() => {
- let data;
- let port;
- let preprocess;
- let enabled = prefs.get('editor.livePreview');
- prefs.subscribe('editor.livePreview', (key, value) => {
- if (!value) {
- if (port) {
- port.disconnect();
- port = null;
- }
- } else if (data && data.id && (data.enabled || editor.dirty.has('enabled'))) {
- createPreviewer();
- updatePreviewer(data);
- }
- enabled = value;
- });
- return {
- /**
- * @param {Function} [fn] - preprocessor
- */
- init(fn) {
- preprocess = fn;
- },
- update(newData) {
- data = newData;
- if (!port) {
- if (!data.id || !data.enabled || !enabled) {
- return;
- }
- createPreviewer();
- }
- updatePreviewer(data);
- },
- };
- function createPreviewer() {
- port = chrome.runtime.connect({name: 'livePreview'});
- port.onDisconnect.addListener(err => {
- throw err;
- });
- }
- async function updatePreviewer(data) {
- const errorContainer = $('#preview-errors');
- try {
- port.postMessage(preprocess ? await preprocess(data) : data);
- errorContainer.classList.add('hidden');
- } catch (err) {
- if (Array.isArray(err)) {
- err = err.join('\n');
- } else if (err && err.index != null) {
- // FIXME: this would fail if editors[0].getValue() !== data.sourceCode
- const pos = editor.getEditors()[0].posFromIndex(err.index);
- err.message = `${pos.line}:${pos.ch} ${err.message || err}`;
- }
- errorContainer.classList.remove('hidden');
- errorContainer.onclick = () => {
- messageBoxProxy.alert(err.message || `${err}`, 'pre');
- };
- }
- }
- })();
- //#endregion
- //#region colorpickerHelper
- (async function colorpickerHelper() {
- prefs.subscribe('editor.colorpicker.hotkey', (id, hotkey) => {
- CodeMirror.commands.colorpicker = invokeColorpicker;
- const extraKeys = CodeMirror.defaults.extraKeys;
- for (const key in extraKeys) {
- if (extraKeys[key] === 'colorpicker') {
- delete extraKeys[key];
- break;
- }
- }
- if (hotkey) {
- extraKeys[hotkey] = 'colorpicker';
- }
- });
- prefs.subscribe('editor.colorpicker', (id, enabled) => {
- const defaults = CodeMirror.defaults;
- const keyName = prefs.get('editor.colorpicker.hotkey');
- defaults.colorpicker = enabled;
- if (enabled) {
- if (keyName) {
- CodeMirror.commands.colorpicker = invokeColorpicker;
- defaults.extraKeys = defaults.extraKeys || {};
- defaults.extraKeys[keyName] = 'colorpicker';
- }
- defaults.colorpicker = {
- tooltip: t('colorpickerTooltip'),
- popup: {
- tooltipForSwitcher: t('colorpickerSwitchFormatTooltip'),
- paletteLine: t('numberedLine'),
- paletteHint: t('colorpickerPaletteHint'),
- hexUppercase: prefs.get('editor.colorpicker.hexUppercase'),
- embedderCallback: state => {
- ['hexUppercase', 'color']
- .filter(name => state[name] !== prefs.get('editor.colorpicker.' + name))
- .forEach(name => prefs.set('editor.colorpicker.' + name, state[name]));
- },
- get maxHeight() {
- return prefs.get('editor.colorpicker.maxHeight');
- },
- set maxHeight(h) {
- prefs.set('editor.colorpicker.maxHeight', h);
- },
- },
- };
- } else {
- if (defaults.extraKeys) {
- delete defaults.extraKeys[keyName];
- }
- }
- cmFactory.globalSetOption('colorpicker', defaults.colorpicker);
- }, {runNow: true});
- await baseInit.domReady;
- $('#colorpicker-settings').onclick = function (event) {
- event.preventDefault();
- const input = createHotkeyInput('editor.colorpicker.hotkey', {onDone: () => helpPopup.close()});
- const popup = helpPopup.show(t('helpKeyMapHotkey'), input);
- const bounds = this.getBoundingClientRect();
- popup.style.left = bounds.right + 10 + 'px';
- popup.style.top = bounds.top - popup.clientHeight / 2 + 'px';
- popup.style.right = 'auto';
- $('input', popup).focus();
- };
- function invokeColorpicker(cm) {
- cm.state.colorpicker.openPopup(prefs.get('editor.colorpicker.color'));
- }
- })();
- //#endregion
|