* parserlib: fast section extraction, tweaks and speedups
* csslint: "simple-not" rule
* csslint: enable and fix "selector-newline" rule
* simplify db: resolve with result
* simplify download()
* remove noCode param as it wastes more time/memory on copying
* styleManager: switch style<->data names to reflect their actual contents
* inline method bodies to avoid indirection and enable better autocomplete/hint/jump support in IDE
* upgrade getEventKeyName to handle mouse clicks
* don't trust location.href as it hides text fragment
* getAllKeys is implemented since Chrome48, FF44
* allow recoverable css errors + async'ify usercss.js
* openManage: unminimize windows
* remove the obsolete Chrome pre-65 workaround
* fix temporal dead zone in apply.js
* ff bug workaround for simple editor window
* consistent window scrolling in scrollToEditor and jumpToPos
* rework waitForSelector and collapsible <details>
* blank paint frame workaround for new Chrome
* extract stuff from edit.js and load on demand
* simplify regexpTester::isShown
* move MozDocMapper to sections-util.js
* extract fitSelectBox()
* initialize router earlier
* use helpPopup.close()
* fix autofocus in popups, follow-up to 5bb1b5ef
* clone objects in prefs.get() + cosmetics
* reuse getAll result for INC
		
	
			
		
			
				
	
	
		
			172 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			172 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* global $ */// dom.js
 | 
						|
/* global CodeMirror */
 | 
						|
/* global editor */
 | 
						|
/* global prefs */
 | 
						|
/* global t */// localization.js
 | 
						|
'use strict';
 | 
						|
 | 
						|
(() => {
 | 
						|
  // CodeMirror miserably fails on keyMap='' so let's ensure it's not
 | 
						|
  if (!prefs.get('editor.keyMap')) {
 | 
						|
    prefs.reset('editor.keyMap');
 | 
						|
  }
 | 
						|
 | 
						|
  const defaults = {
 | 
						|
    autoCloseBrackets: prefs.get('editor.autoCloseBrackets'),
 | 
						|
    mode: 'css',
 | 
						|
    lineNumbers: true,
 | 
						|
    lineWrapping: prefs.get('editor.lineWrapping'),
 | 
						|
    foldGutter: true,
 | 
						|
    gutters: [
 | 
						|
      'CodeMirror-linenumbers',
 | 
						|
      'CodeMirror-foldgutter',
 | 
						|
      ...(prefs.get('editor.linter') ? ['CodeMirror-lint-markers'] : []),
 | 
						|
    ],
 | 
						|
    matchBrackets: true,
 | 
						|
    hintOptions: {},
 | 
						|
    lintReportDelay: prefs.get('editor.lintReportDelay'),
 | 
						|
    styleActiveLine: true,
 | 
						|
    theme: prefs.get('editor.theme'),
 | 
						|
    keyMap: prefs.get('editor.keyMap'),
 | 
						|
    extraKeys: Object.assign(CodeMirror.defaults.extraKeys || {}, {
 | 
						|
      // independent of current keyMap; some are implemented only for the edit page
 | 
						|
      'Alt-Enter': 'toggleStyle',
 | 
						|
      'Alt-PageDown': 'nextEditor',
 | 
						|
      'Alt-PageUp': 'prevEditor',
 | 
						|
      'Ctrl-Pause': 'toggleEditorFocus',
 | 
						|
    }),
 | 
						|
    maxHighlightLength: 100e3,
 | 
						|
  };
 | 
						|
 | 
						|
  Object.assign(CodeMirror.defaults, defaults, prefs.get('editor.options'));
 | 
						|
 | 
						|
  // Adding hotkeys to some keymaps except 'basic' which is primitive by design
 | 
						|
  require(Object.values(typeof editor === 'object' && editor.lazyKeymaps || {}), () => {
 | 
						|
    const KM = CodeMirror.keyMap;
 | 
						|
    const extras = Object.values(CodeMirror.defaults.extraKeys);
 | 
						|
    if (!extras.includes('jumpToLine')) {
 | 
						|
      KM.sublime['Ctrl-G'] = 'jumpToLine';
 | 
						|
      KM.emacsy['Ctrl-G'] = 'jumpToLine';
 | 
						|
      KM.pcDefault['Ctrl-J'] = 'jumpToLine';
 | 
						|
      KM.macDefault['Cmd-J'] = 'jumpToLine';
 | 
						|
    }
 | 
						|
    if (!extras.includes('autocomplete')) {
 | 
						|
      // will be used by 'sublime' on PC via fallthrough
 | 
						|
      KM.pcDefault['Ctrl-Space'] = 'autocomplete';
 | 
						|
      // OSX uses Ctrl-Space and Cmd-Space for something else
 | 
						|
      KM.macDefault['Alt-Space'] = 'autocomplete';
 | 
						|
      // copied from 'emacs' keymap
 | 
						|
      KM.emacsy['Alt-/'] = 'autocomplete';
 | 
						|
      // 'vim' and 'emacs' define their own autocomplete hotkeys
 | 
						|
    }
 | 
						|
    if (!extras.includes('blockComment')) {
 | 
						|
      KM.sublime['Shift-Ctrl-/'] = 'commentSelection';
 | 
						|
    }
 | 
						|
    if (navigator.appVersion.includes('Windows')) {
 | 
						|
      // 'pcDefault' keymap on Windows should have F3/Shift-F3/Ctrl-R
 | 
						|
      if (!extras.includes('findNext')) KM.pcDefault['F3'] = 'findNext';
 | 
						|
      if (!extras.includes('findPrev')) KM.pcDefault['Shift-F3'] = 'findPrev';
 | 
						|
      if (!extras.includes('replace')) KM.pcDefault['Ctrl-R'] = 'replace';
 | 
						|
      // try to remap non-interceptable (Shift-)Ctrl-N/T/W hotkeys
 | 
						|
      // Note: modifier order in CodeMirror is S-C-A
 | 
						|
      for (const char of ['N', 'T', 'W']) {
 | 
						|
        for (const remap of [
 | 
						|
          {from: 'Ctrl-', to: ['Alt-', 'Ctrl-Alt-']},
 | 
						|
          {from: 'Shift-Ctrl-', to: ['Ctrl-Alt-', 'Shift-Ctrl-Alt-']},
 | 
						|
        ]) {
 | 
						|
          const oldKey = remap.from + char;
 | 
						|
          for (const km of Object.values(KM)) {
 | 
						|
            const command = km[oldKey];
 | 
						|
            if (!command) continue;
 | 
						|
            for (const newMod of remap.to) {
 | 
						|
              const newKey = newMod + char;
 | 
						|
              if (newKey in km) continue;
 | 
						|
              km[newKey] = command;
 | 
						|
              delete km[oldKey];
 | 
						|
              break;
 | 
						|
            }
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  });
 | 
						|
 | 
						|
  const cssMime = CodeMirror.mimeModes['text/css'];
 | 
						|
  Object.assign(cssMime.propertyKeywords, {
 | 
						|
    'content-visibility': true,
 | 
						|
    'overflow-anchor': true,
 | 
						|
    'overscroll-behavior': true,
 | 
						|
  });
 | 
						|
  Object.assign(cssMime.colorKeywords, {
 | 
						|
    'darkgrey': true,
 | 
						|
    'darkslategrey': true,
 | 
						|
    'dimgrey': true,
 | 
						|
    'lightgrey': true,
 | 
						|
    'lightslategrey': true,
 | 
						|
    'slategrey': true,
 | 
						|
  });
 | 
						|
  Object.assign(cssMime.valueKeywords, {
 | 
						|
    'blur': true,
 | 
						|
    'brightness': true,
 | 
						|
    'contrast': true,
 | 
						|
    'cubic-bezier': true,
 | 
						|
    'drop-shadow': true,
 | 
						|
    'fit-content': true,
 | 
						|
    'hue-rotate': true,
 | 
						|
    'saturate': true,
 | 
						|
    'sepia': true,
 | 
						|
  });
 | 
						|
  Object.assign(CodeMirror.prototype, {
 | 
						|
    /**
 | 
						|
     * @param {'less' | 'stylus' | ?} [pp] - any value besides `less` or `stylus` sets `css` mode
 | 
						|
     * @param {boolean} [force]
 | 
						|
     */
 | 
						|
    setPreprocessor(pp, force) {
 | 
						|
      const name = pp === 'less' ? 'text/x-less' : pp === 'stylus' ? pp : 'css';
 | 
						|
      const m = this.doc.mode;
 | 
						|
      if (force || (m.helperType ? m.helperType !== pp : m.name !== name)) {
 | 
						|
        this.setOption('mode', name);
 | 
						|
      }
 | 
						|
    },
 | 
						|
    /** Superfast GC-friendly check that runs until the first non-space line */
 | 
						|
    isBlank() {
 | 
						|
      let filled;
 | 
						|
      this.eachLine(({text}) => (filled = text && /\S/.test(text)));
 | 
						|
      return !filled;
 | 
						|
    },
 | 
						|
    /**
 | 
						|
     * Sets cursor and centers it in view if `pos` was out of view
 | 
						|
     * @param {CodeMirror.Pos} pos
 | 
						|
     * @param {CodeMirror.Pos} [end] - will set a selection from `pos` to `end`
 | 
						|
     */
 | 
						|
    jumpToPos(pos, end = pos) {
 | 
						|
      const {curOp} = this;
 | 
						|
      if (!curOp) this.startOperation();
 | 
						|
      const y = this.cursorCoords(pos, 'window').top;
 | 
						|
      const rect = this.display.wrapper.getBoundingClientRect();
 | 
						|
      // case 1) outside of CM viewport or too close to edge so tell CM to render a new viewport
 | 
						|
      if (y < rect.top + 50 || y > rect.bottom - 100) {
 | 
						|
        this.scrollIntoView(pos, rect.height / 2);
 | 
						|
      // case 2) inside CM viewport but outside of window viewport so just scroll the window
 | 
						|
      } else if (y < 0 || y > innerHeight) {
 | 
						|
        editor.scrollToEditor(this);
 | 
						|
      }
 | 
						|
      // Using prototype since our bookmark patch sets cm.setSelection to jumpToPos
 | 
						|
      CodeMirror.prototype.setSelection.call(this, pos, end);
 | 
						|
      if (!curOp) this.endOperation();
 | 
						|
    },
 | 
						|
  });
 | 
						|
 | 
						|
  Object.assign(CodeMirror.commands, {
 | 
						|
    jumpToLine(cm) {
 | 
						|
      const cur = cm.getCursor();
 | 
						|
      const oldDialog = $('.CodeMirror-dialog', cm.display.wrapper);
 | 
						|
      if (oldDialog) cm.focus(); // close the currently opened minidialog
 | 
						|
      cm.openDialog(t.template.jumpToLine.cloneNode(true), str => {
 | 
						|
        const [line, ch] = str.match(/^\s*(\d+)(?:\s*:\s*(\d+))?\s*$|$/);
 | 
						|
        if (line) cm.setCursor(line - 1, ch ? ch - 1 : cur.ch);
 | 
						|
      }, {value: cur.line + 1});
 | 
						|
    },
 | 
						|
  });
 | 
						|
})();
 |