Skip to content

feat(editor): preview cards for external links 🤖🤖🤖 - #3493

Open
AlexDeus20 wants to merge 1 commit into
go-vikunja:mainfrom
AlexDeus20:feat-link-preview
Open

feat(editor): preview cards for external links 🤖🤖🤖#3493
AlexDeus20 wants to merge 1 commit into
go-vikunja:mainfrom
AlexDeus20:feat-link-preview

Conversation

@AlexDeus20

Copy link
Copy Markdown

Closes #3492.

What

Slack-style preview cards for external links in task descriptions and comments.

Backend — GET /api/v2/link-preview?url=

  • Authenticated (global v2 security scheme); returns {url, title, description, image, site_name, favicon}.
  • SSRF: reuses the existing utils.NewSSRFSafeHTTPClient() (code.dny.dev/ssrf), whose Dialer.Control hook validates the resolved IP on every dial — including each redirect hop — so loopback/private/link-local targets are refused before any connection. Same pattern (and //nolint:gosec) as the existing gravatar fetcher.
  • Only http(s) accepted (→ 422 otherwise); 8s per-request timeout (→ 504); upstream/dial failures → 502; response body capped at 2 MiB; og:image/favicon resolved against the final URL and non-http(s) schemes (data:, javascript:) dropped.
  • Metadata parsed with golang.org/x/net/html (already in go.mod) — no new dependency.

Frontend

  • A ProseMirror widget-decoration extension (linkPreview.ts) renders a card below each external link, only in read-only/preview mode, so nothing is stored in the document. Cards are plain DOM (text via textContent), lazily fetched and cached per URL via services/linkPreview.ts.

Testing

  • Backend: unit test for the OG/meta parser (pkg/routes/api/v2/link_preview_test.go) + endpoint test for auth/validation/SSRF (pkg/webtests/huma_link_preview_test.go, run via mage test:filter). gofmt/go vet clean.
  • Verified against a live local backend: https://github.com/ai/nanoid → full OG card; http://127.0.0.1 → 502 (SSRF-blocked); ftp:// → 422; unauthenticated → 401.
  • Frontend: pnpm lint/pnpm lint:styles/vite build pass; verified in a browser (WebKit) that external links in a description render inline cards (GitHub with image + description, example.com), with no new console errors.

Notes

Implemented with the help of Claude Code (AI); I reviewed the code and runtime-tested both the endpoint and the UI. Happy to add a linkpreview.enabled config toggle if you'd like an admin opt-out.

Add a GET /api/v2/link-preview endpoint that fetches an external http(s)
URL and returns its OpenGraph/meta summary (title, description, image,
site name, favicon). Outbound requests reuse the existing SSRF-safe HTTP
client, and the metadata is parsed with golang.org/x/net/html — no new
dependencies. The endpoint requires authentication.

On the frontend, a ProseMirror widget-decoration extension renders
Slack-style preview cards below external links in task descriptions and
comments, but only in read-only (preview) mode, so nothing is stored in
the document. Previews are fetched lazily and cached per URL.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot added area/api REST API surface, endpoint design, request/response shapes area/comments Task comments and discussion area/task-editor Task detail view, inline editing, rich text concern/ux UX polish, ergonomics, workflow complaints without a clear bug labels Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/api REST API surface, endpoint design, request/response shapes area/comments Task comments and discussion area/task-editor Task detail view, inline editing, rich text concern/ux UX polish, ergonomics, workflow complaints without a clear bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Link previews for external URLs in descriptions and comments

1 participant