Skip to content

fix(calendar): make URLs in the event description tappable - #53

Open
lucletoffe wants to merge 1 commit into
bulwarkmail:mainfrom
lucletoffe:fix/calendar-description-links
Open

fix(calendar): make URLs in the event description tappable#53
lucletoffe wants to merge 1 commit into
bulwarkmail:mainfrom
lucletoffe:fix/calendar-description-links

Conversation

@lucletoffe

Copy link
Copy Markdown
Contributor

The problem

A meeting invitation carries the join URL in the event description - Teams, Meet, Zoom and the plain iTIP fallbacks all do - and the event detail sheet renders it as bare <Text>. The one actionable thing in the event cannot be tapped: you have to select the URL by hand and paste it into a browser.

The mail body already linkifies (plainTextToSafeHtml, rendered in the WebView); only the calendar was left out. Same gap as bulwarkmail/webmail#968, which fixes the web client's three description renders.

The change

  • src/lib/linkify-text.ts - splitTextLinks(text) splits plain text into runs and marks the http(s) URLs, deliberately using the same rule as plainTextToSafeHtml in lib/email-html (/(https?:\/\/[^\s<>"']+)/g), so a message linkifies identically in the body and in the calendar. The URL terminating at <> also means the <https://...> form mail clients emit yields a bare URL, not one with a trailing bracket.
  • DetailRow gains an opt-in linkify prop, set on the description row only - never on rows the app composed itself.
  • Each URL goes to openExternalUrl(url, { confirm: true }), the same guard the "Open link" button on the video row already uses: scheme allow-list plus the host confirmation, since this text arrives in someone else's data. The regex is only a hint about where a link starts and ends; open-url remains the thing that decides whether it may be opened.

Checks

  • tsc --noEmit clean.
  • vitest run: 972 passed, 14 skipped, 0 failures.
  • New src/lib/__tests__/linkify-text.test.ts (6 tests): URL marked with surrounding text preserved, <https://...> termination, several URLs across lines, non-http schemes left alone (javascript:, file:, tel:), no characters lost when reassembling, and no lastIndex leak between calls on the module-level regex.
  • Not exercised on a device build - the change is a straight parallel of the web fix, and the rendering path is a <Text> inside the existing DetailRow.

A meeting invitation puts the join URL in the description - Teams, Meet,
Zoom and the plain iTIP fallbacks all do - and the event detail sheet
rendered it as bare <Text>. The one actionable thing in the event could
not be tapped: you had to select the URL by hand and paste it into a
browser. The mail body already linkifies (plainTextToSafeHtml, rendered
in the WebView); only the calendar was left out.

`splitTextLinks` (lib/linkify-text) splits the text into runs and marks
the http(s) URLs, using the same rule as plainTextToSafeHtml so a message
linkifies identically in the body and in the calendar. DetailRow gains an
opt-in `linkify` prop - set on the description row only, not on rows the
app composed itself - and hands each URL to `openExternalUrl` with
`confirm: true`, the same guard the "Open link" button on the video row
already uses: scheme allow-list plus the host confirmation, since this
text arrives in someone else's data.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018QPPAw4SK4M9Z7euVPM2cL
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant