Merge pull request #34 from ZiuChen/v1.2.2

V1.2.2
This commit is contained in:
ZiuChen 2022-09-07 10:03:06 +08:00 committed by GitHub
commit c37c42ff94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 372 additions and 139 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -1,5 +1,5 @@
{ {
"version": "1.1.6", "version": "1.2.2",
"pluginName": "超级剪贴板", "pluginName": "超级剪贴板",
"description": "强大的剪贴板管理工具", "description": "强大的剪贴板管理工具",
"author": "ZiuChen", "author": "ZiuChen",

View File

@ -7,6 +7,7 @@
const fs = require('fs') const fs = require('fs')
const crypto = require('crypto') const crypto = require('crypto')
const { clipboard } = require('electron') const { clipboard } = require('electron')
const time = require('./time')
const homePath = utools.getPath('home') const homePath = utools.getPath('home')
const userDataPath = utools.getPath('userData') const userDataPath = utools.getPath('userData')
@ -36,10 +37,12 @@ class DB {
// 读取磁盘记录到内存 // 读取磁盘记录到内存
const dataBase = JSON.parse(data) const dataBase = JSON.parse(data)
this.dataBase = dataBase this.dataBase = dataBase
// 将超过14天的数据删除 // 将超过14天的数据删除 排除掉收藏
const now = new Date().getTime() const now = new Date().getTime()
const deleteTime = now - '\u0031\u0034' * '\u0032\u0034' * 60 * 60 * 1000 // unicode const deleteTime = now - '\u0031\u0034' * '\u0032\u0034' * 60 * 60 * 1000 // unicode
this.dataBase.data = this.dataBase.data.filter((item) => item.updateTime > deleteTime) this.dataBase.data = this.dataBase.data?.filter(
(item) => item.updateTime > deleteTime || item.collect
)
this.updateDataBaseLocal() this.updateDataBaseLocal()
} catch (err) { } catch (err) {
utools.showNotification('读取剪切板出错' + err) utools.showNotification('读取剪切板出错' + err)
@ -71,14 +74,16 @@ class DB {
addItem(cItem) { addItem(cItem) {
this.dataBase.data.unshift(cItem) this.dataBase.data.unshift(cItem)
this.updateDataBase() this.updateDataBase()
// unicode const exceedCount = this.dataBase.data.length - '\u0035\u0030\u0030'
if (this.dataBase.data.length > '\u0035\u0030\u0030') { if (exceedCount > 0) {
// 达到条数限制 // 达到条数限制 在收藏条数限制内遍历非收藏历史并删除
this.dataBase.data.pop() // 所有被移除的 item都存入tempList
// 仍然大于: 超出了不止一条 const tmpList = []
if (this.dataBase.data.length > '\u0035\u0030\u0030') { for (let i = 0; i < exceedCount; i++) {
this.dataBase.data = this.dataBase.data.splice(0, 499) const item = this.dataBase.data.pop()
tmpList.push(item)
} }
tmpList.forEach((item) => !item.collect || this.dataBase.data.push(item)) // 收藏内容 重新入栈
} }
this.updateDataBaseLocal() this.updateDataBaseLocal()
} }
@ -118,49 +123,49 @@ class DB {
} }
} }
const pbpaste = async () => { const pbpaste = () => {
return new Promise((res) => { // file
// file const files = utools.getCopyedFiles() // null | Array
const files = utools.getCopyedFiles() // null | Array if (files) {
if (files) { return {
res({ type: 'file',
type: 'file', data: JSON.stringify(files)
data: JSON.stringify(files)
})
} }
// text }
const text = clipboard.readText() // text
if (text.trim()) res({ type: 'text', data: text }) const text = clipboard.readText()
// image if (text.trim()) return { type: 'text', data: text }
const image = clipboard.readImage() // 大图卡顿来源 // image
const data = image.toDataURL() const image = clipboard.readImage() // 大图卡顿来源
globalImageOversize = data.length > 4e5 const data = image.toDataURL()
if (!image.isEmpty()) { globalImageOversize = data.length > 4e5
res({ if (!image.isEmpty()) {
type: 'image', return {
data: data type: 'image',
}) data: data
} }
}) }
} }
const watchClipboard = async (db, fn) => { const watchClipboard = async (db, fn) => {
let prev = db.dataBase.data[0] || {} let prev = db.dataBase.data[0] || {}
setInterval(() => { function loop() {
pbpaste().then((item) => { time.sleep(250).then(loop)
item.id = crypto.createHash('md5').update(item.data).digest('hex') const item = pbpaste()
if (item && prev.id != item.id) { if (!item) return
// 剪切板元素 与最近一次复制内容不同 item.id = crypto.createHash('md5').update(item.data).digest('hex')
prev = item if (item && prev.id != item.id) {
fn(item) // 剪切板元素 与最近一次复制内容不同
} else { prev = item
// 剪切板元素 与上次复制内容相同 fn(item)
} } else {
}) // 剪切板元素 与上次复制内容相同
}, 250) }
}
loop()
} }
const copy = (item) => { const copy = (item, isHideMainWindow = true) => {
switch (item.type) { switch (item.type) {
case 'text': case 'text':
utools.copyText(item.data) utools.copyText(item.data)
@ -173,7 +178,7 @@ const copy = (item) => {
utools.copyFile(paths) utools.copyFile(paths)
break break
} }
utools.hideMainWindow() isHideMainWindow && utools.hideMainWindow()
} }
const paste = () => { const paste = () => {
@ -186,7 +191,13 @@ db.init()
const remove = (item) => db.removeItemViaId(item.id) const remove = (item) => db.removeItemViaId(item.id)
const focus = () => document.querySelector('.clip-search input')?.focus() const select = () => document.querySelector('.clip-search input').select()
const focus = () => {
document.querySelector('.clip-search-input').style.display !== 'none'
? document.querySelector('.clip-search-input')?.focus()
: (document.querySelector('.clip-search-btn')?.click(),
document.querySelector('.clip-search-input')?.focus())
}
const toTop = () => (document.scrollingElement.scrollTop = 0) const toTop = () => (document.scrollingElement.scrollTop = 0)
const resetNav = () => document.querySelectorAll('.clip-switch-item')[0]?.click() const resetNav = () => document.querySelectorAll('.clip-switch-item')[0]?.click()
@ -208,8 +219,8 @@ utools.onPluginEnter(() => {
utools.copyText('ImageOverSized') utools.copyText('ImageOverSized')
globalImageOversize = false globalImageOversize = false
} }
document.querySelector('.clip-search input').select() // 进入插件将搜索框内容全选
focus() focus()
select() // 进入插件将搜索框内容全选
toTop() toTop()
resetNav() resetNav()
}) })
@ -219,6 +230,7 @@ window.copy = copy
window.paste = paste window.paste = paste
window.remove = remove window.remove = remove
window.openFile = utools.shellOpenPath window.openFile = utools.shellOpenPath
window.openFileFolder = utools.shellShowItemInFolder
window.getIcon = utools.getFileIcon window.getIcon = utools.getFileIcon
window.focus = focus window.focus = focus
window.toTop = toTop window.toTop = toTop

68
public/time.js Normal file
View File

@ -0,0 +1,68 @@
// author: inu1255
const path = require('path')
function newPromise(fn) {
let a, b
var tmp = {
resolve(x) {
if (this.pending) {
a(x)
this.resolved = true
this.pending = false
}
},
reject(e) {
if (this.pending) {
b(e)
this.rejectd = true
this.pending = false
}
},
pending: true,
resolved: false,
rejected: false
}
/** @type {Promise<T>} */
var pms = new Promise(function (resolve, reject) {
a = resolve
b = reject
if (fn) fn(tmp.resolve, tmp.reject)
})
return Object.assign(pms, tmp)
}
let cbIdx = 1
const cbMap = new Map()
function getWorker() {
if (getWorker.worker) return getWorker.worker
const worker = new Worker(path.join(__dirname, 'time.worker.js'))
getWorker.worker = worker
worker.onmessage = (e) => {
if (e.data && cbMap.has(e.data.cb)) {
cbMap.get(e.data.cb).apply(null, e.data.args)
}
}
return worker
}
function call(method, args) {
const cb = cbIdx++
let pms = newPromise()
cbMap.set(cb, function (err, data) {
if (err) pms.reject(err)
else pms.resolve(data)
})
getWorker().postMessage({
method,
args,
cb
})
return pms
}
function sleep(ms) {
return call('sleep', [ms])
}
exports.sleep = sleep

21
public/time.worker.js Normal file
View File

@ -0,0 +1,21 @@
// author: inu1255
const apis = {
sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
}
onmessage = (event) => {
const data = event.data
if (!data) return
const { cb, method, args } = data
if (!apis[method]) {
postMessage({ cb, err: 'no such method' })
return
}
apis[method].apply(null, args).then(
(res) => postMessage({ cb, data: res }),
(err) => postMessage({ cb, err })
)
}

18
src/cpns/ClipFloatBtn.vue Normal file
View File

@ -0,0 +1,18 @@
<template>
<div class="clip-float-btn">
<div @click="restoreDataBase">🧭</div>
</div>
</template>
<script setup>
// const emit = defineEmits(['onBtnClick'])
// const handleBtnClick = () => emit('onBtnClick')
const restoreDataBase = () => {
//
const flag = window.confirm('确定要清空剪贴板记录吗?')
if (flag) {
window.db.emptyDataBase()
updateShowList('all')
}
}
</script>

View File

@ -1,12 +1,23 @@
<template> <template>
<div class="clip-full-data"> <div class="clip-full-data">
<Transition name="fade"> <Transition name="fade">
<div class="clip-full" v-show="isShow"> <div class="clip-full-wrapper" v-show="isShow">
<div v-if="fullData.type === 'text'"> <div class="clip-full-operate-list">
<div v-text="fullData.data"></div> <template v-for="{ id, name } of btns">
<div
class="clip-full-operate-list-item"
v-if="id !== 'word-split' || (id === 'word-split' && fullData.type !== 'file')"
@click="handleBtnClick(id)"
>
{{ name }}
</div>
</template>
</div> </div>
<div v-else> <template v-if="fullData.type === 'text'">
<FileList :data="fullData.data"></FileList> <div class="clip-full-content" v-text="fullData.data"></div>
</template>
<div v-else class="clip-full-content">
<FileList :data="JSON.parse(fullData.data)"></FileList>
</div> </div>
</div> </div>
</Transition> </Transition>
@ -32,6 +43,28 @@ const props = defineProps({
const emit = defineEmits(['onOverlayClick']) const emit = defineEmits(['onOverlayClick'])
const onOverlayClick = () => emit('onOverlayClick') const onOverlayClick = () => emit('onOverlayClick')
const btns = [
{
id: 'copy-all',
name: '📄 复制全部'
},
{
id: 'word-split',
name: '🎁 智慧分词'
}
]
const handleBtnClick = (id) => {
switch (id) {
case 'copy-all':
window.copy(props.fullData)
emit('onOverlayClick') // 退
break
case 'word-split':
window.alert('增值服务 Comming Soon...')
break
}
}
onMounted(() => { onMounted(() => {
document.addEventListener('keydown', (e) => { document.addEventListener('keydown', (e) => {
const { key } = e const { key } = e
@ -48,10 +81,11 @@ onMounted(() => {
@import '../style'; @import '../style';
.fade-enter-active, .fade-enter-active,
.fade-leave-active { .fade-leave-active {
transition: opacity 0.15s ease; transition: all 0.15s ease;
} }
.fade-enter-from, .fade-enter-from,
.fade-leave-to { .fade-leave-to {
width: 0px;
opacity: 0; opacity: 0;
} }
</style> </style>

View File

@ -14,7 +14,7 @@
<template v-if="item.type === 'text'"> <template v-if="item.type === 'text'">
<span <span
class="clip-data-status" class="clip-data-status"
v-if="item.data.split(`\n`).length - 1 > 8" v-if="item.data.split(`\n`).length - 1 > 7"
@click.stop="handleDataClick(item)" @click.stop="handleDataClick(item)"
> >
查看全部 查看全部
@ -27,7 +27,7 @@
<template v-if="item.type === 'file'"> <template v-if="item.type === 'file'">
<span <span
class="clip-data-status" class="clip-data-status"
v-if="JSON.parse(item.data).length >= 8" v-if="JSON.parse(item.data).length >= 7"
@click.stop="handleDataClick(item)" @click.stop="handleDataClick(item)"
> >
查看全部 查看全部
@ -37,7 +37,7 @@
</div> </div>
<div class="clip-data"> <div class="clip-data">
<template v-if="item.type === 'text'"> <template v-if="item.type === 'text'">
<div>{{ item.data.split(`\n`).slice(0, 8).join(`\n`).trim() }}</div> <div>{{ item.data.split(`\n`).slice(0, 7).join(`\n`).trim() }}</div>
</template> </template>
<template v-if="item.type === 'image'"> <template v-if="item.type === 'image'">
<img class="clip-data-image" :src="item.data" alt="Image" /> <img class="clip-data-image" :src="item.data" alt="Image" />
@ -48,14 +48,20 @@
</div> </div>
</div> </div>
<div class="clip-operate" v-show="activeIndex === index"> <div class="clip-operate" v-show="activeIndex === index">
<template v-for="{ id, title } of operation"> <template v-for="{ id, title, icon } of operation">
<div <div
v-if="id !== 'collect' || (id === 'collect' && item.collect !== true)" v-if="
(id !== 'collect' && id !== 'view' && id !== 'open-folder' && id !== 'un-collect') ||
(id === 'collect' && item.collect !== true) ||
(id === 'view' && item.type !== 'image') ||
(id === 'open-folder' && item.type === 'file') ||
(id === 'un-collect' && item.collect === true)
"
:class="id" :class="id"
:title="title" :title="title"
@click.stop="handleOperateClick({ id, item })" @click.stop="handleOperateClick({ id, item })"
> >
{{ title.slice(0, 1) }} {{ icon }}
</div> </div>
</template> </template>
</div> </div>
@ -96,17 +102,32 @@ const handleDataClick = (item) => emit('onDataChange', item)
const activeIndex = ref(0) const activeIndex = ref(0)
const handleMouseOver = (index) => (activeIndex.value = index) const handleMouseOver = (index) => (activeIndex.value = index)
const operation = [ const operation = [
{ id: 'copy', title: '复制' }, { id: 'copy', title: '复制', icon: '📄' },
{ id: 'collect', title: '收藏' }, { id: 'view', title: '查看全部', icon: '💬' },
{ id: 'remove', title: '删除' } { id: 'open-folder', title: '打开文件夹', icon: '📁' },
{ id: 'collect', title: '收藏', icon: '⭐' },
{ id: 'un-collect', title: '取消收藏', icon: '📤' },
{ id: 'remove', title: '删除', icon: '❌' }
] ]
const handleOperateClick = ({ id, item }) => { const handleOperateClick = ({ id, item }) => {
switch (id) { switch (id) {
case 'copy': case 'copy':
window.copy(item) window.copy(item, false)
break
case 'view':
emit('onDataChange', item)
break
case 'open-folder':
const { data } = item
const fl = JSON.parse(data)
window.openFileFolder(fl[0].path) //
break break
case 'collect': case 'collect':
item.collect = true // important item.collect = true
window.db.updateDataBaseLocal(db)
break
case 'un-collect':
item.collect = undefined
window.db.updateDataBaseLocal(db) window.db.updateDataBaseLocal(db)
break break
case 'remove': case 'remove':

View File

@ -1,6 +1,12 @@
<template> <template>
<div class="clip-search"> <div class="clip-search">
<span class="clip-search-btn" v-show="!filterText && !isFocus" @click="toggleFocusStatus(true)"
>🔍</span
>
<input <input
class="clip-search-input"
@focusout="toggleFocusStatus(false)"
v-show="filterText || isFocus"
v-model="filterText" v-model="filterText"
type="text" type="text"
:placeholder="itemCount ? `🔍 在${itemCount}条历史中检索...` : '🔍 检索剪贴板历史...'" :placeholder="itemCount ? `🔍 在${itemCount}条历史中检索...` : '🔍 检索剪贴板历史...'"
@ -10,7 +16,7 @@
</template> </template>
<script setup> <script setup>
import { ref, watch } from 'vue' import { ref, watch, nextTick } from 'vue'
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
type: String, type: String,
@ -20,6 +26,11 @@ const props = defineProps({
type: Number type: Number
} }
}) })
const isFocus = ref(true)
const toggleFocusStatus = (status) =>
status ? ((isFocus.value = status), nextTick(() => window.focus())) : (isFocus.value = status)
const filterText = ref('') const filterText = ref('')
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
// filterText modelValue // filterText modelValue

View File

@ -0,0 +1,22 @@
.clip-float-btn {
display: flex;
justify-content: center;
align-items: center;
position: fixed;
z-index: 99999;
bottom: 15px;
right: 15px;
height: 50px;
width: 50px;
cursor: pointer;
border-radius: 50%;
font-size: 20px;
background-color: @text-bg-color-lighter;
user-select: none;
&:hover {
font-size: 25px;
color: @bg-color;
background-color: @primary-color;
transition: all 0.15s;
}
}

View File

@ -1,15 +1,44 @@
.clip-full { .clip-full-wrapper {
z-index: 9999999999; z-index: 9999999999;
position: fixed; position: fixed;
top: 0; top: 0;
height: 100%; height: -webkit-fill-available;
width: 70%; width: 70%;
color: @text-color; color: @text-color;
background: @bg-color; background: @bg-color;
padding: 0px 20px 0px 20px; margin: 0px 0px;
padding: 10px 20px;
overflow-y: scroll; overflow-y: scroll;
word-break: break-all; word-break: break-all;
white-space: pre-wrap; white-space: pre-wrap;
.clip-full-operate-list {
display: flex;
align-items: center;
justify-content: flex-end;
color: @text-color;
background-color: @text-bg-color;
padding: 5px 10px;
margin-bottom: 5px;
border-radius: 5px;
&-item {
padding: 10px;
border-radius: 5px;
cursor: pointer;
user-select: none;
margin: 5px 5px;
background-color: @bg-color;
&:hover {
color: @bg-color;
background-color: @primary-color;
transition: all 0.15s;
}
}
}
.clip-full-content {
background-color: @text-bg-color;
padding: 20px;
border-radius: 5px;
}
&::-webkit-scrollbar { &::-webkit-scrollbar {
width: 10px; width: 10px;
height: 10px; height: 10px;
@ -25,9 +54,9 @@
&::-webkit-scrollbar-thumb { &::-webkit-scrollbar-thumb {
background: @text-color-lighter; background: @text-color-lighter;
border-radius: 5px; border-radius: 5px;
} &:hover {
&::-webkit-scrollbar-thumb:hover { background: @text-color;
background: @text-color; }
} }
} }
.clip-overlay { .clip-overlay {

View File

@ -52,14 +52,15 @@
display: flex; display: flex;
overflow: hidden; overflow: hidden;
word-break: break-all; word-break: break-all;
max-height: 150px; max-height: 120px;
max-width: 500px;
padding: 5px; padding: 5px;
white-space: pre-wrap; white-space: pre-wrap;
flex-direction: column; flex-direction: column;
color: @text-color; color: @text-color;
img.clip-data-image { img.clip-data-image {
// 此 class用于区分 file的 image // 此 class用于区分 file的 image
max-height: 140px; // 比外框 max-height少一点 因为有 5px的边框 max-height: 100px; // 比外框 max-height少一点 因为有 5px的边框
max-width: 90%; max-width: 90%;
box-shadow: 0px 0px 3px @text-color; box-shadow: 0px 0px 3px @text-color;
} }
@ -79,8 +80,8 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
min-width: 100px; min-width: 150px;
padding: 10px; padding: 0px 10px;
& * { & * {
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -1,6 +1,16 @@
.clip-search { .clip-search {
display: flex;
align-items: center;
justify-content: flex-end;
min-width: 300px; min-width: 300px;
input { margin-right: 10px;
.clip-search-btn {
width: 25px;
height: 25px;
padding: 10px;
cursor: pointer;
}
.clip-search-input {
width: 90%; width: 90%;
/* normalize */ /* normalize */
background: none; background: none;

View File

@ -7,7 +7,7 @@
&:hover { &:hover {
text-decoration: underline; text-decoration: underline;
&::after { &::after {
content: '📤'; content: '📝';
} }
} }
.clip-file-icon { .clip-file-icon {

View File

@ -1,5 +1,6 @@
.import() { .import() {
/* 导入全部涉及到变量的样式文件 */ /* 导入全部涉及到变量的样式文件 */
@import (multiple) './cpns/clip-float-btn.less';
@import (multiple) './cpns/clip-full-data.less'; @import (multiple) './cpns/clip-full-data.less';
@import (multiple) './cpns/clip-item-list.less'; @import (multiple) './cpns/clip-item-list.less';
@import (multiple) './cpns/clip-search.less'; @import (multiple) './cpns/clip-search.less';

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="main"> <div class="main">
<div class="clip-restore" @click="restoreDataBase">🧺</div> <ClipFloatBtn></ClipFloatBtn>
<ClipFullData <ClipFullData
:isShow="fullDataShow" :isShow="fullDataShow"
:fullData="fullData" :fullData="fullData"
@ -29,6 +29,7 @@ import ClipItemList from '../cpns/ClipItemList.vue'
import ClipFullData from '../cpns/ClipFullData.vue' import ClipFullData from '../cpns/ClipFullData.vue'
import ClipSearch from '../cpns/ClipSearch.vue' import ClipSearch from '../cpns/ClipSearch.vue'
import ClipSwitch from '../cpns/ClipSwitch.vue' import ClipSwitch from '../cpns/ClipSwitch.vue'
import ClipFloatBtn from '../cpns/ClipFloatBtn.vue'
const GAP = 15 // const GAP = 15 //
const offset = ref(0) // const offset = ref(0) //
@ -43,12 +44,25 @@ const updateShowList = (type) => {
type === 'collect' ? item.collect === true : type === 'all' ? item : item.type === type type === 'collect' ? item.collect === true : type === 'all' ? item : item.type === type
) // collect type ) // collect type
.filter((item) => (filterText.value ? item.type !== 'image' : item)) // DataURL .filter((item) => (filterText.value ? item.type !== 'image' : item)) // DataURL
.filter( .filter((item) => {
(item) => if (filterText.value.trim()) {
filterText.value if (filterText.value.trim().indexOf(' ') !== -1) {
? item.data.toLowerCase().indexOf(filterText.value.toLowerCase()) !== -1 // //
: item // const hitArray = []
) for (const f of filterText.value.trim().split(' ')) {
hitArray.push(item.data.toLowerCase().indexOf(f.toLowerCase()) !== -1)
}
// false
return hitArray.indexOf(false) === -1
} else {
//
return item.data.toLowerCase().indexOf(filterText.value.trim().toLowerCase()) !== -1
}
} else {
//
return true
}
})
.slice(0, GAP) // .slice(0, GAP) //
window.toTop() window.toTop()
} }
@ -58,18 +72,13 @@ const handleNavClick = (type) => {
offset.value = 0 // offset.value = 0 //
} }
const fullData = ref({ type: 'text', data: '' }) const fullData = ref({ type: 'text' })
const fullDataShow = ref(false) const fullDataShow = ref(false)
const toggleFullData = (item) => { const toggleFullData = (item) => {
// () // ()
const { type, data } = item const { type } = item
// type: 'text' | 'file' if (type === 'text' || type === 'file') {
if (type === 'text') { fullData.value = item
fullData.value.type = 'text'
fullData.value.data = data
} else if (type === 'file') {
fullData.value.type = 'file'
fullData.value.data = JSON.parse(data)
} }
fullDataShow.value = !fullDataShow.value fullDataShow.value = !fullDataShow.value
} }
@ -82,15 +91,6 @@ const handleDataRemove = () => {
updateShowList(ClipSwitchRef.value.activeTab) updateShowList(ClipSwitchRef.value.activeTab)
} }
const restoreDataBase = () => {
//
const flag = window.confirm('确定要清空剪贴板记录吗?')
if (flag) {
window.db.emptyDataBase()
updateShowList('all')
}
}
onMounted(() => { onMounted(() => {
// Ref // Ref
const activeTab = computed(() => ClipSwitchRef.value.activeTab) const activeTab = computed(() => ClipSwitchRef.value.activeTab)
@ -143,6 +143,8 @@ onMounted(() => {
const isSearch = const isSearch =
(ctrlKey && (key === 'F' || key === 'f')) || (ctrlKey && (key === 'L' || key === 'l')) (ctrlKey && (key === 'F' || key === 'f')) || (ctrlKey && (key === 'L' || key === 'l'))
const isExit = key === 'Escape' const isExit = key === 'Escape'
const isArrow = key === 'ArrowDown' || key === 'ArrowUp'
const isEnter = key === 'Enter'
if (isTab) { if (isTab) {
const tabTypes = tabs.map((item) => item.type) const tabTypes = tabs.map((item) => item.type)
const index = tabTypes.indexOf(activeTab.value) const index = tabTypes.indexOf(activeTab.value)
@ -156,9 +158,8 @@ onMounted(() => {
filterText.value = '' filterText.value = ''
e.stopPropagation() e.stopPropagation()
} }
} else if (ctrlKey || metaKey) { } else if (ctrlKey || metaKey || isArrow || isEnter) {
// Ctrl // Ctrl (utools)
// utools
} else { } else {
window.focus() // window.focus() //
} }
@ -168,25 +169,6 @@ onMounted(() => {
<style lang="less" scoped> <style lang="less" scoped>
@import '../style'; @import '../style';
.clip-restore {
display: flex;
justify-content: center;
align-items: center;
position: fixed;
bottom: 10px;
right: 10px;
height: 50px;
width: 50px;
cursor: pointer;
border-radius: 50%;
font-size: 20px;
background-color: rgb(232, 232, 232);
user-select: none;
&:hover {
// background-color: @primary-color;
transition: all 0.15s;
}
}
.clip-break { .clip-break {
height: 60px; height: 60px;
} }

View File

@ -5,22 +5,25 @@ const path = require('path')
module.exports = { module.exports = {
publicPath: './', publicPath: './',
productionSourceMap: false, productionSourceMap: false,
chainWebpack: config => { chainWebpack: (config) => {
config.optimization config.optimization.minimizer('uglify-plugin').use(UglifyJsPlugin, [
.minimizer('uglify-plugin') {
.use(UglifyJsPlugin, [{
uglifyOptions: { uglifyOptions: {
drop_console: false, drop_console: false,
drop_debugger: false, drop_debugger: false,
pure_funcs: ['console.log'] pure_funcs: ['console.log']
} }
}]) }
config.plugin('copy-plugin') ])
.use(CopyPlugin, [{ config.plugin('copy-plugin').use(CopyPlugin, [
patterns: [{ {
from: path.join(__dirname, 'README.md'), patterns: [
to: path.join(__dirname, 'dist', 'README.md'), {
}], from: path.join(__dirname, 'README.md'),
}]) to: path.join(__dirname, 'dist', 'README.md')
}, }
} ]
}
])
}
}