feat(attributes): make URL attribute values clickable - #1660
Conversation
An attribute whose value is a web address rendered as dead text in both the Identify popup and the attribute table, so the only way to follow it was to select the string and paste it into a browser. Detect a value that is entirely an http(s) URL and render it as a real link in both places. Adds `attributeLinkUrl` in @geolibre/core as the single, deliberately strict test: the whole trimmed value must be one http(s) URL with an authority. Linkifying substrings of prose would get trailing punctuation wrong, and a loose scheme check would let javascript:/file: reach an opener. Also adds a delegated click interceptor for the desktop build. The Tauri webview ignores target="_blank", so an anchor there either does nothing or navigates the single app webview away from GeoLibre with no way back. Anchors are rendered in plenty of places outside React and outside this repo (Identify popups, KML <description> markup, plugin panels), so one document-level listener handles them all rather than patching call sites one at a time. It stays out of the way of same-origin links, non-http(s) schemes, modified/middle clicks, and any handler that already claimed the click. Verified in the browser against the reporter's exact flow (a GeoEditor marker with a "Link" text field) and against USGS earthquake GeoJSON, whose features carry `url` and `detail`, in both light and dark themes. Fixes #1655
📝 WalkthroughWalkthroughThe change validates HTTP(S) attribute values, renders them as secure links in map and desktop attribute views, and opens eligible desktop links through the system browser. Tests cover URL validation and click interception. ChangesAttribute hyperlink flow
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 Biome (2.5.5)apps/geolibre-desktop/src/index.cssFile contains syntax errors that prevent linting: Line 5: Tailwind-specific syntax is disabled. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🔍 Cloudflare PR preview
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/geolibre-desktop/src/components/panels/AttributeTable.tsx`:
- Around line 1924-1946: Update the link onClick handler in AttributeTable so
every link click stops propagation to prevent row selection, but only unmodified
clicks call preventDefault() and openExternalLink(linkUrl). Preserve the
browser’s native behavior for Ctrl-, Cmd-, Shift-, and Alt-clicks by returning
without interception when modifier keys are present.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 9a3980f2-a3e2-4aed-a5c5-3effcbec7ed6
📒 Files selected for processing (9)
apps/geolibre-desktop/src/components/panels/AttributeTable.tsxapps/geolibre-desktop/src/index.cssapps/geolibre-desktop/src/lib/external-link-interceptor.tsapps/geolibre-desktop/src/main.tsxpackages/core/src/hyperlink.tspackages/core/src/index.tspackages/map/src/MapCanvas.tsxtests/attribute-hyperlink.test.tstests/external-link-interceptor.test.ts
There was a problem hiding this comment.
Pull request overview
This PR improves UX around feature attributes by detecting when an attribute value is entirely an http(s) URL and rendering it as a clickable link in both the Identify popup (MapCanvas) and the Attribute Table. For the desktop (Tauri) build, it adds a document-level click interceptor to safely route outbound link clicks to the system browser, avoiding the webview’s _blank limitations.
Changes:
- Add
attributeLinkUrl()to@geolibre/coreto strictly recognize whole-value http(s) URLs (rejecting unsafe/non-openable schemes and prose substrings). - Render URL-valued attributes as
<a>elements in the Identify popup and Attribute Table (with truncation +titlein the table). - Add a desktop-only, delegated external link interceptor (plus CSS styling and targeted tests).
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| tests/external-link-interceptor.test.ts | Adds coverage for the delegated Tauri-only click interceptor behavior. |
| tests/attribute-hyperlink.test.ts | Adds unit tests for strict URL detection in attributeLinkUrl(). |
| packages/map/src/MapCanvas.tsx | Linkifies URL-valued attribute entries in Identify popups. |
| packages/core/src/index.ts | Exports the new hyperlink helper from @geolibre/core. |
| packages/core/src/hyperlink.ts | Introduces strict URL detection for attribute values. |
| apps/geolibre-desktop/src/main.tsx | Installs the external link interceptor at startup (desktop-only behavior gated at runtime). |
| apps/geolibre-desktop/src/lib/external-link-interceptor.ts | Implements delegated outbound http(s) link interception for Tauri. |
| apps/geolibre-desktop/src/index.css | Styles Identify-popup attribute links to match existing KML anchor styling and wrap long URLs. |
| apps/geolibre-desktop/src/components/panels/AttributeTable.tsx | Renders URL-only attribute cells as clickable links and opens them via openExternalLink. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Code reviewI read through the full diff ( Bugs: None found. Security: None found. New anchors use property assignment / JSX props ( Performance: Minor, low-severity nit: Quality: The identify popup ( CLAUDE.md adherence: No new user-facing strings were introduced (the only new visible text is the URL itself, used as Overall this is a well-scoped, carefully tested change (13 + 7 new unit test cases covering the strict-URL parser and the interceptor's edge cases) with no significant issues identified. |
Problem
Reported in discussion #1655: a user put a web address into an attribute (a
Linktext field on a marker they drew). The address shows up in the marker's popup and in the attribute table, but it is plain text, so there is no way to follow it other than selecting the string and pasting it into a browser.Nothing was linkifying attribute values.
createIdentifyPopupElementinpackages/map/src/MapCanvas.tsxsetvalueCell.textContent, and the attribute table cell rendered a bareformatAttributeValue(value)text node.Change
packages/core/src/hyperlink.ts— newattributeLinkUrl(value). Deliberately strict: the whole trimmed value must be a single http(s) URL with an authority. Linkifying substrings of prose has to guess where a URL ends and gets trailing punctuation wrong, and a loose scheme test would letjavascript:orfile:reach an opener.mailto:is excluded too, since the opener cannot open it and a dead anchor is worse than text.truncated inside its cell so a long URL cannot run over the neighbouring column and steal its clicks, with the full URL intitle.apps/geolibre-desktop/src/lib/external-link-interceptor.ts— new delegated click listener, installed once, desktop-only. The Tauri webview ignorestarget="_blank", so an untouched anchor there either does nothing or navigates the single app webview away from GeoLibre with no way back. Anchors are rendered in plenty of places outside React and outside this repo (Identify popups, KML<description>markup, plugin panels), so one document-level listener covers them all rather than patching call sites one at a time. It defers to same-origin links, non-http(s) schemes, modified and middle clicks, and any handler that already calledpreventDefault(the attribute table does).This also fixes the anchors in KML
<description>markup, which were built withtarget="_blank"and so were already dead clicks on the desktop build.Verification
Driven in a real browser against the dev server.
Link(text) with defaulthttps://www.bbc.co.uk/. The cell renders as a link, and clicking the marker with Identify shows one popup containing the same link.urland adetailproperty). Identify renders both as links; clicking one opens the real USGS event page. Confirmed the click reachesdocumentwith the anchor intact, which is what the desktop interceptor relies on.npm run build,npm run test:frontend(4890 passing),npm run lint(0 errors),pre-commit run --files <changed>.New tests:
tests/attribute-hyperlink.test.ts(13 cases overattributeLinkUrl) andtests/external-link-interceptor.test.ts(7 cases over the interceptor).Not covered
maplibre-gl-vectordraws its ownvector-control-popupfor layers added through its panel, and that popup still renders URLs as text. It is a separate component in an upstream package, and it does not appear for the drawn-marker case in this report, so it is left for a follow-up.Fixes #1655
Summary by CodeRabbit
New Features
Bug Fixes