├── static
├── .gitkeep
├── rocket.png
├── plugins
│ ├── superPanel
│ │ ├── assets
│ │ │ ├── link.png
│ │ │ ├── logo.png
│ │ │ ├── new.png
│ │ │ └── terminal.png
│ │ ├── index.html
│ │ └── index.js
│ ├── picker
│ │ ├── picker.js
│ │ ├── picker.css
│ │ └── index.html
│ ├── tpl
│ │ ├── doc.js
│ │ ├── index.js
│ │ ├── list.js
│ │ └── index.html
│ └── vue-router.min.js
├── utils.js
└── preload.js
├── src
├── renderer
│ ├── assets
│ │ ├── .gitkeep
│ │ ├── logo.png
│ │ ├── imgs
│ │ │ ├── help.png
│ │ │ ├── lock.png
│ │ │ ├── picker.png
│ │ │ └── screenshot.png
│ │ ├── api
│ │ │ ├── config.js
│ │ │ ├── index.js
│ │ │ ├── list
│ │ │ │ ├── banner.js
│ │ │ │ └── plugin.js
│ │ │ └── request.js
│ │ ├── common
│ │ │ ├── system.js
│ │ │ ├── constans.js
│ │ │ └── utils.js
│ │ └── keycode.js
│ ├── store
│ │ ├── index.js
│ │ └── modules
│ │ │ ├── index.js
│ │ │ ├── dev.js
│ │ │ └── main.js
│ ├── router
│ │ └── index.js
│ ├── main.js
│ ├── pages
│ │ ├── index
│ │ │ └── index.vue
│ │ ├── search
│ │ │ ├── index.vue
│ │ │ └── subpages
│ │ │ │ ├── plugin.vue
│ │ │ │ ├── market.vue
│ │ │ │ ├── dev.vue
│ │ │ │ └── settings.vue
│ │ └── plugins
│ │ │ └── index.vue
│ └── App.vue
├── main
│ ├── browsers
│ │ ├── index.js
│ │ ├── picker.js
│ │ ├── separate.js
│ │ ├── main.js
│ │ └── superPanel.js
│ ├── common
│ │ ├── common.js
│ │ ├── autoUpdate.js
│ │ ├── config.js
│ │ ├── utils.js
│ │ ├── api.js
│ │ └── listener.js
│ ├── index.dev.js
│ ├── index.js
│ └── tray.js
└── index.ejs
├── .gitignore
├── .github
└── ISSUE_TEMPLATE
│ ├── development-problem.md
│ ├── feature_request.md
│ └── bug-report.md
├── appveyor.yml
├── .babelrc
├── .travis.yml
├── LICENSE
├── .electron-vue
├── dev-client.js
├── webpack.main.config.js
├── build.js
├── webpack.web.config.js
├── dev-runner.js
└── webpack.renderer.config.js
├── README.md
└── package.json
/static/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/renderer/assets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/rocket.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangrongding/dtools/HEAD/static/rocket.png
--------------------------------------------------------------------------------
/src/renderer/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangrongding/dtools/HEAD/src/renderer/assets/logo.png
--------------------------------------------------------------------------------
/src/renderer/assets/imgs/help.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangrongding/dtools/HEAD/src/renderer/assets/imgs/help.png
--------------------------------------------------------------------------------
/src/renderer/assets/imgs/lock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangrongding/dtools/HEAD/src/renderer/assets/imgs/lock.png
--------------------------------------------------------------------------------
/src/renderer/assets/imgs/picker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangrongding/dtools/HEAD/src/renderer/assets/imgs/picker.png
--------------------------------------------------------------------------------
/src/renderer/assets/imgs/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangrongding/dtools/HEAD/src/renderer/assets/imgs/screenshot.png
--------------------------------------------------------------------------------
/static/plugins/superPanel/assets/link.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangrongding/dtools/HEAD/static/plugins/superPanel/assets/link.png
--------------------------------------------------------------------------------
/static/plugins/superPanel/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangrongding/dtools/HEAD/static/plugins/superPanel/assets/logo.png
--------------------------------------------------------------------------------
/static/plugins/superPanel/assets/new.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangrongding/dtools/HEAD/static/plugins/superPanel/assets/new.png
--------------------------------------------------------------------------------
/static/plugins/superPanel/assets/terminal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangrongding/dtools/HEAD/static/plugins/superPanel/assets/terminal.png
--------------------------------------------------------------------------------
/src/renderer/assets/api/config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | development: 'http://118.195.176.247:8080',
3 | production: 'http://118.195.176.247:8080',
4 | };
5 |
--------------------------------------------------------------------------------
/src/renderer/assets/api/index.js:
--------------------------------------------------------------------------------
1 | import plugin from './list/plugin';
2 | import banner from './list/banner';
3 |
4 | export default {
5 | plugin,
6 | banner
7 | }
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | dist/electron/*
3 | dist/web/*
4 | build/
5 | !build/icons
6 | node_modules/
7 | npm-debug.log
8 | npm-debug.log.*
9 | thumbs.db
10 | !.gitkeep
11 | .idea
12 | dist/
13 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/development-problem.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Development Problem
3 | about: 任何开发建议、使用问题、交流学习都可以
4 | title: ''
5 | labels: help wanted
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## 任何开发建议、交流学习都可以
11 |
--------------------------------------------------------------------------------
/src/main/browsers/index.js:
--------------------------------------------------------------------------------
1 | module.exports = () => ({
2 | picker: require("./picker")(),
3 | separator: require("./separate")(),
4 | superPanel: require("./superPanel")(),
5 | main: require("./main")(),
6 | });
7 |
--------------------------------------------------------------------------------
/src/renderer/assets/api/list/banner.js:
--------------------------------------------------------------------------------
1 | import instance from '../request';
2 |
3 | export default {
4 | async query(params) {
5 | const result = await instance.get('/banner/query', {params});
6 | return result.data;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/renderer/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 |
4 | import modules from './modules'
5 |
6 | Vue.use(Vuex)
7 | export default new Vuex.Store({
8 | modules,
9 | strict: process.env.NODE_ENV !== 'production'
10 | })
11 |
--------------------------------------------------------------------------------
/src/renderer/assets/api/request.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import config from "./config";
3 |
4 | const instance = axios.create({
5 | baseURL: config[process.env.NODE_ENV],
6 | timeout: 10000,
7 | withCredentials: true
8 | });
9 |
10 | export default instance;
11 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: 提交一个新特性/功能
4 | title: ''
5 | labels: feature
6 | assignees: ''
7 |
8 | ---
9 |
10 | **您的功能请求是否与问题相关? 请简单描述.**
11 | 清晰简明地描述问题是什么. Ex. I'm always frustrated when [...]
12 |
13 | **请描述一下您想要的解决方案**
14 | 清晰简明地描述您想要发生的事情。
15 |
16 | **描述你考虑过的替代方案**
17 | 清晰简洁地描述您所考虑的任何替代解决方案或功能。
18 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: 报告一个bug
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **描述一下这个bug**
11 | 清楚而简洁地描述了错误是什么
12 |
13 | **复现方式**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **预期行为**
21 | 清晰简明地描述了您预期的发生。
22 |
23 | **截图**
24 | 如果可以,请添加屏幕截图以帮助解释您的问题。
25 |
--------------------------------------------------------------------------------
/src/renderer/store/modules/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * The file enables `@/store/index.js` to import all vuex modules
3 | * in a one-shot manner. There should not be any reason to edit this file.
4 | */
5 |
6 | const files = require.context('.', false, /\.js$/)
7 | const modules = {}
8 |
9 | files.keys().forEach(key => {
10 | if (key === './index.js') return
11 | modules[key.replace(/(\.\/|\.js)/g, '')] = files(key).default
12 | })
13 |
14 | export default modules
15 |
--------------------------------------------------------------------------------
/src/renderer/store/modules/dev.js:
--------------------------------------------------------------------------------
1 | const state = {
2 | main: 0
3 | }
4 |
5 | const mutations = {
6 | DECREMENT_MAIN_COUNTER (state) {
7 | state.main--
8 | },
9 | INCREMENT_MAIN_COUNTER (state) {
10 | state.main++
11 | }
12 | }
13 |
14 | const actions = {
15 | someAsyncTask ({ commit }) {
16 | // do something async
17 | commit('INCREMENT_MAIN_COUNTER')
18 | }
19 | }
20 |
21 | export default {
22 | state,
23 | mutations,
24 | actions
25 | }
26 |
--------------------------------------------------------------------------------
/src/renderer/assets/api/list/plugin.js:
--------------------------------------------------------------------------------
1 | import instance from '../request';
2 |
3 | export default {
4 | async add(params) {
5 | const result = await instance.post('/plugin/create', params);
6 | return result.data;
7 | },
8 | async update(params) {
9 | const result = await instance.post('/plugin/update', params);
10 | return result.data;
11 | },
12 | async query(params) {
13 | const result = await instance.get('/plugin/query', {params});
14 | return result.data;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: 0.1.{build}
2 |
3 | branches:
4 | only:
5 | - master
6 |
7 | image: Visual Studio 2017
8 | platform:
9 | - x64
10 |
11 | cache:
12 | - node_modules
13 | - '%APPDATA%\npm-cache'
14 | - '%USERPROFILE%\.electron'
15 | - '%USERPROFILE%\AppData\Local\Yarn\cache'
16 |
17 | init:
18 | - git config --global core.autocrlf input
19 |
20 | install:
21 | - ps: Install-Product node 8 x64
22 | - git reset --hard HEAD
23 | - yarn
24 | - node --version
25 |
26 | build_script:
27 | - yarn build
28 |
29 | test: off
30 |
--------------------------------------------------------------------------------
/src/renderer/assets/common/system.js:
--------------------------------------------------------------------------------
1 | import {shell, ipcRenderer} from 'electron';
2 | export default {
3 | 'rubick-help': {
4 | help() {
5 | shell.openExternal('https://u.tools/docs/guide/about-uTools.html')
6 | }
7 | },
8 | 'rubick-color': {
9 | pick() {
10 | ipcRenderer.send('start-picker')
11 | }
12 | },
13 | 'rubick-screen-short-cut': {
14 | shortCut() {
15 | ipcRenderer.send('capture-screen', {type: 'start'})
16 | }
17 | },
18 | 'rubick-lock': {
19 | lock() {
20 | ipcRenderer.send('lock-screen');
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "comments": false,
3 | "env": {
4 | "main": {
5 | "presets": [
6 | ["env", {
7 | "targets": { "node": 7 }
8 | }],
9 | "stage-0"
10 | ]
11 | },
12 | "renderer": {
13 | "presets": [
14 | ["env", {
15 | "modules": false
16 | }],
17 | "stage-0"
18 | ]
19 | },
20 | "web": {
21 | "presets": [
22 | ["env", {
23 | "modules": false
24 | }],
25 | "stage-0"
26 | ]
27 | }
28 | },
29 | "plugins": ["transform-runtime"]
30 | }
31 |
--------------------------------------------------------------------------------
/static/utils.js:
--------------------------------------------------------------------------------
1 | const fs = require("fs");
2 |
3 | const getlocalDataFile = () => {
4 | let localDataFile = process.env.HOME;
5 | if (!localDataFile) {
6 | localDataFile = process.env.LOCALAPPDATA;
7 | }
8 | return localDataFile;
9 | };
10 |
11 | function saveData(path, value) {
12 | fs.writeFileSync(path, JSON.stringify(value));
13 | }
14 |
15 | function getData(path, defaultValue) {
16 | try {
17 | return JSON.parse(fs.readFileSync(path, 'utf8'));
18 | } catch (e) {
19 | return defaultValue || undefined;
20 | }
21 | }
22 |
23 | module.exports = {
24 | getlocalDataFile,
25 | saveData,
26 | getData
27 | }
28 |
--------------------------------------------------------------------------------
/static/plugins/picker/picker.js:
--------------------------------------------------------------------------------
1 | const {ipcRenderer} = require("electron");
2 | let colorDomBoxs = null;
3 |
4 | ipcRenderer.on("updatePicker", ((e, args) => {
5 | if (!colorDomBoxs) {
6 | colorDomBoxs = [];
7 | document.querySelectorAll(".content>div").forEach((e => {
8 | colorDomBoxs.push(e.querySelectorAll(":scope > div"))
9 | }));
10 | }
11 | for (let i = 0; i < 9; i ++){
12 | for (let j = 0; j < 9; j ++) {
13 | colorDomBoxs[i][j].style.background = '#' + args[i][j]
14 | }
15 | }
16 | }));
17 |
18 | document.addEventListener(
19 | "keydown",
20 | (event) => {
21 | if (event.key === "Escape") ipcRenderer.send("closePicker");
22 | },
23 | false
24 | );
25 |
--------------------------------------------------------------------------------
/src/main/common/common.js:
--------------------------------------------------------------------------------
1 | import {app} from 'electron';
2 | import './config';
3 | import Listener from './listener';
4 |
5 | export default function init(mainWindow) {
6 | const listener = new Listener();
7 |
8 | // 注册快捷键
9 | listener.registerShortCut(mainWindow);
10 | listener.init(mainWindow);
11 |
12 | // 设置开机启动
13 | const config = global.opConfig.get();
14 | app.setLoginItemSettings({
15 | openAtLogin: config.perf.common.start,
16 | openAsHidden: true,
17 | });
18 |
19 | mainWindow.once("ready-to-show", () => {
20 | // 非隐藏式启动需要显示主窗口
21 | if (!app.getLoginItemSettings().wasOpenedAsHidden) {
22 | mainWindow.show();
23 | }
24 | });
25 |
26 | // 打包后,失焦隐藏
27 | mainWindow.on('blur', () => {
28 | app.isPackaged && mainWindow.hide();
29 | });
30 |
31 | }
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/main/index.dev.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This file is used specifically and only for development. It installs
3 | * `electron-debug` & `vue-devtools`. There shouldn't be any need to
4 | * modify this file, but it can be used to extend your development
5 | * environment.
6 | */
7 |
8 | /* eslint-disable */
9 |
10 | // Install `electron-debug` with `devtron`
11 | require('electron-debug')({ showDevTools: true })
12 |
13 | // Install `vue-devtools`
14 | require('electron').app.on('ready', () => {
15 | let installExtension = require('electron-devtools-installer')
16 | installExtension.default(installExtension.VUEJS_DEVTOOLS)
17 | .then(() => {})
18 | .catch(err => {
19 | console.log('Unable to install `vue-devtools`: \n', err)
20 | })
21 | })
22 |
23 | // Require `main` process to boot app
24 | require('./index')
--------------------------------------------------------------------------------
/static/plugins/tpl/doc.js:
--------------------------------------------------------------------------------
1 | export default {
2 | template: `
3 |
4 |
10 |
11 |
12 | `,
13 | data() {
14 | return {
15 | query: this.$route.query,
16 | menu: [],
17 | active: 0,
18 | }
19 | },
20 | mounted() {
21 | this.menu = JSON.parse(this.query.args).indexes || [];
22 | },
23 | computed: {
24 | path() {
25 | return decodeURIComponent(this.query.rootPath) + (this.menu[this.active] || {}).p
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | rubick2
6 | <% if (htmlWebpackPlugin.options.nodeModules) { %>
7 |
8 |
11 | <% } %>
12 |
13 |
14 |
15 |
16 | <% if (!process.browser) { %>
17 |
20 | <% } %>
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | osx_image: xcode8.3
2 | sudo: required
3 | dist: trusty
4 | language: c
5 | matrix:
6 | include:
7 | - os: osx
8 | - os: linux
9 | env: CC=clang CXX=clang++ npm_config_clang=1
10 | compiler: clang
11 | cache:
12 | directories:
13 | - node_modules
14 | - "$HOME/.electron"
15 | - "$HOME/.cache"
16 | addons:
17 | apt:
18 | packages:
19 | - libgnome-keyring-dev
20 | - icnsutils
21 | before_install:
22 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install git-lfs; fi
23 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install --no-install-recommends -y icnsutils graphicsmagick xz-utils; fi
24 | install:
25 | - nvm install 10
26 | - curl -o- -L https://yarnpkg.com/install.sh | bash
27 | - source ~/.bashrc
28 | - npm install -g xvfb-maybe
29 | - yarn
30 | before_script:
31 | - git lfs pull
32 | script:
33 | - yarn run build
34 | branches:
35 | only:
36 | - master
37 |
--------------------------------------------------------------------------------
/static/plugins/picker/picker.css:
--------------------------------------------------------------------------------
1 | html, body{ margin: 0; user-select: none; overflow: hidden;}
2 | .content {width: 108px; height: 108px; background-color: #fff; border-radius: 5px; }
3 | .content>div{ display: flex; }
4 | .content>div>div{ width: 12px; height: 12px; box-sizing: border-box; border-right: 1px solid #999; border-bottom: 1px solid #999; }
5 | .content>div:first-child>div{ border-top: 1px solid #999; }
6 | .content>div>div:first-child { border-left: 1px solid #999; }
7 | .content>div:first-child>div:first-child { border-top-left-radius: 4px; }
8 | .content>div:first-child>div:last-child { border-top-right-radius: 4px; }
9 | .content>div:last-child>div:first-child { border-bottom-left-radius: 4px; }
10 | .content>div:last-child>div:last-child { border-bottom-right-radius: 4px; }
11 | .center { position: relative; }
12 | .center>div{ position: absolute; top: -2px; left: -2px; width: 100%; height: 100%; border: 2px solid #fff; box-shadow: 0 0 1px #212121; }
13 |
--------------------------------------------------------------------------------
/src/main/browsers/picker.js:
--------------------------------------------------------------------------------
1 | const { BrowserWindow, nativeImage } = require("electron");
2 |
3 | module.exports = () => {
4 | let win;
5 |
6 | let init = (x, y) => {
7 | if (win === null || win === undefined) {
8 | createWindow();
9 | }
10 | };
11 |
12 | let createWindow = () => {
13 | win = new BrowserWindow({
14 | frame: false,
15 | autoHideMenuBar: true,
16 | width: 108,
17 | height: 108,
18 | transparent: true,
19 | alwaysOnTop: true,
20 | resizable: false,
21 | focusable: true,
22 | hasShadow: false,
23 | webPreferences: {
24 | nodeIntegration: true,
25 | devTools: false,
26 | },
27 | });
28 |
29 | win.loadURL(`file://${__static}/plugins/picker/index.html`);
30 | win.on("closed", () => {
31 | win = undefined;
32 | });
33 | };
34 |
35 | let getWindow = () => win;
36 |
37 | return {
38 | init: init,
39 | getWindow: getWindow,
40 | };
41 | };
42 |
--------------------------------------------------------------------------------
/src/main/index.js:
--------------------------------------------------------------------------------
1 | import { app } from 'electron'
2 | import '../renderer/store'
3 | import init from './common/common';
4 | import {autoUpdate} from './common/autoUpdate';
5 | import createTray from './tray';
6 | const {main} = require("./browsers")();
7 | /**
8 | * Set `__static` path to static files in production
9 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-static-assets.html
10 | */
11 | if (process.env.NODE_ENV !== 'development') {
12 | global.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
13 | }
14 | // to fix https://github.com/electron/electron/issues/18397
15 | app.allowRendererProcessReuse = false;
16 | app.dock.hide();
17 |
18 | function createWindow() {
19 | main.init();
20 | init(main.getWindow());
21 | }
22 |
23 | app.on('ready', () => {
24 | createWindow()
25 | createTray(main.getWindow());
26 | autoUpdate();
27 | })
28 |
29 | app.on('window-all-closed', () => {
30 | if (process.platform !== 'darwin') {
31 | app.quit()
32 | }
33 | })
34 |
35 | app.on('activate', () => {
36 | createWindow()
37 | });
38 |
39 |
--------------------------------------------------------------------------------
/static/plugins/tpl/index.js:
--------------------------------------------------------------------------------
1 | import doc from './doc.js';
2 | import list from './list.js';
3 |
4 | function getQueryVariable(variable) {
5 | var query = window.location.search.substring(1);
6 | var vars = query.split("&");
7 | for (var i=0;i'
26 | }).$mount('#app');
27 |
28 | ioHook.start(false);
29 |
30 | let down_time = 0;
31 | let isPress = false;
32 | ioHook.on('mousedown', (e) => {
33 | if (e.button === 1) return;
34 | isPress = true;
35 | down_time = Date.now();
36 | const config = opConfig.get();
37 | setTimeout(async () => {
38 | if (isPress) {
39 | ipcRenderer.send('right-down');
40 | }
41 | }, config.superPanel.mouseDownTime);
42 | })
43 | ioHook.on('mouseup', (e) => {
44 | if(e.button === 1) return;
45 | isPress = false;
46 | });
47 |
--------------------------------------------------------------------------------
/src/main/browsers/separate.js:
--------------------------------------------------------------------------------
1 | const { BrowserWindow } = require("electron");
2 |
3 | module.exports = () => {
4 | let win;
5 |
6 | let init = (opts) => {
7 | createWindow(opts);
8 | };
9 |
10 | let createWindow = (opts) => {
11 | const winURL = process.env.NODE_ENV === 'development'
12 | ? `http://localhost:9080/#/plugin`
13 | : `${__dirname}/index.html`
14 | win = new BrowserWindow({
15 | height: 600,
16 | useContentSize: true,
17 | width: 800,
18 | titleBarStyle: 'hiddenInset',
19 | title: '拉比克',
20 | show: false,
21 | webPreferences: {
22 | webSecurity: false,
23 | enableRemoteModule: true,
24 | backgroundThrottling: false,
25 | webviewTag: true,
26 | nodeIntegration: true // 在网页中集成Node
27 | }
28 | });
29 | process.env.NODE_ENV === 'development' ? win.loadURL(winURL) : win.loadFile(winURL, {
30 | hash: `#/plugin`,
31 | });
32 |
33 | win.webContents.executeJavaScript(`window.setPluginInfo(${opts})`).then(() => {
34 | win.show()
35 | });
36 |
37 | win.on("closed", () => {
38 | win = undefined;
39 | });
40 | };
41 |
42 | let getWindow = () => win;
43 |
44 | return {
45 | init: init,
46 | getWindow: getWindow,
47 | };
48 | };
49 |
--------------------------------------------------------------------------------
/.electron-vue/dev-client.js:
--------------------------------------------------------------------------------
1 | const hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
2 |
3 | hotClient.subscribe(event => {
4 | /**
5 | * Reload browser when HTMLWebpackPlugin emits a new index.html
6 | *
7 | * Currently disabled until jantimon/html-webpack-plugin#680 is resolved.
8 | * https://github.com/SimulatedGREG/electron-vue/issues/437
9 | * https://github.com/jantimon/html-webpack-plugin/issues/680
10 | */
11 | // if (event.action === 'reload') {
12 | // window.location.reload()
13 | // }
14 |
15 | /**
16 | * Notify `mainWindow` when `main` process is compiling,
17 | * giving notice for an expected reload of the `electron` process
18 | */
19 | if (event.action === 'compiling') {
20 | document.body.innerHTML += `
21 |
34 |
35 |
36 | Compiling Main Process...
37 |
38 | `
39 | }
40 | })
41 |
--------------------------------------------------------------------------------
/src/main/browsers/main.js:
--------------------------------------------------------------------------------
1 | const { BrowserWindow, protocol } = require("electron");
2 | module.exports = () => {
3 | let win;
4 |
5 | let init = (opts) => {
6 | createWindow(opts);
7 | };
8 |
9 | let createWindow = (opts) => {
10 | const winURL = process.env.NODE_ENV === 'development'
11 | ? `http://localhost:9080`
12 | : `file://${__dirname}/index.html`
13 |
14 | win = new BrowserWindow({
15 | height: 60,
16 | useContentSize: true,
17 | width: 800,
18 | frame: false,
19 | title: '拉比克',
20 | show: false,
21 | webPreferences: {
22 | webSecurity: false,
23 | enableRemoteModule: true,
24 | backgroundThrottling: false,
25 | webviewTag: true,
26 | nodeIntegration: true // 在网页中集成Node
27 | }
28 | })
29 |
30 | win.loadURL(winURL)
31 |
32 | protocol.interceptFileProtocol('image', (req, callback) => {
33 | const url = req.url.substr(8);
34 | callback(decodeURI(url));
35 | }, (error) => {
36 | if (error) {
37 | console.error('Failed to register protocol');
38 | }
39 | });
40 |
41 | win.once('ready-to-show', () => win.show());
42 | win.on("closed", () => {
43 | win = undefined;
44 | });
45 | };
46 |
47 | let getWindow = () => win;
48 |
49 | return {
50 | init: init,
51 | getWindow: getWindow,
52 | };
53 | };
54 |
--------------------------------------------------------------------------------
/src/main/browsers/superPanel.js:
--------------------------------------------------------------------------------
1 | const { BrowserWindow, ipcMain, app } = require("electron");
2 |
3 | module.exports = () => {
4 | let win;
5 |
6 | let init = (mainWindow) => {
7 | if (win === null || win === undefined) {
8 | createWindow();
9 | ipcMain.on('superPanel-hidden', () => {
10 | win.hide();
11 | });
12 | ipcMain.on('superPanel-setSize', (e, height) => {
13 | win.setSize(250, height);
14 | });
15 | ipcMain.on('superPanel-openPlugin', (e, args) => {
16 | mainWindow.webContents.send('superPanel-openPlugin', args);
17 | });
18 | }
19 | };
20 |
21 | let createWindow = () => {
22 | win = new BrowserWindow({
23 | frame: false,
24 | autoHideMenuBar: true,
25 | width: 250,
26 | height: 50,
27 | show: false,
28 | alwaysOnTop: true,
29 | webPreferences: {
30 | webSecurity: false,
31 | enableRemoteModule: true,
32 | backgroundThrottling: false,
33 | nodeIntegration: true,
34 | devTools: false,
35 | },
36 | });
37 | win.loadURL(`file://${__static}/plugins/superPanel/index.html`);
38 | win.on("closed", () => {
39 | win = undefined;
40 | });
41 | // 打包后,失焦隐藏
42 | win.on('blur', () => {
43 | win.hide();
44 | });
45 | };
46 |
47 | let getWindow = () => win;
48 |
49 | return {
50 | init: init,
51 | getWindow: getWindow,
52 | };
53 | };
54 |
--------------------------------------------------------------------------------
/src/renderer/pages/index/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Ant Design, a design language for background applications, is refined by Ant UED Team
8 |
9 |
10 | {{ item.title }}
11 |
标签
12 |
13 |
17 |
18 |
21 |
22 |
23 |
24 |
25 |
26 |
49 |
63 |
--------------------------------------------------------------------------------
/src/main/common/autoUpdate.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { lt } from 'semver';
3 | import { dialog, shell } from 'electron';
4 | import pkg from '../../../package.json';
5 | const os = require('os');
6 |
7 | const version = pkg.version;
8 | const releaseUrl = 'http://rubick-server.qa.91jkys.com/release/query';
9 |
10 | export async function autoUpdate() {
11 | let res;
12 | try {
13 | res = await axios.get(releaseUrl);
14 | } catch (err) {
15 | console.log(err);
16 | }
17 | if (res) {
18 | const latest = res.data.result[0];
19 | const result = compareVersion2Update(version, latest.version);
20 | if (result) {
21 | const res = await dialog.showMessageBox({
22 | type: 'info',
23 | title: '发现新版本',
24 | buttons: ['Yes', 'No'],
25 | message: `发现新版本${latest.version},更新了很多功能,${latest.msg}, 是否去下载最新的版本?`,
26 | checkboxLabel: '以后不再提醒',
27 | checkboxChecked: false
28 | });
29 | if (res.response === 0) {
30 | if (os.type() === 'Windows_NT') {
31 | // windows
32 | await shell.openExternal(latest.downloadUrl);
33 | } else if (os.type() === 'Darwin') {
34 | // mac
35 | await shell.openExternal(latest.downloadUrl);
36 | } else {
37 | // 不支持提示
38 | dialog.showErrorBox('提示', '系统不支持');
39 | }
40 | }
41 | }
42 | }
43 | }
44 |
45 | // if true -> update else return false
46 | const compareVersion2Update = (current, latest) => {
47 | return lt(current, latest);
48 | };
49 |
--------------------------------------------------------------------------------
/src/main/common/config.js:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import fs from 'fs';
3 | import {getlocalDataFile} from "./utils";
4 | import os from 'os';
5 |
6 | const configPath = path.join(getlocalDataFile(), './rubick-config.json');
7 |
8 | let defaultConfig = {
9 | Darwin: {
10 | perf: {
11 | shortCut: {
12 | showAndHidden: 'Option+R',
13 | separate: 'Ctrl+D'
14 | },
15 | common: {
16 | start: true,
17 | space: true,
18 | },
19 | local: {
20 | search: true,
21 | }
22 | },
23 | superPanel: {
24 | baiduAPI: {
25 | key: '',
26 | appid: '',
27 | },
28 | mouseDownTime: 500
29 | },
30 | global: []
31 | }
32 | }
33 | global.opConfig = {
34 | config: null,
35 | get() {
36 | const platform = os.type();
37 | try {
38 | if (!opConfig.config) {
39 | opConfig.config = JSON.parse(fs.readFileSync(configPath) || JSON.stringify(defaultConfig[platform]));
40 | }
41 | // 重置
42 | if (!opConfig.config.perf || !opConfig.config.superPanel || !opConfig.config.global) {
43 | opConfig.config = defaultConfig[platform];
44 | fs.writeFileSync(configPath, JSON.stringify(opConfig.config));
45 | }
46 | return opConfig.config;
47 | } catch (e) {
48 | opConfig.config = defaultConfig[platform]
49 | return opConfig.config;
50 | }
51 | },
52 | set(key, value) {
53 | opConfig.config[key] = value;
54 | fs.writeFileSync(configPath, JSON.stringify(opConfig.config));
55 | }
56 | }
57 |
58 |
--------------------------------------------------------------------------------
/src/main/common/utils.js:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 |
3 | export const getlocalDataFile = () => {
4 | let localDataFile = process.env.HOME;
5 | if (!localDataFile) {
6 | localDataFile = process.env.LOCALAPPDATA;
7 | }
8 | return localDataFile;
9 | };
10 |
11 | export function saveData(path, value) {
12 | fs.writeFileSync(path, JSON.stringify(value));
13 | }
14 |
15 | export function getData(path, defaultValue) {
16 | try {
17 | return JSON.parse(fs.readFileSync(path, 'utf8'));
18 | } catch (e) {
19 | return defaultValue || undefined;
20 | }
21 | }
22 |
23 | export function throttle (func, wait, options) {
24 | let context, args, result;
25 | let timeout = null;
26 | let previous = 0;
27 | if (!options) options = {};
28 | let later = function() {
29 | previous = options.leading === false ? 0 : Date.now();
30 | timeout = null;
31 | result = func.apply(context, args);
32 | if (!timeout) context = args = null;
33 | };
34 | return function() {
35 | let now = Date.now();
36 | if (!previous && options.leading === false) previous = now;
37 | // 计算剩余时间
38 | let remaining = wait - (now - previous);
39 | context = this;
40 | args = arguments;
41 | if (remaining <= 0 || remaining > wait) {
42 | if (timeout) {
43 | clearTimeout(timeout);
44 | timeout = null;
45 | }
46 | previous = now;
47 | result = func.apply(context, args);
48 | if (!timeout) context = args = null;
49 | } else if (!timeout && options.trailing !== false) {
50 | timeout = setTimeout(later, remaining);
51 | }
52 | return result;
53 | };
54 | }
55 |
56 |
--------------------------------------------------------------------------------
/src/renderer/assets/common/constans.js:
--------------------------------------------------------------------------------
1 | const WINDOW_MAX_HEIGHT = 600;
2 | const WINDOW_MIN_HEIGHT = 60;
3 | const PRE_ITEM_HEIGHT = 60;
4 |
5 | const SYSTEM_PLUGINS = [
6 | {
7 | "pluginName": "rubick 帮助文档",
8 | "logo": require('../imgs/help.png'),
9 | "features": [
10 | {
11 | "code": "help",
12 | "explain": "rubick 帮助文档",
13 | "cmds": [ "Help", "帮助" ]
14 | },
15 | ],
16 | "tag": 'rubick-help',
17 | },
18 | {
19 | "pluginName": "屏幕颜色拾取",
20 | "logo": require('../imgs/picker.png'),
21 | "features": [
22 | {
23 | "code": "pick",
24 | "explain": "rubick 帮助文档",
25 | "cmds": [ "取色", "拾色", 'Pick color' ]
26 | },
27 | ],
28 | "tag": 'rubick-color',
29 | },
30 | {
31 | "pluginName": "截屏",
32 | "logo": require('../imgs/screenshot.png'),
33 | "features": [
34 | {
35 | "code": "shortCut",
36 | "explain": "rubick 屏幕截取",
37 | "cmds": [ "截屏", "shortCut" ]
38 | },
39 | ],
40 | "tag": 'rubick-screen-short-cut',
41 | },
42 | {
43 | "pluginName": "锁屏",
44 | "logo": require('../imgs/lock.png'),
45 | "features": [
46 | {
47 | "code": "lock",
48 | "explain": "锁屏",
49 | "cmds": [ "锁屏", "lock screen" ]
50 | },
51 | ],
52 | "tag": 'rubick-lock',
53 | }
54 | ];
55 |
56 | const APP_FINDER_PATH = [
57 | '/System/Applications',
58 | '/Applications',
59 | '/System/Library/PreferencePanes',
60 | ];
61 |
62 | export {
63 | WINDOW_MAX_HEIGHT,
64 | WINDOW_MIN_HEIGHT,
65 | PRE_ITEM_HEIGHT,
66 | SYSTEM_PLUGINS,
67 | APP_FINDER_PATH,
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/common/api.js:
--------------------------------------------------------------------------------
1 | import {app, BrowserWindow, dialog} from 'electron';
2 |
3 | const puppeteer = require("puppeteer-core");
4 | const pie = require("puppeteer-in-electron")
5 |
6 |
7 | let browser
8 | pie.initialize(app).then(res => {
9 | pie.connect(app, puppeteer).then(b => {
10 | browser = b;
11 | })
12 | })
13 |
14 | export default {
15 | getPath(arg) {
16 | return app.getPath(arg.name);
17 | },
18 | hideMainWindow(arg, mainWindow) {
19 | mainWindow.hide();
20 | },
21 | showMainWindow(arg, mainWindow) {
22 | mainWindow.show();
23 | },
24 | showOpenDialog({options}) {
25 | return JSON.parse(JSON.stringify(dialog.showOpenDialogSync(options)));
26 | },
27 | onPluginEnter(arg) {
28 | return arg
29 | },
30 | setExpendHeight({height}, mainWindow) {
31 | mainWindow.setSize(800, height || 60);
32 | },
33 |
34 | ubrowser: {
35 | goto: async ({winId}) => {
36 | const win = BrowserWindow.fromId(winId);
37 | await win.loadURL(url);
38 | },
39 | async value({selector, value, winId}) {
40 | const win = BrowserWindow.fromId(winId);
41 | const page = await pie.getPage(browser, win);
42 | const nd = await page.$(selector);
43 | nd.type(value);
44 | },
45 |
46 | async click({selector, winId}) {
47 | const win = BrowserWindow.fromId(winId);
48 | const page = await pie.getPage(browser, win);
49 | const nd = await page.$(selector);
50 | nd.click();
51 | },
52 |
53 | async run(options) {
54 | const win = BrowserWindow.fromId(options.winId);
55 | win.setSize(options.width || 800, options.height || 600)
56 | win.once('ready-to-show', () => win.show());
57 | },
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/tray.js:
--------------------------------------------------------------------------------
1 | import { dialog, Menu, Tray, app, shell, ipcMain } from 'electron';
2 | import path from 'path';
3 | import pkg from '../../package.json';
4 |
5 | function createTray(window) {
6 | return new Promise((resolve, reject) => {
7 | const appIcon = new Tray(path.join(__static, './rocket.png'));
8 | const contextMenu = Menu.buildFromTemplate([
9 | {
10 | label: "帮助文档", click: () => {
11 | process.nextTick((() => {
12 | shell.openExternal('https://github.com/clouDr-f2e/rubick');
13 | }))
14 | }
15 | }, {
16 | label: "意见反馈", click: () => {
17 | process.nextTick((() => {
18 | shell.openExternal('https://github.com/clouDr-f2e/rubick/issues')
19 | }))
20 | }
21 | },
22 | {type: "separator"},
23 | {
24 | label: '显示窗口',
25 | accelerator: "Alt+R",
26 | click() {
27 | window.show();
28 | }
29 | },
30 | {
31 | role: 'quit',
32 | label: '退出'
33 | },
34 | {
35 | label: '重启',
36 | click() {
37 | app.relaunch();
38 | app.quit();
39 | }
40 | },
41 | {type: "separator"},
42 | {
43 | label: '偏好设置',
44 | click() {
45 | window.show();
46 | window.webContents.send('tray-setting');
47 | },
48 | },
49 | {
50 | label: '关于',
51 | click() {
52 | dialog.showMessageBox({
53 | title: '拉比克',
54 | message: '极简、插件化的现代桌面软件',
55 | detail: `Version: ${pkg.version}\nAuthor: muwoo`
56 | });
57 | },
58 | },
59 | ]);
60 | appIcon.on('click', () => {
61 | appIcon.popUpContextMenu(contextMenu);
62 | });
63 | appIcon.setContextMenu(contextMenu);
64 |
65 | resolve(appIcon);
66 | });
67 | }
68 |
69 | export default createTray;
70 |
--------------------------------------------------------------------------------
/src/renderer/pages/search/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 插件中心
8 |
9 |
10 |
11 | 已安装
12 |
13 |
14 |
15 | 开发者
16 |
17 |
18 |
19 | 设置
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
47 |
48 |
75 |
--------------------------------------------------------------------------------
/.electron-vue/webpack.main.config.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | process.env.BABEL_ENV = 'main'
4 |
5 | const path = require('path')
6 | const { dependencies } = require('../package.json')
7 | const webpack = require('webpack')
8 |
9 | const MinifyPlugin = require("babel-minify-webpack-plugin")
10 |
11 | let mainConfig = {
12 | entry: {
13 | main: path.join(__dirname, '../src/main/index.js')
14 | },
15 | externals: [
16 | ...Object.keys(dependencies || {})
17 | ],
18 | module: {
19 | rules: [
20 | {
21 | test: /\.js$/,
22 | use: 'babel-loader',
23 | exclude: /node_modules/
24 | },
25 | {
26 | test: /\.node$/,
27 | use: 'node-loader'
28 | },
29 | {
30 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
31 | use: {
32 | loader: 'url-loader',
33 | query: {
34 | limit: 10000,
35 | name: 'imgs/[name]--[folder].[ext]'
36 | }
37 | }
38 | },
39 | ]
40 | },
41 | node: {
42 | __dirname: process.env.NODE_ENV !== 'production',
43 | __filename: process.env.NODE_ENV !== 'production'
44 | },
45 | output: {
46 | filename: '[name].js',
47 | libraryTarget: 'commonjs2',
48 | path: path.join(__dirname, '../dist/electron')
49 | },
50 | plugins: [
51 | new webpack.NoEmitOnErrorsPlugin()
52 | ],
53 | resolve: {
54 | extensions: ['.js', '.json', '.node']
55 | },
56 | target: 'electron-main'
57 | }
58 |
59 | /**
60 | * Adjust mainConfig for development settings
61 | */
62 | if (process.env.NODE_ENV !== 'production') {
63 | mainConfig.plugins.push(
64 | new webpack.DefinePlugin({
65 | '__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`
66 | })
67 | )
68 | }
69 |
70 | /**
71 | * Adjust mainConfig for production settings
72 | */
73 | if (process.env.NODE_ENV === 'production') {
74 | mainConfig.plugins.push(
75 | new MinifyPlugin(),
76 | new webpack.DefinePlugin({
77 | 'process.env.NODE_ENV': '"production"'
78 | })
79 | )
80 | }
81 |
82 | module.exports = mainConfig
83 |
--------------------------------------------------------------------------------
/src/renderer/assets/keycode.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 0: 'That key has no keycode',
3 | 3: 'break',
4 | 8: 'backspace / delete',
5 | 9: 'tab',
6 | 12: 'clear',
7 | 13: 'enter',
8 | 16: 'shift',
9 | 17: 'ctrl',
10 | 18: 'alt',
11 | 19: 'pause/break',
12 | 20: 'caps lock',
13 | 21: 'hangul',
14 | 25: 'hanja',
15 | 27: 'escape',
16 | 28: 'conversion',
17 | 29: 'non-conversion',
18 | 32: 'space',
19 | 33: 'page up',
20 | 34: 'page down',
21 | 35: 'End',
22 | 36: 'Home',
23 | 37: 'Left',
24 | 38: 'Up',
25 | 39: 'Right',
26 | 40: 'Down',
27 | 45: 'Insert',
28 | 46: 'Delete',
29 | 48: '0',
30 | 49: '1',
31 | 50: '2',
32 | 51: '3',
33 | 52: '4',
34 | 53: '5',
35 | 54: '6',
36 | 55: '7',
37 | 56: '8',
38 | 57: '9',
39 | 65: 'A',
40 | 66: 'B',
41 | 67: 'C',
42 | 68: 'D',
43 | 69: 'E',
44 | 70: 'F',
45 | 71: 'G',
46 | 72: 'H',
47 | 73: 'I',
48 | 74: 'J',
49 | 75: 'K',
50 | 76: 'L',
51 | 77: 'M',
52 | 78: 'N',
53 | 79: 'O',
54 | 80: 'P',
55 | 81: 'Q',
56 | 82: 'R',
57 | 83: 'S',
58 | 84: 'T',
59 | 85: 'U',
60 | 86: 'V',
61 | 87: 'W',
62 | 88: 'X',
63 | 89: 'Y',
64 | 90: 'Z',
65 | 112: 'F1',
66 | 113: 'F2',
67 | 114: 'F3',
68 | 115: 'F4',
69 | 116: 'F5',
70 | 117: 'F6',
71 | 118: 'F7',
72 | 119: 'F8',
73 | 120: 'F9',
74 | 121: 'F10',
75 | 122: 'F11',
76 | 123: 'F12',
77 | 186: ';',
78 | 187: '=',
79 | 188: ',',
80 | 189: '-',
81 | 190: '.',
82 | 191: '/',
83 | 192: '`',
84 | 219: '[',
85 | 220: '\\',
86 | 221: ']',
87 | 222: "'",
88 | 223: '`',
89 | 224: 'left or right ⌘ key (firefox)',
90 | 225: 'altgr',
91 | 226: '< /git >, left back slash',
92 | 230: 'GNOME Compose Key',
93 | 231: 'ç',
94 | 233: 'XF86Forward',
95 | 234: 'XF86Back',
96 | 235: 'non-conversion',
97 | 240: 'alphanumeric',
98 | 242: 'hiragana/katakana',
99 | 243: 'half-width/full-width',
100 | 244: 'kanji',
101 | 251: 'unlock trackpad (Chrome/Edge)',
102 | 255: 'toggle touchpad',
103 | };
104 |
--------------------------------------------------------------------------------
/static/plugins/tpl/list.js:
--------------------------------------------------------------------------------
1 | function getQueryVariable(variable) {
2 | var query = window.location.search.substring(1);
3 | var vars = query.split("&");
4 | for (var i=0;i
14 |
15 |
select(item)">
16 |
![]()
17 |
18 |
{{item.title}}
19 |
{{decodeURIComponent(item.description)}}
20 |
21 |
22 |
23 |
24 | `,
25 | data() {
26 | return {
27 | query: this.$route.query,
28 | menu: [],
29 | active: 0,
30 | config: window.exports,
31 | lists: [],
32 | currentSelect: 0,
33 | }
34 | },
35 | mounted() {
36 | this.code = getQueryVariable('code');
37 | this.current = this.config[this.code];
38 | this.current.args.enter && this.current.args.enter({code: this.code, type: '', payload: [] }, (lists) => {
39 | this.lists = lists;
40 | });
41 | ipcRenderer.on(`changeCurrent`, (e, result) => {
42 | if (this.currentSelect + result > this.lists.length - 1 || this.currentSelect + result < 0) return;
43 | this.currentSelect = this.currentSelect + result;
44 | });
45 | ipcRenderer.on(`msg-back-setSubInput`, (e, result) => {
46 | this.current.args.search && this.current.args.search({code: this.code, type: '', payload: [] }, result, (lists) => {
47 | this.lists = lists;
48 | })
49 | });
50 | },
51 | methods: {
52 | select(item) {
53 | this.current.args.select && this.current.args.select({code: this.code, type: '', payload: [] }, item);
54 | },
55 | renderTitle(title) {
56 | const result = title.split(this.searchValue);
57 | return `${result[0]}${this.searchValue}${result[1]}
`
58 | },
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/static/plugins/picker/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Picker
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
--------------------------------------------------------------------------------
/static/plugins/tpl/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Title
6 |
7 |
8 |
9 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |

3 |
4 |
5 | # Rubick
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | 基于 electron 的工具箱,媲美 utools的开源插件,已实现 utools 大部分的 API 能力,所以可以做到无缝适配 utools 开源的插件。
18 | 之所以做这个工具箱一方面是 utools 本身并未开源,但是公司内部的工具库又无法发布到 utools 插件中,所以为了既要享受 utools 生态又要有定制化需求,我们自己参考 utools 设计,做了 Rubick.
19 |
20 | Rubick(拉比克) 是 dota 里面的英雄之一,其核心技能是插件化使用其他英雄的技能,用完即走。非常符合本工具的设计理念,所以取名 Rubick。
21 |
22 | ## 安装包
23 | * [Rubick Mac OS V0.0.2](https://github.com/clouDr-f2e/rubick/releases/download/v0.0.2/rubick2-0.0.2.pkg)
24 |
25 | ## 支持能力
26 | - [x] 支持 uTools 90% API。可直接按照 uTools 文档开发 Rubick 插件
27 | - [x] 支持 uTools github 开源插件。
28 | - [x] 支持远程下载安装插件,支持插件开发者模式
29 | - [x] 支持插件分离
30 | - [x] 支持系统命令取色、截屏、帮助
31 | - [x] 支持超级面板,长按右击呼出
32 | - [x] 支持全局快捷键设置
33 | - [x] 支持搜索本地已安装 app 或 偏好设置
34 | - [ ] 支持 Windows
35 | - [ ] 支持 Linux
36 |
37 |
38 | 
39 |
40 |
41 |
42 | ## 使用问题
43 | 1. 目前 `Rubick` 插件市场 server 端还没有部署,所以目前看不到插件市场的插件。
44 | 2. 依赖于 `robotjs` dev 环境运行请在 `install` 后执行 `npm run rebuild`
45 |
46 | ## 目前支持能力
47 | ### 加载utools生态插件
48 | 拿 `github` 上开源的 斗图 插件举例,要加载斗图插件,只需要将代码 clone下来后,复制其 `plugin.json` 进入搜索框即可使用
49 |
50 | 斗图:https://github.com/vst93/doutu-uToolsPlugin
51 |
52 |
53 |
54 | ### 超级面板
55 | 长按鼠标右键,即可呼起超级面板,可以根据当前鼠标选择内容,匹配对应插件能力。比如当前选择图片后长按右击,则会呼起上传图床插件:
56 |
57 | 
58 |
59 | ### 模板
60 | 为了更贴合 `uTools` 的插件能力,需要实现模板功能,模板即是一个内置 UI 样式的功能插件。
61 |
62 |
63 |
64 | ### utools 自带的系统命令
65 | #### 取色
66 | 基于 `robot.js` 以及 `iohook` 实现。未使用 C++ 扩展。
67 |
68 |
69 |
70 | #### 截屏
71 |
72 |
73 |
74 |
75 | #### 全局快捷键
76 |
77 |
78 |
79 | ### 最后
80 | utools过于强大,目前还没有完全实现其所有功能,不过我们会根据需要不断更新。欢迎小伙伴一起 `pr` 或 `star`
81 |
82 | ## License
83 | This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/clouDr-f2e/rubick/blob/master/LICENSE) file for details.
84 |
85 |
--------------------------------------------------------------------------------
/.electron-vue/build.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | process.env.NODE_ENV = 'production'
4 |
5 | const { say } = require('cfonts')
6 | const chalk = require('chalk')
7 | const del = require('del')
8 | const { spawn } = require('child_process')
9 | const webpack = require('webpack')
10 | const Multispinner = require('multispinner')
11 |
12 |
13 | const mainConfig = require('./webpack.main.config')
14 | const rendererConfig = require('./webpack.renderer.config')
15 | const webConfig = require('./webpack.web.config')
16 |
17 | const doneLog = chalk.bgGreen.white(' DONE ') + ' '
18 | const errorLog = chalk.bgRed.white(' ERROR ') + ' '
19 | const okayLog = chalk.bgBlue.white(' OKAY ') + ' '
20 | const isCI = process.env.CI || false
21 |
22 | if (process.env.BUILD_TARGET === 'clean') clean()
23 | else if (process.env.BUILD_TARGET === 'web') web()
24 | else build()
25 |
26 | function clean () {
27 | del.sync(['build/*', '!build/icons', '!build/icons/icon.*'])
28 | console.log(`\n${doneLog}\n`)
29 | process.exit()
30 | }
31 |
32 | function build () {
33 | greeting()
34 |
35 | del.sync(['dist/electron/*', '!.gitkeep'])
36 |
37 | const tasks = ['main', 'renderer']
38 | const m = new Multispinner(tasks, {
39 | preText: 'building',
40 | postText: 'process'
41 | })
42 |
43 | let results = ''
44 |
45 | m.on('success', () => {
46 | process.stdout.write('\x1B[2J\x1B[0f')
47 | console.log(`\n\n${results}`)
48 | console.log(`${okayLog}take it away ${chalk.yellow('`electron-builder`')}\n`)
49 | process.exit()
50 | })
51 |
52 | pack(mainConfig).then(result => {
53 | results += result + '\n\n'
54 | m.success('main')
55 | }).catch(err => {
56 | m.error('main')
57 | console.log(`\n ${errorLog}failed to build main process`)
58 | console.error(`\n${err}\n`)
59 | process.exit(1)
60 | })
61 |
62 | pack(rendererConfig).then(result => {
63 | results += result + '\n\n'
64 | m.success('renderer')
65 | }).catch(err => {
66 | m.error('renderer')
67 | console.log(`\n ${errorLog}failed to build renderer process`)
68 | console.error(`\n${err}\n`)
69 | process.exit(1)
70 | })
71 | }
72 |
73 | function pack (config) {
74 | return new Promise((resolve, reject) => {
75 | config.mode = 'production'
76 | webpack(config, (err, stats) => {
77 | if (err) reject(err.stack || err)
78 | else if (stats.hasErrors()) {
79 | let err = ''
80 |
81 | stats.toString({
82 | chunks: false,
83 | colors: true
84 | })
85 | .split(/\r?\n/)
86 | .forEach(line => {
87 | err += ` ${line}\n`
88 | })
89 |
90 | reject(err)
91 | } else {
92 | resolve(stats.toString({
93 | chunks: false,
94 | colors: true
95 | }))
96 | }
97 | })
98 | })
99 | }
100 |
101 | function web () {
102 | del.sync(['dist/web/*', '!.gitkeep'])
103 | webConfig.mode = 'production'
104 | webpack(webConfig, (err, stats) => {
105 | if (err || stats.hasErrors()) console.log(err)
106 |
107 | console.log(stats.toString({
108 | chunks: false,
109 | colors: true
110 | }))
111 |
112 | process.exit()
113 | })
114 | }
115 |
116 | function greeting () {
117 | const cols = process.stdout.columns
118 | let text = ''
119 |
120 | if (cols > 85) text = 'lets-build'
121 | else if (cols > 60) text = 'lets-|build'
122 | else text = false
123 |
124 | if (text && !isCI) {
125 | say(text, {
126 | colors: ['yellow'],
127 | font: 'simple3d',
128 | space: false
129 | })
130 | } else console.log(chalk.yellow.bold('\n lets-build'))
131 | console.log()
132 | }
133 |
--------------------------------------------------------------------------------
/src/renderer/pages/plugins/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
108 |
109 |
115 |
--------------------------------------------------------------------------------
/.electron-vue/webpack.web.config.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | process.env.BABEL_ENV = 'web'
4 |
5 | const path = require('path')
6 | const webpack = require('webpack')
7 |
8 | const MinifyPlugin = require("babel-minify-webpack-plugin")
9 | const CopyWebpackPlugin = require('copy-webpack-plugin')
10 | const MiniCssExtractPlugin = require('mini-css-extract-plugin')
11 | const HtmlWebpackPlugin = require('html-webpack-plugin')
12 | const { VueLoaderPlugin } = require('vue-loader')
13 |
14 | let webConfig = {
15 | devtool: '#cheap-module-eval-source-map',
16 | entry: {
17 | web: path.join(__dirname, '../src/renderer/main.js')
18 | },
19 | module: {
20 | rules: [
21 | {
22 | test: /\.scss$/,
23 | use: ['vue-style-loader', 'css-loader', 'sass-loader']
24 | },
25 | {
26 | test: /\.sass$/,
27 | use: ['vue-style-loader', 'css-loader', 'sass-loader?indentedSyntax']
28 | },
29 | {
30 | test: /\.less$/,
31 | use: ['vue-style-loader', 'css-loader', 'less-loader']
32 | },
33 | {
34 | test: /\.css$/,
35 | use: ['vue-style-loader', 'css-loader']
36 | },
37 | {
38 | test: /\.html$/,
39 | use: 'vue-html-loader'
40 | },
41 | {
42 | test: /\.js$/,
43 | use: 'babel-loader',
44 | include: [ path.resolve(__dirname, '../src/renderer') ],
45 | exclude: /node_modules/
46 | },
47 | {
48 | test: /\.vue$/,
49 | use: {
50 | loader: 'vue-loader',
51 | options: {
52 | extractCSS: true,
53 | loaders: {
54 | sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1',
55 | scss: 'vue-style-loader!css-loader!sass-loader',
56 | less: 'vue-style-loader!css-loader!less-loader'
57 | }
58 | }
59 | }
60 | },
61 | {
62 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
63 | use: {
64 | loader: 'url-loader',
65 | query: {
66 | limit: 10000,
67 | name: 'imgs/[name].[ext]'
68 | }
69 | }
70 | },
71 | {
72 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
73 | use: {
74 | loader: 'url-loader',
75 | query: {
76 | limit: 10000,
77 | name: 'fonts/[name].[ext]'
78 | }
79 | }
80 | }
81 | ]
82 | },
83 | plugins: [
84 | new VueLoaderPlugin(),
85 | new MiniCssExtractPlugin({filename: 'styles.css'}),
86 | new HtmlWebpackPlugin({
87 | filename: 'index.html',
88 | template: path.resolve(__dirname, '../src/index.ejs'),
89 | templateParameters(compilation, assets, options) {
90 | return {
91 | compilation: compilation,
92 | webpack: compilation.getStats().toJson(),
93 | webpackConfig: compilation.options,
94 | htmlWebpackPlugin: {
95 | files: assets,
96 | options: options,
97 | },
98 | process,
99 | };
100 | },
101 | minify: {
102 | collapseWhitespace: true,
103 | removeAttributeQuotes: true,
104 | removeComments: true
105 | },
106 | nodeModules: false
107 | }),
108 | new webpack.DefinePlugin({
109 | 'process.env.IS_WEB': 'true'
110 | }),
111 | new webpack.HotModuleReplacementPlugin(),
112 | new webpack.NoEmitOnErrorsPlugin()
113 | ],
114 | output: {
115 | filename: '[name].js',
116 | path: path.join(__dirname, '../dist/web')
117 | },
118 | resolve: {
119 | alias: {
120 | '@': path.join(__dirname, '../src/renderer'),
121 | 'vue$': 'vue/dist/vue.esm.js'
122 | },
123 | extensions: ['.js', '.vue', '.json', '.css']
124 | },
125 | target: 'web'
126 | }
127 |
128 | /**
129 | * Adjust webConfig for production settings
130 | */
131 | if (process.env.NODE_ENV === 'production') {
132 | webConfig.devtool = ''
133 |
134 | webConfig.plugins.push(
135 | new MinifyPlugin(),
136 | new CopyWebpackPlugin([
137 | {
138 | from: path.join(__dirname, '../static'),
139 | to: path.join(__dirname, '../dist/web/static'),
140 | ignore: ['.*']
141 | }
142 | ]),
143 | new webpack.DefinePlugin({
144 | 'process.env.NODE_ENV': '"production"'
145 | }),
146 | new webpack.LoaderOptionsPlugin({
147 | minimize: true
148 | })
149 | )
150 | }
151 |
152 | module.exports = webConfig
153 |
--------------------------------------------------------------------------------
/static/plugins/superPanel/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Title
6 |
7 |
8 |
127 |
128 |
129 |
130 |
131 |

132 |
选择的文本 {{selectData.text.length}} 个
133 |
134 |
139 |
140 |
{{selectData.translate.src}}
141 |
142 |
143 | {{item}}
144 |
145 |
146 |
147 |
148 | {{item}}
149 |
150 |
151 |
152 |
commonClick(op, selectData.fileUrl)" class="options-item" v-for="op in targetOptions">
153 |
154 |
![]()
155 | {{op.name}}
156 |
157 |
158 |
159 |
160 |
161 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rubick2",
3 | "version": "0.0.2",
4 | "author": "muwoo <2424880409@qq.com>",
5 | "description": "An electron-vue project",
6 | "license": null,
7 | "main": "./dist/electron/main.js",
8 | "scripts": {
9 | "build": "node .electron-vue/build.js && electron-builder",
10 | "build:dir": "node .electron-vue/build.js && electron-builder --dir",
11 | "build:clean": "cross-env BUILD_TARGET=clean node .electron-vue/build.js",
12 | "build:web": "cross-env BUILD_TARGET=web node .electron-vue/build.js",
13 | "dev": "node .electron-vue/dev-runner.js",
14 | "pack": "npm run pack:main && npm run pack:renderer",
15 | "pack:main": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.main.config.js",
16 | "pack:renderer": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.renderer.config.js",
17 | "postinstall": "",
18 | "rebuild": " ./node_modules/.bin/electron-rebuild"
19 | },
20 | "build": {
21 | "asar": false,
22 | "productName": "rubick2",
23 | "appId": "com.example.yourapp2",
24 | "compression": "maximum",
25 | "directories": {
26 | "output": "build"
27 | },
28 | "files": [
29 | "dist/electron/**/*"
30 | ],
31 | "electronDownload": {
32 | "mirror": "https://npm.taobao.org/mirrors/electron/"
33 | },
34 | "dmg": {
35 | "contents": [
36 | {
37 | "x": 410,
38 | "y": 150,
39 | "type": "link",
40 | "path": "/Applications"
41 | },
42 | {
43 | "x": 130,
44 | "y": 150,
45 | "type": "file"
46 | }
47 | ]
48 | },
49 | "mac": {
50 | "icon": "build/icons/icon.icns",
51 | "target": "pkg"
52 | },
53 | "win": {
54 | "icon": "build/icons/icon.ico"
55 | },
56 | "linux": {
57 | "icon": "build/icons"
58 | }
59 | },
60 | "dependencies": {
61 | "ant-design-vue": "^1.7.5",
62 | "axios": "^0.18.1",
63 | "download": "^8.0.0",
64 | "download-git-repo": "^3.0.2",
65 | "electron-store": "^8.0.0",
66 | "iohook": "^0.9.3",
67 | "is-chinese": "^1.4.2",
68 | "keycode": "^2.2.0",
69 | "marked": "^2.0.7",
70 | "md5": "^2.3.0",
71 | "mime-types": "^2.1.31",
72 | "node-fetch": "^2.6.1",
73 | "puppeteer-core": "^10.0.0",
74 | "puppeteer-in-electron": "^3.0.3",
75 | "query-string": "^7.0.0",
76 | "request": "^2.88.2",
77 | "request-promise": "^4.2.6",
78 | "robotjs": "git+ssh://git@github.com/Toinane/robotjs.git",
79 | "semver": "^7.3.5",
80 | "sudo-prompt": "^9.2.1",
81 | "unzip": "^0.1.11",
82 | "uuid": "^8.3.2",
83 | "vue": "^2.5.16",
84 | "vue-electron": "^1.0.6",
85 | "vue-router": "^3.0.1",
86 | "vuex": "^3.0.1",
87 | "vuex-electron": "^1.0.0"
88 | },
89 | "devDependencies": {
90 | "ajv": "^6.5.0",
91 | "babel-core": "^6.26.3",
92 | "babel-loader": "^7.1.4",
93 | "babel-minify-webpack-plugin": "^0.3.1",
94 | "babel-plugin-transform-runtime": "^6.23.0",
95 | "babel-preset-env": "^1.7.0",
96 | "babel-preset-stage-0": "^6.24.1",
97 | "babel-register": "^6.26.0",
98 | "cfonts": "^2.1.2",
99 | "chalk": "^2.4.1",
100 | "copy-webpack-plugin": "^4.5.1",
101 | "cross-env": "^5.1.6",
102 | "css-loader": "^0.28.11",
103 | "del": "^3.0.0",
104 | "devtron": "^1.4.0",
105 | "electron": "^11.0.2",
106 | "electron-builder": "22.10.5",
107 | "electron-debug": "^1.5.0",
108 | "electron-devtools-installer": "^2.2.4",
109 | "electron-rebuild": "^2.3.5",
110 | "file-loader": "^1.1.11",
111 | "html-webpack-plugin": "^3.2.0",
112 | "less": "^4.1.1",
113 | "less-loader": "^5.0.0",
114 | "listr": "^0.14.3",
115 | "mini-css-extract-plugin": "0.4.0",
116 | "multispinner": "^0.2.1",
117 | "node-abi": "^2.30.0",
118 | "node-loader": "^0.6.0",
119 | "react": "^17.0.2",
120 | "style-loader": "^0.21.0",
121 | "url-loader": "^1.0.1",
122 | "vue-html-loader": "^1.2.4",
123 | "vue-loader": "^15.2.4",
124 | "vue-style-loader": "^4.1.0",
125 | "vue-template-compiler": "^2.5.16",
126 | "webpack": "^4.15.1",
127 | "webpack-cli": "^3.0.8",
128 | "webpack-dev-server": "^3.1.4",
129 | "webpack-hot-middleware": "^2.22.2",
130 | "webpack-merge": "^4.1.3"
131 | },
132 | "iohook": {
133 | "targets": [
134 | "node-83",
135 | "electron-85"
136 | ],
137 | "platforms": [
138 | "darwin"
139 | ],
140 | "arches": [
141 | "x64",
142 | "ia32"
143 | ]
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/src/renderer/pages/search/subpages/plugin.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | {{pluginDetail.pluginName}}
20 |
{{pluginDetail.version}}
21 |
22 |
23 | 开发者:{{`${pluginDetail.author || '未知'}`}}
24 |
25 |
26 | {{pluginDetail.description}}
27 |
28 |
29 |
32 |
33 |
34 |
35 |
36 |
37 |
{{item.explain}}
38 |
{{cmd}}
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
52 |
53 |
54 |
55 |
102 |
103 |
154 |
--------------------------------------------------------------------------------
/.electron-vue/dev-runner.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const chalk = require('chalk')
4 | const electron = require('electron')
5 | const path = require('path')
6 | const { say } = require('cfonts')
7 | const { spawn } = require('child_process')
8 | const webpack = require('webpack')
9 | const WebpackDevServer = require('webpack-dev-server')
10 | const webpackHotMiddleware = require('webpack-hot-middleware')
11 |
12 | const mainConfig = require('./webpack.main.config')
13 | const rendererConfig = require('./webpack.renderer.config')
14 |
15 | let electronProcess = null
16 | let manualRestart = false
17 | let hotMiddleware
18 |
19 | function logStats (proc, data) {
20 | let log = ''
21 |
22 | log += chalk.yellow.bold(`┏ ${proc} Process ${new Array((19 - proc.length) + 1).join('-')}`)
23 | log += '\n\n'
24 |
25 | if (typeof data === 'object') {
26 | data.toString({
27 | colors: true,
28 | chunks: false
29 | }).split(/\r?\n/).forEach(line => {
30 | log += ' ' + line + '\n'
31 | })
32 | } else {
33 | log += ` ${data}\n`
34 | }
35 |
36 | log += '\n' + chalk.yellow.bold(`┗ ${new Array(28 + 1).join('-')}`) + '\n'
37 |
38 | console.log(log)
39 | }
40 |
41 | function startRenderer () {
42 | return new Promise((resolve, reject) => {
43 | rendererConfig.entry.renderer = [path.join(__dirname, 'dev-client')].concat(rendererConfig.entry.renderer)
44 | rendererConfig.mode = 'development'
45 | const compiler = webpack(rendererConfig)
46 | hotMiddleware = webpackHotMiddleware(compiler, {
47 | log: false,
48 | heartbeat: 2500
49 | })
50 |
51 | compiler.hooks.compilation.tap('compilation', compilation => {
52 | compilation.hooks.htmlWebpackPluginAfterEmit.tapAsync('html-webpack-plugin-after-emit', (data, cb) => {
53 | hotMiddleware.publish({ action: 'reload' })
54 | cb()
55 | })
56 | })
57 |
58 | compiler.hooks.done.tap('done', stats => {
59 | logStats('Renderer', stats)
60 | })
61 |
62 | const server = new WebpackDevServer(
63 | compiler,
64 | {
65 | contentBase: path.join(__dirname, '../'),
66 | quiet: true,
67 | hot: true,
68 | before (app, ctx) {
69 | // app.use(hotMiddleware)
70 | ctx.middleware.waitUntilValid(() => {
71 | resolve()
72 | })
73 | }
74 | }
75 | )
76 |
77 | server.listen(9080)
78 | })
79 | }
80 |
81 | function startMain () {
82 | return new Promise((resolve, reject) => {
83 | mainConfig.entry.main = [path.join(__dirname, '../src/main/index.dev.js')].concat(mainConfig.entry.main)
84 | mainConfig.mode = 'development'
85 | const compiler = webpack(mainConfig)
86 |
87 | compiler.hooks.watchRun.tapAsync('watch-run', (compilation, done) => {
88 | logStats('Main', chalk.white.bold('compiling...'))
89 | hotMiddleware.publish({ action: 'compiling' })
90 | done()
91 | })
92 |
93 | compiler.watch({}, (err, stats) => {
94 | if (err) {
95 | console.log(err)
96 | return
97 | }
98 |
99 | logStats('Main', stats)
100 |
101 | if (electronProcess && electronProcess.kill) {
102 | manualRestart = true
103 | process.kill(electronProcess.pid)
104 | electronProcess = null
105 | startElectron()
106 |
107 | setTimeout(() => {
108 | manualRestart = false
109 | }, 5000)
110 | }
111 |
112 | resolve()
113 | })
114 | })
115 | }
116 |
117 | function startElectron () {
118 | var args = [
119 | '--inspect=5858',
120 | path.join(__dirname, '../dist/electron/main.js')
121 | ]
122 |
123 | // detect yarn or npm and process commandline args accordingly
124 | if (process.env.npm_execpath.endsWith('yarn.js')) {
125 | args = args.concat(process.argv.slice(3))
126 | } else if (process.env.npm_execpath.endsWith('npm-cli.js')) {
127 | args = args.concat(process.argv.slice(2))
128 | }
129 |
130 | electronProcess = spawn(electron, args)
131 |
132 | electronProcess.stdout.on('data', data => {
133 | electronLog(data, 'blue')
134 | })
135 | electronProcess.stderr.on('data', data => {
136 | electronLog(data, 'red')
137 | })
138 |
139 | electronProcess.on('close', () => {
140 | if (!manualRestart) process.exit()
141 | })
142 | }
143 |
144 | function electronLog (data, color) {
145 | let log = ''
146 | data = data.toString().split(/\r?\n/)
147 | data.forEach(line => {
148 | log += ` ${line}\n`
149 | })
150 | if (/[0-9A-z]+/.test(log)) {
151 | console.log(
152 | chalk[color].bold('┏ Electron -------------------') +
153 | '\n\n' +
154 | log +
155 | chalk[color].bold('┗ ----------------------------') +
156 | '\n'
157 | )
158 | }
159 | }
160 |
161 | function greeting () {
162 | const cols = process.stdout.columns
163 | let text = ''
164 |
165 | if (cols > 104) text = 'electron-vue'
166 | else if (cols > 76) text = 'electron-|vue'
167 | else text = false
168 |
169 | if (text) {
170 | say(text, {
171 | colors: ['yellow'],
172 | font: 'simple3d',
173 | space: false
174 | })
175 | } else console.log(chalk.yellow.bold('\n electron-vue'))
176 | console.log(chalk.blue(' getting ready...') + '\n')
177 | }
178 |
179 | function init () {
180 | greeting()
181 |
182 | Promise.all([startRenderer(), startMain()])
183 | .then(() => {
184 | startElectron()
185 | })
186 | .catch(err => {
187 | console.error(err)
188 | })
189 | }
190 |
191 | init()
192 |
--------------------------------------------------------------------------------
/.electron-vue/webpack.renderer.config.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | process.env.BABEL_ENV = 'renderer'
4 |
5 | const path = require('path')
6 | const { dependencies } = require('../package.json')
7 | const webpack = require('webpack')
8 |
9 | const MinifyPlugin = require("babel-minify-webpack-plugin")
10 | const CopyWebpackPlugin = require('copy-webpack-plugin')
11 | const MiniCssExtractPlugin = require('mini-css-extract-plugin')
12 | const HtmlWebpackPlugin = require('html-webpack-plugin')
13 | const { VueLoaderPlugin } = require('vue-loader')
14 |
15 | /**
16 | * List of node_modules to include in webpack bundle
17 | *
18 | * Required for specific packages like Vue UI libraries
19 | * that provide pure *.vue files that need compiling
20 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/webpack-configurations.html#white-listing-externals
21 | */
22 | let whiteListedModules = ['vue']
23 |
24 | let rendererConfig = {
25 | devtool: '#cheap-module-eval-source-map',
26 | entry: {
27 | renderer: path.join(__dirname, '../src/renderer/main.js')
28 | },
29 | externals: [
30 | ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d))
31 | ],
32 | module: {
33 | rules: [
34 | {
35 | test: /\.scss$/,
36 | use: ['vue-style-loader', 'css-loader', 'sass-loader']
37 | },
38 | {
39 | test: /\.sass$/,
40 | use: ['vue-style-loader', 'css-loader', 'sass-loader?indentedSyntax']
41 | },
42 | {
43 | test: /\.less$/,
44 | use: ['vue-style-loader', 'css-loader', 'less-loader']
45 | },
46 | {
47 | test: /\.css$/,
48 | use: ['vue-style-loader', 'css-loader']
49 | },
50 | {
51 | test: /\.html$/,
52 | use: 'vue-html-loader'
53 | },
54 | {
55 | test: /\.js$/,
56 | use: 'babel-loader',
57 | exclude: /node_modules/
58 | },
59 | {
60 | test: /\.node$/,
61 | use: 'node-loader'
62 | },
63 | {
64 | test: /\.vue$/,
65 | use: {
66 | loader: 'vue-loader',
67 | options: {
68 | extractCSS: process.env.NODE_ENV === 'production',
69 | loaders: {
70 | sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1',
71 | scss: 'vue-style-loader!css-loader!sass-loader',
72 | less: 'vue-style-loader!css-loader!less-loader'
73 | }
74 | }
75 | }
76 | },
77 | {
78 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
79 | use: {
80 | loader: 'url-loader',
81 | query: {
82 | limit: 10000,
83 | name: 'imgs/[name]--[folder].[ext]'
84 | }
85 | }
86 | },
87 | {
88 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
89 | loader: 'url-loader',
90 | options: {
91 | limit: 10000,
92 | name: 'media/[name]--[folder].[ext]'
93 | }
94 | },
95 | {
96 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
97 | use: {
98 | loader: 'url-loader',
99 | query: {
100 | limit: 10000,
101 | name: 'fonts/[name]--[folder].[ext]'
102 | }
103 | }
104 | }
105 | ]
106 | },
107 | node: {
108 | __dirname: process.env.NODE_ENV !== 'production',
109 | __filename: process.env.NODE_ENV !== 'production'
110 | },
111 | plugins: [
112 | new VueLoaderPlugin(),
113 | new MiniCssExtractPlugin({filename: 'styles.css'}),
114 | new HtmlWebpackPlugin({
115 | filename: 'index.html',
116 | template: path.resolve(__dirname, '../src/index.ejs'),
117 | templateParameters(compilation, assets, options) {
118 | return {
119 | compilation: compilation,
120 | webpack: compilation.getStats().toJson(),
121 | webpackConfig: compilation.options,
122 | htmlWebpackPlugin: {
123 | files: assets,
124 | options: options,
125 | },
126 | process,
127 | };
128 | },
129 | minify: {
130 | collapseWhitespace: true,
131 | removeAttributeQuotes: true,
132 | removeComments: true
133 | },
134 | nodeModules: process.env.NODE_ENV !== 'production'
135 | ? path.resolve(__dirname, '../node_modules')
136 | : false
137 | }),
138 | new webpack.NoEmitOnErrorsPlugin()
139 | ],
140 | output: {
141 | filename: '[name].js',
142 | libraryTarget: 'commonjs2',
143 | path: path.join(__dirname, '../dist/electron')
144 | },
145 | resolve: {
146 | alias: {
147 | '@': path.join(__dirname, '../src/renderer'),
148 | 'vue$': 'vue/dist/vue.esm.js'
149 | },
150 | extensions: ['.js', '.vue', '.json', '.css', '.node']
151 | },
152 | target: 'electron-renderer'
153 | }
154 |
155 | /**
156 | * Adjust rendererConfig for development settings
157 | */
158 | if (process.env.NODE_ENV !== 'production') {
159 | rendererConfig.plugins.push(
160 | new webpack.HotModuleReplacementPlugin(),
161 | new webpack.DefinePlugin({
162 | '__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`
163 | })
164 | )
165 | }
166 |
167 | /**
168 | * Adjust rendererConfig for production settings
169 | */
170 | if (process.env.NODE_ENV === 'production') {
171 | rendererConfig.devtool = ''
172 |
173 | rendererConfig.plugins.push(
174 | new MinifyPlugin(),
175 | new CopyWebpackPlugin([
176 | {
177 | from: path.join(__dirname, '../static'),
178 | to: path.join(__dirname, '../dist/electron/static'),
179 | ignore: ['.*']
180 | }
181 | ]),
182 | new webpack.DefinePlugin({
183 | 'process.env.NODE_ENV': '"production"'
184 | }),
185 | new webpack.LoaderOptionsPlugin({
186 | minimize: true
187 | })
188 | )
189 | }
190 |
191 | module.exports = rendererConfig
192 |
--------------------------------------------------------------------------------
/src/renderer/pages/search/subpages/market.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
15 |
16 |
![]()
17 |
18 |
19 |
20 |
插件
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
32 | {{ item.pluginName }}
33 |
37 |
38 |
39 |
40 |
46 |
47 |
![]()
48 |
49 |
{{currentSelect.pluginName}}
50 |
51 |
52 | 开发者:{{currentSelect.author}}
53 |
60 | 获取
61 |
62 |
63 |
{{currentSelect.description}}
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
142 |
143 |
220 |
--------------------------------------------------------------------------------
/src/renderer/assets/common/utils.js:
--------------------------------------------------------------------------------
1 | import {WINDOW_MAX_HEIGHT, WINDOW_MIN_HEIGHT, PRE_ITEM_HEIGHT, SYSTEM_PLUGINS} from './constans';
2 | import path from 'path';
3 | import fs from 'fs';
4 | import process from 'child_process';
5 | import Store from 'electron-store';
6 | import downloadFile from 'download';
7 | import {nativeImage, ipcRenderer} from 'electron';
8 | import {APP_FINDER_PATH} from './constans';
9 | import {getlocalDataFile} from "../../../main/common/utils";
10 |
11 | const store = new Store();
12 |
13 | function getWindowHeight(searchList) {
14 | if (!searchList) return WINDOW_MAX_HEIGHT;
15 | if (!searchList.length) return WINDOW_MIN_HEIGHT;
16 | return searchList.length * PRE_ITEM_HEIGHT + WINDOW_MIN_HEIGHT + 5 > WINDOW_MAX_HEIGHT ? WINDOW_MAX_HEIGHT : searchList.length * PRE_ITEM_HEIGHT + WINDOW_MIN_HEIGHT + 5;
17 | }
18 |
19 | function searchKeyValues(lists, value){
20 | return lists.filter(item => {
21 | if (typeof item === 'string') return item.indexOf(value) >= 0;
22 | return item.type.indexOf(value) >= 0;
23 | })
24 | }
25 |
26 | function existOrNot(path) {
27 | return new Promise((resolve, reject) => {
28 | fs.stat(path, async (err, stat) => {
29 | if (err) {
30 | resolve(false);
31 | } else {
32 | resolve(true);
33 | }
34 | });
35 | });
36 | }
37 |
38 | const appPath = getlocalDataFile();
39 |
40 | async function downloadZip(downloadRepoUrl, name) {
41 | try {
42 | const plugin_path = appPath;
43 | // 基础模版所在目录,如果是初始化,则是模板名称,否则是项目名称
44 | const temp_dest = `${plugin_path}/${name}`;
45 | // 下载模板
46 | if (await existOrNot(temp_dest)) {
47 | await process.execSync(`rm -rf ${temp_dest}`);
48 | }
49 |
50 | await downloadFile(downloadRepoUrl, plugin_path,{extract: true});
51 |
52 | return temp_dest;
53 | } catch (e) {
54 | console.log(e);
55 | }
56 | }
57 |
58 | const sysFile = {
59 | savePlugins(plugins) {
60 | ipcRenderer.send('optionPlugin', {
61 | plugins: plugins.filter((plugin) => {
62 | let hasOption = false;
63 | plugin.features.forEach(fe => {
64 | fe.cmds.forEach(cmd => {
65 | if (cmd.type) {
66 | hasOption = true;
67 | }
68 | })
69 | });
70 | return hasOption;
71 | })
72 | });
73 | store.set('user-plugins', plugins);
74 | },
75 | getUserPlugins() {
76 | try {
77 | return store.get('user-plugins');
78 | } catch (e) {
79 | return []
80 | }
81 | },
82 | removeAllPlugins() {
83 | store.delete('user-plugins');
84 | }
85 | }
86 |
87 | function mergePlugins(plugins) {
88 | const result = [
89 | ...plugins,
90 | ...SYSTEM_PLUGINS.map(plugin => {
91 | return {
92 | ...plugin,
93 | status: true,
94 | sourceFile: '',
95 | type: 'system',
96 | }
97 | })
98 | ]
99 |
100 | const target = [];
101 |
102 | result.forEach((item, i) => {
103 | let targetIndex = -1;
104 | target.forEach((tg, j) => {
105 | if (tg.tag === item.tag && tg.type === 'system') {
106 | targetIndex = j
107 | }
108 | });
109 | if (targetIndex === -1) {
110 | target.push(item)
111 | }
112 | });
113 | ipcRenderer && ipcRenderer.send('optionPlugin', {
114 | plugins: target.filter((plugin) => {
115 | let hasOption = false;
116 | plugin.features.forEach(fe => {
117 | fe.cmds.forEach(cmd => {
118 | if (cmd.type) {
119 | hasOption = true;
120 | }
121 | })
122 | });
123 | return hasOption;
124 | })
125 | });
126 |
127 | return target
128 | }
129 |
130 | function find(p, target = 'plugin.json') {
131 | try {
132 | let result;
133 | const fileList = fs.readdirSync(p);
134 | for (let i = 0; i < fileList.length; i++) {
135 | let thisPath = p + "/" + fileList[i];
136 | const data = fs.statSync(thisPath);
137 |
138 | if (data.isFile() && fileList[i] === target) {
139 | result = path.join(thisPath, '../');
140 | return result;
141 | }
142 | if (data.isDirectory()) {
143 | result = find(thisPath);
144 | }
145 | }
146 | return result;
147 | } catch (e) {
148 | console.log(e);
149 | }
150 | }
151 | const fileLists = [];
152 | // 默认搜索目录
153 | APP_FINDER_PATH.forEach((searchPath) => {
154 | fs.readdir(searchPath, async (err, files) => {
155 | try {
156 | for (let i = 0; i < files.length; i++) {
157 | const appName = files[i];
158 | const extname = path.extname(appName);
159 | const appSubStr = appName.split(extname)[0];
160 | if ((extname === '.app' || extname === '.prefPane') >= 0 ) {
161 | try {
162 | const path1 = path.join(searchPath, `${appName}/Contents/Resources/App.icns`);
163 | const path2 = path.join(searchPath, `${appName}/Contents/Resources/AppIcon.icns`);
164 | const path3 = path.join(searchPath, `${appName}/Contents/Resources/${appSubStr}.icns`);
165 | const path4 = path.join(searchPath, `${appName}/Contents/Resources/${appSubStr.replace(' ', '')}.icns`);
166 | let iconPath = path1;
167 | if (fs.existsSync(path1)) {
168 | iconPath = path1;
169 | } else if (fs.existsSync(path2)) {
170 | iconPath = path2;
171 | } else if (fs.existsSync(path3)) {
172 | iconPath = path3;
173 | } else if (fs.existsSync(path4)) {
174 | iconPath = path4;
175 | } else {
176 | // 性能最低的方式
177 | const resourceList = fs.readdirSync(path.join(searchPath, `${appName}/Contents/Resources`));
178 | const iconName = resourceList.filter(file => path.extname(file) === '.icns')[0];
179 | iconPath = path.join(searchPath, `${appName}/Contents/Resources/${iconName}`);
180 | }
181 | const img = await nativeImage.createThumbnailFromPath(iconPath, {width: 64, height: 64});
182 | fileLists.push({
183 | name: appSubStr,
184 | value: 'plugin',
185 | icon: img.toDataURL(),
186 | desc: path.join(searchPath, appName),
187 | type: 'app',
188 | action: `open ${path.join(searchPath, appName).replace(' ', '\\ ')}`
189 | })
190 | } catch (e) {
191 | }
192 |
193 | }
194 | }
195 | } catch (e) {
196 | console.log(e);
197 | }
198 | });
199 | });
200 |
201 |
202 | function debounce(fn, delay) {
203 | let timer
204 | return function () {
205 | const context = this
206 | const args = arguments
207 |
208 | clearTimeout(timer)
209 | timer = setTimeout(function () {
210 | fn.apply(context, args)
211 | }, delay)
212 | }
213 | }
214 |
215 | export {
216 | getWindowHeight,
217 | searchKeyValues,
218 | sysFile,
219 | mergePlugins,
220 | find,
221 | downloadZip,
222 | fileLists,
223 | debounce,
224 | }
225 |
--------------------------------------------------------------------------------
/src/renderer/pages/search/subpages/dev.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | {{pluginDetail.pluginName}}
20 |
{{pluginDetail.version}}
21 |
22 |
23 | 开发者:{{`${pluginDetail.author || '未知'}`}}
24 |
25 |
26 | {{pluginDetail.description}}
27 |
28 |
29 |
32 |
33 |
34 |
35 |
36 |
40 |
41 | 如果你修改了plugin.json文件需要重新加载以应用最近版本
42 |
43 |
44 |
45 |
49 |
50 | 发布后用户可以通过插件中心下载,且享受最新的更新
51 |
52 |
53 |
54 |
58 |
59 | 删除这个插件不可以恢复
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
72 |
73 |
74 |
75 |
142 |
143 |
227 |
--------------------------------------------------------------------------------
/static/plugins/superPanel/index.js:
--------------------------------------------------------------------------------
1 | const {ipcRenderer, nativeImage, remote, clipboard} = require('electron')
2 | const rp = require("request-promise");
3 | const path = require('path');
4 | const fs = require('fs');
5 | const { spawn } = require ('child_process');
6 | const mineType = require("mime-types");
7 |
8 | new Vue({
9 | el: '#app',
10 | data: {
11 | code: '',
12 | current: {},
13 | selectData: {
14 | translate: {},
15 | optionPlugin: [],
16 | },
17 | options: {
18 | translate: [],
19 | common: [
20 | {
21 | type: 'default',
22 | name: '终端中打开',
23 | icon: './assets/terminal.png',
24 | click: (fileUrl) => {
25 | spawn('open', [ '-a', 'Terminal', fileUrl ]);
26 | }
27 | },
28 | {
29 | type: 'default',
30 | name: '新建文件',
31 | icon: './assets/new.png',
32 | click: (fileUrl) => {
33 | remote.dialog.showSaveDialog({
34 | title: "请选择要保存的文件名",
35 | buttonLabel: "保存",
36 | defaultPath: fileUrl.replace('file://', ''),
37 | showsTagField: false,
38 | nameFieldLabel: '',
39 | }).then(result => {
40 | fs.writeFileSync(result.filePath, '');
41 | });
42 | }
43 | },
44 | {
45 | type: 'default',
46 | name: '复制当前路径',
47 | icon: './assets/link.png',
48 | click: (fileUrl) => {
49 | clipboard.writeText(fileUrl.replace('file://', ''))
50 | }
51 | }
52 | ],
53 | selected: [
54 | {
55 | type: 'default',
56 | name: '复制当前路径',
57 | icon: './assets/link.png',
58 | click: (fileUrl) => {
59 | clipboard.writeText(fileUrl.replace('file://', ''))
60 | }
61 | }
62 | ]
63 | },
64 | targetOptions: [],
65 | loading: false,
66 | },
67 | created() {
68 | // 简单唤起超级面板
69 | ipcRenderer.on('trigger-super-panel', (e, args) => {
70 | this.selectData = args;
71 | const ext = path.extname(this.selectData.fileUrl);
72 | // 剪切板只有文本时,显示翻译
73 | if (!this.selectData.fileUrl) {
74 | this.loading = true;
75 | const word = this.selectData.text;
76 | this.translate(word);
77 | this.targetOptions = JSON.parse(JSON.stringify(this.options.translate));
78 | (this.selectData.optionPlugin || []).forEach(plugin => {
79 | plugin.features.forEach(fe => {
80 | fe.cmds.forEach(cmd => {
81 | if (cmd.type === 'regex' && eval(cmd.match).test(word)) {
82 | this.targetOptions.push({
83 | type: 'ext',
84 | name: cmd.label,
85 | icon: plugin.icon,
86 | click: () => {
87 | ipcRenderer.send('superPanel-openPlugin', {
88 | cmd: cmd,
89 | plugin: plugin,
90 | feature: fe,
91 | data: word
92 | })
93 | }
94 | });
95 | }
96 | if (cmd.type === 'over') {
97 | this.targetOptions.push({
98 | type: 'ext',
99 | name: cmd.label,
100 | icon: plugin.icon,
101 | click: () => {
102 | ipcRenderer.send('superPanel-openPlugin', {
103 | cmd: cmd,
104 | plugin: plugin,
105 | feature: fe,
106 | data: word
107 | })
108 | }
109 | });
110 | }
111 | })
112 | });
113 | });
114 | } else if (!ext || path.parse(this.selectData.fileUrl).base === 'Desktop') {
115 | // 如果在桌面上或者没有选择任何文件,则展示通用选项
116 | this.targetOptions = this.options.common;
117 | } else {
118 | // 有文件选择
119 | this.targetOptions = JSON.parse(JSON.stringify(this.options.selected));
120 | // 检测上传
121 | (this.selectData.optionPlugin || []).forEach(plugin => {
122 | plugin.features.forEach(fe => {
123 | fe.cmds.forEach(cmd => {
124 | // 如果是图片,则唤起图片选项
125 | const regImg = /\.(png|jpg|gif|jpeg|webp)$/;
126 | if (cmd.type === 'img' && regImg.test(ext)) {
127 | this.targetOptions.push({
128 | type: 'ext',
129 | name: cmd.label,
130 | icon: plugin.icon,
131 | click: (fileUrl) => {
132 | const base64 = this.fileToBase64(fileUrl);
133 | ipcRenderer.send('superPanel-openPlugin', {
134 | cmd: cmd,
135 | plugin: plugin,
136 | feature: fe,
137 | data: base64,
138 | });
139 | }
140 | })
141 | }
142 | // 如果是文件,且符合文件正则类型
143 | if (cmd.type === 'file' && new RegExp(cmd.match).test(ext)) {
144 | this.targetOptions.push({
145 | type: 'ext',
146 | name: cmd.label,
147 | icon: '',
148 | click: () => {
149 | ipcRenderer.send('superPanel-openPlugin', {
150 | cmd: cmd,
151 | plugin: plugin,
152 | feature: fe,
153 | data: {
154 | isFile: true,
155 | isDirectory: false,
156 | name: path.basename(this.selectData.fileUrl),
157 | path: this.selectData.fileUrl
158 | }
159 | })
160 | }
161 | })
162 | }
163 | })
164 | });
165 | });
166 | }
167 | });
168 |
169 | },
170 |
171 | methods: {
172 | translate(msg) {
173 | const params = encodeURI(
174 | `q=${msg}&keyfrom=neverland&key=969918857&type=data&doctype=json&version=1.1`
175 | );
176 | const options = {
177 | uri: `http://fanyi.youdao.com/openapi.do?${params}`,
178 | };
179 | return rp(options).then((res) => {
180 | this.$set(this.selectData, 'translate', {
181 | ...JSON.parse(res),
182 | src: msg,
183 | });
184 | }).finally(() => {
185 | this.loading = false;
186 | })
187 | },
188 | commonClick(item, fileUrl) {
189 | ipcRenderer.send('superPanel-hidden')
190 | item.click(fileUrl);
191 | },
192 |
193 | fileToBase64 (filePath) {
194 | let data = fs.readFileSync(filePath.replace('file://', ''));
195 | data = new Buffer(data).toString("base64");
196 | let base64 = "data:" + mineType.lookup(filePath) + ";base64," + data;
197 | return base64;
198 | },
199 |
200 | openMainWindow() {
201 | ipcRenderer.send('msg-trigger', {
202 | type: 'showMainWindow',
203 | });
204 | }
205 | },
206 |
207 | watch: {
208 | selectData: {
209 | deep: true,
210 | handler() {
211 | this.$nextTick(() => {
212 | ipcRenderer.send('superPanel-setSize', parseInt(getComputedStyle(document.getElementById('app')).height))
213 | })
214 | }
215 | }
216 | }
217 | })
218 |
--------------------------------------------------------------------------------
/src/main/common/listener.js:
--------------------------------------------------------------------------------
1 | import {BrowserWindow, clipboard, globalShortcut, ipcMain, Notification, screen} from "electron";
2 | import {exec, spawn} from "child_process";
3 | import robot from "robotjs";
4 | import Api from "./api";
5 | import ioHook from 'iohook';
6 | import {throttle} from './utils';
7 |
8 | const browsers = require("../browsers")();
9 | const {picker, separator, superPanel} = browsers;
10 |
11 | class Listener {
12 | constructor() {
13 | this.optionPlugin = {};
14 | }
15 |
16 | getSelectedContent() {
17 | return new Promise((resolve) => {
18 | const lastText = clipboard.readText('clipboard');
19 | // todo 缓存文件
20 | clipboard.clear();
21 |
22 | // 复制选中文案
23 | const platform = process.platform;
24 | if (platform === 'darwin') {
25 | robot.keyTap('c', 'command');
26 | } else {
27 | robot.keyTap('c', 'control');
28 | }
29 |
30 | setTimeout(() => {
31 | // 延时一定时间才能从剪切板内读取到内容
32 | const text = clipboard.readText('clipboard') || ''
33 | const fileUrl = clipboard.read('public.file-url');
34 |
35 | // 如果之前是文案,则回填
36 | clipboard.writeText(lastText);
37 |
38 | resolve({
39 | text,
40 | fileUrl
41 | })
42 | }, 300);
43 | })
44 | }
45 |
46 | registerShortCut(mainWindow) {
47 | const config = global.opConfig.get();
48 | globalShortcut.unregisterAll();
49 | // 注册偏好快捷键
50 | globalShortcut.register(config.perf.shortCut.showAndHidden, () => {
51 | const {x, y} = screen.getCursorScreenPoint();
52 | const currentDisplay = screen.getDisplayNearestPoint({ x, y });
53 | const wx = parseInt(currentDisplay.workArea.x + currentDisplay.workArea.width / 2 - 400);
54 | const wy = parseInt(currentDisplay.workArea.y + currentDisplay.workArea.height / 2 - 200);
55 | mainWindow.setVisibleOnAllWorkspaces(true);
56 | mainWindow.focus();
57 | mainWindow.setVisibleOnAllWorkspaces(false);
58 | mainWindow.setPosition(wx, wy);
59 | mainWindow.show();
60 | });
61 |
62 | globalShortcut.register(config.perf.shortCut.separate, () => {
63 | mainWindow.webContents.send('new-window');
64 | });
65 |
66 | // 注册自定义全局快捷键
67 | config.global.forEach(sc => {
68 | if (!sc.key || !sc.value) return;
69 | globalShortcut.register(sc.key, () => {
70 | mainWindow.webContents.send('global-short-key', sc.value);
71 | });
72 | });
73 | }
74 |
75 | init(mainWindow) {
76 | this.fn = throttle(({x, y}, picker) => {
77 | const img = robot.screen.capture(parseInt(x) - 5, parseInt(y) - 5, 9, 9);
78 |
79 | const colors = {}
80 |
81 | for(let i = 0; i< 9; i++) {
82 | colors[i] = {};
83 | for (let j = 0; j < 9; j++) {
84 | colors[i][j] = img.colorAt(j, i);
85 | }
86 | }
87 | picker.getWindow().webContents.send("updatePicker", colors);
88 | }, 100);
89 |
90 | this.colorPicker();
91 | this.initPlugin();
92 | this.lockScreen();
93 | this.separate();
94 | this.initCapture();
95 | this.superPanel(mainWindow);
96 | this.reRegisterShortCut(mainWindow);
97 | this.changeSize(mainWindow);
98 | this.msgTrigger(mainWindow);
99 | }
100 |
101 | colorPicker() {
102 | // 拾色器
103 | ipcMain.on('start-picker', () => {
104 | // 开启输入侦测
105 | ioHook.start(false);
106 | ioHook.load();
107 | picker.init();
108 |
109 | picker.getWindow().on('close', () => {
110 | ioHook.stop();
111 | ioHook.unload();
112 | });
113 |
114 | let pos = robot.getMousePos();
115 | picker
116 | .getWindow()
117 | .setPosition(parseInt(pos.x) + 10, parseInt(pos.y) + 10);
118 |
119 | const img = robot.screen.capture(parseInt(pos.x) - 5, parseInt(pos.y) - 5, 9, 9);
120 |
121 | const colors = {}
122 |
123 | for(let i = 0; i< 9; i++) {
124 | colors[i] = {};
125 | for (let j = 0; j < 9; j++) {
126 | colors[i][j] = img.colorAt(j, i);
127 | }
128 | }
129 |
130 | picker
131 | .getWindow()
132 | .webContents.send(
133 | "updatePicker",
134 | colors
135 | );
136 |
137 | ipcMain.on("closePicker", () => {
138 | this.closePicker();
139 | });
140 | });
141 | ioHook.on('mousemove', e => {
142 | let x = e.x
143 | let y = e.y
144 | if (!picker.getWindow()) return;
145 | picker.getWindow().setPosition(parseInt(x) + 10, parseInt(y) + 10);
146 | this.fn(e, picker);
147 | })
148 |
149 | ioHook.on('mouseup', e => {
150 | if (e.button === 1) {
151 | let x = e.x
152 | let y = e.y
153 | const color = "#" + robot.getPixelColor(parseInt(x), parseInt(y));
154 | clipboard.writeText("#" + robot.getPixelColor(parseInt(x), parseInt(y)));
155 | new Notification({ title: 'Rubick 通知', body: `${color} 已保存到剪切板` }).show();
156 | this.closePicker();
157 | }
158 | });
159 |
160 | ioHook.on('mouseup', e => {
161 | if (e.button === 3) {
162 | this.closePicker()
163 | }
164 | });
165 | }
166 |
167 | closePicker() {
168 | if (picker.getWindow()) {
169 | ipcMain.removeListener("closePicker", this.closePicker);
170 | picker.getWindow().close();
171 | }
172 | }
173 |
174 | initPlugin() {
175 | ipcMain.on('optionPlugin', (e, args) => {
176 | this.optionPlugin = args;
177 | });
178 | }
179 |
180 | lockScreen() {
181 | // 锁屏
182 | ipcMain.on('lock-screen', () => {
183 | const lockCommands = {
184 | darwin: '/System/Library/CoreServices/"Menu Extras"/User.menu/Contents/Resources/CGSession -suspend',
185 | win32: 'rundll32.exe user32.dll, LockWorkStation',
186 | linux: '(hash gnome-screensaver-command 2>/dev/null && gnome-screensaver-command -l) || (hash dm-tool 2>/dev/null && dm-tool lock)'
187 | };
188 | exec(lockCommands[process.platform]);
189 | });
190 | }
191 |
192 | superPanel(mainWindow) {
193 | // 长按右击呼起超级面板
194 | ipcMain.on('right-down', async () => {
195 | const copyResult = await this.getSelectedContent();
196 | let win = superPanel.getWindow();
197 |
198 | if (win) {
199 | win.webContents.send('trigger-super-panel', {
200 | ...copyResult,
201 | optionPlugin: this.optionPlugin.plugins,
202 | });
203 | } else {
204 | superPanel.init(mainWindow);
205 | win = superPanel.getWindow();
206 |
207 | win.once('ready-to-show', () => {
208 | win.webContents.send('trigger-super-panel', {
209 | ...copyResult,
210 | optionPlugin: this.optionPlugin.plugins,
211 | });
212 | });
213 | }
214 | const pos = robot.getMousePos();
215 | win.setPosition(parseInt(pos.x), parseInt(pos.y));
216 | win.show();
217 | });
218 | }
219 |
220 | reRegisterShortCut(mainWindow) {
221 | ipcMain.on('re-register', (event, arg) => {
222 | this.registerShortCut(mainWindow);
223 | });
224 | }
225 |
226 | changeSize(mainWindow) {
227 | // 修改窗口尺寸
228 | ipcMain.on('changeWindowSize-rubick', (event, arg) => {
229 | mainWindow.setSize(arg.width || 800, arg.height);
230 | });
231 | }
232 |
233 | msgTrigger(mainWindow) {
234 | // 响应 preload.js 事件
235 | ipcMain.on('msg-trigger', async (event, arg) => {
236 | const window = arg.winId ? BrowserWindow.fromId(arg.winId) : mainWindow
237 | const operators = arg.type.split('.');
238 | let fn = Api;
239 | operators.forEach((op) => {
240 | fn = fn[op];
241 | });
242 | const data = await fn(arg, window);
243 | event.sender.send(`msg-back-${arg.type}`, data);
244 | });
245 | }
246 |
247 | separate() {
248 | // 窗口分离
249 | ipcMain.on('new-window', (event, arg) => {
250 | const opts = {
251 | ...arg,
252 | searchType: 'subWindow',
253 | }
254 | separator.init(JSON.stringify(opts));
255 | });
256 | }
257 |
258 | initCapture() {
259 | ipcMain.on('capture-screen', () => {
260 | spawn('/usr/sbin/screencapture', ["-c", "-i", "-r"], {detached: !0});
261 | });
262 | }
263 | }
264 |
265 | export default Listener;
266 |
--------------------------------------------------------------------------------
/static/preload.js:
--------------------------------------------------------------------------------
1 | const {getData, getlocalDataFile, saveData} = require("./utils");
2 |
3 | const marked = require("marked");
4 | const rendererMD = new marked.Renderer();
5 | const path = require('path');
6 | const os = require('os');
7 |
8 | const appPath = path.join(getlocalDataFile());
9 | const dbPath = path.join(appPath, './db.json');
10 |
11 | let filePath = '';
12 | function getQueryVariable(variable) {
13 | var query = window.location.search.substring(1);
14 | var vars = query.split("&");
15 | for (var i=0;i -1) {
23 | filePath = decodeURIComponent(getQueryVariable('targetFile'));
24 | } else {
25 | filePath = location.pathname.replace('file://', '');
26 | }
27 | const {ipcRenderer, nativeImage, clipboard, remote, shell} = require('electron');
28 |
29 | const currentWindow = remote.getCurrentWindow();
30 | const winId = currentWindow.id;
31 | const BrowserWindow = remote.BrowserWindow;
32 |
33 | function convertImgToBase64(url, callback, outputFormat){
34 | var canvas = document.createElement('CANVAS'),
35 | ctx = canvas.getContext('2d'),
36 | img = new Image;
37 | img.crossOrigin = 'Anonymous';
38 | img.onload = function(){
39 | canvas.height = img.height;
40 | canvas.width = img.width;
41 | ctx.drawImage(img,0,0);
42 | var dataURL = canvas.toDataURL(outputFormat || 'image/png');
43 | callback.call(this, dataURL);
44 | canvas = null;
45 | };
46 | img.src = url;
47 | }
48 |
49 | window.utools = window.rubick = {
50 | // 事件
51 | onPluginEnter(cb) {
52 | ipcRenderer.on('onPluginEnter', (e, message) => {
53 | const feature = message.detail;
54 | cb({...feature, type: message.cmd.type ? message.cmd.type : 'text', payload: message.payload})
55 | })
56 | },
57 | onPluginReady(cb) {
58 | ipcRenderer.once('onPluginReady', (e, message) => {
59 | const feature = message.detail
60 | cb({...feature, type: message.cmd.type ? message.cmd.type : 'text', payload: message.payload})
61 | })
62 | },
63 | onPluginOut(cb) {
64 | ipcRenderer.once('onPluginOut', (e, message) => {
65 | const feature = JSON.parse(message.detail)
66 | cb({...feature, type: 'text'})
67 | })
68 | },
69 |
70 | // 窗口交互
71 | hideMainWindow() {
72 | ipcRenderer.send('msg-trigger', {
73 | type: 'hideMainWindow',
74 | });
75 | },
76 | showMainWindow() {
77 | ipcRenderer.send('msg-trigger', {
78 | type: 'showMainWindow',
79 | });
80 | },
81 | setExpendHeight(height) {
82 | ipcRenderer.send('msg-trigger', {
83 | type: 'setExpendHeight',
84 | height,
85 | winId
86 | });
87 | },
88 | setSubInput(onChange, placeHolder, isFocus) {
89 | ipcRenderer.sendToHost('setSubInput', {
90 | placeHolder, isFocus
91 | });
92 | ipcRenderer.on(`msg-back-setSubInput`, (e, result) => {
93 | onChange({text: result});
94 | });
95 | },
96 |
97 | removeSubInput() {
98 | ipcRenderer.sendToHost('removeSubInput');
99 | },
100 |
101 | setSubInputValue(text) {
102 | ipcRenderer.sendToHost('setSubInputValue', {
103 | text
104 | });
105 | },
106 |
107 | getPath(name) {
108 | return remote.app.getPath(name);
109 | },
110 |
111 | showNotification(body, clickFeatureCode) {
112 | const myNotification = new Notification('Rubick 通知', {
113 | body
114 | });
115 | return myNotification;
116 | // todo 实现 clickFeatureCode
117 | },
118 | showOpenDialog(options) {
119 | ipcRenderer.send('msg-trigger', {
120 | type: 'showOpenDialog',
121 | options: {...options},
122 | });
123 | return new Promise((resolve, reject) => {
124 | ipcRenderer.once(`msg-back-showOpenDialog`, (e, result) => {
125 | result ? resolve(result) : reject();
126 | });
127 | })
128 | },
129 |
130 | copyImage(img) {
131 | convertImgToBase64(img,function(base64Image) {
132 | const image = nativeImage.createFromDataURL(base64Image)
133 | clipboard.writeImage(image)
134 | })
135 | },
136 | copyText(text) {
137 | clipboard.writeText(text);
138 | },
139 | db: {
140 | put(data) {
141 | data._rev = '';
142 | let dbData = getData(dbPath) || [];
143 | let target = [];
144 | dbData.some((d, i) => {
145 | if (d._id === data._id) {
146 | target = [d, i]
147 | return true;
148 | }
149 | return false;
150 | });
151 |
152 | // 更新
153 | if (target[0]) {
154 | dbData[target[1]] = data;
155 | } else {
156 | dbData.push(data);
157 | }
158 | saveData(dbPath, dbData);
159 | return {
160 | id: data._id,
161 | ok: true,
162 | rev: '',
163 | }
164 | },
165 | get(key) {
166 | const dbData = getData(dbPath) || [];
167 | return dbData.find(d => d._id === key) || '';
168 | },
169 | remove(key) {
170 | key = typeof key === 'object' ? key._id : key;
171 | let dbData = getData(dbPath);
172 | let find = false;
173 | dbData.some((d, i) => {
174 | if (d._id === key) {
175 | dbData.splice(i, 1);
176 | find = true;
177 | return true;
178 | }
179 | return false;
180 | });
181 | if (find) {
182 | saveData(dbPath, dbData);
183 | return {
184 | id: key,
185 | ok: true,
186 | rev: '',
187 | }
188 | } else {
189 | return {
190 | id: key,
191 | ok: false,
192 | rev: '',
193 | }
194 | }
195 | },
196 | bulkDocs(docs) {
197 | const dbData = getData(dbPath);
198 | dbData.forEach((d, i) => {
199 | const result = docs.find(data => data._id === d._id);
200 | if (result) {
201 | dbData[i] = result;
202 | }
203 | });
204 | saveData(dbPath, dbData);
205 | return docs.map(d => ({
206 | id: d._id,
207 | success: true,
208 | rev: '',
209 | }))
210 | },
211 | allDocs(key) {
212 | const dbData = getData(dbPath);
213 | if (!key) {
214 | return dbData;
215 | }
216 | if (typeof key === 'string') {
217 | return dbData.filter(d => d._id.indexOf(key) >= 0);
218 | }
219 | if (Array.isArray(key)) {
220 | return dbData.filter(d => key.indexOf(d._id) >= 0);
221 | }
222 | return [];
223 | }
224 | },
225 | isDarkColors() {
226 | return false;
227 | },
228 | getFeatures() {
229 | ipcRenderer.sendToHost('getFeatures');
230 | return new Promise(resolve => {
231 | ipcRenderer.on(`msg-back-getFeatures`, (e, result) => {
232 | resolve(result);
233 | });
234 | });
235 | },
236 | setFeature(feature) {
237 | ipcRenderer.sendToHost('setFeature', {feature});
238 | },
239 |
240 | removeFeature(code) {
241 | ipcRenderer.sendToHost('removeFeature', {code});
242 | },
243 | ubrowser: {
244 | winId: '',
245 | async goto(md, opts) {
246 | const objExp = new RegExp(/http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- .\/?%&=]*)?/);
247 | let winId;
248 | let win;
249 | win = new BrowserWindow({
250 | show: false,
251 | title: typeof opts === 'object' ? '' : opts,
252 | webPreferences: {
253 | webSecurity: false,
254 | enableRemoteModule: true,
255 | backgroundThrottling: false,
256 | webviewTag: true,
257 | nodeIntegration: true // 在网页中集成Node
258 | }
259 | });
260 | if(objExp.test(md) && md.indexOf('http') === 0) {
261 | await win.loadURL(md);
262 | winId = win.id;
263 | } else {
264 | marked.setOptions({
265 | renderer: rendererMD,
266 | gfm: true,
267 | tables: true,
268 | breaks: false,
269 | pedantic: false,
270 | sanitize: false,
271 | smartLists: true,
272 | smartypants: false
273 | });
274 | const htmlContent = marked(md);
275 | win.loadURL('data:text/html;charset=UTF-8,' + encodeURIComponent(htmlContent))
276 | win.once('ready-to-show', () => win.show());
277 | winId = win.id;
278 | }
279 | return {
280 | value(selector, value) {
281 | ipcRenderer.send('msg-trigger', {
282 | type: 'ubrowser.value',
283 | winId,
284 | selector, value
285 | });
286 | return new Promise(resolve => {
287 | ipcRenderer.once(`msg-back-ubrowser.value`, (e, result) => {
288 | resolve(this)
289 | });
290 | })
291 | },
292 | click(selector) {
293 | ipcRenderer.send('msg-trigger', {
294 | type: 'ubrowser.click',
295 | winId,
296 | selector,
297 | });
298 | return new Promise(resolve => {
299 | ipcRenderer.once(`msg-back-ubrowser.click`, (e, result) => {
300 | resolve(this)
301 | });
302 | })
303 | },
304 | run(options) {
305 | ipcRenderer.send('msg-trigger', {
306 | type: 'ubrowser.run',
307 | winId,
308 | ...options
309 | });
310 | }
311 | }
312 | },
313 | },
314 |
315 | // 系统
316 | shellOpenExternal(url) {
317 | shell.openExternal(url);
318 | },
319 |
320 | isMacOs() {
321 | return os.type() === 'Darwin';
322 | },
323 |
324 | isWindows() {
325 | return os.type() === 'Windows_NT';
326 | },
327 | }
328 | const preloadPath = getQueryVariable('preloadPath') || './preload.js';
329 |
330 | require(path.join(filePath, '../', preloadPath));
331 | window.exports && ipcRenderer.sendToHost('templateConfig', {config: JSON.parse(JSON.stringify(window.exports))});
332 | window.ipcRenderer = ipcRenderer;
333 |
--------------------------------------------------------------------------------
/src/renderer/store/modules/main.js:
--------------------------------------------------------------------------------
1 | import {clipboard, ipcRenderer, remote} from "electron";
2 | import { v4 as uuidv4 } from 'uuid';
3 | import {
4 | getWindowHeight,
5 | searchKeyValues,
6 | sysFile,
7 | mergePlugins,
8 | find,
9 | downloadZip,
10 | fileLists,
11 | } from '../../assets/common/utils';
12 | import systemMethod from '../../assets/common/system';
13 | import fs from "fs";
14 | import path from 'path';
15 | import {execSync} from 'child_process';
16 |
17 | const state = {
18 | selected: null,
19 | options: [],
20 | showMain: false,
21 | current: ['market'],
22 | searchValue: '',
23 | devPlugins: mergePlugins(sysFile.getUserPlugins() || []),
24 | subPlaceHolder: '',
25 | pluginInfo: (() => {
26 | try {
27 | console.log(window.pluginInfo);
28 | return window.pluginInfo || {}
29 | } catch (e) {}
30 | })(),
31 | }
32 |
33 | const mutations = {
34 | commonUpdate (state, payload) {
35 | Object.keys(payload).forEach((key) => {
36 | state[key] = payload[key];
37 | if (key === 'devPlugins') {
38 | sysFile.savePlugins(payload[key])
39 | }
40 | });
41 | },
42 | setSubPlaceHolder(state, payload) {
43 | state.subPlaceHolder = payload;
44 | },
45 | deleteDevPlugin(state, payload) {
46 | state.devPlugins = state.devPlugins.filter(plugin => plugin.name !== payload.name);
47 | sysFile.savePlugins(state.devPlugins);
48 | },
49 | deleteProdPlugin(state, payload) {
50 | state.devPlugins = state.devPlugins.filter(plugin => plugin.id !== payload.id);
51 | sysFile.savePlugins(state.devPlugins);
52 | // todo 删除 static 目录下的对应插件
53 | },
54 | devPluginStatusChange(state, payload) {
55 | state.devPlugins.forEach(plugin => {
56 | if (plugin.name === payload.name) {
57 | plugin.status = !plugin.status;
58 | }
59 | });
60 | state.devPlugins = [...state.devPlugins];
61 | sysFile.savePlugins(state.devPlugins);
62 | }
63 | }
64 |
65 | const actions = {
66 | showMainUI ({ commit, state }, paylpad) {
67 | ipcRenderer.send('changeWindowSize-rubick', {
68 | height: getWindowHeight(),
69 | });
70 | setTimeout(() => {
71 | commit('commonUpdate', {
72 | showMain: true,
73 | selected: {
74 | key: 'market',
75 | name: '插件中心'
76 | }
77 | });
78 | }, 50);
79 | },
80 | reloadDevPlugin({ commit }, payload) {
81 | const config = JSON.parse(fs.readFileSync(path.join(payload.sourceFile, '../plugin.json'), 'utf-8'));
82 | const pluginConfig = {
83 | ...config,
84 | sourceFile: path.join(payload.sourceFile, `../${config.main}`),
85 | };
86 | const devPlugins = [...state.devPlugins];
87 | commit('commonUpdate', {
88 | devPlugins: devPlugins.map(plugin => {
89 | if (plugin.name === payload.name) {
90 | return {
91 | ...plugin,
92 | ...pluginConfig,
93 | }
94 | }
95 | return plugin;
96 | })
97 | })
98 | },
99 | async onSearch ({ commit }, paylpad) {
100 | if (state.selected && state.selected.key !== 'plugin-container') {
101 | commit('commonUpdate', {searchValue: ''});
102 | return;
103 | }
104 | const value = paylpad.value;
105 | // 在插件界面不触发其他功能
106 | if((state.selected && state.selected.key === 'plugin-container') || paylpad.searchType === 'subWindow') {
107 | commit('commonUpdate', {searchValue: value});
108 | return;
109 | }
110 | const fileUrl = clipboard.read('public.file-url').replace('file://', '');
111 | commit('commonUpdate', {searchValue: value})
112 | // 复制文件
113 | if (fileUrl && value === 'plugin.json') {
114 | const config = JSON.parse(fs.readFileSync(fileUrl, 'utf-8'));
115 |
116 | const pluginConfig = {
117 | ...config,
118 | sourceFile: path.join(fileUrl, `../${config.main || 'index.html'}`),
119 | id: uuidv4(),
120 | type: 'dev',
121 | icon: 'image://' + path.join(fileUrl, `../${config.logo}`),
122 | subType: (() => {
123 | if (config.main) {
124 | return ''
125 | }
126 | return 'template';
127 | })()
128 | };
129 | commit('commonUpdate', {
130 | selected: {
131 | key: 'plugin',
132 | name: 'plugin.json'
133 | },
134 | searchValue: '',
135 | devPlugins: [pluginConfig, ...state.devPlugins],
136 | options: [
137 | {
138 | name: '新建rubick开发插件',
139 | value: 'new-plugin',
140 | icon: 'https://static.91jkys.com/activity/img/b37ff555c748489f88f3adac15b76f18.png',
141 | desc: '新建rubick开发插件',
142 | click: (router) => {
143 | commit('commonUpdate', {
144 | showMain: true,
145 | selected: {
146 | key: 'plugin',
147 | name: '新建rubick开发插件'
148 | },
149 | current: ['dev'],
150 | });
151 | ipcRenderer.send('changeWindowSize-rubick', {
152 | height: getWindowHeight(),
153 | });
154 | router.push('/home/dev')
155 | }
156 | },
157 | {
158 | name: '复制路径',
159 | desc: '复制路径',
160 | value: 'copy-path',
161 | icon: 'https://static.91jkys.com/activity/img/ac0d4df0247345b9a84c8cd7ea3dd696.png',
162 | click: () => {
163 | clipboard.writeText(fileUrl);
164 | commit('commonUpdate', {
165 | showMain: false,
166 | selected: null,
167 | options: [],
168 | });
169 | ipcRenderer.send('changeWindowSize-rubick', {
170 | height: getWindowHeight([]),
171 | });
172 | remote.Notification('Rubick 通知', { body: '复制成功' });
173 | }
174 | }
175 | ]
176 | });
177 | // 调整窗口大小
178 | ipcRenderer.send('changeWindowSize-rubick', {
179 | height: getWindowHeight(state.options),
180 | });
181 | return
182 | }
183 |
184 | let options = [];
185 |
186 | // check 是否是插件
187 | if (value) {
188 | state.devPlugins.forEach((plugin) => {
189 | // dev 插件未开启
190 | if (plugin.type === 'dev' && !plugin.status) return;
191 | const feature = plugin.features;
192 | feature.forEach(fe => {
193 | const cmds = searchKeyValues(fe.cmds, value);
194 | options = [
195 | ...options,
196 | ...cmds.map((cmd) => ({
197 | name: cmd,
198 | value: 'plugin',
199 | icon: plugin.sourceFile ? 'image://' + path.join(plugin.sourceFile, `../${plugin.logo}`) : plugin.logo,
200 | desc: fe.explain,
201 | type: plugin.type,
202 | click: (router) => {
203 | actions.openPlugin({commit}, {cmd, plugin, feature: fe, router});
204 | }
205 | }))
206 | ]
207 | })
208 | });
209 | options = [
210 | ...options,
211 | ...(fileLists.filter(plugin => plugin.name.indexOf(value) >= 0)).map(plugin => {
212 | plugin.click = () => {
213 | actions.openPlugin({commit}, {plugin});
214 | }
215 | return plugin
216 | }),
217 | ]
218 | }
219 |
220 | commit('commonUpdate', {
221 | options
222 | });
223 | ipcRenderer.send('changeWindowSize-rubick', {
224 | height: getWindowHeight(state.options),
225 | });
226 | },
227 | async downloadPlugin({commit}, payload) {
228 | const distUrl = await downloadZip(payload.downloadUrl, payload.name);
229 | const fileUrl = find(distUrl);
230 |
231 | // 复制文件
232 | const config = JSON.parse(fs.readFileSync(`${fileUrl}/plugin.json`, 'utf-8'));
233 | const pluginConfig = {
234 | ...config,
235 | id: uuidv4(),
236 | sourceFile: `${fileUrl}/${config.main}`,
237 | type: 'prod',
238 | icon: payload.logo,
239 | subType: (() => {
240 | if (config.main) {
241 | return ''
242 | }
243 | return 'template';
244 | })()
245 | };
246 | commit('commonUpdate', {
247 | devPlugins: [pluginConfig, ...state.devPlugins],
248 | });
249 | },
250 | openPlugin({commit}, {cmd, plugin, feature, router, payload}) {
251 | if (plugin.type === 'app') {
252 | execSync(plugin.action);
253 | commit('commonUpdate', {
254 | selected: null,
255 | showMain: false,
256 | options: [],
257 | searchValue: '',
258 | });
259 | ipcRenderer.send('changeWindowSize-rubick', {
260 | height: getWindowHeight([]),
261 | });
262 | return;
263 | }
264 | commit('commonUpdate', {
265 | selected: {
266 | key: 'plugin-container',
267 | name: cmd.label ? cmd.label : cmd,
268 | icon: 'image://' + path.join(plugin.sourceFile, `../${plugin.logo}`),
269 | },
270 | searchValue: '',
271 | showMain: true
272 | });
273 | ipcRenderer.send('changeWindowSize-rubick', {
274 | height: getWindowHeight(),
275 | });
276 | if (plugin.type === 'system') {
277 | systemMethod[plugin.tag][feature.code]()
278 | commit('commonUpdate', {
279 | selected: null,
280 | showMain: false,
281 | options: [],
282 | });
283 | ipcRenderer.send('changeWindowSize-rubick', {
284 | height: getWindowHeight([]),
285 | });
286 | router.push({
287 | path: '/home',
288 | });
289 | return;
290 | }
291 | commit('commonUpdate', {
292 | pluginInfo: {
293 | cmd,
294 | ...plugin,
295 | detail: feature,
296 | payload,
297 | }
298 | });
299 |
300 | router.push({
301 | path: '/plugin',
302 | query: {
303 | ...plugin,
304 | _modify: Date.now(),
305 | detail: JSON.stringify(feature)
306 | },
307 | })
308 | }
309 | }
310 |
311 | export default {
312 | namespaced: true,
313 | state,
314 | mutations,
315 | actions
316 | }
317 |
--------------------------------------------------------------------------------
/src/renderer/pages/search/subpages/settings.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 偏好设置
7 |
8 |
9 | 超级面板
10 |
11 |
12 | 全局快捷键
13 |
14 |
15 |
16 |
17 |
18 |
快捷键
19 |
20 |
显示/隐藏快捷键
21 |
changeShortCut(e, 'showAndHidden')">{{ config.perf.shortCut.showAndHidden }}
22 |
23 |
24 |
插件分离快捷键
25 |
changeShortCut(e, 'separate')">{{ config.perf.shortCut.separate }}
26 |
27 |
28 |
39 |
46 |
47 |
48 |
49 |
弹出面板
50 |
51 | 长按鼠标右键
52 |
53 |
54 |
55 |
长按以下设置的毫秒响应
56 |
57 |
58 |

59 |
60 |
61 |
62 |
63 | 按下快捷键,自动搜索对应关键字,当关键字结果完全匹配,且结果唯一时,会直接指向该功能。
64 | 示例
65 |
66 |
67 |
68 |
71 | {{ item.title }}
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
快捷键
80 |
81 |
82 | 先按功能键(Ctrl、Shift、Alt、Option、Command),再按其他普通键。或按 F1-F12 单键
83 |
84 | changeGlobalKey(e, index)"
89 | >
90 | {{ item.key }}
91 |
92 |
93 |
94 |
95 |
96 |
功能关键字
97 |
changeGlobalValue(index, e.target.value)"
103 | />
104 |
105 |
106 |
+ 新增全局快捷功能
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
210 |
211 |
291 |
--------------------------------------------------------------------------------
/src/renderer/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 | {{ selected.name }}
13 |
14 |
15 |
search({value: e.target.value})"
20 | :value="searchValue"
21 | :maxLength="selected && selected.key !== 'plugin-container' ? 0 : 1000"
22 | @keydown.down="(e) => changeCurrent(1)"
23 | @keydown.up="() => changeCurrent(-1)"
24 | @keypress.enter="(e) => targetSearch({value: e.target.value, type: 'enter'})"
25 | @keypress.space="(e) => targetSearch({value: e.target.value, type: 'space'})"
26 | >
27 |
34 |
35 |
36 |
37 |
38 | item.click($router)"
40 | :class="currentSelect === index ? 'active op-item' : 'op-item'"
41 | slot="renderItem"
42 | slot-scope="item, index"
43 | >
44 |
47 |
48 |
53 |
54 | 开发者
55 | 系统
56 |
57 |
58 |
59 |
60 |
78 |
79 |
80 |
81 |
279 |
391 |
--------------------------------------------------------------------------------
/static/plugins/vue-router.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * vue-router v3.5.1
3 | * (c) 2021 Evan You
4 | * @license MIT
5 | */
6 | var t,e;t=this,e=function(){"use strict";function t(t,e){for(var r in e)t[r]=e[r];return t}var e=/[!'()*]/g,r=function(t){return"%"+t.charCodeAt(0).toString(16)},n=/%2C/g,o=function(t){return encodeURIComponent(t).replace(e,r).replace(n,",")};function i(t){try{return decodeURIComponent(t)}catch(t){}return t}var a=function(t){return null==t||"object"==typeof t?t:String(t)};function s(t){var e={};return(t=t.trim().replace(/^(\?|#|&)/,""))?(t.split("&").forEach(function(t){var r=t.replace(/\+/g," ").split("="),n=i(r.shift()),o=r.length>0?i(r.join("=")):null;void 0===e[n]?e[n]=o:Array.isArray(e[n])?e[n].push(o):e[n]=[e[n],o]}),e):e}function u(t){var e=t?Object.keys(t).map(function(e){var r=t[e];if(void 0===r)return"";if(null===r)return o(e);if(Array.isArray(r)){var n=[];return r.forEach(function(t){void 0!==t&&(null===t?n.push(o(e)):n.push(o(e)+"="+o(t)))}),n.join("&")}return o(e)+"="+o(r)}).filter(function(t){return t.length>0}).join("&"):null;return e?"?"+e:""}var c=/\/?$/;function p(t,e,r,n){var o=n&&n.options.stringifyQuery,i=e.query||{};try{i=f(i)}catch(t){}var a={name:e.name||t&&t.name,meta:t&&t.meta||{},path:e.path||"/",hash:e.hash||"",query:i,params:e.params||{},fullPath:d(e,o),matched:t?l(t):[]};return r&&(a.redirectedFrom=d(r,o)),Object.freeze(a)}function f(t){if(Array.isArray(t))return t.map(f);if(t&&"object"==typeof t){var e={};for(var r in t)e[r]=f(t[r]);return e}return t}var h=p(null,{path:"/"});function l(t){for(var e=[];t;)e.unshift(t),t=t.parent;return e}function d(t,e){var r=t.path,n=t.query;void 0===n&&(n={});var o=t.hash;return void 0===o&&(o=""),(r||"/")+(e||u)(n)+o}function v(t,e,r){return e===h?t===e:!!e&&(t.path&&e.path?t.path.replace(c,"")===e.path.replace(c,"")&&(r||t.hash===e.hash&&y(t.query,e.query)):!(!t.name||!e.name)&&t.name===e.name&&(r||t.hash===e.hash&&y(t.query,e.query)&&y(t.params,e.params)))}function y(t,e){if(void 0===t&&(t={}),void 0===e&&(e={}),!t||!e)return t===e;var r=Object.keys(t).sort(),n=Object.keys(e).sort();return r.length===n.length&&r.every(function(r,o){var i=t[r];if(n[o]!==r)return!1;var a=e[r];return null==i||null==a?i===a:"object"==typeof i&&"object"==typeof a?y(i,a):String(i)===String(a)})}function m(t){for(var e=0;e=0&&(e=t.slice(n),t=t.slice(0,n));var o=t.indexOf("?");return o>=0&&(r=t.slice(o+1),t=t.slice(0,o)),{path:t,query:r,hash:e}}(i.path||""),h=r&&r.path||"/",l=f.path?b(f.path,h,n||i.append):h,d=function(t,e,r){void 0===e&&(e={});var n,o=r||s;try{n=o(t||"")}catch(t){n={}}for(var i in e){var u=e[i];n[i]=Array.isArray(u)?u.map(a):a(u)}return n}(f.query,i.query,o&&o.options.parseQuery),v=i.hash||f.hash;return v&&"#"!==v.charAt(0)&&(v="#"+v),{_normalized:!0,path:l,query:d,hash:v}}var H,N=[String,Object],F=[String,Array],z=function(){},D={name:"RouterLink",props:{to:{type:N,required:!0},tag:{type:String,default:"a"},custom:Boolean,exact:Boolean,exactPath:Boolean,append:Boolean,replace:Boolean,activeClass:String,exactActiveClass:String,ariaCurrentValue:{type:String,default:"page"},event:{type:F,default:"click"}},render:function(e){var r=this,n=this.$router,o=this.$route,i=n.resolve(this.to,o,this.append),a=i.location,s=i.route,u=i.href,f={},h=n.options.linkActiveClass,l=n.options.linkExactActiveClass,d=null==h?"router-link-active":h,y=null==l?"router-link-exact-active":l,m=null==this.activeClass?d:this.activeClass,g=null==this.exactActiveClass?y:this.exactActiveClass,w=s.redirectedFrom?p(null,V(s.redirectedFrom),null,n):s;f[g]=v(o,w,this.exactPath),f[m]=this.exact||this.exactPath?f[g]:function(t,e){return 0===t.path.replace(c,"/").indexOf(e.path.replace(c,"/"))&&(!e.hash||t.hash===e.hash)&&function(t,e){for(var r in e)if(!(r in t))return!1;return!0}(t.query,e.query)}(o,w);var b=f[g]?this.ariaCurrentValue:null,x=function(t){K(t)&&(r.replace?n.replace(a,z):n.push(a,z))},R={click:K};Array.isArray(this.event)?this.event.forEach(function(t){R[t]=x}):R[this.event]=x;var k={class:f},E=!this.$scopedSlots.$hasNormal&&this.$scopedSlots.default&&this.$scopedSlots.default({href:u,route:s,navigate:x,isActive:f[m],isExactActive:f[g]});if(E){if(1===E.length)return E[0];if(E.length>1||!E.length)return 0===E.length?e():e("span",{},E)}if("a"===this.tag)k.on=R,k.attrs={href:u,"aria-current":b};else{var C=function t(e){if(e)for(var r,n=0;n-1&&(s.params[h]=r.params[h]);return s.path=M(p.path,s.params),u(p,s,a)}if(s.path){s.params={};for(var l=0;l=t.length?r():t[o]?e(t[o],function(){n(o+1)}):n(o+1)};n(0)}var gt={redirected:2,aborted:4,cancelled:8,duplicated:16};function wt(t,e){return xt(t,e,gt.redirected,'Redirected when going from "'+t.fullPath+'" to "'+function(t){if("string"==typeof t)return t;if("path"in t)return t.path;var e={};return Rt.forEach(function(r){r in t&&(e[r]=t[r])}),JSON.stringify(e,null,2)}(e)+'" via a navigation guard.')}function bt(t,e){return xt(t,e,gt.cancelled,'Navigation cancelled from "'+t.fullPath+'" to "'+e.fullPath+'" with a new navigation.')}function xt(t,e,r,n){var o=new Error(n);return o._isRouter=!0,o.from=t,o.to=e,o.type=r,o}var Rt=["params","query","hash"];function kt(t){return Object.prototype.toString.call(t).indexOf("Error")>-1}function Et(t,e){return kt(t)&&t._isRouter&&(null==e||t.type===e)}function Ct(t){return function(e,r,n){var o=!1,i=0,a=null;At(t,function(t,e,r,s){if("function"==typeof t&&void 0===t.cid){o=!0,i++;var u,c=jt(function(e){var o;((o=e).__esModule||_t&&"Module"===o[Symbol.toStringTag])&&(e=e.default),t.resolved="function"==typeof e?e:H.extend(e),r.components[s]=e,--i<=0&&n()}),p=jt(function(t){var e="Failed to resolve async component "+s+": "+t;a||(a=kt(t)?t:new Error(e),n(a))});try{u=t(c,p)}catch(t){p(t)}if(u)if("function"==typeof u.then)u.then(c,p);else{var f=u.component;f&&"function"==typeof f.then&&f.then(c,p)}}}),o||n()}}function At(t,e){return Ot(t.map(function(t){return Object.keys(t.components).map(function(r){return e(t.components[r],t.instances[r],t,r)})}))}function Ot(t){return Array.prototype.concat.apply([],t)}var _t="function"==typeof Symbol&&"symbol"==typeof Symbol.toStringTag;function jt(t){var e=!1;return function(){for(var r=[],n=arguments.length;n--;)r[n]=arguments[n];if(!e)return e=!0,t.apply(this,r)}}var Tt=function(t,e){this.router=t,this.base=function(t){if(!t)if(J){var e=document.querySelector("base");t=(t=e&&e.getAttribute("href")||"/").replace(/^https?:\/\/[^\/]+/,"")}else t="/";return"/"!==t.charAt(0)&&(t="/"+t),t.replace(/\/$/,"")}(e),this.current=h,this.pending=null,this.ready=!1,this.readyCbs=[],this.readyErrorCbs=[],this.errorCbs=[],this.listeners=[]};function St(t,e,r,n){var o=At(t,function(t,n,o,i){var a=function(t,e){return"function"!=typeof t&&(t=H.extend(t)),t.options[e]}(t,e);if(a)return Array.isArray(a)?a.map(function(t){return r(t,n,o,i)}):r(a,n,o,i)});return Ot(n?o.reverse():o)}function Pt(t,e){if(e)return function(){return t.apply(e,arguments)}}Tt.prototype.listen=function(t){this.cb=t},Tt.prototype.onReady=function(t,e){this.ready?t():(this.readyCbs.push(t),e&&this.readyErrorCbs.push(e))},Tt.prototype.onError=function(t){this.errorCbs.push(t)},Tt.prototype.transitionTo=function(t,e,r){var n,o=this;try{n=this.router.match(t,this.current)}catch(t){throw this.errorCbs.forEach(function(e){e(t)}),t}var i=this.current;this.confirmTransition(n,function(){o.updateRoute(n),e&&e(n),o.ensureURL(),o.router.afterHooks.forEach(function(t){t&&t(n,i)}),o.ready||(o.ready=!0,o.readyCbs.forEach(function(t){t(n)}))},function(t){r&&r(t),t&&!o.ready&&(Et(t,gt.redirected)&&i===h||(o.ready=!0,o.readyErrorCbs.forEach(function(e){e(t)})))})},Tt.prototype.confirmTransition=function(t,e,r){var n=this,o=this.current;this.pending=t;var i,a,s=function(t){!Et(t)&&kt(t)&&(n.errorCbs.length?n.errorCbs.forEach(function(e){e(t)}):console.error(t)),r&&r(t)},u=t.matched.length-1,c=o.matched.length-1;if(v(t,o)&&u===c&&t.matched[u]===o.matched[c])return this.ensureURL(),s(((a=xt(i=o,t,gt.duplicated,'Avoided redundant navigation to current location: "'+i.fullPath+'".')).name="NavigationDuplicated",a));var p=function(t,e){var r,n=Math.max(t.length,e.length);for(r=0;r0)){var e=this.router,r=e.options.scrollBehavior,n=dt&&r;n&&this.listeners.push(ot());var o=function(){var r=t.current,o=$t(t.base);t.current===h&&o===t._startLocation||t.transitionTo(o,function(t){n&&it(e,t,r,!0)})};window.addEventListener("popstate",o),this.listeners.push(function(){window.removeEventListener("popstate",o)})}},e.prototype.go=function(t){window.history.go(t)},e.prototype.push=function(t,e,r){var n=this,o=this.current;this.transitionTo(t,function(t){vt(x(n.base+t.fullPath)),it(n.router,t,o,!1),e&&e(t)},r)},e.prototype.replace=function(t,e,r){var n=this,o=this.current;this.transitionTo(t,function(t){yt(x(n.base+t.fullPath)),it(n.router,t,o,!1),e&&e(t)},r)},e.prototype.ensureURL=function(t){if($t(this.base)!==this.current.fullPath){var e=x(this.base+this.current.fullPath);t?vt(e):yt(e)}},e.prototype.getCurrentLocation=function(){return $t(this.base)},e}(Tt);function $t(t){var e=window.location.pathname;return t&&0===e.toLowerCase().indexOf(t.toLowerCase())&&(e=e.slice(t.length)),(e||"/")+window.location.search+window.location.hash}var qt=function(t){function e(e,r,n){t.call(this,e,r),n&&function(t){var e=$t(t);if(!/^\/#/.test(e))return window.location.replace(x(t+"/#"+e)),!0}(this.base)||Ut()}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e.prototype.setupListeners=function(){var t=this;if(!(this.listeners.length>0)){var e=this.router.options.scrollBehavior,r=dt&&e;r&&this.listeners.push(ot());var n=function(){var e=t.current;Ut()&&t.transitionTo(Bt(),function(n){r&&it(t.router,n,e,!0),dt||Vt(n.fullPath)})},o=dt?"popstate":"hashchange";window.addEventListener(o,n),this.listeners.push(function(){window.removeEventListener(o,n)})}},e.prototype.push=function(t,e,r){var n=this,o=this.current;this.transitionTo(t,function(t){Mt(t.fullPath),it(n.router,t,o,!1),e&&e(t)},r)},e.prototype.replace=function(t,e,r){var n=this,o=this.current;this.transitionTo(t,function(t){Vt(t.fullPath),it(n.router,t,o,!1),e&&e(t)},r)},e.prototype.go=function(t){window.history.go(t)},e.prototype.ensureURL=function(t){var e=this.current.fullPath;Bt()!==e&&(t?Mt(e):Vt(e))},e.prototype.getCurrentLocation=function(){return Bt()},e}(Tt);function Ut(){var t=Bt();return"/"===t.charAt(0)||(Vt("/"+t),!1)}function Bt(){var t=window.location.href,e=t.indexOf("#");return e<0?"":t=t.slice(e+1)}function It(t){var e=window.location.href,r=e.indexOf("#");return(r>=0?e.slice(0,r):e)+"#"+t}function Mt(t){dt?vt(It(t)):window.location.hash=t}function Vt(t){dt?yt(It(t)):window.location.replace(It(t))}var Ht=function(t){function e(e,r){t.call(this,e,r),this.stack=[],this.index=-1}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e.prototype.push=function(t,e,r){var n=this;this.transitionTo(t,function(t){n.stack=n.stack.slice(0,n.index+1).concat(t),n.index++,e&&e(t)},r)},e.prototype.replace=function(t,e,r){var n=this;this.transitionTo(t,function(t){n.stack=n.stack.slice(0,n.index).concat(t),e&&e(t)},r)},e.prototype.go=function(t){var e=this,r=this.index+t;if(!(r<0||r>=this.stack.length)){var n=this.stack[r];this.confirmTransition(n,function(){var t=e.current;e.index=r,e.updateRoute(n),e.router.afterHooks.forEach(function(e){e&&e(n,t)})},function(t){Et(t,gt.duplicated)&&(e.index=r)})}},e.prototype.getCurrentLocation=function(){var t=this.stack[this.stack.length-1];return t?t.fullPath:"/"},e.prototype.ensureURL=function(){},e}(Tt),Nt=function(t){void 0===t&&(t={}),this.app=null,this.apps=[],this.options=t,this.beforeHooks=[],this.resolveHooks=[],this.afterHooks=[],this.matcher=Y(t.routes||[],this);var e=t.mode||"hash";switch(this.fallback="history"===e&&!dt&&!1!==t.fallback,this.fallback&&(e="hash"),J||(e="abstract"),this.mode=e,e){case"history":this.history=new Lt(this,t.base);break;case"hash":this.history=new qt(this,t.base,this.fallback);break;case"abstract":this.history=new Ht(this,t.base)}},Ft={currentRoute:{configurable:!0}};function zt(t,e){return t.push(e),function(){var r=t.indexOf(e);r>-1&&t.splice(r,1)}}return Nt.prototype.match=function(t,e,r){return this.matcher.match(t,e,r)},Ft.currentRoute.get=function(){return this.history&&this.history.current},Nt.prototype.init=function(t){var e=this;if(this.apps.push(t),t.$once("hook:destroyed",function(){var r=e.apps.indexOf(t);r>-1&&e.apps.splice(r,1),e.app===t&&(e.app=e.apps[0]||null),e.app||e.history.teardown()}),!this.app){this.app=t;var r=this.history;if(r instanceof Lt||r instanceof qt){var n=function(t){r.setupListeners(),function(t){var n=r.current,o=e.options.scrollBehavior;dt&&o&&"fullPath"in t&&it(e,t,n,!1)}(t)};r.transitionTo(r.getCurrentLocation(),n,n)}r.listen(function(t){e.apps.forEach(function(e){e._route=t})})}},Nt.prototype.beforeEach=function(t){return zt(this.beforeHooks,t)},Nt.prototype.beforeResolve=function(t){return zt(this.resolveHooks,t)},Nt.prototype.afterEach=function(t){return zt(this.afterHooks,t)},Nt.prototype.onReady=function(t,e){this.history.onReady(t,e)},Nt.prototype.onError=function(t){this.history.onError(t)},Nt.prototype.push=function(t,e,r){var n=this;if(!e&&!r&&"undefined"!=typeof Promise)return new Promise(function(e,r){n.history.push(t,e,r)});this.history.push(t,e,r)},Nt.prototype.replace=function(t,e,r){var n=this;if(!e&&!r&&"undefined"!=typeof Promise)return new Promise(function(e,r){n.history.replace(t,e,r)});this.history.replace(t,e,r)},Nt.prototype.go=function(t){this.history.go(t)},Nt.prototype.back=function(){this.go(-1)},Nt.prototype.forward=function(){this.go(1)},Nt.prototype.getMatchedComponents=function(t){var e=t?t.matched?t:this.resolve(t).route:this.currentRoute;return e?[].concat.apply([],e.matched.map(function(t){return Object.keys(t.components).map(function(e){return t.components[e]})})):[]},Nt.prototype.resolve=function(t,e,r){var n=V(t,e=e||this.history.current,r,this),o=this.match(n,e),i=o.redirectedFrom||o.fullPath;return{location:n,route:o,href:function(t,e,r){var n="hash"===r?"#"+e:e;return t?x(t+"/"+n):n}(this.history.base,i,this.mode),normalizedTo:n,resolved:o}},Nt.prototype.getRoutes=function(){return this.matcher.getRoutes()},Nt.prototype.addRoute=function(t,e){this.matcher.addRoute(t,e),this.history.current!==h&&this.history.transitionTo(this.history.getCurrentLocation())},Nt.prototype.addRoutes=function(t){this.matcher.addRoutes(t),this.history.current!==h&&this.history.transitionTo(this.history.getCurrentLocation())},Object.defineProperties(Nt.prototype,Ft),Nt.install=function t(e){if(!t.installed||H!==e){t.installed=!0,H=e;var r=function(t){return void 0!==t},n=function(t,e){var n=t.$options._parentVnode;r(n)&&r(n=n.data)&&r(n=n.registerRouteInstance)&&n(t,e)};e.mixin({beforeCreate:function(){r(this.$options.router)?(this._routerRoot=this,this._router=this.$options.router,this._router.init(this),e.util.defineReactive(this,"_route",this._router.history.current)):this._routerRoot=this.$parent&&this.$parent._routerRoot||this,n(this,this)},destroyed:function(){n(this)}}),Object.defineProperty(e.prototype,"$router",{get:function(){return this._routerRoot._router}}),Object.defineProperty(e.prototype,"$route",{get:function(){return this._routerRoot._route}}),e.component("RouterView",g),e.component("RouterLink",D);var o=e.config.optionMergeStrategies;o.beforeRouteEnter=o.beforeRouteLeave=o.beforeRouteUpdate=o.created}},Nt.version="3.5.1",Nt.isNavigationFailure=Et,Nt.NavigationFailureType=gt,Nt.START_LOCATION=h,J&&window.Vue&&window.Vue.use(Nt),Nt},"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).VueRouter=e();
7 |
--------------------------------------------------------------------------------