浏览代码

feat: add search box

Gerald 8 年之前
父节点
当前提交
69f7594b5a

+ 1 - 1
icons/more.svg

@@ -1 +1 @@
-<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M380.26 843.25l-87.663-82.33 264.355-248.321-265.488-249.422 87.796-82.43 352.416 330.885-.867.833.767.767-87.595 82.496-.1-.1z"/></svg>
+<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M380.26 843.25l-87.663-82.33 264.355-248.321-265.488-249.422 87.796-82.43 352.416 330.885-.867.833.767.767-87.595 82.496-.1-.1z"/></svg>

+ 1 - 0
icons/plus.svg

@@ -0,0 +1 @@
+<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><defs><style/></defs><path d="M914.286 420.571v109.715q0 22.857-16.019 38.839t-38.838 16.018H621.714v237.714q0 22.857-16.018 38.839t-38.839 16.018H457.143q-22.857 0-38.839-16.018t-16.018-38.839V585.143H164.57q-22.857 0-38.838-16.018t-16.019-38.84V420.572q0-22.857 16.019-38.838t38.838-16.019h237.715V128q0-22.857 16.018-38.839t38.839-16.018h109.714q22.857 0 38.839 16.018T621.714 128v237.714H859.43q22.857 0 38.838 16.019t16.019 38.838z"/></svg>

+ 11 - 0
src/_locales/cs/messages.yml

@@ -96,6 +96,7 @@ buttonUpdateAll:
 anchorGetMoreScripts:
   description: Link to get more scripts.
   message: Získej více skritpů
+  touched: false
 msgLoading:
   description: Message shown in the options page before script list is loaded.
   message: Nahrávám...
@@ -108,6 +109,7 @@ labelAutoUpdate:
 labelIgnoreGrant:
   description: Option to suppress no @grant warnings.
   message: Zobrazovat všechna @grant varování
+  touched: false
 labelDataImport:
   description: Section title of data import.
   message: Obnova dat
@@ -395,3 +397,12 @@ msgScriptUpdated:
 labelNotifyUpdates:
   description: Option to show notification when script is updated.
   message: ''
+installFrom:
+  description: Label for button to install script from a userscript site.
+  message: ''
+labelSearchScript:
+  description: Placeholder for script search box.
+  message: ''
+labelNoSearchScripts:
+  description: Message shown when no script is found in search results.
+  message: ''

+ 11 - 0
src/_locales/de/messages.yml

@@ -96,6 +96,7 @@ buttonUpdateAll:
 anchorGetMoreScripts:
   description: Link to get more scripts.
   message: Mehr Skripte finden
+  touched: false
 msgLoading:
   description: Message shown in the options page before script list is loaded.
   message: Lade ...
@@ -108,6 +109,7 @@ labelAutoUpdate:
 labelIgnoreGrant:
   description: Option to suppress no @grant warnings.
   message: Unterdrücke @grant Warnungen
+  touched: false
 labelDataImport:
   description: Section title of data import.
   message: Importieren
@@ -393,3 +395,12 @@ msgScriptUpdated:
 labelNotifyUpdates:
   description: Option to show notification when script is updated.
   message: ''
+installFrom:
+  description: Label for button to install script from a userscript site.
+  message: ''
+labelSearchScript:
+  description: Placeholder for script search box.
+  message: ''
+labelNoSearchScripts:
+  description: Message shown when no script is found in search results.
+  message: ''

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

@@ -96,6 +96,7 @@ buttonUpdateAll:
 anchorGetMoreScripts:
   description: Link to get more scripts.
   message: Get more scripts
+  touched: false
 msgLoading:
   description: Message shown in the options page before script list is loaded.
   message: Loading ...
@@ -108,6 +109,7 @@ labelAutoUpdate:
 labelIgnoreGrant:
   description: Option to suppress no @grant warnings.
   message: Suppress no @grant warnings
+  touched: false
 labelDataImport:
   description: Section title of data import.
   message: Data Import
@@ -395,3 +397,12 @@ msgScriptUpdated:
 labelNotifyUpdates:
   description: Option to show notification when script is updated.
   message: Notify script updates
+installFrom:
+  description: Label for button to install script from a userscript site.
+  message: Install from $1
+labelSearchScript:
+  description: Placeholder for script search box.
+  message: Search scripts...
+labelNoSearchScripts:
+  description: Message shown when no script is found in search results.
+  message: No script is found.

+ 11 - 0
src/_locales/es/messages.yml

@@ -96,6 +96,7 @@ buttonUpdateAll:
 anchorGetMoreScripts:
   description: Link to get more scripts.
   message: Consigue mas scripts
+  touched: false
 msgLoading:
   description: Message shown in the options page before script list is loaded.
   message: Cargando ...
@@ -108,6 +109,7 @@ labelAutoUpdate:
 labelIgnoreGrant:
   description: Option to suppress no @grant warnings.
   message: Suprime las advertencias de scripts que no tenga lineas @grant
+  touched: false
 labelDataImport:
   description: Section title of data import.
   message: Importar datos
@@ -399,3 +401,12 @@ msgScriptUpdated:
 labelNotifyUpdates:
   description: Option to show notification when script is updated.
   message: ''
+installFrom:
+  description: Label for button to install script from a userscript site.
+  message: ''
+labelSearchScript:
+  description: Placeholder for script search box.
+  message: ''
+labelNoSearchScripts:
+  description: Message shown when no script is found in search results.
+  message: ''

+ 11 - 0
src/_locales/id/messages.yml

@@ -96,6 +96,7 @@ buttonUpdateAll:
 anchorGetMoreScripts:
   description: Link to get more scripts.
   message: Dapatkan lebih banyak skrip
+  touched: false
 msgLoading:
   description: Message shown in the options page before script list is loaded.
   message: Memuat ...
@@ -108,6 +109,7 @@ labelAutoUpdate:
 labelIgnoreGrant:
   description: Option to suppress no @grant warnings.
   message: Menekan tanpa peringatan @grant
+  touched: false
 labelDataImport:
   description: Section title of data import.
   message: Impor Data
@@ -397,3 +399,12 @@ msgScriptUpdated:
 labelNotifyUpdates:
   description: Option to show notification when script is updated.
   message: ''
+installFrom:
+  description: Label for button to install script from a userscript site.
+  message: ''
+labelSearchScript:
+  description: Placeholder for script search box.
+  message: ''
+labelNoSearchScripts:
+  description: Message shown when no script is found in search results.
+  message: ''

+ 11 - 0
src/_locales/ja/messages.yml

@@ -96,6 +96,7 @@ buttonUpdateAll:
 anchorGetMoreScripts:
   description: Link to get more scripts.
   message: スクリプトの入手
+  touched: false
 msgLoading:
   description: Message shown in the options page before script list is loaded.
   message: 読み込み中…
@@ -108,6 +109,7 @@ labelAutoUpdate:
 labelIgnoreGrant:
   description: Option to suppress no @grant warnings.
   message: '@grant が存在しない場合の警告を表示しない'
+  touched: false
 labelDataImport:
   description: Section title of data import.
   message: インポート
@@ -395,3 +397,12 @@ msgScriptUpdated:
 labelNotifyUpdates:
   description: Option to show notification when script is updated.
   message: スクリプトの更新を通知する
+installFrom:
+  description: Label for button to install script from a userscript site.
+  message: ''
+labelSearchScript:
+  description: Placeholder for script search box.
+  message: ''
+labelNoSearchScripts:
+  description: Message shown when no script is found in search results.
+  message: ''

+ 11 - 0
src/_locales/pl/messages.yml

@@ -96,6 +96,7 @@ buttonUpdateAll:
 anchorGetMoreScripts:
   description: Link to get more scripts.
   message: Znajdź nowe skrypty
+  touched: false
 msgLoading:
   description: Message shown in the options page before script list is loaded.
   message: Wczytywanie ...
@@ -108,6 +109,7 @@ labelAutoUpdate:
 labelIgnoreGrant:
   description: Option to suppress no @grant warnings.
   message: Suppress no @grant warnings
+  touched: false
 labelDataImport:
   description: Section title of data import.
   message: Import danych
@@ -395,3 +397,12 @@ msgScriptUpdated:
 labelNotifyUpdates:
   description: Option to show notification when script is updated.
   message: ''
+installFrom:
+  description: Label for button to install script from a userscript site.
+  message: ''
+labelSearchScript:
+  description: Placeholder for script search box.
+  message: ''
+labelNoSearchScripts:
+  description: Message shown when no script is found in search results.
+  message: ''

+ 11 - 0
src/_locales/ro/messages.yml

@@ -96,6 +96,7 @@ buttonUpdateAll:
 anchorGetMoreScripts:
   description: Link to get more scripts.
   message: Ia mai multe scripturi
+  touched: false
 msgLoading:
   description: Message shown in the options page before script list is loaded.
   message: Se încarcă...
@@ -108,6 +109,7 @@ labelAutoUpdate:
 labelIgnoreGrant:
   description: Option to suppress no @grant warnings.
   message: Suprimă avertismentele „no @grant”
+  touched: false
 labelDataImport:
   description: Section title of data import.
   message: Import de date
@@ -395,3 +397,12 @@ msgScriptUpdated:
 labelNotifyUpdates:
   description: Option to show notification when script is updated.
   message: ''
+installFrom:
+  description: Label for button to install script from a userscript site.
+  message: ''
+labelSearchScript:
+  description: Placeholder for script search box.
+  message: ''
+labelNoSearchScripts:
+  description: Message shown when no script is found in search results.
+  message: ''

+ 24 - 6
src/_locales/ru/messages.yml

@@ -3,7 +3,9 @@ extName:
   message: Violentmonkey
 extDescription:
   description: Description for this extension.
-  message: Violentmonkey позволяет пользователю автоматически вводить скрипты в страницы чтобы изменять их вид или поведение.
+  message: >-
+    Violentmonkey позволяет пользователю автоматически вводить скрипты в
+    страницы чтобы изменять их вид или поведение.
 msgUpdated:
   description: Message shown when a script is updated/reinstalled.
   message: Скрипт обновлен.
@@ -96,6 +98,7 @@ buttonUpdateAll:
 anchorGetMoreScripts:
   description: Link to get more scripts.
   message: Скачать скрипты
+  touched: false
 msgLoading:
   description: Message shown in the options page before script list is loaded.
   message: Загрузка...
@@ -108,6 +111,7 @@ labelAutoUpdate:
 labelIgnoreGrant:
   description: Option to suppress no @grant warnings.
   message: Не показывать предупреждения об отсутствии поля @grant.
+  touched: false
 labelDataImport:
   description: Section title of data import.
   message: Импорт данных
@@ -152,7 +156,9 @@ labelTranslator:
   message: 'Переводчики: '
 anchorTranslator:
   description: Translator shown on about tab.
-  message: 'softovikk, <a href=https://github.com/alexesprit>alexesprit</a>, <a href=https://github.com/bershan2>bershan2</a>'
+  message: >-
+    softovikk, <a href=https://github.com/alexesprit>alexesprit</a>, <a
+    href=https://github.com/bershan2>bershan2</a>
 labelCurrentLang:
   description: Label of current language.
   message: 'Текущий язык: '
@@ -305,13 +311,15 @@ labelShowBadge:
   message: Показывать количество активных скриптов на значке.
 labelLineNumber:
   description: Label for line number jumper.
-  message: Номер строки:
+  message: 'Номер строки:'
 labelBlacklist:
   description: Label for global blacklist settings in security section.
   message: Чёрный список
 descBlacklist:
   description: HTML Description for the global blacklist.
-  message: Скрипты не будут вводиться в документы, URL адреса которых совпадают с правилами в этом списке.
+  message: >-
+    Скрипты не будут вводиться в документы, URL адреса которых совпадают с
+    правилами в этом списке.
 buttonSaveBlacklist:
   description: Button to save global blacklist.
   message: Сохранить
@@ -348,8 +356,9 @@ labelCustomCSS:
 descCustomCSS:
   description: Description of custom CSS section.
   message: >-
-    Пользовательские стили CSS для страницы Настройки и страницы установки скриптов. 
-    Если Вы не знаете что это такое, пожалуйста не редактируйте этот текст.
+    Пользовательские стили CSS для страницы Настройки и страницы установки
+    скриптов. Если Вы не знаете что это такое, пожалуйста не редактируйте этот
+    текст.
 buttonSaveCustomCSS:
   description: Label for button to save custom CSS.
   message: Сохранить
@@ -395,3 +404,12 @@ msgScriptUpdated:
 labelNotifyUpdates:
   description: Option to show notification when script is updated.
   message: Уведомлять об обновлениях скриптов.
+installFrom:
+  description: Label for button to install script from a userscript site.
+  message: ''
+labelSearchScript:
+  description: Placeholder for script search box.
+  message: ''
+labelNoSearchScripts:
+  description: Message shown when no script is found in search results.
+  message: ''

+ 11 - 0
src/_locales/sr/messages.yml

@@ -96,6 +96,7 @@ buttonUpdateAll:
 anchorGetMoreScripts:
   description: Link to get more scripts.
   message: Набави још скрипти
+  touched: false
 msgLoading:
   description: Message shown in the options page before script list is loaded.
   message: Учитавање...
@@ -108,6 +109,7 @@ labelAutoUpdate:
 labelIgnoreGrant:
   description: Option to suppress no @grant warnings.
   message: Не приказуј упозорење о недостатку линије @grant
+  touched: false
 labelDataImport:
   description: Section title of data import.
   message: Увоз података
@@ -395,3 +397,12 @@ msgScriptUpdated:
 labelNotifyUpdates:
   description: Option to show notification when script is updated.
   message: ''
+installFrom:
+  description: Label for button to install script from a userscript site.
+  message: ''
+labelSearchScript:
+  description: Placeholder for script search box.
+  message: ''
+labelNoSearchScripts:
+  description: Message shown when no script is found in search results.
+  message: ''

+ 11 - 0
src/_locales/vi/messages.yml

@@ -96,6 +96,7 @@ buttonUpdateAll:
 anchorGetMoreScripts:
   description: Link to get more scripts.
   message: Tìm thêm script
+  touched: false
 msgLoading:
   description: Message shown in the options page before script list is loaded.
   message: Đang tải ...
@@ -108,6 +109,7 @@ labelAutoUpdate:
 labelIgnoreGrant:
   description: Option to suppress no @grant warnings.
   message: Bỏ qua cảnh báo không có @grant
+  touched: false
 labelDataImport:
   description: Section title of data import.
   message: Nhập dữ liệu
@@ -395,3 +397,12 @@ msgScriptUpdated:
 labelNotifyUpdates:
   description: Option to show notification when script is updated.
   message: ''
+installFrom:
+  description: Label for button to install script from a userscript site.
+  message: ''
+labelSearchScript:
+  description: Placeholder for script search box.
+  message: ''
+labelNoSearchScripts:
+  description: Message shown when no script is found in search results.
+  message: ''

+ 11 - 0
src/_locales/zh/messages.yml

@@ -96,6 +96,7 @@ buttonUpdateAll:
 anchorGetMoreScripts:
   description: Link to get more scripts.
   message: 获取更多脚本
+  touched: false
 msgLoading:
   description: Message shown in the options page before script list is loaded.
   message: 加载中 ...
@@ -108,6 +109,7 @@ labelAutoUpdate:
 labelIgnoreGrant:
   description: Option to suppress no @grant warnings.
   message: 不显示缺少@grant的警告
+  touched: false
 labelDataImport:
   description: Section title of data import.
   message: 数据导入
@@ -393,3 +395,12 @@ msgScriptUpdated:
 labelNotifyUpdates:
   description: Option to show notification when script is updated.
   message: 脚本更新时提示
+installFrom:
+  description: Label for button to install script from a userscript site.
+  message: 从$1安装
+labelSearchScript:
+  description: Placeholder for script search box.
+  message: 搜索脚本...
+labelNoSearchScripts:
+  description: Message shown when no script is found in search results.
+  message: 没有找到脚本!

+ 18 - 1
src/options/app.js

@@ -1,7 +1,7 @@
 import Vue from 'vue';
 import 'src/common/browser';
 import 'src/common/sprite';
-import { sendMessage, i18n } from 'src/common';
+import { sendMessage, i18n, getLocaleString } from 'src/common';
 import options from 'src/common/options';
 import { store, features } from './utils';
 import App from './views/app';
@@ -61,6 +61,18 @@ function initialize() {
   });
 }
 
+function initSearch(script) {
+  const meta = script.meta || {};
+  script._search = [
+    meta.name,
+    getLocaleString(meta, 'name'),
+    meta.description,
+    getLocaleString(meta, 'description'),
+    script.custom.name,
+    script.custom.description,
+  ].filter(Boolean).join('\n').toLowerCase();
+}
+
 function loadData() {
   sendMessage({ cmd: 'GetData' })
   .then(data => {
@@ -71,6 +83,9 @@ function loadData() {
     ].forEach((key) => {
       Vue.set(store, key, data[key]);
     });
+    if (store.scripts) {
+      store.scripts.forEach(initSearch);
+    }
     store.loading = false;
     // features.reset(data.version);
     features.reset('sync');
@@ -87,6 +102,7 @@ function initMain() {
     },
     AddScript(data) {
       data.message = '';
+      initSearch(data);
       store.scripts.push(data);
     },
     UpdateScript(data) {
@@ -96,6 +112,7 @@ function initMain() {
         Object.keys(data).forEach((key) => {
           Vue.set(script, key, data[key]);
         });
+        initSearch(script);
       }
     },
     RemoveScript(data) {

+ 30 - 31
src/options/style.css

@@ -3,10 +3,13 @@
   padding: 0;
   box-sizing: border-box;
 }
+:focus {
+  outline: none;
+}
 html {
   height: 100%;
   font: 14px menu;
-  background: #eee;
+  background: #f0f0f0;
 }
 body {
   height: 100%;
@@ -22,17 +25,25 @@ h1, h2 {
 button {
   padding: 0 .5rem;
   font-size: 1rem;
+  line-height: 1.5;
+  background: white;
+  border: 1px solid #bbb;
+  cursor: pointer;
 }
 a {
   color: dodgerblue;
 }
-input[disabled] ~ * {
-  color: gray;
-}
 hr {
   border: none;
   border-top: 1px solid darkgray;
 }
+input[disabled] ~ * {
+  color: gray;
+}
+input[type=text] {
+  display: block;
+  width: 100%;
+}
 textarea {
   display: block;
   width: 100%;
@@ -108,13 +119,14 @@ textarea {
   &-menu {
     position: absolute;
     top: 100%;
-    right: 0;
     margin-top: .4rem;
-    padding: .5rem 1rem;
-    background: #fbfbfb;
-    border-radius: .3rem;
-    box-shadow: 0 1px 1px #bbb;
+    padding: .5rem;
+    border: 1px solid #bbb;
+    background: white;
     z-index: 10;
+    .dropdown-right & {
+      right: 0;
+    }
   }
   &:not(.show) .dropdown-menu {
     display: none;
@@ -159,28 +171,11 @@ aside img {
   color: black;
 }
 .content {
-  position: relative;
-  height: 100%;
-  margin-left: 14rem;
-  padding: 1.2rem;
-  background: white;
-  border-left: 1px solid darkgray;
-  border-right: 1px solid darkgray;
-  overflow-y: auto;
-}
-.content > header {
-  height: 2rem;
-  line-height: 2rem;
-  padding: 0 .5rem;
-  border-bottom: 1px solid darkgray;
 }
 #currentLang {
   color: green;
   font-weight: bold;
 }
-.no-pad {
-  padding: 0;
-}
 section {
   position: relative;
   margin-top: 2rem;
@@ -282,7 +277,7 @@ code {
   z-index: 9;
 }
 .edit {
-  background: #eee;
+  background: #f0f0f0;
 }
 .feature {
   &-text {
@@ -312,10 +307,14 @@ svg path {
   fill: currentColor;
 }
 
-.tab-settings {
-  textarea {
-    height: 10em;
-  }
+.tab {
+  position: relative;
+  height: 100%;
+  margin-left: 14rem;
+  padding: 1.2rem;
+  background: white;
+  border-left: 1px solid darkgray;
+  border-right: 1px solid darkgray;
 }
 
 .CodeMirror-foldmarker {

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

@@ -2,7 +2,7 @@
   <div class="flex flex-col h-100">
     <div class="frame-block">
       <div class="buttons pull-right">
-        <div v-dropdown class="confirm-options">
+        <div v-dropdown class="confirm-options dropdown-right">
           <button dropdown-toggle v-text="i18n('buttonInstallOptions')"></button>
           <div class="dropdown-menu options-panel" @mousedown.stop>
             <label>

+ 0 - 2
src/options/views/edit/index.vue

@@ -386,8 +386,6 @@ export default {
     > div {
       display: inline-block;
       padding: 8px 16px;
-      border-top-left-radius: 6px;
-      border-top-right-radius: 6px;
       color: #bbb;
       &.active {
         background: white;

+ 1 - 1
src/options/views/main.vue

@@ -12,7 +12,7 @@
         <a href="#?t=About" :class="{active: tab === 'About'}" v-text="i18n('sideMenuAbout')"></a>
       </div>
     </aside>
-    <component :is="tab"></component>
+    <component :is="tab" class="tab"></component>
   </div>
 </template>
 

+ 1 - 1
src/options/views/tab-about.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="content">
+  <div>
     <h1>
       <span v-text="i18n('labelAbout')"></span>
       <small v-text="`v${version}`"></small>

+ 86 - 13
src/options/views/tab-installed.vue

@@ -1,21 +1,33 @@
 <template>
-  <div class="content no-pad">
+  <div class="tab-installed">
     <header class="flex">
       <div class="flex-auto">
-        <button v-text="i18n('buttonNew')" @click="newScript"></button>
-        <button v-text="i18n('buttonUpdateAll')" @click="updateAll"></button>
-        <button v-text="i18n('buttonInstallFromURL')" @click="installFromURL"></button>
-      </div>
-      <div v-dropdown>
-        <button dropdown-toggle v-text="i18n('anchorGetMoreScripts')"></button>
-        <div class="dropdown-menu">
-          <a href="https://openuserjs.org/" target="_blank">OpenUserJS</a>
-          <a href="https://greasyfork.org/scripts" target="_blank">GreasyFork</a>
+        <div v-dropdown="{autoClose: true}">
+          <button dropdown-toggle>
+            <svg class="icon"><use xlink:href="#plus" /></svg>
+          </button>
+          <div class="dropdown-menu">
+            <a href="#" v-text="i18n('buttonNew')" @click.prevent="newScript"></a>
+            <a v-text="i18n('installFrom', 'OpenUserJS')" href="https://openuserjs.org/" target="_blank"></a>
+            <a v-text="i18n('installFrom', 'GreasyFork')" href="https://greasyfork.org/scripts" target="_blank"></a>
+            <a href="#" v-text="i18n('buttonInstallFromURL')" @click.prevent="installFromURL"></a>
+          </div>
         </div>
+        <tooltip :title="i18n('buttonUpdateAll')" placement="down">
+          <button @click="updateAll">
+            <svg class="icon"><use xlink:href="#refresh" /></svg>
+          </button>
+        </tooltip>
+      </div>
+      <div class="filter-search">
+        <input type="text" :placeholder="i18n('labelSearchScript')" v-model="search">
+        <svg class="icon"><use xlink:href="#search" /></svg>
+      </div>
+      <div>
       </div>
     </header>
     <div class="scripts">
-      <item v-for="script in store.scripts" :key="script"
+      <item v-for="script in scripts" :key="script.id"
       :script="script" @edit="editScript" @move="moveScript"></item>
     </div>
     <div class="backdrop" :class="{mask: store.loading}" v-show="message">
@@ -26,22 +38,30 @@
 </template>
 
 <script>
-import { i18n, sendMessage, noop } from 'src/common';
+import { i18n, sendMessage, noop, debounce } from 'src/common';
 import Item from './script-item';
 import Edit from './edit';
 import { store, showMessage } from '../utils';
+import Tooltip from './tooltip';
 
 export default {
   components: {
     Item,
     Edit,
+    Tooltip,
   },
   data() {
     return {
       store,
       script: null,
+      search: null,
+      scripts: store.scripts,
     };
   },
+  watch: {
+    search: 'onUpdate',
+    'store.scripts': 'onUpdate',
+  },
   computed: {
     message() {
       if (this.store.loading) {
@@ -50,9 +70,19 @@ export default {
       if (!this.store.scripts.length) {
         return i18n('labelNoScripts');
       }
+      if (!this.scripts.length) {
+        return i18n('labelNoSearchScripts');
+      }
     },
   },
   methods: {
+    onUpdate: debounce(function onUpdate() {
+      const { search } = this;
+      const { scripts } = this.store;
+      this.scripts = search
+        ? scripts.filter(script => (script._search || '').includes(search.toLowerCase()))
+        : scripts;
+    }, 200),
     newScript() {
       sendMessage({ cmd: 'NewScript' })
       .then((script) => {
@@ -62,6 +92,9 @@ export default {
     updateAll() {
       sendMessage({ cmd: 'CheckUpdateAll' });
     },
+    openURL(url) {
+      window.open(url);
+    },
     installFromURL() {
       new Promise((resolve, reject) => {
         showMessage({
@@ -124,10 +157,35 @@ export default {
 </script>
 
 <style>
+.tab-installed {
+  padding: 0;
+  > header {
+    height: 3rem;
+    align-items: center;
+    padding: 0 .5rem;
+    line-height: 1.5;
+    border-bottom: 1px solid darkgray;
+  }
+  .dropdown-menu {
+    padding: .5rem;
+    white-space: nowrap;
+    > a {
+      display: block;
+      width: 100%;
+      padding: .5rem;
+      text-decoration: none;
+      color: #666;
+      &:hover {
+        color: inherit;
+        background: #fbfbfb;
+      }
+    }
+  }
+}
 .backdrop,
 .scripts {
   position: absolute;
-  top: 2rem;
+  top: 3rem;
   left: 0;
   right: 0;
   bottom: 0;
@@ -154,4 +212,19 @@ export default {
   background: rgba(0,0,0,.08);
   /*transition: opacity 1s;*/
 }
+.filter-search {
+  position: relative;
+  width: 12rem;
+  .icon {
+    position: absolute;
+    height: 100%;
+    top: 0;
+    right: .5rem;
+  }
+  > input {
+    padding-left: .5rem;
+    padding-right: 2rem;
+    line-height: 2;
+  }
+}
 </style>

+ 10 - 1
src/options/views/tab-settings/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="content tab-settings">
+  <div class="tab-settings">
     <h1 v-text="i18n('labelSettings')"></h1>
     <section>
       <h3 v-text="i18n('labelGeneral')"></h3>
@@ -61,3 +61,12 @@ export default {
   },
 };
 </script>
+
+<style>
+.tab-settings {
+  overflow-y: auto;
+  textarea {
+    height: 10em;
+  }
+}
+</style>

+ 20 - 5
src/options/views/tooltip.vue

@@ -20,7 +20,7 @@ export default {
 <style>
 $bg-color: rgba(0,0,0,.8);
 $border-side: 4px solid transparent;
-$border-base: 8px solid $bg-color;
+$border-base: 6px solid $bg-color;
 
 .tooltip {
   position: relative;
@@ -41,19 +41,34 @@ $border-base: 8px solid $bg-color;
       content: '';
       position: absolute;
     }
+    &.tooltip-up,
+    &.tooltip-down {
+      &,
+      &::before {
+        left: 50%;
+        transform: translateX(-50%);
+      }
+    }
     &.tooltip-up {
-      left: 50%;
       bottom: 100%;
-      transform: translate(-50%,-10px);
+      margin-bottom: 1rem;
       &::before {
         top: 100%;
-        left: 50%;
-        transform: translateX(-50%);
         border-top: $border-base;
         border-left: $border-side;
         border-right: $border-side;
       }
     }
+    &.tooltip-down {
+      top: 100%;
+      margin-top: 1rem;
+      &::before {
+        bottom: 100%;
+        border-left: $border-side;
+        border-right: $border-side;
+        border-bottom: $border-base;
+      }
+    }
     &.tooltip-right {
       top: 50%;
       left: 100%;