efficient-easel 0.0.0 → 0.96.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/LICENSE +10 -0
- package/README.md +723 -4
- package/dist/autosave/index.d.ts +101 -0
- package/dist/autosave/index.js +1 -0
- package/dist/blocks/index.d.ts +295 -0
- package/dist/blocks/index.js +0 -0
- package/dist/buckets-1vRlQceY.d.ts +13 -0
- package/dist/canvas/index.d.ts +194 -0
- package/dist/canvas/index.js +1 -0
- package/dist/chrome/chrome.css +570 -0
- package/dist/chrome/index.d.ts +219 -0
- package/dist/chrome/index.js +21 -0
- package/dist/chunk-2LUSK32B.js +1 -0
- package/dist/chunk-2OAZYSWJ.js +1 -0
- package/dist/chunk-67VZRQLG.js +13 -0
- package/dist/chunk-6JDII7N3.js +1 -0
- package/dist/chunk-6OPSJGAI.js +1 -0
- package/dist/chunk-6WY6ZXJG.js +1 -0
- package/dist/chunk-7A7O2JWQ.js +1 -0
- package/dist/chunk-BZ5ASXOL.js +1 -0
- package/dist/chunk-CERTFXCV.js +13 -0
- package/dist/chunk-EFTTSIMK.js +1 -0
- package/dist/chunk-FI23ALVJ.js +1 -0
- package/dist/chunk-FJE4IDBQ.js +1 -0
- package/dist/chunk-GGTPNGFJ.js +1 -0
- package/dist/chunk-H4B72XCJ.js +1 -0
- package/dist/chunk-H6DDIBQ4.js +1 -0
- package/dist/chunk-KDIVA6WD.js +1 -0
- package/dist/chunk-LPRQDDQO.js +1 -0
- package/dist/chunk-MCLBHENY.js +1 -0
- package/dist/chunk-MFIRYGHW.js +1 -0
- package/dist/chunk-NKB3KVAB.js +1 -0
- package/dist/chunk-O2NLQBFX.js +1 -0
- package/dist/chunk-OGZFDJ6G.js +1 -0
- package/dist/chunk-OWBO4WKA.js +0 -0
- package/dist/chunk-QB3BZIRA.js +5 -0
- package/dist/chunk-RE4LP46F.js +1 -0
- package/dist/chunk-STFMZJKU.js +14 -0
- package/dist/chunk-SUO5627P.js +1 -0
- package/dist/chunk-TDA4HH54.js +1 -0
- package/dist/chunk-WK3AFPXY.js +1 -0
- package/dist/chunk-YIMRTIBN.js +3 -0
- package/dist/chunk-YNZZYHA4.js +450 -0
- package/dist/chunk-ZAOM6PFH.js +1 -0
- package/dist/chunk-ZNCEOSXO.js +1 -0
- package/dist/copy/copy.css +282 -0
- package/dist/copy/index.d.ts +336 -0
- package/dist/copy/index.js +10 -0
- package/dist/data/index.d.ts +208 -0
- package/dist/data/index.js +1 -0
- package/dist/drag/drag.css +360 -0
- package/dist/drag/index.d.ts +379 -0
- package/dist/drag/index.js +18 -0
- package/dist/edit-lock/index.d.ts +354 -0
- package/dist/edit-lock/index.js +1 -0
- package/dist/editor/editor.css +58 -0
- package/dist/editor/index.d.ts +361 -0
- package/dist/editor/index.js +9 -0
- package/dist/fields/fields.css +207 -0
- package/dist/fields/index.d.ts +273 -0
- package/dist/fields/index.js +46 -0
- package/dist/gold/gold.css +196 -0
- package/dist/gold/index.d.ts +280 -0
- package/dist/gold/index.js +1 -0
- package/dist/icons/index.d.ts +474 -0
- package/dist/icons/index.js +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1 -0
- package/dist/markup/index.d.ts +452 -0
- package/dist/markup/index.js +1 -0
- package/dist/menu/index.d.ts +49 -0
- package/dist/menu/index.js +15 -0
- package/dist/menu/menu.css +254 -0
- package/dist/node-ops/index.d.ts +91 -0
- package/dist/node-ops/index.js +1 -0
- package/dist/panel/index.d.ts +1088 -0
- package/dist/panel/index.js +16 -0
- package/dist/panel/panel.css +1460 -0
- package/dist/pick/index.d.ts +153 -0
- package/dist/pick/index.js +14 -0
- package/dist/pick/pick.css +235 -0
- package/dist/plan-DtNw2VuI.d.ts +88 -0
- package/dist/rail/index.d.ts +145 -0
- package/dist/rail/index.js +1 -0
- package/dist/rail/rail.css +175 -0
- package/dist/render/index.d.ts +117 -0
- package/dist/render/index.js +5 -0
- package/dist/resolve-DpH8YQOA.d.ts +57 -0
- package/dist/shell/index.d.ts +159 -0
- package/dist/shell/index.js +1 -0
- package/dist/shell/shell.css +122 -0
- package/dist/strings/catalogue.d.ts +141 -0
- package/dist/strings/catalogue.js +1 -0
- package/dist/strings/index.d.ts +56 -0
- package/dist/strings/index.js +2 -0
- package/dist/style-clipboard/index.d.ts +184 -0
- package/dist/style-clipboard/index.js +1 -0
- package/dist/tree/index.d.ts +173 -0
- package/dist/tree/index.js +1 -0
- package/dist/tree/tree.css +618 -0
- package/dist/types-CEqUFH2j.d.ts +129 -0
- package/dist/types-CuNiSnX6.d.ts +204 -0
- package/dist/types-DNrn_w4V.d.ts +88 -0
- package/dist/types-DziKCwsN.d.ts +105 -0
- package/dist/types-_Vayx8ov.d.ts +153 -0
- package/dist/types-pGBXevBU.d.ts +131 -0
- package/dist/useNodeOps-6IAej9jy.d.ts +68 -0
- package/dist/watch-DmasQA61.d.ts +60 -0
- package/dist/wordFit-9B9nqyf3.d.ts +35 -0
- package/dist/words-B2YLaKEr.d.ts +30 -0
- package/dist/words-B87GIFWH.d.ts +86 -0
- package/dist/words-BK-npta-.d.ts +27 -0
- package/dist/words-CD3WADyb.d.ts +51 -0
- package/dist/words-CsRWXkY4.d.ts +129 -0
- package/dist/words-D9ChvbNC.d.ts +52 -0
- package/dist/words-DOhF-4D4.d.ts +35 -0
- package/dist/words-DagJdwIA.d.ts +78 -0
- package/dist/words-DfxEp_RH.d.ts +142 -0
- package/package.json +202 -3
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The edit lock's own words (`efficient-easel/strings`, 0.95.0): how long ago,
|
|
3
|
+
* and who, when the backend could not say.
|
|
4
|
+
*
|
|
5
|
+
* Plain TypeScript with no import, like the module it belongs to. A count that
|
|
6
|
+
* can be one or many is a function, so a host does its own plural.
|
|
7
|
+
*/
|
|
8
|
+
type LockWords = {
|
|
9
|
+
/** Under 45 seconds, or a time that is not a number. */
|
|
10
|
+
justNow: string;
|
|
11
|
+
/** 1 to 59 minutes. */
|
|
12
|
+
minutesAgo: (v: {
|
|
13
|
+
count: number;
|
|
14
|
+
}) => string;
|
|
15
|
+
/** An hour or more. */
|
|
16
|
+
hoursAgo: (v: {
|
|
17
|
+
count: number;
|
|
18
|
+
}) => string;
|
|
19
|
+
/** The person, when the backend sent no name. */
|
|
20
|
+
someone: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Who may WRITE the open document, and what to tell the operator when they may
|
|
25
|
+
* not — for EVERY editor in the house.
|
|
26
|
+
*
|
|
27
|
+
* This module is the merge of two files that had grown side by side and said
|
|
28
|
+
* the same things in two places (card 11161):
|
|
29
|
+
*
|
|
30
|
+
* * `efficient-admin-portal/src/components/email/campaignEditLock.ts` — the
|
|
31
|
+
* campaign designer's copy, which alone had the TAKE-OVER half;
|
|
32
|
+
* * `efficient-shop/lib/editor-page-lock.ts` — the storefront editor's copy,
|
|
33
|
+
* which alone had the claim/heartbeat driver, the auth-rejection rule and
|
|
34
|
+
* the "a template has no lock" rule.
|
|
35
|
+
*
|
|
36
|
+
* Neither was a subset of the other, so this file keeps EVERY capability of
|
|
37
|
+
* both, and both editors now import it instead of carrying a copy.
|
|
38
|
+
*
|
|
39
|
+
* TWO INDEPENDENT PROTECTIONS, and it matters which is which:
|
|
40
|
+
*
|
|
41
|
+
* * the SAVE-TIME version check is what actually protects work. The backend
|
|
42
|
+
* refuses a document write whose base version has moved on, so no amount of
|
|
43
|
+
* client-side confusion can erase a colleague's edits. Nothing here can
|
|
44
|
+
* weaken it — the token travels with the write and the server decides.
|
|
45
|
+
* * this advisory LOCK only stops two people STARTING, and is soft on
|
|
46
|
+
* purpose: it expires, it can always be taken over, and — see
|
|
47
|
+
* `accessAfterClaimFailure` — it FAILS OPEN. A lock that could refuse to
|
|
48
|
+
* let anybody edit would be worse than no lock at all. A save consults it
|
|
49
|
+
* in one case only: a tab somebody TOOK the document from is refused
|
|
50
|
+
* (`EDIT_LOCK_TAKEN`, read by `readLockTaken`), so it stops at its next
|
|
51
|
+
* save instead of its next heartbeat.
|
|
52
|
+
*
|
|
53
|
+
* The backend rule is one rule shared by both (efficient/editlock.py). These
|
|
54
|
+
* are two CONSUMERS of one server-side contract, which is exactly why one
|
|
55
|
+
* client module can serve them.
|
|
56
|
+
*
|
|
57
|
+
* WHAT IS NOT SHARED, and why there is a factory rather than plain functions:
|
|
58
|
+
* the two editors talk to the backend through different HTTP clients, so a
|
|
59
|
+
* failure arrives as an `AxiosError` in one and as an `ApiError` in the other,
|
|
60
|
+
* and the refused-save payload carries a different `code`. Those two facts —
|
|
61
|
+
* and nothing else — are handed in by the consumer through `createEditLock`.
|
|
62
|
+
* The module itself imports no HTTP client and knows about no repo.
|
|
63
|
+
*
|
|
64
|
+
* LEAF DISCIPLINE: pure TypeScript. No React, no `document`/`window`, no
|
|
65
|
+
* dependency at all. `crypto` is touched inside a function, never at module
|
|
66
|
+
* eval, so a Next server component may import this module safely.
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
/** The `edit-lock` body — identical for every editable document. */
|
|
70
|
+
type EditLockBody = {
|
|
71
|
+
held_by_you: boolean;
|
|
72
|
+
holder_name: string;
|
|
73
|
+
client_id: string;
|
|
74
|
+
last_seen_at: string;
|
|
75
|
+
seconds_since_seen: number;
|
|
76
|
+
seconds_since_held: number;
|
|
77
|
+
heartbeat_seconds: number;
|
|
78
|
+
expiry_seconds: number;
|
|
79
|
+
};
|
|
80
|
+
/** The other operator, as the notices need to describe them. */
|
|
81
|
+
type LockHolder = {
|
|
82
|
+
name: string;
|
|
83
|
+
/** Seconds since their last heartbeat, as the SERVER measured it. */
|
|
84
|
+
secondsSinceSeen: number;
|
|
85
|
+
/** Seconds since they TOOK the document, as the SERVER measured it. Not the
|
|
86
|
+
* same number: an active holder's heartbeat keeps `secondsSinceSeen` near
|
|
87
|
+
* zero, so only this one can say "took over 20 seconds ago". */
|
|
88
|
+
secondsSinceHeld: number;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Whether the open document may be written, and why not when it may not.
|
|
92
|
+
*
|
|
93
|
+
* `editing` is the ONLY writable state, and `canWriteDocument` is the only place
|
|
94
|
+
* that says so — so a new state added later is read-only until someone
|
|
95
|
+
* deliberately makes it writable, rather than writable until someone remembers
|
|
96
|
+
* to block it.
|
|
97
|
+
*/
|
|
98
|
+
type EditAccess =
|
|
99
|
+
/** The claim is still in flight. Holding writes for the moment it takes is
|
|
100
|
+
* what stops a fast typist's first autosave landing before we know whether
|
|
101
|
+
* somebody else is in here. */
|
|
102
|
+
{
|
|
103
|
+
kind: 'checking';
|
|
104
|
+
}
|
|
105
|
+
/** You hold the document. */
|
|
106
|
+
| {
|
|
107
|
+
kind: 'editing';
|
|
108
|
+
}
|
|
109
|
+
/** Somebody else holds it and you have not chosen yet — view, or take over. */
|
|
110
|
+
| {
|
|
111
|
+
kind: 'blocked';
|
|
112
|
+
holder: LockHolder;
|
|
113
|
+
}
|
|
114
|
+
/** You chose to look without touching. */
|
|
115
|
+
| {
|
|
116
|
+
kind: 'readonly';
|
|
117
|
+
holder: LockHolder;
|
|
118
|
+
}
|
|
119
|
+
/** You HAD it and somebody took it. Your work is still on screen. */
|
|
120
|
+
| {
|
|
121
|
+
kind: 'takenover';
|
|
122
|
+
holder: LockHolder;
|
|
123
|
+
};
|
|
124
|
+
/** The one gate on the document write path. */
|
|
125
|
+
declare const canWriteDocument: (access: EditAccess) => boolean;
|
|
126
|
+
/**
|
|
127
|
+
* The access a freshly opened document starts from.
|
|
128
|
+
*
|
|
129
|
+
* A document WITH a lock starts `checking` and the claim answers within a round
|
|
130
|
+
* trip. One WITHOUT — the storefront's templates — has no claim effect at all,
|
|
131
|
+
* so `checking` there is not a question awaiting an answer, it is a state
|
|
132
|
+
* nothing will ever leave, and every write for the rest of the session is
|
|
133
|
+
* refused: the merchant's edits and, first of all, the pinned Header/Footer seed
|
|
134
|
+
* that runs in the chrome's mount commit. The editor's MOUNT has always known
|
|
135
|
+
* this; a document SWITCH did not, which is what card 1096 found.
|
|
136
|
+
*
|
|
137
|
+
* Not a loosening of `canWriteDocument`: it says which QUESTION a document opens
|
|
138
|
+
* with, and a document with no lock was never asking one.
|
|
139
|
+
*/
|
|
140
|
+
declare function accessForNewDocument(hasEditLock: boolean): EditAccess;
|
|
141
|
+
/** The holder named by a state that has one (for the banner / the notices). */
|
|
142
|
+
declare const holderOf: (access: EditAccess) => LockHolder | null;
|
|
143
|
+
/** Read the `edit-lock` body — from a 200 or from a 409's error payload. */
|
|
144
|
+
declare function readLockHolder(data: unknown): LockHolder | null;
|
|
145
|
+
/** The five capabilities Puck's `permissions` prop carries for the canvas. */
|
|
146
|
+
type DocumentPermissions = {
|
|
147
|
+
edit: boolean;
|
|
148
|
+
insert: boolean;
|
|
149
|
+
delete: boolean;
|
|
150
|
+
duplicate: boolean;
|
|
151
|
+
drag: boolean;
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* The canvas's Puck `permissions`, derived from the write gate.
|
|
155
|
+
*
|
|
156
|
+
* BOTH branches name ALL FIVE keys — and that completeness is the whole point,
|
|
157
|
+
* not tidiness. Puck does not replace this prop when it changes; it MERGES it
|
|
158
|
+
* over the permissions it already holds (store/slices/permissions.ts,
|
|
159
|
+
* `useRegisterPermissionsSlice`: `{ ...existingGlobalPermissions, ...prop }`),
|
|
160
|
+
* and a field's editability is the live `getPermissions().edit`. Puck mounts
|
|
161
|
+
* while the lock claim is still `checking`, so `canEdit` is false and the store
|
|
162
|
+
* latches every key false. A PARTIAL writable value (say `{ drag: false }`)
|
|
163
|
+
* would then merge WITHOUT an `edit` key and leave `edit:false` stuck — every
|
|
164
|
+
* inspector field disabled for good, even after the claim resolves to
|
|
165
|
+
* `editing`. Spelling out edit/insert/delete/duplicate on the writable branch
|
|
166
|
+
* resets them, so the panel comes back the instant `canEdit` flips true.
|
|
167
|
+
*
|
|
168
|
+
* `drag` stays false in EITHER state: both editors move blocks with native DnD
|
|
169
|
+
* rails of their own, so Puck's flaky cross-iframe canvas drag is always off.
|
|
170
|
+
*
|
|
171
|
+
* Write safety is unchanged: the `checking` window is still non-editable here,
|
|
172
|
+
* and the host's own write gate still drops any edit attempted before the claim
|
|
173
|
+
* resolves, so nothing is saved early.
|
|
174
|
+
*/
|
|
175
|
+
declare const documentPermissions: (canEdit: boolean) => DocumentPermissions;
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* "just now" / "2 minutes ago" — one phrasing, shared by every notice.
|
|
179
|
+
*
|
|
180
|
+
* `words` is the host's language (`useEditorStrings().lock`, 0.95.0); left out,
|
|
181
|
+
* it is the English it always was. The thresholds are not words and do not
|
|
182
|
+
* move: under 45 seconds is "just now", then whole minutes, then whole hours.
|
|
183
|
+
*/
|
|
184
|
+
declare function describeAge(seconds: number, words?: LockWords): string;
|
|
185
|
+
/** Names the person, or says so honestly when the backend could not. */
|
|
186
|
+
declare const nameOrSomeone: (name: string, words?: LockWords) => string;
|
|
187
|
+
/**
|
|
188
|
+
* The identity of THIS editor tab.
|
|
189
|
+
*
|
|
190
|
+
* Per-tab, not per-user: it is what lets the backend tell "me, in a second tab"
|
|
191
|
+
* (take my own lock over silently) from "me, here". Deliberately not persisted —
|
|
192
|
+
* a reload IS a new editor session, and a remembered id would let a closed tab's
|
|
193
|
+
* claim look alive.
|
|
194
|
+
*/
|
|
195
|
+
declare function newEditorClientId(): string;
|
|
196
|
+
/**
|
|
197
|
+
* The `code` of a document write refused because somebody else TOOK the
|
|
198
|
+
* document from this tab (backend `efficient/editlock.py`,
|
|
199
|
+
* `EDIT_LOCK_TAKEN_CODE`). The write names its tab with the same `client_id`
|
|
200
|
+
* its claim sends; a write that does not is never refused this way.
|
|
201
|
+
*
|
|
202
|
+
* One code for every document type, unlike the version conflict's: the body
|
|
203
|
+
* is the `edit-lock` body, which is already the same everywhere.
|
|
204
|
+
*/
|
|
205
|
+
declare const EDIT_LOCK_TAKEN = "edit_lock_taken";
|
|
206
|
+
/** A refused save: the stored document moved on since this editor loaded it. */
|
|
207
|
+
type SaveConflict = {
|
|
208
|
+
/** Who saved over you. Empty when the backend could not attribute the write. */
|
|
209
|
+
savedBy: string;
|
|
210
|
+
/** True when it was YOU, in another tab — never blame a colleague for it. */
|
|
211
|
+
savedByYou: boolean;
|
|
212
|
+
secondsAgo: number;
|
|
213
|
+
};
|
|
214
|
+
/**
|
|
215
|
+
* A write the editor is HOLDING rather than sending, and the two other reasons
|
|
216
|
+
* it might be held. `onChange` never drops a refused write — the operator's
|
|
217
|
+
* screen is the only copy of it — so each hold owes a release.
|
|
218
|
+
*/
|
|
219
|
+
type HeldWrite = {
|
|
220
|
+
/** An unpersisted layout is waiting (the host's pending-data ref). */
|
|
221
|
+
pending: boolean;
|
|
222
|
+
/** Autosave paused: the merchant turned writes off, and the pill's resume is
|
|
223
|
+
* the only thing that may flush. */
|
|
224
|
+
autoSaveEnabled: boolean;
|
|
225
|
+
/** A refused save: their work sits behind a banner and only they decide what
|
|
226
|
+
* happens to it. Retrying behind their back is how it gets lost. */
|
|
227
|
+
saveConflict: boolean;
|
|
228
|
+
};
|
|
229
|
+
/** Everything the claim path reads and drives, passed in rather than imported so
|
|
230
|
+
* the path itself can be RUN in a test — a rule that only lives inside a React
|
|
231
|
+
* effect is a rule nothing can check. */
|
|
232
|
+
type ClaimBeatDeps = {
|
|
233
|
+
/** Claim the document (or take it). */
|
|
234
|
+
claim: (takeover: boolean) => Promise<{
|
|
235
|
+
heartbeat_seconds: number;
|
|
236
|
+
}>;
|
|
237
|
+
/** The effect's cleanup ran: a late answer must not grant a document this
|
|
238
|
+
* editor has already left, nor flush one that is no longer open. */
|
|
239
|
+
cancelled: () => boolean;
|
|
240
|
+
currentAccess: () => EditAccess;
|
|
241
|
+
heldWrite: () => HeldWrite;
|
|
242
|
+
setAccess: (access: EditAccess) => void;
|
|
243
|
+
/** Send the held write — the host's one write path, never a second one. */
|
|
244
|
+
releaseHeldWrite: () => void;
|
|
245
|
+
/** Start the heartbeat that KEEPS the document, at the backend's own interval. */
|
|
246
|
+
startHeartbeat: (intervalMs: number) => void;
|
|
247
|
+
/** Stop beating. Called when the SESSION is refused, never when a single
|
|
248
|
+
* request fails — see `isAuthRejection`. The effect re-runs and claims again
|
|
249
|
+
* the moment a fresh token arrives, so this ends a doomed loop rather than
|
|
250
|
+
* ending the editor's hold on the document. */
|
|
251
|
+
stopHeartbeat: () => void;
|
|
252
|
+
/** A take-over was granted and nothing of this editor's is waiting to be
|
|
253
|
+
* written (`shouldReloadAfterTakeover`): load the stored document if it moved
|
|
254
|
+
* since this copy was loaded. Optional — a host without it keeps its copy. */
|
|
255
|
+
reloadIfMoved?: () => void;
|
|
256
|
+
};
|
|
257
|
+
/**
|
|
258
|
+
* Must a write held ONLY because the document was not yet writable go out now?
|
|
259
|
+
*
|
|
260
|
+
* Keyed on the TRANSITION, not on the current state: `beat` also runs on every
|
|
261
|
+
* heartbeat, where access is already `editing`. Releasing on "is writable" would
|
|
262
|
+
* flush the merchant's in-progress edit on every beat and delete the debounce
|
|
263
|
+
* that keeps whole-document writes rare.
|
|
264
|
+
*
|
|
265
|
+
* Exported so the rule has a test of its own, and because it is the one piece of
|
|
266
|
+
* `makeClaimBeat` a caller can reason about without a driver.
|
|
267
|
+
*/
|
|
268
|
+
declare function shouldReleaseHeldWrite(previous: EditAccess, next: EditAccess, held: HeldWrite): boolean;
|
|
269
|
+
/**
|
|
270
|
+
* After a take-over was GRANTED, should the host check whether the stored
|
|
271
|
+
* document moved, and load it if so, before the operator edits?
|
|
272
|
+
*
|
|
273
|
+
* The taker's copy is whatever they opened, and the person they took it from
|
|
274
|
+
* may have saved since — in the seconds before learning they were displaced.
|
|
275
|
+
* Editing that copy means the taker's first save is refused as "saved
|
|
276
|
+
* elsewhere". When NOTHING of theirs is waiting to be written, loading the
|
|
277
|
+
* stored copy costs them nothing: from `blocked` / `readonly` the canvas was
|
|
278
|
+
* read-only, so there is nothing to lose.
|
|
279
|
+
*
|
|
280
|
+
* Never when something IS waiting — the edits a displaced tab held, taken
|
|
281
|
+
* back with "Take it back". Reloading would throw them away. They go out
|
|
282
|
+
* instead (`shouldReleaseHeldWrite`), and the version check refuses them if the
|
|
283
|
+
* other person saved in between. Never with a refused save on screen either:
|
|
284
|
+
* that notice has its own Reload, and it is the operator's to press.
|
|
285
|
+
*
|
|
286
|
+
* Only the question. Whether the version moved is the host's to find out: each
|
|
287
|
+
* document keeps its version its own way.
|
|
288
|
+
*/
|
|
289
|
+
declare function shouldReloadAfterTakeover(previous: EditAccess, next: EditAccess, held: Pick<HeldWrite, 'pending' | 'saveConflict'>): boolean;
|
|
290
|
+
/**
|
|
291
|
+
* Has the stored document moved on from the version this copy is based on?
|
|
292
|
+
*
|
|
293
|
+
* Both are version tokens exactly as the API served them (`layout_updated_at`,
|
|
294
|
+
* `design_updated_at`), and a version never goes backwards, so any difference
|
|
295
|
+
* is a newer write. The instant comparison only stops a copy that is somehow
|
|
296
|
+
* AHEAD of the stored one from reading as moved. Two writes in the same
|
|
297
|
+
* millisecond still count as moved: `Date` cannot see microseconds, and an
|
|
298
|
+
* unneeded reload of an untouched copy costs nothing.
|
|
299
|
+
*/
|
|
300
|
+
declare function documentVersionMoved(stored: string, ours: string): boolean;
|
|
301
|
+
/**
|
|
302
|
+
* The host's half of a reload after a take-over (`shouldReloadAfterTakeover`):
|
|
303
|
+
* read the stored version, and reload only if it moved AND nothing of the
|
|
304
|
+
* operator's is waiting by the time the answer comes back — the canvas opened
|
|
305
|
+
* the moment the take-over landed, so they may have typed during the read, and
|
|
306
|
+
* that edit is theirs. Resolves whether it reloaded. A read that fails, or a
|
|
307
|
+
* version that is not known, keeps the copy: the version check still stands
|
|
308
|
+
* behind the next save.
|
|
309
|
+
*/
|
|
310
|
+
declare function reloadIfVersionMoved(d: {
|
|
311
|
+
/** The version this copy is based on. */
|
|
312
|
+
ours: string | null;
|
|
313
|
+
/** The version stored now. */
|
|
314
|
+
stored: () => Promise<string | null>;
|
|
315
|
+
/** An edit is not on the server yet — read live. */
|
|
316
|
+
hasPending: () => boolean;
|
|
317
|
+
reload: () => void;
|
|
318
|
+
}): Promise<boolean>;
|
|
319
|
+
/** A failed request reduced to the only two things any decision here needs. */
|
|
320
|
+
type EditLockFailure = {
|
|
321
|
+
status: number;
|
|
322
|
+
data: unknown;
|
|
323
|
+
};
|
|
324
|
+
/**
|
|
325
|
+
* The two things that genuinely differ between one editor and the next.
|
|
326
|
+
*
|
|
327
|
+
* Kept to two on purpose. Anything a THIRD editor would also have to answer
|
|
328
|
+
* belongs in this module, not in another adapter.
|
|
329
|
+
*/
|
|
330
|
+
type EditLockAdapter = {
|
|
331
|
+
/**
|
|
332
|
+
* Pull `{ status, data }` out of whatever this repo's HTTP client threw, or
|
|
333
|
+
* null when the rejection is not a server answer at all (a dropped socket, a
|
|
334
|
+
* programming error). Every "fails open" branch below leans on that null.
|
|
335
|
+
*/
|
|
336
|
+
readFailure: (err: unknown) => EditLockFailure | null;
|
|
337
|
+
/** The `code` the backend puts in a refused-save body for THIS document type
|
|
338
|
+
* — `page_changed_elsewhere`, `campaign_changed_elsewhere`. */
|
|
339
|
+
conflictCode: string;
|
|
340
|
+
};
|
|
341
|
+
/** The decisions that need the adapter. Everything else above is plain. */
|
|
342
|
+
type EditLock = {
|
|
343
|
+
accessAfterClaimFailure: (err: unknown) => EditAccess;
|
|
344
|
+
accessAfterTakeoverFailure: (err: unknown, current: EditAccess) => EditAccess;
|
|
345
|
+
accessAfterHeartbeatFailure: (err: unknown, current: EditAccess) => EditAccess;
|
|
346
|
+
isAuthRejection: (err: unknown) => boolean;
|
|
347
|
+
readSaveConflict: (err: unknown) => SaveConflict | null;
|
|
348
|
+
readLockTaken: (err: unknown) => LockHolder | null;
|
|
349
|
+
makeClaimBeat: (deps: ClaimBeatDeps) => (takeover: boolean) => Promise<void>;
|
|
350
|
+
};
|
|
351
|
+
/** Bind the decisions to one editor's HTTP client and document type. */
|
|
352
|
+
declare function createEditLock(adapter: EditLockAdapter): EditLock;
|
|
353
|
+
|
|
354
|
+
export { type ClaimBeatDeps, type DocumentPermissions, EDIT_LOCK_TAKEN, type EditAccess, type EditLock, type EditLockAdapter, type EditLockBody, type EditLockFailure, type HeldWrite, type LockHolder, type LockWords, type SaveConflict, accessForNewDocument, canWriteDocument, createEditLock, describeAge, documentPermissions, documentVersionMoved, holderOf, nameOrSomeone, newEditorClientId, readLockHolder, reloadIfVersionMoved, shouldReleaseHeldWrite, shouldReloadAfterTakeover };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{EDIT_LOCK_TAKEN,accessForNewDocument,canWriteDocument,createEditLock,describeAge,documentPermissions,documentVersionMoved,holderOf,nameOrSomeone,newEditorClientId,readLockHolder,reloadIfVersionMoved,shouldReleaseHeldWrite,shouldReloadAfterTakeover}from"../chunk-TDA4HH54.js";import"../chunk-RE4LP46F.js";
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* efficient-easel/editor — the canvas's own marks (blueprint 5.1): the
|
|
3
|
+
* lists' empty and selected states, and the outlines over the selected and
|
|
4
|
+
* hovered block. Puck's `puck.css` drew these until now; the rules follow it.
|
|
5
|
+
*
|
|
6
|
+
* The canvas is an iframe of its own, so this sheet must reach it: import it
|
|
7
|
+
* once in the host, and `CanvasFrame` copies the host's stylesheets into the
|
|
8
|
+
* frame. Tokens fall back to the shell blue, because the frame's document has
|
|
9
|
+
* no `.es-editor` root to define them.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/* The frame's root (CanvasFrame). Puck's `[data-puck-entry]` rule: a stacking
|
|
13
|
+
context of its own, so a block's z-index stays inside the page. */
|
|
14
|
+
[data-puck-entry] {
|
|
15
|
+
position: relative;
|
|
16
|
+
z-index: 0;
|
|
17
|
+
}
|
|
18
|
+
/* A frame that FILLS its parent (`height="fill"`): the page is at least as tall
|
|
19
|
+
as the frame, as Puck's `#frame-root` was, so a short page's background and
|
|
20
|
+
its top list reach the bottom of the canvas. `height: 1px` gives the root a
|
|
21
|
+
height of its own for a list's `height: 100%` to resolve against. Not on an
|
|
22
|
+
`auto` frame, whose height comes FROM the page: there `100vh` is the frame
|
|
23
|
+
itself, and the page could never be shorter than the frame again. */
|
|
24
|
+
#frame-root.es-frame-fill {
|
|
25
|
+
height: 1px;
|
|
26
|
+
min-height: 100vh;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.es-zone {
|
|
30
|
+
position: relative;
|
|
31
|
+
height: 100%;
|
|
32
|
+
min-height: var(--min-empty-height);
|
|
33
|
+
width: 100%;
|
|
34
|
+
outline-offset: -2px;
|
|
35
|
+
}
|
|
36
|
+
.es-zone.has-children {
|
|
37
|
+
min-height: 0;
|
|
38
|
+
}
|
|
39
|
+
.es-zone:empty,
|
|
40
|
+
.es-zone.is-area-selected:not(.is-root),
|
|
41
|
+
.es-zone.is-hovering-area:not(.is-root) {
|
|
42
|
+
background: color-mix(in srgb, var(--es-accent, #2563eb) 6%, transparent);
|
|
43
|
+
outline: 2px dashed color-mix(in srgb, var(--es-accent, #2563eb) 35%, transparent);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.es-canvas-outline {
|
|
47
|
+
pointer-events: none;
|
|
48
|
+
box-sizing: border-box;
|
|
49
|
+
z-index: 2147483000;
|
|
50
|
+
}
|
|
51
|
+
.es-canvas-outline.is-selected {
|
|
52
|
+
outline: 2px solid var(--es-accent, #2563eb);
|
|
53
|
+
outline-offset: -1px;
|
|
54
|
+
}
|
|
55
|
+
.es-canvas-outline.is-hover {
|
|
56
|
+
outline: 1px solid color-mix(in srgb, var(--es-accent, #2563eb) 60%, transparent);
|
|
57
|
+
outline-offset: -1px;
|
|
58
|
+
}
|