mirror of
https://github.com/ZiuChen/ClipboardManager.git
synced 2025-06-07 13:54:05 +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": "超级剪贴板",
|
"pluginName": "超级剪贴板",
|
||||||
"description": "强大的剪贴板管理工具",
|
"description": "强大的剪贴板管理工具",
|
||||||
"author": "ZiuChen",
|
"author": "ZiuChen",
|
||||||
|
@ -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
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>
|
<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>
|
||||||
|
@ -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':
|
||||||
|
@ -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的值
|
||||||
|
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;
|
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 {
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
&:hover {
|
&:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
&::after {
|
&::after {
|
||||||
content: '📤';
|
content: '📝';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.clip-file-icon {
|
.clip-file-icon {
|
||||||
|
@ -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';
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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')
|
||||||
},
|
}
|
||||||
}
|
]
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user