mirror of
https://github.com/rubickCenter/rubick
synced 2025-12-25 03:49:26 +08:00
✨ 支持文件检索呼起插件
This commit is contained in:
@@ -10,7 +10,10 @@
|
||||
:searchValue="searchValue"
|
||||
:placeholder="placeholder"
|
||||
:pluginLoading="pluginLoading"
|
||||
:clipboardFile="clipboardFile || []"
|
||||
@choosePlugin="choosePlugin"
|
||||
@focus="searchFocus"
|
||||
@clearClipbord="clearClipboardFile"
|
||||
/>
|
||||
</div>
|
||||
<Result
|
||||
@@ -18,6 +21,7 @@
|
||||
:searchValue="searchValue"
|
||||
:currentSelect="currentSelect"
|
||||
:options="options"
|
||||
:clipboardFile="clipboardFile || []"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -41,6 +45,9 @@ const {
|
||||
currentPlugin,
|
||||
placeholder,
|
||||
pluginLoading,
|
||||
searchFocus,
|
||||
clipboardFile,
|
||||
clearClipboardFile,
|
||||
} = createPluginManager();
|
||||
|
||||
initPlugins();
|
||||
@@ -57,13 +64,13 @@ getPluginInfo({
|
||||
remote.getGlobal("LOCAL_PLUGINS").addPlugin(res);
|
||||
});
|
||||
|
||||
watch([searchValue], () => {
|
||||
watch([options], () => {
|
||||
currentSelect.value = 0;
|
||||
if (currentPlugin.value.name) return;
|
||||
nextTick(() => {
|
||||
ipcRenderer.sendSync("msg-trigger", {
|
||||
type: "setExpendHeight",
|
||||
data: getWindowHeight(searchValue.value ? options.value : []),
|
||||
data: getWindowHeight(options.value),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
BIN
src/renderer/assets/file.png
Normal file
BIN
src/renderer/assets/file.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
BIN
src/renderer/assets/folder.png
Normal file
BIN
src/renderer/assets/folder.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
BIN
src/renderer/assets/link.png
Normal file
BIN
src/renderer/assets/link.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.4 KiB |
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div v-show="!!options.length && searchValue && !currentPlugin.name" class="options" ref="scrollDom">
|
||||
<div v-show="!!options.length && (searchValue || !!clipboardFile.length) && !currentPlugin.name" class="options" ref="scrollDom">
|
||||
<a-list item-layout="horizontal" :dataSource="options">
|
||||
<template #renderItem="{ item, index }">
|
||||
<a-list-item
|
||||
@@ -46,10 +46,12 @@ const props = defineProps({
|
||||
default: 0,
|
||||
},
|
||||
currentPlugin: {},
|
||||
clipboardFile: () => [],
|
||||
});
|
||||
|
||||
const renderTitle = (title) => {
|
||||
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: red">${props.searchValue}</span>${result[1]}</div>`;
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<template>
|
||||
<div class="rubick-select">
|
||||
<div class="select-tag" v-show="currentPlugin.cmd">{{ currentPlugin.cmd }}</div>
|
||||
<div class="clipboard-tag" v-if="!!clipboardFile.length">
|
||||
<img :src="clipboardFile[0].isFile ? require('../assets/file.png') : require('../assets/folder.png')" />
|
||||
{{ clipboardFile[0].name }}
|
||||
</div>
|
||||
<a-input
|
||||
id="search"
|
||||
class="main-input"
|
||||
@@ -11,6 +15,7 @@
|
||||
:value="searchValue"
|
||||
:placeholder="placeholder || 'Hi, Rubick2'"
|
||||
@keypress.enter="(e) => keydownEvent(e)"
|
||||
@focus="emit('focus')"
|
||||
>
|
||||
<template #suffix>
|
||||
<div class="suffix-tool" >
|
||||
@@ -36,15 +41,16 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, defineEmits, ref } from "vue";
|
||||
import { defineProps, defineEmits, ref, computed } from "vue";
|
||||
import { ipcRenderer, remote } from "electron";
|
||||
import { LoadingOutlined, MoreOutlined } from "@ant-design/icons-vue";
|
||||
|
||||
const opConfig = remote.getGlobal("OP_CONFIG");
|
||||
const { Menu } = remote;
|
||||
|
||||
const config = ref(opConfig.get());
|
||||
|
||||
const props = defineProps({
|
||||
const props: any = defineProps({
|
||||
searchValue: {
|
||||
type: [String, Number],
|
||||
default: "",
|
||||
@@ -55,6 +61,7 @@ const props = defineProps({
|
||||
},
|
||||
currentPlugin: {},
|
||||
pluginLoading: Boolean,
|
||||
clipboardFile: () => [],
|
||||
});
|
||||
|
||||
const changeValue = (e) => {
|
||||
@@ -69,11 +76,12 @@ const emit = defineEmits([
|
||||
"openMenu",
|
||||
"changeSelect",
|
||||
"choosePlugin",
|
||||
"focus",
|
||||
]);
|
||||
|
||||
const keydownEvent = (e, index) => {
|
||||
const { ctrlKey, shiftKey, altKey, metaKey } = e;
|
||||
const modifiers = [];
|
||||
const modifiers: Array<string> = [];
|
||||
ctrlKey && modifiers.push("control");
|
||||
shiftKey && modifiers.push("shift");
|
||||
altKey && modifiers.push("alt");
|
||||
@@ -98,7 +106,7 @@ const checkNeedInit = (e) => {
|
||||
}
|
||||
};
|
||||
|
||||
const targetSearch = ({ value, type }) => {
|
||||
const targetSearch = ({ value }) => {
|
||||
if (props.currentPlugin.name) {
|
||||
return ipcRenderer.sendSync("msg-trigger", {
|
||||
type: "sendSubInputChangeEvent",
|
||||
@@ -109,13 +117,14 @@ const targetSearch = ({ value, type }) => {
|
||||
|
||||
const closeTag = () => {
|
||||
emit("changeSelect", {});
|
||||
emit("clearClipbord");
|
||||
ipcRenderer.send("msg-trigger", {
|
||||
type: "removePlugin",
|
||||
});
|
||||
};
|
||||
|
||||
const showSeparate = () => {
|
||||
let pluginMenu = [
|
||||
let pluginMenu: any = [
|
||||
{
|
||||
label: config.value.perf.common.hideOnBlur ? "钉住" : "自动隐藏",
|
||||
click: changeHideOnBlur,
|
||||
@@ -233,5 +242,22 @@ const newWindow = () => {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
.clipboard-tag {
|
||||
white-space: pre;
|
||||
user-select: none;
|
||||
font-size: 16px;
|
||||
height: 32px;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
border: 1px solid #e6e6e6;
|
||||
padding: 0 8px;
|
||||
margin-right: 12px;
|
||||
img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
133
src/renderer/plugins-manager/clipboardWatch.ts
Normal file
133
src/renderer/plugins-manager/clipboardWatch.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import getCopyFiles from "@/common/utils/getCopyFiles";
|
||||
import { clipboard, nativeImage, remote, ipcRenderer } from "electron";
|
||||
import path from "path";
|
||||
import pluginClickEvent from "./pluginClickEvent";
|
||||
import { ref } from "vue";
|
||||
|
||||
export default ({
|
||||
currentPlugin,
|
||||
optionsRef,
|
||||
openPlugin,
|
||||
setOptionsRef,
|
||||
}) => {
|
||||
const clipboardFile: any = ref([]);
|
||||
const searchFocus = () => {
|
||||
if (currentPlugin.value.name) return;
|
||||
const fileList = getCopyFiles();
|
||||
// 拷贝的是文件
|
||||
if (fileList) {
|
||||
window.setSubInputValue({ value: "" });
|
||||
clipboardFile.value = fileList;
|
||||
const localPlugins = remote.getGlobal("LOCAL_PLUGINS").getLocalPlugins();
|
||||
const options: any = [
|
||||
{
|
||||
name: "复制路径",
|
||||
value: "plugin",
|
||||
icon: require("../assets/link.png"),
|
||||
desc: "复制路径到剪切板",
|
||||
click: () => {
|
||||
clipboard.writeText(fileList.map(file => file.path).join(","));
|
||||
ipcRenderer.send("msg-trigger", { type: "hideMainWindow" });
|
||||
},
|
||||
},
|
||||
];
|
||||
// 判断复制的文件类型是否一直
|
||||
const commonLen = fileList.filter(
|
||||
(file) => path.extname(fileList[0].path) === path.extname(file.path)
|
||||
).length;
|
||||
// 复制路径
|
||||
if (commonLen !== fileList.length) {
|
||||
setOptionsRef(options);
|
||||
return;
|
||||
}
|
||||
|
||||
// 再正则插件
|
||||
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();
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
setOptionsRef(options);
|
||||
clipboard.clear();
|
||||
return;
|
||||
}
|
||||
const clipboardType = clipboard.availableFormats();
|
||||
if (!clipboardType.length) return;
|
||||
if ("text/plain" === clipboardType[0]) {
|
||||
const contentText = clipboard.readText();
|
||||
if (contentText.trim()) {
|
||||
clearClipboardFile();
|
||||
window.setSubInputValue({ value: contentText });
|
||||
}
|
||||
}
|
||||
clipboard.clear();
|
||||
};
|
||||
|
||||
const clearClipboardFile = () => {
|
||||
clipboardFile.value = [];
|
||||
optionsRef.value = [];
|
||||
};
|
||||
|
||||
return {
|
||||
searchFocus,
|
||||
clipboardFile,
|
||||
clearClipboardFile,
|
||||
};
|
||||
};
|
||||
@@ -39,7 +39,7 @@ const createPluginManager = (): any => {
|
||||
plugin: JSON.parse(
|
||||
JSON.stringify({
|
||||
...plugin,
|
||||
ext: {
|
||||
ext: plugin.ext || {
|
||||
code: plugin.feature.code,
|
||||
type: plugin.cmd.type || "text",
|
||||
payload: null,
|
||||
@@ -54,16 +54,21 @@ const createPluginManager = (): any => {
|
||||
}
|
||||
};
|
||||
|
||||
const { searchValue, onSearch, setSearchValue, placeholder } = searchManager();
|
||||
const { options } = optionsManager({
|
||||
searchValue,
|
||||
appList,
|
||||
openPlugin,
|
||||
currentPlugin: toRefs(state).currentPlugin,
|
||||
});
|
||||
const { searchValue, onSearch, setSearchValue, placeholder } =
|
||||
searchManager();
|
||||
const { options, searchFocus, clipboardFile, clearClipboardFile } =
|
||||
optionsManager({
|
||||
searchValue,
|
||||
appList,
|
||||
openPlugin,
|
||||
currentPlugin: toRefs(state).currentPlugin,
|
||||
});
|
||||
// plugin operation
|
||||
const getPluginInfo = async ({ pluginName, pluginPath }) => {
|
||||
const pluginInfo = await pluginInstance.getAdapterInfo(pluginName, pluginPath);
|
||||
const pluginInfo = await pluginInstance.getAdapterInfo(
|
||||
pluginName,
|
||||
pluginPath
|
||||
);
|
||||
return {
|
||||
...pluginInfo,
|
||||
indexPath: commonConst.dev()
|
||||
@@ -115,6 +120,9 @@ const createPluginManager = (): any => {
|
||||
options,
|
||||
searchValue,
|
||||
placeholder,
|
||||
searchFocus,
|
||||
clipboardFile,
|
||||
clearClipboardFile,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,27 +1,44 @@
|
||||
import { ref, toRaw, toRefs, watch } from "vue";
|
||||
import { ref, watch } from "vue";
|
||||
import throttle from "lodash.throttle";
|
||||
import { remote } from "electron";
|
||||
import path from "path";
|
||||
import { PLUGIN_INSTALL_DIR as baseDir } from "@/common/constans/renderer";
|
||||
import commonConst from "@/common/utils/commonConst";
|
||||
import pluginClickEvent from "./pluginClickEvent";
|
||||
import useFocus from "./clipboardWatch";
|
||||
|
||||
function formatReg(regStr) {
|
||||
const flags = regStr.replace(/.*\/([gimy]*)$/, "$1");
|
||||
const pattern = flags.replace(new RegExp("^/(.*?)/" + flags + "$"), "$1");
|
||||
return new RegExp(pattern, flags);
|
||||
}
|
||||
|
||||
function searchKeyValues(lists, value) {
|
||||
return lists.filter((item) => {
|
||||
if (typeof item === "string") {
|
||||
return item.toLowerCase().indexOf(value.toLowerCase()) >= 0;
|
||||
}
|
||||
return item.type.toLowerCase().indexOf(value.toLowerCase()) >= 0;
|
||||
if (item.type === "regex") {
|
||||
return formatReg(item.match).test(value);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
const optionsManager = ({ searchValue, appList, openPlugin, currentPlugin }) => {
|
||||
const optionsManager = ({
|
||||
searchValue,
|
||||
appList,
|
||||
openPlugin,
|
||||
currentPlugin,
|
||||
}) => {
|
||||
const optionsRef = ref([]);
|
||||
|
||||
watch(searchValue, () => search(searchValue.value));
|
||||
// search Input operation
|
||||
const search = throttle((value) => {
|
||||
if (currentPlugin.value.name) return;
|
||||
if (!value) return;
|
||||
if (clipboardFile.value.length) return;
|
||||
if (!value) {
|
||||
optionsRef.value = [];
|
||||
return;
|
||||
}
|
||||
const localPlugins = remote.getGlobal("LOCAL_PLUGINS").getLocalPlugins();
|
||||
let options: any = [];
|
||||
// todo 先搜索 plugin
|
||||
@@ -34,34 +51,25 @@ const optionsManager = ({ searchValue, appList, openPlugin, currentPlugin }) =>
|
||||
options = [
|
||||
...options,
|
||||
...cmds.map((cmd) => ({
|
||||
name: cmd,
|
||||
name: cmd.label || cmd,
|
||||
value: "plugin",
|
||||
icon: plugin.logo,
|
||||
desc: fe.explain,
|
||||
type: plugin.pluginType,
|
||||
click: () => {
|
||||
const pluginPath = path.resolve(
|
||||
baseDir,
|
||||
"node_modules",
|
||||
plugin.name
|
||||
);
|
||||
const pluginDist = {
|
||||
...toRaw(plugin),
|
||||
indexPath: `file://${path.join(
|
||||
pluginPath,
|
||||
"./",
|
||||
plugin.main || ""
|
||||
)}`,
|
||||
pluginClickEvent({
|
||||
plugin,
|
||||
fe,
|
||||
cmd,
|
||||
feature: fe,
|
||||
};
|
||||
// 模板文件
|
||||
if (!plugin.main) {
|
||||
pluginDist.tplPath = commonConst.dev()
|
||||
? "http://localhost:8082/#/"
|
||||
: `file://${__static}/tpl/index.html`;
|
||||
}
|
||||
openPlugin(pluginDist);
|
||||
ext: cmd.type
|
||||
? {
|
||||
code: fe.code,
|
||||
type: cmd.type || "text",
|
||||
payload: searchValue.value,
|
||||
}
|
||||
: null,
|
||||
openPlugin,
|
||||
});
|
||||
},
|
||||
})),
|
||||
];
|
||||
@@ -104,8 +112,22 @@ const optionsManager = ({ searchValue, appList, openPlugin, currentPlugin }) =>
|
||||
optionsRef.value = options;
|
||||
}, 500);
|
||||
|
||||
const setOptionsRef = (options) => {
|
||||
optionsRef.value = options;
|
||||
};
|
||||
|
||||
const { searchFocus, clipboardFile, clearClipboardFile } = useFocus({
|
||||
currentPlugin,
|
||||
optionsRef,
|
||||
openPlugin,
|
||||
setOptionsRef,
|
||||
});
|
||||
|
||||
return {
|
||||
options: optionsRef,
|
||||
searchFocus,
|
||||
clipboardFile,
|
||||
clearClipboardFile,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
30
src/renderer/plugins-manager/pluginClickEvent.ts
Normal file
30
src/renderer/plugins-manager/pluginClickEvent.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { PLUGIN_INSTALL_DIR as baseDir } from "@/common/constans/renderer";
|
||||
import path from "path";
|
||||
import { toRaw } from "vue";
|
||||
import commonConst from "@/common/utils/commonConst";
|
||||
|
||||
export default function pluginClickEvent({ plugin, fe, cmd, ext, openPlugin }) {
|
||||
const pluginPath = path.resolve(
|
||||
baseDir,
|
||||
"node_modules",
|
||||
plugin.name
|
||||
);
|
||||
const pluginDist = {
|
||||
...toRaw(plugin),
|
||||
indexPath: `file://${path.join(
|
||||
pluginPath,
|
||||
"./",
|
||||
plugin.main || ""
|
||||
)}`,
|
||||
cmd: cmd.label || cmd,
|
||||
feature: fe,
|
||||
ext,
|
||||
};
|
||||
// 模板文件
|
||||
if (!plugin.main) {
|
||||
pluginDist.tplPath = commonConst.dev()
|
||||
? "http://localhost:8082/#/"
|
||||
: `file://${__static}/tpl/index.html`;
|
||||
}
|
||||
openPlugin(pluginDist);
|
||||
}
|
||||
Reference in New Issue
Block a user