├── docs ├── about │ └── README.md ├── code │ └── README.md ├── guide │ └── README.md ├── zh │ ├── guide │ │ └── README.md │ ├── code │ │ └── README.md │ ├── README.md │ ├── about │ │ └── README.md │ └── changelog │ │ └── README.md ├── changelog │ └── README.md ├── .vuepress │ ├── public │ │ └── img │ │ │ ├── crown.png │ │ │ ├── intro.gif │ │ │ ├── Github.png │ │ │ ├── chrome-icon.png │ │ │ └── crown.svg │ └── config.js └── README.md ├── src ├── App.vue ├── stylus │ ├── popup.styl │ ├── vuetify.styl │ ├── content.styl │ ├── content-script.styl │ └── options.styl ├── types │ ├── shims-vue.d.ts │ ├── shims-tsx.d.ts │ └── crown.d.ts ├── views │ ├── Popup.vue │ ├── Content.vue │ └── Options.vue ├── background │ ├── default-config.ts │ ├── handle-selected-item.ts │ ├── main.ts │ ├── omnibox.ts │ └── filter-search-data.ts ├── router.ts ├── common │ └── utils.ts ├── store.ts ├── main.ts ├── content-script.ts ├── genarate-manifest.ts ├── api │ └── index.ts ├── assets │ └── logo.svg └── components │ └── searchView.vue ├── babel.config.js ├── public ├── icons │ ├── 128.png │ ├── 16.png │ ├── 19.png │ ├── 38.png │ └── 48.png ├── index.html └── _locales │ ├── zh_CN │ └── messages.json │ └── en │ └── messages.json ├── tests └── unit │ ├── .eslintrc.js │ └── example.spec.ts ├── README.md ├── .yarnclean ├── tsconfig.json ├── .travis.yml ├── vue.config.js ├── .gitignore └── package.json /docs/about/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/code/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/guide/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/zh/guide/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/zh/code/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: auto 3 | --- 4 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@vue/app'], 3 | } 4 | -------------------------------------------------------------------------------- /src/stylus/popup.styl: -------------------------------------------------------------------------------- 1 | .is-active 2 | background: rgba(#000, 0.04); 3 | -------------------------------------------------------------------------------- /docs/changelog/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: auto 3 | --- 4 | 5 | # 更新记录 6 | -------------------------------------------------------------------------------- /public/icons/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crown3/crown/HEAD/public/icons/128.png -------------------------------------------------------------------------------- /public/icons/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crown3/crown/HEAD/public/icons/16.png -------------------------------------------------------------------------------- /public/icons/19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crown3/crown/HEAD/public/icons/19.png -------------------------------------------------------------------------------- /public/icons/38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crown3/crown/HEAD/public/icons/38.png -------------------------------------------------------------------------------- /public/icons/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crown3/crown/HEAD/public/icons/48.png -------------------------------------------------------------------------------- /tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | jest: true, 4 | }, 5 | } 6 | -------------------------------------------------------------------------------- /src/types/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue' 3 | export default Vue 4 | } 5 | -------------------------------------------------------------------------------- /docs/.vuepress/public/img/crown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crown3/crown/HEAD/docs/.vuepress/public/img/crown.png -------------------------------------------------------------------------------- /docs/.vuepress/public/img/intro.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crown3/crown/HEAD/docs/.vuepress/public/img/intro.gif -------------------------------------------------------------------------------- /docs/.vuepress/public/img/Github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crown3/crown/HEAD/docs/.vuepress/public/img/Github.png -------------------------------------------------------------------------------- /docs/.vuepress/public/img/chrome-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crown3/crown/HEAD/docs/.vuepress/public/img/chrome-icon.png -------------------------------------------------------------------------------- /src/stylus/vuetify.styl: -------------------------------------------------------------------------------- 1 | // vuetify style 2 | $color-pack = false; 3 | 4 | @import '~vuetify/src/stylus/app'; 5 | 6 | .application--wrap 7 | min-height: unset !important; -------------------------------------------------------------------------------- /src/stylus/content.styl: -------------------------------------------------------------------------------- 1 | html 2 | height: 100%; 3 | 4 | .application--wrap 5 | box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2), 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12); -------------------------------------------------------------------------------- /src/stylus/content-script.styl: -------------------------------------------------------------------------------- 1 | #CrownExtensionWrapper 2 | position: fixed; 3 | top: 10%; 4 | bottom: 10%; 5 | left: 50%; 6 | z-index: 2147483647; 7 | display: none; 8 | transform: translateX(-50%); 9 | 10 | #CrownExtensionIframe 11 | overflow: hidden; 12 | width: 666px; 13 | height: 100%; 14 | border: none; 15 | -------------------------------------------------------------------------------- /src/views/Popup.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /src/types/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from 'vue' 2 | 3 | declare global { 4 | namespace JSX { 5 | // tslint:disable no-empty-interface 6 | interface Element extends VNode {} 7 | // tslint:disable no-empty-interface 8 | interface ElementClass extends Vue {} 9 | interface IntrinsicElements { 10 | [elem: string]: any 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/views/Content.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /src/stylus/options.styl: -------------------------------------------------------------------------------- 1 | // overlay injected stylesheet's influence in options page 2 | html 3 | overflow-y: auto; 4 | background: #fafafa; 5 | 6 | input[type='text'] 7 | border-color: transparent; 8 | 9 | &:focus 10 | border-color: transparent !important; 11 | 12 | .v-input--selection-controls__input input 13 | opacity: 0 !important; 14 | 15 | .application--wrap 16 | box-shadow: unset; -------------------------------------------------------------------------------- /tests/unit/example.spec.ts: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import HelloWorld from '@/components/HelloWorld.vue' 3 | 4 | describe('HelloWorld.vue', () => { 5 | it('renders props.msg when passed', () => { 6 | const msg = 'new message' 7 | const wrapper = shallowMount(HelloWorld, { 8 | propsData: { msg }, 9 | }) 10 | expect(wrapper.text()).toMatch(msg) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | heroImage: /img/crown.svg 4 | actionText: How to use → 5 | actionLink: /guide/ 6 | features: 7 | - title: 在 Chrome 任何地方使用 8 | details: 不仅可以在弹出框启用该插件, 网页中, 搜索栏中也可以 9 | - title: 自定义各种快捷操作 10 | details: 你可以自定义打开弹出框的快捷键, 在网页中打开的快捷键, 以及每个不同搜索子列的快捷键 11 | - title: 提高效率, 简洁至上 12 | details: 让你随时可以快速定位自己需要的内容, 提高你的 Chrome 效率 13 | footer: MIT Licensed | Copyright © 2017-present Crown Chen 14 | --- 15 | -------------------------------------------------------------------------------- /docs/zh/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | heroImage: /img/crown.svg 4 | actionText: 如何使用 → 5 | actionLink: /zh/guide/ 6 | features: 7 | - title: 在 Chrome 任何地方使用 8 | details: 不仅可以在弹出框启用该插件, 网页中, 搜索栏中也可以 9 | - title: 自定义各种快捷操作 10 | details: 你可以自定义打开弹出框的快捷键, 在网页中打开的快捷键, 以及每个不同搜索子列的快捷键 11 | - title: 提高效率, 简洁至上 12 | details: 让你随时可以快速定位自己需要的内容, 提高你的 Chrome 效率 13 | footer: MIT Licensed | Copyright © 2017-present Crown Chen 14 | --- 15 | -------------------------------------------------------------------------------- /src/background/default-config.ts: -------------------------------------------------------------------------------- 1 | const defaultConfig: ExtensionConfig = { 2 | bookmark: { 3 | isDefault: true, 4 | keyword: 'bm', 5 | desc: 'Bookmarks', 6 | }, 7 | tab: { 8 | isDefault: true, 9 | keyword: 't', 10 | desc: 'Tabs', 11 | }, 12 | RCT: { 13 | isDefault: false, 14 | keyword: 'rct', 15 | desc: 'Recently Closed Tabs', 16 | }, 17 | } 18 | 19 | export default defaultConfig 20 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | crown-extension 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # my-vue 2 | 3 | ## Project setup 4 | ``` 5 | yarn install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | yarn run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | yarn run build 16 | ``` 17 | 18 | ### Run your tests 19 | ``` 20 | yarn run test 21 | ``` 22 | 23 | ### Lints and fixes files 24 | ``` 25 | yarn run lint 26 | ``` 27 | 28 | ### Customize configuration 29 | See [Configuration Reference](https://cli.vuejs.org/config/). 30 | -------------------------------------------------------------------------------- /src/background/handle-selected-item.ts: -------------------------------------------------------------------------------- 1 | import { openNewTab, restoreRecentTab, updateTabStatus } from '@/api' 2 | 3 | function handleSelectedItem(item: QueryResultItem) { 4 | switch (item.type) { 5 | case 'bookmark': 6 | openNewTab(item.subtitle as string) 7 | break 8 | case 'tab': 9 | updateTabStatus(item.id as number) 10 | break 11 | case 'RCT': 12 | restoreRecentTab(item.id as string) 13 | break 14 | 15 | default: 16 | break 17 | } 18 | } 19 | 20 | export default handleSelectedItem 21 | -------------------------------------------------------------------------------- /src/router.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import Popup from './views/Popup.vue' 4 | 5 | Vue.use(Router) 6 | 7 | export default new Router({ 8 | routes: [ 9 | { 10 | path: '/', 11 | name: 'home', 12 | component: Popup, 13 | }, 14 | { 15 | path: '/options', 16 | name: 'options', 17 | component: () => 18 | import(/* webpackChunkName: "options" */ './views/Options.vue'), 19 | }, 20 | { 21 | path: '/content', 22 | name: 'content', 23 | component: () => 24 | import(/* webpackChunkName: "content" */ './views/Content.vue'), 25 | }, 26 | ], 27 | }) 28 | -------------------------------------------------------------------------------- /.yarnclean: -------------------------------------------------------------------------------- 1 | # test directories 2 | __tests__ 3 | test 4 | tests 5 | powered-test 6 | 7 | # asset directories 8 | docs 9 | doc 10 | website 11 | images 12 | assets 13 | 14 | # examples 15 | example 16 | examples 17 | 18 | # code coverage directories 19 | coverage 20 | .nyc_output 21 | 22 | # build scripts 23 | Makefile 24 | Gulpfile.js 25 | Gruntfile.js 26 | 27 | # configs 28 | appveyor.yml 29 | circle.yml 30 | codeship-services.yml 31 | codeship-steps.yml 32 | wercker.yml 33 | .tern-project 34 | .gitattributes 35 | .editorconfig 36 | .*ignore 37 | .eslintrc 38 | .jshintrc 39 | .flowconfig 40 | .documentup.json 41 | .yarn-metadata.json 42 | .travis.yml 43 | 44 | # misc 45 | *.md 46 | -------------------------------------------------------------------------------- /src/common/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * https://stackoverflow.com/questions/35802159/chrome-omnibox-special-characters-throw-error 3 | * handle a error about (omnibox description xmlParseEntityRef: no name) 4 | */ 5 | function encodeXml(xml: string): string { 6 | const dom: HTMLElement = document.createElement('div') 7 | dom.textContent = xml 8 | return dom.innerHTML 9 | } 10 | 11 | /** 12 | * Whether each item is eligible 13 | */ 14 | function isEachEligible(arr: any[], tested: string): boolean { 15 | return arr.every(item => new RegExp(`${item}`, 'gi').test(tested)) 16 | } 17 | 18 | const getKeys = (o: T): Array => 19 | Object.keys(o) as Array 20 | 21 | export { encodeXml, isEachEligible, getKeys } 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "esModuleInterop": true, 10 | "allowSyntheticDefaultImports": true, 11 | "sourceMap": true, 12 | "baseUrl": ".", 13 | "types": ["webpack-env", "jest", "vuetify"], 14 | "paths": { 15 | "@/*": ["src/*"], 16 | "@c/*": ["src/components/*"] 17 | }, 18 | "lib": ["esnext", "dom", "dom.iterable", "scripthost"] 19 | }, 20 | "include": [ 21 | "src/**/*.ts", 22 | "src/**/*.tsx", 23 | "src/**/*.vue", 24 | "tests/**/*.ts", 25 | "tests/**/*.tsx" 26 | ], 27 | "exclude": ["node_modules"] 28 | } 29 | -------------------------------------------------------------------------------- /src/store.ts: -------------------------------------------------------------------------------- 1 | import defaultConfig from '@/background/default-config' 2 | import Vue from 'vue' 3 | import Vuex from 'vuex' 4 | import createPersistedState from 'vuex-persistedstate' 5 | 6 | interface ChangeConfigPayload { 7 | type: 'bookmark' | 'tab' | 'RCT' 8 | key: 'isDefault' | 'keyword' 9 | value: string | boolean 10 | } 11 | 12 | Vue.use(Vuex) 13 | 14 | export default new Vuex.Store({ 15 | strict: process.env.NODE_ENV !== 'production', 16 | state: { 17 | config: defaultConfig, 18 | }, 19 | mutations: { 20 | changeItemConfig(state, { type, key, value }: ChangeConfigPayload) { 21 | state.config[type][key] = value 22 | }, 23 | }, 24 | plugins: [ 25 | createPersistedState({ 26 | key: process.env.VUE_APP_VERSION, 27 | }), 28 | ], 29 | }) 30 | -------------------------------------------------------------------------------- /docs/zh/about/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: auto 3 | --- 4 | 5 | # 关于 6 | 7 | ## 为什么开发该插件, 以及接下来的开发方向? 8 | 9 | 最开始(2017.08)自己在找同时支持书签以及标签页快速切换的插件, 但一直在市面上没找到满足自己需求的插件, 然后刚好那段时间自己又比较闲, 遂一锄头下去, 开坑!!! 10 | 11 | 关于接下来的开发方向, 其实最开始的时候只是想做一个适合自己的小型检索器之类的东西, 没准备造一个很复杂的轮子, 但是自己在逛 V2EX 时看到这么个帖子[Steward V3.3 发布](https://www.v2ex.com/t/432706#reply3), 发现已经有人开发了一个比我成熟很多的产品, 但是自己去查看它的源码时发现里面引用的东西很乱, 而且功能做的很杂, 有很多自己觉得不必要的功能在里面, 所以决定自己去把这个东西继续写下去, 主要方向就是以快速检索自己需要的信息为主, 加上一些浏览增强工具 (尽量采用可配置的权限管理机制, 以及对功能的开关, 例如获取个人最近关闭的浏览器标签数据就需要用户授予访问 `chrome.sessions` 权限才能启用该功能) 12 | 13 | ## 收费相关问题 14 | 15 | 该项目会保持永久免费的状态 (主要是不想给自己太大的开发压力:joy:), 以后可能会增加捐赠渠道, 目前没有相关计划... 16 | 17 | ## 关于我 :crown: 18 | 19 | 如果大家有什么需要的功能或者好的建议, 欢迎大家来交流, 主要可以通过以下方式找到我 20 | 21 | - V2EX: [来撩?](https://www.v2ex.com/member/CrownLeo) 22 | - Telegram: [加我好友](https://t.me/CrownChen) 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | language: node_js 3 | 4 | cache: 5 | yarn: true 6 | directories: 7 | - 'node_modules' 8 | 9 | jobs: 10 | include: 11 | - stage: gh-pages 12 | node_js: lts/* 13 | script: 14 | - yarn run lint 15 | before_deploy: 16 | - yarn run docs:build 17 | deploy: 18 | provider: pages 19 | skip-cleanup: true 20 | github-token: $GITHUB_TOKEN 21 | keep-history: true 22 | local-dir: 'gh-pages' 23 | on: 24 | branch: master 25 | - stage: semantic-release 26 | node_js: lts/* 27 | script: 28 | - yarn run lint 29 | deploy: 30 | provider: script 31 | skip_cleanup: true 32 | script: 33 | - npx semantic-release 34 | on: 35 | branch: master 36 | -------------------------------------------------------------------------------- /docs/zh/changelog/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: auto 3 | --- 4 | 5 | # 更新记录 6 | 7 | ## V2.0.0 8 | 9 | ### 新增功能 10 | 11 | - 新增对最近关闭过 `标签页` 页面进行查找, 筛选 `(默认关闭, 需要授权开启)` 12 | - 对各种搜索子项目启用快捷键设置, 放弃了原来根据 `tab` 键来切换搜索栏目的交互方式 13 | - **新增默认搜索设置**, 对于部分搜索栏目可以设置其是否出现在默认搜索结果中 14 | - 搜索时, 可以 **同时搜索多个子项目**, 并使用多个单词来对结果进行筛选 15 | - 新增插件设置页面 16 | - 新增插件在网页中的弹出显示以及相关功能支持 17 | - 新增插件在搜索栏中的简化支持 `(由于 chrome 默认显示数目限制, 简化筛选结果)` 18 | - 重新设计了相关 UI 样式 19 | - 对 **中英** 双语言的支持 20 | 21 | ### 代码优化 22 | 23 | - 重新设计了底层代码, 使其更方便日后对其他功能的增加以及扩展 24 | - 搜索过程中使用了并发执行来加快处理速度 25 | - 使用 `stylus` 来替代原来使用的 `sass` css处理器 26 | 27 | ## V1.2.0 以及之前版本 28 | 29 | ::: tip 30 | `V1.2.0`是写这个插件时的第一个完整版本, 只是简单对的自己需要的功能 (`书签` && `标签页` 的搜索以及筛选结果) 做了一次简单开发以及相关BUG的修改, 故之前的相关提交记录统一合并在这里记录 31 | ::: 32 | 33 | ### 新增功能 34 | 35 | - 对 Chrome `书签` 进行查找并筛选结果 36 | - 对 Chrome `标签页` 进行查找并筛选结果 37 | - 筛选结果 (只支持单个单词) 38 | - 使用 `tab` 键来进行 `书签` 和 `标签页` 的搜索切换 39 | - 只能在弹出框中使用该插件 40 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | import Vue from 'vue' 3 | import App from './App.vue' 4 | import router from './router' 5 | 6 | import VApp from 'vuetify/es5/components/VApp' 7 | import VChip from 'vuetify/es5/components/VChip' 8 | import VGrid from 'vuetify/es5/components/VGrid' 9 | import VList from 'vuetify/es5/components/VList' 10 | import VSwitch from 'vuetify/es5/components/VSwitch' 11 | import VTextField from 'vuetify/es5/components/VTextField' 12 | import VToolbar from 'vuetify/es5/components/VToolbar' 13 | import Vuetify from 'vuetify/es5/components/Vuetify' 14 | 15 | import '@/stylus/vuetify.styl' 16 | 17 | Vue.use(Vuetify, { 18 | components: { 19 | VApp, 20 | VGrid, 21 | VList, 22 | VTextField, 23 | VToolbar, 24 | VSwitch, 25 | VChip, 26 | }, 27 | }) 28 | 29 | Vue.config.productionTip = false 30 | 31 | new Vue({ 32 | router, 33 | store, 34 | render: h => h(App), 35 | }).$mount('#app') 36 | -------------------------------------------------------------------------------- /src/content-script.ts: -------------------------------------------------------------------------------- 1 | import '@/stylus/content-script.styl' 2 | import { browser } from 'webextension-polyfill-ts' 3 | 4 | const frag = document.createElement('div') 5 | frag.id = 'CrownExtensionWrapper' 6 | document.body.appendChild(frag) 7 | 8 | const ele = document.getElementById('CrownExtensionWrapper') as HTMLElement 9 | browser.runtime.onMessage.addListener((response: CMessage) => { 10 | switch (response.type) { 11 | case 'openExtension': 12 | if (!document.getElementById('CrownExtensionIframe')) { 13 | ele.innerHTML = `` 16 | } 17 | ele.style.display = 'block' 18 | break 19 | case 'closeExtension': 20 | ele.innerHTML = '' 21 | ele.style.display = 'none' 22 | break 23 | 24 | default: 25 | break 26 | } 27 | }) 28 | 29 | document.body.addEventListener('click', (event: Event) => { 30 | if (!(ele === event.target || ele.contains(event.target as Element))) { 31 | ele.style.display = 'none' 32 | } 33 | }) 34 | -------------------------------------------------------------------------------- /public/_locales/zh_CN/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extDesc": { 3 | "message": "一款能让你的 Chrome 更加顺滑的 Launchbar !!!", 4 | "description": "扩展描述" 5 | }, 6 | "contentHotKey": { 7 | "message": "在网页中打开 Crown 扩展", 8 | "description": "在网页中唤起 Crown 扩展" 9 | }, 10 | "searchPlaceholder": { 11 | "message": "等候您的指令...", 12 | "description": "searchView 输入框的默认提示语" 13 | }, 14 | "bookmark": { 15 | "message": "书签" 16 | }, 17 | "bookmark_label": { 18 | "message": "书签", 19 | "description": "书签在搜索列表中的标记" 20 | }, 21 | "tab": { 22 | "message": "标签页" 23 | }, 24 | "tab_label": { 25 | "message": "标签", 26 | "description": "标签在搜索列表中的标记" 27 | }, 28 | "RCT": { 29 | "message": "最近关闭的标签页" 30 | }, 31 | "RCT_label": { 32 | "message": "最近关闭", 33 | "description": "最近关闭的标签页在搜索列表中的标识" 34 | }, 35 | "keyword_label": { 36 | "message": "关键词", 37 | "description": "检索触发关键词在搜索列表中的标识" 38 | }, 39 | "contentScript_unavailable": { 40 | "message": "因为浏览器的限制, 您无法在 部分受限页面里 或者 网页未加载完成时 唤醒 Crown 扩展", 41 | "description": "content-script 不可用时的提示语" 42 | }, 43 | "isSetDefault": { 44 | "message": "在默认的搜索列表中显示来自 $TYPE$ 的结果?", 45 | "description": "在默认搜索结果中显示某分类下的结果", 46 | "placeholders": { 47 | "type": { 48 | "content": "$1", 49 | "example": "书签" 50 | } 51 | } 52 | }, 53 | "keywordOfSet": { 54 | "message": "用来单独搜索 $TYPE$ 的关键词", 55 | "description": "单独搜索的触发关键词", 56 | "placeholders": { 57 | "type": { 58 | "content": "$1", 59 | "example": "书签" 60 | } 61 | } 62 | }, 63 | "tip_notEmpty": { 64 | "message": "该字段不能为空哦 !!!", 65 | "description": "表格字段不能为空的提示语" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/background/main.ts: -------------------------------------------------------------------------------- 1 | import { findActiveTab, sendMsg, sendMsgToActiveTab } from '@/api' 2 | import filterSearchData from '@/background/filter-search-data' 3 | import handleSelectedItem from '@/background/handle-selected-item' 4 | import bindOmniboxEvent from '@/background/omnibox' 5 | import { browser } from 'webextension-polyfill-ts' 6 | 7 | browser.commands.onCommand.addListener(async command => { 8 | // custom command 9 | if (command === 'open-in-content') { 10 | // find current tab, then send a message to show(or insert) extension dom 11 | sendMsgToActiveTab({ 12 | from: 'background', 13 | to: 'content-script', 14 | type: 'openExtension', 15 | }) 16 | } 17 | }) 18 | 19 | browser.runtime.onMessage.addListener(async (request: CMessage) => { 20 | if (request.type === 'select') { 21 | if (request.from === 'content') { 22 | const { id } = await findActiveTab() 23 | browser.tabs.sendMessage(id as number, { 24 | from: 'background', 25 | to: 'content-script', 26 | type: 'closeExtension', 27 | }) 28 | } 29 | handleSelectedItem(request.content) 30 | return 31 | } 32 | if (request.type === 'queryRequest') { 33 | const result = await filterSearchData(request.content) 34 | switch (request.from) { 35 | case 'popup': 36 | sendMsg({ 37 | from: 'background', 38 | to: 'popup', 39 | type: 'queryResult', 40 | content: result, 41 | }) 42 | break 43 | case 'content': 44 | sendMsgToActiveTab({ 45 | from: 'background', 46 | to: 'content', 47 | type: 'queryResult', 48 | content: result, 49 | }) 50 | break 51 | default: 52 | break 53 | } 54 | return 55 | } 56 | }) 57 | 58 | // handle omnibox event 59 | bindOmniboxEvent() 60 | -------------------------------------------------------------------------------- /src/types/crown.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' 2 | declare module '*.png' 3 | declare module '*.jpg' 4 | declare module '*.styl' 5 | declare module '*.json' { 6 | const value: any 7 | export default value 8 | } 9 | 10 | interface SingleOmniboxSearch { 11 | content: string 12 | description: string 13 | } 14 | 15 | type CSource = 'background' | 'content' | 'content-script' | 'popup' 16 | 17 | interface CMessageBasic { 18 | from: CSource 19 | to: CSource 20 | } 21 | 22 | interface CMessage1 extends CMessageBasic { 23 | type: 'select' 24 | content: QueryResultItem 25 | } 26 | 27 | interface CMessage2 extends CMessageBasic { 28 | type: 'openExtension' | 'closeExtension' 29 | } 30 | 31 | interface CMessage3 extends CMessageBasic { 32 | type: 'queryResult' 33 | content: QueryResultItem[] 34 | } 35 | interface CMessage4 extends CMessageBasic { 36 | type: 'queryRequest' 37 | content: string 38 | } 39 | 40 | type CMessage = CMessage1 | CMessage2 | CMessage3 | CMessage4 41 | 42 | type CMessageType = 'tab' | 'bookmark' | 'RCT' | 'keyword' 43 | 44 | interface QueryResultItem { 45 | /** item of search result's type */ 46 | type: CMessageType 47 | /** item of search result's main text */ 48 | title: string | undefined 49 | /** item of search result's sub text */ 50 | subtitle: string | undefined 51 | /** item id */ 52 | id: number | string 53 | /** the status is active(selected) ? */ 54 | active?: boolean 55 | /** item keyword */ 56 | keyword?: string 57 | } 58 | 59 | interface SingleSearch { 60 | type: CMessageType 61 | searchQueue: string[] 62 | } 63 | 64 | interface ExtensionConfig { 65 | /** The related settings of bookmark */ 66 | bookmark: ItemSearchConfig 67 | /** The related settings of tab */ 68 | tab: ItemSearchConfig 69 | /** The related settings of recently closed tab */ 70 | RCT: ItemSearchConfig 71 | } 72 | 73 | interface ItemSearchConfig { 74 | /** Is set as default search option? */ 75 | isDefault: boolean 76 | /** The keyword to trigger the search */ 77 | keyword: string 78 | /** Related description */ 79 | desc: string 80 | } 81 | -------------------------------------------------------------------------------- /public/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extDesc": { 3 | "message": "A launchbar in your chrome, which make you chrome smoother!!!", 4 | "description": "Extension description" 5 | }, 6 | "contentHotKey": { 7 | "message": "open crown extension in your current page", 8 | "description": "the hot key's tip to open content-script" 9 | }, 10 | "searchPlaceholder": { 11 | "message": "My Lord ...", 12 | "description": "the input placeholder in searchView" 13 | }, 14 | "bookmark": { 15 | "message": "Bookmarks" 16 | }, 17 | "bookmark_label": { 18 | "message": "BM", 19 | "description": "bookmark label in search list" 20 | }, 21 | "tab": { 22 | "message": "Tabs" 23 | }, 24 | "tab_label": { 25 | "message": "Tab", 26 | "description": "bookmark label in search list" 27 | }, 28 | "RCT": { 29 | "message": "Recently Closed Tabs" 30 | }, 31 | "RCT_label": { 32 | "message": "RCT", 33 | "description": "Recently closed tabs label in search list" 34 | }, 35 | "keyword_label": { 36 | "message": "keyword", 37 | "description": "keyword label in search list" 38 | }, 39 | "contentScript_unavailable": { 40 | "message": "Due to browser limitations, you won't be able to wake up Crown extensions on partially restricted pages or when pages are not loaded.", 41 | "description": "a tip when content-script is unavailable" 42 | }, 43 | "isSetDefault": { 44 | "message": "Whether the results of $TYPE$ will appear in your default search results ?", 45 | "description": "Is it set to a part of the default search results?", 46 | "placeholders": { 47 | "type": { 48 | "content": "$1", 49 | "example": "bookmarks" 50 | } 51 | } 52 | }, 53 | "keywordOfSet": { 54 | "message": "The keyword used to search for $TYPE$", 55 | "description": "set the keyword to search", 56 | "placeholders": { 57 | "type": { 58 | "content": "$1", 59 | "example": "bookmarks" 60 | } 61 | } 62 | }, 63 | "tip_notEmpty": { 64 | "message": "This field is required !!!", 65 | "description": "The tip which the field is not allowed to be empty" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/genarate-manifest.ts: -------------------------------------------------------------------------------- 1 | let background_scripts, content_scripts, web_accessible_resources 2 | 3 | if (process.env.NODE_ENV === 'development') { 4 | background_scripts = ['chunk-vendors.js', 'background.js'] 5 | content_scripts = [ 6 | { 7 | matches: [''], 8 | js: ['chunk-vendors.js', 'content-script.js'], 9 | }, 10 | ] 11 | web_accessible_resources = ['main.html', '*.js'] 12 | } else { 13 | background_scripts = ['js/chunk-vendors.js', 'js/background.js'] 14 | content_scripts = [ 15 | { 16 | matches: [''], 17 | css: ['css/content-script.css'], 18 | js: ['js/chunk-vendors.js', 'js/content-script.js'], 19 | }, 20 | ] 21 | web_accessible_resources = ['main.html', 'js/*.js', 'css/*.css'] 22 | } 23 | 24 | module.exports = { 25 | manifest_version: 2, 26 | version: process.env.VUE_APP_VERSION, 27 | name: 'Crown', 28 | author: '@chenjuncrown', 29 | homepage_url: 'https://crown3.github.io/crown/', 30 | description: '__MSG_extDesc__', 31 | default_locale: 'en', 32 | icons: { 33 | '16': 'icons/16.png', 34 | '48': 'icons/48.png', 35 | '128': 'icons/128.png', 36 | }, 37 | permissions: ['bookmarks', 'sessions', 'tabs', ''], 38 | background: { 39 | scripts: background_scripts, 40 | persistent: false, 41 | }, 42 | browser_action: { 43 | default_popup: 'main.html', 44 | default_title: 'Crown', 45 | default_icon: { 46 | '19': 'icons/19.png', 47 | '38': 'icons/38.png', 48 | }, 49 | }, 50 | offline_enabled: true, 51 | options_ui: { 52 | chrome_style: true, 53 | page: 'main.html#/options', 54 | }, 55 | content_scripts, 56 | content_security_policy: 57 | "script-src 'self' 'unsafe-eval' https://ssl.google-analytics.com; object-src 'self'", 58 | web_accessible_resources, 59 | commands: { 60 | _execute_browser_action: { 61 | suggested_key: { 62 | default: 'Alt+X', 63 | }, 64 | }, 65 | 'open-in-content': { 66 | description: '__MSG_contentHotKey__', 67 | global: true, 68 | suggested_key: { 69 | default: 'Alt+S', 70 | }, 71 | }, 72 | }, 73 | omnibox: { 74 | keyword: 'c', 75 | }, 76 | } 77 | -------------------------------------------------------------------------------- /src/background/omnibox.ts: -------------------------------------------------------------------------------- 1 | import filterSearchData from '@/background/filter-search-data' 2 | import handleSelectedItem from '@/background/handle-selected-item' 3 | import { encodeXml } from '@/common/utils' 4 | import { browser } from 'webextension-polyfill-ts' 5 | 6 | // Cache search results 7 | const searchResults: QueryResultItem[] = [] 8 | 9 | // Set a default prompt 10 | function setSuggestion(describe: string) { 11 | /** 12 | * The supported tags are 13 | * 'url' (for a literal URL), 14 | * 'match' (for highlighting text that matched what the user's query), 15 | * 'dim' (for dim helper text). 16 | * @example 'src: Search Chromium source' 17 | */ 18 | browser.omnibox.setDefaultSuggestion({ 19 | description: describe, 20 | }) 21 | } 22 | 23 | function bindOmniboxEvent() { 24 | // User has started a keyword input session by typing the extension's keyword. 25 | browser.omnibox.onInputStarted.addListener(() => { 26 | setSuggestion('My lord ...') 27 | }) 28 | 29 | // handle search results 30 | browser.omnibox.onInputChanged.addListener(async (text, suggest) => { 31 | if (!text) { 32 | return 33 | } 34 | const result = await filterSearchData(text) 35 | const temp: SingleOmniboxSearch[] = [] 36 | const regex = new RegExp(`${text.replace(/\s+/g, '|')}`, 'gi') 37 | searchResults.length = 0 38 | 39 | result.some(item => { 40 | // 提前终止循环 41 | if (item.type !== 'keyword') { 42 | temp.push({ 43 | content: `${item.title} @index=${temp.length + 1}`, 44 | description: `${item.type}: ${`${encodeXml( 45 | item.title as string 46 | )} - ${encodeXml(item.subtitle as string)}`.replace( 47 | regex, 48 | '$&' 49 | )}`, 50 | }) 51 | 52 | searchResults.push(item) 53 | 54 | return temp.length > 4 55 | } 56 | return false 57 | }) 58 | 59 | suggest(temp) 60 | }) 61 | 62 | // handle selecting omnibox list item 63 | browser.omnibox.onInputEntered.addListener((...args: string[]) => { 64 | /** 65 | * @param 66 | * str: adressbar text 67 | * disposition: "currentTab", "newForegroundTab", or "newBackgroundTab". This is the recommended context to display results. For example, if the omnibox command is to navigate to a certain URL, a disposition of 'newForegroundTab' means the navigation should take place in a new selected tab. 68 | */ 69 | const index = +args[0].split('@index=').slice(-1) 70 | handleSelectedItem(searchResults[index - 1]) 71 | }) 72 | } 73 | 74 | export default bindOmniboxEvent 75 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path') 2 | const ExtensionReload = require('webpack-chrome-extension-reloader') 3 | const GenerateJsonPlugin = require('generate-json-webpack-plugin') 4 | 5 | const isDev = process.env.NODE_ENV === 'development' 6 | 7 | process.env.VUE_APP_VERSION = require('./package.json').version 8 | 9 | module.exports = { 10 | pages: { 11 | main: 'src/main.ts', 12 | 'content-script': 'src/content-script.ts', 13 | background: 'src/background/main.ts', 14 | }, 15 | 16 | // custom webpack config 17 | filenameHashing: false, 18 | 19 | configureWebpack: { 20 | optimization: { 21 | // just split js 22 | splitChunks: { 23 | cacheGroups: { 24 | vendors: { 25 | name: 'chunk-vendors', 26 | test: /[\\\/]node_modules[\\\/].*js/, // eslint-disable-line no-useless-escape 27 | minChunks: 2, 28 | priority: -10, 29 | chunks: 'initial', 30 | }, 31 | common: { 32 | name: 'chunk-common', 33 | test: /\.js$/, 34 | minChunks: 2, 35 | priority: -20, 36 | chunks: 'initial', 37 | reuseExistingChunk: true, 38 | }, 39 | }, 40 | }, 41 | }, 42 | }, 43 | 44 | chainWebpack: config => { 45 | if (isDev) { 46 | config 47 | .plugin('extension-hotreload') 48 | .use(ExtensionReload, [{ reloadPage: true }]) 49 | } 50 | 51 | // generate manifest json 52 | config 53 | .plugin('generate-manifest') 54 | .use(GenerateJsonPlugin, [ 55 | 'manifest.json', 56 | require('./src/genarate-manifest.ts'), 57 | undefined, 58 | isDev ? 2 : 0, 59 | ]) 60 | 61 | // Don't generate content-script's html file 62 | config.plugins.delete('html-content-script').delete('html-background') 63 | 64 | config.resolve.alias 65 | .set('@c', resolve('src/components')) // common alias 66 | .set('lodash-es', 'lodash') // reduce lodash size 67 | 68 | // Image Compression 69 | config.module 70 | .rule('imgCompression') 71 | .test(/\.(jpe?g|png|gif|svg)$/) 72 | .pre() 73 | .use('image-webpack-loader') 74 | .loader('image-webpack-loader') 75 | 76 | // vuetify 77 | config.module 78 | .rule('vue') 79 | .use('vue-loader') 80 | .loader('vue-loader') 81 | .tap(options => 82 | Object.assign(options, { 83 | transformAssetUrls: { 84 | 'v-img': ['src', 'lazy-src'], 85 | 'v-card': 'src', 86 | 'v-card-media': 'src', 87 | 'v-responsive': 'src', 88 | //... 89 | }, 90 | }) 91 | ) 92 | }, 93 | } 94 | -------------------------------------------------------------------------------- /src/background/filter-search-data.ts: -------------------------------------------------------------------------------- 1 | import { queryBM, queryRecentLyClosed, queryTab } from '@/api' 2 | import { getKeys, isEachEligible } from '@/common/utils' 3 | import store from '@/store' 4 | 5 | const extConfig: Readonly = store.state.config 6 | 7 | // Match related keyword list 8 | function searchKeyword(splitSearchStr: string[]) { 9 | const temp: QueryResultItem[] = [] 10 | Object.values(extConfig).forEach(item => { 11 | if (isEachEligible(splitSearchStr, `${item.desc} ${item.keyword}`)) { 12 | temp.push({ 13 | type: 'keyword', 14 | title: `Search ${item.desc}`, 15 | subtitle: `Search ${item.desc} for "..."`, 16 | keyword: item.keyword, 17 | id: item.desc, 18 | }) 19 | } 20 | }) 21 | return temp 22 | } 23 | 24 | // Get an array which need to search 25 | function filterKeyword(splitSearchStr: string[]) { 26 | const temp: SingleSearch[] = [] 27 | getKeys(extConfig).some(key => { 28 | const value = extConfig[key] 29 | if (value.keyword === splitSearchStr[0]) { 30 | // Only search for a category 31 | temp.length = 0 32 | temp.push({ 33 | type: key, 34 | searchQueue: splitSearchStr.slice(1), 35 | }) 36 | return true 37 | } 38 | if (value.isDefault) { 39 | temp.push({ 40 | type: key, 41 | searchQueue: splitSearchStr, 42 | }) 43 | } 44 | return false 45 | }) 46 | // Whether it is all default search or only search for a category, the keyowrd category should show 47 | temp.unshift({ 48 | type: 'keyword', 49 | searchQueue: splitSearchStr, 50 | }) 51 | return temp 52 | } 53 | 54 | // Search different items based on different categories 55 | async function searchFromType(item: SingleSearch) { 56 | let result: QueryResultItem[] = [] 57 | switch (item.type) { 58 | case 'keyword': 59 | result = searchKeyword(item.searchQueue) 60 | break 61 | case 'bookmark': 62 | result = await queryBM(item.searchQueue) 63 | break 64 | case 'tab': 65 | result = await queryTab(item.searchQueue) 66 | break 67 | case 'RCT': 68 | result = await queryRecentLyClosed(item.searchQueue) 69 | break 70 | default: 71 | break 72 | } 73 | return result 74 | } 75 | 76 | async function filterSearchData(searchStr: string) { 77 | const temp: QueryResultItem[] = [] 78 | const strArr = searchStr.replace(/ +/g, ' ').split(' ') 79 | const searchPromises = filterKeyword(strArr).map(async item => { 80 | const itemResult = await searchFromType(item) 81 | return itemResult 82 | }) 83 | for (const searchPromise of searchPromises) { 84 | temp.push(...(await searchPromise)) 85 | } 86 | return temp 87 | } 88 | 89 | export default filterSearchData 90 | -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | dest: 'gh-pages', 3 | base: '/crown/', 4 | locales: { 5 | '/': { 6 | lang: 'en-US', 7 | title: 'Crown', 8 | description: 'A quick launchbar in your chrome, may be your next Alfred in Chrome' 9 | }, 10 | '/zh/': { 11 | lang: 'zh-CN', 12 | title: 'Crown', 13 | description: '一款 Chrome 上的 launchbar, 希望能成为你 Chrome 上的 Alfred' 14 | } 15 | }, 16 | head: [ 17 | ['link', { 18 | rel: 'icon', 19 | href: `/img/crown.png` 20 | }], 21 | ['link', { 22 | rel: 'manifest', 23 | href: '/manifest.json' 24 | }], 25 | ['meta', { 26 | name: 'theme-color', 27 | content: '#3eaf7c' 28 | }], 29 | ['meta', { 30 | name: 'apple-mobile-web-app-capable', 31 | content: 'yes' 32 | }], 33 | ['meta', { 34 | name: 'apple-mobile-web-app-status-bar-style', 35 | content: 'black' 36 | }], 37 | ['link', { 38 | rel: 'apple-touch-icon', 39 | href: `/img/crown.png` 40 | }], 41 | ['link', { 42 | rel: 'mask-icon', 43 | href: '/img/crown.svg', 44 | color: '#3eaf7c' 45 | }], 46 | ['meta', { 47 | name: 'msapplication-TileImage', 48 | content: '/img/crown.png' 49 | }], 50 | ['meta', { 51 | name: 'msapplication-TileColor', 52 | content: '#000000' 53 | }] 54 | ], 55 | serviceWorker: true, 56 | themeConfig: { 57 | repo: 'crown3/crown', 58 | editLinks: true, 59 | docsDir: 'docs', 60 | locales: { 61 | '/': { 62 | label: 'English', 63 | selectText: 'Languages', 64 | editLinkText: 'Edit this page on GitHub', 65 | lastUpdated: 'Last Updated', 66 | nav: [{ 67 | text: 'Guide', 68 | link: '/guide/', 69 | }, 70 | { 71 | text: 'Code', 72 | link: '/code/' 73 | }, 74 | { 75 | text: 'About', 76 | link: '/about/' 77 | }, 78 | { 79 | text: 'Change log', 80 | link: '/changelog/' 81 | }, 82 | ], 83 | }, 84 | '/zh/': { 85 | label: '简体中文', 86 | selectText: '选择语言', 87 | editLinkText: '在 GitHub 上编辑此页', 88 | lastUpdated: '上次更新', 89 | nav: [{ 90 | text: '指南', 91 | link: '/zh/guide/', 92 | }, 93 | { 94 | text: '代码', 95 | link: '/zh/code/' 96 | }, 97 | { 98 | text: '关于', 99 | link: '/zh/about/' 100 | }, 101 | { 102 | text: '更新记录', 103 | link: '/zh/changelog/' 104 | }, 105 | ], 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/views/Options.vue: -------------------------------------------------------------------------------- 1 | 69 | 70 | 117 | 118 | 121 | -------------------------------------------------------------------------------- /src/api/index.ts: -------------------------------------------------------------------------------- 1 | import { isEachEligible } from '@/common/utils' 2 | import { browser } from 'webextension-polyfill-ts' 3 | 4 | /** 5 | * To get some bookmark that meets the requirements 6 | */ 7 | async function queryBM(queryQueue: string[]) { 8 | /** 9 | * Multiple queries only need to be deduplicated on the results of the first query, 10 | * without having to query the second pass 11 | */ 12 | const len = queryQueue.length 13 | if (!len) { 14 | return [] 15 | } 16 | const result = await browser.bookmarks.search(queryQueue[0]) 17 | const temp: QueryResultItem[] = [] 18 | for (const item of result) { 19 | if (item.dateGroupModified) { 20 | break 21 | } 22 | if (len === 1 || isEachEligible(queryQueue, `${item.url} ${item.title}`)) { 23 | temp.push({ 24 | type: 'bookmark', 25 | title: item.title || item.url, 26 | subtitle: item.url, 27 | id: item.id, 28 | }) 29 | } 30 | } 31 | return temp 32 | } 33 | 34 | /** 35 | * To get some tab that meets the requirements 36 | */ 37 | async function queryTab(queryQueue: string[]) { 38 | // Similar to the above queryBM method 39 | const result = await browser.tabs.query({ 40 | windowId: browser.windows.WINDOW_ID_CURRENT, 41 | windowType: 'normal', 42 | }) 43 | const temp: QueryResultItem[] = [] 44 | for (const item of result) { 45 | // if length === 0, show all tabs data 46 | if ( 47 | queryQueue.length === 0 || 48 | isEachEligible(queryQueue, `${item.url} ${item.title}`) 49 | ) { 50 | temp.push({ 51 | type: 'tab', 52 | title: item.title, 53 | subtitle: item.url, 54 | 55 | active: item.highlighted && item.active, 56 | id: item.id as number, 57 | }) 58 | } 59 | } 60 | return temp 61 | } 62 | 63 | /** 64 | * send message to the special tab. 65 | * @link that extensions cannot send messages to content scripts using browser.runtime.sendMessage. To send messages to content scripts, use tabs.sendMessage. 66 | */ 67 | async function sendMsgToActiveTab(data: CMessage) { 68 | try { 69 | const { id } = await findActiveTab() 70 | await browser.tabs.sendMessage(id as number, data) 71 | } catch (error) { 72 | // if currnet tab isn't available, alert a tip 73 | alert(browser.i18n.getMessage('contentScript_unavailable')) 74 | } 75 | } 76 | 77 | /** 78 | * send message with using browser.runtime 79 | */ 80 | function sendMsg(data: CMessage) { 81 | browser.runtime.sendMessage(data) 82 | } 83 | 84 | /** 85 | * find active tab which you are focusing 86 | */ 87 | async function findActiveTab() { 88 | const result = await browser.tabs.query({ 89 | active: true, 90 | currentWindow: true, 91 | }) 92 | return result[0] 93 | } 94 | 95 | /** 96 | * update the specified tab status 97 | */ 98 | function updateTabStatus( 99 | id: number, 100 | status = { 101 | highlighted: true, 102 | active: true, 103 | } 104 | ) { 105 | browser.tabs.update(id, status) 106 | } 107 | 108 | /** 109 | * open a new tab with the specified url 110 | */ 111 | function openNewTab(url: string) { 112 | browser.tabs.create({ url }) 113 | } 114 | 115 | /** 116 | * query recently cloed tab 117 | */ 118 | async function queryRecentLyClosed(queryQueue: string[]) { 119 | const result = await browser.sessions.getRecentlyClosed({ maxResults: 25 }) 120 | const temp: QueryResultItem[] = [] 121 | for (const item of result) { 122 | // filter tab 123 | if (!item.tab) { 124 | break 125 | } 126 | if ( 127 | queryQueue.length === 0 || 128 | isEachEligible(queryQueue, `${item.tab.url} ${item.tab.title}`) 129 | ) { 130 | temp.push({ 131 | type: 'RCT', 132 | title: item.tab.title, 133 | subtitle: item.tab.url, 134 | id: item.tab.sessionId as string, 135 | }) 136 | } 137 | } 138 | return temp 139 | } 140 | 141 | /** 142 | * reopen the tab which recently closed 143 | */ 144 | function restoreRecentTab(sessionId: string) { 145 | browser.sessions.restore(sessionId) 146 | } 147 | 148 | export { 149 | findActiveTab, 150 | openNewTab, 151 | queryBM, 152 | queryRecentLyClosed, 153 | queryTab, 154 | restoreRecentTab, 155 | sendMsg, 156 | sendMsgToActiveTab, 157 | updateTabStatus, 158 | } 159 | -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/searchView.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 164 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig 2 | 3 | # Created by https://www.gitignore.io/api/node,sublimetext,visualstudiocode,webstorm,stylus 4 | 5 | ### Node ### 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # Optional npm cache directory 48 | .npm 49 | 50 | # Optional eslint cache 51 | .eslintcache 52 | 53 | # Optional REPL history 54 | .node_repl_history 55 | 56 | # Output of 'npm pack' 57 | *.tgz 58 | 59 | # Yarn Integrity file 60 | .yarn-integrity 61 | 62 | # dotenv environment variables file 63 | .env.local 64 | .env.*.local 65 | 66 | # parcel-bundler cache (https://parceljs.org/) 67 | .cache 68 | 69 | # next.js build output 70 | .next 71 | 72 | # nuxt.js build output 73 | .nuxt 74 | 75 | # vuepress build output 76 | .vuepress/dist 77 | 78 | # Serverless directories 79 | .serverless 80 | 81 | ### Stylus ### 82 | *.css 83 | 84 | ### SublimeText ### 85 | # Cache files for Sublime Text 86 | *.tmlanguage.cache 87 | *.tmPreferences.cache 88 | *.stTheme.cache 89 | 90 | # Workspace files are user-specific 91 | *.sublime-workspace 92 | 93 | # Project files should be checked into the repository, unless a significant 94 | # proportion of contributors will probably not be using Sublime Text 95 | # *.sublime-project 96 | 97 | # SFTP configuration file 98 | sftp-config.json 99 | 100 | # Package control specific files 101 | Package Control.last-run 102 | Package Control.ca-list 103 | Package Control.ca-bundle 104 | Package Control.system-ca-bundle 105 | Package Control.cache/ 106 | Package Control.ca-certs/ 107 | Package Control.merged-ca-bundle 108 | Package Control.user-ca-bundle 109 | oscrypto-ca-bundle.crt 110 | bh_unicode_properties.cache 111 | 112 | # Sublime-github package stores a github token in this file 113 | # https://packagecontrol.io/packages/sublime-github 114 | GitHub.sublime-settings 115 | 116 | ### VisualStudioCode ### 117 | .vscode/* 118 | !.vscode/settings.json 119 | !.vscode/tasks.json 120 | !.vscode/launch.json 121 | !.vscode/extensions.json 122 | 123 | ### WebStorm ### 124 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 125 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 126 | 127 | # User-specific stuff 128 | .idea/**/workspace.xml 129 | .idea/**/tasks.xml 130 | .idea/**/usage.statistics.xml 131 | .idea/**/dictionaries 132 | .idea/**/shelf 133 | 134 | # Generated files 135 | .idea/**/contentModel.xml 136 | 137 | # Sensitive or high-churn files 138 | .idea/**/dataSources/ 139 | .idea/**/dataSources.ids 140 | .idea/**/dataSources.local.xml 141 | .idea/**/sqlDataSources.xml 142 | .idea/**/dynamic.xml 143 | .idea/**/uiDesigner.xml 144 | .idea/**/dbnavigator.xml 145 | 146 | # Gradle 147 | .idea/**/gradle.xml 148 | .idea/**/libraries 149 | 150 | # Gradle and Maven with auto-import 151 | # When using Gradle or Maven with auto-import, you should exclude module files, 152 | # since they will be recreated, and may cause churn. Uncomment if using 153 | # auto-import. 154 | # .idea/modules.xml 155 | # .idea/*.iml 156 | # .idea/modules 157 | 158 | # CMake 159 | cmake-build-*/ 160 | 161 | # Mongo Explorer plugin 162 | .idea/**/mongoSettings.xml 163 | 164 | # File-based project format 165 | *.iws 166 | 167 | # IntelliJ 168 | out/ 169 | 170 | # mpeltonen/sbt-idea plugin 171 | .idea_modules/ 172 | 173 | # JIRA plugin 174 | atlassian-ide-plugin.xml 175 | 176 | # Cursive Clojure plugin 177 | .idea/replstate.xml 178 | 179 | # Crashlytics plugin (for Android Studio and IntelliJ) 180 | com_crashlytics_export_strings.xml 181 | crashlytics.properties 182 | crashlytics-build.properties 183 | fabric.properties 184 | 185 | # Editor-based Rest Client 186 | .idea/httpRequests 187 | 188 | # Android studio 3.1+ serialized cache file 189 | .idea/caches/build_file_checksums.ser 190 | 191 | ### WebStorm Patch ### 192 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 193 | 194 | # *.iml 195 | # modules.xml 196 | # .idea/misc.xml 197 | # *.ipr 198 | 199 | # Sonarlint plugin 200 | .idea/sonarlint 201 | 202 | 203 | # End of https://www.gitignore.io/api/node,sublimetext,visualstudiocode,webstorm,stylus 204 | 205 | # Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option) 206 | 207 | /dist 208 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "crown--chrome_extension", 3 | "version": "2.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vue-cli-service build --watch --mode development", 7 | "build": "vue-cli-service build", 8 | "analyze": "vue-cli-service build --report && opn ./dist/report.html", 9 | "lint": "vue-cli-service lint", 10 | "test:unit": "vue-cli-service test:unit", 11 | "docs": "vuepress dev docs", 12 | "docs:build": "vuepress build docs" 13 | }, 14 | "dependencies": { 15 | "vue": "^2.5.17", 16 | "vue-router": "^3.0.1", 17 | "vuetify": "^1.3.9", 18 | "vuex": "^3.0.1", 19 | "vuex-persistedstate": "^2.5.4", 20 | "webextension-polyfill-ts": "^0.8.9" 21 | }, 22 | "devDependencies": { 23 | "@semantic-release/changelog": "^3.0.2", 24 | "@semantic-release/commit-analyzer": "^6.1.0", 25 | "@semantic-release/github": "^5.2.8", 26 | "@semantic-release/release-notes-generator": "^7.1.4", 27 | "@types/jest": "^23.1.4", 28 | "@vue/cli-plugin-babel": "^3.1.1", 29 | "@vue/cli-plugin-eslint": "^3.1.5", 30 | "@vue/cli-plugin-typescript": "^3.1.1", 31 | "@vue/cli-plugin-unit-jest": "^3.1.1", 32 | "@vue/cli-service": "^3.1.4", 33 | "@vue/eslint-config-prettier": "^4.0.0", 34 | "@vue/eslint-config-typescript": "^3.1.0", 35 | "@vue/test-utils": "^1.0.0-beta.20", 36 | "babel-core": "7.0.0-bridge.0", 37 | "babel-eslint": "^10.0.1", 38 | "eslint": "^5.8.0", 39 | "eslint-plugin-vue": "^5.0.0-0", 40 | "generate-json-webpack-plugin": "^0.3.1", 41 | "image-webpack-loader": "^4.5.0", 42 | "lint-staged": "^7.2.2", 43 | "opn-cli": "^4.0.0", 44 | "semantic-release": "^15.13.2", 45 | "stylus": "^0.54.5", 46 | "stylus-loader": "^3.0.2", 47 | "ts-jest": "^23.0.0", 48 | "typescript": "^3.0.0", 49 | "vue-template-compiler": "^2.5.17", 50 | "vuepress": "^0.14.5", 51 | "webpack-chrome-extension-reloader": "^0.8.3" 52 | }, 53 | "eslintConfig": { 54 | "root": true, 55 | "env": { 56 | "node": true 57 | }, 58 | "extends": [ 59 | "plugin:vue/essential", 60 | "@vue/prettier", 61 | "@vue/typescript" 62 | ], 63 | "rules": { 64 | "prettier/prettier": [ 65 | "warn", 66 | { 67 | "singleQuote": true, 68 | "semi": false, 69 | "trailingComma": "es5" 70 | } 71 | ] 72 | }, 73 | "parserOptions": { 74 | "parser": "typescript-eslint-parser" 75 | } 76 | }, 77 | "postcss": { 78 | "plugins": { 79 | "autoprefixer": {} 80 | } 81 | }, 82 | "browserslist": [ 83 | "last 2 Chrome versions" 84 | ], 85 | "jest": { 86 | "moduleFileExtensions": [ 87 | "js", 88 | "jsx", 89 | "json", 90 | "vue", 91 | "ts", 92 | "tsx" 93 | ], 94 | "transform": { 95 | "^.+\\.vue$": "vue-jest", 96 | ".+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$": "jest-transform-stub", 97 | "^.+\\.tsx?$": "ts-jest" 98 | }, 99 | "moduleNameMapper": { 100 | "^@/(.*)$": "/src/$1" 101 | }, 102 | "snapshotSerializers": [ 103 | "jest-serializer-vue" 104 | ], 105 | "testMatch": [ 106 | "**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)" 107 | ], 108 | "testURL": "http://localhost/" 109 | }, 110 | "gitHooks": { 111 | "pre-commit": "lint-staged" 112 | }, 113 | "lint-staged": { 114 | "*.js": [ 115 | "vue-cli-service lint", 116 | "git add" 117 | ], 118 | "*.vue": [ 119 | "vue-cli-service lint", 120 | "git add" 121 | ] 122 | }, 123 | "release": { 124 | "plugins": [ 125 | [ 126 | "@semantic-release/commit-analyzer", 127 | { 128 | "preset": "angular", 129 | "releaseRules": [ 130 | { 131 | "type": "docs", 132 | "scope": "README", 133 | "release": "patch" 134 | }, 135 | { 136 | "type": "refactor", 137 | "release": "patch" 138 | }, 139 | { 140 | "type": "style", 141 | "release": "patch" 142 | } 143 | ], 144 | "parserOpts": { 145 | "noteKeywords": [ 146 | "BREAKING CHANGE", 147 | "BREAKING CHANGES" 148 | ] 149 | } 150 | } 151 | ], 152 | [ 153 | "@semantic-release/release-notes-generator", 154 | { 155 | "preset": "angular", 156 | "parserOpts": { 157 | "noteKeywords": [ 158 | "BREAKING CHANGE", 159 | "BREAKING CHANGES", 160 | "BREAKING" 161 | ] 162 | }, 163 | "writerOpts": { 164 | "commitsSort": [ 165 | "subject", 166 | "scope" 167 | ] 168 | } 169 | } 170 | ], 171 | [ 172 | "@semantic-release/changelog", 173 | { 174 | "changelogFile": "docs/CHANGELOG.md" 175 | } 176 | ], 177 | [ 178 | "@semantic-release/github", 179 | { 180 | "assets": [ 181 | { 182 | "path": "dist/", 183 | "label": "all file" 184 | } 185 | ] 186 | } 187 | ] 188 | ] 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /docs/.vuepress/public/img/crown.svg: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------