├── 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 |
2 |
3 |
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 |
2 |
3 |
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 |
2 |
3 |
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 |
2 |
3 |
4 |
5 | # {{ I18n('bookmark') }}
6 |
7 |
8 | updateIsDefault('bookmark', value)"
13 | class="body-1"
14 | >
15 |
16 |
17 | updateKeyword('bookmark', value)"
22 | ref="key_bookmark"
23 | >
24 |
25 |
26 | # {{ I18n('tab') }}
27 |
28 |
29 | updateIsDefault('tab', value)"
34 | >
35 |
36 |
37 | updateKeyword('tab', value)"
42 | ref="key_tab"
43 | >
44 |
45 |
46 |
47 |
48 | updateIsDefault('RCT', value)"
53 | >
54 |
55 |
56 | updateKeyword('RCT', value)"
61 | ref="key_RCT"
62 | >
63 |
64 |
65 |
66 |
67 |
68 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
19 |
20 |
21 |
22 |
23 |
29 |
30 |
36 |
37 |
38 |
39 |
40 | {{ I18n(item.type + '_label') }}
41 |
42 | {{ item.subtitle }}
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
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 |
--------------------------------------------------------------------------------