mirror of
https://github.com/rubickCenter/rubick
synced 2025-10-26 22:51:25 +08:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0cbbe3525e | ||
|
|
d1ce6a307d | ||
|
|
a26a03e948 | ||
|
|
c99a3eb74c | ||
|
|
32c6cf5d1c | ||
|
|
978b48264f | ||
|
|
79e39018fd | ||
|
|
fe159427b8 | ||
|
|
b94d725d69 | ||
|
|
8fe916020b | ||
|
|
d442fae447 | ||
|
|
9a3ca8403b | ||
|
|
96f53e3b40 | ||
|
|
b06df01527 | ||
|
|
c7eb266002 | ||
|
|
9b303aa5c7 | ||
|
|
aa059b2596 | ||
|
|
c21c08c370 | ||
|
|
b3c5d30bfb | ||
|
|
2255cb783f |
@@ -88,7 +88,7 @@ If you need more features, please come here to give us suggestions:[issues](ht
|
||||
We will add valuable ideas to the later development. At the same time, welcome to join and build together。
|
||||
|
||||
## 贡献
|
||||
This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)]. <a href="https://github.com/rubickCenter/rubick/graphs/contributors"><img src="https://opencollective.com/rubick/contributors.svg?width=890&button=false" /></a>
|
||||
This project exists thanks to all the people who contribute. [[Contribute](https://github.com/rubickCenter/rubick/graphs/contributors)]. <a href="https://github.com/rubickCenter/rubick/graphs/contributors"><img src="https://opencollective.com/rubick/contributors.svg?width=890&button=false" /></a>
|
||||
|
||||
## 反馈
|
||||
对本项目有兴趣或者想要交流学习的同学可以扫码加下面的微信,备注 rubick,帮助我们更好的成长:
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
有价值的想法我们会加入到后期的开发当中。同时也欢迎一起加入共建。
|
||||
|
||||
## 贡献
|
||||
This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)]. <a href="https://github.com/rubickCenter/rubick/graphs/contributors"><img src="https://opencollective.com/rubick/contributors.svg?width=890&button=false" /></a>
|
||||
This project exists thanks to all the people who contribute. [[Contribute](https://github.com/rubickCenter/rubick/graphs/contributors)]. <a href="https://github.com/rubickCenter/rubick/graphs/contributors"><img src="https://opencollective.com/rubick/contributors.svg?width=890&button=false" /></a>
|
||||
|
||||
## 反馈
|
||||
对本项目有兴趣或者想要交流学习的同学可以扫码加下面的微信,备注 rubick,帮助我们更好的成长:
|
||||
|
||||
@@ -80,7 +80,7 @@ window.showNotification = function () {
|
||||
rubick.showNotification('HI, rubick')
|
||||
}
|
||||
```
|
||||
rubick 更多支持 API 能力参考:[rubick 全局API](https://github.com/rubickCenter/rubick/blob/master/static/preload.js#L49)
|
||||
rubick 更多支持 API 能力参考:[rubick 全局API](https://github.com/rubickCenter/rubick/blob/master/public/preload.js)
|
||||
|
||||
### 测试写好的插件
|
||||
由于 `rubick` 插件是基于 `npm` 的管理方式,所以开发者调试插件,也是基于 `npm` 的软连接的方式进行调试。
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "rubick-system-feature",
|
||||
"pluginName": "rubick 系统菜单",
|
||||
"description": "rubick 系统菜单",
|
||||
"pluginName": "系统菜单",
|
||||
"description": "系统菜单",
|
||||
"main": "index.html",
|
||||
"logo": "https://pic1.zhimg.com/80/v2-c09780808301668a82e6646cb42f0806_720w.png",
|
||||
"version": "0.0.0",
|
||||
@@ -10,19 +10,19 @@
|
||||
"features": [
|
||||
{
|
||||
"code": "market",
|
||||
"explain": "rubick 插件市场",
|
||||
"explain": "插件市场",
|
||||
"cmds":[
|
||||
"插件市场"
|
||||
]
|
||||
},{
|
||||
"code": "installed",
|
||||
"explain": "rubick 已安装插件",
|
||||
"explain": "已安装插件",
|
||||
"cmds":[
|
||||
"已安装插件"
|
||||
]
|
||||
},{
|
||||
"code": "settings",
|
||||
"explain": "rubick 偏好设置",
|
||||
"explain": "偏好设置",
|
||||
"cmds":[
|
||||
"偏好设置"
|
||||
]
|
||||
|
||||
@@ -1,4 +1,21 @@
|
||||
const remote = require('@electron/remote');
|
||||
const { ipcRenderer } = require('electron');
|
||||
|
||||
const ipcSendSync = (type, data) => {
|
||||
const returnValue = ipcRenderer.sendSync('msg-trigger', {
|
||||
type,
|
||||
data,
|
||||
});
|
||||
if (returnValue instanceof Error) throw returnValue;
|
||||
return returnValue;
|
||||
};
|
||||
|
||||
const ipcSend = (type, data) => {
|
||||
ipcRenderer.send('msg-trigger', {
|
||||
type,
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
window.market = {
|
||||
getLocalPlugins() {
|
||||
@@ -13,4 +30,17 @@ window.market = {
|
||||
refreshPlugin(plugin) {
|
||||
return remote.getGlobal('LOCAL_PLUGINS').refreshPlugin(plugin);
|
||||
},
|
||||
addLocalStartPlugin(plugin) {
|
||||
ipcSend('addLocalStartPlugin', { plugin });
|
||||
},
|
||||
removeLocalStartPlugin(plugin) {
|
||||
ipcSend('removeLocalStartPlugin', { plugin });
|
||||
},
|
||||
dbDump(target) {
|
||||
ipcSend('dbDump', { target });
|
||||
},
|
||||
|
||||
dbImport(target) {
|
||||
ipcSend('dbImport', { target });
|
||||
},
|
||||
};
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 3.2 KiB |
@@ -96,6 +96,12 @@ export default {
|
||||
accessToken: 'access token',
|
||||
placeholder: 'required for private network gitlab warehouse',
|
||||
},
|
||||
localstart: {
|
||||
title: 'Local Start',
|
||||
},
|
||||
database: {
|
||||
title: 'Data Synchronization',
|
||||
},
|
||||
},
|
||||
dev: {
|
||||
title: 'Developer',
|
||||
|
||||
@@ -94,6 +94,12 @@ export default {
|
||||
accessToken: 'access token',
|
||||
placeholder: '内网gitlab仓库必填',
|
||||
},
|
||||
localstart: {
|
||||
title: '本地启动',
|
||||
},
|
||||
database: {
|
||||
title: '多端数据同步',
|
||||
},
|
||||
},
|
||||
dev: {
|
||||
title: '开发者',
|
||||
|
||||
268
feature/src/views/settings/database.vue
Normal file
268
feature/src/views/settings/database.vue
Normal file
@@ -0,0 +1,268 @@
|
||||
<template>
|
||||
<div class="export-header">
|
||||
<a-button @click="exportData" size="small" type="primary" style="margin-right: 10px;">
|
||||
导出数据
|
||||
<template #icon>
|
||||
<ExportOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
<a-button @click="importData" danger size="small" style="margin-right: 10px;">
|
||||
导入数据
|
||||
<template #icon>
|
||||
<ImportOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
<a-button type="link" size="small" @click="showSetting = true">
|
||||
<template #icon>
|
||||
<SettingOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</div>
|
||||
<a-list item-layout="horizontal" :data-source="dataPlugins">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<template #actions>
|
||||
<a v-if="!item.plugin.isdownload && !item.plugin.isloading" key="list-loadmore-edit"
|
||||
@click="() => downloadPlugin(item.plugin)">
|
||||
<CloudDownloadOutlined style="font-size: 18px;"/>
|
||||
</a>
|
||||
<a v-if="item.plugin.isloading" key="list-loadmore-edit">
|
||||
<LoadingOutlined style="font-size: 18px;"/>
|
||||
</a>
|
||||
<a key="list-loadmore-edit" @click="() => showKeys(item)">
|
||||
<DatabaseOutlined style="font-size: 18px;"/>
|
||||
</a>
|
||||
</template>
|
||||
<a-list-item-meta :description="`${item.keys.length} 份文档`">
|
||||
<template #title>
|
||||
<div>
|
||||
<span>{{ item.plugin.pluginName }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #avatar>
|
||||
<a-avatar shape="square" :src="item.plugin.logo"/>
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
<a-drawer
|
||||
v-model:visible="open"
|
||||
width="400"
|
||||
:closable="false"
|
||||
:title="currentSelect.plugin.pluginName"
|
||||
placement="right"
|
||||
>
|
||||
<p
|
||||
class="key-item"
|
||||
:key="key"
|
||||
@click="() => showDetail(key)"
|
||||
v-for="key in currentSelect.keys"
|
||||
>
|
||||
{{ key }}
|
||||
</p>
|
||||
</a-drawer>
|
||||
<a-modal
|
||||
centered
|
||||
bodyStyle="max-height: 500px; overflow: auto"
|
||||
:footer="null"
|
||||
:closable="false"
|
||||
v-model:visible="show"
|
||||
>
|
||||
<pre>{{ JSON.stringify(detail, null, 2) }}</pre>
|
||||
</a-modal>
|
||||
<a-modal
|
||||
v-model:visible="showSetting"
|
||||
title="webdav 账户配置"
|
||||
:footer="null"
|
||||
>
|
||||
<a-alert v-if="formState.suport === 'jianguo'" style="margin-bottom: 20px;" type="info" show-icon>
|
||||
<template #message>
|
||||
<div
|
||||
@click="openHelp"
|
||||
class="openHelp">
|
||||
点击查看如何获取坚果云账号密码
|
||||
</div>
|
||||
</template>
|
||||
</a-alert>
|
||||
<a-form
|
||||
:model="formState"
|
||||
name="basic"
|
||||
:label-col="{ span: 8 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
autocomplete="off"
|
||||
@finish="handleOk"
|
||||
>
|
||||
<a-form-item
|
||||
label="webdav 提供商"
|
||||
name="suport"
|
||||
>
|
||||
<a-select v-model:value="formState.suport">
|
||||
<a-select-option value="jianguo">坚果云</a-select-option>
|
||||
<a-select-option value="auto">自定义</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="服务器地址"
|
||||
name="url"
|
||||
v-show="formState.suport === 'auto'"
|
||||
:rules="[{ required: true, message: '请填写服务器地址!' }]"
|
||||
>
|
||||
<a-input v-model:value="formState.url" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="账户"
|
||||
name="username"
|
||||
:rules="[{ required: true, message: '请填写 username!' }]"
|
||||
>
|
||||
<a-input v-model:value="formState.username" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="密码"
|
||||
name="password"
|
||||
:rules="[{ required: true, message: '请填写 password!' }]"
|
||||
>
|
||||
<a-input-password v-model:value="formState.password" />
|
||||
</a-form-item>
|
||||
<a-form-item :wrapper-col="{ offset: 8, span: 16 }">
|
||||
<a-button type="primary" html-type="submit">保存设置</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script setup>
|
||||
import { useStore } from 'vuex';
|
||||
import { computed, ref, reactive } from 'vue';
|
||||
import {
|
||||
DatabaseOutlined,
|
||||
CloudDownloadOutlined,
|
||||
LoadingOutlined,
|
||||
ExportOutlined,
|
||||
ImportOutlined,
|
||||
SettingOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const open = ref(false);
|
||||
const show = ref(false);
|
||||
const showSetting = ref(false);
|
||||
const currentSelect = ref({ plugin: {} });
|
||||
const detail = ref({});
|
||||
|
||||
const defaultConfig = window.rubick.dbStorage.getItem(
|
||||
'rubick-db-jg-webdav'
|
||||
) || {
|
||||
url: 'https://dav.jianguoyun.com/dav/',
|
||||
username: '',
|
||||
password: '',
|
||||
};
|
||||
|
||||
if (!defaultConfig.suport) {
|
||||
defaultConfig.suport = 'jianguo';
|
||||
}
|
||||
|
||||
const formState = reactive(defaultConfig);
|
||||
|
||||
const showKeys = (item) => {
|
||||
open.value = true;
|
||||
currentSelect.value = item;
|
||||
};
|
||||
|
||||
const handleOk = () => {
|
||||
window.rubick.dbStorage.setItem(
|
||||
'rubick-db-jg-webdav',
|
||||
JSON.parse(JSON.stringify(formState))
|
||||
);
|
||||
message.success('保存成功');
|
||||
showSetting.value = false;
|
||||
};
|
||||
|
||||
const showDetail = (key) => {
|
||||
show.value = true;
|
||||
detail.value = window.rubick.db.get(key);
|
||||
};
|
||||
|
||||
const exportData = () => {
|
||||
if (!formState.password || !formState.username) {
|
||||
return showSetting.value = true;
|
||||
}
|
||||
window.market.dbDump(JSON.parse(JSON.stringify(formState)));
|
||||
};
|
||||
|
||||
const importData = () => {
|
||||
if (!formState.password || !formState.username) {
|
||||
return showSetting.value = true;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '导入确认',
|
||||
content: '导入坚果云数据将会覆盖本地数据,是否确认导入?',
|
||||
onOk() {
|
||||
window.market.dbImport(JSON.parse(JSON.stringify(formState)));
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const openHelp = () => {
|
||||
window.rubick.shellOpenExternal('https://help.jianguoyun.com/?p=2064');
|
||||
};
|
||||
|
||||
const store = useStore();
|
||||
|
||||
const pluginsData = window.rubick.db.get('RUBICK_PLUGIN_INFO');
|
||||
|
||||
const totalPlugins = computed(() => store.state.totalPlugins);
|
||||
|
||||
const dataPlugins = computed(() => {
|
||||
if (!pluginsData) return [];
|
||||
return pluginsData.data.map((item) => {
|
||||
let plugin = null;
|
||||
if (['rubick-system-feature'].includes(item.name)) {
|
||||
plugin = {
|
||||
pluginName: '主程序',
|
||||
isdownload: true,
|
||||
logo: require('../../assets/logo.png'),
|
||||
};
|
||||
} else {
|
||||
plugin = totalPlugins.value.find((p) => p.name === item.name);
|
||||
}
|
||||
const data = item.keys.map((key) => window.rubick.db.get(key));
|
||||
return {
|
||||
...item,
|
||||
plugin,
|
||||
data,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const startDownload = (name) => store.dispatch('startDownload', name);
|
||||
const successDownload = (name) => store.dispatch('successDownload', name);
|
||||
|
||||
const downloadPlugin = async (plugin) => {
|
||||
startDownload(plugin.name);
|
||||
await window.market.downloadPlugin(plugin);
|
||||
message.success(t('feature.dev.installSuccess', { pluginName: plugin.name }));
|
||||
successDownload(plugin.name);
|
||||
};
|
||||
</script>
|
||||
<style lang="less">
|
||||
.openHelp {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.export-header {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
border-bottom: 1px solid #eee;
|
||||
text-align: right;
|
||||
}
|
||||
.key-item {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: var(--ant-primary-color);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -14,6 +14,12 @@
|
||||
</template>
|
||||
{{ $t('feature.settings.basic.title') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="localstart">
|
||||
<template #icon>
|
||||
<FolderOpenOutlined />
|
||||
</template>
|
||||
{{ $t('feature.settings.localstart.title') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="global">
|
||||
<template #icon>
|
||||
<LaptopOutlined />
|
||||
@@ -26,10 +32,16 @@
|
||||
<!-- </template>-->
|
||||
<!-- {{ $t('feature.settings.superPanel.title') }}-->
|
||||
<!-- </a-menu-item>-->
|
||||
<a-menu-item key="localhost">
|
||||
<a-menu-item key="database">
|
||||
<template #icon>
|
||||
<DatabaseOutlined />
|
||||
</template>
|
||||
{{ $t('feature.settings.database.title') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="localhost">
|
||||
<template #icon>
|
||||
<SafetyOutlined />
|
||||
</template>
|
||||
{{ $t('feature.settings.intranet.title') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
@@ -220,6 +232,8 @@
|
||||
</div>
|
||||
<SuperPanel v-if="currentSelect[0] === 'superpanel'" />
|
||||
<Localhost v-if="currentSelect[0] === 'localhost'" />
|
||||
<LocalStart v-if="currentSelect[0] === 'localstart'" />
|
||||
<DataBase v-if="currentSelect[0] === 'database'" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -232,6 +246,8 @@ import {
|
||||
MinusCircleOutlined,
|
||||
PlusCircleOutlined,
|
||||
UserOutlined,
|
||||
FolderOpenOutlined,
|
||||
SafetyOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
import debounce from 'lodash.debounce';
|
||||
import { ref, reactive, watch, toRefs, computed } from 'vue';
|
||||
@@ -239,6 +255,8 @@ import keycodes from './keycode';
|
||||
import Localhost from './localhost.vue';
|
||||
import SuperPanel from './super-panel.vue';
|
||||
import UserInfo from './user-info';
|
||||
import LocalStart from './local-start';
|
||||
import DataBase from './database';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import localConfig from '@/confOp';
|
||||
|
||||
|
||||
112
feature/src/views/settings/local-start.vue
Normal file
112
feature/src/views/settings/local-start.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<div class="file-container" @drop.prevent="dropFile" @dragenter="checkDrop" @dragover="checkDrop">
|
||||
<a-alert message="可拖放文件夹到这里加入启动" type="info" show-icon />
|
||||
<a-list item-layout="horizontal" :data-source="localStartList">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<template #actions>
|
||||
<a key="list-loadmore-edit" @click="() => remove(item)">移除</a>
|
||||
</template>
|
||||
<a-list-item-meta :description="item.desc">
|
||||
<template #title>
|
||||
<div>
|
||||
<span :class="item.del ? 'del-title' : ''">{{item.name}}</span>
|
||||
<span v-if="item.del" class="has-del">文件不存在</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #avatar>
|
||||
<a-avatar shape="square" :src="item.icon" />
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
const fs = window.require('fs');
|
||||
const process = window.require('process');
|
||||
|
||||
const dbId = 'rubick-local-start-app';
|
||||
|
||||
const localStartList = ref(window.rubick.dbStorage.getItem(dbId) || []);
|
||||
|
||||
const checkFileExists = () => {
|
||||
localStartList.value = localStartList.value.map((plugin) => {
|
||||
if (!fs.existsSync(plugin.desc)) {
|
||||
return {
|
||||
...plugin,
|
||||
del: true,
|
||||
};
|
||||
}
|
||||
return plugin;
|
||||
});
|
||||
};
|
||||
|
||||
checkFileExists();
|
||||
|
||||
const dropFile = (e) => {
|
||||
const files = Array.from(e.dataTransfer.files).map((file) => {
|
||||
const action =
|
||||
process.platform === 'win32'
|
||||
? `start "dummyclient" "${file.path}"`
|
||||
: `open ${file.path.replace(/ /g, '\\ ')}`;
|
||||
const plugin = {
|
||||
icon: window.rubick.getFileIcon(file.path),
|
||||
value: 'plugin',
|
||||
desc: file.path,
|
||||
pluginType: 'app',
|
||||
name: file.name,
|
||||
action,
|
||||
keyWords: [file.name],
|
||||
names: [file.name],
|
||||
};
|
||||
window.market.addLocalStartPlugin(plugin);
|
||||
return plugin;
|
||||
});
|
||||
localStartList.value = [
|
||||
...localStartList.value,
|
||||
...files,
|
||||
];
|
||||
window.rubick.dbStorage.setItem(
|
||||
dbId,
|
||||
JSON.parse(JSON.stringify(localStartList.value))
|
||||
);
|
||||
};
|
||||
|
||||
const remove = (item) => {
|
||||
localStartList.value = localStartList.value.filter(
|
||||
(app) => app.desc !== item.desc
|
||||
);
|
||||
window.rubick.dbStorage.setItem(
|
||||
dbId,
|
||||
JSON.parse(JSON.stringify(localStartList.value))
|
||||
);
|
||||
window.market.removeLocalStartPlugin(JSON.parse(JSON.stringify(item)));
|
||||
};
|
||||
|
||||
const checkDrop = (e) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.file-container {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
background: var(--color-body-bg);
|
||||
height: calc(~'100vh - 106px');
|
||||
.del-title {
|
||||
text-decoration-line: line-through;
|
||||
text-decoration-color: var(--ant-error-color);
|
||||
}
|
||||
.has-del {
|
||||
color: var(--ant-error-color);
|
||||
font-size: 12px;
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "rubick",
|
||||
"version": "3.0.1",
|
||||
"version": "3.2.3",
|
||||
"author": "muwoo <2424880409@qq.com>",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -31,11 +31,17 @@
|
||||
"get-mac-apps": "^1.0.2",
|
||||
"got": "^11.8.3",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"memorystream": "^0.3.1",
|
||||
"node-key-sender": "^1.0.11",
|
||||
"pinyin-match": "^1.2.4",
|
||||
"pouchdb": "^7.2.2",
|
||||
"pouchdb-load": "^1.4.6",
|
||||
"pouchdb-replication-stream": "^1.2.9",
|
||||
"simple-plist": "0.2.1",
|
||||
"vue": "^3.0.0",
|
||||
"vue-router": "^4.0.0-0",
|
||||
"vuex": "^4.0.0-0",
|
||||
"webdav": "4.11.3",
|
||||
"worker-loader": "^3.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "rubick-system-feature",
|
||||
"pluginName": "rubick 系统菜单",
|
||||
"description": "rubick 系统菜单",
|
||||
"pluginName": "系统菜单",
|
||||
"description": "系统菜单",
|
||||
"main": "index.html",
|
||||
"logo": "https://pic1.zhimg.com/80/v2-c09780808301668a82e6646cb42f0806_720w.png",
|
||||
"version": "0.0.0",
|
||||
@@ -10,19 +10,19 @@
|
||||
"features": [
|
||||
{
|
||||
"code": "market",
|
||||
"explain": "rubick 插件市场",
|
||||
"explain": "插件市场",
|
||||
"cmds":[
|
||||
"插件市场"
|
||||
]
|
||||
},{
|
||||
"code": "installed",
|
||||
"explain": "rubick 已安装插件",
|
||||
"explain": "已安装插件",
|
||||
"cmds":[
|
||||
"已安装插件"
|
||||
]
|
||||
},{
|
||||
"code": "settings",
|
||||
"explain": "rubick 偏好设置",
|
||||
"explain": "偏好设置",
|
||||
"cmds":[
|
||||
"偏好设置"
|
||||
]
|
||||
|
||||
@@ -1,4 +1,21 @@
|
||||
const remote = require('@electron/remote');
|
||||
const { ipcRenderer } = require('electron');
|
||||
|
||||
const ipcSendSync = (type, data) => {
|
||||
const returnValue = ipcRenderer.sendSync('msg-trigger', {
|
||||
type,
|
||||
data,
|
||||
});
|
||||
if (returnValue instanceof Error) throw returnValue;
|
||||
return returnValue;
|
||||
};
|
||||
|
||||
const ipcSend = (type, data) => {
|
||||
ipcRenderer.send('msg-trigger', {
|
||||
type,
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
window.market = {
|
||||
getLocalPlugins() {
|
||||
@@ -13,4 +30,17 @@ window.market = {
|
||||
refreshPlugin(plugin) {
|
||||
return remote.getGlobal('LOCAL_PLUGINS').refreshPlugin(plugin);
|
||||
},
|
||||
addLocalStartPlugin(plugin) {
|
||||
ipcSend('addLocalStartPlugin', { plugin });
|
||||
},
|
||||
removeLocalStartPlugin(plugin) {
|
||||
ipcSend('removeLocalStartPlugin', { plugin });
|
||||
},
|
||||
dbDump(target) {
|
||||
ipcSend('dbDump', { target });
|
||||
},
|
||||
|
||||
dbImport(target) {
|
||||
ipcSend('dbImport', { target });
|
||||
},
|
||||
};
|
||||
|
||||
13
public/installer.nsh
Normal file
13
public/installer.nsh
Normal file
@@ -0,0 +1,13 @@
|
||||
!macro customInstall
|
||||
SetRegView 64
|
||||
WriteRegStr HKCR "*\shell\rubick" "" "open w&ith rubick"
|
||||
WriteRegStr HKCR "*\shell\rubick" "Icon" "$INSTDIR\rubick.exe"
|
||||
WriteRegStr HKCR "*\shell\rubick\command" "" '"$INSTDIR\rubick.exe" "search" "%1"'
|
||||
SetRegView 32
|
||||
WriteRegStr HKCR "*\shell\rubick" "" "open w&ith rubick"
|
||||
WriteRegStr HKCR "*\shell\rubick" "Icon" "$INSTDIR\rubick.exe"
|
||||
WriteRegStr HKCR "*\shell\rubick\command" "" '"$INSTDIR\rubick.exe" "search" "%1"'
|
||||
!macroend
|
||||
!macro customUninstall
|
||||
DeleteRegKey HKCR "*\shell\rubick"
|
||||
!macroend
|
||||
31
public/rubick.workflow/Contents/Info.plist
Normal file
31
public/rubick.workflow/Contents/Info.plist
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSServices</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSBackgroundColorName</key>
|
||||
<string>background</string>
|
||||
<key>NSIconName</key>
|
||||
<string>NSTouchBarSend</string>
|
||||
<key>NSMenuItem</key>
|
||||
<dict>
|
||||
<key>default</key>
|
||||
<string>rubick 打开</string>
|
||||
</dict>
|
||||
<key>NSMessage</key>
|
||||
<string>runWorkflowAsService</string>
|
||||
<key>NSRequiredContext</key>
|
||||
<dict>
|
||||
<key>NSApplicationIdentifier</key>
|
||||
<string>com.apple.finder</string>
|
||||
</dict>
|
||||
<key>NSSendFileTypes</key>
|
||||
<array>
|
||||
<string>public.item</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
BIN
public/rubick.workflow/Contents/QuickLook/Thumbnail.png
Normal file
BIN
public/rubick.workflow/Contents/QuickLook/Thumbnail.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
226
public/rubick.workflow/Contents/document.wflow
Normal file
226
public/rubick.workflow/Contents/document.wflow
Normal file
@@ -0,0 +1,226 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>AMApplicationBuild</key>
|
||||
<string>512</string>
|
||||
<key>AMApplicationVersion</key>
|
||||
<string>2.10</string>
|
||||
<key>AMDocumentVersion</key>
|
||||
<string>2</string>
|
||||
<key>actions</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>action</key>
|
||||
<dict>
|
||||
<key>AMAccepts</key>
|
||||
<dict>
|
||||
<key>Container</key>
|
||||
<string>List</string>
|
||||
<key>Optional</key>
|
||||
<true/>
|
||||
<key>Types</key>
|
||||
<array>
|
||||
<string>com.apple.cocoa.string</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>AMActionVersion</key>
|
||||
<string>2.0.3</string>
|
||||
<key>AMApplication</key>
|
||||
<array>
|
||||
<string>自动操作</string>
|
||||
</array>
|
||||
<key>AMParameterProperties</key>
|
||||
<dict>
|
||||
<key>COMMAND_STRING</key>
|
||||
<dict/>
|
||||
<key>CheckedForUserDefaultShell</key>
|
||||
<dict/>
|
||||
<key>inputMethod</key>
|
||||
<dict/>
|
||||
<key>shell</key>
|
||||
<dict/>
|
||||
<key>source</key>
|
||||
<dict/>
|
||||
</dict>
|
||||
<key>AMProvides</key>
|
||||
<dict>
|
||||
<key>Container</key>
|
||||
<string>List</string>
|
||||
<key>Types</key>
|
||||
<array>
|
||||
<string>com.apple.cocoa.string</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>ActionBundlePath</key>
|
||||
<string>/System/Library/Automator/Run Shell Script.action</string>
|
||||
<key>ActionName</key>
|
||||
<string>运行Shell脚本</string>
|
||||
<key>ActionParameters</key>
|
||||
<dict>
|
||||
<key>COMMAND_STRING</key>
|
||||
<string>/Applications/rubick.app/Contents/MacOS/rubick search "$@" > /dev/null 2>&1 &</string>
|
||||
<key>CheckedForUserDefaultShell</key>
|
||||
<true/>
|
||||
<key>inputMethod</key>
|
||||
<integer>1</integer>
|
||||
<key>shell</key>
|
||||
<string>/bin/bash</string>
|
||||
<key>source</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
<key>BundleIdentifier</key>
|
||||
<string>com.apple.RunShellScript</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2.0.3</string>
|
||||
<key>CanShowSelectedItemsWhenRun</key>
|
||||
<false/>
|
||||
<key>CanShowWhenRun</key>
|
||||
<true/>
|
||||
<key>Category</key>
|
||||
<array>
|
||||
<string>AMCategoryUtilities</string>
|
||||
</array>
|
||||
<key>Class Name</key>
|
||||
<string>RunShellScriptAction</string>
|
||||
<key>InputUUID</key>
|
||||
<string>7E11290E-3E3F-4D65-988B-3E2A8B597EEF</string>
|
||||
<key>Keywords</key>
|
||||
<array>
|
||||
<string>Shell</string>
|
||||
<string>脚本</string>
|
||||
<string>命令</string>
|
||||
<string>运行</string>
|
||||
<string>Unix</string>
|
||||
</array>
|
||||
<key>OutputUUID</key>
|
||||
<string>475C56B3-F23A-4D39-BB41-4B00F76B096B</string>
|
||||
<key>UUID</key>
|
||||
<string>BBBABDAB-D65B-41BF-B1EB-03ACA5AF4714</string>
|
||||
<key>UnlocalizedApplications</key>
|
||||
<array>
|
||||
<string>Automator</string>
|
||||
</array>
|
||||
<key>arguments</key>
|
||||
<dict>
|
||||
<key>0</key>
|
||||
<dict>
|
||||
<key>default value</key>
|
||||
<integer>0</integer>
|
||||
<key>name</key>
|
||||
<string>inputMethod</string>
|
||||
<key>required</key>
|
||||
<string>0</string>
|
||||
<key>type</key>
|
||||
<string>0</string>
|
||||
<key>uuid</key>
|
||||
<string>0</string>
|
||||
</dict>
|
||||
<key>1</key>
|
||||
<dict>
|
||||
<key>default value</key>
|
||||
<false/>
|
||||
<key>name</key>
|
||||
<string>CheckedForUserDefaultShell</string>
|
||||
<key>required</key>
|
||||
<string>0</string>
|
||||
<key>type</key>
|
||||
<string>0</string>
|
||||
<key>uuid</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
<key>2</key>
|
||||
<dict>
|
||||
<key>default value</key>
|
||||
<string></string>
|
||||
<key>name</key>
|
||||
<string>source</string>
|
||||
<key>required</key>
|
||||
<string>0</string>
|
||||
<key>type</key>
|
||||
<string>0</string>
|
||||
<key>uuid</key>
|
||||
<string>2</string>
|
||||
</dict>
|
||||
<key>3</key>
|
||||
<dict>
|
||||
<key>default value</key>
|
||||
<string></string>
|
||||
<key>name</key>
|
||||
<string>COMMAND_STRING</string>
|
||||
<key>required</key>
|
||||
<string>0</string>
|
||||
<key>type</key>
|
||||
<string>0</string>
|
||||
<key>uuid</key>
|
||||
<string>3</string>
|
||||
</dict>
|
||||
<key>4</key>
|
||||
<dict>
|
||||
<key>default value</key>
|
||||
<string>/bin/sh</string>
|
||||
<key>name</key>
|
||||
<string>shell</string>
|
||||
<key>required</key>
|
||||
<string>0</string>
|
||||
<key>type</key>
|
||||
<string>0</string>
|
||||
<key>uuid</key>
|
||||
<string>4</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>isViewVisible</key>
|
||||
<integer>1</integer>
|
||||
<key>location</key>
|
||||
<string>309.000000:305.000000</string>
|
||||
<key>nibPath</key>
|
||||
<string>/System/Library/Automator/Run Shell Script.action/Contents/Resources/Base.lproj/main.nib</string>
|
||||
</dict>
|
||||
<key>isViewVisible</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>connectors</key>
|
||||
<dict/>
|
||||
<key>workflowMetaData</key>
|
||||
<dict>
|
||||
<key>applicationBundleID</key>
|
||||
<string>com.apple.finder</string>
|
||||
<key>applicationBundleIDsByPath</key>
|
||||
<dict>
|
||||
<key>/System/Library/CoreServices/Finder.app</key>
|
||||
<string>com.apple.finder</string>
|
||||
</dict>
|
||||
<key>applicationPath</key>
|
||||
<string>/System/Library/CoreServices/Finder.app</string>
|
||||
<key>applicationPaths</key>
|
||||
<array>
|
||||
<string>/System/Library/CoreServices/Finder.app</string>
|
||||
</array>
|
||||
<key>inputTypeIdentifier</key>
|
||||
<string>com.apple.Automator.fileSystemObject</string>
|
||||
<key>outputTypeIdentifier</key>
|
||||
<string>com.apple.Automator.nothing</string>
|
||||
<key>presentationMode</key>
|
||||
<integer>15</integer>
|
||||
<key>processesInput</key>
|
||||
<false/>
|
||||
<key>serviceApplicationBundleID</key>
|
||||
<string>com.apple.finder</string>
|
||||
<key>serviceApplicationPath</key>
|
||||
<string>/System/Library/CoreServices/Finder.app</string>
|
||||
<key>serviceInputTypeIdentifier</key>
|
||||
<string>com.apple.Automator.fileSystemObject</string>
|
||||
<key>serviceOutputTypeIdentifier</key>
|
||||
<string>com.apple.Automator.nothing</string>
|
||||
<key>serviceProcessesInput</key>
|
||||
<false/>
|
||||
<key>systemImageName</key>
|
||||
<string>NSTouchBarSend</string>
|
||||
<key>useAutomaticInputType</key>
|
||||
<false/>
|
||||
<key>workflowTypeIdentifier</key>
|
||||
<string>com.apple.Automator.servicesMenu</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,12 +1,14 @@
|
||||
const WINDOW_MAX_HEIGHT = 600;
|
||||
const WINDOW_MIN_HEIGHT = 60;
|
||||
const PRE_ITEM_HEIGHT = 60;
|
||||
const HISTORY_HEIGHT = 80;
|
||||
|
||||
export default (searchList: Array<any>): number => {
|
||||
if (!searchList) return WINDOW_MAX_HEIGHT;
|
||||
if (!searchList.length) return WINDOW_MIN_HEIGHT;
|
||||
return searchList.length * PRE_ITEM_HEIGHT + WINDOW_MIN_HEIGHT + 5 >
|
||||
export default (searchList: Array<any>, historyList): number => {
|
||||
const defaultHeight = historyList.length ? HISTORY_HEIGHT : 0;
|
||||
if (!searchList) return WINDOW_MAX_HEIGHT + defaultHeight;
|
||||
if (!searchList.length) return WINDOW_MIN_HEIGHT + defaultHeight;
|
||||
return searchList.length * PRE_ITEM_HEIGHT + WINDOW_MIN_HEIGHT >
|
||||
WINDOW_MAX_HEIGHT
|
||||
? WINDOW_MAX_HEIGHT
|
||||
: searchList.length * PRE_ITEM_HEIGHT + WINDOW_MIN_HEIGHT + 5;
|
||||
: searchList.length * PRE_ITEM_HEIGHT + WINDOW_MIN_HEIGHT;
|
||||
};
|
||||
|
||||
@@ -16,16 +16,7 @@ if (!exists) {
|
||||
|
||||
const isZhRegex = /[\u4e00-\u9fa5]/;
|
||||
|
||||
async function getAppIcon(
|
||||
appPath: string,
|
||||
nativeImage: {
|
||||
createThumbnailFromPath: (
|
||||
iconPath: string,
|
||||
size: { width: number; height: number }
|
||||
) => { toDataURL: () => string };
|
||||
},
|
||||
name: string
|
||||
) {
|
||||
async function getAppIcon(appPath: string, nativeImage: any, name: string) {
|
||||
try {
|
||||
const iconpath = path.join(icondir, `${name}.png`);
|
||||
const iconnone = path.join(icondir, `${name}.none`);
|
||||
@@ -33,52 +24,40 @@ async function getAppIcon(
|
||||
const existsnone = fs.existsSync(iconnone);
|
||||
if (exists) return true;
|
||||
if (existsnone) return false;
|
||||
const appName: string = appPath.split('/').pop() || '';
|
||||
const extname: string = path.extname(appName);
|
||||
const appSubStr: string = appName.split(extname)[0];
|
||||
const path1 = path.join(appPath, `/Contents/Resources/App.icns`);
|
||||
const path2 = path.join(appPath, `/Contents/Resources/AppIcon.icns`);
|
||||
const path3 = path.join(appPath, `/Contents/Resources/${appSubStr}.icns`);
|
||||
const path4 = path.join(
|
||||
appPath,
|
||||
`/Contents/Resources/${appSubStr.replace(' ', '')}.icns`
|
||||
);
|
||||
let iconPath: string = path1;
|
||||
if (fs.existsSync(path1)) {
|
||||
iconPath = path1;
|
||||
} else if (fs.existsSync(path2)) {
|
||||
iconPath = path2;
|
||||
} else if (fs.existsSync(path3)) {
|
||||
iconPath = path3;
|
||||
} else if (fs.existsSync(path4)) {
|
||||
iconPath = path4;
|
||||
} else {
|
||||
// 性能最低的方式
|
||||
const resourceList = fs.readdirSync(
|
||||
path.join(appPath, `/Contents/Resources`)
|
||||
);
|
||||
const iconName = resourceList.filter(
|
||||
(file) => path.extname(file) === '.icns'
|
||||
)[0];
|
||||
if (!iconName) {
|
||||
fs.writeFileSync(iconnone, '');
|
||||
return false;
|
||||
}
|
||||
iconPath = path.join(appPath, `/Contents/Resources/${iconName}`);
|
||||
}
|
||||
const img = await nativeImage.createThumbnailFromPath(iconPath, {
|
||||
width: 64,
|
||||
height: 64,
|
||||
});
|
||||
|
||||
const base64Data = img.toDataURL().replace(/^data:.+;base64,/, '"');
|
||||
|
||||
const result = Buffer.from(base64Data, 'base64');
|
||||
|
||||
fs.writeFile(iconpath, result, 'base64', () => {
|
||||
// todo
|
||||
});
|
||||
|
||||
// const appName: string = appPath.split('/').pop() || '';
|
||||
// const extname: string = path.extname(appName);
|
||||
// const appSubStr: string = appName.split(extname)[0];
|
||||
// const path1 = path.join(appPath, `/Contents/Resources/App.icns`);
|
||||
// const path2 = path.join(appPath, `/Contents/Resources/AppIcon.icns`);
|
||||
// const path3 = path.join(appPath, `/Contents/Resources/${appSubStr}.icns`);
|
||||
// const path4 = path.join(
|
||||
// appPath,
|
||||
// `/Contents/Resources/${appSubStr.replace(' ', '')}.icns`
|
||||
// );
|
||||
// let iconPath: string = path1;
|
||||
// if (fs.existsSync(path1)) {
|
||||
// iconPath = path1;
|
||||
// } else if (fs.existsSync(path2)) {
|
||||
// iconPath = path2;
|
||||
// } else if (fs.existsSync(path3)) {
|
||||
// iconPath = path3;
|
||||
// } else if (fs.existsSync(path4)) {
|
||||
// iconPath = path4;
|
||||
// } else {
|
||||
// // 性能最低的方式
|
||||
// const resourceList = fs.readdirSync(
|
||||
// path.join(appPath, `/Contents/Resources`)
|
||||
// );
|
||||
// const iconName = resourceList.filter(
|
||||
// (file) => path.extname(file) === '.icns'
|
||||
// )[0];
|
||||
// if (!iconName) {
|
||||
// fs.writeFileSync(iconnone, '');
|
||||
// return false;
|
||||
// }
|
||||
// iconPath = path.join(appPath, `/Contents/Resources/${iconName}`);
|
||||
// }
|
||||
await getMacApps.app2png(appPath, iconpath);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
@@ -120,12 +99,12 @@ export default async (nativeImage: any) => {
|
||||
};
|
||||
|
||||
if (app._name && isZhRegex.test(app._name)) {
|
||||
const [, pinyinArr] = translate(app._name);
|
||||
const firstLatter = pinyinArr.map((py) => py[0]);
|
||||
// 拼音
|
||||
fileOptions.keyWords.push(pinyinArr.join(''));
|
||||
// 缩写
|
||||
fileOptions.keyWords.push(firstLatter.join(''));
|
||||
// const [, pinyinArr] = translate(app._name);
|
||||
// const firstLatter = pinyinArr.map((py) => py[0]);
|
||||
// // 拼音
|
||||
// fileOptions.keyWords.push(pinyinArr.join(''));
|
||||
// // 缩写
|
||||
// fileOptions.keyWords.push(firstLatter.join(''));
|
||||
// 中文
|
||||
fileOptions.keyWords.push(app._name);
|
||||
}
|
||||
|
||||
109
src/core/app-search/get-mac-app/app2png.ts
Normal file
109
src/core/app-search/get-mac-app/app2png.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { exec } from 'child_process';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const plist = require('simple-plist');
|
||||
|
||||
const getIconFile = (appFileInput) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const plistPath = path.join(appFileInput, 'Contents', 'Info.plist');
|
||||
plist.readFile(plistPath, (err, data) => {
|
||||
if (err || !data.CFBundleIconFile) {
|
||||
return resolve(
|
||||
'/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericApplicationIcon.icns'
|
||||
);
|
||||
}
|
||||
const iconFile = path.join(
|
||||
appFileInput,
|
||||
'Contents',
|
||||
'Resources',
|
||||
data.CFBundleIconFile
|
||||
);
|
||||
const iconFiles = [iconFile, iconFile + '.icns', iconFile + '.tiff'];
|
||||
const existedIcon = iconFiles.find((iconFile) => {
|
||||
return fs.existsSync(iconFile);
|
||||
});
|
||||
resolve(
|
||||
existedIcon ||
|
||||
'/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericApplicationIcon.icns'
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// const sortIcons = (icons) => {
|
||||
// const aWins = -1;
|
||||
// const bWins = 1;
|
||||
// const catWins = 0;
|
||||
// return icons.sort((a, b) => {
|
||||
// const aSize = parseInt(a.match(/(\d+)x\1/)[1], 10);
|
||||
// const bSize = parseInt(b.match(/(\d+)x\1/)[1], 10);
|
||||
// if (aSize === bSize) {
|
||||
// if (a.indexOf('@2x') > -1) return aWins;
|
||||
// if (b.indexOf('@2x') > -1) return bWins;
|
||||
// return catWins;
|
||||
// }
|
||||
// if (aSize > bSize) return aWins;
|
||||
// if (aSize < bSize) return bWins;
|
||||
// return catWins;
|
||||
// });
|
||||
// };
|
||||
|
||||
// const icnsToPng = (iconFile, pngFileOutput) => {
|
||||
// const outputDir = pngFileOutput.split('.')[0] + '.iconset'
|
||||
// return new Promise((resolve, reject) => {
|
||||
// exec(`iconutil --convert iconset '${iconFile}' --output '${outputDir}'`, (error) => {
|
||||
// if (error) return reject(error)
|
||||
// fs.readdir(outputDir, (error, files) => {
|
||||
// if (error) {
|
||||
// return resolve(tiffToPng(iconFile, pngFileOutput))
|
||||
// }
|
||||
// const realIcons = files.map((file) => {
|
||||
// return path.join(outputDir, file)
|
||||
// })
|
||||
// const biggestIcon = sortIcons(realIcons).find(Boolean)
|
||||
// fs.rename(biggestIcon, pngFileOutput, (error) => {
|
||||
// error ? reject(error) : resolve(realIcons.filter((file) => {
|
||||
// return file !== biggestIcon
|
||||
// }))
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
// }).then((files) => {
|
||||
// // Cleanup temp icons
|
||||
// return Promise.all(files.map((file) => {
|
||||
// return new Promise((resolve, reject) => {
|
||||
// fs.unlink(file, (error) => {
|
||||
// error ? reject(error) : resolve()
|
||||
// })
|
||||
// })
|
||||
// }))
|
||||
// }).then(() => {
|
||||
// // Cleanup temp directory
|
||||
// return new Promise((resolve, reject) => {
|
||||
// fs.rmdir(outputDir, (error) => {
|
||||
// error ? reject(error) : resolve()
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
// }
|
||||
|
||||
const tiffToPng = (iconFile, pngFileOutput) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(
|
||||
`sips -s format png '${iconFile}' --out '${pngFileOutput}' --resampleHeightWidth 64 64`,
|
||||
(error) => {
|
||||
error ? reject(error) : resolve(null);
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const app2png = (appFileInput, pngFileOutput) => {
|
||||
return getIconFile(appFileInput).then((iconFile) => {
|
||||
return tiffToPng(iconFile, pngFileOutput);
|
||||
});
|
||||
};
|
||||
|
||||
export default app2png;
|
||||
@@ -1,4 +1,5 @@
|
||||
import getApps from "./getApps";
|
||||
import getApps from './getApps';
|
||||
import app2png from './app2png';
|
||||
|
||||
export default {
|
||||
getApps: () => {
|
||||
@@ -7,4 +8,5 @@ export default {
|
||||
isInstalled: (appName) => {
|
||||
return new Promise((resolve, reject) => getApps(resolve, reject, appName));
|
||||
},
|
||||
app2png,
|
||||
};
|
||||
|
||||
@@ -78,12 +78,12 @@ function fileDisplay(filePath) {
|
||||
keyWords.push(path.basename(appDetail.target, '.exe'));
|
||||
|
||||
if (isZhRegex.test(appName)) {
|
||||
const [, pinyinArr] = translate(appName);
|
||||
const zh_firstLatter = pinyinArr.map((py) => py[0]);
|
||||
// 拼音
|
||||
keyWords.push(pinyinArr.join(''));
|
||||
// const [, pinyinArr] = translate(appName);
|
||||
// const zh_firstLatter = pinyinArr.map((py) => py[0]);
|
||||
// // 拼音
|
||||
// keyWords.push(pinyinArr.join(''));
|
||||
// 缩写
|
||||
keyWords.push(zh_firstLatter.join(''));
|
||||
// keyWords.push(zh_firstLatter.join(''));
|
||||
} else {
|
||||
const firstLatter = appName
|
||||
.split(' ')
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import PouchDB from "pouchdb";
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import PouchDB from 'pouchdb';
|
||||
import { DBError, Doc, DocRes } from './types';
|
||||
import WebDavOP from './webdav';
|
||||
|
||||
import { DBError, Doc, DocRes } from "./types";
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const replicationStream = require('pouchdb-replication-stream/dist/pouchdb.replication-stream.min.js');
|
||||
// const load = require('pouchdb-load/dist/pouchdb.load.min.js');
|
||||
|
||||
export default class {
|
||||
PouchDB.plugin(replicationStream.plugin);
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const load = require('pouchdb-load');
|
||||
PouchDB.plugin({ loadIt: load.load });
|
||||
PouchDB.adapter('writableStream', replicationStream.adapters.writableStream);
|
||||
|
||||
export default class DB {
|
||||
readonly docMaxByteLength;
|
||||
readonly docAttachmentMaxByteLength;
|
||||
public dbpath;
|
||||
@@ -15,7 +25,7 @@ export default class {
|
||||
this.docMaxByteLength = 2 * 1024 * 1024; // 2M
|
||||
this.docAttachmentMaxByteLength = 20 * 1024 * 1024; // 20M
|
||||
this.dbpath = dbPath;
|
||||
this.defaultDbName = path.join(dbPath, "default");
|
||||
this.defaultDbName = path.join(dbPath, 'default');
|
||||
}
|
||||
|
||||
init(): void {
|
||||
@@ -24,11 +34,11 @@ export default class {
|
||||
}
|
||||
|
||||
getDocId(name: string, id: string): string {
|
||||
return name + "/" + id;
|
||||
return name + '/' + id;
|
||||
}
|
||||
|
||||
replaceDocId(name: string, id: string): string {
|
||||
return id.replace(name + "/", "");
|
||||
return id.replace(name + '/', '');
|
||||
}
|
||||
|
||||
errorInfo(name: string, message: string): DBError {
|
||||
@@ -38,7 +48,7 @@ export default class {
|
||||
private checkDocSize(doc: Doc<any>) {
|
||||
if (Buffer.byteLength(JSON.stringify(doc)) > this.docMaxByteLength) {
|
||||
return this.errorInfo(
|
||||
"exception",
|
||||
'exception',
|
||||
`doc max size ${this.docMaxByteLength / 1024 / 1024} M`
|
||||
);
|
||||
}
|
||||
@@ -78,15 +88,15 @@ export default class {
|
||||
async remove(name: string, doc: Doc<any> | string) {
|
||||
try {
|
||||
let target;
|
||||
if ("object" == typeof doc) {
|
||||
if ('object' == typeof doc) {
|
||||
target = doc;
|
||||
if (!target._id || "string" !== typeof target._id) {
|
||||
return this.errorInfo("exception", "doc _id error");
|
||||
if (!target._id || 'string' !== typeof target._id) {
|
||||
return this.errorInfo('exception', 'doc _id error');
|
||||
}
|
||||
target._id = this.getDocId(name, target._id);
|
||||
} else {
|
||||
if ("string" !== typeof doc) {
|
||||
return this.errorInfo("exception", "param error");
|
||||
if ('string' !== typeof doc) {
|
||||
return this.errorInfo('exception', 'param error');
|
||||
}
|
||||
target = await this.pouchDB.get(this.getDocId(name, doc));
|
||||
}
|
||||
@@ -94,7 +104,7 @@ export default class {
|
||||
target._id = result.id = this.replaceDocId(name, result.id);
|
||||
return result;
|
||||
} catch (e: any) {
|
||||
if ("object" === typeof doc) {
|
||||
if ('object' === typeof doc) {
|
||||
doc._id = this.replaceDocId(name, doc._id);
|
||||
}
|
||||
return this.errorInfo(e.name, e.message);
|
||||
@@ -107,11 +117,11 @@ export default class {
|
||||
): Promise<DBError | Array<DocRes>> {
|
||||
let result;
|
||||
try {
|
||||
if (!Array.isArray(docs)) return this.errorInfo("exception", "not array");
|
||||
if (!Array.isArray(docs)) return this.errorInfo('exception', 'not array');
|
||||
if (docs.find((e) => !e._id))
|
||||
return this.errorInfo("exception", "doc not _id field");
|
||||
return this.errorInfo('exception', 'doc not _id field');
|
||||
if (new Set(docs.map((e) => e._id)).size !== docs.length)
|
||||
return this.errorInfo("exception", "_id value exists as");
|
||||
return this.errorInfo('exception', '_id value exists as');
|
||||
for (const doc of docs) {
|
||||
const err = this.checkDocSize(doc);
|
||||
if (err) return err;
|
||||
@@ -144,20 +154,20 @@ export default class {
|
||||
): Promise<DBError | Array<DocRes>> {
|
||||
const config: any = { include_docs: true };
|
||||
if (key) {
|
||||
if ("string" == typeof key) {
|
||||
if ('string' == typeof key) {
|
||||
config.startkey = this.getDocId(name, key);
|
||||
config.endkey = config.startkey + "";
|
||||
config.endkey = config.startkey + '';
|
||||
} else {
|
||||
if (!Array.isArray(key))
|
||||
return this.errorInfo(
|
||||
"exception",
|
||||
"param only key(string) or keys(Array[string])"
|
||||
'exception',
|
||||
'param only key(string) or keys(Array[string])'
|
||||
);
|
||||
config.keys = key.map((key) => this.getDocId(name, key));
|
||||
}
|
||||
} else {
|
||||
config.startkey = this.getDocId(name, "");
|
||||
config.endkey = config.startkey + "";
|
||||
config.startkey = this.getDocId(name, '');
|
||||
config.endkey = config.startkey + '';
|
||||
}
|
||||
const result: Array<any> = [];
|
||||
try {
|
||||
@@ -172,4 +182,26 @@ export default class {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public async dumpDb(config: {
|
||||
url: string;
|
||||
username: string;
|
||||
password: string;
|
||||
}): Promise<void> {
|
||||
const webdavClient = new WebDavOP(config);
|
||||
webdavClient.createWriteStream(this.pouchDB);
|
||||
}
|
||||
|
||||
public async importDb(config: {
|
||||
url: string;
|
||||
username: string;
|
||||
password: string;
|
||||
}): Promise<void> {
|
||||
const webdavClient = new WebDavOP(config);
|
||||
await this.pouchDB.destroy();
|
||||
const syncDb = new DB(this.dbpath);
|
||||
syncDb.init();
|
||||
this.pouchDB = syncDb.pouchDB;
|
||||
await webdavClient.createReadStream(this.pouchDB);
|
||||
}
|
||||
}
|
||||
|
||||
92
src/core/db/webdav.ts
Normal file
92
src/core/db/webdav.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { Notification, app } from 'electron';
|
||||
import MemoryStream from 'memorystream';
|
||||
|
||||
import { createClient } from 'webdav';
|
||||
import { WebDAVClient } from 'webdav/dist/node/types';
|
||||
|
||||
type WebDavOptions = {
|
||||
username: string;
|
||||
password: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
type DBInstance = {
|
||||
loadIt: (stream: unknown) => void;
|
||||
dump: (stream: unknown) => void;
|
||||
};
|
||||
|
||||
export default class WebDav {
|
||||
public client: WebDAVClient;
|
||||
private cloudPath = '/rubick/db.txt';
|
||||
|
||||
constructor({ username, password, url }: WebDavOptions) {
|
||||
this.client = createClient(url, {
|
||||
username,
|
||||
password,
|
||||
});
|
||||
this.client
|
||||
.exists('/')
|
||||
.then((result) => {
|
||||
!result &&
|
||||
new Notification({
|
||||
title: '导出失败',
|
||||
body: 'webdav 连接失败',
|
||||
}).show();
|
||||
})
|
||||
.catch((r) => {
|
||||
new Notification({
|
||||
title: '导出失败',
|
||||
body: 'WebDav连接出错' + r,
|
||||
}).show();
|
||||
});
|
||||
}
|
||||
|
||||
async createWriteStream(dbInstance: DBInstance): Promise<void> {
|
||||
try {
|
||||
const result = await this.client.exists('/rubick');
|
||||
if (!result) {
|
||||
await this.client.createDirectory('/rubick');
|
||||
}
|
||||
} catch (e) {
|
||||
new Notification({
|
||||
title: '导出失败',
|
||||
body: 'WebDav目录创建出错:' + e,
|
||||
}).show();
|
||||
}
|
||||
const ws = new MemoryStream();
|
||||
dbInstance.dump(ws);
|
||||
ws.pipe(
|
||||
this.client.createWriteStream(this.cloudPath, {}, () => {
|
||||
new Notification({
|
||||
title: '已导出到坚果云',
|
||||
body: `文件目录为:${this.cloudPath}`,
|
||||
}).show();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async createReadStream(dbInstance: DBInstance): Promise<void> {
|
||||
try {
|
||||
const result = await this.client.exists(this.cloudPath);
|
||||
if (!result) {
|
||||
return new Notification({
|
||||
title: '导入失败',
|
||||
body: '请确认坚果云上已存在数据',
|
||||
}).show();
|
||||
}
|
||||
const str = await this.client.getFileContents(this.cloudPath, {
|
||||
format: 'text',
|
||||
});
|
||||
await dbInstance.loadIt(str);
|
||||
new Notification({
|
||||
title: '导入成功',
|
||||
body: '数据已导入到 rubick,主应用数据需重启后生效',
|
||||
}).show();
|
||||
} catch (e) {
|
||||
new Notification({
|
||||
title: '导入失败',
|
||||
body: 'WebDav目录导入出错:' + e,
|
||||
}).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { app, BrowserWindow, protocol, nativeTheme } from 'electron';
|
||||
import path from 'path';
|
||||
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib';
|
||||
import versonHandler from '../common/versionHandler';
|
||||
// import versonHandler from '../common/versionHandler';
|
||||
import localConfig from '@/main/common/initLocalConfig';
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
require('@electron/remote/main').initialize();
|
||||
@@ -55,7 +55,7 @@ export default () => {
|
||||
win.webContents.executeJavaScript(
|
||||
`window.rubick && window.rubick.hooks && typeof window.rubick.hooks.onShow === "function" && window.rubick.hooks.onShow()`
|
||||
);
|
||||
versonHandler.checkUpdate();
|
||||
// versonHandler.checkUpdate();
|
||||
// win.webContents.openDevTools();
|
||||
});
|
||||
|
||||
|
||||
@@ -9,8 +9,6 @@ import {
|
||||
screen,
|
||||
shell,
|
||||
} from 'electron';
|
||||
import { runner, detach } from '../browsers';
|
||||
import DBInstance from './db';
|
||||
import fs from 'fs';
|
||||
import { screenCapture } from '@/core';
|
||||
import plist from 'plist';
|
||||
@@ -20,11 +18,14 @@ import { DECODE_KEY } from '@/common/constans/main';
|
||||
import getCopyFiles from '@/common/utils/getCopyFiles';
|
||||
|
||||
import mainInstance from '../index';
|
||||
import { runner, detach } from '../browsers';
|
||||
import DBInstance from './db';
|
||||
import getWinPosition from './getWinPosition';
|
||||
|
||||
const runnerInstance = runner();
|
||||
const detachInstance = detach();
|
||||
|
||||
class API extends DBInstance {
|
||||
public currentPlugin: null | any = null;
|
||||
init(mainWindow: BrowserWindow) {
|
||||
// 响应 preload.js 事件
|
||||
ipcMain.on('msg-trigger', async (event, arg) => {
|
||||
@@ -65,6 +66,7 @@ class API extends DBInstance {
|
||||
const originWindow = this.getCurrentWindow(window, e);
|
||||
if (!originWindow) return;
|
||||
originWindow.setBounds({ x: x - mouseX, y: y - mouseY, width, height });
|
||||
getWinPosition.setPosition(x - mouseX, y - mouseY);
|
||||
}
|
||||
|
||||
public loadPlugin({ data: plugin }, window) {
|
||||
@@ -319,6 +321,22 @@ class API extends DBInstance {
|
||||
ks.sendKeys(keys);
|
||||
}
|
||||
}
|
||||
|
||||
public addLocalStartPlugin({ data: { plugin } }, window) {
|
||||
window.webContents.executeJavaScript(
|
||||
`window.addLocalStartPlugin(${JSON.stringify({
|
||||
plugin,
|
||||
})})`
|
||||
);
|
||||
}
|
||||
|
||||
public removeLocalStartPlugin({ data: { plugin } }, window) {
|
||||
window.webContents.executeJavaScript(
|
||||
`window.removeLocalStartPlugin(${JSON.stringify({
|
||||
plugin,
|
||||
})})`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default new API();
|
||||
|
||||
@@ -5,8 +5,29 @@ const dbInstance = new LocalDb(app.getPath('userData'));
|
||||
dbInstance.init();
|
||||
|
||||
export default class DBInstance {
|
||||
public currentPlugin: null | any = null;
|
||||
private DBKEY = 'RUBICK_DB_DEFAULT';
|
||||
public dbPut({ data }) {
|
||||
private DB_INFO_KET = 'RUBICK_PLUGIN_INFO';
|
||||
public async dbPut({ data }) {
|
||||
// 记录插件有哪些 dbkey,用于后续的数据同步
|
||||
if (this.currentPlugin && this.currentPlugin.name) {
|
||||
let dbInfo: any = await dbInstance.get(this.DBKEY, this.DB_INFO_KET);
|
||||
if (!dbInfo) {
|
||||
dbInfo = { data: [], _id: this.DB_INFO_KET };
|
||||
}
|
||||
const item = dbInfo.data.find(
|
||||
(it) => it.name === this.currentPlugin.name
|
||||
);
|
||||
if (item) {
|
||||
!item.keys.includes(data.data._id) && item.keys.push(data.data._id);
|
||||
} else {
|
||||
dbInfo.data.push({
|
||||
name: this.currentPlugin.name,
|
||||
keys: [data.data._id],
|
||||
});
|
||||
}
|
||||
dbInstance.put(this.DBKEY, dbInfo);
|
||||
}
|
||||
return dbInstance.put(this.DBKEY, data.data);
|
||||
}
|
||||
|
||||
@@ -25,4 +46,12 @@ export default class DBInstance {
|
||||
public dbAllDocs({ data }) {
|
||||
return dbInstance.allDocs(this.DBKEY, data.key);
|
||||
}
|
||||
|
||||
public dbDump({ data }) {
|
||||
return dbInstance.dumpDb(data.target);
|
||||
}
|
||||
|
||||
public dbImport({ data }) {
|
||||
return dbInstance.importDb(data.target);
|
||||
}
|
||||
}
|
||||
|
||||
84
src/main/common/getSearchFiles.ts
Normal file
84
src/main/common/getSearchFiles.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import path from 'path';
|
||||
import fs from 'fs-extra';
|
||||
import os from 'os';
|
||||
|
||||
const getSearchFiles = (argv = process.argv, cwd = process.cwd()) => {
|
||||
const files = argv.slice(2); // 过滤['rubick.exe', 'search']这两个参数,直接获取需要上传的图片路径
|
||||
let result: any = [];
|
||||
if (files.length > 0) {
|
||||
// 如果图片列表不为空
|
||||
result = files
|
||||
.map((item) => {
|
||||
if (path.isAbsolute(item)) {
|
||||
// 如果是绝对路径
|
||||
return {
|
||||
path: item,
|
||||
};
|
||||
} else {
|
||||
const tempPath = path.join(cwd, item); // 如果是相对路径,就拼接
|
||||
if (fs.existsSync(tempPath)) {
|
||||
// 判断文件是否存在
|
||||
return {
|
||||
path: tempPath,
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
})
|
||||
.filter((item) => item !== null); // 排除为null的路径
|
||||
}
|
||||
return result; // 返回结果
|
||||
};
|
||||
|
||||
const putFileToRubick = (webContents, files) => {
|
||||
webContents.executeJavaScript(`window.searchFocus(${JSON.stringify(files)})`);
|
||||
};
|
||||
|
||||
const copyFileOutsideOfElectronAsar = function (
|
||||
sourceInAsarArchive,
|
||||
destOutsideAsarArchive
|
||||
) {
|
||||
if (fs.existsSync(sourceInAsarArchive)) {
|
||||
// file will be copied
|
||||
if (fs.statSync(sourceInAsarArchive).isFile()) {
|
||||
const file = destOutsideAsarArchive;
|
||||
const dir = path.dirname(file);
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
|
||||
fs.writeFileSync(file, fs.readFileSync(sourceInAsarArchive));
|
||||
}
|
||||
|
||||
// dir is browsed
|
||||
else if (fs.statSync(sourceInAsarArchive).isDirectory()) {
|
||||
fs.readdirSync(sourceInAsarArchive).forEach(function (fileOrFolderName) {
|
||||
copyFileOutsideOfElectronAsar(
|
||||
sourceInAsarArchive + '/' + fileOrFolderName,
|
||||
destOutsideAsarArchive + '/' + fileOrFolderName
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const macBeforeOpen = () => {
|
||||
const dest = `${os.homedir}/Library/Services/rubick.workflow`;
|
||||
if (fs.existsSync(dest)) {
|
||||
// 判断是否存在
|
||||
return true;
|
||||
} else {
|
||||
// 如果不存在就复制过去
|
||||
try {
|
||||
copyFileOutsideOfElectronAsar(
|
||||
path.join(__static, 'rubick.workflow'),
|
||||
dest
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export { getSearchFiles, putFileToRubick, macBeforeOpen };
|
||||
34
src/main/common/getWinPosition.ts
Normal file
34
src/main/common/getWinPosition.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { screen } from 'electron';
|
||||
|
||||
const winPosition = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
id: -1,
|
||||
getPosition(): { x: number; y: number } {
|
||||
const { x, y } = screen.getCursorScreenPoint();
|
||||
const currentDisplay = screen.getDisplayNearestPoint({ x, y });
|
||||
if (winPosition.id !== currentDisplay.id) {
|
||||
winPosition.id = currentDisplay.id;
|
||||
winPosition.x = parseInt(
|
||||
String(
|
||||
currentDisplay.workArea.x + currentDisplay.workArea.width / 2 - 400
|
||||
)
|
||||
);
|
||||
winPosition.y = parseInt(
|
||||
String(
|
||||
currentDisplay.workArea.y + currentDisplay.workArea.height / 2 - 200
|
||||
)
|
||||
);
|
||||
}
|
||||
return {
|
||||
x: winPosition.x,
|
||||
y: winPosition.y,
|
||||
};
|
||||
},
|
||||
setPosition(x: number, y: number): void {
|
||||
winPosition.x = x;
|
||||
winPosition.y = y;
|
||||
},
|
||||
};
|
||||
|
||||
export default winPosition;
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from 'electron';
|
||||
import screenCapture from '@/core/screen-capture';
|
||||
import localConfig from '@/main/common/initLocalConfig';
|
||||
import winPosition from './getWinPosition';
|
||||
|
||||
const registerHotKey = (mainWindow: BrowserWindow): void => {
|
||||
// 设置开机启动
|
||||
@@ -56,20 +57,7 @@ const registerHotKey = (mainWindow: BrowserWindow): void => {
|
||||
globalShortcut.register(config.perf.shortCut.showAndHidden, () => {
|
||||
const currentShow = mainWindow.isVisible() && mainWindow.isFocused();
|
||||
if (currentShow) return mainWindow.hide();
|
||||
|
||||
const { x, y } = screen.getCursorScreenPoint();
|
||||
const currentDisplay = screen.getDisplayNearestPoint({ x, y });
|
||||
const wx = parseInt(
|
||||
String(
|
||||
currentDisplay.workArea.x + currentDisplay.workArea.width / 2 - 400
|
||||
)
|
||||
);
|
||||
const wy = parseInt(
|
||||
String(
|
||||
currentDisplay.workArea.y + currentDisplay.workArea.height / 2 - 200
|
||||
)
|
||||
);
|
||||
|
||||
const { x: wx, y: wy } = winPosition.getPosition();
|
||||
mainWindow.setAlwaysOnTop(false);
|
||||
mainWindow.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
|
||||
mainWindow.focus();
|
||||
|
||||
@@ -9,17 +9,23 @@ export default () => {
|
||||
let systemPlugins = totalPlugins.filter(
|
||||
(plugin) => plugin.pluginType === 'system'
|
||||
);
|
||||
systemPlugins = systemPlugins.map((plugin) => {
|
||||
const pluginPath = path.resolve(
|
||||
PLUGIN_INSTALL_DIR,
|
||||
'node_modules',
|
||||
plugin.name
|
||||
);
|
||||
return {
|
||||
...plugin,
|
||||
indexPath: path.join(pluginPath, './', plugin.entry),
|
||||
};
|
||||
});
|
||||
systemPlugins = systemPlugins
|
||||
.map((plugin) => {
|
||||
try {
|
||||
const pluginPath = path.resolve(
|
||||
PLUGIN_INSTALL_DIR,
|
||||
'node_modules',
|
||||
plugin.name
|
||||
);
|
||||
return {
|
||||
...plugin,
|
||||
indexPath: path.join(pluginPath, './', plugin.entry),
|
||||
};
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
const hooks = {
|
||||
onReady: [],
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { dialog } from 'electron';
|
||||
import { autoUpdater } from 'electron-updater';
|
||||
import pkg from '../../../package.json';
|
||||
import API from './api';
|
||||
import commonConst from '@/common/utils/commonConst';
|
||||
import { main } from '../browsers';
|
||||
import { app } from 'electron';
|
||||
|
||||
class VersionHandler {
|
||||
private lastestVersion: string;
|
||||
|
||||
@@ -13,6 +13,11 @@ import API from './common/api';
|
||||
import createTray from './common/tray';
|
||||
import registerHotKey from './common/registerHotKey';
|
||||
import localConfig from './common/initLocalConfig';
|
||||
import {
|
||||
getSearchFiles,
|
||||
putFileToRubick,
|
||||
macBeforeOpen,
|
||||
} from './common/getSearchFiles';
|
||||
|
||||
import '../common/utils/localPlugin';
|
||||
|
||||
@@ -41,6 +46,7 @@ class App {
|
||||
beforeReady() {
|
||||
// 系统托盘
|
||||
if (commonConst.macOS()) {
|
||||
macBeforeOpen();
|
||||
if (commonConst.production() && !app.isInApplicationsFolder()) {
|
||||
app.moveToApplicationsFolder();
|
||||
} else {
|
||||
@@ -83,14 +89,20 @@ class App {
|
||||
}
|
||||
|
||||
onRunning() {
|
||||
app.on('second-instance', () => {
|
||||
// 当运行第二个实例时,将会聚焦到myWindow这个窗口
|
||||
app.on('second-instance', (event, commandLine, workingDirectory) => {
|
||||
const files = getSearchFiles(commandLine, workingDirectory);
|
||||
const win = this.windowCreator.getWindow();
|
||||
// 当运行第二个实例时,将会聚焦到myWindow这个窗口
|
||||
// 如果有文件列表作为参数,说明是命令行启动
|
||||
if (win) {
|
||||
if (win.isMinimized()) {
|
||||
win.restore();
|
||||
}
|
||||
win.focus();
|
||||
if (files.length > 0) {
|
||||
win.show();
|
||||
putFileToRubick(win.webContents, files);
|
||||
}
|
||||
}
|
||||
});
|
||||
app.on('activate', () => {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
:searchValue="searchValue"
|
||||
:placeholder="placeholder"
|
||||
:pluginLoading="pluginLoading"
|
||||
:pluginHistory="pluginHistory"
|
||||
:clipboardFile="clipboardFile || []"
|
||||
@choosePlugin="choosePlugin"
|
||||
@focus="searchFocus"
|
||||
@@ -20,6 +21,7 @@
|
||||
@readClipboardContent="readClipboardContent"
|
||||
/>
|
||||
<Result
|
||||
:pluginHistory="pluginHistory"
|
||||
:currentPlugin="currentPlugin"
|
||||
:searchValue="searchValue"
|
||||
:currentSelect="currentSelect"
|
||||
@@ -37,7 +39,6 @@ import Search from './components/search.vue';
|
||||
import getWindowHeight from '../common/utils/getWindowHeight';
|
||||
import createPluginManager from './plugins-manager';
|
||||
import useDrag from '../common/utils/dragWindow';
|
||||
import commonConst from '@/common/utils/commonConst';
|
||||
|
||||
const { onMouseDown } = useDrag();
|
||||
const remote = window.require('@electron/remote');
|
||||
@@ -58,6 +59,7 @@ const {
|
||||
setSearchValue,
|
||||
clearClipboardFile,
|
||||
readClipboardContent,
|
||||
pluginHistory,
|
||||
} = createPluginManager();
|
||||
|
||||
initPlugins();
|
||||
@@ -74,24 +76,37 @@ getPluginInfo({
|
||||
remote.getGlobal('LOCAL_PLUGINS').addPlugin(res);
|
||||
});
|
||||
|
||||
watch([options], () => {
|
||||
watch([options, pluginHistory], () => {
|
||||
currentSelect.value = 0;
|
||||
if (currentPlugin.value.name) return;
|
||||
nextTick(() => {
|
||||
ipcRenderer.sendSync('msg-trigger', {
|
||||
type: 'setExpendHeight',
|
||||
data: getWindowHeight(options.value),
|
||||
data: getWindowHeight(options.value, pluginHistory.value),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const changeIndex = (index) => {
|
||||
if (!options.value.length) return;
|
||||
if (!options.value.length) {
|
||||
if (!pluginHistory.value.length) return;
|
||||
if (
|
||||
currentSelect.value + index > pluginHistory.value.length - 1 ||
|
||||
currentSelect.value + index < 0
|
||||
) {
|
||||
currentSelect.value = 0;
|
||||
return;
|
||||
}
|
||||
currentSelect.value = currentSelect.value + index;
|
||||
return;
|
||||
}
|
||||
if (
|
||||
currentSelect.value + index > options.value.length - 1 ||
|
||||
currentSelect.value + index < 0
|
||||
)
|
||||
) {
|
||||
currentSelect.value = 0;
|
||||
return;
|
||||
}
|
||||
currentSelect.value = currentSelect.value + index;
|
||||
};
|
||||
|
||||
@@ -101,14 +116,20 @@ const openMenu = (ext) => {
|
||||
feature: menuPluginInfo.value.features[0],
|
||||
cmd: '插件市场',
|
||||
ext,
|
||||
click: () => openMenu(ext),
|
||||
});
|
||||
};
|
||||
|
||||
window.rubick.openMenu = openMenu;
|
||||
|
||||
const choosePlugin = () => {
|
||||
const currentChoose = options.value[currentSelect.value];
|
||||
currentChoose.click();
|
||||
if (options.value.length) {
|
||||
const currentChoose = options.value[currentSelect.value];
|
||||
currentChoose.click();
|
||||
} else {
|
||||
const currentChoose = pluginHistory.value[currentSelect.value];
|
||||
currentChoose.click();
|
||||
}
|
||||
};
|
||||
|
||||
const clearSearchValue = () => {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// 背景色
|
||||
--color-body-bg: #fff;
|
||||
--color-menu-bg: #f3efef;
|
||||
--color-list-hover: #e2e2e2;
|
||||
--color-list-hover: #ebeee8;
|
||||
--color-input-hover: #fff;
|
||||
// 边框
|
||||
--color-border-light: #f0f0f0;
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
<template>
|
||||
<div
|
||||
v-show="
|
||||
!!options.length &&
|
||||
(searchValue || !!clipboardFile.length) &&
|
||||
!currentPlugin.name
|
||||
"
|
||||
v-show="!currentPlugin.name"
|
||||
class="options"
|
||||
ref="scrollDom"
|
||||
>
|
||||
<a-list item-layout="horizontal" :dataSource="sort(options)">
|
||||
<div class="history-plugins" v-if="!options.length || !(searchValue || !!clipboardFile.length)">
|
||||
<a-row>
|
||||
<a-col
|
||||
@click="() => item.click()"
|
||||
:class="currentSelect === index ? 'active history-item' : 'history-item'"
|
||||
:span="3"
|
||||
v-for="(item, index) in pluginHistory"
|
||||
:key="index"
|
||||
>
|
||||
<a-avatar style="border-radius: 0" :src="item.icon" />
|
||||
<div class="name ellpise">{{item.pluginName || item._name || item.name}}</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<a-list v-else item-layout="horizontal" :dataSource="sort(options)">
|
||||
<template #renderItem="{ item, index }">
|
||||
<a-list-item
|
||||
@click="() => item.click()"
|
||||
@@ -16,7 +26,7 @@
|
||||
>
|
||||
<a-list-item-meta :description="renderDesc(item.desc)">
|
||||
<template #title>
|
||||
<span v-html="renderTitle(item.name)"></span>
|
||||
<span v-html="renderTitle(item.name, item.match)"></span>
|
||||
</template>
|
||||
<template #avatar>
|
||||
<a-avatar style="border-radius: 0" :src="item.icon" />
|
||||
@@ -52,18 +62,15 @@ const props = defineProps({
|
||||
default: 0,
|
||||
},
|
||||
currentPlugin: {},
|
||||
pluginHistory: (() => [])(),
|
||||
clipboardFile: (() => [])(),
|
||||
});
|
||||
|
||||
const renderTitle = (title) => {
|
||||
const renderTitle = (title, match) => {
|
||||
if (typeof title !== 'string') return;
|
||||
if (!props.searchValue) return title;
|
||||
const result = title.toLowerCase().split(props.searchValue.toLowerCase());
|
||||
if (result && result.length > 1) {
|
||||
return `<div>${result[0]}<span style='color: var(--ant-error-color)'>${props.searchValue}</span>${result[1]}</div>`;
|
||||
} else {
|
||||
return `<div>${result[0]}</div>`;
|
||||
}
|
||||
if (!props.searchValue || !match) return title;
|
||||
const result = title.substring(match[0], match[1] + 1);
|
||||
return `<div>${title.substring(0, match[0])}<span style='color: var(--ant-error-color)'>${result}</span>${title.substring(match[1]+1, title.length)}</div>`;
|
||||
};
|
||||
|
||||
const renderDesc = (desc) => {
|
||||
@@ -91,15 +98,45 @@ const sort = (options) => {
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.ellpise {
|
||||
overflow:hidden;
|
||||
text-overflow:ellipsis;
|
||||
display:-webkit-box;
|
||||
-webkit-line-clamp:1;
|
||||
-webkit-box-orient:vertical;
|
||||
}
|
||||
|
||||
.options {
|
||||
position: absolute;
|
||||
top: 62px;
|
||||
top: 60px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 99;
|
||||
max-height: calc(~'100vh - 64px');
|
||||
max-height: calc(~'100vh - 60px');
|
||||
overflow: auto;
|
||||
background: var(--color-body-bg);
|
||||
.history-plugins {
|
||||
width: 100%;
|
||||
border-top: 1px dashed #ddd;
|
||||
box-sizing: border-box;
|
||||
.history-item {
|
||||
box-sizing: border-box;
|
||||
height: 79px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
border-right: 1px dashed #ddd;
|
||||
&.active {
|
||||
background: var(--color-list-hover);
|
||||
}
|
||||
}
|
||||
.name {
|
||||
margin-top: 4px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
.op-item {
|
||||
padding: 0 10px;
|
||||
height: 60px;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
class="main-input"
|
||||
@input="e => changeValue(e)"
|
||||
@keydown.down="e => keydownEvent(e, 'down')"
|
||||
@keydown.tab="e => keydownEvent(e, 'down')"
|
||||
@keydown.up="e => keydownEvent(e, 'up')"
|
||||
@keydown="e => checkNeedInit(e)"
|
||||
:value="searchValue"
|
||||
@@ -58,7 +59,7 @@ import { LoadingOutlined, MoreOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
const remote = window.require('@electron/remote');
|
||||
import localConfig from '../confOp';
|
||||
const { Menu } = remote;
|
||||
const { Menu, app } = remote;
|
||||
|
||||
const config: any = ref(localConfig.getConfig());
|
||||
|
||||
@@ -71,6 +72,7 @@ const props: any = defineProps({
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
pluginHistory: (() => [])(),
|
||||
currentPlugin: {},
|
||||
pluginLoading: Boolean,
|
||||
clipboardFile: (() => [])(),
|
||||
@@ -107,7 +109,7 @@ const keydownEvent = (e, key: string) => {
|
||||
modifiers,
|
||||
},
|
||||
});
|
||||
const runPluginDisable = e.target.value === '' || props.currentPlugin.name;
|
||||
const runPluginDisable = ((e.target.value === '' && !props.pluginHistory.length) || props.currentPlugin.name) ;
|
||||
switch (key) {
|
||||
case 'up':
|
||||
emit('changeCurrent', -1);
|
||||
@@ -230,9 +232,14 @@ 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');
|
||||
try {
|
||||
return ipcRenderer.sendSync('msg-trigger', {
|
||||
type: 'getFileIcon',
|
||||
data: { path: props.clipboardFile[0].path },
|
||||
});
|
||||
} catch (e) {
|
||||
return require('../assets/file.png');
|
||||
}
|
||||
};
|
||||
|
||||
const newWindow = () => {
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
Avatar,
|
||||
Tag,
|
||||
ConfigProvider,
|
||||
Row,
|
||||
Col,
|
||||
} from 'ant-design-vue';
|
||||
import App from './App.vue';
|
||||
import localConfig from './confOp';
|
||||
@@ -26,4 +28,6 @@ createApp(App)
|
||||
.use(Input)
|
||||
.use(Avatar)
|
||||
.use(Tag)
|
||||
.use(Row)
|
||||
.use(Col)
|
||||
.mount('#app');
|
||||
|
||||
@@ -8,13 +8,13 @@ import { ref } from 'vue';
|
||||
|
||||
export default ({ currentPlugin, optionsRef, openPlugin, setOptionsRef }) => {
|
||||
const clipboardFile: any = ref([]);
|
||||
const searchFocus = () => {
|
||||
const searchFocus = (files) => {
|
||||
const config: any = localConfig.getConfig();
|
||||
// 未开启自动粘贴
|
||||
if (!config.perf.common.autoPast) return;
|
||||
|
||||
if (currentPlugin.value.name) return;
|
||||
const fileList = getCopyFiles();
|
||||
const fileList = files || getCopyFiles();
|
||||
// 拷贝的是文件
|
||||
if (fileList) {
|
||||
window.setSubInputValue({ value: '' });
|
||||
@@ -43,74 +43,79 @@ export default ({ currentPlugin, optionsRef, openPlugin, setOptionsRef }) => {
|
||||
}
|
||||
|
||||
// 再正则插件
|
||||
localPlugins.forEach((plugin) => {
|
||||
const feature = plugin.features;
|
||||
// 系统插件无 features 的情况,不需要再搜索
|
||||
if (!feature) return;
|
||||
feature.forEach((fe) => {
|
||||
const ext = path.extname(fileList[0].path);
|
||||
fe.cmds.forEach((cmd) => {
|
||||
const regImg = /\.(png|jpg|gif|jpeg|webp)$/;
|
||||
if (
|
||||
cmd.type === 'img' &&
|
||||
regImg.test(ext) &&
|
||||
fileList.length === 1
|
||||
) {
|
||||
options.push({
|
||||
name: cmd.label,
|
||||
value: 'plugin',
|
||||
icon: plugin.logo,
|
||||
desc: fe.explain,
|
||||
type: plugin.pluginType,
|
||||
click: () => {
|
||||
pluginClickEvent({
|
||||
plugin,
|
||||
fe,
|
||||
cmd,
|
||||
ext: {
|
||||
code: fe.code,
|
||||
type: cmd.type || 'text',
|
||||
payload: nativeImage
|
||||
.createFromPath(fileList[0].path)
|
||||
.toDataURL(),
|
||||
},
|
||||
openPlugin,
|
||||
});
|
||||
clearClipboardFile();
|
||||
},
|
||||
});
|
||||
}
|
||||
// 如果是文件,且符合文件正则类型
|
||||
if (
|
||||
fileList.length > 1 ||
|
||||
(cmd.type === 'file' && new RegExp(cmd.match).test(ext))
|
||||
) {
|
||||
options.push({
|
||||
name: cmd,
|
||||
value: 'plugin',
|
||||
icon: plugin.logo,
|
||||
desc: fe.explain,
|
||||
type: plugin.pluginType,
|
||||
click: () => {
|
||||
pluginClickEvent({
|
||||
plugin,
|
||||
fe,
|
||||
cmd,
|
||||
ext: {
|
||||
code: fe.code,
|
||||
type: cmd.type || 'text',
|
||||
payload: fileList,
|
||||
},
|
||||
openPlugin,
|
||||
});
|
||||
clearClipboardFile();
|
||||
},
|
||||
});
|
||||
}
|
||||
if (fileList.length === 1) {
|
||||
localPlugins.forEach((plugin) => {
|
||||
const feature = plugin.features;
|
||||
// 系统插件无 features 的情况,不需要再搜索
|
||||
if (!feature) return;
|
||||
feature.forEach((fe) => {
|
||||
const ext = path.extname(fileList[0].path);
|
||||
fe.cmds.forEach((cmd) => {
|
||||
const regImg = /\.(png|jpg|gif|jpeg|webp)$/;
|
||||
if (
|
||||
cmd.type === 'img' &&
|
||||
regImg.test(ext) &&
|
||||
fileList.length === 1
|
||||
) {
|
||||
const option = {
|
||||
name: cmd.label,
|
||||
value: 'plugin',
|
||||
icon: plugin.logo,
|
||||
desc: fe.explain,
|
||||
type: plugin.pluginType,
|
||||
click: () => {
|
||||
pluginClickEvent({
|
||||
plugin,
|
||||
fe,
|
||||
cmd,
|
||||
ext: {
|
||||
code: fe.code,
|
||||
type: cmd.type || 'text',
|
||||
payload: nativeImage
|
||||
.createFromPath(fileList[0].path)
|
||||
.toDataURL(),
|
||||
},
|
||||
openPlugin,
|
||||
option,
|
||||
});
|
||||
clearClipboardFile();
|
||||
},
|
||||
};
|
||||
options.push(option);
|
||||
}
|
||||
// 如果是文件,且符合文件正则类型
|
||||
if (
|
||||
fileList.length > 1 ||
|
||||
(cmd.type === 'file' && new RegExp(cmd.match).test(ext))
|
||||
) {
|
||||
const option = {
|
||||
name: cmd,
|
||||
value: 'plugin',
|
||||
icon: plugin.logo,
|
||||
desc: fe.explain,
|
||||
type: plugin.pluginType,
|
||||
click: () => {
|
||||
pluginClickEvent({
|
||||
plugin,
|
||||
fe,
|
||||
cmd,
|
||||
option,
|
||||
ext: {
|
||||
code: fe.code,
|
||||
type: cmd.type || 'text',
|
||||
payload: fileList,
|
||||
},
|
||||
openPlugin,
|
||||
});
|
||||
clearClipboardFile();
|
||||
},
|
||||
};
|
||||
options.push(option);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
setOptionsRef(options);
|
||||
clipboard.clear();
|
||||
return;
|
||||
@@ -155,7 +160,7 @@ export default ({ currentPlugin, optionsRef, openPlugin, setOptionsRef }) => {
|
||||
feature.forEach((fe) => {
|
||||
fe.cmds.forEach((cmd) => {
|
||||
if (cmd.type === 'img') {
|
||||
options.push({
|
||||
const option = {
|
||||
name: cmd.label,
|
||||
value: 'plugin',
|
||||
icon: plugin.logo,
|
||||
@@ -172,10 +177,12 @@ export default ({ currentPlugin, optionsRef, openPlugin, setOptionsRef }) => {
|
||||
payload: dataUrl,
|
||||
},
|
||||
openPlugin,
|
||||
option,
|
||||
});
|
||||
clearClipboardFile();
|
||||
},
|
||||
});
|
||||
};
|
||||
options.push(option);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ import { execSync } from 'child_process';
|
||||
import searchManager from './search';
|
||||
import optionsManager from './options';
|
||||
import { PLUGIN_INSTALL_DIR as baseDir } from '@/common/constans/renderer';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
const createPluginManager = (): any => {
|
||||
const pluginInstance = new PluginHandler({
|
||||
@@ -21,12 +22,35 @@ const createPluginManager = (): any => {
|
||||
localPlugins: [],
|
||||
currentPlugin: {},
|
||||
pluginLoading: false,
|
||||
pluginHistory: [],
|
||||
});
|
||||
|
||||
const appList = ref([]);
|
||||
const appList: any = ref([]);
|
||||
|
||||
const initPlugins = async () => {
|
||||
appList.value = await appSearch(nativeImage);
|
||||
initLocalStartPlugin();
|
||||
};
|
||||
|
||||
const initLocalStartPlugin = () => {
|
||||
const result = ipcRenderer.sendSync('msg-trigger', {
|
||||
type: 'dbGet',
|
||||
data: {
|
||||
id: 'rubick-local-start-app',
|
||||
},
|
||||
});
|
||||
if (result && result.value) {
|
||||
appList.value.push(...result.value);
|
||||
}
|
||||
};
|
||||
|
||||
window.removeLocalStartPlugin = ({ plugin }) => {
|
||||
appList.value = appList.value.filter((app) => app.desc !== plugin.desc);
|
||||
};
|
||||
|
||||
window.addLocalStartPlugin = ({ plugin }) => {
|
||||
window.removeLocalStartPlugin({ plugin });
|
||||
appList.value.push(plugin);
|
||||
};
|
||||
|
||||
const loadPlugin = async (plugin) => {
|
||||
@@ -43,7 +67,7 @@ const createPluginManager = (): any => {
|
||||
state.pluginLoading = false;
|
||||
};
|
||||
|
||||
const openPlugin = async (plugin) => {
|
||||
const openPlugin = async (plugin, option) => {
|
||||
if (plugin.pluginType === 'ui' || plugin.pluginType === 'system') {
|
||||
if (state.currentPlugin && state.currentPlugin.name === plugin.name) {
|
||||
return;
|
||||
@@ -64,8 +88,29 @@ const createPluginManager = (): any => {
|
||||
});
|
||||
}
|
||||
if (plugin.pluginType === 'app') {
|
||||
execSync(plugin.action);
|
||||
try {
|
||||
execSync(plugin.action);
|
||||
} catch (e) {
|
||||
message.error('启动应用出错,请确保启动应用存在!');
|
||||
}
|
||||
}
|
||||
window.initRubick();
|
||||
changePluginHistory({
|
||||
...plugin,
|
||||
...option,
|
||||
});
|
||||
};
|
||||
|
||||
const changePluginHistory = (plugin) => {
|
||||
if (state.pluginHistory.length >= 8) {
|
||||
state.pluginHistory.pop();
|
||||
}
|
||||
state.pluginHistory.forEach((p, index) => {
|
||||
if (p.name === plugin.name) {
|
||||
state.pluginHistory.splice(index, 1);
|
||||
}
|
||||
});
|
||||
state.pluginHistory.unshift(plugin);
|
||||
};
|
||||
|
||||
const { searchValue, onSearch, setSearchValue, placeholder } =
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ref, watch } from 'vue';
|
||||
import throttle from 'lodash.throttle';
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { getGlobal } from '@electron/remote';
|
||||
import PinyinMatch from 'pinyin-match';
|
||||
import pluginClickEvent from './pluginClickEvent';
|
||||
import useFocus from './clipboardWatch';
|
||||
|
||||
@@ -14,7 +15,7 @@ function formatReg(regStr) {
|
||||
function searchKeyValues(lists, value, strict = false) {
|
||||
return lists.filter((item) => {
|
||||
if (typeof item === 'string') {
|
||||
return item.toLowerCase().indexOf(value.toLowerCase()) >= 0;
|
||||
return !!PinyinMatch.match(item, value);
|
||||
}
|
||||
if (item.type === 'regex' && !strict) {
|
||||
return formatReg(item.match).test(value);
|
||||
@@ -52,29 +53,34 @@ const optionsManager = ({
|
||||
const cmds = searchKeyValues(fe.cmds, value, strict);
|
||||
options = [
|
||||
...options,
|
||||
...cmds.map((cmd) => ({
|
||||
name: cmd.label || cmd,
|
||||
value: 'plugin',
|
||||
icon: plugin.logo,
|
||||
desc: fe.explain,
|
||||
type: plugin.pluginType,
|
||||
zIndex: cmd.label ? 0 : 1, // 排序权重
|
||||
click: () => {
|
||||
pluginClickEvent({
|
||||
plugin,
|
||||
fe,
|
||||
cmd,
|
||||
ext: cmd.type
|
||||
? {
|
||||
code: fe.code,
|
||||
type: cmd.type || 'text',
|
||||
payload: searchValue.value,
|
||||
}
|
||||
: null,
|
||||
openPlugin,
|
||||
});
|
||||
},
|
||||
})),
|
||||
...cmds.map((cmd) => {
|
||||
const option = {
|
||||
name: cmd.label || cmd,
|
||||
value: 'plugin',
|
||||
icon: plugin.logo,
|
||||
desc: fe.explain,
|
||||
type: plugin.pluginType,
|
||||
match: PinyinMatch.match(cmd.label || cmd, value),
|
||||
zIndex: cmd.label ? 0 : 1, // 排序权重
|
||||
click: () => {
|
||||
pluginClickEvent({
|
||||
plugin,
|
||||
fe,
|
||||
cmd,
|
||||
ext: cmd.type
|
||||
? {
|
||||
code: fe.code,
|
||||
type: cmd.type || 'text',
|
||||
payload: searchValue.value,
|
||||
}
|
||||
: null,
|
||||
openPlugin,
|
||||
option,
|
||||
});
|
||||
},
|
||||
};
|
||||
return option;
|
||||
}),
|
||||
];
|
||||
});
|
||||
});
|
||||
@@ -89,13 +95,16 @@ const optionsManager = ({
|
||||
descMap.set(plugin, true);
|
||||
let has = false;
|
||||
plugin.keyWords.some((keyWord) => {
|
||||
const match = PinyinMatch.match(keyWord, value);
|
||||
if (
|
||||
keyWord
|
||||
.toLocaleUpperCase()
|
||||
.indexOf(value.toLocaleUpperCase()) >= 0
|
||||
// keyWord
|
||||
// .toLocaleUpperCase()
|
||||
// .indexOf(value.toLocaleUpperCase()) >= 0 ||
|
||||
match
|
||||
) {
|
||||
has = keyWord;
|
||||
plugin.name = keyWord;
|
||||
plugin.match = match;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -106,13 +115,14 @@ const optionsManager = ({
|
||||
}
|
||||
})
|
||||
.map((plugin) => {
|
||||
return {
|
||||
const option = {
|
||||
...plugin,
|
||||
zIndex: 1,
|
||||
click: () => {
|
||||
openPlugin(plugin);
|
||||
openPlugin(plugin, option);
|
||||
},
|
||||
};
|
||||
return option;
|
||||
}),
|
||||
];
|
||||
return options;
|
||||
@@ -146,6 +156,8 @@ const optionsManager = ({
|
||||
setOptionsRef,
|
||||
});
|
||||
|
||||
window.searchFocus = searchFocus;
|
||||
|
||||
return {
|
||||
options: optionsRef,
|
||||
searchFocus,
|
||||
|
||||
@@ -3,7 +3,14 @@ import path from 'path';
|
||||
import { toRaw } from 'vue';
|
||||
import commonConst from '@/common/utils/commonConst';
|
||||
|
||||
export default function pluginClickEvent({ plugin, fe, cmd, ext, openPlugin }) {
|
||||
export default function pluginClickEvent({
|
||||
plugin,
|
||||
fe,
|
||||
cmd,
|
||||
ext,
|
||||
openPlugin,
|
||||
option,
|
||||
}) {
|
||||
const pluginPath = path.resolve(baseDir, 'node_modules', plugin.name);
|
||||
const pluginDist = {
|
||||
...toRaw(plugin),
|
||||
@@ -24,5 +31,5 @@ export default function pluginClickEvent({ plugin, fe, cmd, ext, openPlugin }) {
|
||||
? 'http://localhost:8081/#/'
|
||||
: `file://${__static}/feature/index.html`;
|
||||
}
|
||||
openPlugin(pluginDist);
|
||||
openPlugin(pluginDist, option);
|
||||
}
|
||||
|
||||
3
src/renderer/shims-vue.d.ts
vendored
3
src/renderer/shims-vue.d.ts
vendored
@@ -21,7 +21,10 @@ interface Window {
|
||||
loadPlugin: (plugin: any) => void;
|
||||
updatePlugin: (plugin: any) => void;
|
||||
initRubick: () => void;
|
||||
addLocalStartPlugin: (plugin: any) => void;
|
||||
removeLocalStartPlugin: (plugin: any) => void;
|
||||
setCurrentPlugin: (plugin: any) => void;
|
||||
pluginLoaded: () => void;
|
||||
getMainInputInfo: () => any;
|
||||
searchFocus: (args: any) => any;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ module.exports = {
|
||||
],
|
||||
// Use this to change the entry point of your app's render process. default src/[main|index].[js|ts]
|
||||
builderOptions: {
|
||||
productName: 'rubick2',
|
||||
productName: 'rubick',
|
||||
appId: 'com.muwoo.rubick',
|
||||
compression: 'maximum',
|
||||
directories: {
|
||||
@@ -67,7 +67,13 @@ module.exports = {
|
||||
},
|
||||
mac: {
|
||||
icon: 'public/icons/icon.icns',
|
||||
target: ['dmg', 'zip'],
|
||||
target: [
|
||||
{
|
||||
target: 'dmg',
|
||||
arch: ['x64', 'arm64'],
|
||||
},
|
||||
],
|
||||
artifactName: 'rubick-${version}-${arch}.dmg',
|
||||
gatekeeperAssess: false,
|
||||
entitlementsInherit: './release/entitlements.mac.plist',
|
||||
entitlements: './release/entitlements.mac.plist',
|
||||
@@ -79,11 +85,19 @@ module.exports = {
|
||||
},
|
||||
win: {
|
||||
icon: 'public/icons/icon.ico',
|
||||
target: 'nsis',
|
||||
artifactName: 'rubick-Setup-${version}-${arch}.exe',
|
||||
target: [
|
||||
{
|
||||
target: 'nsis',
|
||||
arch: ['x64', 'ia32'],
|
||||
},
|
||||
],
|
||||
},
|
||||
nsis: {
|
||||
shortcutName: 'rubick',
|
||||
oneClick: false,
|
||||
allowToChangeInstallationDirectory: true,
|
||||
include: 'public/installer.nsh',
|
||||
},
|
||||
linux: {
|
||||
icon: 'public/icons/',
|
||||
|
||||
Reference in New Issue
Block a user