@jsenv/core 41.5.35 → 41.5.38

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.
@@ -1,224 +0,0 @@
1
- import { isEditorKey, openPagePicker, isCommandKey, loadPages, asPageUrl } from "../page_picker.js";
2
-
3
- /*
4
- * cmd+K / ctrl+K on any dev-served page: the .html files the server serves, as a
5
- * tree one walks, filter as you type, Enter to go there — cmd/ctrl+Enter to go
6
- * there in another tab and stay here, the same thing cmd+click on a row does.
7
- *
8
- * The tree itself is not this file's: it is the page picker the dev server ships
9
- * for everyone who has to ask "which page?" (see protocol_file/client/page_picker.js
10
- * — the clients dashboard asks it too, to send another browser somewhere). What
11
- * belongs here is the key, and what taking a row means in this tab.
12
- *
13
- * cmd+E / ctrl+E is the other half of the same question: instead of going to a
14
- * page, open its file in the editor (the server does it, see
15
- * GET /.internal/open_file/*). Pressed on the page itself it opens the page one
16
- * is on; pressed inside the switcher it opens the row one is looking at.
17
- *
18
- * Injected into every page (see jsenv_plugin_page_switcher.js), which is why it
19
- * is careful about what it takes:
20
- * - the key is watched at the end of the bubble, on window, and a press that
21
- * was already handled (defaultPrevented) is left alone — a page with its own
22
- * cmd+K keeps it, and gets it first;
23
- * - nothing is fetched, built or styled until the first time it opens;
24
- * - everything it renders lives in a shadow root, so the page's own CSS cannot
25
- * reach it and it cannot reach the page's.
26
- */
27
-
28
-
29
- // The server asks the OS to open a file in whatever editor is configured
30
- // (VSCode here) — it takes a file url, which is why the page list carries one
31
- // per page (see html_pages.js).
32
- const OPEN_FILE_ENDPOINT = "/.internal/open_file/";
33
- // Open across a reload: this is a dev tool on a page one is editing, and a hot
34
- // reload in the middle of looking for the next page should not close what one
35
- // was looking at. In sessionStorage, not localStorage: it says what THIS tab was
36
- // doing a second ago, which is not something to remember tomorrow.
37
- const OPEN_KEY = "jsenv_page_switcher_open";
38
-
39
- // The page this script is running in, as the list spells its urls.
40
- const currentPageUrl = () => asPageUrl(window.location.href);
41
-
42
- const isSwitcherKey = (event) =>
43
- (event.key === "k" || event.key === "K") && isCommandKey(event);
44
-
45
- const FLASH_STYLE_TEXT = /* css */ `
46
- :host {
47
- position: fixed;
48
- right: 16px;
49
- bottom: 16px;
50
- /* Above the picker's own panel: it is the switcher that triggers it. */
51
- z-index: 2147483647;
52
- display: block;
53
- font-family: system-ui, sans-serif;
54
- pointer-events: none;
55
- }
56
- .flash {
57
- padding: 8px 14px;
58
- color: light-dark(#0f172a, #e2e8f0);
59
- font-size: 13px;
60
- background: light-dark(white, #1e293b);
61
- border-radius: 8px;
62
- box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
63
- color-scheme: light dark;
64
- }
65
- .flash[data-error] {
66
- color: light-dark(#991b1b, #fecaca);
67
- background: light-dark(#fee2e2, #7f1d1d);
68
- }
69
- `;
70
-
71
- // Opening a file in an editor happens in another application, on another
72
- // screen sometimes: without a word here, a press that failed and a press that
73
- // worked look exactly the same. Its own host and its own shadow root, so it
74
- // can be shown whether or not the switcher is open.
75
- let flashHost = null;
76
- let flashBox = null;
77
- let flashTimeout = null;
78
- const flash = (message, isError) => {
79
- if (!flashHost) {
80
- flashHost = document.createElement("div");
81
- const shadow = flashHost.attachShadow({ mode: "open" });
82
- const style = document.createElement("style");
83
- style.textContent = FLASH_STYLE_TEXT;
84
- flashBox = document.createElement("div");
85
- flashBox.className = "flash";
86
- shadow.append(style, flashBox);
87
- }
88
- flashBox.textContent = message;
89
- flashBox.toggleAttribute("data-error", Boolean(isError));
90
- // Appended last every time, so it sits above the picker's host when both
91
- // are on the page and they share the same z-index.
92
- document.body.append(flashHost);
93
- window.clearTimeout(flashTimeout);
94
- flashTimeout = window.setTimeout(() => flashHost.remove(), 2500);
95
- };
96
-
97
- const openInEditor = async (file) => {
98
- if (!file || !file.fileUrl) {
99
- flash("This page is not a file the server lists.", true);
100
- return;
101
- }
102
- flash(`Opening ${file.name || file.url} in editor…`);
103
- try {
104
- const response = await fetch(
105
- `${OPEN_FILE_ENDPOINT}${encodeURIComponent(file.fileUrl)}`,
106
- );
107
- if (response.status === 404) {
108
- // The route exists only when the server is willing to expose the
109
- // machine it runs on (see start_server.js).
110
- flash("This server does not open files in an editor.", true);
111
- } else if (!response.ok) {
112
- flash(`Editor said no (${response.status}).`, true);
113
- }
114
- } catch {
115
- flash("Could not reach the dev server.", true);
116
- }
117
- };
118
-
119
- const rememberOpen = (isOpen) => {
120
- try {
121
- if (isOpen) {
122
- window.sessionStorage.setItem(OPEN_KEY, "1");
123
- } else {
124
- window.sessionStorage.removeItem(OPEN_KEY);
125
- }
126
- } catch {
127
- // private mode, quota — it just will not come back
128
- }
129
- };
130
- const wasOpen = () => {
131
- try {
132
- return window.sessionStorage.getItem(OPEN_KEY) === "1";
133
- } catch {
134
- return false;
135
- }
136
- };
137
-
138
- let picker = null;
139
-
140
- // The same key opens it and puts it away: pressed while it is up, cmd+K is the
141
- // reader saying "not this, back to what I was doing".
142
- const toggleSwitcher = () => {
143
- if (picker) {
144
- picker.close();
145
- return;
146
- }
147
- openSwitcher();
148
- };
149
-
150
- const openSwitcher = () => {
151
- picker = openPagePicker({
152
- // The other key is written where one is already looking: a shortcut nobody
153
- // is told about is a shortcut nobody presses. Both names, not the one this
154
- // platform uses — the reader knows which of the two their keyboard has, and
155
- // it keeps what the panel says the same everywhere.
156
- placeholder: "Go to page… (cmd/ctrl+Enter: new tab, cmd/ctrl+E: editor)",
157
- hereUrl: window.location.href,
158
- // Going there is something this browser can do, so a row is a link: its
159
- // menu, its middle click and its cmd+click all work.
160
- getHref: (page) => page.url,
161
- onPick: (page) => {
162
- // Going somewhere is done with it: what reopens across a reload is a
163
- // switcher one was still using.
164
- rememberOpen(false);
165
- window.location.href = page.url;
166
- },
167
- onEdit: (page) => {
168
- // Done with the switcher: the answer to "where does this live" arrives
169
- // in the editor, not here.
170
- rememberOpen(false);
171
- openInEditor(page);
172
- },
173
- onClose: () => {
174
- picker = null;
175
- rememberOpen(false);
176
- },
177
- });
178
- rememberOpen(true);
179
- };
180
-
181
- // The page one is looking at, in the editor. The list is where the file url
182
- // comes from, so a page the server does not list (an @fs url, something under
183
- // node_modules) says so rather than opening the wrong thing.
184
- const openCurrentPageInEditor = async () => {
185
- const here = currentPageUrl();
186
- const pages = await loadPages();
187
- const page = pages.find((candidate) => candidate.url === here);
188
- openInEditor(page && { ...page, name: here.split("/").pop() });
189
- };
190
-
191
- // Last in line, on purpose: on window (the end of the bubble), and registered
192
- // once the page has loaded — so every listener the page set up while parsing is
193
- // already in place and has already had this press. If any of them called
194
- // preventDefault, the key was theirs and nothing happens here.
195
- const listenSwitcherKey = () => {
196
- window.addEventListener("keydown", (event) => {
197
- if (event.defaultPrevented) {
198
- return;
199
- }
200
- if (isSwitcherKey(event)) {
201
- // Ours now: the browser has its own use for cmd+K (the address bar),
202
- // which it must not get.
203
- event.preventDefault();
204
- toggleSwitcher();
205
- return;
206
- }
207
- if (isEditorKey(event)) {
208
- event.preventDefault();
209
- openCurrentPageInEditor();
210
- }
211
- });
212
- };
213
-
214
- const setup = () => {
215
- listenSwitcherKey();
216
- if (wasOpen()) {
217
- openSwitcher();
218
- }
219
- };
220
- if (document.readyState === "complete") {
221
- setup();
222
- } else {
223
- window.addEventListener("load", setup, { once: true });
224
- }