Compare commits

...

39 Commits

Author SHA1 Message Date
muwoo
1e4757f70d Merge remote-tracking branch 'origin/master' 2022-02-18 11:43:45 +08:00
muwoo
6adf25dbee 🐛 修复 allDocs 问题,新增 shellShowItemInFolder API 2022-02-18 11:43:30 +08:00
木偶
7fbb12d04b Merge pull request #90 from renmu123/master
fix typo
2022-02-17 19:29:00 +08:00
renmu123
ded16b9580 fix typo 2022-02-17 16:07:07 +08:00
Starry North
d379c58082 Merge pull request #87 from gclm/dev-gclm-85
feat: 新增mac打包为dmg
feat: 给rubick 配置一个专门的应用目录
2022-02-12 13:55:40 +08:00
gclm
8a536374ea feat: 给rubick 配置一个专门的应用目录 2022-02-11 15:15:40 +08:00
gclm
bcfc968c9d feat: 新增mac打包为dmg 2022-02-11 14:41:47 +08:00
木偶
4bf3f3a602 Update README.md 2022-01-24 15:26:00 +08:00
muwoo
497de040cf suport linux 2022-01-24 14:57:13 +08:00
muwoo
a22a78fa0a suport linux 2022-01-24 14:42:49 +08:00
muwoo
d2894c66ba suport linux 2022-01-24 14:24:52 +08:00
muwoo
ff7473deb2 suport linux 2022-01-24 14:16:01 +08:00
muwoo
842a44a6d1 suport linux 2022-01-24 13:40:24 +08:00
muwoo
751c73b3a6 ♻️ 优化插件中心功能 2022-01-17 20:32:08 +08:00
muwoo
240175c571 🐛 修复系统插件无法打开ui界面问题 2022-01-17 16:00:23 +08:00
muwoo
8107d74537 👷 构建问题 2022-01-14 10:21:58 +08:00
muwoo
861950145f Merge remote-tracking branch 'origin/master' 2022-01-14 10:12:10 +08:00
muwoo
7b320c9dd1 👷 构建问题 2022-01-14 10:11:52 +08:00
木偶
6640d66fac Update FUNDING.yml 2022-01-13 20:53:54 +08:00
muwoo
357846d2e6 👷 构建问题 2022-01-13 20:38:59 +08:00
muwoo
3b3ddf224c 👷 构建问题 2022-01-13 20:32:55 +08:00
muwoo
4439d0548f 👷 构建问题 2022-01-13 20:29:17 +08:00
muwoo
21163b2277 👷 构建问题 2022-01-13 20:13:50 +08:00
muwoo
480aaf2970 👷 构建问题 2022-01-13 20:12:27 +08:00
muwoo
06596d87ae 👷 构建问题 2022-01-13 19:57:17 +08:00
muwoo
1008e86fbb 👷 构建问题 2022-01-13 19:17:08 +08:00
muwoo
62ec316337 :ci: 构建问题 2022-01-13 18:59:57 +08:00
木偶
735a450260 Merge pull request #81 from rubickCenter/feat-dev
LGTM
2022-01-13 18:41:08 +08:00
muwoo
417ab071df 🐛 #80 修复 win depd bug 2022-01-13 18:40:04 +08:00
muwoo
1e73ab5ee6 🐛 #80 修复 win depd bug 2022-01-13 18:38:44 +08:00
muwoo
e5ff219685 ref: 修复win下多文件复制 2022-01-13 13:48:20 +08:00
muwoo
2beac06e7c ref: 修复win下多文件复制 2022-01-13 13:47:55 +08:00
muwoo
6b96df3da5 🐛 修复复制文件bug 2022-01-13 12:04:53 +08:00
muwoo
8521262344 Merge remote-tracking branch 'origin/master' 2022-01-11 19:39:12 +08:00
muwoo
4cf00f9270 支持 removePlugin API 2022-01-11 19:38:52 +08:00
muwoo
bdae8c280b ref: 支持win搜索快捷启动 2022-01-11 12:18:08 +08:00
muwoo
04e674d1cd 支持内网部署配置能力 2022-01-11 10:17:41 +08:00
muwoo
371565744e 支持内网部署配置能力 2022-01-10 19:13:26 +08:00
muwoo
58aabb9f1e 增加首屏打开速度 2022-01-05 19:41:14 +08:00
44 changed files with 651 additions and 172 deletions

2
.github/FUNDING.yml vendored
View File

@@ -1,4 +1,4 @@
# These are supported funding model platforms
github: #muwoo
custom: ['https://muwoo.github.io/blogs'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
custom: ['https://rubickcenter.github.io/rubick/run/#%E8%B5%9E%E5%8A%A9'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -23,7 +23,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-2019]
os: [macos-latest, windows-2019, ubuntu-latest]
# create steps
steps:
@@ -47,6 +47,7 @@ jobs:
run: |
yarn
yarn global add xvfb-maybe
yarn global add @vue/cli
- name: Build & release app
run: |

View File

@@ -32,6 +32,8 @@ Based on electron open source toolbox, free integration of rich plug-ins, to cre
## Installation package
* [Rubick Mac OS](https://github.com/rubickCenter/rubick/releases)
* [Rubick Windows](https://github.com/rubickCenter/rubick/releases)
* [Rubick Linux](https://github.com/rubickCenter/rubick/releases)
## Feature list
- [x] Plug-in management based on npm package mode, installing plugins is as easy as installing npm packages.
@@ -41,6 +43,8 @@ Based on electron open source toolbox, free integration of rich plug-ins, to cre
- [x] Support searching for locally installed apps or preferences
- [x] Support MacOS
- [x] Support Windows
- [x] Support Linux
## Docs

View File

@@ -1,3 +1,9 @@
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"],
plugins: [
[
"import",
{ libraryName: "ant-design-vue", libraryDirectory: "es", style: "css" },
], // `style: true` 会加载 less 文件
],
};

View File

@@ -23,7 +23,7 @@
#### 示例
```js
rubcik.onPluginReady(({ code, type, payload }) => {
rubick.onPluginReady(({ code, type, payload }) => {
console.log('插件装配完成,已准备好')
})
/*

View File

@@ -50,6 +50,27 @@ macos 选择 `pkg` 文件windows 选择 `exe` 文件。
![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/01ef50fbfa064ba9a88bebe1531eacd4~tplv-k3u1fbpfcp-watermark.image)
### 内网部署
::: tip
如果把插件发布到公网 `npm` 如果不符合您的公司安全要求,`rubick` 支持内网私有源和私有插件库,如果您需要内网部署使用,可以自行配置以下规则。
:::
`rubick` 依赖 `npm` 仓库做插件管理,依赖 `gitee` 做插件数据存储所以如果要进行内网部署主要需要替换这2个设置。详细设置
`插件市场 -> 设置 -> 内网部署设置`
![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1319b177fb544017ae10b4a703e8efa6~tplv-k3u1fbpfcp-watermark.image?)
#### 1. 替换 npm 源
插件发布到私有 `npm` 源即可。
#### 2. 替换 `gitee` 源为内网 `gitlab`: database url
* clone 下载 rubick 插件库:[https://gitee.com/monkeyWang/rubick-database](https://gitee.com/monkeyWang/rubick-database)
* 提交仓库到私有 `gitlab` 库。
替换格式:`https://gitlab.xxx.com/api/v4/projects/{projectId}/repository/files/` 。因为接口为 `gitlab openAPI`,所以需要填写仓库 `access_token`
### 更多功能
如果您还需要更多功能,欢迎来这里给我们提建议:[issues](https://github.com/rubickCenter/rubick/issues) 。
有价值的想法我们会加入到后期的开发当中。同时也欢迎一起加入共建。

View File

@@ -1,30 +1,53 @@
import axios from "axios";
let baseURL = "https://gitee.com/monkeyWang/rubick-database/raw/master";
let access_token = "";
try {
const dbdata = window.rubick.db.get("rubick-localhost-config");
baseURL = dbdata.data.database;
access_token = dbdata.data.access_token;
} catch (e) {
// ignore
}
const instance = axios.create({
baseURL: baseURL || "https://gitee.com/monkeyWang/rubick-database/raw/master",
});
export default {
async getTotalPlugins() {
const res = await axios.get(
"https://gitee.com/monkeyWang/rubick-database/raw/master/plugins/total-plugins.json"
);
let targetPath = "plugins/total-plugins.json";
if (access_token) {
targetPath = `${encodeURIComponent(targetPath)}/raw?access_token=${access_token}&ref=master`
}
const res = await instance.get(targetPath);
return res.data;
},
async getFinderDetail() {
const res = await axios.get(
"https://gitee.com/monkeyWang/rubick-database/raw/master/plugins/finder.json"
);
let targetPath = "plugins/finder.json";
if (access_token) {
targetPath = `${encodeURIComponent(targetPath)}/raw?access_token=${access_token}&ref=master`
}
const res = await instance.get(targetPath);
return res.data;
},
async getSystemDetail() {
const res = await axios.get(
"https://gitee.com/monkeyWang/rubick-database/raw/master/plugins/system.json"
);
let targetPath = "plugins/system.json";
if (access_token) {
targetPath = `${encodeURIComponent(targetPath)}/raw?access_token=${access_token}&ref=master`
}
const res = await instance.get(targetPath);
return res.data;
},
async getWorkerDetail() {
const res = await axios.get(
"https://gitee.com/monkeyWang/rubick-database/raw/master/plugins/worker.json"
);
let targetPath = "plugins/worker.json";
if (access_token) {
targetPath = `${encodeURIComponent(targetPath)}/raw?access_token=${access_token}&ref=master`
}
const res = await instance.get(targetPath);
return res.data;
},
@@ -33,16 +56,20 @@ export default {
return res.data;
},
async getSearchDetail(url: string) {
const res = await axios.get(
"https://gitee.com/monkeyWang/rubick-database/raw/master/plugins/search.json"
);
async getSearchDetail() {
let targetPath = "plugins/search.json";
if (access_token) {
targetPath = `${encodeURIComponent(targetPath)}/raw?access_token=${access_token}&ref=master`
}
const res = await instance.get(targetPath);
return res.data;
},
async getDevDetail(url: string) {
const res = await axios.get(
"https://gitee.com/monkeyWang/rubick-database/raw/master/plugins/dev.json"
);
async getDevDetail() {
let targetPath = "plugins/dev.json";
if (access_token) {
targetPath = `${encodeURIComponent(targetPath)}/raw?access_token=${access_token}&ref=master`
}
const res = await instance.get(targetPath);
return res.data;
},
};

View File

@@ -53,6 +53,21 @@ export default createStore({
totalPlugins,
});
},
startUnDownload({ commit, state }, name) {
const localPlugins = (window as any).market.getLocalPlugins();
localPlugins.forEach(
(origin: { isdwonload?: any; name?: any; isloading: boolean }) => {
if (origin.name === name) {
origin.isloading = true;
}
}
);
commit("commonUpdate", {
localPlugins,
});
},
successDownload({ commit, state }, name) {
const totalPlugins = JSON.parse(JSON.stringify(state.totalPlugins));
totalPlugins.forEach(
@@ -70,10 +85,20 @@ export default createStore({
localPlugins,
});
},
updateLocalPlugin({ commit }) {
async updateLocalPlugin({ commit }) {
const localPlugins = (window as any).market.getLocalPlugins();
const totalPlugins = await request.getTotalPlugins();
totalPlugins.forEach(
(origin: { isdwonload?: any; name?: any; isloading: boolean }) => {
origin.isdwonload = isDownload(origin, localPlugins);
origin.isloading = false;
}
);
commit("commonUpdate", {
localPlugins,
totalPlugins,
});
},
},

View File

@@ -40,8 +40,11 @@
type="danger"
size="small"
shape="round"
:loading="pluginDetail.isloading"
@click="deletePlugin(pluginDetail)"
>移除</a-button
>
移除
</a-button
>
</div>
</div>
@@ -56,17 +59,11 @@
<div>{{ item.explain }}</div>
<a-tag
:key="cmd"
@click="
openPlugin({
cmd,
plugin: pluginDetail,
feature: item,
router: $router,
})
"
v-for="cmd in item.cmds"
@close="removeFeature(cmd)"
:color="!cmd.label && '#87d068'"
>
{{ cmd }}
{{ cmd.label || cmd }}
</a-tag>
</div>
</div>
@@ -101,6 +98,7 @@ const localPlugins = computed(() =>
)
);
const updateLocalPlugin = () => store.dispatch("updateLocalPlugin");
const startUnDownload = (name) => store.dispatch("startUnDownload", name);
const currentSelect = ref([0]);
@@ -124,6 +122,7 @@ const readme = computed(() => {
});
const deletePlugin = async (plugin) => {
startUnDownload(plugin.name);
await window.market.deletePlugin(plugin);
updateLocalPlugin();
};
@@ -150,6 +149,7 @@ const deletePlugin = async (plugin) => {
height: 100%;
padding: 10px 0;
border-right: 1px solid #eee;
overflow: auto;
.item {
padding: 10px 20px;
display: flex;

View File

@@ -4,7 +4,7 @@
<div class="list-item">
<a-list :grid="{ gutter: 16, column: 2 }" :data-source="list">
<template #renderItem="{ item, index }">
<a-list-item @click="showDetail(item)">
<a-list-item v-if="item" @click="showDetail(item)">
<template #actions>
<a-button style="color: #ff4ea4;" type="text" :loading="item.isloading">
<CloudDownloadOutlined
@@ -57,9 +57,9 @@
<div class="desc">
{{ detail.description }}
</div>
<a-button shape="round" type="primary">
<a-button v-if="!detail.isdwonload" @click.stop="downloadPlugin(detail)" shape="round" type="primary" :loading="detail.isloading">
<template #icon>
<CloudDownloadOutlined />
<CloudDownloadOutlined v-show="!detail.isloading && !detail.isdwonload" />
</template>
获取
</a-button>

View File

@@ -14,6 +14,12 @@
</template>
全局快捷键
</a-menu-item>
<a-menu-item key="localhost">
<template #icon>
<DatabaseOutlined />
</template>
内网部署配置
</a-menu-item>
</a-menu>
</div>
<div class="settings-detail">
@@ -32,26 +38,6 @@
{{ shortCut.showAndHidden }}
</div>
</div>
<!-- <div class="settings-item-li">-->
<!-- <div class="label">插件分离快捷键</div>-->
<!-- <div-->
<!-- class="value"-->
<!-- tabIndex="-1"-->
<!-- @keyup="(e) => changeShortCut(e, 'separate')"-->
<!-- >-->
<!-- {{ shortCut.separate }}-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="settings-item-li">-->
<!-- <div class="label">返回主界面</div>-->
<!-- <div-->
<!-- class="value"-->
<!-- tabIndex="-1"-->
<!-- @keyup="(e) => changeShortCut(e, 'quit')"-->
<!-- >-->
<!-- {{ shortCut.quit }}-->
<!-- </div>-->
<!-- </div>-->
</div>
<div class="setting-item">
<div class="title">通用</div>
@@ -80,17 +66,6 @@
></a-switch>
</div>
</div>
<!-- <div class="setting-item">-->
<!-- <div class="title">本地搜索启动</div>-->
<!-- <div class="settings-item-li">-->
<!-- <div class="label">搜索启动应用&文件</div>-->
<!-- <a-switch-->
<!-- v-model:checked="local.search"-->
<!-- checked-children="开"-->
<!-- un-checked-children="关"-->
<!-- ></a-switch>-->
<!-- </div>-->
<!-- </div>-->
</div>
<div v-if="currentSelect[0] === 'global'">
<a-collapse>
@@ -147,15 +122,17 @@
</div>
<div @click="addConfig" class="add-global">+ 新增全局快捷功能</div>
</div>
<Localhost v-if="currentSelect[0] === 'localhost'" />
</div>
</div>
</template>
<script setup>
import { ToolOutlined, LaptopOutlined } from "@ant-design/icons-vue";
import { ToolOutlined, LaptopOutlined, DatabaseOutlined } from "@ant-design/icons-vue";
import debounce from "lodash.debounce";
import { ref, reactive, watch, toRefs, toRaw } from "vue";
import keycodes from "./keycode";
import Localhost from "./localhost.vue";
const { remote, ipcRenderer } = window.require("electron");

View File

@@ -0,0 +1,90 @@
<template>
<a-alert
message="把插件发布到公网 npm 如果不符合您的公司安全要求rubick 支持内网私有源和私有插件库,如果您需要内网部署使用,可以自行配置以下规则。"
type="warning"
style="margin-bottom: 20px"
/>
<a-form
name="custom-validation"
ref="formRef"
:model="formState"
:rules="rules"
v-bind="layout"
>
<a-form-item has-feedback label="npm 源" name="register">
<a-input
placeholder="https://registry.npm.taobao.org"
v-model:value="formState.register"
/>
</a-form-item>
<a-form-item has-feedback label="database url" name="database">
<a-input
placeholder="https://gitee.com/monkeyWang/rubick-database/raw/master"
v-model:value="formState.database"
/>
</a-form-item>
<a-form-item has-feedback label="access_token" name="access_token">
<a-input
placeholder="内网gitlab仓库必填"
v-model:value="formState.access_token"
/>
</a-form-item>
<a-form-item :wrapper-col="{ span: 18, offset: 6 }">
<a-button @click="submit" type="primary">确定</a-button>
<a-button style="margin-left: 10px" @click="resetForm">恢复默认</a-button>
</a-form-item>
</a-form>
</template>
<script lang="ts" setup>
import { ref, toRaw } from "vue";
import { message } from "ant-design-vue";
let _rev: any;
let defaultConfig = {
register: "https://registry.npm.taobao.org",
database: "https://gitee.com/monkeyWang/rubick-database/raw/master",
access_token: "",
};
try {
const dbdata = window.rubick.db.get("rubick-localhost-config");
defaultConfig = dbdata.data;
_rev = dbdata._rev;
} catch (e) {
// ignore
}
const formState = ref(JSON.parse(JSON.stringify(defaultConfig)));
const rules = {
register: [{ required: true, trigger: "change" }],
database: [{ required: true, trigger: "change" }],
};
const layout = {
labelCol: { span: 6 },
wrapperCol: { span: 18 },
};
const resetForm = () => {
formState.value = {
register: "https://registry.npm.taobao.org",
database: "https://gitee.com/monkeyWang/rubick-database/raw/master",
access_token: "",
};
};
const submit = () => {
const changeData: any = {
_id: "rubick-localhost-config",
data: toRaw(formState.value),
};
if (_rev) {
changeData._rev = _rev;
}
window.rubick.db.put(changeData);
message.success("设置成功!重启插件市场后生效!");
};
</script>

View File

@@ -1,6 +1,7 @@
{
"name": "rubick",
"version": "2.0.1-beta.11",
"version": "2.0.2",
"author": "muwoo <2424880409@qq.com>",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
@@ -14,6 +15,9 @@
"postuninstall": "electron-builder install-app-deps"
},
"main": "background.js",
"optionalDependencies": {
"electron-clipboard-ex": "^1.3.3"
},
"dependencies": {
"@better-scroll/core": "^2.4.2",
"ant-design-vue": "^2.2.8",
@@ -23,7 +27,6 @@
"fix-path": "^3.0.0",
"get-mac-apps": "^1.0.2",
"got": "^11.8.3",
"libnpmsearch": "^3.1.2",
"lodash.throttle": "^4.1.1",
"pouchdb": "^7.2.2",
"vue": "^3.0.0",
@@ -45,7 +48,9 @@
"@vue/compiler-sfc": "^3.0.0",
"@vue/eslint-config-prettier": "^6.0.0",
"@vue/eslint-config-typescript": "^7.0.0",
"babel-plugin-import": "^1.13.3",
"electron": "^13.0.0",
"electron-builder": "22.13.1",
"electron-devtools-installer": "^3.1.0",
"eslint": "^6.7.2",
"eslint-plugin-prettier": "^3.3.1",
@@ -55,8 +60,7 @@
"prettier": "^2.2.1",
"typescript": "~4.1.5",
"vue-cli-plugin-electron-builder": "~2.1.1",
"worker-plugin": "^5.0.1",
"electron-builder": "22.13.1"
"worker-plugin": "^5.0.1"
},
"__npminstall_done": false
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="favicon.ico"><title>feature</title><link href="css/app.f8214d90.css" rel="preload" as="style"><link href="js/app.bad50f73.js" rel="preload" as="script"><link href="js/chunk-vendors.e8fd66a6.js" rel="preload" as="script"><link href="css/app.f8214d90.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but feature doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="js/chunk-vendors.e8fd66a6.js"></script><script src="js/app.bad50f73.js"></script></body></html>
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="favicon.ico"><title>feature</title><link href="css/app.efef6e76.css" rel="preload" as="style"><link href="js/app.b6f0fed9.js" rel="preload" as="script"><link href="js/chunk-vendors.335eb4e0.js" rel="preload" as="script"><link href="css/app.efef6e76.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but feature doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="js/chunk-vendors.335eb4e0.js"></script><script src="js/app.b6f0fed9.js"></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

BIN
public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -137,4 +137,12 @@ window.rubick = {
},
getLocalId: () => ipcSendSync("getLocalId"),
removePlugin() {
ipcSend("removePlugin");
},
shellShowItemInFolder: path => {
ipcSend("shellShowItemInFolder", { path });
},
};

View File

@@ -17,8 +17,9 @@ export default function getCopyFiles(): Array<any> | null {
return null;
}
} else if (commonConst.windows()) {
const filePath = clipboard.readBuffer('FileNameW').toString('ucs2').replace(RegExp(String.fromCharCode(0), 'g'), '');
fileInfo = [filePath];
/* eslint-disable */
const clipboardEx = require("electron-clipboard-ex");
fileInfo = clipboardEx.readFilePaths();
// todo
} else {
if (!commonConst.linux()) return null;

View File

@@ -1,7 +1,14 @@
import path from "path";
import fs from "fs";
export default (): string => {
let localDataFile: any = process.env.HOME;
if (!localDataFile) {
localDataFile = process.env.LOCALAPPDATA;
}
return localDataFile;
const rubickPath = path.join(localDataFile, "rubick")
if (!fs.existsSync(rubickPath)) {
fs.mkdirSync(rubickPath);
}
return rubickPath;
};

View File

@@ -1,7 +1,6 @@
import path from "path";
import fs from "fs";
import getLocalDataFile from "./getLocalDataFile";
import commonConst from "./commonConst";
import defaultConfigForAnyPlatform from "../constans/defaultConfig";
const configPath = path.join(getLocalDataFile(), "./rubick-config.json");
@@ -10,34 +9,34 @@ global.OP_CONFIG = {
config: null,
get() {
try {
if (!global.config) {
global.config = JSON.parse(
if (!global.OP_CONFIG.config) {
global.OP_CONFIG.config = JSON.parse(
fs.readFileSync(configPath, "utf8") ||
JSON.stringify(defaultConfigForAnyPlatform)
);
}
// 重置
if (
!global.config.version ||
global.config.version < defaultConfigForAnyPlatform.version
!global.OP_CONFIG.config.version ||
global.OP_CONFIG.config.version < defaultConfigForAnyPlatform.version
) {
global.config = defaultConfigForAnyPlatform;
global.OP_CONFIG.config = defaultConfigForAnyPlatform;
fs.writeFileSync(
configPath,
JSON.stringify(defaultConfigForAnyPlatform)
);
}
return global.config;
return global.OP_CONFIG.config;
} catch (e) {
global.config = defaultConfigForAnyPlatform;
return global.config;
global.OP_CONFIG.config = defaultConfigForAnyPlatform;
return global.OP_CONFIG.config;
}
},
set(value) {
global.config = {
...global.config,
global.OP_CONFIG.config = {
...global.OP_CONFIG.config,
...value,
};
fs.writeFileSync(configPath, JSON.stringify(global.config));
fs.writeFileSync(configPath, JSON.stringify(global.OP_CONFIG.config));
},
};

View File

@@ -3,12 +3,27 @@ import fs from "fs";
import getLocalDataFile from "./getLocalDataFile";
import { PluginHandler } from "@/core";
import { PLUGIN_INSTALL_DIR as baseDir } from "@/common/constans/main";
import { API } from "@/main/common/api";
const configPath = path.join(getLocalDataFile(), "./rubick-local-plugin.json");
const pluginInstance = new PluginHandler({
baseDir,
});
let registry;
let pluginInstance;
(async () => {
try {
registry = (await API.dbGet({ data: { id: "rubick-localhost-config" } })).data.register;
console.log(registry);
pluginInstance = new PluginHandler({
baseDir,
registry,
});
} catch (e) {
pluginInstance = new PluginHandler({
baseDir,
registry,
});
}
})();
global.LOCAL_PLUGINS = {
PLUGINS: [],

View File

@@ -1,3 +1,217 @@
import path from "path";
import originfs from "original-fs";
const app_paths = [
"/usr/share/applications",
"/var/lib/snapd/desktop/applications",
`${window.process.env.HOME}/.local/share/applications`,
];
const emptyIcon = "";
function dirAppRead(dir, target) {
let files: Array<string> | null = null;
try {
if (!originfs.existsSync(dir)) return;
files = originfs.readdirSync(dir);
} catch (e) {
return;
}
if (files.length !== 0) {
for (const file of files) {
const app = path.join(dir, file);
path.extname(app) === ".desktop" && target.push(app);
}
}
}
function convertEntryFile2Feature(appPath) {
let appInfo: any = null;
try {
appInfo = originfs.readFileSync(appPath, "utf8");
} catch (e) {
return null;
}
if (!appInfo.includes("[Desktop Entry]")) {
return null;
}
appInfo = appInfo
.substr(appInfo.indexOf("[Desktop Entry]"))
.replace("[Desktop Entry]", "")
.trim();
/**
* appInfo eg:
* Version=1.0
* Name=FireFox
* Name[ar]=***
* Name[ast]=***
* [Desktop Action new-private-window]
* Name=***
*/
const splitIndex = appInfo.indexOf("\n[");
if (splitIndex > 0) {
appInfo = appInfo.substr(0, splitIndex).trim();
}
const targetAppInfo: any = {};
appInfo.match(/^[\w\-[\]]+ ?=.*$/gm).forEach((e) => {
const index = e.indexOf("=");
targetAppInfo[e.substr(0, index).trim()] = e.substr(index + 1).trim();
});
/**
* targetAppInfo = {
* Type: "Application",
* Version: "1.0",
* Exec: "xxxx",
* }
*/
if (targetAppInfo.Type !== "Application") {
return null;
}
if (!targetAppInfo.Exec) {
return null;
}
if (
targetAppInfo.NoDisplay === "true" &&
!targetAppInfo.Exec.startsWith("gnome-control-center")
) {
return null;
}
let os = String(window.process.env.DESKTOP_SESSION).toLowerCase();
if (os === "ubuntu") {
os = "gnome";
if (
targetAppInfo.OnlyShowIn &&
!targetAppInfo.OnlyShowIn.toLowerCase().includes(os)
) {
return null;
}
}
if (
targetAppInfo.NotShowIn &&
targetAppInfo.NotShowIn.toLowerCase().includes(os)
) {
return null;
}
let icon = targetAppInfo.Icon;
if (!icon) return null;
if (icon.startsWith("/")) {
if (!originfs.existsSync(icon)) return null;
} else if (
appPath.startsWith("/usr/share/applications") ||
appPath.startsWith("/var/lib/snapd/desktop/applications")
) {
icon = getIcon(icon);
} else {
if (
!appPath.startsWith(
(window as any).process.env.HOME + "/.local/share/applications"
)
)
return null;
appPath = path.join(
(window as any).process.env.HOME,
".local/share/icons",
appPath + ".png"
);
originfs.existsSync(appPath) || (appPath = emptyIcon);
}
let desc = "";
const LANG = (window as any).process.env.LANG.split(".")[0];
if (`Comment[${LANG}]` in targetAppInfo) {
desc = targetAppInfo[`Comment[${LANG}]`];
} else if (targetAppInfo.Comment) {
desc = targetAppInfo.Comment;
} else {
desc = appPath;
}
let execPath = targetAppInfo.Exec.replace(/ %[A-Za-z]/g, "")
.replace(/"/g, "")
.trim();
targetAppInfo.Terminal === "true" &&
(execPath = "gnome-terminal -x " + execPath);
const info = {
value: "plugin",
pluginType: "app",
desc,
icon: "file://" + icon,
keyWords: [targetAppInfo.Name],
action: execPath,
};
if ("X-Ubuntu-Gettext-Domain" in targetAppInfo) {
const cmd = targetAppInfo["X-Ubuntu-Gettext-Domain"];
cmd && cmd !== targetAppInfo.Name && info.keyWords.push(cmd);
}
return info;
}
function getIcon(filePath) {
const themes = [
"ubuntu-mono-dark",
"ubuntu-mono-light",
"Yaru",
"hicolor",
"Adwaita",
"Humanity",
];
const sizes = ["48x48", "48", "scalable", "256x256", "512x512", "256", "512"];
const types = [
"apps",
"categories",
"devices",
"mimetypes",
"legacy",
"actions",
"places",
"status",
"mimes",
];
const exts = [".png", ".svg"];
for (const theme of themes) {
for (const size of sizes) {
for (const type of types) {
for (const ext of exts) {
let iconPath = path.join(
"/usr/share/icons",
theme,
size,
type,
filePath + ext
);
if (originfs.existsSync(iconPath)) return iconPath;
iconPath = path.join(
"/usr/share/icons",
theme,
type,
size,
filePath + ext
);
if (originfs.existsSync(iconPath)) return iconPath;
}
}
}
}
return originfs.existsSync(path.join("/usr/share/pixmaps", filePath + ".png"))
? path.join("/usr/share/pixmaps", filePath + ".png")
: emptyIcon;
}
export default () => {
// todo linux search
const apps: any = [];
const fileList = [];
app_paths.forEach((dir) => {
dirAppRead(dir, fileList);
});
fileList.forEach((e) => {
apps.push(convertEntryFile2Feature(e));
});
return apps.filter((app) => !!app);
};

View File

@@ -6,8 +6,14 @@ import { shell } from "electron";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const fileIcon = require("extract-file-icon");
const filePath = path.resolve("C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs");
const appData = path.join(os.homedir(), "./AppData/Roaming");
const startMenu = path.join(appData, "Microsoft\\Windows\\Start Menu\\Programs");
const fileLists: any = [];
const isZhRegex = /[\u4e00-\u9fa5]/;
@@ -58,7 +64,10 @@ function fileDisplay(filePath) {
} catch(e) {
//
}
if (!appDetail.target || appDetail.target.toLowerCase().indexOf("unin") >= 0) return;
if (!appDetail.target || appDetail.target.toLowerCase().indexOf("unin") >= 0 || appDetail.args) return;
// C:/program/cmd.exe => cmd
keyWords.push(path.basename(appDetail.target, ".exe"));
if (isZhRegex.test(appName)) {
const py = translate(appName);
@@ -101,5 +110,6 @@ function fileDisplay(filePath) {
export default () => {
fileDisplay(filePath);
fileDisplay(startMenu);
return fileLists;
};

View File

@@ -1,9 +1,7 @@
import appSearch from "@/core/app-search";
import PluginHandler from "@/core/plugin-handler";
import LocalDb from "@/core/db";
export {
appSearch,
PluginHandler,
LocalDb,
};

View File

@@ -3,12 +3,12 @@ import {
AdapterInfo,
} from "@/core/plugin-handler/types";
import fs from "fs-extra";
import search, { Result } from "libnpmsearch";
import path from "path";
import got from "got";
import fixPath from "fix-path";
import spawn from "cross-spawn";
import { ipcRenderer } from "electron";
fixPath();
@@ -37,7 +37,19 @@ class AdapterHandler {
);
}
this.baseDir = options.baseDir;
this.registry = options.registry || "https://registry.npm.taobao.org";
let register = options.registry || "https://registry.npm.taobao.org";
try {
const dbdata = ipcRenderer.sendSync("msg-trigger", {
type: "dbGet",
data: { id: "rubick-localhost-config" },
});
register = dbdata.data.register;
} catch (e) {
// ignore
}
this.registry = register || "https://registry.npm.taobao.org";
}
/**
@@ -77,30 +89,6 @@ class AdapterHandler {
await this.execCommand(installCmd, adapters);
}
/**
* 从 npm 搜索插件
* 传入 streamFunc 可以流式处理
* @param {string} adapter 插件名称
* @param {(data: Result) => void} [streamFunc] 流式处理钩子
* @memberof AdapterHandler
*/
async search(adapter: string, streamFunc?: (data: Result) => void) {
return await new Promise<Result[]>((resolve, reject) => {
const result: Result[] = [];
const stream = search.stream(adapter);
stream.on("data", (data: Result) => {
result.push(data);
if (streamFunc !== undefined) streamFunc(data);
});
stream.on("end", () => {
resolve(result);
});
stream.on("error", (e: any) => {
reject(e);
});
});
}
/**
* 更新指定插件
* @param {...string[]} adapters 插件名称

View File

@@ -34,7 +34,6 @@ export default () => {
// Load the index.html when not in development
win.loadURL("app://./index.html");
}
protocol.interceptFileProtocol("image", (req, callback) => {
const url = req.url.substr(8);
callback(decodeURI(url));

View File

@@ -6,6 +6,7 @@ import {
Notification,
nativeImage,
clipboard,
shell,
} from "electron";
import { runner, detach } from "../browsers";
import fs from "fs";
@@ -19,7 +20,7 @@ const dbInstance = new LocalDb(app.getPath("userData"));
dbInstance.init();
const API: any = {
export const API: any = {
currentPlugin: null,
DBKEY: "RUBICK_DB_DEFAULT",
getCurrentWindow: (window, e) => {
@@ -155,7 +156,7 @@ const API: any = {
return dbInstance.bulkDocs(API.DBKEY, data.docs);
},
dbAllDocs({ data }) {
return dbInstance.bulkDocs(API.DBKEY, data.key);
return dbInstance.allDocs(API.DBKEY, data.key);
},
getFeatures() {
return API.currentPlugin.features;
@@ -185,9 +186,12 @@ const API: any = {
removeFeature({ data }, window) {
API.currentPlugin = {
...API.currentPlugin,
features: API.currentPlugin.features.filter(
(feature) => feature.code !== data.code
),
features: API.currentPlugin.features.filter((feature) => {
if (data.code.type) {
return feature.code.type !== data.code.type;
}
return feature.code !== data.code;
}),
};
window.webContents.executeJavaScript(
`window.updatePlugin(${JSON.stringify({
@@ -236,13 +240,21 @@ const API: any = {
detachInputChange({ data }) {
API.sendSubInputChangeEvent({ data });
},
getLocalId() {
return encodeURIComponent(app.getPath("home"));
},
shellShowItemInFolder({ data }) {
shell.showItemInFolder(data.path);
return true;
},
};
export default (mainWindow: BrowserWindow) => {
// 响应 preload.js 事件
ipcMain.on("msg-trigger", async (event, arg) => {
const window = arg.winId ? BrowserWindow.fromId(arg.winId) : mainWindow;
const data = await API[arg.type](arg, window, event);
event.returnValue = data;
// event.sender.send(`msg-back-${arg.type}`, data);

View File

@@ -1,6 +1,6 @@
<template>
<div v-if="commonConst.windows()" class="drag-bar"></div>
<div :class="!commonConst.windows() && 'drag'" id="components-layout">
<div v-if="commonConst.windows() || commonConst.linux()" class="drag-bar"></div>
<div :class="!commonConst.windows() && !commonConst.linux() && 'drag'" id="components-layout">
<div class="rubick-select">
<Search
:currentPlugin="currentPlugin"

View File

@@ -1,6 +1,6 @@
<template>
<div v-show="!!options.length && (searchValue || !!clipboardFile.length) && !currentPlugin.name" class="options" ref="scrollDom">
<a-list item-layout="horizontal" :dataSource="options">
<a-list item-layout="horizontal" :dataSource="sort(options)">
<template #renderItem="{ item, index }">
<a-list-item
@click="() => item.click()"
@@ -66,6 +66,20 @@ const renderDesc = (desc) => {
}
return desc;
};
const sort = (options) => {
for (let i = 0; i < options.length; i++) {
for (let j = i + 1; j < options.length; j++) {
if (options[j].zIndex > options[i].zIndex) {
let temp = options[i];
options[i] = options[j];
options[j] = temp;
}
}
}
return options;
};
</script>
<style lang="less">

View File

@@ -3,7 +3,8 @@
<div class="select-tag" v-show="currentPlugin.cmd">{{ currentPlugin.cmd }}</div>
<div :class="clipboardFile[0].name ? 'clipboard-tag' : 'clipboard-img'" v-if="!!clipboardFile.length">
<img :src="getIcon()" />
{{ clipboardFile[0].name }}
<div class="ellipse">{{ clipboardFile[0].name }}</div>
<a-tag color="#aaa" v-if="clipboardFile.length > 1">{{ clipboardFile.length }}</a-tag>
</div>
<a-input
id="search"
@@ -177,7 +178,7 @@ const changeHideOnBlur = () => {
const getIcon = () => {
if (props.clipboardFile[0].dataUrl) return props.clipboardFile[0].dataUrl;
return props.clipboardFile[0].isFile ? require("../assets/file.png") : require("../assets/folder.png")
}
};
const newWindow = () => {
ipcRenderer.send("msg-trigger", {
@@ -197,6 +198,12 @@ const newWindow = () => {
left: 0;
width: 100%;
align-items: center;
.ellipse {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 200px;
}
.select-tag {
white-space: pre;
user-select: none;

View File

@@ -1,6 +1,12 @@
import { createApp } from "vue";
import Antd from "ant-design-vue";
import { Button, List, Spin, Input, Avatar, Tag } from "ant-design-vue";
import App from "./App.vue";
import "ant-design-vue/dist/antd.css";
createApp(App).use(Antd).mount("#app");
createApp(App)
.use(Button)
.use(List)
.use(Spin)
.use(Input)
.use(Avatar)
.use(Tag)
.mount("#app");

View File

@@ -120,8 +120,8 @@ export default ({
clearClipboardFile();
window.setSubInputValue({ value: contentText });
}
clipboard.clear();
}
clipboard.clear();
};
const clearClipboardFile = () => {

View File

@@ -1,6 +1,7 @@
import { reactive, toRefs, ref } from "vue";
import { nativeImage, remote, ipcRenderer } from "electron";
import { appSearch, PluginHandler } from "@/core";
import appSearch from "@/core/app-search";
import { PluginHandler } from "@/core";
import path from "path";
import commonConst from "@/common/utils/commonConst";
import { execSync } from "child_process";
@@ -28,7 +29,7 @@ const createPluginManager = (): any => {
};
const openPlugin = (plugin) => {
if (plugin.pluginType === "ui") {
if (plugin.pluginType === "ui" || plugin.pluginType === "system") {
if (state.currentPlugin && state.currentPlugin.name === plugin.name) {
return;
}

View File

@@ -54,6 +54,7 @@ const optionsManager = ({
icon: plugin.logo,
desc: fe.explain,
type: plugin.pluginType,
zIndex: cmd.label ? 0 : 1, // 排序权重
click: () => {
pluginClickEvent({
plugin,
@@ -61,10 +62,10 @@ const optionsManager = ({
cmd,
ext: cmd.type
? {
code: fe.code,
type: cmd.type || "text",
payload: searchValue.value,
}
code: fe.code,
type: cmd.type || "text",
payload: searchValue.value,
}
: null,
openPlugin,
});
@@ -101,14 +102,17 @@ const optionsManager = ({
}
})
.map((plugin) => {
plugin.click = () => {
openPlugin(plugin);
return {
...plugin,
zIndex: 1,
click: () => {
openPlugin(plugin);
},
};
return plugin;
}),
];
return options;
}
};
watch(searchValue, () => search(searchValue.value));
// search Input operation
@@ -126,7 +130,12 @@ const optionsManager = ({
optionsRef.value = options;
};
const { searchFocus, clipboardFile, clearClipboardFile, readClipboardContent } = useFocus({
const {
searchFocus,
clipboardFile,
clearClipboardFile,
readClipboardContent,
} = useFocus({
currentPlugin,
optionsRef,
openPlugin,

View File

@@ -15,6 +15,7 @@ module.exports = {
entry: "src/renderer/main.ts",
},
},
productionSourceMap: false,
pluginOptions: {
electronBuilder: {
nodeIntegration: true,
@@ -50,7 +51,10 @@ module.exports = {
},
mac: {
icon: "public/icons/icon.icns",
target: "pkg",
target: [
"dmg",
"pkg"
],
extendInfo: {
LSUIElement: 1,
},
@@ -66,6 +70,7 @@ module.exports = {
linux: {
icon: "public/icons/",
publish: ["github"],
target: "deb",
},
},
},