Browse Source

fix: toggle save/reset buttons depending on value (#1114)

* fix: toggle save/reset buttons depending on value

* simplify
tophf 4 years ago
parent
commit
e39f50f153

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

@@ -67,15 +67,9 @@ buttonRestore:
 buttonSave:
   description: Button to save modifications of a script.
   message: Save
-buttonSaveBlacklist:
-  description: Button to save global blacklist.
-  message: Save
 buttonSaveClose:
   description: Button to save modifications of a script and then close the editing page.
   message: Save & Close
-buttonSaveCustomCSS:
-  description: Label for button to save custom CSS.
-  message: Save
 buttonShowEditorState:
   description: Button to show the list of currently used CodeMirror options.
   message: Show editor state

+ 1 - 6
src/common/hook-setting.js

@@ -12,13 +12,8 @@ options.hook((data) => {
   });
 });
 
-/**
- option.get() is called even if it's not ready (a null or default value will be used),
- which shouldn't happen usually as we retrieve the options in browser.js the first thing,
- so either do `await options.ready` beforehand or handle the empty/default value inside update()
-*/
 export default function hookSetting(key, update) {
-  update(options.get(key));
+  options.ready.then(() => update(options.get(key)));
   const list = hooks[key] || (hooks[key] = []);
   list.push(update);
   return () => {

+ 50 - 22
src/common/ui/setting-text.vue

@@ -1,17 +1,26 @@
 <template>
-  <textarea
-    class="monospace-font"
-    :class="{'has-error': error}"
-    spellcheck="false"
-    v-model="value"
-    :disabled="disabled"
-    :title="error"
-    @change="onChange"
-  />
+  <div>
+    <textarea
+      class="monospace-font"
+      :class="{'has-error': error}"
+      spellcheck="false"
+      v-model="value"
+      :disabled="disabled"
+      :title="error"
+      @change="onChange"
+    />
+    <button v-if="hasSave" v-text="i18n('buttonSave')" @click="onSave"
+            :disabled="disabled || !canSave"/>
+    <button v-if="hasReset" v-text="i18n('buttonReset')" @click="onReset"
+            :disabled="disabled || !canReset"/>
+    <slot/>
+  </div>
 </template>
 
 <script>
+import { deepEqual, objectGet } from '../object';
 import options from '../options';
+import defaults from '../options-defaults';
 import hookSetting from '../hook-setting';
 
 export default {
@@ -19,16 +28,19 @@ export default {
     name: String,
     json: Boolean,
     disabled: Boolean,
-    sync: {
+    hasSave: {
       type: Boolean,
       default: true,
     },
+    hasReset: Boolean,
   },
   data() {
     return {
       value: null,
       jsonValue: null,
       error: null,
+      canSave: null,
+      canReset: null,
     };
   },
   created() {
@@ -36,26 +48,42 @@ export default {
       ? (value => JSON.stringify(value, null, '  '))
       // XXX compatible with old data format
       : (value => (Array.isArray(value) ? value.join('\n') : value || ''));
-    this.revoke = hookSetting(this.name, val => { this.value = handle(val); });
-    if (this.json) this.$watch('value', this.parseJson);
+    this.revoke = hookSetting(this.name, val => {
+      this.savedValue = val;
+      val = handle(val);
+      if (this.value !== val) this.value = val; // will call onInput
+      else this.onInput(val);
+    });
+    this.defaultValue = objectGet(defaults, this.name);
+    this.$watch('value', this.onInput);
   },
   beforeDestroy() {
     this.revoke();
   },
   methods: {
-    parseJson() {
-      try {
-        this.jsonValue = JSON.parse(this.value);
-        this.error = null;
-      } catch (e) {
-        this.error = e.message || e;
+    onInput(val) {
+      if (this.json) {
+        try {
+          val = JSON.parse(val);
+          this.jsonValue = val;
+          this.error = null;
+        } catch (e) {
+          this.error = e.message || e;
+        }
       }
+      this.canSave = this.hasSave && !this.error && !deepEqual(val, this.savedValue || '');
+      this.canReset = this.hasReset && !deepEqual(val, this.defaultValue || '');
     },
     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);
+      if (!this.error) {
+        options.set(this.name, this.json ? this.jsonValue : this.value);
+      }
+    },
+    onSave() {
+      this.$emit('save');
+    },
+    onReset() {
+      options.set(this.name, this.defaultValue);
     },
   },
 };

+ 3 - 6
src/options/views/tab-settings/vm-blacklist.vue

@@ -5,14 +5,12 @@
       {{i18n('descBlacklist')}}
       <a href="https://violentmonkey.github.io/posts/smart-rules-for-blacklist/#blacklist-patterns" target="_blank" rel="noopener noreferrer" v-text="i18n('learnBlacklist')"></a>
     </p>
-    <setting-text name="blacklist" ref="blacklist" />
-    <button v-text="i18n('buttonSaveBlacklist')" @click="onSave"></button>
+    <setting-text name="blacklist" @save="onSave"/>
   </section>
 </template>
 
 <script>
-import { i18n, sendCmd } from '#/common';
-import options from '#/common/options';
+import { sendCmd } from '#/common';
 import { showMessage } from '#/options/utils';
 import SettingText from '#/common/ui/setting-text';
 
@@ -22,8 +20,7 @@ export default {
   },
   methods: {
     onSave() {
-      options.set('blacklist', this.$refs.blacklist.value);
-      showMessage({ text: i18n('msgSavedBlacklist') });
+      showMessage({ text: this.i18n('msgSavedBlacklist') });
       sendCmd('BlacklistReset');
     },
   },

+ 2 - 6
src/options/views/tab-settings/vm-css.vue

@@ -2,14 +2,11 @@
   <section>
     <h3 v-text="i18n('labelCustomCSS')"></h3>
     <p v-html="i18n('descCustomCSS')"></p>
-    <setting-text name="customCSS" ref="css" />
-    <button v-text="i18n('buttonSaveCustomCSS')" @click="onSave"></button>
+    <setting-text name="customCSS" @save="onSave"/>
   </section>
 </template>
 
 <script>
-import { i18n } from '#/common';
-import options from '#/common/options';
 import { showMessage } from '#/options/utils';
 import SettingText from '#/common/ui/setting-text';
 
@@ -19,8 +16,7 @@ export default {
   },
   methods: {
     onSave() {
-      options.set('customCSS', this.$refs.css.value);
-      showMessage({ text: i18n('msgSavedCustomCSS') });
+      showMessage({ text: this.i18n('msgSavedCustomCSS') });
     },
   },
 };

+ 6 - 12
src/options/views/tab-settings/vm-editor.vue

@@ -2,20 +2,17 @@
   <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>
-    <button v-text="i18n('buttonShowEditorState')" @click="toggleStateHint" />
+    <setting-text name="editor" ref="editor" :json="true" :has-reset="true" @save="onSave">
+      <button v-text="i18n('buttonShowEditorState')" @click="toggleStateHint"/>
+    </setting-text>
     <pre v-text="hint" class="monospace-font dim-hint" />
   </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 {
   data() {
@@ -29,12 +26,7 @@ export default {
   },
   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);
+      showMessage({ text: this.$refs.editor.error || this.i18n('msgSavedEditorOptions') });
     },
     toggleBoolean(event) {
       const el = /** @type HTMLTextAreaElement */ event.target;
@@ -44,6 +36,8 @@ export default {
       if (toggled && !document.execCommand('insertText', false, toggled)) {
         el.value = value.slice(0, start) + toggled + value.slice(end);
         el.setSelectionRange(start + toggled.length, start + toggled.length);
+        el.dispatchEvent(new Event('input'));
+        el.onblur = () => el.dispatchEvent(new Event('change'));
       }
     },
     async toggleStateHint() {

+ 2 - 13
src/options/views/tab-settings/vm-template.vue

@@ -1,15 +1,11 @@
 <template>
   <section>
     <h3 v-text="i18n('labelScriptTemplate')"></h3>
-    <setting-text name="scriptTemplate" ref="template" />
-    <button v-text="i18n('buttonSave')" @click="onSave"></button>
-    <button v-text="i18n('buttonReset')" @click="onReset"></button>
+    <setting-text name="scriptTemplate" @save="onSave"/>
   </section>
 </template>
 
 <script>
-import { i18n } from '#/common';
-import options from '#/common/options';
 import { showMessage } from '#/options/utils';
 import SettingText from '#/common/ui/setting-text';
 
@@ -19,14 +15,7 @@ export default {
   },
   methods: {
     onSave() {
-      this.setTemplate(this.$refs.template.value);
-    },
-    onReset() {
-      this.setTemplate('');
-    },
-    setTemplate(value) {
-      options.set('scriptTemplate', value);
-      showMessage({ text: i18n('msgSavedScriptTemplate') });
+      showMessage({ text: this.i18n('msgSavedScriptTemplate') });
     },
   },
 };