-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patheditor.html
More file actions
209 lines (204 loc) · 10.7 KB
/
Copy patheditor.html
File metadata and controls
209 lines (204 loc) · 10.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<meta name="theme-color" content="#ffffff" />
<title>FUTO Notes Editor</title>
<!-- Legacy Android System WebView runtime shims (github#8, Milkdown T10).
The editor bundle targets ES2020, so its SYNTAX floor is Chromium 80 — but
parsing is only half the floor. Dependencies reach for built-in methods
that landed later, and those load clean and then throw:
- Svelte 5's client runtime calls String.prototype.replaceAll (85) while
mounting, so without it the whole app dies before first paint.
- @milkdown/transformer calls Array.prototype.at (92) in its serializer
stack and its mark-merge pass — every parse and every save.
A classic (non-module) script runs before the deferred module bundle, so
both are in place by the time anything uses them. Engines below the ES2020
syntax floor are caught natively (see Android EditorEngineSupport.kt), and
tests/editor-embed-webview-floor.spec.ts holds the whole floor: it deletes
each built-in, proves the editor still runs, and audits the built bundle
for post-floor built-ins no shim covers. -->
<script>
if (!String.prototype.replaceAll) {
String.prototype.replaceAll = function (search, replacement) {
if (Object.prototype.toString.call(search) === '[object RegExp]') {
if (!search.global) {
throw new TypeError('replaceAll must be called with a global RegExp');
}
return String.prototype.replace.call(this, search, replacement);
}
var escaped = String(search).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
return String.prototype.replace.call(this, new RegExp(escaped, 'g'), replacement);
};
}
// Array.prototype.at / String.prototype.at, in an IIFE so the helpers do not
// become page globals. Covers Array and String only — a TypedArray `.at`
// would need its own line here, and the bundle uses none.
//
// defineProperty, not assignment: a plain `Array.prototype.at = ...` is an
// ENUMERABLE own property, so every `for (const k in someArray)` in the page
// — ours and every dependency's — would start yielding 'at'. Deleting the
// shim is safer than shipping that.
(function () {
// ToIntegerOrInfinity, near enough: Math.trunc gives NaN for undefined
// and non-numeric strings, and `|| 0` folds NaN and -0 to +0. Reads
// `this.length` raw rather than through ToLength, so it is exact for
// arrays and strings and approximate for a hand-rolled array-like.
var at = function (index) {
var length = this.length;
var i = Math.trunc(index) || 0;
if (i < 0) i += length;
return i < 0 || i >= length ? undefined : this[i];
};
var define = function (target) {
Object.defineProperty(target, 'at', {
value: at,
writable: true,
enumerable: false,
configurable: true,
});
};
if (!Array.prototype.at) define(Array.prototype);
if (!String.prototype.at) define(String.prototype);
})();
</script>
<!-- Engine capability preflight (github#8). The bundle is built with vite
`target: 'ES2020'`, so an engine that can't parse it throws on the module
script below and the editor never mounts — a blank pane. This classic ES5
script parses on ANY engine, compiles the syntax the bundle needs, and
leaves the verdict here for the native host to read (Android:
EditorEngineSupport.kt's ENGINE_PROBE_JS). Capability, never a version
number — apps/android/AGENTS.md records the vendor-WebView regression that
taught us the difference. Deliberately not a futoBridge message: the
contract in packages/editor/src/bridge.ts belongs to the bundle that just
failed to parse, and adding a message would bump BRIDGE_VERSION and oblige
both native hosts (M10) for a verdict only Android reads. -->
<script>
window.__futoEngineUnsupported = (function () {
try {
// Parse-only: constructing a Function compiles the body, never runs it.
// Optional chaining and nullish coalescing both landed in Chromium 80,
// so one probe covers the floor.
new Function('var o = {}; return o?.a ?? 1;');
return null;
} catch (e) {
return 'ES2020 syntax (optional chaining / nullish coalescing)';
}
})();
</script>
<style>
/* ------------------------------------------------------------------ *
* Embedded editor layout for the native iOS/Android WebView hosts.
*
* Only the layout SCAFFOLD is here: html, body, #editor, and carrying a
* definite height down through the one wrapper the editor mounts. The
* editor's INSIDE — which element scrolls, its momentum and overscroll
* behaviour, its gutters and safe-area insets, and the keyboard tail —
* belongs to MilkdownEditor.svelte's own <style> on `.futo-milkdown` /
* `.milkdown` / `.ProseMirror`. Svelte component CSS is emitted
* UNLAYERED into this bundle, so it survives on the same pre-@layer
* engines the comments below are about; nothing needs to be duplicated
* here for them.
*
* Keep every declaration in this file inside the Chromium 80 floor. The
* app's own @layer'd CSS is discarded wholesale by those engines
* (@layer is Chromium 99), so this stylesheet is the whole page layout
* on them.
* ------------------------------------------------------------------ */
html, body {
height: 100%;
margin: 0;
padding: 0;
overflow: hidden; /* the page never scrolls; .ProseMirror does */
overscroll-behavior: none;
/* Both native hosts render this page in a TRANSPARENT web view
* (iOS: isOpaque=false + .clear, Android: setBackgroundColor(TRANSPARENT))
* and paint the app background behind it (iOS Theme.background, Android
* Compose surface). app.css's @layer base would otherwise paint the web
* palette's --color-bg over it — dark #0A0A0A vs the hosts' dark
* surfaces — so the editor pane visibly mismatches the surrounding
* native UI in dark mode. Unlayered, so it beats the layered rule. */
background: transparent;
}
/* Pin the body to the web view, unlayered (github#33).
*
* This is a deliberate copy of src/styles/base.css's `body { position:
* fixed; inset: 0 }` — the rule that gives #editor below a containing block
* with a REAL height. base.css lives in `@layer base`, and `@layer` is
* Chromium 99, so every WebView in the 80–98 band drops it. Without it the
* body is static and zero-height, and #editor's offsets fall back to the
* initial containing block, which this engine sizes to ZERO (the same defect
* that makes it resolve 100vh/100dvh to 0 — measured on an Android 11 /
* Chromium 83 WebView: `position: absolute; top: 0; bottom: 0` gives 0px
* there, while an explicit pixel height or a fixed-position box gives the
* correct 555px). The whole height chain below then collapses, `.ProseMirror`
* never becomes a scroll container, and the editor scrolls the ROOT document
* to reveal the caret instead — which slides the note up under the shell's
* native title bar as you type (github#33, reported on 1.7.0 / Android 10).
*
* Identical declarations to base.css's, so engines that DO apply that layer
* see no change; longhands rather than `inset` because that shorthand is
* Chromium 87 (see #editor below). Keep the two in step. */
body {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
/* Legacy-WebView text-color fallback (github#8). Non-updated Android 8-10
* system WebViews predate @layer (Chromium 99) and drop EVERY rule inside
* Tailwind's layers — including the @theme light tokens — leaving UA-black
* text over the hosts' surface (invisible in dark mode). The
* [data-theme='dark'] variable overrides in theme.css stay unlayered, so
* var() still resolves there; the literal fallback covers light mode, where
* the @layer'd token is lost. Inherited from html, so it never outranks the
* layered rules that modern engines apply on body, nor MilkdownEditor's own
* unlayered `.ProseMirror` color. The hex must equal theme.css's light
* --color-text (locked by the legacy-WebView tests in
* tests/editor-embed-bridge.spec.ts). */
html {
color: var(--color-text, #0f0f0f);
}
/* Fill the web view — a DEFINITE height for the chain below.
*
* LONGHANDS, never `inset: 0` (github#33). The `inset` shorthand is
* Chromium 87; the editor's engine floor is 80, so a WebView in the 80–86
* band drops that one declaration and leaves `position: absolute` with all
* four offsets `auto`. #editor then shrink-wraps its text at the static
* position instead of filling the pane: the wrapper's and `.ProseMirror`'s
* `height: 100%` resolve against an auto-height parent, `.ProseMirror`
* never becomes a scroll container, and the note grows past the bottom of
* the pane with nothing to scroll — so scroll-caret-into-view moves the
* ROOT document and the note slides up under the shell's native title bar.
* Reported on 1.7.0 / Android 10. Locked by the "pre-inset WebView" tests
* in tests/editor-embed-bridge.spec.ts. The same goes for any other
* post-80 CSS added to THIS stylesheet: everything the app's own
* @layer'd CSS declares is already discarded by these engines
* (@layer is Chromium 99), so this rule is the whole layout on them. */
#editor {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
/* MilkdownEditor.svelte mounts a single wrapper <div class="futo-milkdown">
* inside #editor; carry #editor's definite height down through it so the
* `.milkdown` / `.ProseMirror` percentage heights inside it resolve, and
* `.ProseMirror` becomes the scroll container. The component declares
* `.futo-milkdown { height: 100% }` itself — this is the unconditional,
* structural copy, kept because it does not depend on the component's
* scoping class surviving, and because a definite height here is the one
* thing the legacy engines above cannot recover from if it is missing. */
#editor > :first-child {
height: 100% !important;
}
</style>
</head>
<body>
<div id="editor"></div>
<script type="module" src="/src/editor-embed/main.ts"></script>
</body>
</html>