From a0cf4b07b2c06a08d7ef7836dcc9084bf180b8ca Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 29 May 2026 14:14:40 +0200 Subject: [PATCH] Show clipboard file entries without inline video playback --- shell/plugins/clipboard/Clipboard.qml | 10 ++-- shell/plugins/clipboard/ClipboardHistory.js | 64 +++++++++++++++++++-- test/shell.d/clipboard-test.sh | 26 +++++++++ 3 files changed, 90 insertions(+), 10 deletions(-) diff --git a/shell/plugins/clipboard/Clipboard.qml b/shell/plugins/clipboard/Clipboard.qml index 2419b8f6..b35a246c 100644 --- a/shell/plugins/clipboard/Clipboard.qml +++ b/shell/plugins/clipboard/Clipboard.qml @@ -426,7 +426,7 @@ Item { spacing: Style.space(10) Image { - visible: parent.parent.entryType === "image" + visible: parent.parent.previewImage.length > 0 width: visible ? parent.height : 0 height: parent.height source: parent.parent.previewImage @@ -436,13 +436,13 @@ Item { } Text { - width: parent.width - (parent.parent.entryType === "image" ? parent.height + parent.spacing : 0) + width: parent.width - (parent.parent.previewImage.length > 0 ? parent.height + parent.spacing : 0) height: parent.height text: parent.parent.previewText color: parent.parent.hasCursor ? root.selectedText : root.foreground font.family: root.fontFamily font.pixelSize: Style.font.title - opacity: parent.parent.entryType === "image" ? 0.72 : 1.0 + opacity: parent.parent.entryType === "image" || parent.parent.entryType === "file" ? 0.72 : 1.0 elide: Text.ElideRight wrapMode: Text.NoWrap verticalAlignment: Text.AlignVCenter @@ -483,7 +483,7 @@ Item { } Text { - visible: parent.activeRow && parent.activeRow.entryType === "text" + visible: parent.activeRow && !parent.activeRow.previewImage anchors.fill: parent anchors.leftMargin: root.contentMargin anchors.rightMargin: 0 @@ -499,7 +499,7 @@ Item { } Image { - visible: parent.activeRow && parent.activeRow.entryType === "image" + visible: parent.activeRow && parent.activeRow.previewImage anchors.fill: parent anchors.leftMargin: root.contentMargin anchors.rightMargin: 0 diff --git a/shell/plugins/clipboard/ClipboardHistory.js b/shell/plugins/clipboard/ClipboardHistory.js index 313704b1..54433f0c 100644 --- a/shell/plugins/clipboard/ClipboardHistory.js +++ b/shell/plugins/clipboard/ClipboardHistory.js @@ -92,7 +92,46 @@ function parseEntryJson(line) { function searchableText(entry) { if (!entry) return "" if (entry.type === "image") return "image screenshot " + String(entry.mime || "") + " " + String(entry.capturedAt || "") - return String(entry.text || "") + return String(entry.text || "") + " " + fileEntryText(entry) +} + +function decodeFileUri(uri) { + var value = String(uri || "").trim() + if (value.indexOf("file://") !== 0) return "" + + var path = value.substring(7) + if (path.indexOf("localhost/") === 0) path = path.substring(9) + if (path.charAt(0) !== "/") return "" + + try { return decodeURIComponent(path) } catch (e) { return path } +} + +function filePaths(entry) { + if (!entry || entry.type !== "text") return [] + + var lines = String(entry.text || "").split(/\r?\n/) + var paths = [] + for (var i = 0; i < lines.length; i++) { + var path = decodeFileUri(lines[i]) + if (path) paths.push(path) + } + return paths +} + +function fileName(path) { + var parts = String(path || "").split("/") + return parts.length > 0 ? parts[parts.length - 1] : String(path || "") +} + +function isImagePath(path) { + return /\.(png|jpe?g|webp|gif|bmp|tiff?)$/i.test(String(path || "")) +} + +function fileEntryText(entry) { + var paths = filePaths(entry) + if (paths.length === 0) return "" + if (paths.length === 1) return fileName(paths[0]) + return paths.length + " files" } function imagePreviewText(entry) { @@ -106,9 +145,18 @@ function imagePreviewText(entry) { function previewText(entry) { if (!entry) return "" if (entry.type === "image") return imagePreviewText(entry) + var fileText = fileEntryText(entry) + if (fileText) return fileText return String(entry.text || "").replace(/\s+/g, " ") } +function fullText(entry) { + if (!entry) return "" + var paths = filePaths(entry) + if (paths.length > 0) return paths.join("\n") + return String(entry.text || "") +} + function displayRows(history, query, limit) { var values = Array.isArray(history) ? history : [] var needle = String(query || "").trim().toLowerCase() @@ -124,13 +172,16 @@ function displayRows(history, query, limit) { if (!entry) continue if (needle && searchableText(entry).toLowerCase().indexOf(needle) < 0) continue + var paths = filePaths(entry) + var isFile = paths.length > 0 var isImage = entry.type === "image" + var previewPath = isImage ? String(entry.path || "") : (isFile && paths.length === 1 && isImagePath(paths[0]) ? paths[0] : "") rows.push({ - entryType: entry.type, - fullText: isImage ? "" : String(entry.text || ""), + entryType: isFile ? "file" : entry.type, + fullText: isImage ? "" : fullText(entry), previewText: previewText(entry), - previewImage: isImage ? String(entry.path || "") : "", - path: isImage ? String(entry.path || "") : "", + previewImage: previewPath, + path: isImage ? String(entry.path || "") : (isFile && paths.length === 1 ? paths[0] : ""), mime: isImage ? String(entry.mime || "image/png") : "text/plain", index: i }) @@ -152,6 +203,9 @@ if (typeof module !== "undefined") { searchableText: searchableText, previewText: previewText, imagePreviewText: imagePreviewText, + filePaths: filePaths, + fileEntryText: fileEntryText, + fullText: fullText, displayRows: displayRows } } diff --git a/test/shell.d/clipboard-test.sh b/test/shell.d/clipboard-test.sh index 0d1008d1..7e464f93 100644 --- a/test/shell.d/clipboard-test.sh +++ b/test/shell.d/clipboard-test.sh @@ -95,6 +95,32 @@ assertDeepEqual( 'clipboard display rows collapse text whitespace' ) +assertDeepEqual( + clipboard.displayRows([{ type: 'text', text: 'file:///home/dhh/Videos/screenrecording-2026-05-29_13-56-43-720p.gif\n' }], '', 50)[0], + { + entryType: 'file', + fullText: '/home/dhh/Videos/screenrecording-2026-05-29_13-56-43-720p.gif', + previewText: 'screenrecording-2026-05-29_13-56-43-720p.gif', + previewImage: '/home/dhh/Videos/screenrecording-2026-05-29_13-56-43-720p.gif', + path: '/home/dhh/Videos/screenrecording-2026-05-29_13-56-43-720p.gif', + mime: 'text/plain', + index: 0 + }, + 'clipboard display rows show file uri entries as files' +) + +assertDeepEqual( + clipboard.displayRows([{ type: 'text', text: 'file:///home/dhh/One.txt\nfile:///home/dhh/Two.txt\n' }], '', 50)[0].previewText, + '2 files', + 'clipboard display rows summarize multiple file uri entries' +) + +assertDeepEqual( + clipboard.displayRows([{ type: 'text', text: 'file:///home/dhh/Videos/demo.mp4\n' }], '', 50)[0].previewImage, + '', + 'clipboard display rows do not preview video file uri entries inline' +) + assertDeepEqual(clipboard.displayRows(history, '', 0), [], 'clipboard display rows supports zero result limit') assertDeepEqual(clipboard.addEntry(history, 'next', 0), [], 'clipboard addEntry supports zero history limit') JS