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

feat: replace prompt and confirm with custom message box

Gerald 8 лет назад
Родитель
Сommit
aa684eff2a

+ 0 - 21
src/options/style.css

@@ -364,27 +364,6 @@ code {
     }
   }
 }
-.message {
-  position: absolute;
-  width: 14rem;
-  top: 0;
-  left: 50%;
-  margin-left: -7rem;
-  padding: 1rem;
-  z-index: 10;
-  background: white;
-  border-bottom-left-radius: .2rem;
-  border-bottom-right-radius: .2rem;
-  box-shadow: 0 0 .2rem rgba(0,0,0,.2);
-}
-.message-enter-active,
-.message-leave-active {
-  transition: transform .5s;
-}
-.message-enter,
-.message-leave-active {
-  transform: translateY(-120%);
-}
 .icon {
   width: 1rem;
   height: 1rem;

+ 9 - 6
src/options/utils/index.js

@@ -19,11 +19,14 @@ function initMessage() {
   }).$mount(el);
 }
 
-export function showMessage(data) {
+export function showMessage(options) {
   initMessage();
-  store.messages.push(data);
-  setTimeout(() => {
-    const i = store.messages.indexOf(data);
-    if (i >= 0) store.messages.splice(i, 1);
-  }, 2000);
+  const message = Object.assign({}, options, !options.buttons && {
+    onInit(vm) {
+      setTimeout(() => {
+        vm.$emit('dismiss');
+      }, 2000);
+    },
+  });
+  store.messages.push(message);
 }

+ 5 - 5
src/options/views/confirm.vue

@@ -20,7 +20,7 @@
         <button v-text="i18n('buttonClose')" @click="close"></button>
       </div>
       <h1><span v-text="i18n('labelInstall')"></span> - <span v-text="i18n('extName')"></span></h1>
-      <div class="ellipsis confirm-url" :title="query.url" v-text="query.url"></div>
+      <div class="ellipsis confirm-url" :title="query.u" v-text="query.u"></div>
       <div class="ellipsis confirm-msg" v-text="message"></div>
     </div>
     <div class="frame-block flex-auto p-rel">
@@ -81,7 +81,7 @@ export default {
       this.installable = false;
       const { code: oldCode } = this;
       return this.getScript(this.query.u)
-      .then((code) => {
+      .then(code => {
         if (changedOnly && oldCode === code) return Promise.reject();
         this.code = code;
       });
@@ -91,7 +91,7 @@ export default {
         cmd: 'ParseMeta',
         data: this.code,
       })
-      .then((script) => {
+      .then(script => {
         const urls = Object.keys(script.resources)
         .map(key => script.resources[key]);
         const length = script.require.length + urls.length;
@@ -122,7 +122,7 @@ export default {
       .then(() => {
         this.message = this.i18n('msgLoadedData');
         this.installable = true;
-      }, (err) => {
+      }, err => {
         this.message = this.i18n('msgErrorLoadingDependency', [err]);
         return Promise.reject();
       });
@@ -134,7 +134,7 @@ export default {
       return request(url, {
         responseType: isBlob ? 'blob' : null,
       })
-      .then(data => {
+      .then(({ data }) => {
         if (!isBlob) return data;
         return new Promise(resolve => {
           const reader = new FileReader();

+ 21 - 5
src/options/views/edit.vue

@@ -123,7 +123,7 @@
 
 <script>
 import CodeMirror from 'codemirror';
-import { i18n, debounce, sendMessage } from 'src/common';
+import { i18n, debounce, sendMessage, noop } from 'src/common';
 import { showMessage } from '../utils';
 import VmCode from './code';
 
@@ -307,14 +307,30 @@ export default {
       .then((script) => {
         this.script = script;
         this.canSave = false;
-      }, (err) => {
+      }, err => {
         showMessage({ text: err });
       });
     },
     close() {
-      if (!this.canSave || confirm(i18n('confirmNotSaved'))) {
-        this.$emit('close');
-      }
+      (this.canSave ? Promise.reject() : Promise.resolve())
+      .catch(() => new Promise((resolve, reject) => {
+        showMessage({
+          input: false,
+          text: i18n('confirmNotSaved'),
+          buttons: [
+            {
+              text: 'OK',
+              onClick: resolve,
+            },
+            {
+              text: 'Cancel',
+              onClick: reject,
+            },
+          ],
+          onBackdropClick: reject,
+        });
+      }))
+      .then(() => this.$emit('close'), noop);
     },
     saveClose() {
       this.save().then(this.close);

+ 0 - 18
src/options/views/message.vue

@@ -1,18 +0,0 @@
-<template>
-  <transition-group tag="div" name="message">
-    <div v-for="message in store.messages" class="message"
-    :key="message" v-text="message.text"></div>
-  </transition-group>
-</template>
-
-<script>
-import { store } from '../utils';
-
-export default {
-  data() {
-    return {
-      store,
-    };
-  },
-};
-</script>

+ 28 - 0
src/options/views/message/index.vue

@@ -0,0 +1,28 @@
+<template>
+  <transition-group tag="div" name="message">
+    <item v-for="message in store.messages" :key="message"
+    :message="message" @dismiss="onDismiss(message)" />
+  </transition-group>
+</template>
+
+<script>
+import { store } from 'src/options/utils';
+import Item from './item';
+
+export default {
+  components: {
+    Item,
+  },
+  data() {
+    return {
+      store,
+    };
+  },
+  methods: {
+    onDismiss(message) {
+      const i = store.messages.indexOf(message);
+      if (i >= 0) store.messages.splice(i, 1);
+    },
+  },
+};
+</script>

+ 85 - 0
src/options/views/message/item.vue

@@ -0,0 +1,85 @@
+<template>
+  <div class="message-wrap">
+    <div class="message-backdrop fixed-full"
+    v-if="message.backdrop || message.onBackdropClick"
+    @click="onBackdropClick"></div>
+    <div class="message">
+      <div v-if="message.text" v-text="message.text"></div>
+      <form v-if="message.buttons">
+        <input type="text" v-if="message.input !== false" v-model="message.input">
+        <div>
+          <button v-for="button in message.buttons" class="mr-1"
+          :type="button.type || 'button'" v-text="button.text"
+          @click="onButtonClick(button)"></button>
+        </div>
+      </form>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  props: ['message'],
+  mounted() {
+    const input = this.$el.querySelector('input');
+    if (input) input.focus();
+    const { onInit } = this.message;
+    if (onInit) onInit(this);
+  },
+  methods: {
+    onButtonClick(button) {
+      const { onClick } = button;
+      if (onClick) {
+        if (onClick(this.message.input) !== false) this.dismiss();
+      }
+    },
+    onBackdropClick() {
+      const { onBackdropClick } = this.message;
+      if (onBackdropClick) {
+        if (onBackdropClick() !== false) this.dismiss();
+      }
+    },
+    dismiss() {
+      this.$emit('dismiss');
+    },
+  },
+};
+</script>
+
+<style>
+.message {
+  position: absolute;
+  width: 14rem;
+  top: 0;
+  left: 50%;
+  margin-left: -7rem;
+  padding: 1rem;
+  background: white;
+  border-bottom-left-radius: .2rem;
+  border-bottom-right-radius: .2rem;
+  box-shadow: 0 0 .2rem rgba(0,0,0,.2);
+  transition: transform .5s;
+  &-wrap {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    z-index: 10;
+    /* For Vue.js to know the transition duration */
+    transition: transform .5s;
+  }
+  &-backdrop {
+    background: black;
+    opacity: .4;
+    transition: opacity .5s;
+    .message-enter &,
+    .message-leave-active & {
+      opacity: 0;
+    }
+  }
+  .message-enter &,
+  .message-leave-active & {
+    transform: translateY(-120%);
+  }
+}
+</style>

+ 26 - 8
src/options/views/tab-installed.vue

@@ -20,10 +20,10 @@
 </template>
 
 <script>
-import { i18n, sendMessage } from 'src/common';
+import { i18n, sendMessage, noop } from 'src/common';
 import Item from './script-item';
 import Edit from './edit';
-import { store } from '../utils';
+import { store, showMessage } from '../utils';
 
 export default {
   components: {
@@ -57,13 +57,31 @@ export default {
       sendMessage({ cmd: 'CheckUpdateAll' });
     },
     installFromURL() {
-      const url = prompt(i18n('hintInputURL'));
-      if (url && url.includes('://')) {
-        const urlOptions = browser.runtime.getURL(browser.runtime.getManifest().options_page);
-        browser.tabs.create({
-          url: `${urlOptions}#confirm?u=${encodeURIComponent(url)}`,
+      new Promise((resolve, reject) => {
+        showMessage({
+          text: i18n('hintInputURL'),
+          onBackdropClick: reject,
+          buttons: [
+            {
+              type: 'submit',
+              text: 'OK',
+              onClick: resolve,
+            },
+            {
+              text: 'Cancel',
+              onClick: reject,
+            },
+          ],
         });
-      }
+      })
+      .then(url => {
+        if (url && url.includes('://')) {
+          const urlOptions = browser.runtime.getURL(browser.runtime.getManifest().options_page);
+          browser.tabs.create({
+            url: `${urlOptions}#confirm?u=${encodeURIComponent(url)}`,
+          });
+        }
+      }, noop);
     },
     editScript(id) {
       this.script = this.store.scripts.find(script => script.id === id);

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

@@ -27,9 +27,7 @@ export default {
       .map(item => item.trim())
       .filter(Boolean);
       options.set('blacklist', rules);
-      showMessage({
-        text: i18n('msgSavedBlacklist'),
-      });
+      showMessage({ text: i18n('msgSavedBlacklist') });
       sendMessage({ cmd: 'BlacklistReset' });
     },
   },

+ 1 - 3
src/options/views/tab-settings/vm-css.vue

@@ -23,9 +23,7 @@ export default {
   methods: {
     onSave() {
       options.set('customCSS', (this.css || '').trim());
-      showMessage({
-        text: i18n('msgSavedCustomCSS'),
-      });
+      showMessage({ text: i18n('msgSavedCustomCSS') });
     },
   },
 };

+ 1 - 3
src/options/views/tab-settings/vm-import.vue

@@ -130,8 +130,6 @@ function importData(file) {
     return Promise.all(entries.map(entry => getVMFile(entry, vm)));
   })
   .then(res => res.filter(Boolean).length)
-  .then((count) => {
-    showMessage({ text: i18n('msgImported', [count]) });
-  });
+  .then(count => { showMessage({ text: i18n('msgImported', [count]) }); });
 }
 </script>