Open links from QR payloads: Ctrl+O opens the decoded URL (browser) instead of the image; clickable Open-link chips in link and QR previews
This commit is contained in:
+10
@@ -213,6 +213,16 @@ function urlDomain(url) {
|
||||
return m ? m[1] : ""
|
||||
}
|
||||
|
||||
// First URL inside arbitrary text ("visit https://x.io/a for more", QR
|
||||
// payloads, …). Returns "" when none. Bare domains are NOT extracted here —
|
||||
// too noisy for prose; the shell-side open script has its own fallback.
|
||||
function extractUrl(text) {
|
||||
var m = /https?:\/\/[^\s\]"'<>]+/i.exec(String(text || ""))
|
||||
if (m) return m[0].replace(/[.,;:)!]+$/, "")
|
||||
m = /(?:^|\s)(www\.[^\s\]"<>]+)/i.exec(String(text || ""))
|
||||
return m ? m[1].replace(/[.,;:)!]+$/, "") : ""
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- apps
|
||||
|
||||
var APP_NAMES = {
|
||||
|
||||
@@ -794,6 +794,7 @@ Item {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: root.listWidth + Style.space(14)
|
||||
result: root.currentResult
|
||||
onOpen: root.openResult(root.currentResult)
|
||||
visible: root.currentResult !== null
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ Item {
|
||||
property var result: null
|
||||
property var entry: result ? result.row.entry : null
|
||||
property string derived: result ? result.row.type : ""
|
||||
// Wired by the picker: opens the current result (browser for links).
|
||||
property var onOpen: function() {}
|
||||
|
||||
readonly property string font_: Style.font.menuFamily
|
||||
readonly property color fg: Color.menu.text
|
||||
@@ -51,6 +53,17 @@ Item {
|
||||
return hsl ? "hsl(" + hsl[0] + ", " + hsl[1] + "%, " + hsl[2] + "%)" : ""
|
||||
}
|
||||
|
||||
// URL inside the decoded QR payload ("" when the payload is not a link).
|
||||
function qrUrl() {
|
||||
if (!entry || !entry.qr) return ""
|
||||
var url = Classify.extractUrl(entry.qr)
|
||||
if (url) return url
|
||||
var trimmed = String(entry.qr).trim()
|
||||
return root.bareDomainRe.test(trimmed) ? trimmed : ""
|
||||
}
|
||||
|
||||
readonly property var bareDomainRe: /^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}(?:\/[^\s]*)?$/i
|
||||
|
||||
function metaChips() {
|
||||
if (!result) return []
|
||||
var r = result.row
|
||||
@@ -292,6 +305,42 @@ Item {
|
||||
font.family: root.font_
|
||||
font.pixelSize: Style.font.caption
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
radius: height / 2
|
||||
color: Util.alpha(Color.accent, 0.15)
|
||||
width: linkOpenLabel.implicitWidth + Style.space(16)
|
||||
height: Style.space(22)
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.space(4)
|
||||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: ""
|
||||
color: Color.accent
|
||||
font.family: root.font_
|
||||
font.pixelSize: Style.font.caption
|
||||
}
|
||||
|
||||
Text {
|
||||
id: linkOpenLabel
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "Open link"
|
||||
color: Color.accent
|
||||
font.family: root.font_
|
||||
font.pixelSize: Style.font.caption
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.onOpen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// image body
|
||||
@@ -359,6 +408,44 @@ Item {
|
||||
wrapMode: TextEdit.WrapAnywhere
|
||||
selectionColor: Util.alpha(Color.accent, 0.4)
|
||||
}
|
||||
|
||||
// QR payloads frequently encode links — offer the open affordance.
|
||||
Rectangle {
|
||||
visible: root.qrUrl().length > 0
|
||||
radius: height / 2
|
||||
color: Util.alpha(Color.accent, 0.15)
|
||||
width: qrOpenLabel.implicitWidth + Style.space(16)
|
||||
height: Style.space(20)
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.space(4)
|
||||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: ""
|
||||
color: Color.accent
|
||||
font.family: root.font_
|
||||
font.pixelSize: Style.font.caption
|
||||
}
|
||||
|
||||
Text {
|
||||
id: qrOpenLabel
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "Open link"
|
||||
color: Color.accent
|
||||
font.family: root.font_
|
||||
font.pixelSize: Style.font.caption
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.onOpen()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,9 @@ full theme integration.
|
||||
domain, JSON is pretty-printed, file lists show paths — every preview with
|
||||
metadata chips (app, date, words/lines, size, paste counts)
|
||||
- **QR codes** — copied QR images are decoded with `zbarimg`; the payload is
|
||||
shown in the list, preview, and is searchable like any text clip
|
||||
shown in the list, preview, and is searchable like any text clip — and if it
|
||||
encodes a link, `Ctrl+O` (or the preview's "Open link" chip) opens it in the
|
||||
browser directly
|
||||
- **Rich capture** — a `wl-paste --watch` daemon records every clip with mime
|
||||
type, byte size, source app (via `hyprctl`), timestamp, and image dimensions.
|
||||
Text, images, and `file://` URI lists (file-manager copies) are supported;
|
||||
@@ -67,7 +69,7 @@ Updating: `omarchy plugin update tank.clipboard` · Uninstall: `omarchy plugin r
|
||||
| `Ctrl+N` / `Ctrl+P` (or arrows) | navigate results |
|
||||
| `Enter` | copy to clipboard and paste into the focused window |
|
||||
| `Shift+Enter` | copy only |
|
||||
| `Ctrl+O` | open (link → browser, image → editor, file → xdg-open, text → editor) |
|
||||
| `Ctrl+O` | open (link → browser, image → editor, file → xdg-open, text → editor). QR images open their **decoded link** in the browser; the preview pane also has clickable "Open link" chips for links and QR payloads |
|
||||
| `Tab` | pin/unpin |
|
||||
| `Ctrl+=` | pause/resume recording |
|
||||
| `Delete` | remove entry · `Shift+Delete` clear all (with confirm) |
|
||||
|
||||
@@ -38,6 +38,17 @@ open_text() {
|
||||
case $(jq -r '.type' <<<"$entry") in
|
||||
image)
|
||||
path=$(jq -r '.path' <<<"$entry")
|
||||
# A decoded QR payload often IS a link — opening it beats opening the image.
|
||||
qr=$(jq -r '.qr // empty' <<<"$entry")
|
||||
if [[ -n $qr ]]; then
|
||||
url=$(printf '%s' "$qr" | grep -Eom1 'https?://[^[:space:]"<>]+' || true)
|
||||
if [[ -z $url ]] && [[ $qr =~ ^[[:alnum:]][[:alnum:].-]*\.[[:alpha:]]{2,}(/[^[:space:]]*)?[[:space:]]*$ ]]; then
|
||||
url="https://${BASH_REMATCH[0]}"
|
||||
fi
|
||||
if [[ -n $url ]]; then
|
||||
exec omarchy-launch-browser "$url"
|
||||
fi
|
||||
fi
|
||||
[[ -r $path ]] || exit 0
|
||||
if command -v tensaku-edit >/dev/null 2>&1; then
|
||||
exec tensaku-edit "$path"
|
||||
|
||||
@@ -114,3 +114,13 @@ test("colorToRgb rejects junk", () => {
|
||||
assert.equal(Classify.colorToRgb("#gg"), null)
|
||||
assert.equal(Classify.colorToRgb("hello"), null)
|
||||
})
|
||||
|
||||
test("extractUrl finds links in text and QR payloads", () => {
|
||||
assert.equal(Classify.extractUrl("see https://omarchy.org/docs?a=1 for details."), "https://omarchy.org/docs?a=1")
|
||||
assert.equal(Classify.extractUrl("go to www.example.com now"), "www.example.com")
|
||||
assert.equal(Classify.extractUrl("https://x.io"), "https://x.io")
|
||||
assert.equal(Classify.extractUrl("no link here"), "")
|
||||
assert.equal(Classify.extractUrl(""), "")
|
||||
// trailing punctuation is not part of the URL
|
||||
assert.equal(Classify.extractUrl("(https://a.io/x)."), "https://a.io/x")
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user