Просмотр исходного кода

editor: allow user to customize options

tophf 6 лет назад
Родитель
Сommit
ca4bacc9a9

+ 9 - 0
src/_locales/en/messages.yml

@@ -115,6 +115,12 @@ descCustomCSS:
   message: >-
     Custom CSS for options page and script installation page. If you are not
     sure what this is for, please do not edit it.
+descEditorOptions:
+  description: Description of editor options JSON section.
+  message: >-
+    Custom CodeMirror options in JSON object like {"indentUnit":2, "smartIndent":true}
+    however note that Violentmonkey doesn't support all of them.
+    Full list: https://codemirror.net/doc/manual.html#config
 editLabelMeta:
   description: Metadata section in settings tab of script editor.
   message: Custom metadata
@@ -492,6 +498,9 @@ msgSavedBlacklist:
 msgSavedCustomCSS:
   description: Message shown when custom CSS is saved.
   message: Custom style is updated.
+msgSavedEditorOptions:
+  description: Message shown when editor options are saved.
+  message: Editor options are updated.
 msgSavedScriptTemplate:
   description: Message shown when custom script template is saved.
   message: Custom script template is updated.

+ 1 - 42
src/background/utils/options.js

@@ -1,49 +1,8 @@
 import { initHooks, debounce, normalizeKeys } from '#/common';
 import { objectGet, objectSet } from '#/common/object';
+import defaults from '#/common/options-defaults';
 import { register } from './init';
 
-const defaults = {
-  isApplied: true,
-  autoUpdate: true,
-  // ignoreGrant: false,
-  lastUpdate: 0,
-  lastModified: 0,
-  showBadge: 'unique', // '' | 'unique' | 'total'
-  exportValues: true,
-  closeAfterInstall: false,
-  trackLocalFile: false,
-  autoReload: false,
-  features: null,
-  blacklist: null,
-  syncScriptStatus: true,
-  sync: null,
-  customCSS: null,
-  importSettings: true,
-  notifyUpdates: false,
-  version: null,
-  defaultInjectInto: 'page', // 'page' | 'auto',
-  filters: {
-    sort: 'exec',
-  },
-  editor: {
-    lineWrapping: false,
-    indentUnit: 2,
-  },
-  scriptTemplate: `\
-// ==UserScript==
-// @name        New script {{name}}
-// @namespace   Violentmonkey Scripts
-// @match       {{url}}
-// @grant       none
-// @version     1.0
-// @author      -
-// @description {{date}}
-// ==/UserScript==
-`,
-  // Enables automatic updates to the default template with new versions of VM
-  /** @type {?Boolean} this must be |null| for template-hook.js upgrade routine */
-  scriptTemplateEdited: null,
-};
 let changes = {};
 const hooks = initHooks();
 const callHooksLater = debounce(callHooks, 100);

+ 44 - 0
src/common/options-defaults.js

@@ -0,0 +1,44 @@
+export default {
+  isApplied: true,
+  autoUpdate: true,
+  // ignoreGrant: false,
+  lastUpdate: 0,
+  lastModified: 0,
+  showBadge: 'unique', // '' | 'unique' | 'total'
+  exportValues: true,
+  closeAfterInstall: false,
+  trackLocalFile: false,
+  autoReload: false,
+  features: null,
+  blacklist: null,
+  syncScriptStatus: true,
+  sync: null,
+  customCSS: null,
+  importSettings: true,
+  notifyUpdates: false,
+  version: null,
+  defaultInjectInto: 'page', // 'page' | 'auto',
+  filters: {
+    sort: 'exec',
+  },
+  editor: {
+    lineWrapping: false,
+    indentWithTabs: false,
+    indentUnit: 2,
+    undoDepth: 200,
+  },
+  scriptTemplate: `\
+// ==UserScript==
+// @name        New script {{name}}
+// @namespace   Violentmonkey Scripts
+// @match       {{url}}
+// @grant       none
+// @version     1.0
+// @author      -
+// @description {{date}}
+// ==/UserScript==
+`,
+  // Enables automatic updates to the default template with new versions of VM
+  /** @type {?Boolean} this must be |null| for template-hook.js upgrade routine */
+  scriptTemplateEdited: null,
+};

+ 33 - 6
src/common/ui/setting-text.vue

@@ -4,6 +4,7 @@
     spellcheck="false"
     v-model="value"
     :disabled="disabled"
+    :title="error"
     @change="onChange"
   />
 </template>
@@ -15,6 +16,7 @@ import hookSetting from '../hook-setting';
 export default {
   props: {
     name: String,
+    json: Boolean,
     disabled: Boolean,
     sync: {
       type: Boolean,
@@ -24,26 +26,51 @@ export default {
   data() {
     return {
       value: null,
+      jsonValue: null,
+      error: null,
     };
   },
   created() {
-    // XXX compatible with old data format
-    const handle = value => (Array.isArray(value) ? value.join('\n') : (value || ''));
+    const handle = this.json
+      ? (value => JSON.stringify(value, null, '  '))
+      // XXX compatible with old data format
+      : (value => (Array.isArray(value) ? value.join('\n') : value || ''));
     this.value = handle(options.get(this.name));
     this.revoke = hookSetting(this.name, (value) => {
       this.value = handle(value);
     });
+    if (this.json) this.$watch('value', this.parseJson);
   },
   beforeDestroy() {
     this.revoke();
   },
   methods: {
-    onChange() {
-      if (this.sync) {
-        options.set(this.name, this.value);
+    parseJson() {
+      try {
+        this.jsonValue = JSON.parse(this.value);
+        this.error = null;
+      } catch (e) {
+        this.error = e.message || e;
       }
-      this.$emit('change', this.value);
+    },
+    onChange() {
+      if (this.error) return;
+      const value = this.json ? this.jsonValue : this.value;
+      if (this.sync) options.set(this.name, value);
+      this.$emit('change', value);
     },
   },
 };
 </script>
+
+<style>
+  textarea {
+    &[title] {
+      border-color: #4004;
+      background: #f001;
+      &:focus {
+        border-color: #400c;
+      }
+    }
+  }
+</style>

+ 3 - 22
src/options/views/tab-settings/index.vue

@@ -52,21 +52,7 @@
       </button>
     </div>
     <div v-show="showAdvanced">
-      <section>
-        <h3 v-text="i18n('labelEditor')"></h3>
-        <div class="mb-1">
-          <label>
-            <setting-check name="editor.lineWrapping" />
-            <span v-text="i18n('labelLineWrapping')"></span>
-          </label>
-        </div>
-        <div class="mb-1">
-          <label>
-            <span class="mr-1" v-text="i18n('labelIndentUnit')"></span>
-            <input type="number" min="1" class="w-1" v-model="indentUnit" />
-          </label>
-        </div>
-      </section>
+      <vm-editor />
       <vm-template />
       <vm-blacklist />
       <vm-css />
@@ -83,6 +69,7 @@ import Icon from '#/common/ui/icon';
 import VmImport from './vm-import';
 import VmExport from './vm-export';
 import VmSync from './vm-sync';
+import VmEditor from './vm-editor';
 import VmTemplate from './vm-template';
 import VmBlacklist from './vm-blacklist';
 import VmCss from './vm-css';
@@ -101,13 +88,6 @@ const items = [
       return value === 'auto' ? 'auto' : 'page';
     },
   },
-  {
-    name: 'indentUnit',
-    key: 'editor.indentUnit',
-    normalize(value) {
-      return +value || 2;
-    },
-  },
 ];
 const settings = {
   showAdvanced: false,
@@ -122,6 +102,7 @@ export default {
     VmImport,
     VmExport,
     VmSync,
+    VmEditor,
     VmTemplate,
     VmBlacklist,
     VmCss,

+ 42 - 0
src/options/views/tab-settings/vm-editor.vue

@@ -0,0 +1,42 @@
+<template>
+  <section>
+    <h3 v-text="i18n('labelEditor')"></h3>
+    <p v-html="i18n('descEditorOptions')" />
+    <setting-text name="editor" ref="editor" :json="true" />
+    <button v-text="i18n('buttonSave')" @click="onSave"></button>
+    <button v-text="i18n('buttonReset')" @click="onReset"></button>
+  </section>
+</template>
+
+<script>
+import { i18n } from '#/common';
+import options from '#/common/options';
+import { showMessage } from '#/options/utils';
+import SettingText from '#/common/ui/setting-text';
+import defaults from '#/common/options-defaults';
+
+export default {
+  components: {
+    SettingText,
+  },
+  mounted() {
+    this.$refs.editor.$el.addEventListener('dblclick', this.toggleBoolean);
+  },
+  methods: {
+    onSave() {
+      const { jsonValue, error } = this.$refs.editor;
+      if (!error) options.set('editor', jsonValue);
+      showMessage({ text: error || i18n('msgSavedEditorOptions') });
+    },
+    onReset() {
+      options.set('editor', defaults.editor);
+    },
+    toggleBoolean(event) {
+      const el = event.target;
+      const selection = el.value.slice(el.selectionStart, el.selectionEnd);
+      const toggled = selection === 'false' && 'true' || selection === 'true' && 'false';
+      if (toggled) document.execCommand('insertText', false, toggled);
+    },
+  },
+};
+</script>