feat(sheets): add sheet-to-sheet hyperlinks#498
Open
the-narwhal wants to merge 1 commit intoProtonMail:mainfrom
Open
feat(sheets): add sheet-to-sheet hyperlinks#498the-narwhal wants to merge 1 commit intoProtonMail:mainfrom
the-narwhal wants to merge 1 commit intoProtonMail:mainfrom
Conversation
Allows users to create a hyperlink in one sheet that navigates to another
sheet within the same file. Selecting "Sheet in this file" in the Insert
Link dialog presents a dropdown of all sheets; clicking the resulting
link in the cell tooltip switches the active tab.
Internal links are stored as the string "#sheet:<sheetId>" using the
numeric ID rather than the sheet name, so links survive renames.
Changes
-------
- Add sheetLink.ts: encode/decode "#sheet:<id>" strings with strict
round-trip validation (rejects "#sheet:1abc", "#sheet:1.5", etc.)
- InsertLink.tsx: add a "Web link / Sheet in this file" segmented toggle
above the existing URL input. The sheet tab shows an Ariakit Select
dropdown populated from sheets.list (visible sheets in tab order).
Opening the dialog on a cell that already has a sheet link pre-selects
the correct tab and sheet. Both branches share the existing Apply /
Cancel flow; Cancel now reuses the already-captured close ref instead
of re-invoking useUI.$ at render time.
The two initialisation effects are merged into one to prevent a React
flush-time race where the defaulting branch (set first sheet) fired
after the parse branch in the same render, overwriting the linked
sheet ID with sheets[0].
- CellTooltip.tsx: LinkInfo detects internal links and handles them
differently from external URLs:
- Icon: grid-2 instead of globe
- Display text: sheet name (resolved live from sheets.listIncludingHidden
so renames are reflected immediately)
- Click: calls sheets.show() if the target is hidden, then setActiveId()
— matching the behaviour of the sheet tab switcher in BottomBar
- Copy link: copies the raw "#sheet:<id>" string, not the display name,
so the value can be round-tripped back into the dialog
- Copy link is no longer disabled in read-only mode (non-mutating action)
- useSheetLinkInfo looks up sheets from listIncludingHidden so links to
hidden sheets display the correct name and remain clickable
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Sheet-to-sheet hyperlinks
Implements the ability to create a clickable link in one sheet that navigates to another sheet within the same file — a frequently requested feature from the Proton community.
I was unable to run or build the application locally (node_modules not installed in this environment), so this PR has not been manually tested end-to-end. It has been statically reviewed and audited for correctness, but it should go through your normal QA pass before merging.
This is intended as a reference implementation, feel free to merge it as-is, iterate on top of it, or simply use it as a starting point. All design decisions are documented below and open to feedback.
What changed
sheetLink.ts(new)A small utility module for encoding and decoding internal sheet links. Links are stored as the string
#sheet:<sheetId>, using the numeric ID rather than the sheet name so that links survive renames. The parser uses a strict round-trip check (String(parseInt(raw)) === raw) to reject malformed values like#sheet:1abcor#sheet:1.5.InsertLink.tsxA segmented "Web link / Sheet in this file" toggle is added above the existing URL input. Selecting the sheet tab shows an Ariakit
Selectdropdown populated fromsheets.list(visible sheets in tab order, matching the bottom bar). Opening the dialog on a cell that already carries a sheet link pre-selects the correct tab and sheet.One correctness fix was required here: the original two-effect initialisation pattern (one to read the existing hyperlink, one to default to the first sheet) caused a React flush-time race where the defaulting branch overwrote the parsed sheet ID. These are now merged into a single effect.
CellTooltip.tsxLinkInfodetects#sheet:links and handles them differently from external URLs:globegrid-2showSheet()if hidden, thensetActiveId()#sheet:<id>stringSheet name resolution uses
sheets.listIncludingHiddenso that links pointing to a hidden sheet still display the correct name and remain clickable (clicking auto-unhides the sheet, matching the tab switcher behaviour inBottomBar). If the target sheet has been deleted the tooltip falls back to "Unknown sheet" and the click is a no-op.Design decisions open to feedback
Link format
#sheet:<id>is stored as a plain string through the existingonInsertLinkAPI. This avoids any changes to the@rowsncolumnsdata layer but means the format lives entirely in our layer. An alternative would be to use the library's native{ kind, location }object format ifonInsertLinkever starts accepting it.No cell reference the current implementation links to a sheet only, not to a specific cell or range. Extending the format to
#sheet:<id>!A1and switching the click handler togoToCell()would be a natural follow-up.Hidden sheets in the picker the dropdown intentionally shows only visible sheets (
sheets.list). If linking to a hidden sheet is a desired workflow, the picker could switch tosheets.listIncludingHiddenwith a visual indicator for hidden entries.