支持文件检索呼起插件

This commit is contained in:
muwoo
2021-12-20 18:33:00 +08:00
parent ced8aa846b
commit ff118dfe2d
25 changed files with 351 additions and 70 deletions

View File

@@ -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),
});
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -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>`;

View File

@@ -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>

View 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,
};
};

View File

@@ -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,
};
};

View File

@@ -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,
};
};

View 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);
}