使用组件而非插件的形式重写 quickcommandUI

This commit is contained in:
fofolee 2022-04-25 01:43:57 +08:00
parent b0d06875b3
commit 6d8034d950
18 changed files with 426 additions and 493 deletions

View File

@ -212,22 +212,22 @@ if (process.platform !== 'linux') quickcommand.runInTerminal = function(cmdline,
} }
let getCommandToLaunchTerminal = (cmdline, dir) => { let getCommandToLaunchTerminal = (cmdline, dir) => {
let cd = '' let cd = ''
if (utools.isWindows()) { if (utools.isWindows()) {
let appPath = path.join(utools.getPath('home'), '/AppData/Local/Microsoft/WindowsApps/') let appPath = path.join(utools.getPath('home'), '/AppData/Local/Microsoft/WindowsApps/')
// 直接 existsSync wt.exe 无效 // 直接 existsSync wt.exe 无效
if (fs.existsSync(appPath) && fs.readdirSync(appPath).includes('wt.exe')) { if (fs.existsSync(appPath) && fs.readdirSync(appPath).includes('wt.exe')) {
cmdline = cmdline.replace(/"/g, `\\"`) cmdline = cmdline.replace(/"/g, `\\"`)
if (dir) cd = `-d "${dir.replace(/\\/g, '/')}"` if (dir) cd = `-d "${dir.replace(/\\/g, '/')}"`
command = `${appPath}wt.exe ${cd} cmd /k "${cmdline}"` command = `${appPath}wt.exe ${cd} cmd /k "${cmdline}"`
} else {
cmdline = cmdline.replace(/"/g, `^"`)
if (dir) cd = `cd /d "${dir.replace(/\\/g, '/')}" &&`
command = `${cd} start "" cmd /k "${cmdline}"`
}
} else { } else {
cmdline = cmdline.replace(/"/g, `^"`) cmdline = cmdline.replace(/"/g, `\\"`)
if (dir) cd = `cd /d "${dir.replace(/\\/g, '/')}" &&` if (dir) cd = `cd ${dir.replace(/ /g, `\\\\ `)} &&`
command = `${cd} start "" cmd /k "${cmdline}"`
}
} else {
cmdline = cmdline.replace(/"/g, `\\"`)
if (dir) cd = `cd ${dir.replace(/ /g, `\\\\ `)} &&`
if (fs.existsSync('/Applications/iTerm.app')) { if (fs.existsSync('/Applications/iTerm.app')) {
command = `osascript -e 'tell application "iTerm" command = `osascript -e 'tell application "iTerm"
create window with default profile create window with default profile
@ -476,7 +476,6 @@ let getSandboxFuns = () => {
var sandbox = { var sandbox = {
fetch: fetch.bind(window), fetch: fetch.bind(window),
utools: getuToolsLite(), utools: getuToolsLite(),
quickcommand: _.cloneDeep(quickcommand),
electron, electron,
axios, axios,
Audio, Audio,
@ -503,17 +502,17 @@ let liteErr = e => {
utools.isDev() && (window.godMode = code => eval(code)) utools.isDev() && (window.godMode = code => eval(code))
// vm 模块将无法在渲染进程中使用,改用 ses 来执行代码 // vm 模块将无法在渲染进程中使用,改用 ses 来执行代码
window.evalCodeInSandbox = (code, userVars = {}) => { window.evalCodeInSandbox = (code, addVars = {}) => {
let sandboxWithUV = Object.assign(userVars, getSandboxFuns()) let sandboxWithAD = Object.assign(addVars, getSandboxFuns())
try { try {
return new Compartment(sandboxWithUV).evaluate(code); return new Compartment(sandboxWithAD).evaluate(code);
} catch (error) { } catch (error) {
throw liteErr(error) throw liteErr(error)
} }
} }
let isWatchingError = false let isWatchingError = false
window.runCodeInSandbox = (code, callback, userVars = {}) => { window.runCodeInSandbox = (code, callback, addVars = {}) => {
let sandbox = getSandboxFuns() let sandbox = getSandboxFuns()
sandbox.console = { sandbox.console = {
log: (...stdout) => { log: (...stdout) => {
@ -524,9 +523,9 @@ window.runCodeInSandbox = (code, callback, userVars = {}) => {
callback(null, parseStdout(stderr)) callback(null, parseStdout(stderr))
} }
} }
let sandboxWithUV = Object.assign(userVars, sandbox) let sandboxWithAD = Object.assign(addVars, sandbox)
try { try {
new Compartment(sandboxWithUV).evaluate(code) new Compartment(sandboxWithAD).evaluate(code)
} catch (e) { } catch (e) {
console.log('Error: ', e) console.log('Error: ', e)
callback(null, liteErr(e)) callback(null, liteErr(e))
@ -629,7 +628,7 @@ window.quickcommandHttpServer = () => {
// 错误返回 500 // 错误返回 500
if (stderr) return httpResponse(res, 500, stderr) if (stderr) return httpResponse(res, 500, stderr)
return httpResponse(res, 200, stdout) return httpResponse(res, 200, stdout)
}, userVars) }, Object.assign(userVars, _.cloneDeep(quickcommand)))
} }
httpServer = http.createServer() httpServer = http.createServer()
httpServer.on('request', (req, res) => { httpServer.on('request', (req, res) => {

View File

@ -117,7 +117,7 @@ module.exports = configure(function(ctx) {
// directives: [], // directives: [],
// Quasar plugins // Quasar plugins
plugins: ['Dialog', 'Notify'] plugins: ['Notify']
}, },
// animations: 'all', // --- includes all animations // animations: 'all', // --- includes all animations

View File

@ -2,6 +2,7 @@
<router-view v-slot="{ Component }"> <router-view v-slot="{ Component }">
<component ref="view" :is="Component" /> <component ref="view" :is="Component" />
</router-view> </router-view>
<QuickCommand />
</template> </template>
<script> <script>
@ -11,8 +12,10 @@ import UTOOLS from "./js/utools.js";
import programmings from "./js/options/programs.js"; import programmings from "./js/options/programs.js";
import defaultProfile from "./js/options/defaultProfile.js"; import defaultProfile from "./js/options/defaultProfile.js";
import Cron from "croner"; import Cron from "croner";
import QuickCommand from "components/quickcommandUI/QuickCommand";
export default defineComponent({ export default defineComponent({
components: { QuickCommand },
name: "App", name: "App",
data() { data() {
return { return {
@ -21,6 +24,7 @@ export default defineComponent({
profile: defaultProfile, profile: defaultProfile,
utools: UTOOLS, utools: UTOOLS,
cronJobs: {}, cronJobs: {},
enterData: {},
}; };
}, },
computed: {}, computed: {},
@ -82,8 +86,7 @@ export default defineComponent({
// 使 // 使
this.usageStatistics(enter.code, this.parseDate(new Date())); this.usageStatistics(enter.code, this.parseDate(new Date()));
this.$q.dark.set(utools.isDarkColors()); this.$q.dark.set(utools.isDarkColors());
quickcommand.enterData = enter; this.enterData = enter;
quickcommand.payload = enter.payload;
this.$router.push(enter.code); this.$router.push(enter.code);
}, },
outPlugin() { outPlugin() {
@ -91,7 +94,7 @@ export default defineComponent({
_.cloneDeep(this.profile), _.cloneDeep(this.profile),
this.utools.DBPRE.CFG + "preferences" this.utools.DBPRE.CFG + "preferences"
); );
this.$refs.view.$refs?.commandEditor?.saveCodeHistory() this.$refs.view.$refs?.commandEditor?.saveCodeHistory();
this.$router.push("/"); this.$router.push("/");
}, },
runCronTask(featureCode, cronExp) { runCronTask(featureCode, cronExp) {
@ -102,7 +105,9 @@ export default defineComponent({
runCommandSilently(featureCode) { runCommandSilently(featureCode) {
let command = this.utools.getDB(this.utools.DBPRE.QC + featureCode); let command = this.utools.getDB(this.utools.DBPRE.QC + featureCode);
if (command.program === "quickcommand") { if (command.program === "quickcommand") {
window.runCodeInSandbox(command.cmd, () => {}); window.runCodeInSandbox(command.cmd, () => {}, {
quickcommand: _.cloneDeep(quickcommand),
});
} else { } else {
let option = let option =
command.program === "custom" command.program === "custom"

View File

@ -113,14 +113,21 @@ export default {
utools.outPlugin(); utools.outPlugin();
}, 500); }, 500);
if (currentCommand.program === "quickcommand") { if (currentCommand.program === "quickcommand") {
window.runCodeInSandbox(currentCommand.cmd, (stdout, stderr) => { let qc = _.cloneDeep(quickcommand);
if (stderr) { qc.enterData = this.$root.enterData;
return quitBeforeShowResult qc.type = this.$root.enterData.type;
? alert(stderr) window.runCodeInSandbox(
: this.showRunResult(stderr, false, action); currentCommand.cmd,
} (stdout, stderr) => {
!outPlugin && this.showRunResult(stdout, true, action); if (stderr) {
}); return quitBeforeShowResult
? alert(stderr)
: this.showRunResult(stderr, false, action);
}
!outPlugin && this.showRunResult(stdout, true, action);
},
{ quickcommand: qc }
);
} else if (currentCommand.program === "html") { } else if (currentCommand.program === "html") {
this.showRunResult(currentCommand.cmd, true, action); this.showRunResult(currentCommand.cmd, true, action);
} else { } else {
@ -150,7 +157,9 @@ export default {
let spVars = _.filter(specialVars, (sp) => sp.repl); let spVars = _.filter(specialVars, (sp) => sp.repl);
_.forIn(spVars, (val, key) => { _.forIn(spVars, (val, key) => {
if (cmd.includes(val.label.slice(0, -2))) { if (cmd.includes(val.label.slice(0, -2))) {
cmd = cmd.replace(val.match, (x) => val.repl(x)); cmd = cmd.replace(val.match, (x) =>
val.repl(x, this.$root.enterData)
);
} }
}); });
return cmd; return cmd;
@ -208,7 +217,7 @@ export default {
if (!this.needTempPayload) return; if (!this.needTempPayload) return;
let type = let type =
currentCommand.cmdType || currentCommand.features?.cmds[0].type; currentCommand.cmdType || currentCommand.features?.cmds[0].type;
quickcommand.enterData = { this.$root.enterData = {
type: type || "text", type: type || "text",
payload: await commandTypes[type]?.tempPayload?.(), payload: await commandTypes[type]?.tempPayload?.(),
}; };
@ -255,14 +264,11 @@ export default {
this.clear(); this.clear();
}, },
clear() { clear() {
if (!!this.quickcommandListener) {
document.removeEventListener("keydown", this.quickcommandListener);
}
if (!!this.childProcess) { if (!!this.childProcess) {
quickcommand.kill(this.childProcess.pid); quickcommand.kill(this.childProcess.pid);
} }
quickcommand.closeWaitBtn?.(); quickcommand.removeListener();
quickcommand.closeWaitBtn = () => {}; quickcommand.closeWaitButton();
}, },
frameLoad(initHeight) { frameLoad(initHeight) {
this.outputAutoHeight(this.fromUtools); this.outputAutoHeight(this.fromUtools);
@ -272,11 +278,5 @@ export default {
unmounted() { unmounted() {
this.stopRun(); this.stopRun();
}, },
mounted() {
quickcommand.listenKeydown = (callback) => {
this.quickcommandListener = callback;
document.addEventListener("keydown", callback);
};
},
}; };
</script> </script>

View File

@ -2,7 +2,7 @@
export default { export default {
mounted() { mounted() {
utools.setExpendHeight(0); utools.setExpendHeight(0);
quickcommand.enterData.payload.forEach((file) => { this.$root.payload.forEach((file) => {
let uid = this.getUid(); let uid = this.getUid();
let fileInfo = window.getFileInfo({ let fileInfo = window.getFileInfo({
type: "file", type: "file",

View File

@ -24,7 +24,7 @@ export default {
); );
url = choise.text + "://" + url; url = choise.text + "://" + url;
} }
let title = quickcommand.enterData.payload.title let title = this.$root.payload.title
.replace(/和另外 \d+ 个页面.*/, "") .replace(/和另外 \d+ 个页面.*/, "")
.replace(/[-|—] .*?[Edge|Firefox|Chrome].*/, "") .replace(/[-|—] .*?[Edge|Firefox|Chrome].*/, "")
.trim(); .trim();

View File

@ -1,21 +1,20 @@
<template> <template>
<q-dialog ref="dialog" @hide="onDialogHide"> <q-card class="q-dialog-plugin">
<q-card class="q-dialog-plugin"> <q-card-section>
<q-card-section> <div class="text-h5" align="center" v-text="options.title"></div>
<div class="text-h5" align="center" v-text="title"></div> </q-card-section>
</q-card-section> <q-card-section class="q-gutter-lg">
<q-card-section class="q-gutter-lg"> <div v-for="(label, index) in options.labels" :key="index">
<div v-for="(label, index) in labels" :key="index"> <q-btn
<q-btn class="full-width"
class="full-width" color="primary"
color="primary" :label="label"
:label="label" v-close-popup
@click="onOKClick(label, index)" @click="clickOK(label, index)"
/> />
</div> </div>
</q-card-section> </q-card-section>
</q-card> </q-card>
</q-dialog>
</template> </template>
<script> <script>
@ -26,29 +25,11 @@ export default {
}; };
}, },
props: { props: {
labels: Array, options: Object,
title: String,
}, },
emits: ["ok", "hide"],
methods: { methods: {
show() { clickOK(label, index) {
this.$refs.dialog.show(); this.$emit("clickOK", { id: index, text: label });
},
hide() {
this.$refs.dialog.hide();
},
onDialogHide() {
this.$emit("hide");
},
onOKClick(label, index) {
this.$emit("ok", { id: index, text: label });
this.hide();
},
onCancelClick() {
this.hide();
}, },
}, },
}; };

View File

@ -0,0 +1,34 @@
<template>
<q-card class="q-dialog-plugin">
<q-card-section>
<div class="text-h5" align="left" v-text="options.title"></div>
</q-card-section>
<q-card-section>
{{ options.message }}
</q-card-section>
<q-card-section class="flex justify-end q-gutter-sm">
<q-btn flat label="取消" color="grey" v-close-popup />
<q-btn
flat
autofocus
label="确定"
color="primary"
v-close-popup
@click="clickOK()"
/>
</q-card-section>
</q-card>
</template>
<script>
export default {
methods: {
clickOK() {
this.$emit("clickOK");
},
},
props: {
options: Object,
},
};
</script>

View File

@ -1,63 +1,41 @@
<template> <template>
<q-dialog ref="dialog" @hide="onDialogHide"> <q-card class="q-dialog-plugin">
<q-card class="q-dialog-plugin"> <q-card-section>
<q-card-section> <div class="text-h5" align="center" v-text="options.title"></div>
<div class="text-h5" align="center" v-text="title"></div> </q-card-section>
</q-card-section> <q-card-section class="q-gutter-sm">
<q-card-section class="q-gutter-sm"> <div v-for="(label, index) in options.labels" :key="index">
<div v-for="(label, index) in labels" :key="index"> <q-input
<q-input outlined
outlined v-model="results[index]"
v-model="results[index]" :label="label"
:label="label" :hint="options.hints[index]"
:hint="hints[index]" hide-hint
hide-hint autofocus
autofocus @keyup.enter="clickOK"
@keyup.enter="onOKClick" />
/> </div>
</div> </q-card-section>
</q-card-section> <q-card-actions align="right">
<q-card-actions align="right"> <q-btn color="blue-9" label="确定" @click="clickOK" v-close-popup />
<q-btn color="blue-9" label="确定" @click="onOKClick" /> <q-btn color="negative" label="取消" v-close-popup />
<q-btn color="negative" label="取消" @click="onCancelClick" /> </q-card-actions>
</q-card-actions> </q-card>
</q-card>
</q-dialog>
</template> </template>
<script> <script>
export default { export default {
data() { data() {
return { return {
results: this.values, results: this.options.values,
}; };
}, },
props: { props: {
labels: Array, options: Object,
title: String,
hints: Array,
values: Array
}, },
emits: ["ok", "hide"],
methods: { methods: {
show() { clickOK() {
this.$refs.dialog.show(); this.$emit("clickOK", this.results);
},
hide() {
this.$refs.dialog.hide();
},
onDialogHide() {
this.$emit("hide");
},
onOKClick() {
this.$emit("ok", this.results);
this.hide();
},
onCancelClick() {
this.hide();
}, },
}, },
}; };

View File

@ -0,0 +1,156 @@
<template>
<q-dialog
v-model="showDialog"
:maximized="maximized"
:transition-show="maximized ? 'fade' : 'scale'"
:transition-hide="maximized ? 'fade' : 'scale'"
>
<component
ref="ui"
:is="currentUI"
:options="options"
@clickOK="clickOK"
@hide="showDialog = false"
/>
</q-dialog>
<!-- waitButton 单独一个 -->
<q-dialog seamless position="right" style="z-index: 9999" v-model="showWb">
<q-card>
<q-btn color="primary" :label="wbLabel" @click="wbEvent" v-close-popup />
</q-card>
</q-dialog>
</template>
<script>
import { Notify } from "quasar";
import { markRaw } from "vue";
import InputBox from "components/quickcommandUI/InputBox";
import ButtonBox from "components/quickcommandUI/ButtonBox";
import ConfirmBox from "components/quickcommandUI/ConfirmBox";
import TextArea from "components/quickcommandUI/TextArea";
import SelectList from "components/quickcommandUI/SelectList";
export default {
components: {
InputBox: markRaw(InputBox),
ButtonBox: markRaw(ButtonBox),
ConfirmBox: markRaw(ConfirmBox),
TextArea: markRaw(TextArea),
SelectList: markRaw(SelectList),
},
data() {
return {
currentUI: null,
options: {},
showDialog: false,
maximized: false,
showWb: false,
wbLabel: "",
listeners: [],
};
},
mounted() {
const quickcommandUI = {
showInputBox: (options = ["请输入"], title = "") =>
new Promise((reslove, reject) => {
let props = {
labels: [],
values: [],
hints: [],
title: title,
};
if (!_.isObject(options))
return reject(new TypeError(`应为 Object, 而非 ${typeof options}`));
if (_.isArray(options)) props.labels = options;
else Object.assign(props, options);
this.showUI(InputBox, props, false, reslove);
}),
showButtonBox: (labels = ["确定"], title = "") =>
new Promise((reslove, reject) => {
if (!_.isArray(labels))
return reject(new TypeError(`应为 Array, 而非 ${typeof labels}`));
this.showUI(ButtonBox, { labels, title }, false, reslove);
}),
showConfirmBox: (message = "", title = "提示") =>
new Promise((reslove, reject) => {
this.showUI(ConfirmBox, { message, title }, false, reslove);
}),
showMessageBox: (message, icon = "success", time = 3000) => {
if (icon === "success") icon = "positive";
if (icon === "error") icon = "negative";
Notify.create({
type: icon,
message: message,
timeout: time,
position: "top",
});
},
showTextArea: (placeholder = "", value = "") =>
new Promise((reslove, reject) => {
this.showUI(TextArea, { placeholder, value }, true, reslove);
}),
showSelectList: (initItems, options = {}) =>
new Promise((reslove, reject) => {
if (!_.isArray(initItems))
return reject(
new TypeError(`应为 Array, 而非 ${typeof initItems}`)
);
let defaultOptions = {
placeholder: "输入进行筛选,支持拼音",
optionType: "plaintext",
enableSearch: true,
showCancelButton: false,
closeOnSelect: true,
};
options = Object.assign(defaultOptions, options);
this.showUI(SelectList, { initItems, options }, true, reslove);
}),
showWaitButton: (callback, label = "确定") => {
this.wbLabel = label;
this.showWb = true;
this.wbEvent = callback;
},
closeWaitButton: () => {
this.showWb = false;
},
updateSelectList: (opt, id) => {
if (this.currentUI !== SelectList) throw "请先创建 selectList";
if (typeof id === "undefined") this.$refs.ui.items.push(opt);
else this.$refs.ui.items[id] = opt;
},
listenKeydown: (callback) => {
this.listeners.push(callback);
document.addEventListener("keydown", callback);
},
removeListener: () => {
this.listeners.forEach((listener) => {
document.removeEventListener("keydown", listener);
});
},
};
Object.assign(window.quickcommand, quickcommandUI);
Object.freeze(quickcommand);
},
methods: {
clickOK() {},
wbEvent() {},
showUI(uiComponent, options, maximized, reslove) {
this.showDialog = true;
this.options = options;
this.maximized = maximized;
this.currentUI = uiComponent;
this.clickOK = reslove;
},
},
};
</script>

View File

@ -1,66 +1,54 @@
<template> <template>
<q-dialog <q-card @keydown="keyEvent">
@keydown="keyEvent" <q-virtual-scroll
maximized ref="scrollBar"
ref="dialog" :style="{ maxHeight: listMaxHeight + 'px', height: '100vh' }"
transition-show="fade" :virtual-scroll-slice-size="lazyItemSize"
transition-hide="fade" :virtual-scroll-item-size="itemHeight"
@hide="onDialogHide" @virtual-scroll="scrollEvent"
> :items="matchedItems"
<q-card> >
<q-virtual-scroll <template v-slot="{ item, index }">
ref="scrollBar" <q-item
:style="{ maxHeight: listMaxHeight + 'px', height: '100vh' }" :key="index"
:virtual-scroll-slice-size="lazyItemSize" clickable
:virtual-scroll-item-size="itemHeight" v-ripple
@virtual-scroll="scrollEvent" @mousemove="currentIndex = index"
:items="matchedItems" @click="clickOK"
> manual-focus
<template v-slot="{ item, index }"> :focused="index === currentIndex"
<q-item :active="index === currentIndex"
:key="index" :style="{
clickable height: itemHeight + 'px',
v-ripple }"
@mousemove="currentIndex = index" >
@click="onOKClick" <q-item-section v-if="isText">
manual-focus <q-item-label lines="1">{{ item }}</q-item-label>
:focused="index === currentIndex" </q-item-section>
:active="index === currentIndex" <q-item-section v-else-if="isJson" class="content-start q-gutter-md">
:style="{ <q-avatar size="40px" v-if="item.icon">
height: itemHeight + 'px', <q-img :src="item.icon" />
}" </q-avatar>
> <q-item-label lines="1">{{ item.title }}</q-item-label>
<q-item-section v-if="isText"> <q-item-label lines="1" caption>{{
<q-item-label lines="1">{{ item }}</q-item-label> item.description
</q-item-section> }}</q-item-label>
<q-item-section </q-item-section>
v-else-if="isJson" <q-item-section v-else-if="isHtml">
class="content-start q-gutter-md" <div v-html="item"></div>
> </q-item-section>
<q-avatar size="40px" v-if="item.icon"> </q-item>
<q-img :src="item.icon" /> </template>
</q-avatar> </q-virtual-scroll>
<q-item-label lines="1">{{ item.title }}</q-item-label> <q-btn
<q-item-label lines="1" caption>{{ v-if="options.showCancelButton"
item.description class="absolute-bottom-right q-ma-xs"
}}</q-item-label> round
</q-item-section> color="primary"
<q-item-section v-else-if="isHtml"> icon="close"
<div v-html="item"></div> @click="hide"
</q-item-section> />
</q-item> </q-card>
</template>
</q-virtual-scroll>
<q-btn
v-if="options.showCancelButton"
class="absolute-bottom-right q-ma-xs"
round
color="primary"
icon="close"
@click="onCancelClick"
/>
</q-card>
</q-dialog>
</template> </template>
<script> <script>
@ -69,7 +57,7 @@ import pinyinMatch from "pinyin-match";
export default { export default {
data() { data() {
return { return {
items: this.initItems, items: this.options.initItems,
listMaxHeight: 500, listMaxHeight: 500,
currentIndex: 0, currentIndex: 0,
itemHeight: 50, itemHeight: 50,
@ -78,11 +66,7 @@ export default {
}; };
}, },
mounted() { mounted() {
quickcommand.updateSelectList = (opt, id) => { this.options.options.enableSearch && this.setSubInput();
if (typeof id === "undefined") this.items.push(opt);
else this.items[id] = opt;
};
this.options.enableSearch && this.setSubInput();
this.setUtoolsHeight(this.itemHeight * this.matchedItemsSize); this.setUtoolsHeight(this.itemHeight * this.matchedItemsSize);
}, },
unmounted() { unmounted() {
@ -102,48 +86,33 @@ export default {
return this.matchedItems.length; return this.matchedItems.length;
}, },
isJson() { isJson() {
return this.options.optionType === "json"; return this.options.options.optionType === "json";
}, },
isHtml() { isHtml() {
return this.options.optionType === "html"; return this.options.options.optionType === "html";
}, },
isText() { isText() {
return this.options.optionType === "plaintext"; return this.options.options.optionType === "plaintext";
}, },
}, },
props: { props: {
options: Object, options: Object,
initItems: Array,
}, },
emits: ["ok", "hide"],
methods: { methods: {
show() {
this.$refs.dialog.show();
},
hide() { hide() {
this.$refs.dialog.hide();
},
onDialogHide() {
this.clear(); this.clear();
this.$emit("hide"); this.$emit("hide");
}, },
onOKClick() { clickOK() {
let selected = let selected = this.isJson
this.options.optionType === "json" ? this.matchedItems[this.currentIndex]
? this.matchedItems[this.currentIndex] : {
: { id: this.currentIndex,
id: this.currentIndex, text: this.matchedItems[this.currentIndex],
text: this.matchedItems[this.currentIndex], };
}; this.$emit("clickOK", selected);
this.$emit("ok", selected); this.options.options.closeOnSelect && this.hide();
this.options.closeOnSelect && this.hide();
},
onCancelClick() {
this.hide();
}, },
keyEvent(e) { keyEvent(e) {
@ -159,7 +128,7 @@ export default {
); );
break; break;
case 13: case 13:
this.onOKClick(); this.clickOK();
return; return;
} }
this.$refs.scrollBar.scrollTo(this.currentIndex); this.$refs.scrollBar.scrollTo(this.currentIndex);
@ -181,7 +150,7 @@ export default {
setSubInput() { setSubInput() {
utools.setSubInput(({ text }) => { utools.setSubInput(({ text }) => {
this.searchWords = text; this.searchWords = text;
}, this.options.placeholder); }, this.options.options.placeholder);
}, },
setUtoolsHeight(height) { setUtoolsHeight(height) {
@ -191,8 +160,13 @@ export default {
clear() { clear() {
utools.removeSubInput(); utools.removeSubInput();
this.setUtoolsHeight(this.listMaxHeight); this.setUtoolsHeight(this.listMaxHeight);
quickcommand.updateSelectList = () => {};
}, },
}, },
}; };
</script> </script>
<style scoped>
.q-card--dark {
background: var(--q-dark-page);
}
</style>

View File

@ -1,65 +1,43 @@
<template> <template>
<q-dialog maximized ref="dialog" @hide="onDialogHide"> <q-card class="q-dialog-plugin">
<q-card class="q-dialog-plugin"> <textarea
<textarea v-model="result"
v-model="result" :placeholder="options.placeholder"
:placeholder="placeholder" autofocus
autofocus class="fixed"
class="fixed" :style="{
:style="{ top: 0,
top: 0, bottom: 0,
bottom: 0, left: 0,
left: 0, right: 0,
right: 0, background: '#00000000',
background: '#00000000', color: $q.dark.isActive ? 'white' : 'black',
color: $q.dark.isActive ? 'white' : 'black', fontSize: '16px',
fontSize: '16px', outline: 'none',
outline: 'none', border: '3px solid #3577cb',
border: '3px solid #3577cb', borderRadius: '5px',
borderRadius: '5px', }"
}" />
/> <div class="fixed-bottom-right q-pa-md q-gutter-sm">
<div <q-btn round color="blue-grey" icon="arrow_back" v-close-popup />
class="fixed-bottom-right q-pa-md q-gutter-sm" <q-btn round color="primary" icon="done" @click="clickOK" />
> </div>
<q-btn round color="blue-grey" icon="arrow_back" @click="onCancelClick" /> </q-card>
<q-btn round color="primary" icon="done" @click="onOKClick" />
</div>
</q-card>
</q-dialog>
</template> </template>
<script> <script>
export default { export default {
data() { data() {
return { return {
result: this.value, result: this.options.value,
}; };
}, },
props: { props: {
placeholder: String, options: Object,
value: String,
}, },
emits: ["ok", "hide"],
methods: { methods: {
show() { clickOK() {
this.$refs.dialog.show(); this.$emit("clickOK", this.result);
},
hide() {
this.$refs.dialog.hide();
},
onDialogHide() {
this.$emit("hide");
},
onOKClick() {
this.$emit("ok", this.result);
this.hide();
},
onCancelClick() {
this.hide();
}, },
}, },
}; };

View File

@ -1,47 +0,0 @@
<template>
<q-dialog
ref="dialog"
seamless
position="right"
@hide="onDialogHide"
style="z-index: 9999"
>
<q-card>
<q-btn color="primary" :label="label" @click="onOKClick" v-close-popup />
</q-card>
</q-dialog>
</template>
<script>
export default {
props: {
label: String,
},
mounted() {
quickcommand.closeWaitBtn = () => {
this.$refs?.dialog?.hide();
};
},
emits: ["ok", "hide"],
methods: {
show() {
this.$refs.dialog.show();
},
hide() {
this.$refs.dialog.hide();
},
onDialogHide() {
this.$emit("hide");
},
onOKClick() {
this.$emit("ok");
},
onCancelClick() {
this.hide();
},
},
};
</script>

View File

@ -8,10 +8,10 @@ let escapeItem = item => {
return item.replace('$', '$$$') return item.replace('$', '$$$')
} }
let handlingJsonVar = (jsonVar, name) => { let handlingJsonVar = (jsonVar, name, payload) => {
try { try {
return escapeItem(window.evalCodeInSandbox(jsonVar.slice(2, -2), { return escapeItem(window.evalCodeInSandbox(jsonVar.slice(2, -2), {
[name]: quickcommand.enterData.payload [name]: payload
})) }))
} catch (e) { } catch (e) {
return utools.showNotification(e) return utools.showNotification(e)
@ -73,7 +73,7 @@ const specialVars = {
label: "{{input}}", label: "{{input}}",
desc: "主输入框的文本", desc: "主输入框的文本",
match: /{{input}}/mg, match: /{{input}}/mg,
repl: () => quickcommand.enterData.payload repl: (text, enterData) => enterData.payload
}, },
pwd: { pwd: {
name: "pwd", name: "pwd",
@ -88,21 +88,21 @@ const specialVars = {
desc: "当前窗口信息JSON格式可以指定键值如{{WindowInfo.id}}", desc: "当前窗口信息JSON格式可以指定键值如{{WindowInfo.id}}",
type: "json", type: "json",
match: /{{WindowInfo(.*?)}}/mg, match: /{{WindowInfo(.*?)}}/mg,
repl: jsonVar => handlingJsonVar(jsonVar, "WindowInfo") repl: (jsonVar, enterData) => handlingJsonVar(jsonVar, "WindowInfo", enterData.payload)
}, },
MatchImage: { MatchImage: {
name: "MatchImage", name: "MatchImage",
label: "{{MatchImage}}", label: "{{MatchImage}}",
desc: "匹配到图片的 DataUrl", desc: "匹配到图片的 DataUrl",
match: /{{MatchImage}}/mg, match: /{{MatchImage}}/mg,
repl: () => quickcommand.enterData.payload repl: (text, enterData) => enterData.payload
}, },
SelectFile: { SelectFile: {
name: "SelectFile", name: "SelectFile",
label: "{{SelectFile}}", label: "{{SelectFile}}",
desc: "文件管理器选中的文件不支持Linux", desc: "文件管理器选中的文件不支持Linux",
match: /{{SelectFile}}/mg, match: /{{SelectFile}}/mg,
repl: () => window.getSelectFile(quickcommand.enterData.payload.id) repl: (text, enterData) => window.getSelectFile(enterData.payload.id)
}, },
MatchedFiles: { MatchedFiles: {
name: "MatchedFiles", name: "MatchedFiles",
@ -110,14 +110,14 @@ const specialVars = {
desc: "匹配的文件JSON格式可以指定键值如{{MatchedFiles[0].path}}", desc: "匹配的文件JSON格式可以指定键值如{{MatchedFiles[0].path}}",
type: "json", type: "json",
match: /{{MatchedFiles(.*?)}}/mg, match: /{{MatchedFiles(.*?)}}/mg,
repl: jsonVar => handlingJsonVar(jsonVar, "MatchedFiles") repl: (jsonVar, enterData) => handlingJsonVar(jsonVar, "MatchedFiles", enterData.payload)
}, },
type: { type: {
name: "type", name: "type",
label: "{{type}}", label: "{{type}}",
desc: "onPluginEnter的type匹配的类型", desc: "onPluginEnter的type匹配的类型",
match: /{{type}}/mg, match: /{{type}}/mg,
repl: () => quickcommand.enterData.type repl: (text, enterData) => enterData.type
}, },
payload: { payload: {
name: "payload", name: "payload",
@ -125,7 +125,7 @@ const specialVars = {
desc: "onPluginEnter的payload,当为JSON时可以指定键值如{{payload.id}}", desc: "onPluginEnter的payload,当为JSON时可以指定键值如{{payload.id}}",
type: "json", type: "json",
match: /{{payload(.*?)}}/mg, match: /{{payload(.*?)}}/mg,
repl: jsonVar => handlingJsonVar(jsonVar, "payload") repl: (jsonVar, enterData) => handlingJsonVar(jsonVar, "payload", enterData.payload)
}, },
js: { js: {
name: "js", name: "js",

View File

@ -1,131 +0,0 @@
/**
* 通过quickcommand的api快速生成可交互的UI界面
* UI界面基于quasar
*/
import {
Dialog,
Notify
} from 'quasar'
import inputBox from "../components/quickcommandUI/InputBox"
import buttonBox from "../components/quickcommandUI/ButtonBox"
import TextArea from "../components/quickcommandUI/TextArea"
import SelectList from "../components/quickcommandUI/SelectList"
import WaitButton from "../components/quickcommandUI/waitButton"
const quickcommand = {
showInputBox: (options = ["请输入"], title = "") => new Promise((reslove, reject) => {
let props = {
labels: [],
values: [],
hints: [],
title: title
}
if (!_.isObject(options)) return reject(new TypeError(`应为 Object, 而非 ${typeof options}`))
if (_.isArray(options)) props.labels = options
else Object.assign(props, options)
Dialog.create({
component: inputBox,
componentProps: props
}).onOk(results => {
reslove(Array.from(results))
}).onCancel(() => {
console.log('取消')
})
}),
showButtonBox: (labels = ["确定"], title = "") => new Promise((reslove, reject) => {
if (!_.isArray(labels)) return reject(new TypeError(`应为 Array, 而非 ${typeof labels}`))
let props = {
labels: labels,
title: title
}
Dialog.create({
component: buttonBox,
componentProps: props
}).onOk(results => {
reslove(results)
}).onCancel(() => {
console.log('取消')
})
}),
showConfirmBox: (message = "", title = "提示") => new Promise((reslove, reject) => {
Dialog.create({
title: title,
message: message,
cancel: true,
persistent: true
}).onOk(() => {
reslove(true)
}).onCancel(() => {
reslove(false)
})
}),
showMessageBox: (message, icon = 'success', time = 3000) => {
if (icon === 'success') icon = 'positive'
if (icon === 'error') icon = 'negative'
Notify.create({
type: icon,
message: message,
timeout: time,
position: 'top',
})
},
showTextArea: (placeholder = "", value = "") => new Promise((reslove, reject) => {
let props = {
placeholder: placeholder,
value: value
}
Dialog.create({
component: TextArea,
componentProps: props
}).onOk(results => {
reslove(results)
}).onCancel(() => {
console.log('取消')
})
}),
showSelectList: (selects, options = {}) => new Promise((reslove, reject) => {
if (!_.isArray(selects)) return reject(new TypeError(`应为 Array, 而非 ${typeof selects}`))
let defaultOptions = {
placeholder: "输入进行筛选,支持拼音",
optionType: "plaintext",
enableSearch: true,
showCancelButton: false,
closeOnSelect: true
}
Object.assign(defaultOptions, options)
let props = {
initItems: selects,
options: defaultOptions
}
Dialog.create({
component: SelectList,
componentProps: props
}).onOk(results => {
reslove(results)
}).onCancel(() => {
console.log('取消')
})
}),
showWaitButton: (callback, label = "确定") => {
Dialog.create({
component: WaitButton,
componentProps: {
label
}
}).onOk(() => {
callback()
}).onCancel(() => {
console.log('取消')
})
}
}
export default quickcommand

View File

@ -315,8 +315,8 @@ export default {
initPage() { initPage() {
// newcommand // newcommand
if (this.newCommandDirect) { if (this.newCommandDirect) {
if (quickcommand.enterData.type === "text") this.addNewCommand(); if (this.$root.enterData.type === "text") this.addNewCommand();
else this.editCommand(JSON.parse(quickcommand.enterData.payload)); else this.editCommand(JSON.parse(this.$root.enterData.payload));
this.$router.push("/configuration"); this.$router.push("/configuration");
} }
// features // features

View File

@ -174,8 +174,10 @@ interface quickcommandApi {
/** /**
* *
*
* 退
*/ */
closeWaitBtn(): void; closeWaitButton(): void;
/** /**
* *
@ -192,6 +194,13 @@ interface quickcommandApi {
*/ */
listenKeydown(callback: (event) => void): void; listenKeydown(callback: (event) => void): void;
/**
*
*
* 退
*/
removeListener(): void;
/** /**
* *
* @param ms * @param ms

View File

@ -1,9 +1,6 @@
import { route } from 'quasar/wrappers' import { route } from 'quasar/wrappers'
import { createRouter, createMemoryHistory, createWebHistory, createWebHashHistory } from 'vue-router' import { createRouter, createMemoryHistory, createWebHistory, createWebHashHistory } from 'vue-router'
import routes from './routes' import routes from './routes'
import quickcommandUI from '../js/quickcommand'
Object.assign(window.quickcommand, quickcommandUI)
/* /*
* If not building with SSR mode, you can * If not building with SSR mode, you can
@ -14,20 +11,20 @@ Object.assign(window.quickcommand, quickcommandUI)
* with the Router instance. * with the Router instance.
*/ */
export default route(function (/* { store, ssrContext } */) { export default route(function( /* { store, ssrContext } */ ) {
const createHistory = process.env.SERVER const createHistory = process.env.SERVER ?
? createMemoryHistory createMemoryHistory :
: (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory) (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory)
const Router = createRouter({ const Router = createRouter({
scrollBehavior: () => ({ left: 0, top: 0 }), scrollBehavior: () => ({ left: 0, top: 0 }),
routes, routes,
// Leave this as is and make changes in quasar.conf.js instead! // Leave this as is and make changes in quasar.conf.js instead!
// quasar.conf.js -> build -> vueRouterMode // quasar.conf.js -> build -> vueRouterMode
// quasar.conf.js -> build -> publicPath // quasar.conf.js -> build -> publicPath
history: createHistory(process.env.MODE === 'ssr' ? void 0 : process.env.VUE_ROUTER_BASE) history: createHistory(process.env.MODE === 'ssr' ? void 0 : process.env.VUE_ROUTER_BASE)
}) })
return Router return Router
}) })