From d3458e679a0dc6638bce4e4eaef7a60fabd788dd Mon Sep 17 00:00:00 2001 From: Kuingsmile <96409857+Kuingsmile@users.noreply.github.com> Date: Fri, 12 Jun 2026 00:34:05 -0700 Subject: [PATCH] :sparkles: Feature(custom): support upload absolute file path text in clipboard ISSUES CLOSED: #538 --- src/main/utils/clipboardFilePath.ts | 46 ++++++++++++++++++++++++++ src/main/utils/common.ts | 7 +++- tests/clipboard-file-path.test.ts | 51 +++++++++++++++++++++++++++++ tests/getraw.test.ts | 2 +- 4 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 src/main/utils/clipboardFilePath.ts create mode 100644 tests/clipboard-file-path.test.ts diff --git a/src/main/utils/clipboardFilePath.ts b/src/main/utils/clipboardFilePath.ts new file mode 100644 index 00000000..bd82d2c7 --- /dev/null +++ b/src/main/utils/clipboardFilePath.ts @@ -0,0 +1,46 @@ +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +import fs from 'fs-extra' + +const unwrapClipboardPathText = (text: string): string => { + if ((text.startsWith('"') && text.endsWith('"')) || (text.startsWith("'") && text.endsWith("'"))) { + return text.slice(1, -1).trim() + } + return text +} + +const normalizeClipboardPathText = (text: string): string => { + const lines = text + .trim() + .split(/\r?\n/) + .map(line => line.trim()) + .filter(Boolean) + + if (lines.length !== 1) return '' + + const filePath = unwrapClipboardPathText(lines[0]) + if (!filePath) return '' + + if (/^file:\/\//i.test(filePath)) { + try { + return fileURLToPath(filePath) + } catch (_e) { + return '' + } + } + + return filePath +} + +export const getClipboardTextFilePath = (text: string): string => { + const filePath = normalizeClipboardPathText(text) + if (!filePath || !path.isAbsolute(filePath)) return '' + + try { + const stats = fs.statSync(filePath) + return stats.isFile() ? path.normalize(filePath) : '' + } catch (_e) { + return '' + } +} diff --git a/src/main/utils/common.ts b/src/main/utils/common.ts index 80f2a160..0e82d87d 100644 --- a/src/main/utils/common.ts +++ b/src/main/utils/common.ts @@ -10,6 +10,7 @@ import fs from 'fs-extra' import { IPicGo } from 'piclist' import { isProxy, isRef, toRaw, unref } from 'vue' +import { getClipboardTextFilePath } from '~/utils/clipboardFilePath' import { configPaths } from '~/utils/configPaths' import { IShortUrlServer } from '~/utils/enum' @@ -147,7 +148,11 @@ export const getClipboardFilePath = (): string => { .readBuffer('FileNameW') ?.toString('ucs2') ?.replace(RegExp(String.fromCharCode(0), 'g'), '') - return imgPath || '' + if (imgPath) return imgPath + } + + if (img.isEmpty()) { + return getClipboardTextFilePath(clipboard.readText()) } return '' diff --git a/tests/clipboard-file-path.test.ts b/tests/clipboard-file-path.test.ts new file mode 100644 index 00000000..65d058bd --- /dev/null +++ b/tests/clipboard-file-path.test.ts @@ -0,0 +1,51 @@ +import os from 'node:os' +import path from 'node:path' +import { pathToFileURL } from 'node:url' + +import fs from 'fs-extra' +import { afterEach, describe, expect, it } from 'vitest' + +import { getClipboardTextFilePath } from '../src/main/utils/clipboardFilePath' + +const tempDirs: string[] = [] + +const createTempFile = (fileName = 'clipboard image.png') => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'piclist-clipboard-')) + const filePath = path.join(dir, fileName) + tempDirs.push(dir) + fs.writeFileSync(filePath, 'test') + return filePath +} + +afterEach(() => { + tempDirs.splice(0).forEach(dir => fs.removeSync(dir)) +}) + +describe('getClipboardTextFilePath', () => { + it('returns an existing absolute file path from clipboard text', () => { + const filePath = createTempFile() + + expect(getClipboardTextFilePath(` ${filePath} `)).toBe(path.normalize(filePath)) + }) + + it('unwraps quoted file paths', () => { + const filePath = createTempFile() + + expect(getClipboardTextFilePath(`"${filePath}"`)).toBe(path.normalize(filePath)) + }) + + it('supports file URL text', () => { + const filePath = createTempFile() + + expect(getClipboardTextFilePath(pathToFileURL(filePath).href)).toBe(path.normalize(filePath)) + }) + + it('rejects relative paths, missing files, directories, and multi-line text', () => { + const filePath = createTempFile() + + expect(getClipboardTextFilePath(path.basename(filePath))).toBe('') + expect(getClipboardTextFilePath(path.join(os.tmpdir(), 'missing-clipboard-image.png'))).toBe('') + expect(getClipboardTextFilePath(path.dirname(filePath))).toBe('') + expect(getClipboardTextFilePath(`${filePath}\n${filePath}`)).toBe('') + }) +}) diff --git a/tests/getraw.test.ts b/tests/getraw.test.ts index a9ec59e7..3a5bc1e9 100644 --- a/tests/getraw.test.ts +++ b/tests/getraw.test.ts @@ -93,7 +93,7 @@ describe('getRawData 深度扩展工具测试', () => { }) it('应当正确克隆 Date 和 RegExp', () => { - const date = new Date('2024-01-01') + const date = new Date('2024-01-02') const reg = /test/g const data = reactive({ date, reg })