mirror of
https://github.com/ZiuChen/ClipboardManager.git
synced 2025-06-07 22:04:06 +08:00
commit
c37c42ff94
BIN
docs/img1.png
BIN
docs/img1.png
Binary file not shown.
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 47 KiB |
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.1.6",
|
||||
"version": "1.2.2",
|
||||
"pluginName": "超级剪贴板",
|
||||
"description": "强大的剪贴板管理工具",
|
||||
"author": "ZiuChen",
|
||||
|
@ -7,6 +7,7 @@
|
||||
const fs = require('fs')
|
||||
const crypto = require('crypto')
|
||||
const { clipboard } = require('electron')
|
||||
const time = require('./time')
|
||||
|
||||
const homePath = utools.getPath('home')
|
||||
const userDataPath = utools.getPath('userData')
|
||||
@ -36,10 +37,12 @@ class DB {
|
||||
// 读取磁盘记录到内存
|
||||
const dataBase = JSON.parse(data)
|
||||
this.dataBase = dataBase
|
||||
// 将超过14天的数据删除
|
||||
// 将超过14天的数据删除 排除掉收藏
|
||||
const now = new Date().getTime()
|
||||
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()
|
||||
} catch (err) {
|
||||
utools.showNotification('读取剪切板出错' + err)
|
||||
@ -71,14 +74,16 @@ class DB {
|
||||
addItem(cItem) {
|
||||
this.dataBase.data.unshift(cItem)
|
||||
this.updateDataBase()
|
||||
// unicode
|
||||
if (this.dataBase.data.length > '\u0035\u0030\u0030') {
|
||||
// 达到条数限制
|
||||
this.dataBase.data.pop()
|
||||
// 仍然大于: 超出了不止一条
|
||||
if (this.dataBase.data.length > '\u0035\u0030\u0030') {
|
||||
this.dataBase.data = this.dataBase.data.splice(0, 499)
|
||||
const exceedCount = this.dataBase.data.length - '\u0035\u0030\u0030'
|
||||
if (exceedCount > 0) {
|
||||
// 达到条数限制 在收藏条数限制内遍历非收藏历史并删除
|
||||
// 所有被移除的 item都存入tempList
|
||||
const tmpList = []
|
||||
for (let i = 0; i < exceedCount; i++) {
|
||||
const item = this.dataBase.data.pop()
|
||||
tmpList.push(item)
|
||||
}
|
||||
tmpList.forEach((item) => !item.collect || this.dataBase.data.push(item)) // 收藏内容 重新入栈
|
||||
}
|
||||
this.updateDataBaseLocal()
|
||||
}
|
||||
@ -118,49 +123,49 @@ class DB {
|
||||
}
|
||||
}
|
||||
|
||||
const pbpaste = async () => {
|
||||
return new Promise((res) => {
|
||||
// file
|
||||
const files = utools.getCopyedFiles() // null | Array
|
||||
if (files) {
|
||||
res({
|
||||
type: 'file',
|
||||
data: JSON.stringify(files)
|
||||
})
|
||||
const pbpaste = () => {
|
||||
// file
|
||||
const files = utools.getCopyedFiles() // null | Array
|
||||
if (files) {
|
||||
return {
|
||||
type: 'file',
|
||||
data: JSON.stringify(files)
|
||||
}
|
||||
// text
|
||||
const text = clipboard.readText()
|
||||
if (text.trim()) res({ type: 'text', data: text })
|
||||
// image
|
||||
const image = clipboard.readImage() // 大图卡顿来源
|
||||
const data = image.toDataURL()
|
||||
globalImageOversize = data.length > 4e5
|
||||
if (!image.isEmpty()) {
|
||||
res({
|
||||
type: 'image',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
// text
|
||||
const text = clipboard.readText()
|
||||
if (text.trim()) return { type: 'text', data: text }
|
||||
// image
|
||||
const image = clipboard.readImage() // 大图卡顿来源
|
||||
const data = image.toDataURL()
|
||||
globalImageOversize = data.length > 4e5
|
||||
if (!image.isEmpty()) {
|
||||
return {
|
||||
type: 'image',
|
||||
data: data
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const watchClipboard = async (db, fn) => {
|
||||
let prev = db.dataBase.data[0] || {}
|
||||
setInterval(() => {
|
||||
pbpaste().then((item) => {
|
||||
item.id = crypto.createHash('md5').update(item.data).digest('hex')
|
||||
if (item && prev.id != item.id) {
|
||||
// 剪切板元素 与最近一次复制内容不同
|
||||
prev = item
|
||||
fn(item)
|
||||
} else {
|
||||
// 剪切板元素 与上次复制内容相同
|
||||
}
|
||||
})
|
||||
}, 250)
|
||||
function loop() {
|
||||
time.sleep(250).then(loop)
|
||||
const item = pbpaste()
|
||||
if (!item) return
|
||||
item.id = crypto.createHash('md5').update(item.data).digest('hex')
|
||||
if (item && prev.id != item.id) {
|
||||
// 剪切板元素 与最近一次复制内容不同
|
||||
prev = item
|
||||
fn(item)
|
||||
} else {
|
||||
// 剪切板元素 与上次复制内容相同
|
||||
}
|
||||
}
|
||||
loop()
|
||||
}
|
||||
|
||||
const copy = (item) => {
|
||||
const copy = (item, isHideMainWindow = true) => {
|
||||
switch (item.type) {
|
||||
case 'text':
|
||||
utools.copyText(item.data)
|
||||
@ -173,7 +178,7 @@ const copy = (item) => {
|
||||
utools.copyFile(paths)
|
||||
break
|
||||
}
|
||||
utools.hideMainWindow()
|
||||
isHideMainWindow && utools.hideMainWindow()
|
||||
}
|
||||
|
||||
const paste = () => {
|
||||
@ -186,7 +191,13 @@ db.init()
|
||||
|
||||
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 resetNav = () => document.querySelectorAll('.clip-switch-item')[0]?.click()
|
||||
|
||||
@ -208,8 +219,8 @@ utools.onPluginEnter(() => {
|
||||
utools.copyText('ImageOverSized')
|
||||
globalImageOversize = false
|
||||
}
|
||||
document.querySelector('.clip-search input').select() // 进入插件将搜索框内容全选
|
||||
focus()
|
||||
select() // 进入插件将搜索框内容全选
|
||||
toTop()
|
||||
resetNav()
|
||||
})
|
||||
@ -219,6 +230,7 @@ window.copy = copy
|
||||
window.paste = paste
|
||||
window.remove = remove
|
||||
window.openFile = utools.shellOpenPath
|
||||
window.openFileFolder = utools.shellShowItemInFolder
|
||||
window.getIcon = utools.getFileIcon
|
||||
window.focus = focus
|
||||
window.toTop = toTop
|
||||
|
68
public/time.js
Normal file
68
public/time.js
Normal 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
21
public/time.worker.js
Normal 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
18
src/cpns/ClipFloatBtn.vue
Normal 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>
|
@ -1,12 +1,23 @@
|
||||
<template>
|
||||
<div class="clip-full-data">
|
||||
<Transition name="fade">
|
||||
<div class="clip-full" v-show="isShow">
|
||||
<div v-if="fullData.type === 'text'">
|
||||
<div v-text="fullData.data"></div>
|
||||
<div class="clip-full-wrapper" v-show="isShow">
|
||||
<div class="clip-full-operate-list">
|
||||
<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 v-else>
|
||||
<FileList :data="fullData.data"></FileList>
|
||||
<template v-if="fullData.type === 'text'">
|
||||
<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>
|
||||
</Transition>
|
||||
@ -32,6 +43,28 @@ const props = defineProps({
|
||||
const emit = defineEmits(['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(() => {
|
||||
document.addEventListener('keydown', (e) => {
|
||||
const { key } = e
|
||||
@ -48,10 +81,11 @@ onMounted(() => {
|
||||
@import '../style';
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.15s ease;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
width: 0px;
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
@ -14,7 +14,7 @@
|
||||
<template v-if="item.type === 'text'">
|
||||
<span
|
||||
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)"
|
||||
>
|
||||
查看全部
|
||||
@ -27,7 +27,7 @@
|
||||
<template v-if="item.type === 'file'">
|
||||
<span
|
||||
class="clip-data-status"
|
||||
v-if="JSON.parse(item.data).length >= 8"
|
||||
v-if="JSON.parse(item.data).length >= 7"
|
||||
@click.stop="handleDataClick(item)"
|
||||
>
|
||||
查看全部
|
||||
@ -37,7 +37,7 @@
|
||||
</div>
|
||||
<div class="clip-data">
|
||||
<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 v-if="item.type === 'image'">
|
||||
<img class="clip-data-image" :src="item.data" alt="Image" />
|
||||
@ -48,14 +48,20 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="clip-operate" v-show="activeIndex === index">
|
||||
<template v-for="{ id, title } of operation">
|
||||
<template v-for="{ id, title, icon } of operation">
|
||||
<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"
|
||||
:title="title"
|
||||
@click.stop="handleOperateClick({ id, item })"
|
||||
>
|
||||
{{ title.slice(0, 1) }}
|
||||
{{ icon }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@ -96,17 +102,32 @@ const handleDataClick = (item) => emit('onDataChange', item)
|
||||
const activeIndex = ref(0)
|
||||
const handleMouseOver = (index) => (activeIndex.value = index)
|
||||
const operation = [
|
||||
{ id: 'copy', title: '复制' },
|
||||
{ id: 'collect', title: '收藏' },
|
||||
{ id: 'remove', title: '删除' }
|
||||
{ id: 'copy', title: '复制', icon: '📄' },
|
||||
{ id: 'view', title: '查看全部', icon: '💬' },
|
||||
{ id: 'open-folder', title: '打开文件夹', icon: '📁' },
|
||||
{ id: 'collect', title: '收藏', icon: '⭐' },
|
||||
{ id: 'un-collect', title: '取消收藏', icon: '📤' },
|
||||
{ id: 'remove', title: '删除', icon: '❌' }
|
||||
]
|
||||
const handleOperateClick = ({ id, item }) => {
|
||||
switch (id) {
|
||||
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
|
||||
case 'collect':
|
||||
item.collect = true // important
|
||||
item.collect = true
|
||||
window.db.updateDataBaseLocal(db)
|
||||
break
|
||||
case 'un-collect':
|
||||
item.collect = undefined
|
||||
window.db.updateDataBaseLocal(db)
|
||||
break
|
||||
case 'remove':
|
||||
|
@ -1,6 +1,12 @@
|
||||
<template>
|
||||
<div class="clip-search">
|
||||
<span class="clip-search-btn" v-show="!filterText && !isFocus" @click="toggleFocusStatus(true)"
|
||||
>🔍</span
|
||||
>
|
||||
<input
|
||||
class="clip-search-input"
|
||||
@focusout="toggleFocusStatus(false)"
|
||||
v-show="filterText || isFocus"
|
||||
v-model="filterText"
|
||||
type="text"
|
||||
:placeholder="itemCount ? `🔍 在${itemCount}条历史中检索...` : '🔍 检索剪贴板历史...'"
|
||||
@ -10,7 +16,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { ref, watch, nextTick } from 'vue'
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
@ -20,6 +26,11 @@ const props = defineProps({
|
||||
type: Number
|
||||
}
|
||||
})
|
||||
|
||||
const isFocus = ref(true)
|
||||
const toggleFocusStatus = (status) =>
|
||||
status ? ((isFocus.value = status), nextTick(() => window.focus())) : (isFocus.value = status)
|
||||
|
||||
const filterText = ref('')
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
// filterText变了 通知父组件修改 modelValue的值
|
||||
|
22
src/style/cpns/clip-float-btn.less
Normal file
22
src/style/cpns/clip-float-btn.less
Normal 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;
|
||||
}
|
||||
}
|
@ -1,15 +1,44 @@
|
||||
.clip-full {
|
||||
.clip-full-wrapper {
|
||||
z-index: 9999999999;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
height: -webkit-fill-available;
|
||||
width: 70%;
|
||||
color: @text-color;
|
||||
background: @bg-color;
|
||||
padding: 0px 20px 0px 20px;
|
||||
margin: 0px 0px;
|
||||
padding: 10px 20px;
|
||||
overflow-y: scroll;
|
||||
word-break: break-all;
|
||||
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 {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
@ -25,9 +54,9 @@
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: @text-color-lighter;
|
||||
border-radius: 5px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background: @text-color;
|
||||
&:hover {
|
||||
background: @text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
.clip-overlay {
|
||||
|
@ -52,14 +52,15 @@
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
max-height: 150px;
|
||||
max-height: 120px;
|
||||
max-width: 500px;
|
||||
padding: 5px;
|
||||
white-space: pre-wrap;
|
||||
flex-direction: column;
|
||||
color: @text-color;
|
||||
img.clip-data-image {
|
||||
// 此 class用于区分 file的 image
|
||||
max-height: 140px; // 比外框 max-height少一点 因为有 5px的边框
|
||||
max-height: 100px; // 比外框 max-height少一点 因为有 5px的边框
|
||||
max-width: 90%;
|
||||
box-shadow: 0px 0px 3px @text-color;
|
||||
}
|
||||
@ -79,8 +80,8 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 100px;
|
||||
padding: 10px;
|
||||
min-width: 150px;
|
||||
padding: 0px 10px;
|
||||
& * {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -1,6 +1,16 @@
|
||||
.clip-search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
min-width: 300px;
|
||||
input {
|
||||
margin-right: 10px;
|
||||
.clip-search-btn {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.clip-search-input {
|
||||
width: 90%;
|
||||
/* normalize */
|
||||
background: none;
|
||||
|
@ -7,7 +7,7 @@
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
&::after {
|
||||
content: '📤';
|
||||
content: '📝';
|
||||
}
|
||||
}
|
||||
.clip-file-icon {
|
||||
|
@ -1,5 +1,6 @@
|
||||
.import() {
|
||||
/* 导入全部涉及到变量的样式文件 */
|
||||
@import (multiple) './cpns/clip-float-btn.less';
|
||||
@import (multiple) './cpns/clip-full-data.less';
|
||||
@import (multiple) './cpns/clip-item-list.less';
|
||||
@import (multiple) './cpns/clip-search.less';
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="main">
|
||||
<div class="clip-restore" @click="restoreDataBase">🧺</div>
|
||||
<ClipFloatBtn></ClipFloatBtn>
|
||||
<ClipFullData
|
||||
:isShow="fullDataShow"
|
||||
:fullData="fullData"
|
||||
@ -29,6 +29,7 @@ import ClipItemList from '../cpns/ClipItemList.vue'
|
||||
import ClipFullData from '../cpns/ClipFullData.vue'
|
||||
import ClipSearch from '../cpns/ClipSearch.vue'
|
||||
import ClipSwitch from '../cpns/ClipSwitch.vue'
|
||||
import ClipFloatBtn from '../cpns/ClipFloatBtn.vue'
|
||||
|
||||
const GAP = 15 // 懒加载 每次添加的条数
|
||||
const offset = ref(0) // 懒加载 偏移量
|
||||
@ -43,12 +44,25 @@ const updateShowList = (type) => {
|
||||
type === 'collect' ? item.collect === true : type === 'all' ? item : item.type === type
|
||||
) // 是 collect则返回所有收藏 否则按照 type返回
|
||||
.filter((item) => (filterText.value ? item.type !== 'image' : item)) // 有过滤词 排除掉图片 DataURL
|
||||
.filter(
|
||||
(item) =>
|
||||
filterText.value
|
||||
? item.data.toLowerCase().indexOf(filterText.value.toLowerCase()) !== -1 // 有过滤词 不区分大小写检索
|
||||
: item // 无过滤词 返回全部
|
||||
)
|
||||
.filter((item) => {
|
||||
if (filterText.value.trim()) {
|
||||
if (filterText.value.trim().indexOf(' ') !== -1) {
|
||||
// 有过滤词 有空格
|
||||
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) // 重新切分懒加载列表
|
||||
window.toTop()
|
||||
}
|
||||
@ -58,18 +72,13 @@ const handleNavClick = (type) => {
|
||||
offset.value = 0 // 重置懒加载偏移量
|
||||
}
|
||||
|
||||
const fullData = ref({ type: 'text', data: '' })
|
||||
const fullData = ref({ type: 'text' })
|
||||
const fullDataShow = ref(false)
|
||||
const toggleFullData = (item) => {
|
||||
// 是否显示全部数据 (查看全部)
|
||||
const { type, data } = item
|
||||
// type: 'text' | 'file'
|
||||
if (type === 'text') {
|
||||
fullData.value.type = 'text'
|
||||
fullData.value.data = data
|
||||
} else if (type === 'file') {
|
||||
fullData.value.type = 'file'
|
||||
fullData.value.data = JSON.parse(data)
|
||||
const { type } = item
|
||||
if (type === 'text' || type === 'file') {
|
||||
fullData.value = item
|
||||
}
|
||||
fullDataShow.value = !fullDataShow.value
|
||||
}
|
||||
@ -82,15 +91,6 @@ const handleDataRemove = () => {
|
||||
updateShowList(ClipSwitchRef.value.activeTab)
|
||||
}
|
||||
|
||||
const restoreDataBase = () => {
|
||||
// 情况数据库
|
||||
const flag = window.confirm('确定要清空剪贴板记录吗?')
|
||||
if (flag) {
|
||||
window.db.emptyDataBase()
|
||||
updateShowList('all')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 获取挂载的导航组件 Ref
|
||||
const activeTab = computed(() => ClipSwitchRef.value.activeTab)
|
||||
@ -143,6 +143,8 @@ onMounted(() => {
|
||||
const isSearch =
|
||||
(ctrlKey && (key === 'F' || key === 'f')) || (ctrlKey && (key === 'L' || key === 'l'))
|
||||
const isExit = key === 'Escape'
|
||||
const isArrow = key === 'ArrowDown' || key === 'ArrowUp'
|
||||
const isEnter = key === 'Enter'
|
||||
if (isTab) {
|
||||
const tabTypes = tabs.map((item) => item.type)
|
||||
const index = tabTypes.indexOf(activeTab.value)
|
||||
@ -156,9 +158,8 @@ onMounted(() => {
|
||||
filterText.value = ''
|
||||
e.stopPropagation()
|
||||
}
|
||||
} else if (ctrlKey || metaKey) {
|
||||
// 仅有 Ctrl时 什么也不执行
|
||||
// utools模拟执行粘贴时触发
|
||||
} else if (ctrlKey || metaKey || isArrow || isEnter) {
|
||||
// 仅有 Ctrl时 什么也不执行 (utools模拟执行粘贴时触发)
|
||||
} else {
|
||||
window.focus() // 其他键盘事件 直接聚焦搜索框
|
||||
}
|
||||
@ -168,25 +169,6 @@ onMounted(() => {
|
||||
|
||||
<style lang="less" scoped>
|
||||
@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 {
|
||||
height: 60px;
|
||||
}
|
||||
|
@ -5,22 +5,25 @@ const path = require('path')
|
||||
module.exports = {
|
||||
publicPath: './',
|
||||
productionSourceMap: false,
|
||||
chainWebpack: config => {
|
||||
config.optimization
|
||||
.minimizer('uglify-plugin')
|
||||
.use(UglifyJsPlugin, [{
|
||||
chainWebpack: (config) => {
|
||||
config.optimization.minimizer('uglify-plugin').use(UglifyJsPlugin, [
|
||||
{
|
||||
uglifyOptions: {
|
||||
drop_console: false,
|
||||
drop_debugger: false,
|
||||
pure_funcs: ['console.log']
|
||||
}
|
||||
}])
|
||||
config.plugin('copy-plugin')
|
||||
.use(CopyPlugin, [{
|
||||
patterns: [{
|
||||
from: path.join(__dirname, 'README.md'),
|
||||
to: path.join(__dirname, 'dist', 'README.md'),
|
||||
}],
|
||||
}])
|
||||
},
|
||||
}
|
||||
])
|
||||
config.plugin('copy-plugin').use(CopyPlugin, [
|
||||
{
|
||||
patterns: [
|
||||
{
|
||||
from: path.join(__dirname, 'README.md'),
|
||||
to: path.join(__dirname, 'dist', 'README.md')
|
||||
}
|
||||
]
|
||||
}
|
||||
])
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user