Use text selection from ?select= when opening the editor from Sidekick
- Dominant language
- JavaScript
- Stars
- 34
- Forks
- 71
- Avg merge
- 2d 11h
- Merged PRs (30d)
- 29
Description
## Background
AEM Sidekick can switch from a rendered content page (preview/live) into the DA
editor. When the user has text selected on the content page, Sidekick appends
that selection to the DA edit URL as a `?select=` query param so the editor can
scroll to and re-select the same text.
(Native text fragments `:~:text=` can't be used here: the browser strips the
directive from `window.location`, DA content is rendered in shadow DOM and loads
asynchronously, and DA navigates same-document via `hashchange` — so none of the
conditions for native text fragments are met. Hence a query param that DA reads
and applies itself.)
Sidekick side: adobe/aem-sidekick#889 / adobe/aem-sidekick#891.
## Request
On editor load, if the URL has a `?select=` param, locate that text in the
ProseMirror document and select + scroll to it.
## URL / param format
```
https://da.live/edit?select=Lorem+ipsum+dolor#/org/site/path
```
- Single `select` param, standard URL-encoded (spaces as `+`).
- Read it with `URLSearchParams.get('select')`, which decodes it for you:
```js
const select = new URL(window.location.href).searchParams.get('select');
```
## When to apply — on initial collab sync (no polling)
The document content arrives over the Yjs/WebSocket collab connection, so apply
once the provider has synced. `onWsSync` already exists in
`blocks/edit/prose/index.js` and fires once (or immediately if already synced),
self-removes, and defers a tick so `y-prosemirror` can flush into the PM doc:
```js
// in initProse, alongside the existing sync-gated setup
onWsSync(wsProvider, () => applySelectFromUrl());
```
Guard against re-applying on in-tab hash navigations to another doc:
```js
let selectApplied = false;
function applySelectFromUrl() {
if (selectApplied) return;
const select = new URL(window.location.href).searchParams.get('select');
if (!select) return;
selectApplied = true;
const { view } = window;
const found = findRange(view.state.doc, select); // { from, to } | null
if (!found) return;
const sel = TextSelection.create(view.state.doc, found.from, found.to);
view.dispatch(view.state.tr.setSelection(sel).scrollIntoView());
view.focus();
}
```
## Locating the text (`findRange`)
Build a normalized flat-text index of the doc with a position map, `indexOf` the
selected text, and map back to ProseMirror positions. Normalize whitespace so
rendered-vs-source differences don't prevent a match. On multiple occurrences,
first match wins for now (see below).
## Keep the param in the URL
Do **not** strip `?select=` after applying it — users share these URLs to point
each other at a specific selection. (The `selectApplied` guard prevents
re-triggering within the same page load.)
## Notes / caveats
- Match against `window.view.state.doc` (source), which can differ from the
rendered page for structured blocks/metadata — best-effort for those, reliable
for prose.
- **Duplicate text:** if the selected text appears more than once, the first
match wins. Sidekick can later send a few words of surrounding context to
disambiguate (e.g. `selectPrefix`/`selectSuffix`); deferred until users report
it as a problem.
- The value is only ever used for an in-document text search (never injected as
HTML), so there's no XSS surface.
## Acceptance
- Opening a DA edit URL with `?select=` scrolls to and selects the referenced
text once content has synced.
- The param remains in the URL and isn't re-applied on subsequent in-tab
navigations.
Contributor guide
Research direction
The entry point is blocks/edit/prose/index.js; read initProse and the existing onWsSync setup, then trace window.view.state.doc and ProseMirror selection handling. Done means a synced URL with ?select= selects and scrolls to the first normalized text match, preserves the parameter, and does not reapply after in-tab navigation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- frontend
- Issue type
- Feature
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100