feat(clipboard): add a copy-as-text action for entries with a text alternates

- New Entries captured from a mixed image+text offer keep the image as the primary mime i.e. bitmap
- Copy Text context-menu item on atlernates

Closes #2849
This commit is contained in:
purian23
2026-09-01 19:33:23 -04:00
parent 0c51946f4f
commit 5bb6058751
4 changed files with 37 additions and 5 deletions
+11 -2
View File
@@ -205,6 +205,8 @@ func handleCopyEntry(conn *models.Conn, req models.Request, m *Manager) {
return
}
textOnly := params.BoolOpt(req.Params, "textOnly", false) && entry.AltMimeType != ""
if entry.AltMimeType == "" {
filePath := m.EntryToFile(entry)
if filePath != "" {
@@ -220,8 +222,15 @@ func handleCopyEntry(conn *models.Conn, req models.Request, m *Manager) {
}
}
if err := m.SetClipboardEntry(entry); err != nil {
models.RespondError(conn, req.ID, err.Error())
var setErr error
switch {
case textOnly:
setErr = m.SetClipboard(entry.AltData, entry.AltMimeType)
default:
setErr = m.SetClipboardEntry(entry)
}
if setErr != nil {
models.RespondError(conn, req.ID, setErr.Error())
return
}
@@ -29,6 +29,7 @@ Item {
readonly property bool hasPinnedDuplicate: !!entry && !entry.pinned && ClipboardService.getPinnedEntryByHash(entry.hash) !== null
readonly property bool canEditEntry: !!entry && !(entry.isImage ?? false)
readonly property bool hasTextAlternative: !!entry && (entry.altMimeType ?? "") !== ""
readonly property string pinText: entry?.pinned || hasPinnedDuplicate ? I18n.tr("Unpin") : I18n.tr("Pin")
readonly property string pinIcon: entry?.pinned || hasPinnedDuplicate ? "keep_off" : "push_pin"
@@ -48,6 +49,15 @@ Item {
}
];
if (hasTextAlternative) {
items.splice(1, 0, {
type: "item",
icon: "text_fields",
text: I18n.tr("Copy Text"),
action: copyEntryAsText
});
}
if (canEditEntry) {
items.push({
type: "item",
@@ -180,6 +190,13 @@ Item {
hide();
}
function copyEntryAsText() {
if (!entry || !hasTextAlternative)
return;
modal?.copyEntryAsText(entry);
hide();
}
function togglePin() {
if (!entry)
return;
@@ -108,6 +108,10 @@ FocusScope {
ClipboardService.copyEntry(entry, () => root.requestClose(false));
}
function copyEntryAsText(entry) {
ClipboardService.copyEntry(entry, () => root.requestClose(false), true);
}
function selectedEntry() {
const entries = activeTab === "saved" ? pinnedEntries : unpinnedEntries;
if (!entries || entries.length === 0 || selectedIndex < 0 || selectedIndex >= entries.length)
+5 -3
View File
@@ -202,15 +202,17 @@ Singleton {
unpinnedEntries = [];
}
function copyEntry(entry, closeCallback) {
function copyEntry(entry, closeCallback, textOnly) {
const asText = textOnly === true;
DMSService.sendRequest("clipboard.copyEntry", {
"id": entry.id
"id": entry.id,
"textOnly": asText
}, function (response) {
if (response.error) {
ToastService.showError(I18n.tr("Failed to copy entry"));
return;
}
ToastService.showInfo(entry.isImage ? I18n.tr("Image copied to clipboard") : I18n.tr("Copied to clipboard"));
ToastService.showInfo(entry.isImage && !asText ? I18n.tr("Image copied to clipboard") : I18n.tr("Copied to clipboard"));
historyCopied();
if (closeCallback) {
closeCallback();