chore: add CONTRIBUTING.md for project guidelines and enhance README with author follow section
This commit is contained in:
@@ -1,10 +1,15 @@
|
||||
import { execFile } from 'node:child_process'
|
||||
import type { Dirent } from 'node:fs'
|
||||
import fs from 'node:fs/promises'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
import { dialog, shell } from 'electron'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
import { promisify } from 'node:util'
|
||||
import { clipboard, dialog, shell } from 'electron'
|
||||
import { type IpcContext, IpcMethod, IpcService } from 'electron-ipc-decorator'
|
||||
|
||||
const execFileAsync = promisify(execFile)
|
||||
|
||||
class FileSystemService extends IpcService {
|
||||
static readonly groupName = 'fs'
|
||||
|
||||
@@ -109,10 +114,48 @@ class FileSystemService extends IpcService {
|
||||
return false
|
||||
}
|
||||
|
||||
@IpcMethod()
|
||||
async copyFileToClipboard(_context: IpcContext, filePath: string): Promise<boolean> {
|
||||
try {
|
||||
if (!filePath) {
|
||||
return false
|
||||
}
|
||||
|
||||
const sanitizedPath = this.sanitizePath(filePath)
|
||||
const normalizedPath = path.normalize(sanitizedPath)
|
||||
const stats = await fs.stat(normalizedPath)
|
||||
if (!stats.isFile()) {
|
||||
return false
|
||||
}
|
||||
|
||||
const resolvedPath = path.resolve(normalizedPath)
|
||||
|
||||
await this.copyFileToClipboardByPlatform(resolvedPath)
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('Failed to copy file to clipboard:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private sanitizePath(target: string): string {
|
||||
return target.trim().replace(/^['"]|['"]$/g, '')
|
||||
}
|
||||
|
||||
private async copyFileToClipboardByPlatform(resolvedPath: string): Promise<void> {
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
await this.copyFileToClipboardWindows(resolvedPath)
|
||||
return
|
||||
case 'darwin':
|
||||
await this.copyFileToClipboardMac(resolvedPath)
|
||||
return
|
||||
default:
|
||||
await this.copyFileToClipboardLinux(resolvedPath)
|
||||
}
|
||||
}
|
||||
|
||||
private async findLikelyFile(directory: string, expectedPath: string): Promise<string | null> {
|
||||
try {
|
||||
const dirStats = await fs.stat(directory)
|
||||
@@ -171,6 +214,80 @@ class FileSystemService extends IpcService {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private async copyFileToClipboardWindows(resolvedPath: string): Promise<void> {
|
||||
const escaped = resolvedPath.replace(/'/g, "''")
|
||||
try {
|
||||
await execFileAsync('powershell.exe', [
|
||||
'-NoLogo',
|
||||
'-NoProfile',
|
||||
'-Command',
|
||||
`Set-Clipboard -Path '${escaped}'`
|
||||
])
|
||||
return
|
||||
} catch (error) {
|
||||
console.error('PowerShell clipboard copy failed, falling back to manual buffer:', error)
|
||||
}
|
||||
|
||||
const winPath = resolvedPath.replace(/\//g, '\\')
|
||||
const fileList = `${winPath}\u0000\u0000`
|
||||
const encodedList = Buffer.from(fileList, 'ucs2')
|
||||
|
||||
const dropFilesStructSize = 20
|
||||
const buffer = Buffer.alloc(dropFilesStructSize + encodedList.length)
|
||||
buffer.writeUInt32LE(dropFilesStructSize, 0)
|
||||
buffer.writeInt32LE(0, 4)
|
||||
buffer.writeInt32LE(0, 8)
|
||||
buffer.writeUInt32LE(0, 12)
|
||||
buffer.writeUInt32LE(1, 16)
|
||||
encodedList.copy(buffer, dropFilesStructSize)
|
||||
|
||||
clipboard.writeBuffer('CF_HDROP', buffer)
|
||||
clipboard.writeBuffer('Preferred DropEffect', Buffer.from([1, 0, 0, 0]))
|
||||
clipboard.writeBuffer('FileNameW', Buffer.from(`${path.basename(resolvedPath)}\u0000`, 'ucs2'))
|
||||
clipboard.writeBuffer('FileName', Buffer.from(`${path.basename(resolvedPath)}\u0000`, 'ascii'))
|
||||
}
|
||||
|
||||
private async copyFileToClipboardMac(resolvedPath: string): Promise<void> {
|
||||
const escaped = resolvedPath.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
|
||||
try {
|
||||
await execFileAsync('osascript', ['-e', `set the clipboard to (POSIX file "${escaped}")`])
|
||||
return
|
||||
} catch (error) {
|
||||
console.error('osascript clipboard copy failed, falling back to manual buffer:', error)
|
||||
}
|
||||
|
||||
const entries = [
|
||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
||||
'<plist version="1.0">',
|
||||
'<array>',
|
||||
` <string>${this.escapeForPlist(resolvedPath)}</string>`,
|
||||
'</array>',
|
||||
'</plist>'
|
||||
]
|
||||
const plist = Buffer.from(entries.join('\n'), 'utf8')
|
||||
clipboard.writeBuffer('NSFilenamesPboardType', plist)
|
||||
|
||||
const fileUrl = pathToFileURL(resolvedPath).toString()
|
||||
clipboard.writeBuffer('public.file-url', Buffer.from(`${fileUrl}\n`, 'utf8'))
|
||||
}
|
||||
|
||||
private async copyFileToClipboardLinux(resolvedPath: string): Promise<void> {
|
||||
const fileUrl = pathToFileURL(resolvedPath).toString()
|
||||
const content = `copy\n${fileUrl}`
|
||||
clipboard.writeBuffer('x-special/gnome-copied-files', Buffer.from(content, 'utf8'))
|
||||
clipboard.writeBuffer('text/uri-list', Buffer.from(`${fileUrl}\n`, 'utf8'))
|
||||
}
|
||||
|
||||
private escapeForPlist(value: string): string {
|
||||
return value
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
}
|
||||
}
|
||||
|
||||
export { FileSystemService }
|
||||
|
||||
Reference in New Issue
Block a user