@medicine-wheel/episode-ui 0.0.0-stage → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +215 -2
- package/dist/address.d.ts +30 -0
- package/dist/address.d.ts.map +1 -0
- package/dist/address.js +64 -0
- package/dist/address.js.map +1 -0
- package/dist/browser/episode-files.js +89 -0
- package/dist/browser.d.ts +14 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +14 -0
- package/dist/browser.js.map +1 -0
- package/dist/ceremony.d.ts +48 -0
- package/dist/ceremony.d.ts.map +1 -0
- package/dist/ceremony.js +52 -0
- package/dist/ceremony.js.map +1 -0
- package/dist/chronicle-theme.d.ts +60 -0
- package/dist/chronicle-theme.d.ts.map +1 -0
- package/dist/chronicle-theme.js +64 -0
- package/dist/chronicle-theme.js.map +1 -0
- package/dist/episode-files.d.ts +148 -0
- package/dist/episode-files.d.ts.map +1 -0
- package/dist/episode-files.js +997 -0
- package/dist/episode-files.js.map +1 -0
- package/dist/file-tree.d.ts +51 -0
- package/dist/file-tree.d.ts.map +1 -0
- package/dist/file-tree.js +112 -0
- package/dist/file-tree.js.map +1 -0
- package/dist/http.d.ts +31 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +88 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/react.d.ts +32 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +58 -0
- package/dist/react.js.map +1 -0
- package/dist/room.d.ts +82 -0
- package/dist/room.d.ts.map +1 -0
- package/dist/room.js +48 -0
- package/dist/room.js.map +1 -0
- package/dist/shelf-filter.d.ts +22 -0
- package/dist/shelf-filter.d.ts.map +1 -0
- package/dist/shelf-filter.js +34 -0
- package/dist/shelf-filter.js.map +1 -0
- package/dist/types.d.ts +108 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +70 -0
- package/dist/types.js.map +1 -0
- package/package.json +98 -4
- package/src/address.ts +61 -0
- package/src/browser.ts +13 -0
- package/src/ceremony.tsx +185 -0
- package/src/chronicle-theme.ts +82 -0
- package/src/episode-files.ts +1143 -0
- package/src/episode-ui.css +889 -0
- package/src/file-tree.ts +135 -0
- package/src/http.ts +123 -0
- package/src/index.ts +91 -0
- package/src/react.tsx +94 -0
- package/src/room.tsx +177 -0
- package/src/shelf-filter.tsx +98 -0
- package/src/types.ts +154 -0
package/README.md
CHANGED
|
@@ -1,3 +1,216 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @medicine-wheel/episode-ui
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Browse, read and edit the files of a Miadi episode in the browser.
|
|
4
|
+
|
|
5
|
+
In the Miadi stack, an episode is the folder a piece of work is filed under, and this package
|
|
6
|
+
is how a person opens one: browse its files, read one, preview its markdown, edit it in
|
|
7
|
+
CodeMirror, go full screen with only Save and Back, and survive a conflict without losing the
|
|
8
|
+
draft. It also has React components for a ceremony's working notes, its attachments, and the
|
|
9
|
+
episode shelf. It is the browser half of `@medicine-wheel/episode-vessel`, which holds the rules for
|
|
10
|
+
the folder on the server side.
|
|
11
|
+
|
|
12
|
+
## Where it lives
|
|
13
|
+
|
|
14
|
+
Up to 0.6.0 (jgwill/Miadi e4e1b141) this package was `@miadi/episode-ui`, published from jgwill/Miadi. It moved to
|
|
15
|
+
jgwill/medicine-wheel in 0.25.0 so that dependencies run one way: the medicine-wheel suite is
|
|
16
|
+
the foundation, and Miadi consumes it. From 0.7.0, `@miadi/episode-ui` re-exports this package with the
|
|
17
|
+
same entry points, so code that imports it keeps working. Ref jgwill/medicine-wheel#166.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm i @medicine-wheel/episode-ui
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## One files workspace, two kinds of host
|
|
24
|
+
|
|
25
|
+
`mountEpisodeFiles` is framework-free, so a plain page and a React app mount the
|
|
26
|
+
same implementation.
|
|
27
|
+
|
|
28
|
+
React:
|
|
29
|
+
|
|
30
|
+
```jsx
|
|
31
|
+
import { EpisodeFiles, httpEpisodeFiles } from "@medicine-wheel/episode-ui"
|
|
32
|
+
import "@medicine-wheel/episode-ui/styles.css"
|
|
33
|
+
|
|
34
|
+
const service = httpEpisodeFiles({ fileUrl: (file) => `/api/files/${file.relativePath}` })
|
|
35
|
+
<EpisodeFiles files={files} capabilities={capabilities} service={service} />
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
A page with no bundler: serve `dist/browser/episode-files.js`
|
|
39
|
+
(CodeMirror, marked and DOMPurify inlined) and `src/episode-ui.css`.
|
|
40
|
+
|
|
41
|
+
```html
|
|
42
|
+
<div id="files"></div>
|
|
43
|
+
<script type="module">
|
|
44
|
+
import { mountEpisodeFiles, httpEpisodeFiles } from "/vendor/episode-ui/episode-files.js"
|
|
45
|
+
mountEpisodeFiles(document.getElementById("files"), {
|
|
46
|
+
files,
|
|
47
|
+
service: httpEpisodeFiles({
|
|
48
|
+
fileUrl: (file) => `/api/episode-file?file=${encodeURIComponent(file.relativePath)}`,
|
|
49
|
+
mediaUrl: (file) => `/api/episode-media?file=${encodeURIComponent(file.relativePath)}`,
|
|
50
|
+
}),
|
|
51
|
+
})
|
|
52
|
+
</script>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## What it does
|
|
56
|
+
|
|
57
|
+
- A filterable file tree: the folders the paths name, every one open, so a file's
|
|
58
|
+
folder is plain to see. Guidance files are dimmed and media and documents marked.
|
|
59
|
+
- Markdown opens in a sanitised preview (`marked` + DOMPurify); `Source` shows CodeMirror.
|
|
60
|
+
- CodeMirror 6 with markdown, JSON and YAML colouring, undo, search (Ctrl/Cmd-F),
|
|
61
|
+
line wrapping, and Ctrl/Cmd-S to save.
|
|
62
|
+
- `⛶` opens the viewer full screen with only the file name, its state, Save and `×`.
|
|
63
|
+
Esc returns.
|
|
64
|
+
- Audio and video play inline; images show inline; documents are a download.
|
|
65
|
+
- A markdown preview shows the episode's own images: `` is
|
|
66
|
+
resolved from the file's folder and shown from the host's `mediaUrl`.
|
|
67
|
+
- **Add images** (button, or drop files on the workspace) appears when the
|
|
68
|
+
capabilities include `upload` and the service has `upload` (`httpEpisodeFiles`
|
|
69
|
+
has it only when the host passes `uploadUrl`). Each image is sent
|
|
70
|
+
to `images/<name>`, the name reduced to safe characters and suffixed `-2`, `-3`
|
|
71
|
+
when taken; the new files are listed at once, `onFilesAdded` tells the host, and
|
|
72
|
+
the last one opens.
|
|
73
|
+
- An open image shows the record beside it: caption, role and, at the bottom, the
|
|
74
|
+
credit (`service.readRecord`). A role holding `edit` gets **Describe** (role,
|
|
75
|
+
caption, alt text, origin, tool, made by, chosen by; `service.saveRecord` over
|
|
76
|
+
the revision read) and **Use as cover** (`service.setCover`). `httpEpisodeFiles`
|
|
77
|
+
offers them when the host passes `recordUrl` and `coverUrl`.
|
|
78
|
+
- Leaving a file or the page with unsaved changes asks first.
|
|
79
|
+
|
|
80
|
+
## The folders
|
|
81
|
+
|
|
82
|
+
The vessel lists files, each with its path, and no folders. The workspace builds
|
|
83
|
+
the folders from the paths and draws them as a tree:
|
|
84
|
+
|
|
85
|
+
- Every folder is open to begin with. A folder closes and opens from its row, and
|
|
86
|
+
**Collapse all** / **Expand all** does every folder at once. Only a closed folder
|
|
87
|
+
is remembered, so a folder that arrives later opens.
|
|
88
|
+
- A row says the file's name; its whole path is the row's tooltip, and
|
|
89
|
+
`data-path` carries it for a host or a test.
|
|
90
|
+
- At each level a `README.md` comes first, then the folders by name, then the
|
|
91
|
+
files in the vessel's order (text, documents, guidance, images, media). Names
|
|
92
|
+
compare numerically: `chapter-2` comes before `chapter-10`.
|
|
93
|
+
- A folder shows how many files are beneath it, at any depth.
|
|
94
|
+
- Typing in the filter shows each match in its folders, every one open whatever was
|
|
95
|
+
closed before; clearing it brings back the folders as the reader left them.
|
|
96
|
+
- A file opened from a link or an address opens the folders it sits in.
|
|
97
|
+
- A list with no folder in it looks as it always did.
|
|
98
|
+
|
|
99
|
+
`listing="flat"` keeps the old list of whole paths, for a host that wants it.
|
|
100
|
+
|
|
101
|
+
The grouping is also exported, framework-free, for a host that draws its own:
|
|
102
|
+
`buildFileTree(files)` returns the root folder (`children`, each a `file` or a
|
|
103
|
+
`folder` with its `path` and `fileCount`), `ancestorFolders(path)` names the
|
|
104
|
+
folders above a file and `folderPaths(tree)` every folder in the tree.
|
|
105
|
+
|
|
106
|
+
## The open file as an address
|
|
107
|
+
|
|
108
|
+
The workspace holds no URL, so a host that wants the open file in its address
|
|
109
|
+
passes `open` and listens to `onSelectionChange`:
|
|
110
|
+
|
|
111
|
+
```jsx
|
|
112
|
+
<EpisodeFiles
|
|
113
|
+
files={files}
|
|
114
|
+
service={service}
|
|
115
|
+
open={{ path: "notes.md", mode: "source" }} // from the address; null closes
|
|
116
|
+
onSelectionChange={(selection, { push }) => {
|
|
117
|
+
// selection: { path, mode? } or null. mode is "preview" | "source" for a text file.
|
|
118
|
+
// push is true when a person opened another file, or kept a draft instead of
|
|
119
|
+
// following the address: add a history entry. Otherwise replace the current one.
|
|
120
|
+
}}
|
|
121
|
+
/>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
- A requested path waits for `files` to arrive and for the right to read, then
|
|
125
|
+
opens and scrolls the viewer into view. `open={{ path, reveal: false }}` opens
|
|
126
|
+
it without scrolling, for a room that opens on its script by default and should
|
|
127
|
+
not carry a phone past its header. A file a person clicks still scrolls into
|
|
128
|
+
view on a narrow screen.
|
|
129
|
+
- `open` is acted on only when its value changes, so re-rendering with the same
|
|
130
|
+
one does nothing, and a click the address has not caught up with is kept.
|
|
131
|
+
- A path the episode does not hold, or a file too large to open, says so. A
|
|
132
|
+
mode that does not fit the file is corrected, and the corrected selection is
|
|
133
|
+
reported back.
|
|
134
|
+
- An unsaved draft is never dropped by an address change: the person is asked.
|
|
135
|
+
A refusal reports the file still shown with `push`, so the entry Back or
|
|
136
|
+
Forward moved to is kept rather than overwritten.
|
|
137
|
+
- A relative link in a markdown preview is read from that file's folder. A plain
|
|
138
|
+
click on a link to another file of the episode opens it here in preview, with
|
|
139
|
+
`push`. Pass `fileHref={(selection) => url}` and those links also carry the
|
|
140
|
+
file's address, so a new tab or a copied link lands on the same file. A link
|
|
141
|
+
that leaves the episode (a scheme, a root path, a climb above the root) is
|
|
142
|
+
left as written. `episodeLinkTarget(fromPath, href)` is the same resolution.
|
|
143
|
+
|
|
144
|
+
The address rule is also exported on its own: `selectionFromSearch(search)`
|
|
145
|
+
reads `?file=&mode=`, `addressWithSelection(href, selection)` writes it into an
|
|
146
|
+
address and keeps every other parameter, and `writeSelectionToAddress(selection,
|
|
147
|
+
{ push })` writes it into the browser's history. In React, one hook does all of it:
|
|
148
|
+
|
|
149
|
+
```jsx
|
|
150
|
+
const address = useEpisodeFilesAddress() // or { search: useSearchParams().toString() } in Next.js
|
|
151
|
+
<EpisodeFiles {...address} files={files} service={service} />
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## The room's masthead, lineage and source issues
|
|
155
|
+
|
|
156
|
+
`EpisodeMasthead`, `LineageList` and `SourceIssueList` draw the top of an
|
|
157
|
+
episode room. They hold no state and fetch nothing, so a server page renders them
|
|
158
|
+
and passes a link callback:
|
|
159
|
+
|
|
160
|
+
```jsx
|
|
161
|
+
<EpisodeMasthead numberLabel="551" title={summary.title} goal={summary.goal}
|
|
162
|
+
date={summary.date} status={summary.status} type={summary.type}
|
|
163
|
+
direction={{ label: "West", oj: "Epangishmok", glyph: "🌊", color: "#5b78b4" }}
|
|
164
|
+
coverUrl={coverUrl} theme={CHRONICLE_THEME} />
|
|
165
|
+
<LineageList edges={await readEpisodeLineage(root, episode)} href={(folder) => `/episodes/${folder}`} />
|
|
166
|
+
<SourceIssueList issues={summary.references.map((ref) => ({ ref, url: issueUrl(ref) }))} />
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
An edge whose number two folders claim lists both, each linked.
|
|
170
|
+
|
|
171
|
+
## The chronicle theme
|
|
172
|
+
|
|
173
|
+
`@medicine-wheel/episode-ui/chronicle-theme` is the Chronicle's dark surface as data:
|
|
174
|
+
`CHRONICLE_PALETTE`, `CHRONICLE_THEME` (an `EpisodeUITheme`, which
|
|
175
|
+
`@miadi/attention-ui`'s panel also takes), `DIRECTIONS` (the four directions with
|
|
176
|
+
their names, colours and glyphs), `FONT_SANS` and `FONT_MONO`. Every text colour
|
|
177
|
+
of `CHRONICLE_THEME` reads at 4.5:1 or better on its background and its card.
|
|
178
|
+
|
|
179
|
+
## One file, no folder
|
|
180
|
+
|
|
181
|
+
A host that opens a single file it names itself, such as Miadi's `/e` editor, passes
|
|
182
|
+
that file in `files` and in `open` with `layout: "single"`: the list, filter and
|
|
183
|
+
count are hidden and the viewer takes the width. `outsideFileHref(path)` gives a
|
|
184
|
+
link in the preview to a file the workspace does not hold an address of its own;
|
|
185
|
+
without it, such a link says the file is not here.
|
|
186
|
+
|
|
187
|
+
## The conflict path
|
|
188
|
+
|
|
189
|
+
A save carries the revision the file was read at. When someone else wrote in
|
|
190
|
+
between, the host answers 409 with `currentRevision`: the draft stays in the
|
|
191
|
+
editor, the message says what is on disk now, and Reload is a choice the person
|
|
192
|
+
makes. **A save is never retried automatically.**
|
|
193
|
+
|
|
194
|
+
## The contract
|
|
195
|
+
|
|
196
|
+
No component holds a URL or a token, knows a chronicle root, or decides what a
|
|
197
|
+
person is allowed to do. The host passes a `service`; `httpEpisodeFiles` is the
|
|
198
|
+
one module that fetches, and only from URLs the host gives it. The host resolves
|
|
199
|
+
capabilities (`@medicine-wheel/episode-vessel/permissions`) and stays the authority; a
|
|
200
|
+
capability here decides only what is offered. A test holds this contract.
|
|
201
|
+
|
|
202
|
+
| export | what it is for |
|
|
203
|
+
|---|---|
|
|
204
|
+
| `mountEpisodeFiles(el, options)` | the files workspace; returns `{ update, destroy }` |
|
|
205
|
+
| `EpisodeFiles` | the same, as a React component |
|
|
206
|
+
| `httpEpisodeFiles({ fileUrl, mediaUrl?, headers?, uploadUrl?, recordUrl?, coverUrl? })` | the service for a host that answers GET/PUT JSON; with `uploadUrl` it POSTs a new image's bytes, with `recordUrl` it reads and writes an image's record, with `coverUrl` it names the cover |
|
|
207
|
+
| `imageUploadPath(name, type, taken)` | the `images/` path a chosen image is added at |
|
|
208
|
+
| `CeremonyNoteEditor` | a ceremony's working notes (React) |
|
|
209
|
+
| `CeremonyAttachments` | what a circle was held about (React) |
|
|
210
|
+
| `EpisodeShelfFilter` | find an episode by number, title or slug (React) |
|
|
211
|
+
|
|
212
|
+
## Styling
|
|
213
|
+
|
|
214
|
+
One stylesheet of plain class names over CSS custom properties, set from an
|
|
215
|
+
`EpisodeUITheme`. Touch targets are 44px because the first surface this runs on
|
|
216
|
+
is a phone.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The open file as the page's address: `?file=<path>&mode=preview|source`.
|
|
3
|
+
*
|
|
4
|
+
* A reload, a shared link and Back all land on the same file because the
|
|
5
|
+
* address carries it. These functions are that rule, with no framework: read a
|
|
6
|
+
* selection from a query string, and write one into an address. Miadi's room
|
|
7
|
+
* and eury's room each wrote them by hand; `useEpisodeFilesAddress` is the
|
|
8
|
+
* React form.
|
|
9
|
+
*/
|
|
10
|
+
import type { EpisodeFileSelection } from "./episode-files.js";
|
|
11
|
+
export declare const FILE_PARAM = "file";
|
|
12
|
+
export declare const MODE_PARAM = "mode";
|
|
13
|
+
/** The selection a query string names (`?file=&mode=`), or null when it names none. A mode it does not know is dropped. */
|
|
14
|
+
export declare function selectionFromSearch(search: string | URLSearchParams): EpisodeFileSelection | null;
|
|
15
|
+
/**
|
|
16
|
+
* `href` with the selection written into its query, every other parameter and
|
|
17
|
+
* the fragment kept. `null` removes the file and its mode. The address keeps
|
|
18
|
+
* its form: absolute stays absolute, `/episodes/551` stays rooted, and
|
|
19
|
+
* `551?x=1`, `?x=1` or `#top` stay relative to the page.
|
|
20
|
+
*/
|
|
21
|
+
export declare function addressWithSelection(href: string, selection: EpisodeFileSelection | null): string;
|
|
22
|
+
/**
|
|
23
|
+
* Write the selection into the browser's address: a new history entry when
|
|
24
|
+
* `push` (a person opened another file), otherwise a replacement. Nothing is
|
|
25
|
+
* written when the address already says it.
|
|
26
|
+
*/
|
|
27
|
+
export declare function writeSelectionToAddress(selection: EpisodeFileSelection | null, { push }: {
|
|
28
|
+
push: boolean;
|
|
29
|
+
}): void;
|
|
30
|
+
//# sourceMappingURL=address.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"address.d.ts","sourceRoot":"","sources":["../src/address.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAY,MAAM,oBAAoB,CAAA;AAExE,eAAO,MAAM,UAAU,SAAS,CAAA;AAChC,eAAO,MAAM,UAAU,SAAS,CAAA;AAIhC,2HAA2H;AAC3H,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,GAAG,oBAAoB,GAAG,IAAI,CAMjG;AAKD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,oBAAoB,GAAG,IAAI,GAAG,MAAM,CAWjG;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,oBAAoB,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAOjH"}
|
package/dist/address.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The open file as the page's address: `?file=<path>&mode=preview|source`.
|
|
3
|
+
*
|
|
4
|
+
* A reload, a shared link and Back all land on the same file because the
|
|
5
|
+
* address carries it. These functions are that rule, with no framework: read a
|
|
6
|
+
* selection from a query string, and write one into an address. Miadi's room
|
|
7
|
+
* and eury's room each wrote them by hand; `useEpisodeFilesAddress` is the
|
|
8
|
+
* React form.
|
|
9
|
+
*/
|
|
10
|
+
export const FILE_PARAM = "file";
|
|
11
|
+
export const MODE_PARAM = "mode";
|
|
12
|
+
const isMode = (value) => value === "preview" || value === "source";
|
|
13
|
+
/** The selection a query string names (`?file=&mode=`), or null when it names none. A mode it does not know is dropped. */
|
|
14
|
+
export function selectionFromSearch(search) {
|
|
15
|
+
const params = typeof search === "string" ? new URLSearchParams(search) : search;
|
|
16
|
+
const path = params.get(FILE_PARAM);
|
|
17
|
+
if (!path)
|
|
18
|
+
return null;
|
|
19
|
+
const mode = params.get(MODE_PARAM);
|
|
20
|
+
return isMode(mode) ? { path, mode } : { path };
|
|
21
|
+
}
|
|
22
|
+
/** A base for reading a relative href: no scheme a browser fetches, and a folder to strip off again. */
|
|
23
|
+
const RELATIVE_BASE = "address:/~/";
|
|
24
|
+
/**
|
|
25
|
+
* `href` with the selection written into its query, every other parameter and
|
|
26
|
+
* the fragment kept. `null` removes the file and its mode. The address keeps
|
|
27
|
+
* its form: absolute stays absolute, `/episodes/551` stays rooted, and
|
|
28
|
+
* `551?x=1`, `?x=1` or `#top` stay relative to the page.
|
|
29
|
+
*/
|
|
30
|
+
export function addressWithSelection(href, selection) {
|
|
31
|
+
const absolute = /^[a-z][a-z0-9+.-]*:/i.test(href);
|
|
32
|
+
const url = new URL(href, RELATIVE_BASE);
|
|
33
|
+
if (selection)
|
|
34
|
+
url.searchParams.set(FILE_PARAM, selection.path);
|
|
35
|
+
else
|
|
36
|
+
url.searchParams.delete(FILE_PARAM);
|
|
37
|
+
if (selection?.mode)
|
|
38
|
+
url.searchParams.set(MODE_PARAM, selection.mode);
|
|
39
|
+
else
|
|
40
|
+
url.searchParams.delete(MODE_PARAM);
|
|
41
|
+
if (absolute)
|
|
42
|
+
return url.href;
|
|
43
|
+
const rooted = href.startsWith("/");
|
|
44
|
+
const path = !rooted && url.pathname.startsWith("/~/") ? url.pathname.slice(3) : url.pathname;
|
|
45
|
+
return `${path}${url.search}${url.hash}`;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Write the selection into the browser's address: a new history entry when
|
|
49
|
+
* `push` (a person opened another file), otherwise a replacement. Nothing is
|
|
50
|
+
* written when the address already says it.
|
|
51
|
+
*/
|
|
52
|
+
export function writeSelectionToAddress(selection, { push }) {
|
|
53
|
+
if (typeof window === "undefined")
|
|
54
|
+
return;
|
|
55
|
+
const next = addressWithSelection(window.location.href, selection);
|
|
56
|
+
if (next === window.location.href)
|
|
57
|
+
return;
|
|
58
|
+
// `null` state, as Next.js's app router documents for its own pushState sync.
|
|
59
|
+
if (push)
|
|
60
|
+
window.history.pushState(null, "", next);
|
|
61
|
+
else
|
|
62
|
+
window.history.replaceState(null, "", next);
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=address.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"address.js","sourceRoot":"","sources":["../src/address.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAA;AAChC,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAA;AAEhC,MAAM,MAAM,GAAG,CAAC,KAAoB,EAAqB,EAAE,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,QAAQ,CAAA;AAErG,2HAA2H;AAC3H,MAAM,UAAU,mBAAmB,CAAC,MAAgC;IAClE,MAAM,MAAM,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAA;IAChF,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;IACnC,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAA;IACtB,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;IACnC,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAA;AACjD,CAAC;AAED,wGAAwG;AACxG,MAAM,aAAa,GAAG,aAAa,CAAA;AAEnC;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAY,EAAE,SAAsC;IACvF,MAAM,QAAQ,GAAG,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAClD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,aAAa,CAAC,CAAA;IACxC,IAAI,SAAS;QAAE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,EAAE,SAAS,CAAC,IAAI,CAAC,CAAA;;QAC1D,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;IACxC,IAAI,SAAS,EAAE,IAAI;QAAE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,EAAE,SAAS,CAAC,IAAI,CAAC,CAAA;;QAChE,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;IACxC,IAAI,QAAQ;QAAE,OAAO,GAAG,CAAC,IAAI,CAAA;IAC7B,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAA;IACnC,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAA;IAC7F,OAAO,GAAG,IAAI,GAAG,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,IAAI,EAAE,CAAA;AAC1C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CAAC,SAAsC,EAAE,EAAE,IAAI,EAAqB;IACzG,IAAI,OAAO,MAAM,KAAK,WAAW;QAAE,OAAM;IACzC,MAAM,IAAI,GAAG,oBAAoB,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;IAClE,IAAI,IAAI,KAAK,MAAM,CAAC,QAAQ,CAAC,IAAI;QAAE,OAAM;IACzC,8EAA8E;IAC9E,IAAI,IAAI;QAAE,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,CAAA;;QAC7C,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,CAAA;AAClD,CAAC"}
|