Compare commits

...

55 Commits

Author SHA1 Message Date
muwoo
3b4ca0e289 Update LESS loader options in vue.config.js 2025-11-27 18:15:37 +08:00
muwoo
b75188fdb8 Update Yarn command to install Vue CLI 2025-11-27 18:06:42 +08:00
muwoo
bf3ae7c9ba Update main.yml 2025-11-27 17:53:32 +08:00
muwoo
b7b16e2f3e Update main.yml 2025-11-27 17:47:04 +08:00
muwoo
3fec3665b6 Update Vue CLI version in GitHub Actions workflow 2025-11-27 17:42:16 +08:00
muwoo
b7587a454f Merge pull request #477 from lanxiuyun/support-copy-file
windows 支持复制文件到剪贴板
2025-11-27 17:34:48 +08:00
lanxiuyun
f5f3f030ce 添加注释 2025-11-27 17:32:14 +08:00
lanxiuyun
0bddb33fde 优化可读性 2025-11-27 16:29:53 +08:00
lanxiuyun
9005d070aa windows复制文件到剪切板 2025-11-27 16:01:58 +08:00
lanxiuyun
9adfa84cab 简化复制代码 2025-11-27 15:32:42 +08:00
lanxiuyun
15c160e45d 支持复制文件 2025-11-27 14:47:47 +08:00
muwoo
6bf613042a Update Vue CLI version in workflow 2025-11-20 21:48:34 +08:00
muwoo
04fe2e03a6 Pin Vue CLI version to 5.0.8 in workflow 2025-11-20 21:43:41 +08:00
muwoo
6c0d34fc4f Update package.json 2025-11-19 20:21:55 +08:00
muwoo
3fa9bb0384 Merge pull request #472 from 25juan/feature/volta
fix:修复当使用 volta 管理 node 版本的时候安装插件失败
2025-11-19 20:21:12 +08:00
muwoo
791115901a Merge pull request #473 from lanxiuyun/fix-detach
Fix: detach bug
2025-11-19 20:20:36 +08:00
lanxiuyun
a879ed6555 fix-detach 2025-11-11 13:56:52 +08:00
sgellar
28b58e7976 fix:修复当使用 volta 管理 node 版本的时候安装插件失败 2025-11-11 12:20:57 +08:00
muwoo
dc54b25f84 Merge pull request #464 from lanxiuyun/fix-feature-npm-i
fix: feature中的npm i
2025-09-16 10:21:04 +08:00
lanxiuyun
fc51a383bf 更新 less-loader 版本 2025-09-13 15:41:28 +08:00
muwoo
a546bc0d59 Update package.json 2025-07-25 18:40:09 +08:00
muwoo
c732e448c3 Merge pull request #449 from siriusol/master
fix #448
2025-07-25 18:39:43 +08:00
Ther
fbc7da0606 fix #448 2025-07-25 18:24:52 +08:00
muwoo
6315ec12ae Update main.yml 2025-07-24 14:13:59 +08:00
muwoo
3ba8250d7c Update package.json 2025-07-24 14:12:18 +08:00
muwoo
706aa84374 Merge pull request #432 from magicedy/fix-key-conflicts
fix: resolve key conflicts between local-start-app and plugin-history
2025-07-24 14:11:24 +08:00
muwoo
f70bf3983e Merge pull request #444 from lanxiuyun/ctrl+ctrl
Double click the modifer to pop up window
2025-07-24 14:10:09 +08:00
muwoo
47359308fc Update package.json 2025-07-17 20:56:32 +08:00
muwoo
3f03e5578e Merge pull request #438 from siriusol/master
fix issues #437
2025-07-17 20:56:14 +08:00
lanxiuyun
7cabbe26f5 删除提示重启文本 2025-06-12 15:09:38 +08:00
lanxiuyun
5c048c6341 优化代码可读性 2025-06-12 15:08:37 +08:00
lanxiuyun
e90a30c8a4 优化 uIOhook 逻辑 2025-06-12 15:04:10 +08:00
lanxiuyun
599538db76 Update package.json 2025-06-10 14:33:16 +08:00
lanxiuyun
e9c41b6bdb Update index.vue 2025-06-10 14:23:51 +08:00
lanxiuyun
1e7a8209b7 支持双击快捷键 2025-06-10 14:14:37 +08:00
lanxiuyun
481cd44ab3 Update registerHotKey.ts 2025-06-10 10:53:13 +08:00
lanxiuyun
d41caa742b Update index.vue 2025-06-10 10:39:44 +08:00
lanxiuyun
69218a728b Update index.vue 2025-06-10 10:25:25 +08:00
Ther
73cef1512c fix #437 2025-05-24 19:42:18 +08:00
magicedy
986ad42ed7 fix: resolve key conflicts between local-start-app and plugin-history 2025-04-27 12:14:32 +08:00
muwoo
42aec3403a Merge pull request #415 from yangyangdaji/readme
readme: some fix
2025-03-04 16:45:10 +08:00
yangyangdaji
8c15dba68d readme (en) some fix
1. fix blue line between badges in github
2. change badges tooltip

Signed-off-by: yangyangdaji <1504305527@qq.com>
2025-02-27 11:55:26 +08:00
yangyangdaji
37647de7a8 readme (zh-cn) 一些修复
1. 修复一些徽章在 github 浏览时出现的蓝色蓝点
2. 修复徽章一些徽章的提示名称

Signed-off-by: yangyangdaji <1504305527@qq.com>
2025-02-27 11:51:04 +08:00
muwoo
1b2c9b3577 Merge pull request #411 from PineSongCN/master
🐛 修复 #410
2025-01-21 16:53:24 +08:00
muwoo
7cb832829d Update README.md 2025-01-21 16:52:19 +08:00
徐松
02da06c450 Update package.json version @4.3.2 2025-01-21 16:46:41 +08:00
徐松
e613fc19d4 🐛 修复 修改database url已过期源为https://gitee.com/monkeyWang/rubickdatabase/raw/master 2025-01-21 16:45:05 +08:00
徐松
c7807aeb3f 🐛 修复 https://github.com/rubickCenter/rubick/issues/410\#issuecomment-2601278762 2025-01-20 15:46:19 +08:00
muwoo
9813573679 Update package.json 2024-12-26 18:40:45 +08:00
muwoo
61ec18f0dc fix: #404 2024-12-26 18:40:27 +08:00
wangwei1240
5ca1ba1271 feat: 为了更好的安装体验,切换到依赖本地 node 环境 2024-12-11 20:06:21 +08:00
wangwei1240
8c3186afef Merge remote-tracking branch 'origin/master' 2024-12-11 20:05:20 +08:00
wangwei1240
de0e9ed8f2 fix: 更新版本 2024-12-11 20:05:00 +08:00
muwoo
ef85084730 Update package.json 2024-10-05 18:41:32 +08:00
muwoo
b4b7c81b60 Update index.ts 2024-10-05 18:41:16 +08:00
19 changed files with 419 additions and 127 deletions

View File

@@ -23,7 +23,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-2019, ubuntu-latest]
os: [macos-latest, windows-2022, ubuntu-latest]
# create steps
steps:
@@ -52,7 +52,7 @@ jobs:
run: |
yarn
yarn global add xvfb-maybe
yarn global add @vue/cli
yarn global add @vue/cli@4.5.0 --frozen-lockfile --ignore-engines
- name: Build feature
run: |
cd ./feature

View File

@@ -1,36 +1,22 @@
English | [简体中文](./README.zh-CN.md)
<div align= "center">
<img align="center" width=200 src="./public/logo.png" />
</div>
<div align= "center">
<h1>Rubick</h1>
<img alt="release" src="https://img.shields.io/github/downloads/rubickCenter/rubick/total" />
<a href="https://github.com/rubickCenter/rubick/releases">
<img alt="release" src="https://img.shields.io/github/package-json/v/rubickCenter/rubick" />
</a>
<a href="https://github.com/rubickCenter/rubick/actions">
<img alt=building src=https://img.shields.io/github/actions/workflow/status/rubickCenter/rubick/main.yml>
</a>
<a href="https://github.com/rubickCenter/rubick/blob/master/LICENSE">
<img alt="npm" src="https://img.shields.io/github/license/rubickCenter/rubick" />
</a>
<a href="https://github.com/rubickCenter/rubick/stargazers">
<img alt="star" src="https://img.shields.io/github/stars/rubickCenter/rubick?style=social">
</a>
<a href="https://gitee.com/monkeyWang/rubick">
<img alt="码云" src="https://img.shields.io/badge/Gitee--yellow.svg?style=social&logo=data:image/svg+xml;base64,PHN2ZyB0PSIxNTc0ODM3MTM4ODM3IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjE3NzAiICAgICB3aWR0aD0iMTYiIGhlaWdodD0iMTYiPiAgICA8cGF0aCBkPSJNODkxIDQyOC44SDQ2NS44Yy0yMC40IDAtMzcgMTYuNS0zNyAzN3Y5Mi40YzAgMjAuNCAxNi41IDM3IDM3IDM3aDI1OC45YzIwLjQgMCAzNyAxNi42IDM3IDM3djE4LjRjMCA2MS4zLTQ5LjcgMTEwLjktMTEwLjkgMTEwLjlIMjk5LjRjLTIwLjQgMC0zNy0xNi42LTM3LTM3VjM3My4yYzAtNjEuMyA0OS43LTExMC45IDExMC45LTExMC45aDUxNy42YzIwLjQgMCAzNy0xNi41IDM3LTM3bDAuMS05Mi4zYzAtMjAuNC0xNi41LTM3LTM3LTM3SDM3My4zQzIyMC4yIDk2IDk2IDIyMC4yIDk2IDM3My4zVjg5MWMwIDIwLjQgMTYuNiAzNyAzNyAzN2g1NDUuNEM4MTYuMiA5MjggOTI4IDgxNi4zIDkyOCA2NzguNFY0NjUuOGMwLTIwLjQtMTYuNi0zNy0zNy0zN3oiICAgICAgICAgIGZpbGw9IiNkODFlMDYiIHAtaWQ9IjE3NzEiPjwvcGF0aD48L3N2Zz4="/>
</a>
<div align="center">
<h1>Rubick</h1>
<img alt="downloads" src="https://img.shields.io/github/downloads/rubickCenter/rubick/total" />
<a href="https://github.com/rubickCenter/rubick/releases"><img alt="latest release" src="https://img.shields.io/github/package-json/v/rubickCenter/rubick" /></a>
<a href="https://github.com/rubickCenter/rubick/actions"><img alt="github action building" src="https://img.shields.io/github/actions/workflow/status/rubickCenter/rubick/main.yml" /></a>
<a href="https://github.com/rubickCenter/rubick/blob/master/LICENSE"><img alt="license" src="https://img.shields.io/github/license/rubickCenter/rubick" /></a>
<a href="https://github.com/rubickCenter/rubick/stargazers"><img alt="github stars" src="https://img.shields.io/github/stars/rubickCenter/rubick?style=social" /></a>
<a href="https://gitee.com/monkeyWang/rubick"><img alt="gitee mirror" src="https://img.shields.io/badge/Gitee--yellow.svg?style=social&logo=data:image/svg+xml;base64,PHN2ZyB0PSIxNTc0ODM3MTM4ODM3IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjE3NzAiICAgICB3aWR0aD0iMTYiIGhlaWdodD0iMTYiPiAgICA8cGF0aCBkPSJNODkxIDQyOC44SDQ2NS44Yy0yMC40IDAtMzcgMTYuNS0zNyAzN3Y5Mi40YzAgMjAuNCAxNi41IDM3IDM3IDM3aDI1OC45YzIwLjQgMCAzNyAxNi42IDM3IDM3djE4LjRjMCA2MS4zLTQ5LjcgMTEwLjktMTEwLjkgMTEwLjlIMjk5LjRjLTIwLjQgMC0zNy0xNi42LTM3LTM3VjM3My4yYzAtNjEuMyA0OS43LTExMC45IDExMC45LTExMC45aDUxNy42YzIwLjQgMCAzNy0xNi41IDM3LTM3bDAuMS05Mi4zYzAtMjAuNC0xNi41LTM3LTM3LTM3SDM3My4zQzIyMC4yIDk2IDk2IDIyMC4yIDk2IDM3My4zVjg5MWMwIDIwLjQgMTYuNiAzNyAzNyAzN2g1NDUuNEM4MTYuMiA5MjggOTI4IDgxNi4zIDkyOCA2NzguNFY0NjUuOGMwLTIwLjQtMTYuNi0zNy0zNy0zN3oiICAgICAgICAgIGZpbGw9IiNkODFlMDYiIHAtaWQ9IjE3NzEiPjwvcGF0aD48L3N2Zz4=" /></a>
</div>
<div align= "center">
<img align="center" src="https://picx.zhimg.com/80/v2-f8fe09ef125dac5fdcbef3fe00f92b21_720w.png" />
</div>
Open-source plugin-based desktop efficiency toolbox. The plugins are installed and uninstalled based on npm, which is very lightweight. The plugin data supports webdav multi-terminal synchronization, which is very secure. It supports internal network deployment and can be customized for further development, which is very flexible.
## Get Rubick
@@ -104,6 +90,13 @@ If the project is helpful to you, you can buy me a cup of coffee as a reward!
<img width="180" src="https://picx.zhimg.com/80/v2-3160247d6099053405e6cd2cb6afb5e5_720w.png">
</div>
## 友情链接
<a href="https://pro.kuaitu.cc/" target="_blank">
<img width="100" src="https://github.com/user-attachments/assets/6127488e-466b-4e71-98ab-00fb3c76553e" />
</a>
## Feedback
Those who are interested in this project or want to exchange and learn can scan the QR code and add the following WeChat, with the comment rubick, to help us grow better.

View File

@@ -5,25 +5,14 @@
<img align="center" width=200 src="./public/logo.png" />
</div>
<div align= "center">
<h1>Rubick</h1>
<img alt="release" src="https://img.shields.io/github/downloads/rubickCenter/rubick/total" />
<a href="https://github.com/rubickCenter/rubick/releases">
<img alt="release" src="https://img.shields.io/github/package-json/v/rubickCenter/rubick" />
</a>
<a href="https://github.com/rubickCenter/rubick/actions">
<img alt=building src=https://img.shields.io/github/actions/workflow/status/rubickCenter/rubick/main.yml>
</a>
<a href="https://github.com/rubickCenter/rubick/blob/master/LICENSE">
<img alt="npm" src="https://img.shields.io/github/license/rubickCenter/rubick" />
</a>
<a href="https://github.com/rubickCenter/rubick/stargazers">
<img alt="star" src="https://img.shields.io/github/stars/rubickCenter/rubick?style=social">
</a>
<a href="https://gitee.com/monkeyWang/rubick">
<img alt="码云" src="https://img.shields.io/badge/Gitee--yellow.svg?style=social&logo=data:image/svg+xml;base64,PHN2ZyB0PSIxNTc0ODM3MTM4ODM3IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjE3NzAiICAgICB3aWR0aD0iMTYiIGhlaWdodD0iMTYiPiAgICA8cGF0aCBkPSJNODkxIDQyOC44SDQ2NS44Yy0yMC40IDAtMzcgMTYuNS0zNyAzN3Y5Mi40YzAgMjAuNCAxNi41IDM3IDM3IDM3aDI1OC45YzIwLjQgMCAzNyAxNi42IDM3IDM3djE4LjRjMCA2MS4zLTQ5LjcgMTEwLjktMTEwLjkgMTEwLjlIMjk5LjRjLTIwLjQgMC0zNy0xNi42LTM3LTM3VjM3My4yYzAtNjEuMyA0OS43LTExMC45IDExMC45LTExMC45aDUxNy42YzIwLjQgMCAzNy0xNi41IDM3LTM3bDAuMS05Mi4zYzAtMjAuNC0xNi41LTM3LTM3LTM3SDM3My4zQzIyMC4yIDk2IDk2IDIyMC4yIDk2IDM3My4zVjg5MWMwIDIwLjQgMTYuNiAzNyAzNyAzN2g1NDUuNEM4MTYuMiA5MjggOTI4IDgxNi4zIDkyOCA2NzguNFY0NjUuOGMwLTIwLjQtMTYuNi0zNy0zNy0zN3oiICAgICAgICAgIGZpbGw9IiNkODFlMDYiIHAtaWQ9IjE3NzEiPjwvcGF0aD48L3N2Zz4="/>
</a>
<div align="center">
<h1>Rubick</h1>
<img alt="累计下载数" src="https://img.shields.io/github/downloads/rubickCenter/rubick/total" />
<a href="https://github.com/rubickCenter/rubick/releases"><img alt="最新发布版本" src="https://img.shields.io/github/package-json/v/rubickCenter/rubick" /></a>
<a href="https://github.com/rubickCenter/rubick/actions"><img alt="github action 构建" src="https://img.shields.io/github/actions/workflow/status/rubickCenter/rubick/main.yml" /></a>
<a href="https://github.com/rubickCenter/rubick/blob/master/LICENSE"><img alt="许可证" src="https://img.shields.io/github/license/rubickCenter/rubick" /></a>
<a href="https://github.com/rubickCenter/rubick/stargazers"><img alt="github 收藏数" src="https://img.shields.io/github/stars/rubickCenter/rubick?style=social" /></a>
<a href="https://gitee.com/monkeyWang/rubick"><img alt="gitee 镜像源" src="https://img.shields.io/badge/Gitee--yellow.svg?style=social&logo=data:image/svg+xml;base64,PHN2ZyB0PSIxNTc0ODM3MTM4ODM3IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjE3NzAiICAgICB3aWR0aD0iMTYiIGhlaWdodD0iMTYiPiAgICA8cGF0aCBkPSJNODkxIDQyOC44SDQ2NS44Yy0yMC40IDAtMzcgMTYuNS0zNyAzN3Y5Mi40YzAgMjAuNCAxNi41IDM3IDM3IDM3aDI1OC45YzIwLjQgMCAzNyAxNi42IDM3IDM3djE4LjRjMCA2MS4zLTQ5LjcgMTEwLjktMTEwLjkgMTEwLjlIMjk5LjRjLTIwLjQgMC0zNy0xNi42LTM3LTM3VjM3My4yYzAtNjEuMyA0OS43LTExMC45IDExMC45LTExMC45aDUxNy42YzIwLjQgMCAzNy0xNi41IDM3LTM3bDAuMS05Mi4zYzAtMjAuNC0xNi41LTM3LTM3LTM3SDM3My4zQzIyMC4yIDk2IDk2IDIyMC4yIDk2IDM3My4zVjg5MWMwIDIwLjQgMTYuNiAzNyAzNyAzN2g1NDUuNEM4MTYuMiA5MjggOTI4IDgxNi4zIDkyOCA2NzguNFY0NjUuOGMwLTIwLjQtMTYuNi0zNy0zNy0zN3oiICAgICAgICAgIGZpbGw9IiNkODFlMDYiIHAtaWQ9IjE3NzEiPjwvcGF0aD48L3N2Zz4=" /></a>
</div>
<div align= "center">

View File

@@ -42,7 +42,7 @@
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-vue": "^7.0.0",
"less": "^4.1.3",
"less-loader": "5.0.0",
"less-loader": "^6.2.0",
"prettier": "^2.2.1",
"typescript": "~4.1.5"
},

View File

@@ -14,7 +14,7 @@ try {
const instance = axios.create({
timeout: 4000,
baseURL:
baseURL || 'https://gitcode.net/rubickcenter/rubick-database/-/raw/master',
baseURL || 'https://gitee.com/monkeyWang/rubickdatabase/raw/master',
});
export default {

View File

@@ -274,12 +274,16 @@ const state = reactive({
custom: {},
});
// 添加lastKeyPressTime变量来跟踪按键时间
const lastKeyPressTime = ref(0);
const DOUBLE_CLICK_THRESHOLD = 300; // 双击时间阈值(毫秒)
const isWindows = window?.rubick?.isWindows();
const tipText = computed(() => {
const optionKeyName = isWindows ? 'Alt' : 'Option、Command';
return t('feature.settings.global.addShortcutKeyTips', {
optionKeyName: optionKeyName,
});
}) + `此外你也可以双击修饰键如Ctrl+Ctrl`;
});
const currentSelect = ref(['userInfo']);
@@ -314,33 +318,60 @@ watch(state, setConfig);
const changeShortCut = (e, key) => {
let compose = '';
// 添加是否包含功能键的判断
let incluFuncKeys = false;
const currentTime = Date.now();
const isDoubleClick = currentTime - lastKeyPressTime.value < DOUBLE_CLICK_THRESHOLD;
lastKeyPressTime.value = currentTime;
// 处理 F1-F12 功能键
if (e.keyCode >= 112 && e.keyCode <= 123) {
state.shortCut[key] = keycodes[e.keyCode].toUpperCase();
return;
}
// 处理双击功能键的情况
if (isDoubleClick) {
if (e.keyCode === 17) { // Ctrl
state.shortCut[key] = 'Ctrl+Ctrl';
return;
}
if (e.keyCode === 18) { // Alt
state.shortCut[key] = 'Option+Option';
return;
}
if (e.keyCode === 16) { // Shift
state.shortCut[key] = 'Shift+Shift';
return;
}
if (e.keyCode === 93) { // Command
state.shortCut[key] = 'Command+Command';
return;
}
}
// 处理功能键+普通键的组合
let hasModifierKey = false;
if (e.ctrlKey && e.keyCode !== 17) {
compose += '+Ctrl';
incluFuncKeys = true;
hasModifierKey = true;
}
if (e.shiftKey && e.keyCode !== 16) {
compose += '+Shift';
incluFuncKeys = true;
hasModifierKey = true;
}
if (e.altKey && e.keyCode !== 18) {
compose += '+Option';
incluFuncKeys = true;
hasModifierKey = true;
}
if (e.metaKey && e.keyCode !== 93) {
compose += '+Command';
incluFuncKeys = true;
hasModifierKey = true;
}
compose += '+' + keycodes[e.keyCode].toUpperCase();
compose = compose.substring(1);
if (
incluFuncKeys &&
e.keyCode !== 16 &&
e.keyCode !== 17 &&
e.keyCode !== 18 &&
e.keyCode !== 93
) {
// 只有当有修饰键时才添加普通键
if (hasModifierKey) {
compose += '+' + keycodes[e.keyCode].toUpperCase();
compose = compose.substring(1);
state.shortCut[key] = compose;
} else {
// 不做处理

View File

@@ -17,7 +17,7 @@
name="register"
>
<a-input
placeholder="https://registry.npm.taobao.org"
placeholder="https://registry.npmmirror.com"
v-model:value="formState.register"
/>
</a-form-item>
@@ -27,7 +27,7 @@
name="database"
>
<a-input
placeholder="https://gitcode.net/rubickcenter/rubick-database/-/raw/master"
placeholder="https://gitee.com/monkeyWang/rubickdatabase/raw/master"
v-model:value="formState.database"
/>
</a-form-item>
@@ -54,8 +54,8 @@ import { message } from 'ant-design-vue';
let _rev: any;
let defaultConfig = {
register: 'https://registry.npm.taobao.org',
database: 'https://gitcode.net/rubickcenter/rubick-database/-/raw/master',
register: 'https://registry.npmmirror.com',
database: 'https://gitee.com/monkeyWang/rubickdatabase/raw/master',
access_token: '',
};
@@ -81,7 +81,7 @@ const layout = {
const resetForm = () => {
formState.value = {
register: 'https://registry.npmmirror.com',
database: 'https://gitcode.net/rubickcenter/rubick-database/-/raw/master',
database: 'https://gitee.com/monkeyWang/rubickdatabase/raw/master',
access_token: '',
};
};

View File

@@ -7,7 +7,9 @@ module.exports = {
// 向预处理器 Loader 传递配置选项
less: {
// 配置less其他样式解析用法一致
javascriptEnabled: true, // 设置为true
lessOptions: {
javascriptEnabled: true, // 设置为true
},
},
},
},

View File

@@ -1,6 +1,6 @@
{
"name": "rubick",
"version": "4.2.8",
"version": "4.3.7",
"author": "muwoo <2424880409@qq.com>",
"private": true,
"scripts": {
@@ -39,6 +39,7 @@
"pouchdb-load": "^1.4.6",
"pouchdb-replication-stream": "^1.2.9",
"simple-plist": "0.2.1",
"uiohook-napi": "^1.5.4",
"vue": "^3.0.0",
"vue-router": "^4.0.0-0",
"vuex": "^4.0.0-0",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -4,6 +4,6 @@ import path from 'path';
const appPath = app.getPath('userData');
const PLUGIN_INSTALL_DIR = path.join(appPath, './rubick-plugins-new');
const PLUGIN_HISTORY = 'rubick-local-start-app';
const PLUGIN_HISTORY = 'rubick-plugin-history';
export { PLUGIN_INSTALL_DIR, PLUGIN_HISTORY };

View File

@@ -7,11 +7,10 @@ import path from 'path';
import got from 'got';
import fixPath from 'fix-path';
import spawn from 'cross-spawn';
import { ipcRenderer } from 'electron';
import axios from 'axios';
import npm from 'npm';
fixPath();
/**
@@ -37,12 +36,19 @@ class AdapterHandler {
fs.mkdirsSync(options.baseDir);
fs.writeFileSync(
`${options.baseDir}/package.json`,
'{"dependencies":{}}'
// '{"dependencies":{}}'
// fix 插件安装时node版本问题
JSON.stringify({
dependencies: {},
volta: {
node: '16.19.1',
},
})
);
}
this.baseDir = options.baseDir;
let register = options.registry || 'https://registry.npmmirror.com/';
let register = options.registry || 'https://registry.npmmirror.com';
try {
const dbdata = ipcRenderer.sendSync('msg-trigger', {
@@ -61,7 +67,7 @@ class AdapterHandler {
const packageJSON = JSON.parse(
fs.readFileSync(`${this.baseDir}/package.json`, 'utf-8')
);
const registryUrl = `${this.registry}${name}`;
const registryUrl = `https://registry.npmmirror.com/${name}`;
// 从npm源中获取依赖包的最新版本
try {
@@ -158,32 +164,43 @@ class AdapterHandler {
*/
private async execCommand(cmd: string, modules: string[]): Promise<string> {
return new Promise((resolve: any, reject: any) => {
const module =
let args: string[] = [cmd].concat(
cmd !== 'uninstall' && cmd !== 'link'
? modules.map((m) => `${m}@latest`)
: modules;
const config: any = {
prefix: this.baseDir,
save: true,
cache: path.join(this.baseDir, 'cache'),
};
: modules
);
if (cmd !== 'link') {
config.registry = this.registry;
args = args
.concat('--color=always')
.concat('--save')
.concat(`--registry=${this.registry}`);
}
npm.load(config, function (err) {
npm.commands[cmd](module, function (er, data) {
if (!err) {
console.log(data);
resolve({ code: -1, data });
} else {
reject({ code: -1, data: err });
}
});
npm.on('log', function (message) {
// log installation progress
console.log(message);
});
const npm = spawn('npm', args, {
cwd: this.baseDir,
});
console.log(args);
let output = '';
npm.stdout
.on('data', (data: string) => {
output += data; // 获取输出日志
})
.pipe(process.stdout);
npm.stderr
.on('data', (data: string) => {
output += data; // 获取报错日志
})
.pipe(process.stderr);
npm.on('close', (code: number) => {
if (!code) {
resolve({ code: 0, data: output }); // 如果没有报错就输出正常日志
} else {
reject({ code: code, data: output }); // 如果报错就输出报错日志
}
});
});
}

View File

@@ -82,7 +82,7 @@ export default () => {
};
const init = (plugin, window: BrowserWindow) => {
if (view === null || view === undefined) {
if (view === null || view === undefined || view.inDetach) {
createView(plugin, window);
// if (viewInstance.getView(plugin.name) && !commonConst.dev()) {
// view = viewInstance.getView(plugin.name).view;
@@ -176,14 +176,29 @@ export default () => {
const removeView = (window: BrowserWindow) => {
if (!view) return;
executeHooks('PluginOut', null);
// 先记住这次要移除的视图,防止后面异步代码里全局引用被换掉
const snapshotView = view;
setTimeout(() => {
window.removeBrowserView(view);
if (!view.inDetach) {
window.setBrowserView(null);
view.webContents?.destroy();
// 获取当前视图,判断是否已经换成了新视图
const currentView = window.getBrowserView?.();
window.removeBrowserView(snapshotView);
// 主窗口的插件视图仍然挂着旧实例时,需要还原主窗口 UI
if (!snapshotView.inDetach) {
// 如果窗口还挂着旧视图,说明还没换掉,需要把主窗口恢复到初始状态
if (currentView === snapshotView) {
window.setBrowserView(null);
if (view === snapshotView) {
window.webContents?.executeJavaScript(`window.initRubick()`);
view = undefined;
}
}
snapshotView.webContents?.destroy();
}
// 分离窗口只需释放全局引用,视图由分离窗口继续管理
else if (view === snapshotView) {
view = undefined;
}
window.webContents?.executeJavaScript(`window.initRubick()`);
view = undefined;
}, 0);
};

View File

@@ -27,6 +27,30 @@ import DBInstance from './db';
import getWinPosition from './getWinPosition';
import path from 'path';
import commonConst from '@/common/utils/commonConst';
import { copyFilesToWindowsClipboard } from './windowsClipboard';
/**
* sanitize input files 剪贴板文件合法性校验
* @param input
* @returns
*/
const sanitizeInputFiles = (input: unknown): string[] => {
const candidates = Array.isArray(input)
? input
: typeof input === 'string'
? [input]
: [];
return candidates
.map((filePath) => (typeof filePath === 'string' ? filePath.trim() : ''))
.filter((filePath) => {
if (!filePath) return false;
try {
return fs.existsSync(filePath);
} catch {
return false;
}
});
};
const runnerInstance = runner();
const detachInstance = detach();
@@ -230,13 +254,28 @@ class API extends DBInstance {
}
public copyFile({ data }) {
if (data.file && fs.existsSync(data.file)) {
clipboard.writeBuffer(
'NSFilenamesPboardType',
Buffer.from(plist.build([data.file]))
);
return true;
const targetFiles = sanitizeInputFiles(data?.file);
if (!targetFiles.length) {
return false;
}
if (process.platform === 'darwin') {
try {
clipboard.writeBuffer(
'NSFilenamesPboardType',
Buffer.from(plist.build(targetFiles))
);
return true;
} catch {
return false;
}
}
if (process.platform === 'win32') {
return copyFilesToWindowsClipboard(targetFiles);
}
return false;
}

View File

@@ -10,6 +10,7 @@ import {
import screenCapture from '@/core/screen-capture';
import localConfig from '@/main/common/initLocalConfig';
import winPosition from './getWinPosition';
import { uIOhook, UiohookKey } from 'uiohook-napi';
const registerHotKey = (mainWindow: BrowserWindow): void => {
// 设置开机启动
@@ -57,27 +58,43 @@ const registerHotKey = (mainWindow: BrowserWindow): void => {
}
};
// 显示主窗口
function mainWindowPopUp() {
const currentShow = mainWindow.isVisible() && mainWindow.isFocused();
if (currentShow) return mainWindow.hide();
const { x: wx, y: wy } = winPosition.getPosition();
mainWindow.setAlwaysOnTop(false);
mainWindow.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
mainWindow.focus();
mainWindow.setVisibleOnAllWorkspaces(false, {
visibleOnFullScreen: true,
});
mainWindow.setPosition(wx, wy);
mainWindow.show();
}
const init = async () => {
await setAutoLogin();
await setDarkMode();
await setTheme();
const config = await localConfig.getConfig();
globalShortcut.unregisterAll();
// 注册偏好快捷键
globalShortcut.register(config.perf.shortCut.showAndHidden, () => {
const currentShow = mainWindow.isVisible() && mainWindow.isFocused();
if (currentShow) return mainWindow.hide();
const { x: wx, y: wy } = winPosition.getPosition();
mainWindow.setAlwaysOnTop(false);
mainWindow.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
mainWindow.focus();
mainWindow.setVisibleOnAllWorkspaces(false, {
visibleOnFullScreen: true,
});
mainWindow.setPosition(wx, wy);
mainWindow.show();
});
// 注册偏好快捷键
// 处理显示/隐藏快捷键的注册
const doublePressShortcuts = ['Ctrl+Ctrl', 'Option+Option', 'Shift+Shift', 'Command+Command'];
const isDoublePressShortcut = doublePressShortcuts.includes(config.perf.shortCut.showAndHidden);
if (isDoublePressShortcut) {
// 双击快捷键(如 Ctrl+Ctrl详见 uIOhookRegister 函数实现
} else {
// 注册普通快捷键(如 Ctrl+Space、F8 等)
globalShortcut.register(config.perf.shortCut.showAndHidden, () => {
mainWindowPopUp();
});
}
// 截图快捷键
globalShortcut.register(config.perf.shortCut.capture, () => {
screenCapture(mainWindow, (data) => {
data &&
@@ -93,6 +110,17 @@ const registerHotKey = (mainWindow: BrowserWindow): void => {
// mainWindow.show();
});
// 添加局部快捷键监听
mainWindow.webContents.on('before-input-event', (event, input) => {
if (input.key.toLowerCase() === 'w'
&& (input.control || input.meta) && !input.alt && !input.shift) {
event.preventDefault();
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.hide();
}
}
});
// 注册自定义全局快捷键
config.global.forEach((sc) => {
if (!sc.key || !sc.value) return;
@@ -101,9 +129,48 @@ const registerHotKey = (mainWindow: BrowserWindow): void => {
});
});
};
uIOhookRegister(mainWindowPopUp);
init();
ipcMain.on('re-register', () => {
init();
});
};
export default registerHotKey;
function uIOhookRegister(callback: () => void) {
let lastModifierPress = Date.now();
uIOhook.on('keydown', async (uio_event) => {
const config = await localConfig.getConfig(); // 此处还有优化空间
if (
![
'Ctrl+Ctrl',
'Option+Option',
'Shift+Shift',
'Command+Command',
].includes(config.perf.shortCut.showAndHidden)
) {
return;
}
// 双击快捷键,如 Ctrl+Ctrl
const modifers = config.perf.shortCut.showAndHidden.split('+');
const showAndHiddenKeyStr = modifers.pop(); // Ctrl
const keyStr2uioKeyCode = {
Ctrl: UiohookKey.Ctrl,
Shift: UiohookKey.Shift,
Option: UiohookKey.Alt,
Command: UiohookKey.Comma,
};
if (uio_event.keycode === keyStr2uioKeyCode[showAndHiddenKeyStr]) {
const currentTime = Date.now();
if (currentTime - lastModifierPress < 300) {
callback(); // 调用 mainWindowPopUp
}
lastModifierPress = currentTime;
}
});
uIOhook.start();
}

View File

@@ -0,0 +1,133 @@
import { clipboard } from 'electron';
import path from 'path';
// 仅在 Windows 平台辅助操作剪贴板多文件格式。
type ClipboardExModule = typeof import('electron-clipboard-ex');
const DROPFILES_HEADER_SIZE = 20;
let clipboardExModule: ClipboardExModule | null = null;
/**
* Windows 平台专用:尝试加载第三方库 electron-clipboard-ex。
* 这个库能够调用系统底层接口写入“文件复制”数据,成功率更高。
* 其他系统无需加载它,因此这里做了“按需加载”的处理。
*/
const ensureClipboardEx = (): ClipboardExModule | null => {
if (process.platform !== 'win32') return null;
if (clipboardExModule) return clipboardExModule;
try {
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
clipboardExModule = require('electron-clipboard-ex');
} catch {
clipboardExModule = null;
}
return clipboardExModule;
};
/**
* 把一组文件路径变成 Windows 规定的文本格式。
* 要求:每个路径之间用单个空字符分隔,最后再额外放两个空字符,表示列表结束。
* Windows 资源管理器会按这个格式解析我们复制到剪贴板的文件。
*/
const buildWindowsFileListPayload = (files: string[]): Buffer =>
Buffer.from(`${files.join('\0')}\0\0`, 'utf16le');
/**
* 构造 CF_HDROP 专用的二进制数据。
* 这是 Windows 复制文件时的底层格式,前 20 字节是固定的结构头,
* 后面紧跟着具体的文件路径(由 buildWindowsFileListPayload 生成)。
* 只要把这个内容写入剪贴板,任何支持粘贴文件的程序都能理解。
*/
const buildWindowsFileDropBuffer = (files: string[]): Buffer => {
const payload = buildWindowsFileListPayload(files);
const header = Buffer.alloc(DROPFILES_HEADER_SIZE);
header.writeUInt32LE(DROPFILES_HEADER_SIZE, 0);
header.writeInt32LE(0, 4);
header.writeInt32LE(0, 8);
header.writeUInt32LE(0, 12);
header.writeUInt32LE(1, 16);
const result = Buffer.alloc(header.length + payload.length);
for (let i = 0; i < header.length; i += 1) {
result[i] = header[i];
}
for (let i = 0; i < payload.length; i += 1) {
result[header.length + i] = payload[i];
}
return result;
};
/**
* 复制/移动/创建快捷方式 等不同操作在 Windows 中对应不同的“意图”值。
* Preferred DropEffect 告诉系统:当前剪贴板数据应该以何种方式处理。
* 我们默认写入“copy”相当于普通的复制粘贴。
*/
const buildDropEffectBuffer = (effect: 'copy' | 'move' | 'link' = 'copy') => {
const effectMap = {
copy: 1,
move: 2,
link: 4,
} as const;
const buffer = Buffer.alloc(4);
buffer.writeUInt32LE(effectMap[effect], 0);
return buffer;
};
/**
* 直接使用 Electron 内置 API 写入多种剪贴板格式。
* 步骤:
* 1. 写入二进制的 CF_HDROP含头部与路径列表
* 2. 写入纯文本形式的 FileNameW备选格式
* 3. 写入 Preferred DropEffect告诉系统“这是复制”
* 全部成功后,读取一次 CF_HDROP 的长度,确认剪贴板里确实有内容。
*/
const writeWindowsBuffers = (files: string[]): boolean => {
try {
clipboard.writeBuffer('CF_HDROP', buildWindowsFileDropBuffer(files));
clipboard.writeBuffer('FileNameW', buildWindowsFileListPayload(files));
clipboard.writeBuffer('Preferred DropEffect', buildDropEffectBuffer('copy'));
return clipboard.readBuffer('CF_HDROP').length > 0;
} catch {
return false;
}
};
/**
* 如果项目中安装了 electron-clipboard-ex我们优先使用它。
* 理由:该库通过原生方式与系统交互,兼容性往往优于 Electron 的 JS 层写入。
* 调用成功后,必要时读回文件列表做一次数量校验,确保复制的文件数量正确。
*/
const writeWithClipboardEx = (files: string[]): boolean => {
const clipboardEx = ensureClipboardEx();
if (!clipboardEx) return false;
try {
clipboardEx.writeFilePaths(files);
if (typeof clipboardEx.readFilePaths === 'function') {
const result = clipboardEx.readFilePaths();
return Array.isArray(result) && result.length === files.length;
}
return true;
} catch {
return false;
}
};
/**
* 对外暴露的唯一入口。
* 1. 先把所有路径换成 Windows 可识别的标准形式path.normalize
* 2. 尝试使用 electron-clipboard-ex 写入,如果成功就结束。
* 3. 若第三方库不可用或失败,再退回 Electron 原生写入流程。
* 这一层屏蔽了所有细节,外部调用者只需传入字符串数组即可。
*/
export const copyFilesToWindowsClipboard = (files: string[]): boolean => {
const normalizedFiles = files
.map((filePath) => path.normalize(filePath))
.filter(Boolean);
if (!normalizedFiles.length) return false;
if (writeWithClipboardEx(normalizedFiles)) {
return true;
}
return writeWindowsBuffers(normalizedFiles);
};

View File

@@ -1,8 +1,13 @@
<template>
<div v-show="!currentPlugin.name && config.perf.common.history" class="options">
<div v-show="!currentPlugin.name" class="options">
<div
class="history-plugins"
v-if="!options.length || !(searchValue || !!clipboardFile.length)"
v-if="
!options.length &&
!searchValue &&
!clipboardFile.length &&
config.perf.common.history
"
>
<a-row>
<a-col

View File

@@ -46,7 +46,7 @@ const createPluginManager = (): any => {
const initLocalStartPlugin = () => {
const result = ipcRenderer.sendSync('msg-trigger', {
type: 'dbGet',
data: { id: PLUGIN_HISTORY },
data: { id: 'rubick-local-start-app' },
});
if (result && result.value) {
appList.value.push(...result.value);