@usefragment/core 0.0.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.
@@ -0,0 +1,3075 @@
1
+ // Generated by dts-bundle-generator v9.5.1
2
+
3
+ import { Extension } from '@codemirror/state';
4
+
5
+ /**
6
+ * La classe mère des plugins (§11.3) — et la PLUS SIMPLE du projet. C'est le but.
7
+ *
8
+ * Chaque registerX() fait EXACTEMENT deux choses, jamais trois :
9
+ *
10
+ * const retrait = this.app.<registre>.register(…) // 1. j'écris dans la table
11
+ * this.register(retrait) // 2. je programme mon retrait
12
+ *
13
+ * registry.ts ignore l'existence de component.ts, et inversement. Cette classe
14
+ * est le SEUL endroit où ils se rencontrent — le mariage, répété autant de fois
15
+ * qu'il y a de points d'extension, sans aucune logique propre. C'est ce qui
16
+ * rend « le coût marginal d'un point d'extension quasi nul » (§4) : chaque
17
+ * nouveau registerX() hérite gratuitement de la sémantique de démontage.
18
+ *
19
+ * La désactivation d'un plugin, c'est unload() : le rejeu de la pile en sens
20
+ * inverse. L'auteur n'écrit jamais de onunload() — il n'a jamais eu la
21
+ * responsabilité de nettoyer, donc il ne peut pas l'oublier.
22
+ *
23
+ * Noter le paramètre `owner` passé aux registres : app.registries devient
24
+ * auditable — chaque entrée dit quel plugin l'a posée.
25
+ */
26
+ declare abstract class Plugin$1 extends Component {
27
+ app: App;
28
+ manifest: PluginManifest;
29
+ constructor(app: App, manifest: PluginManifest);
30
+ /** §8.3 — un nouveau type de vue. */
31
+ registerView(type: string, creator: ViewCreator): void;
32
+ /** §8.3 — extension de fichier → type de vue. Ouvre photo.png dans ta vue. */
33
+ registerExtensions(extensions: string[], viewType: string): void;
34
+ /** §11.5 — l'id est préfixé par celui du plugin : deux plugins peuvent avoir 'open'. */
35
+ addCommand(command: Command): Command;
36
+ /** Retrait explicite. Prend l'id NON préfixé — le préfixe est ajouté ici. */
37
+ removeCommand(id: string): void;
38
+ /** §9.2 — une extension CM6 injectée dans les éditeurs (via livePreviewExtensions). */
39
+ registerEditorExtension(extension: Extension): void;
40
+ /** §10.2 — transformation du HTML rendu (le mode lecture, quand il existera). */
41
+ registerMarkdownPostProcessor(processor: MarkdownPostProcessor, priority?: number): void;
42
+ /** §10.2 — un bloc ```lang devient une zone d'UI arbitraire. */
43
+ registerMarkdownCodeBlockProcessor(lang: string, processor: CodeBlockProcessor): void;
44
+ /** §12.6 — une icône, ajoutée OU écrasant une Lucide du même nom. */
45
+ addIcon(name: string, svg: string): void;
46
+ /**
47
+ * §11.3 — une icône dans le ribbon. Renvoie le bouton, comme Obsidian :
48
+ * les plugins s'en servent pour le styler ou le cacher. C'est possible
49
+ * parce que trigger('changed') est SYNCHRONE : pendant le register()
50
+ * ci-dessous, WorkspaceRibbon a déjà reconstruit et posé buttonEl.
51
+ */
52
+ addRibbonIcon(icon: string, title: string, callback: (evt: MouseEvent) => unknown): HTMLElement;
53
+ /**
54
+ * View layers — contribue un layer au catalogue, retiré à unload
55
+ * (plan-view-layers.md). Même mariage que registerView : j'écris dans la
56
+ * table, je programme mon retrait.
57
+ */
58
+ registerLayer(spec: LayerSpec): void;
59
+ /**
60
+ * §15 bis — une sous-commande du CLI (API 1.12 d'Obsidian).
61
+ *
62
+ * Préfixée par l'id du plugin, comme une commande : `templater` qui
63
+ * enregistre `create-from-template` répond à
64
+ * `fragment templater:create-from-template template="x"`.
65
+ *
66
+ * Le handler REND sa sortie ; il n'a pas de stdout. Lever une `CliError`
67
+ * choisit le code de sortie ; lever autre chose donne 1 avec la pile.
68
+ */
69
+ registerCliHandler(command: string, description: string, flags: CliFlags, handler: CliHandler): void;
70
+ private get dataPath();
71
+ loadData(): Promise<unknown | null>;
72
+ saveData(data: unknown): Promise<void>;
73
+ }
74
+ declare abstract class WorkspaceItem extends Events {
75
+ app: App;
76
+ workspace: Workspace;
77
+ abstract parent: WorkspaceParent | null;
78
+ containerEl: HTMLElement;
79
+ constructor(workspace: Workspace, className: string);
80
+ getRoot(): WorkspaceItem;
81
+ /**
82
+ * §8.2 — chaque nœud sait se réduire à du JSON. C'est le Composite qui fait
83
+ * son travail : le Workspace n'a aucune connaissance des formes de nœud, il
84
+ * demande à ses trois racines et récupère l'arbre entier.
85
+ */
86
+ abstract serialize(): LayoutNode;
87
+ }
88
+ declare abstract class WorkspaceParent extends WorkspaceItem {
89
+ children: WorkspaceItem[];
90
+ protected get childrenEl(): HTMLElement;
91
+ addChild(child: WorkspaceItem, index?: number): void;
92
+ removeChild(child: WorkspaceItem): void;
93
+ }
94
+ declare class CommandManager extends Events {
95
+ name: string;
96
+ label: string;
97
+ /** Le stockage, COMPOSÉ : toute la mécanique clef→valeur vit ici. */
98
+ private table;
99
+ private app;
100
+ constructor(app: App);
101
+ /**
102
+ * Enregistre une commande et rend sa fonction de retrait (§3.2).
103
+ *
104
+ * ★ On NE MUTE PAS l'objet reçu (A26) : le désucrage écrit `checkCallback`
105
+ * sur NOTRE copie (`entry`), qui devient l'entrée du registre. L'appelant
106
+ * garde son objet intact ; pour tenir un handle vivant sur la commande
107
+ * réellement exécutée, il lit l'entrée via findCommand (Plugin.addCommand).
108
+ *
109
+ * @param command Son `id` doit déjà être préfixé (Plugin.addCommand s'en charge).
110
+ * @param owner L'id du plugin propriétaire, pour l'audit.
111
+ * @returns La fonction de retrait, à confier à `Component.register`.
112
+ */
113
+ addCommand(command: Command, owner?: string): () => void;
114
+ /**
115
+ * Retrait explicite par id (parité d'API avec Obsidian).
116
+ *
117
+ * Préférer la fermeture rendue par `addCommand` : elle porte la garde
118
+ * d'identité, celle-ci non — un retrait explicite est un ordre.
119
+ */
120
+ removeCommand(id: string): void;
121
+ /** Lecture brute, sans aucun filtrage de disponibilité. */
122
+ findCommand(id: string): Command | undefined;
123
+ /**
124
+ * Les commandes DISPONIBLES maintenant — le seul endroit qui passe
125
+ * `checking = true`.
126
+ *
127
+ * ★ Une commande dont le `checkCallback` LÈVE est masquée, pas propagée :
128
+ * un plugin tiers ne doit pas pouvoir vider la palette de l'utilisateur.
129
+ */
130
+ listCommands(): Command[];
131
+ /**
132
+ * Exécution par id. Rend `true` si la commande a tourné ; `false` si l'id est
133
+ * inconnu, si la commande était indisponible, ou si elle a levé (A30).
134
+ */
135
+ executeCommandById(id: string, evt?: Event | null): boolean;
136
+ /**
137
+ * Exécution directe. Rend « la commande a-t-elle tourné ? » (voir l'en-tête).
138
+ *
139
+ * ★ Pose `app.lastEvent` AVANT d'appeler : une commande peut ainsi lire les
140
+ * modificateurs de la frappe ou du clic qui l'a déclenchée (Keymap.isModEvent).
141
+ */
142
+ executeCommand(command: Command, evt?: Event | null): boolean;
143
+ get size(): number;
144
+ entries(): Array<{
145
+ key: string;
146
+ value: Command;
147
+ owner: string;
148
+ }>;
149
+ }
150
+ declare class DragManager extends Events {
151
+ app: App;
152
+ source: DragData | null;
153
+ private ghostEl;
154
+ private captureEl;
155
+ private pointerId;
156
+ private onMove;
157
+ private onUp;
158
+ private onCancel;
159
+ constructor(app: App);
160
+ startDrag(event: PointerEvent, captureEl: HTMLElement, data: DragData, label: string): void;
161
+ private moveGhost;
162
+ endDrag(): void;
163
+ }
164
+ declare class HotkeyManager {
165
+ /** id de commande → raccourcis par défaut. Jamais persistés. */
166
+ defaultKeys: Record<string, Hotkey[]>;
167
+ /** id de commande → raccourcis de l'utilisateur. C'est le fichier. */
168
+ private custom;
169
+ private baked;
170
+ private bakedHotkeys;
171
+ private bakedIds;
172
+ private app;
173
+ private rechargerBientot;
174
+ constructor(app: App);
175
+ private get chemin();
176
+ load(): Promise<void>;
177
+ save(): Promise<void>;
178
+ getDefaultHotkeys(id: string): Hotkey[] | undefined;
179
+ addDefaultHotkeys(id: string, hotkeys: Hotkey[]): void;
180
+ removeDefaultHotkeys(id: string): void;
181
+ /** Les personnalisations d'une commande, ou `undefined` si elle n'en a pas. */
182
+ getHotkeys(id: string): Hotkey[] | undefined;
183
+ /** Une COPIE : la table interne n'est modifiable que par set/remove. */
184
+ getCustomKeys(): Record<string, Hotkey[]>;
185
+ setHotkeys(id: string, hotkeys: Hotkey[]): void;
186
+ removeHotkeys(id: string): void;
187
+ /** Ce qu'il faut AFFICHER pour une commande : la personnalisation, sinon le défaut. */
188
+ hotkeysForCommand(id: string): Hotkey[];
189
+ /** Force la recompilation au prochain dispatch. Public pour les tests. */
190
+ invalidate(): void;
191
+ private bake;
192
+ /**
193
+ * Frappe → commande. Rend `false` pour consommer, `undefined` pour laisser passer.
194
+ *
195
+ * ★ NE CONSULTE PAS LA DISPONIBILITÉ, et c'est délibéré (cf. l'en-tête de
196
+ * CommandManager). Une commande d'éditeur déclenchée sans éditeur actif
197
+ * ne fait rien MAIS AVALE QUAND MÊME LA FRAPPE — parce qu'`executeCommand`
198
+ * rend `true` dès que rien n'a levé.
199
+ *
200
+ * C'est le comportement d'Obsidian, et la source du fameux « mon raccourci
201
+ * ne fait rien, et en plus il bloque le comportement par défaut ». On le
202
+ * copie sciemment : le corriger voudrait dire appeler `checkCallback(true)`
203
+ * sur chaque liaison à chaque frappe, dans le chemin le plus chaud de l'app.
204
+ */
205
+ private onTrigger;
206
+ }
207
+ declare class OrderedRegistry<T> extends Events {
208
+ name: string;
209
+ label: string;
210
+ items: Array<{
211
+ value: T;
212
+ priority: number;
213
+ owner: string;
214
+ }>;
215
+ constructor(name: string, label?: string);
216
+ register(value: T, { priority, owner }?: {
217
+ priority?: number;
218
+ owner?: string;
219
+ }): (() => void);
220
+ values(): T[];
221
+ entries(): Array<{
222
+ key: string;
223
+ value: T;
224
+ owner: string;
225
+ }>;
226
+ get size(): number;
227
+ }
228
+ declare class Plugins {
229
+ app: App;
230
+ /** Les instances chargées, par id. */
231
+ plugins: Map<string, Plugin$1>;
232
+ /** Les manifestes découverts (chargés ou non). */
233
+ manifests: Map<string, PluginManifest>;
234
+ /** Les classes internes enregistrées par seedCore, en attente de load. */
235
+ private internal;
236
+ constructor(app: App);
237
+ get pluginsDir(): string;
238
+ /** seedCore passe par ici — AUCUN privilège : la classe sera instanciée et
239
+ * chargée exactement comme un plugin communautaire. */
240
+ registerInternalPlugin(manifest: PluginManifest, ctor: PluginConstructor): void;
241
+ /**
242
+ * §16.1 étape 5 — après le Vault, avant que l'UI ne dépende des vues.
243
+ * Internes d'abord (markdown, explorateur), puis découverte du disque.
244
+ */
245
+ initialize(): Promise<void>;
246
+ private discoverCommunityPlugins;
247
+ /**
248
+ * §11.2 reconstitué : lire main.js via l'adapter, l'évaluer comme module
249
+ * CommonJS, servir les modules hôtes par le shim, instancier, charger.
250
+ */
251
+ private loadCommunityPlugin;
252
+ private instantiate;
253
+ /**
254
+ * §16.4 — la désactivation est L'EXACT MIROIR : unload() rejoue tous les
255
+ * register* en sens inverse, les registres se vident (garde d'identité
256
+ * comprise), les vues ouvertes du plugin dégradent en placeholder — et se
257
+ * rematérialiseront à la réactivation, via viewRegistry.on('changed').
258
+ */
259
+ disablePlugin(id: string): void;
260
+ enablePlugin(id: string): Promise<void>;
261
+ }
262
+ declare class Registry<T> extends Events {
263
+ map: Map<string, {
264
+ value: T;
265
+ owner: string;
266
+ }>;
267
+ name: string;
268
+ label: string;
269
+ constructor(name: string, label?: string);
270
+ register(key: string, value: T, owner?: string): (() => void);
271
+ /**
272
+ * Retrait explicite par clef ; rend `true` si quelque chose a été retiré.
273
+ *
274
+ * La fermeture rendue par `register()` reste la voie NORMALE — elle porte la
275
+ * garde d'identité (ne défaire que si l'entrée est toujours la sienne). Ceci
276
+ * est un ordre direct, sans garde : à réserver aux retraits explicites.
277
+ */
278
+ remove(key: string): boolean;
279
+ get(key: string): T | undefined;
280
+ has(key: string): boolean;
281
+ keys(): Array<string>;
282
+ entries(): Array<{
283
+ key: string;
284
+ value: T;
285
+ owner: string;
286
+ }>;
287
+ get size(): number;
288
+ }
289
+ declare class ViewOverlays implements OverlayHost {
290
+ private paneParent;
291
+ /** Le repère de `coordsAtPos` — c'est LUI qui héberge le plan document. */
292
+ private documentParent;
293
+ private viewportRoot;
294
+ private documentRoot;
295
+ private geometryCbs;
296
+ private observer;
297
+ private lastWidths;
298
+ /**
299
+ * L'échelle appliquée au plan document par la vue, lue à CHAQUE conversion.
300
+ *
301
+ * ★ POURQUOI une fonction et pas un nombre : le zoom d'une vue PDF change
302
+ * sans prévenir personne. Une valeur copiée serait périmée au premier
303
+ * zoom — le même piège que l'écart de repère qu'on a supprimé plus haut,
304
+ * et pour la même raison : ne jamais dupliquer ce qui bouge.
305
+ */
306
+ private scale;
307
+ /**
308
+ * La COLONNE DE LECTURE — l'élément dont les marges sont les marges.
309
+ *
310
+ * ★ POURQUOI c'est une entrée distincte du plan document, alors que ç'a
311
+ * longtemps été le même élément : parce que ce sont deux rôles différents,
312
+ * et qu'ils ne coïncident que par accident. Le plan est un REPÈRE (l'origine
313
+ * des coordonnées) ; la colonne est une GÉOMÉTRIE (là où s'arrête le texte).
314
+ * Sur un markdown, `.cm-scroller` était les deux à la fois, donc la
315
+ * confusion ne coûtait rien. Sur un PDF, le plan est une boîte 0×0 — en
316
+ * déduire la colonne donnait une marge gauche négative et une marge droite
317
+ * large comme le pane entier.
318
+ *
319
+ * Une vue DÉCLARE donc sa colonne (`ItemView.documentColumn`) au lieu qu'on
320
+ * la devine. Défaut : le plan lui-même, ce qui préserve le comportement des
321
+ * vues où les deux coïncident vraiment.
322
+ */
323
+ private columnParent;
324
+ constructor(paneParent: HTMLElement, documentParent?: HTMLElement | null, scale?: () => number, columnParent?: HTMLElement | null);
325
+ /** L'échelle, bornée : une valeur nulle ou absurde ferait exploser les divisions. */
326
+ private echelle;
327
+ mount(el: HTMLElement, plane: OverlayPlane): () => void;
328
+ clientToViewport(x: number, y: number): {
329
+ x: number;
330
+ y: number;
331
+ } | null;
332
+ clientToDocument(x: number, y: number): {
333
+ x: number;
334
+ y: number;
335
+ } | null;
336
+ gutterBand(side: GutterSide): {
337
+ left: number;
338
+ width: number;
339
+ } | null;
340
+ onGeometryChange(cb: () => void): () => void;
341
+ destroy(): void;
342
+ /**
343
+ * L'origine du repère `coordsAtPos`, en coords CLIENT — l'inverse exact du
344
+ * `getBase()` de CM6. Le terme de scroll est nul tant que le scroller de
345
+ * l'éditeur ne scrolle pas lui-même (chez nous c'est `.doc-scroll` qui s'en
346
+ * charge), mais le garder rend le calcul juste dans les deux cas.
347
+ */
348
+ private documentOrigin;
349
+ /**
350
+ * Un seul ResizeObserver pour toute la vue, ouvert au premier abonné et fermé
351
+ * au dernier. Il regarde les deux boîtes dont dépendent les marges : le pane et
352
+ * la colonne.
353
+ *
354
+ * Le filtre sur les LARGEURS n'est pas de l'optimisation prématurée : la
355
+ * colonne change de hauteur à chaque frappe, ce qui déclencherait l'observateur
356
+ * en continu pour une géométrie de marge inchangée.
357
+ */
358
+ private ensureObserver;
359
+ private stopObserver;
360
+ private ensureViewport;
361
+ private ensureDocument;
362
+ private static makeRoot;
363
+ }
364
+ declare class WorkspaceRibbon {
365
+ workspace: Workspace;
366
+ app: App;
367
+ side: "left" | "right";
368
+ containerEl: HTMLElement;
369
+ /** Les actions des plugins — la partie pilotée par le registre. */
370
+ private actionsEl;
371
+ constructor(workspace: Workspace, side?: "left" | "right");
372
+ /**
373
+ * Projette le registre dans le DOM. replaceChildren + boutons STABLES :
374
+ * un retrait fait disparaître le bouton (plus ré-appendu), un ajout
375
+ * s'insère à sa place de priorité — et les références déjà rendues aux
376
+ * plugins par addRibbonIcon restent les éléments vivants, listeners
377
+ * compris. Reconstruire les boutons à chaque fois les rendrait orphelins.
378
+ */
379
+ private rebuild;
380
+ /** Crée le bouton d'un item UNE SEULE FOIS — mémoïsé sur l'item lui-même. */
381
+ private buttonFor;
382
+ }
383
+ declare class WorkspaceRoot extends WorkspaceSplit {
384
+ parent: WorkspaceParent | null;
385
+ constructor(workspace: Workspace);
386
+ }
387
+ declare class WorkspaceSidedock extends WorkspaceSplit {
388
+ collapsed: boolean;
389
+ side: "left" | "right";
390
+ constructor(workspace: Workspace, side: "left" | "right");
391
+ /**
392
+ * Un dock ajoute les deux choses qui lui sont propres : son repli (un vrai
393
+ * champ) et sa largeur (une propriété CSS posée par
394
+ * Workspace.attachSidebarResize). `--sidebar-width` est lue sur le style
395
+ * INLINE et non calculée : tant que l'utilisateur n'a pas dragué, elle est
396
+ * vide — et c'est exactement ce qu'on veut sérialiser, « pas de largeur
397
+ * choisie », pour que le CSS garde la main au restore.
398
+ */
399
+ serialize(): SplitLayout;
400
+ toggle(): void;
401
+ collapse(): void;
402
+ expand(): void;
403
+ }
404
+ declare class WorkspaceSplit extends WorkspaceParent {
405
+ parent: WorkspaceParent | null;
406
+ direction: SplitDirection;
407
+ constructor(workspace: Workspace, direction?: SplitDirection, className?: string);
408
+ /**
409
+ * §8.2 — la géométrie est LUE DANS LE DOM au moment de sérialiser.
410
+ *
411
+ * C'est un choix assumé : les proportions vivent dans le `flex-grow` que
412
+ * pose attachResize (plus bas), et nulle part ailleurs. Le modèle n'en a
413
+ * pas de copie, donc rien à tenir synchronisé — mais le DOM reste la source
414
+ * de vérité de la géométrie, et une lecture faite pendant que l'arbre est
415
+ * caché ou en transition rendrait des valeurs fausses. C'est le garde
416
+ * `layoutReady` de Workspace.saveLayout() qui écarte ce cas.
417
+ */
418
+ serialize(): SplitLayout;
419
+ protected serializeChildren(): LayoutNode[];
420
+ setDirection(direction: SplitDirection): void;
421
+ addChild(child: WorkspaceItem, index?: number): void;
422
+ removeChild(child: WorkspaceItem): void;
423
+ /**
424
+ * Le point UNIQUE « les enfants de ce split ont changé ». Deux responsabilités
425
+ * indissociables d'un changement structurel : refaire les poignées, et
426
+ * rétablir l'invariant de géométrie. Appelé par addChild, removeChild et la
427
+ * restauration (Workspace.applyGeometry) — un seul propriétaire, pour qu'une
428
+ * future mutation ne puisse pas oublier l'un des deux (le bug d'origine :
429
+ * removeChild refaisait les poignées mais pas la géométrie).
430
+ */
431
+ reflowGeometry(): void;
432
+ /**
433
+ * L'invariant de géométrie : la somme des flex-grow des enfants vaut leur
434
+ * nombre (moyenne 1), proportions gardées. Sans lui, une somme < 1 laisse un
435
+ * trou (piège flexbox §9.7, voir normalizeGrows) — typiquement l'unique
436
+ * survivant d'un merge, hérité d'un resize à 0.95. On lit le flex-grow inline
437
+ * (growInline), donc ça marche même sur un split encore détaché (restauration).
438
+ */
439
+ private normalizeGrow;
440
+ /**
441
+ * (Re)crée les poignées : une avant chaque enfant sauf le premier. Ce sont
442
+ * des <div> DOM, PAS des nœuds de children[] — d'où la régénération plutôt
443
+ * qu'un suivi. Sans état ; l'état du resize est capturé au pointerdown.
444
+ */
445
+ private rebuildResizeHandles;
446
+ /**
447
+ * Drague la poignée → transfère du flex-grow entre les deux voisins EN
448
+ * GARDANT leur somme constante : les autres panes ne bougent pas. Les enfants
449
+ * sont flex: 1 1 0% → taille ∝ flex-grow.
450
+ */
451
+ private attachResize;
452
+ }
453
+ declare class WorkspaceTabs extends WorkspaceParent {
454
+ parent: WorkspaceParent | null;
455
+ tabHeaderEl: HTMLElement;
456
+ tabHeaderInnerEl: HTMLElement;
457
+ tabContentEl: HTMLElement;
458
+ private outlineEl;
459
+ private outlinePathEl;
460
+ private outlineObserver;
461
+ private activeIndex;
462
+ private suppressTabClick;
463
+ constructor(workspace: Workspace);
464
+ /**
465
+ * On redirige l'insertion vers tabContentEl : sans ça, addChild() mettrait
466
+ * les feuilles à côté de la barre d'onglets au lieu de dessous.
467
+ */
468
+ protected get childrenEl(): HTMLElement;
469
+ addChild(child: WorkspaceItem, index?: number): void;
470
+ removeChild(child: WorkspaceItem): void;
471
+ /** §8.2 — une pile, et l'onglet qu'elle montrait. */
472
+ serialize(): TabsLayout;
473
+ selectTab(index: number): void;
474
+ getActiveLeaf(): WorkspaceLeaf | null;
475
+ /**
476
+ * Reconstruit la barre d'onglets. Public : une feuille dont la vue vient de
477
+ * changer (setViewState) appelle ceci sur son parent — sans ça, un onglet
478
+ * garderait le titre de l'ANCIENNE vue (« Vide » après l'ouverture d'un
479
+ * fichier, par exemple).
480
+ */
481
+ refreshHeaders(): void;
482
+ private rebuildTabHeaders;
483
+ /** L'interstice (0…N) à cette abscisse, en scannant les onglets. */
484
+ private dropIndexAt;
485
+ /** Traduit un point en zone de drop, selon la géométrie PROPRE de la pile. */
486
+ private zoneAt;
487
+ /**
488
+ * Le commit d'un drop : selon la zone sous le curseur, réordonne, rejoint la
489
+ * pile (centre), ou crée un split (les 4 bords). C'est ici — et pas dans
490
+ * l'aperçu — que la vraie hiérarchie WorkspaceSplit entre en jeu.
491
+ */
492
+ dropLeaf(leaf: WorkspaceLeaf, x: number, y: number): void;
493
+ /** Réordonne dans cette pile, à l'interstice sous le curseur. */
494
+ private reorderLeaf;
495
+ /**
496
+ * Crée un split : une nouvelle pile accueille `leaf`, du côté indiqué par la
497
+ * zone. La RÈGLE unique :
498
+ * - même axe que le parent → pile sœur (addChild) ;
499
+ * - axe perpendiculaire → on enveloppe ce pane dans un nouveau WorkspaceSplit
500
+ * de l'axe voulu, contenant [pane, nouvelle pile] dans le bon ordre.
501
+ * Le rendu sort du flex (mod-vertical/horizontal), aucun inset bricolé.
502
+ *
503
+ * PUBLIC depuis §11.5 : le drag l'appelait seul ; `Workspace.createLeafBySplit`
504
+ * — donc les commandes `workspace:split-*` et le menu ⋯ — passent par la
505
+ * MÊME méthode. Une seule implémentation du split, trois portes d'entrée.
506
+ */
507
+ splitLeaf(leaf: WorkspaceLeaf, zone: DropZone): void;
508
+ /**
509
+ * Actions sur les onglets, puis la liste de TOUS les onglets (icône + titre,
510
+ * coche sur l'actif). Builder + sections, comme les autres menus du projet
511
+ * (voir core/Menu.ts).
512
+ */
513
+ private showTabListMenu;
514
+ /** Ferme tous les onglets du pane. Le dernier detach déclenche onPileEmptied
515
+ * (le pane se ferme s'il est dans un split, sinon un onglet vide s'ouvre). */
516
+ private closeAll;
517
+ /**
518
+ * Aperçu du drop selon la zone sous le curseur : barre de réordonnancement
519
+ * (bas de la barre) ou aperçu de split (les 4 bords). Purement visuel et
520
+ * éphémère — aucune mutation d'arbre ici.
521
+ */
522
+ showDropIndicator(x: number, y: number): void;
523
+ /** La barre --drop-x à l'interstice sous le curseur. */
524
+ private showReorderBar;
525
+ clearDropIndicator(): void;
526
+ /**
527
+ * Retrace le contour de l'onglet actif : la baseline de la barre,
528
+ * interrompue sous l'onglet pour épouser sa silhouette — montée, arcs
529
+ * convexes en haut, arcs concaves au pied (ceux que les ::before/::after
530
+ * CSS remplissent). Un seul path pour toute la barre : une bordure CSS
531
+ * par morceau laisserait des raccords visibles aux jonctions.
532
+ */
533
+ private updateTabOutline;
534
+ }
535
+ declare global {
536
+ interface Window {
537
+ app: App;
538
+ }
539
+ }
540
+ /**
541
+ * Un maillon PUREMENT MARQUEUR de la hiérarchie — corps vide, et c'est fidèle :
542
+ * chez Obsidian aussi elle ne déclare rien du tout.
543
+ *
544
+ * Son rôle est de séparer, dans la chaîne d'héritage, les vues de fichier
545
+ * simplement lisibles (image, PDF) de celles qui savent écrire (TextFileView →
546
+ * MarkdownView). Un `instanceof EditableFileView` répond alors à « cette vue
547
+ * peut-elle modifier son fichier ? » sans connaître le type concret.
548
+ *
549
+ * Ne la supprime pas sous prétexte qu'elle est vide : c'est le point
550
+ * d'accrochage prévu pour ce qui concernera l'édition en général.
551
+ */
552
+ export declare abstract class EditableFileView extends FileView {
553
+ }
554
+ export declare abstract class FileView extends ItemView {
555
+ file: TFile | null;
556
+ allowNoFile: boolean;
557
+ navigation: boolean;
558
+ constructor(leaf: WorkspaceLeaf);
559
+ onload(): void;
560
+ loadFile(file: TFile | null): Promise<void>;
561
+ onLoadFile(file: TFile): Promise<void>;
562
+ onUnloadFile(file: TFile): Promise<void>;
563
+ onRename(file: TFile): Promise<void>;
564
+ canAcceptExtension(extension: string): boolean;
565
+ /** Le fichier de la vue, pour le LayerContext (ItemView le veut null par défaut). */
566
+ protected getViewFile(): TFile | null;
567
+ /**
568
+ * L'étage FICHIER du menu ⋯ (§5.3). Les entrées grisées sont la silhouette
569
+ * du menu d'Obsidian : chacune s'activera quand sa machinerie existera —
570
+ * le TODO dit laquelle.
571
+ */
572
+ onPaneMenu(menu: Menu, source: string): void;
573
+ /**
574
+ * Le geste « Renommer » — template method : FileView ne sait pas COMMENT on
575
+ * renomme (chaque vue a sa propre UI). MarkdownView focus son titre inline ;
576
+ * une vue sans UI de renommage laisse le no-op.
577
+ */
578
+ protected requestRename(): void;
579
+ getDisplayText(): string;
580
+ getState(): Record<string, unknown>;
581
+ setState(state: unknown, result: ViewStateResult): Promise<void>;
582
+ }
583
+ /**
584
+ * §14 — une SuggestModal branchée sur la recherche floue.
585
+ *
586
+ * Les trois trous de SuggestModal se réduisent à trois trous plus petits :
587
+ * quels ÉLÉMENTS (`getItems`), quel TEXTE pour chacun (`getItemText`), que
588
+ * faire du choix (`onChooseItem`). Le filtrage, le tri et le surlignage sont
589
+ * fournis. Le quick switcher et la palette de commandes sont exactement ça.
590
+ *
591
+ * ★ `T` est l'élément de la sous-classe ; ce que SuggestModal manipule est un
592
+ * `FuzzyMatch<T>` — l'élément ET son match, pour surligner au rendu. La
593
+ * sous-classe n'a pas à le savoir : `onChooseItem` reçoit `T` nu.
594
+ */
595
+ export declare abstract class FuzzySuggestModal<T> extends SuggestModal<FuzzyMatch<T>> {
596
+ abstract getItems(): T[];
597
+ abstract getItemText(item: T): string;
598
+ abstract onChooseItem(item: T, evt: MouseEvent | KeyboardEvent): void;
599
+ getSuggestions(query: string): FuzzyMatch<T>[];
600
+ /**
601
+ * Le tri, isolé pour être surchargeable.
602
+ *
603
+ * ★ Requête VIDE : on ne trie PAS. `getItems()` a alors le dernier mot sur
604
+ * l'ordre — c'est ce qui permet à la palette de montrer épinglés puis
605
+ * récents avant la première frappe. Dès qu'on tape, c'est le score.
606
+ */
607
+ protected sortSuggestions(resultats: FuzzyMatch<T>[], query: string): void;
608
+ /** Défaut : le texte, avec les plages matchées surlignées. */
609
+ renderSuggestion(value: FuzzyMatch<T>, el: HTMLElement): void;
610
+ onChooseSuggestion(value: FuzzyMatch<T>, evt: MouseEvent | KeyboardEvent): void;
611
+ }
612
+ export declare abstract class ItemView extends View {
613
+ headerEl: HTMLElement;
614
+ actionsEl: HTMLElement;
615
+ moreOptionsEl: HTMLElement;
616
+ contentEl: HTMLElement;
617
+ constructor(leaf: WorkspaceLeaf);
618
+ /** Active cette feuille, puis exécute — le contrat de toute entrée de menu ⋯. */
619
+ protected executerIci(commandId: string): void;
620
+ addAction(icon: string, title: string, callback: (evt: MouseEvent) => unknown): HTMLElement;
621
+ /** Points de montage d'overlay de cette vue (viewport ; + document si éditeur). */
622
+ protected overlays: ViewOverlays | null;
623
+ /** Ids des layers actifs sur CETTE vue (survit à rebuildView via l'état éphémère). */
624
+ private activeLayers;
625
+ /** Les instances montées, par id — ressources jetables (un disposer chacune). */
626
+ private mountedLayers;
627
+ /**
628
+ * La surface de document de la vue, s'il y en a une. Défaut : aucune.
629
+ *
630
+ * ★ PUBLIC depuis §11.5, et ce n'est pas un détail : `Workspace.activeEditor`
631
+ * le lit pour résoudre la cible d'une commande d'éditeur. Le hook servait
632
+ * jusqu'ici aux seuls layers, qui vivent DANS la vue ; il est désormais
633
+ * aussi une porte vers l'extérieur.
634
+ */
635
+ getEditor(): DocumentSurface | null;
636
+ /**
637
+ * Le facteur d'échelle du plan document, quand la vue en applique un.
638
+ *
639
+ * ★ POURQUOI ce hook existe : une vue peut zoomer (le PDF le fait). Le plan
640
+ * document vit alors dans un espace NON zoomé — c'est ce qui rend les
641
+ * ancres invariantes au zoom : la page change de taille en pixels, pas
642
+ * l'annotation relativement à elle. Mais un événement pointeur, lui, arrive
643
+ * toujours en pixels écran : la conversion doit donc diviser par l'échelle.
644
+ *
645
+ * Même mécanique que le `scaleX/scaleY` de CM6, pour la même raison. Défaut
646
+ * 1 : une vue qui ne zoome pas ne paie rien.
647
+ */
648
+ protected documentScale(): number;
649
+ /**
650
+ * La COLONNE DE LECTURE de la vue — l'élément dont les bords définissent les
651
+ * marges où se rangent les widgets de gouttière.
652
+ *
653
+ * ★ Distinct de `documentPlane()`, et la distinction n'est pas théorique :
654
+ * le plan est un REPÈRE, la colonne est une GÉOMÉTRIE. Ils coïncidaient
655
+ * tant que la seule vue ancrable était le markdown, où `.cm-scroller` joue
656
+ * les deux rôles. Une vue PDF a un plan de taille nulle — en déduire la
657
+ * colonne donnait une marge gauche négative et une marge droite large comme
658
+ * tout le pane.
659
+ *
660
+ * Défaut : le plan, ce qui préserve exactement le comportement des vues où
661
+ * les deux sont le même élément.
662
+ */
663
+ protected documentColumn(): HTMLElement | null;
664
+ /**
665
+ * L'élément qui sert de REPÈRE à `coordsAtPos`, si la vue a un éditeur : c'est
666
+ * lui qui hébergera le plan document, pour que les coordonnées de l'éditeur y
667
+ * retombent sans conversion. Défaut : aucun (plan viewport seul).
668
+ */
669
+ protected documentPlane(): HTMLElement | null;
670
+ /** Le fichier de la vue, pour le LayerContext (FileView le surcharge). */
671
+ protected getViewFile(): TFile | null;
672
+ /**
673
+ * Monté après onOpen() (cf. View.open) : l'éditeur de la sous-classe existe,
674
+ * donc documentPlane() peut le référencer.
675
+ */
676
+ protected onAfterOpen(): void;
677
+ /** Bascule un layer sur cette vue et remonte l'écran dans la foulée (synchrone). */
678
+ toggleLayer(id: string): void;
679
+ private showLayerMenu;
680
+ /** Le catalogue × l'activation × l'applicabilité → montage/démontage par clef. */
681
+ private reconcileLayers;
682
+ private layerApplies;
683
+ private layerContext;
684
+ getEphemeralState(): Record<string, unknown>;
685
+ setEphemeralState(state: unknown): void;
686
+ }
687
+ /**
688
+ * §9.4 / §12.3 — une modale de SUGGESTION : un champ, une liste, un choix.
689
+ *
690
+ * C'est le « framework inversé » du document d'archi : la classe impose le
691
+ * squelette (le champ, le clavier, la sélection, le défilement) et la
692
+ * sous-classe remplit trois trous — d'où viennent les suggestions, comment en
693
+ * dessiner une, que faire de celle qu'on choisit. Le quick switcher et la
694
+ * palette de commandes ne sont que deux remplissages de ces trois trous.
695
+ *
696
+ * ★ Le clavier passe par la PORTÉE de la modale (héritée de Modal), pas par des
697
+ * `keydown` sur l'input. Différence concrète : les flèches et Entrée sont
698
+ * consommées AVANT de pouvoir atteindre quoi que ce soit d'autre, et un
699
+ * raccourci global sur ces touches ne peut pas voler la frappe.
700
+ *
701
+ * Classes CSS d'Obsidian — l'API des thèmes :
702
+ *
703
+ * .modal-container.mod-dim
704
+ * .modal-bg
705
+ * .prompt ← remplace .modal
706
+ * .prompt-input-container
707
+ * input.prompt-input
708
+ * .prompt-results
709
+ * .suggestion-item.is-selected
710
+ * .prompt-instructions
711
+ * .prompt-instruction
712
+ * .prompt-instruction-command
713
+ */
714
+ export declare abstract class SuggestModal<T> extends Modal {
715
+ /** Nombre maximal de suggestions rendues. */
716
+ limit: number;
717
+ emptyStateText: string;
718
+ inputEl: HTMLInputElement;
719
+ resultContainerEl: HTMLElement;
720
+ private instructionsEl;
721
+ private suggestions;
722
+ private itemEls;
723
+ private selected;
724
+ /** Numéro de la dernière requête : une réponse asynchrone en retard est jetée. */
725
+ private requete;
726
+ constructor(app: App);
727
+ /** D'où viennent les suggestions pour cette requête. Peut être asynchrone. */
728
+ abstract getSuggestions(query: string): T[] | Promise<T[]>;
729
+ /** Dessine UNE suggestion dans `el` (déjà `.suggestion-item`). */
730
+ abstract renderSuggestion(value: T, el: HTMLElement): void;
731
+ /** Que faire de celle que l'utilisateur a choisie. La modale est déjà fermée. */
732
+ abstract onChooseSuggestion(item: T, evt: MouseEvent | KeyboardEvent): void;
733
+ /** Rien ne correspond. Défaut : le texte d'état vide. */
734
+ onNoSuggestion(): void;
735
+ setPlaceholder(placeholder: string): this;
736
+ setInstructions(instructions: Instruction[]): this;
737
+ onOpen(): void;
738
+ onClose(): void;
739
+ private updateSuggestions;
740
+ private setSelected;
741
+ private useSelectedItem;
742
+ /** Ferme PUIS notifie : la sous-classe peut ouvrir autre chose sans conflit de portée. */
743
+ selectSuggestion(value: T, evt: MouseEvent | KeyboardEvent): void;
744
+ }
745
+ export declare abstract class TAbstractFile {
746
+ vault: Vault;
747
+ path: string;
748
+ parent: TFolder | null;
749
+ constructor(vault: Vault, path: string, parent?: TFolder | null);
750
+ get name(): string;
751
+ }
752
+ export declare abstract class TextFileView extends EditableFileView {
753
+ data: string;
754
+ requestSave: () => void;
755
+ constructor(leaf: WorkspaceLeaf);
756
+ abstract getViewData(): string;
757
+ abstract setViewData(data: string, clear: boolean): void;
758
+ abstract clear(): void;
759
+ onLoadFile(file: TFile): Promise<void>;
760
+ onUnloadFile(file: TFile): Promise<void>;
761
+ save(clear?: boolean): Promise<void>;
762
+ onload(): void;
763
+ }
764
+ export declare abstract class View extends Component {
765
+ app: App;
766
+ leaf: WorkspaceLeaf;
767
+ containerEl: HTMLElement;
768
+ icon: string;
769
+ navigation: boolean;
770
+ /**
771
+ * §12.5 — les raccourcis propres à cette vue, actifs quand elle a le focus.
772
+ *
773
+ * Une vue qui en pose un doit l'empiler elle-même
774
+ * (`app.keymap.pushScope(this.scope)`) au focus et le dépiler à la perte —
775
+ * le workspace ne le fait pas à sa place, parce qu'il ne sait pas ce qu'une
776
+ * vue considère comme « avoir le focus ».
777
+ */
778
+ scope: Scope | null;
779
+ constructor(leaf: WorkspaceLeaf);
780
+ abstract getViewType(): string;
781
+ abstract getDisplayText(): string;
782
+ getIcon(): string;
783
+ protected onOpen(): Promise<void>;
784
+ protected onClose(): Promise<void>;
785
+ /**
786
+ * Hook de câblage de la BASE, joué APRÈS onOpen() (donc après que la vue a
787
+ * bâti son DOM et, le cas échéant, son éditeur). ItemView s'en sert pour
788
+ * monter l'hébergement de layers — le dev de vue n'a rien à faire. À ne pas
789
+ * confondre avec onOpen(), qui est le hook des sous-classes.
790
+ */
791
+ protected onAfterOpen(): void;
792
+ open(parentEl: Node): Promise<void>;
793
+ close(): Promise<void>;
794
+ getState(): Record<string, unknown>;
795
+ setState(state: unknown, result: ViewStateResult): Promise<void>;
796
+ getEphemeralState(): Record<string, unknown>;
797
+ setEphemeralState(state: unknown): void;
798
+ onResize(): void;
799
+ /**
800
+ * Le menu ⋯ du panneau. Interception par mutation (§5.3) : la vue reçoit un
801
+ * Menu EN CONSTRUCTION et y AJOUTE ses entrées — elle ne le crée pas, ne
802
+ * l'affiche pas, ne le ferme pas. C'est l'ouvreur (l'onglet) qui orchestre,
803
+ * et les plugins passeront après elle sur le même objet.
804
+ */
805
+ onPaneMenu(menu: Menu, source: "more-options" | "tab-header" | string): void;
806
+ }
807
+ export declare class App {
808
+ vault: Vault;
809
+ workspace: Workspace;
810
+ plugins: Plugins;
811
+ dragManager: DragManager;
812
+ /** La version de Fragment (injectée à la compilation, cf. __APP_VERSION__).
813
+ * Le cœur en est propriétaire — un plugin lit `app.version`, pas la
814
+ * constante de build, qui n'existe pas pour lui. */
815
+ version: string;
816
+ /** Le pont vers les capacités du main (eval hors CSP, shim sur le PATH).
817
+ * Câblé au boot (bootApp.tsx) depuis window.electron.cli ; le cœur et les
818
+ * plugins passent par ici plutôt que de toucher window.electron. */
819
+ cliHost: CliHost;
820
+ keymap: Keymap;
821
+ scope: Scope;
822
+ hotkeyManager: HotkeyManager;
823
+ viewRegistry: Registry<ViewCreator>;
824
+ extensionRegistry: Registry<string>;
825
+ embedRegistry: Registry<unknown>;
826
+ commands: CommandManager;
827
+ icons: Registry<string>;
828
+ protocolHandlers: Registry<ProtocolHandler>;
829
+ codeBlockProcessors: Registry<CodeBlockProcessor>;
830
+ propertyWidgets: Registry<unknown>;
831
+ layers: Registry<LayerSpec>;
832
+ cliHandlers: Registry<CliHandlerSpec>;
833
+ postProcessors: OrderedRegistry<MarkdownPostProcessor>;
834
+ editorExtensions: OrderedRegistry<Extension>;
835
+ ribbonItems: OrderedRegistry<RibbonItem>;
836
+ statusBarItems: OrderedRegistry<unknown>;
837
+ settingTabs: OrderedRegistry<unknown>;
838
+ /** Pour l'inspecteur d'UI — l'ordre d'affichage. */
839
+ registries: InspectableRegistry[];
840
+ lastEvent: Event | null;
841
+ constructor();
842
+ /** §15 — une URL arrive, on cherche l'action MAINTENANT dans la table. */
843
+ handleProtocol(action: string, params: Record<string, string>): void;
844
+ private cleLocale;
845
+ loadLocalStorage(key: string): unknown;
846
+ saveLocalStorage(key: string, data: unknown): void;
847
+ }
848
+ /**
849
+ * Une erreur qui PORTE un code de sortie.
850
+ *
851
+ * Un handler qui lève n'importe quoi rend EXIT.HANDLER_ERROR (1) avec la pile.
852
+ * Un handler qui lève une CliError choisit son code — « commande introuvable »
853
+ * n'est pas un plantage, c'est un 7, et un script doit pouvoir les distinguer.
854
+ */
855
+ export declare class CliError extends Error {
856
+ readonly code: number;
857
+ constructor(code: number, message: string);
858
+ }
859
+ export declare class Component {
860
+ _children: Component[];
861
+ _cleanups: (() => void)[];
862
+ _loaded: boolean;
863
+ load(): void;
864
+ unload(): void;
865
+ onunload(): void;
866
+ onload(): void;
867
+ addChild<T extends Component>(component: T): T;
868
+ removeChild<T extends Component>(component: T): T;
869
+ register(cb: () => any): void;
870
+ registerEvent(eventRef: EventRef): void;
871
+ registerDomEvent<K extends keyof HTMLElementEventMap>(el: HTMLElement, type: K, callback: (this: HTMLElement, ev: HTMLElementEventMap[K]) => unknown, options?: boolean | AddEventListenerOptions): void;
872
+ registerDomEvent<K extends keyof DocumentEventMap>(el: Document, type: K, callback: (this: Document, ev: DocumentEventMap[K]) => unknown, options?: boolean | AddEventListenerOptions): void;
873
+ registerDomEvent<K extends keyof WindowEventMap>(el: Window, type: K, callback: (this: Window, ev: WindowEventMap[K]) => unknown, options?: boolean | AddEventListenerOptions): void;
874
+ registerInterval(id: number): number;
875
+ }
876
+ export declare class EventRef {
877
+ emitter: Events;
878
+ name: string;
879
+ cb: (...args: any[]) => unknown;
880
+ ctx: unknown;
881
+ constructor(emitter: Events, name: string, cb: (...args: any[]) => unknown, ctx: unknown);
882
+ off(): void;
883
+ }
884
+ export declare class Events {
885
+ _handlers: Map<string, EventRef[]>;
886
+ on(name: string, cb: (...args: any[]) => unknown, ctx?: unknown): EventRef;
887
+ /** Désinscription par identité de fonction — conservée par compatibilité. */
888
+ off(name: string, cb: (...args: any[]) => unknown): void;
889
+ /** ★ Désinscription par référence. Ce que registerEvent() appelle. */
890
+ offref(ref: EventRef): void;
891
+ trigger(name: string, ...data: any[]): void;
892
+ /** §5.1 — le dispatch défensif. */
893
+ tryTrigger(ref: EventRef, args: any[]): void;
894
+ /** Introspection — sert à l'inspecteur d'UI. */
895
+ _listenerCount(name: string): number;
896
+ }
897
+ export declare class Keymap {
898
+ /** Les modificateurs de la DERNIÈRE frappe vue, en forme canonique. */
899
+ modifiers: string;
900
+ /** La portée du bas de la pile — celle où le HotkeyManager s'installe. */
901
+ readonly rootScope: Scope;
902
+ /** La portée active. C'est elle qui reçoit la frappe en premier. */
903
+ scope: Scope;
904
+ private prevScopes;
905
+ private detacher;
906
+ /**
907
+ * @param cible où écouter. `null` = nulle part (mode headless, tests) — le
908
+ * Keymap reste alors parfaitement utilisable par appel direct
909
+ * à `handleKeyEvent`, ce dont les tests se servent.
910
+ */
911
+ constructor(cible?: EventTarget | null);
912
+ destroy(): void;
913
+ getRootScope(): Scope;
914
+ pushScope(scope: Scope): void;
915
+ /**
916
+ * ★ Dépiler une portée qui n'est PAS au sommet ne se plaint pas : on la
917
+ * retire de la pile, et le sommet ne bouge pas. C'est ce qui rend le
918
+ * démontage désordonné sans danger — deux modales fermées dans le
919
+ * « mauvais » ordre laissent quand même une pile cohérente.
920
+ */
921
+ popScope(scope: Scope): void;
922
+ /** Public pour que les tests puissent injecter une frappe sans DOM. */
923
+ handleKeyEvent(evt: KeyboardEvent): boolean | void;
924
+ /** Les modificateurs d'un événement, en forme canonique. */
925
+ static getModifiers(evt: Pick<KeyboardEvent, "ctrlKey" | "metaKey" | "altKey" | "shiftKey">): string;
926
+ /** `['Mod','Shift']` → `"Ctrl,Shift"`. Résout, trie, joint. */
927
+ static compileModifiers(modifiers: Modifier[], plateforme?: Plateforme): string;
928
+ /**
929
+ * L'inverse — pour AFFICHER une liaison enregistrée en dur.
930
+ *
931
+ * Le modificateur natif de la plateforme redevient `Mod`, ce qui est la
932
+ * bonne réponse à « comment nommer cette combinaison à l'utilisateur ? ».
933
+ */
934
+ static decompileModifiers(compiles: string, plateforme?: Plateforme): Modifier[];
935
+ static isModifierKey(key: string): boolean;
936
+ /** Ce modificateur-là est-il enfoncé ? `Mod` se résout selon la plateforme. */
937
+ static isModifier(evt: Pick<KeyboardEvent, "ctrlKey" | "metaKey" | "altKey" | "shiftKey">, modifier: Modifier, plateforme?: Plateforme): boolean;
938
+ /**
939
+ * La liaison correspond-elle à la frappe ?
940
+ *
941
+ * ★ Les modificateurs sont comparés à l'IDENTIQUE, pas par inclusion :
942
+ * `Mod+P` ne se déclenche PAS sur `Mod+Shift+P`. Sans quoi tout raccourci
943
+ * serait le préfixe de tous ses sur-ensembles.
944
+ *
945
+ * ★ La touche est acceptée sur `vkey` (emplacement physique) OU sur `key`
946
+ * sans tenir compte de la casse. Le second point n'est pas une commodité :
947
+ * `Shift+A` produit `key === 'A'` tandis que `A` seul produit `'a'`, et
948
+ * une liaison écrite `'A'` doit marcher dans les deux cas — la
949
+ * différenciation par Shift est déjà faite par les modificateurs.
950
+ */
951
+ static isMatch(liaison: Liaison, ctx: KeymapContext): boolean;
952
+ /**
953
+ * §8.5 — l'événement demande-t-il une ouverture ailleurs ?
954
+ *
955
+ * C'est LA raison d'être d'`app.lastEvent` : une commande n'a pas accès à
956
+ * l'événement qui l'a déclenchée, mais elle peut lire celui-ci pour décider
957
+ * entre remplacer la feuille, ouvrir un onglet, ou splitter.
958
+ */
959
+ static isModEvent(evt: Event | null | undefined): PaneType | false;
960
+ }
961
+ /**
962
+ * Le menu contextuel (§12.3) — un builder impératif, DOM pur, à durée de vie
963
+ * courte : construit, montré, détruit à la fermeture. Jamais réutilisé.
964
+ *
965
+ * ★ POURQUOI extends Component : tout ce que le menu acquiert (listeners de
966
+ * fermeture sur document, éléments DOM, sous-menus) est enregistré via
967
+ * register*()/addChild() et rejoué à hide(). Un menu ne peut pas fuir — même
968
+ * mécanique que les vues. Les SOUS-MENUS sont des enfants Component : fermer
969
+ * un menu démonte récursivement toute la chaîne ouverte, à n'importe quelle
970
+ * profondeur, sans une ligne de gestion dédiée.
971
+ *
972
+ * ★ POURQUOI ce n'est PAS un registre : un menu est une contribution À
973
+ * L'INSTANT T. Le pattern est l'interception par mutation (§5.3) — celui qui
974
+ * ouvre le menu le construit, le peuple, puis le passe aux listeners d'un
975
+ * événement (`editor-menu`, `file-menu`, onPaneMenu) qui le MUTENT :
976
+ *
977
+ * const menu = new Menu();
978
+ * menu.addItem((i) => i.setTitle('Copier').onClick(…));
979
+ * workspace.trigger('editor-menu', menu, editor, view); // chacun ajoute
980
+ * menu.showAtMouseEvent(evt);
981
+ *
982
+ * « C'est moins pur que des contributions déclaratives, mais radicalement
983
+ * plus simple » — et cohérent avec la confiance totale accordée aux plugins.
984
+ */
985
+ export declare class Menu extends Component {
986
+ /** La racine, créée tout de suite mais attachée au body seulement à show. */
987
+ dom: HTMLElement;
988
+ /** Le menu qui nous héberge comme sous-menu. `null` pour un menu racine. */
989
+ private parentMenu;
990
+ /** Nos sous-menus (aussi enfants Component — ceci n'est que l'index typé). */
991
+ private childMenus;
992
+ /** Le sous-menu actuellement déployé, s'il y en a un. */
993
+ private activeSubmenu;
994
+ private items;
995
+ private hideCallbacks;
996
+ private shown;
997
+ /** L'ordre des sections déclaré par l'OUVREUR du menu (voir addSections). */
998
+ private declaredSections;
999
+ constructor();
1000
+ /** Le builder central. Rend `this` — tout s'enchaîne. */
1001
+ addItem(cb: (item: MenuItem) => unknown): this;
1002
+ addSeparator(): this;
1003
+ /**
1004
+ * Déclare l'ordre des sections de ce menu — appelé par celui qui OUVRE le
1005
+ * menu, avant de le passer aux listeners (`file-menu`, onPaneMenu…).
1006
+ *
1007
+ * ★ POURQUOI les sections : c'est la pièce qui rend l'interception par
1008
+ * mutation (§5.3) viable à plusieurs contributeurs. Sans elles, l'ordre
1009
+ * visuel du menu dépend de l'ordre d'exécution des listeners — arbitraire.
1010
+ * Avec setSection, chaque item déclare son GROUPE (« je suis une action
1011
+ * destructive ») et atterrit au bon endroit, peu importe qui a parlé
1012
+ * quand. Même contrat qu'Obsidian : setSection est public (0.15.3),
1013
+ * addSections est réservé à l'ouvreur.
1014
+ *
1015
+ * Les sections non déclarées apparaissent après, dans l'ordre de première
1016
+ * apparition. Les items sans section forment le groupe '' (idem).
1017
+ */
1018
+ addSections(sections: string[]): this;
1019
+ /**
1020
+ * Regroupe les items par section et régénère les séparateurs ENTRE les
1021
+ * groupes. Appelé une seule fois, à show — un menu est one-shot, l'ordre
1022
+ * final n'est connu qu'à l'affichage (comme Obsidian : « Only works when
1023
+ * menu is not shown yet »).
1024
+ *
1025
+ * OPT-IN : sans aucun setSection ni addSections, ne touche à rien — les
1026
+ * menus existants (ordre d'insertion + addSeparator manuel) sont
1027
+ * inchangés. En mode sections, les addSeparator manuels sont retirés :
1028
+ * les deux régimes ne se mélangent pas, ce sont les groupes qui séparent.
1029
+ */
1030
+ private applySections;
1031
+ onHide(callback: () => unknown): this;
1032
+ showAtMouseEvent(evt: MouseEvent): this;
1033
+ showAtPosition(x: number, y: number): this;
1034
+ hide(): this;
1035
+ onunload(): void;
1036
+ /** Ce point est-il chez nous OU chez un de nos descendants ? Récursif. */
1037
+ private isInside;
1038
+ /** @internal — appelé par MenuItem.setSubmenu(). */
1039
+ attachSubmenu(sub: Menu): void;
1040
+ /** @internal — survol d'un item : déploie son sous-menu, replie les autres. */
1041
+ onItemHover(item: MenuItem): void;
1042
+ /** @internal — un clic d'item ferme TOUTE la chaîne, pas juste son niveau. */
1043
+ getRoot(): Menu;
1044
+ }
1045
+ /**
1046
+ * Une entrée de menu. Construite UNIQUEMENT via menu.addItem() — le
1047
+ * constructeur n'est pas exporté du point de vue de l'API.
1048
+ *
1049
+ * Tous les setters rendent `this` : c'est le style builder du §11.7, le même
1050
+ * que le futur `new Setting(el).setName(…).addToggle(…)`.
1051
+ */
1052
+ export declare class MenuItem {
1053
+ dom: HTMLElement;
1054
+ /** Le sous-menu de cet item, s'il en a un (voir setSubmenu). */
1055
+ submenu: Menu | null;
1056
+ /** La section de cet item ('' = sans section). Lu par Menu.applySections. */
1057
+ section: string;
1058
+ private iconEl;
1059
+ private titleEl;
1060
+ private menu;
1061
+ private disabled;
1062
+ constructor(menu: Menu);
1063
+ setTitle(title: string): this;
1064
+ setIcon(icon: string): this;
1065
+ setChecked(checked: boolean): this;
1066
+ setDisabled(disabled: boolean): this;
1067
+ /** Marque une entrée destructive (Supprimer…) — en rouge, comme Obsidian. */
1068
+ setWarning(warning: boolean): this;
1069
+ /**
1070
+ * Place cet item dans une section — le placement devient DÉCLARATIF :
1071
+ * l'item dit à quel groupe il appartient au lieu de dépendre de l'ordre
1072
+ * d'exécution des listeners. Voir Menu.addSections pour l'ordre des
1073
+ * groupes. data-section est posé sur le DOM, comme Obsidian, pour que
1074
+ * l'inspection révèle les ids de sections d'un menu existant.
1075
+ */
1076
+ setSection(section: string): this;
1077
+ onClick(callback: (evt: MouseEvent) => unknown): this;
1078
+ /**
1079
+ * Attache un sous-menu à cet item et LE REND — on le peuple ensuite avec la
1080
+ * même API que n'importe quel menu :
1081
+ *
1082
+ * menu.addItem((i) => {
1083
+ * i.setTitle('Trier par');
1084
+ * const sub = i.setSubmenu();
1085
+ * sub.addItem((s) => s.setTitle('Nom').onClick(…));
1086
+ * });
1087
+ *
1088
+ * Nesting illimité gratuit : un sous-menu est un Menu complet, donc ses
1089
+ * items peuvent avoir des sous-menus — et la cascade de fermeture suit,
1090
+ * parce que chaque niveau est l'enfant Component du précédent.
1091
+ */
1092
+ setSubmenu(): Menu;
1093
+ }
1094
+ /**
1095
+ * §12.3 — la modale : une boîte par-dessus tout, avec SA portée clavier.
1096
+ *
1097
+ * ★ POURQUOI extends Component : tout ce qu'une modale acquiert — son DOM dans
1098
+ * le body, sa portée dans la pile du Keymap, ses écouteurs — est enregistré
1099
+ * via register*() et rejoué à close(). Une modale ne peut pas fuir, et elle
1100
+ * peut se ROUVRIR : load()/unload() sont symétriques, la palette de commandes
1101
+ * réutilise la même instance à chaque Mod+P.
1102
+ *
1103
+ * ★ LA PORTÉE est le point important. À l'ouverture, `keymap.pushScope(scope)` :
1104
+ * tant que la modale est là, sa portée est la SEULE interrogée — les
1105
+ * raccourcis globaux sont masqués, Échap ferme, et une sous-classe y pose ses
1106
+ * propres liaisons (flèches, Entrée) sans risquer de les voir interceptées
1107
+ * par l'éditeur en dessous. Le dépilement est enregistré comme un cleanup,
1108
+ * donc symétrique par construction.
1109
+ *
1110
+ * Les classes CSS sont celles d'Obsidian — c'est l'API publique des thèmes :
1111
+ *
1112
+ * .modal-container.mod-dim
1113
+ * .modal-bg
1114
+ * .modal
1115
+ * .modal-close-button
1116
+ * .modal-title
1117
+ * .modal-content
1118
+ */
1119
+ export declare class Modal extends Component {
1120
+ app: App;
1121
+ /** Chaînée à la racine (Scope.pourVue) : Échap ferme la modale et est
1122
+ * consommé ici, mais les raccourcis globaux non gérés remontent — une
1123
+ * modale ne gèle pas Mod+P (A3). */
1124
+ scope: Scope;
1125
+ /** La racine, plein écran. Attachée au body à open(), retirée à close(). */
1126
+ containerEl: HTMLElement;
1127
+ /** Le voile derrière la boîte. Cliquer dessus ferme. */
1128
+ bgEl: HTMLElement;
1129
+ /** La boîte elle-même. */
1130
+ modalEl: HTMLElement;
1131
+ titleEl: HTMLElement;
1132
+ /** Le seul endroit où une sous-classe écrit. */
1133
+ contentEl: HTMLElement;
1134
+ constructor(app: App);
1135
+ open(): void;
1136
+ close(): void;
1137
+ /** Appelé à chaque ouverture, une fois le DOM attaché et la portée empilée. */
1138
+ onOpen(): void;
1139
+ /** Appelé à chaque fermeture, AVANT le démontage. */
1140
+ onClose(): void;
1141
+ setTitle(title: string): this;
1142
+ setContent(content: string | Node): this;
1143
+ }
1144
+ export declare class Notice {
1145
+ /** L'élément DOM de la notification — exposé comme chez Obsidian. */
1146
+ readonly noticeEl: HTMLElement;
1147
+ private masquee;
1148
+ private timer;
1149
+ /**
1150
+ * @param message Le texte (ou un fragment DOM) à afficher.
1151
+ * @param duration Millisecondes avant disparition ; `0` = persistante (elle
1152
+ * ne part qu'au clic). Défaut : 5 s, comme Obsidian.
1153
+ */
1154
+ constructor(message: string | DocumentFragment, duration?: number);
1155
+ /** Remplace le contenu affiché. Rend `this`, pour chaîner (parité Obsidian). */
1156
+ setMessage(message: string | DocumentFragment): this;
1157
+ /** Ferme la notification (idempotent) : transition de sortie, puis retrait. */
1158
+ hide(): void;
1159
+ }
1160
+ /**
1161
+ * §12.5 — une PORTÉE clavier : un ensemble de liaisons, empilable.
1162
+ *
1163
+ * Le problème qu'elle résout : quand une modale est ouverte, Échap doit la
1164
+ * fermer et surtout PAS déclencher le « Échap » de la vue en dessous ; les
1165
+ * flèches doivent naviguer dans la liste et pas dans le texte. Sans pile de
1166
+ * portées, chaque composant pose son `keydown` sur `window` et le premier
1167
+ * arrivé gagne — c'est-à-dire le hasard de l'ordre de montage.
1168
+ *
1169
+ * ★ LA RÈGLE DE PROPAGATION, en une phrase : une liaison EXPLICITE arrête la
1170
+ * chaîne même si elle ne consomme rien ; une liaison ATTRAPE-TOUT
1171
+ * (`key === null && modifiers === null`) qui ne consomme rien laisse
1172
+ * continuer.
1173
+ *
1174
+ * C'est ce qui permet au HotkeyManager de poser UN SEUL handler attrape-tout
1175
+ * sur la portée racine : quand aucun raccourci ne correspond, il rend
1176
+ * `undefined` et la chaîne se poursuit normalement.
1177
+ */
1178
+ export declare class Scope {
1179
+ keys: KeymapEventHandler[];
1180
+ parent: Scope | null;
1181
+ constructor(parent?: Scope | null);
1182
+ /**
1183
+ * La portée d'une VUE ou d'une MODALE : chaînée à la racine.
1184
+ *
1185
+ * Parent = `app.scope`. La vue/modale a la priorité (ses liaisons sont vues
1186
+ * d'abord), mais tout ce qu'elle ne reconnaît PAS remonte à la racine —
1187
+ * Mod+P, Mod+S et les autres globaux restent joignables. C'est le
1188
+ * `new Scope(app.scope)` d'Obsidian.
1189
+ */
1190
+ static pourVue(app: App): Scope;
1191
+ /**
1192
+ * La portée d'un MENU one-shot : parent nul, masquage ASSUMÉ.
1193
+ *
1194
+ * Échap ferme et CONSOMME (ne remonte pas à CodeMirror) ; tout le reste est
1195
+ * bloqué tant que le menu est ouvert — on ne veut pas déclencher un raccourci
1196
+ * global derrière un menu contextuel.
1197
+ *
1198
+ * ★ À NE PAS employer pour un élément PERSISTANT (barre d'outils) : le
1199
+ * masquage y gèlerait les globaux dans toute l'app (le bug A3). Un tel
1200
+ * élément prend `Scope.pourVue`.
1201
+ */
1202
+ static echap(fermer: () => void): Scope;
1203
+ /**
1204
+ * @param modifiers `null` = n'importe quels modificateurs.
1205
+ * @param key `null` = n'importe quelle touche. Les deux à `null` = attrape-tout.
1206
+ * @returns le handler, à garder pour `unregister`.
1207
+ */
1208
+ register(modifiers: Modifier[] | null, key: string | null, func: KeymapEventListener): KeymapEventHandler;
1209
+ unregister(handler: KeymapEventHandler): void;
1210
+ /** Le dispatch. Rend `false` pour consommer la frappe, `undefined` pour laisser passer. */
1211
+ handleKey(evt: KeyboardEvent, ctx: KeymapContext): boolean | void;
1212
+ }
1213
+ /**
1214
+ * Nomenclature :
1215
+ *
1216
+ * "dossier/note.md" → name "note.md" basename "note" extension "md"
1217
+ * "README" → name "README" basename "README" extension ""
1218
+ * ".gitignore" → name ".gitignore" basename ".gitignore" extension ""
1219
+ *
1220
+ */
1221
+ export declare class TFile extends TAbstractFile {
1222
+ stat: FileStats;
1223
+ constructor(vault: Vault, path: string, stat: FileStats, parent?: TFolder | null);
1224
+ get extension(): string;
1225
+ get basename(): string;
1226
+ }
1227
+ export declare class TFolder extends TAbstractFile {
1228
+ children: TAbstractFile[];
1229
+ isRoot(): boolean;
1230
+ }
1231
+ export declare class Toolbar extends Component {
1232
+ /** La racine, créée tout de suite mais attachée au parent seulement à show. */
1233
+ dom: HTMLElement;
1234
+ /**
1235
+ * La poignée — toujours au BOUT de la barre (à droite en horizontal, en bas
1236
+ * en vertical). On la traîne pour déplacer la barre, on la double-clique pour
1237
+ * la faire basculer.
1238
+ */
1239
+ handleEl: HTMLElement;
1240
+ /**
1241
+ * La section en cours de remplissage. Une section est un GROUPE d'items entre
1242
+ * deux séparateurs — c'est elle qui porte la mise en colonnes.
1243
+ *
1244
+ * ★ POURQUOI le séparateur définit la section, plutôt qu'un addSection()
1245
+ * explicite : le séparateur EST déjà la frontière, visuellement. Deux
1246
+ * notions de découpage — l'une pour l'œil, l'autre pour la grille —
1247
+ * finiraient par diverger, et il faudrait alors expliquer laquelle gagne.
1248
+ */
1249
+ private sectionEl;
1250
+ private items;
1251
+ private hideCallbacks;
1252
+ private shown;
1253
+ private orientation;
1254
+ private columns;
1255
+ /**
1256
+ * L'utilisateur a-t-il posé la barre lui-même ? Une fois vrai, l'appelant
1257
+ * doit cesser de la recentrer : sa position devient un choix, pas un défaut.
1258
+ */
1259
+ private userPlaced;
1260
+ /** L'écart entre le pointeur et le coin de la barre, figé au début du drag. */
1261
+ private grab;
1262
+ /**
1263
+ * Le conteneur qui héberge la barre ET la borne. Passer la `contentEl` d'une
1264
+ * vue y enferme la toolbar ; le défaut `document.body` la laisse libre dans
1265
+ * la fenêtre, comme le menu.
1266
+ */
1267
+ private parentEl;
1268
+ constructor(parentEl?: HTMLElement);
1269
+ /** Le builder central. Rend `this` — tout s'enchaîne. */
1270
+ addItem(cb: (item: ToolbarItem) => unknown): this;
1271
+ /** Ferme la section courante et en ouvre une neuve. */
1272
+ addSeparator(): this;
1273
+ /**
1274
+ * Le nombre de colonnes sur lequel CHAQUE section range ses items — et sur
1275
+ * lequel les rangées d'options (setOptions) rangent les leurs.
1276
+ *
1277
+ * ★ Ne mord qu'en mode VERTICAL. À l'horizontale, une section est une ligne :
1278
+ * parler de colonnes y voudrait dire « combien d'items avant de passer à la
1279
+ * ligne », c'est-à-dire l'inverse. Plutôt que d'inventer une seconde
1280
+ * sémantique pour le même mot, l'horizontale ignore le réglage.
1281
+ *
1282
+ * Défaut : 2 — assez pour qu'une palette de six couleurs tienne en trois
1283
+ * rangs. `setColumns(1)` donne la barre la plus fine possible, au prix de la
1284
+ * hauteur.
1285
+ */
1286
+ setColumns(columns: number): this;
1287
+ getColumns(): number;
1288
+ onHide(callback: () => unknown): this;
1289
+ /**
1290
+ * Bascule le sens de la barre. Le clamp est rejoué dans la foulée : une barre
1291
+ * horizontale posée en bas de l'écran dépasserait en passant à la verticale.
1292
+ */
1293
+ setOrientation(orientation: ToolbarOrientation): this;
1294
+ toggleOrientation(): this;
1295
+ getOrientation(): ToolbarOrientation;
1296
+ /** L'utilisateur a-t-il déplacé la barre à la main ? (voir userPlaced) */
1297
+ isUserPlaced(): boolean;
1298
+ showAtPosition(x: number, y: number): this;
1299
+ /**
1300
+ * Repositionne une toolbar déjà montrée — appelé quand le pane qu'elle
1301
+ * surplombe bouge (fenêtre redimensionnée, split). Sans effet si la toolbar
1302
+ * n'est pas affichée.
1303
+ *
1304
+ * `x`/`y` sont en coordonnées CLIENT : celles de `getBoundingClientRect` et
1305
+ * des événements pointeur, donc celles que l'appelant a déjà sous la main.
1306
+ * La conversion vers le repère du parent vit ICI, en un seul endroit — c'est
1307
+ * ce qui permet au drag comme au placement par défaut de l'ignorer.
1308
+ */
1309
+ moveTo(x: number, y: number): this;
1310
+ hide(): this;
1311
+ onunload(): void;
1312
+ /** Ouvre une section neuve, insérée avant la poignée si elle existe déjà. */
1313
+ private nouvelleSection;
1314
+ /**
1315
+ * Le déplacement à la poignée, et la bascule au double-clic.
1316
+ *
1317
+ * ★ POURQUOI une poignée et pas la barre entière : chaque pixel de la barre
1318
+ * est un bouton. Attraper le fond pour déplacer marcherait tant qu'on vise
1319
+ * entre deux items, et raterait le reste du temps. La poignée est la seule
1320
+ * zone dont le geste ne peut pas être confondu avec un clic d'outil.
1321
+ *
1322
+ * Le seuil de 4px est celui de DocWidget : sans lui, un double-clic sur la
1323
+ * poignée (donc deux pointerdown suivis de micro-mouvements) déplacerait la
1324
+ * barre de deux pixels avant de la faire pivoter.
1325
+ *
1326
+ * pointercancel et lostpointercapture sont traités comme pointerup : perdre
1327
+ * le focus fenêtre (alt-tab, capture d'écran) ne délivre jamais de pointerup,
1328
+ * et la barre resterait collée au curseur — le piège documenté par
1329
+ * DragManager pour le drag d'onglets.
1330
+ */
1331
+ private wireDrag;
1332
+ /**
1333
+ * @internal — un item d'un groupe vient d'être activé : on désactive ses
1334
+ * pairs. C'est la toolbar qui arbitre, pas les items entre eux : un item ne
1335
+ * connaît que son groupe, jamais ses voisins.
1336
+ */
1337
+ activateInGroup(item: ToolbarItem): void;
1338
+ }
1339
+ /**
1340
+ * Un bouton de barre d'outils. Construit UNIQUEMENT via toolbar.addItem().
1341
+ *
1342
+ * Tous les setters rendent `this` : le style builder du §11.7, celui de MenuItem.
1343
+ */
1344
+ export declare class ToolbarItem {
1345
+ dom: HTMLElement;
1346
+ /** Le groupe d'exclusivité ('' = aucun). Lu par Toolbar.activateInGroup. */
1347
+ group: string;
1348
+ private iconEl;
1349
+ private labelEl;
1350
+ private optionsEl;
1351
+ private toolbar;
1352
+ private disabled;
1353
+ private active;
1354
+ constructor(toolbar: Toolbar);
1355
+ setIcon(icon: string): this;
1356
+ setLabel(label: string): this;
1357
+ /** L'infobulle — et le seul a11y du projet aujourd'hui (cf. ItemView.addAction). */
1358
+ setTooltip(tooltip: string): this;
1359
+ /**
1360
+ * Inscrit cet item dans un groupe exclusif : l'activer désactive ses pairs.
1361
+ * C'est ce qui fait des boutons crayon / surligneur / gomme un vrai choix
1362
+ * d'outil plutôt que trois bascules indépendantes.
1363
+ */
1364
+ setGroup(group: string): this;
1365
+ setActive(active: boolean): this;
1366
+ isActive(): boolean;
1367
+ setDisabled(disabled: boolean): this;
1368
+ onClick(callback: (evt: MouseEvent) => unknown): this;
1369
+ /**
1370
+ * Transforme cet item en RANGÉE D'OPTIONS — la couleur, l'épaisseur, tout
1371
+ * choix à faible cardinalité qui mérite d'être visible en permanence plutôt
1372
+ * que caché derrière un sous-menu.
1373
+ *
1374
+ * ★ POURQUOI ici et pas dans le plugin : sans ça, chaque consommateur
1375
+ * réinventerait sa rangée de pastilles et la toolbar ne serait qu'un
1376
+ * conteneur. Une option est un couple (valeur, aspect) ; l'aspect est une
1377
+ * icône, une pastille colorée ou un disque dimensionné — assez pour couvrir
1378
+ * couleur ET épaisseur sans rien coder de spécifique au dessin.
1379
+ */
1380
+ setOptions(options: readonly ToolbarOption[], onPick: (value: string) => unknown): this;
1381
+ /** Marque l'option courante d'une rangée posée par setOptions(). */
1382
+ setValue(value: string): this;
1383
+ }
1384
+ export declare class Vault extends Events {
1385
+ adapter: DataAdapter;
1386
+ configDir: string;
1387
+ root: TFolder;
1388
+ fileMap: Map<string, TAbstractFile>;
1389
+ private readCache;
1390
+ private processQueue;
1391
+ constructor(adapter: DataAdapter);
1392
+ load(): Promise<void>;
1393
+ /**
1394
+ * Un niveau de l'arbre, puis la descente.
1395
+ *
1396
+ * ★ Le commentaire qui ouvrait cette fonction disait « elle demande
1397
+ * beaucoup de transactions dans l'IPC Bus si on ne fait pas gaffe ».
1398
+ * Ce n'est plus vrai : l'adapter lit le disque dans CE processus (modèle
1399
+ * Obsidian, cf. FileSystemAdapter.ts). `list()` et `stat()` sont des
1400
+ * appels de fonction, pas des allers-retours — et c'est exactement pour
1401
+ * ça que `listWithStats`, notre seul écart avec l'API d'Obsidian, a pu
1402
+ * être supprimé.
1403
+ */
1404
+ private buildFolder;
1405
+ private onChange;
1406
+ getName(): string;
1407
+ getRoot(): TFolder;
1408
+ getAbstractFileByPath(path: string): TAbstractFile | null;
1409
+ /** Rend `null` si le chemin désigne un dossier — pas seulement s'il est absent. */
1410
+ getFileByPath(path: string): TFile | null;
1411
+ getFolderByPath(path: string): TFolder | null;
1412
+ getAllLoadedFiles(): TAbstractFile[];
1413
+ getFiles(): TFile[];
1414
+ getMarkdownFiles(): TFile[];
1415
+ getAllFolders(includeRoot?: boolean): TFolder[];
1416
+ read(file: TFile): Promise<string>;
1417
+ cachedRead(file: TFile): Promise<string>;
1418
+ readBinary(file: TFile): Promise<ArrayBuffer>;
1419
+ /**
1420
+ * L'adresse d'un fichier pour la plateforme (§6.1). À préférer à readBinary
1421
+ * partout où c'est la PLATEFORME qui consomme les octets — image, audio,
1422
+ * vidéo, PDF : le renderer ne les voit alors jamais passer.
1423
+ *
1424
+ * Le Vault est le seul à tenir le TFile, donc le seul à connaître le mtime :
1425
+ * c'est lui qui fournit le cache-buster. L'adapter, lui, décide de ce qu'il
1426
+ * en fait — d'où le paramètre plutôt qu'une concaténation ici.
1427
+ */
1428
+ getResourcePath(file: TFile): string;
1429
+ private refreshStat;
1430
+ private assertCreatable;
1431
+ create(path: string, data: string, options?: DataWriteOptions): Promise<TFile>;
1432
+ createBinary(path: string, data: ArrayBuffer, options?: DataWriteOptions): Promise<TFile>;
1433
+ createFolder(path: string): Promise<TFolder>;
1434
+ modify(file: TFile, data: string, options?: DataWriteOptions): Promise<void>;
1435
+ modifyBinary(file: TFile, data: ArrayBuffer, options?: DataWriteOptions): Promise<void>;
1436
+ append(file: TFile, data: string, options?: DataWriteOptions): Promise<void>;
1437
+ appendBinary(file: TFile, data: ArrayBuffer, options?: DataWriteOptions): Promise<void>;
1438
+ process(file: TFile, fn: (data: string) => string, options?: DataWriteOptions): Promise<string>;
1439
+ delete(file: TAbstractFile, force?: boolean): Promise<void>;
1440
+ /**
1441
+ * `system: true` → corbeille de l'OS ; `false` → .trash/ du coffre.
1442
+ * Repli sur la corbeille locale si l'OS refuse (volume réseau, corbeille
1443
+ * désactivée) : mieux vaut un fichier récupérable dans .trash/ qu'un échec.
1444
+ */
1445
+ trash(file: TAbstractFile, system: boolean): Promise<void>;
1446
+ private detachAndAnnounce;
1447
+ rename(file: TAbstractFile, newPath: string): Promise<void>;
1448
+ copy<T extends TAbstractFile>(file: T, newPath: string): Promise<T>;
1449
+ private parentPathOf;
1450
+ protected addToTree(file: TAbstractFile): void;
1451
+ protected removeFromTree(file: TAbstractFile): void;
1452
+ protected moveInTree(file: TAbstractFile, newPath: string): void;
1453
+ on(name: "create", callback: (file: TAbstractFile) => unknown, ctx?: unknown): EventRef;
1454
+ on(name: "modify", callback: (file: TAbstractFile) => unknown, ctx?: unknown): EventRef;
1455
+ on(name: "delete", callback: (file: TAbstractFile) => unknown, ctx?: unknown): EventRef;
1456
+ on(name: "rename", callback: (file: TAbstractFile, oldPath: string) => unknown, ctx?: unknown): EventRef;
1457
+ /**
1458
+ * §6.4 — le disque a bougé, AVANT toute interprétation.
1459
+ *
1460
+ * Les quatre événements ci-dessus parlent de l'arbre des fichiers : ils
1461
+ * portent un TFile et n'existent que pour ce que le coffre connaît.
1462
+ * Celui-ci parle du DISQUE : un chemin brut, y compris dans .fragment/, que
1463
+ * l'arbre ne contiendra jamais.
1464
+ *
1465
+ * C'est ce qui permet au HotkeyManager de voir hotkeys.json changer.
1466
+ */
1467
+ on(name: "raw", callback: (path: string) => unknown, ctx?: unknown): EventRef;
1468
+ static recurseChildren(root: TFolder, cb: (file: TAbstractFile) => void): void;
1469
+ }
1470
+ export declare class WidgetLayer {
1471
+ private editor;
1472
+ private overlays;
1473
+ private widgets;
1474
+ private offChange;
1475
+ private offGeometry;
1476
+ constructor(editor: DocumentSurface | null, overlays: OverlayHost);
1477
+ addWidget(el: HTMLElement, anchor: WidgetAnchor): WidgetHandle;
1478
+ /** Point client → coords viewport (ou null hors du pane) — pour le drop-to-spawn. */
1479
+ clientToViewport(x: number, y: number): {
1480
+ x: number;
1481
+ y: number;
1482
+ } | null;
1483
+ /**
1484
+ * L'ancre DOCUMENT qui laisse l'objet EXACTEMENT où il est à l'écran.
1485
+ *
1486
+ * Le piège que ça referme : `posAtCoords` rend l'offset du caractère le plus
1487
+ * PROCHE, et `coordsAtPos` le coin de CE glyphe — s'ancrer avec dx/dy = 0
1488
+ * aimante donc sur le glyphe et fait sauter l'objet d'une hauteur de ligne.
1489
+ * On garde l'écart, mesuré dans le repère document des deux côtés.
1490
+ */
1491
+ documentAnchorAt(clientX: number, clientY: number): WidgetAnchor | null;
1492
+ /** Le symétrique : l'ancre VIEWPORT à la même place à l'écran. */
1493
+ viewportAnchorAt(clientX: number, clientY: number): WidgetAnchor | null;
1494
+ /**
1495
+ * L'ancre de MARGE qui garde l'objet au même NIVEAU vertical qu'à l'écran.
1496
+ *
1497
+ * Une ancre de marge ne retient qu'un `y` : le `x` et la largeur sont imposés
1498
+ * par la bande à chaque placement, puisqu'ils dépendent du pane. On sonde donc
1499
+ * le texte au MILIEU de la colonne — pas sous le widget, qui est justement à
1500
+ * côté du texte, voire hors de la zone rendue.
1501
+ */
1502
+ gutterAnchorAt(clientY: number, side: GutterSide): WidgetAnchor | null;
1503
+ /** Y a-t-il la place d'afficher un widget dans cette marge ? */
1504
+ gutterFits(side: GutterSide): boolean;
1505
+ /** L'ancrage document est-il possible ici ? (Faux sans éditeur : PDF, image…) */
1506
+ canAnchorToDocument(): boolean;
1507
+ destroy(): void;
1508
+ /** Change l'ancre d'un widget monté ; bascule de plan si le plan change. */
1509
+ private reanchor;
1510
+ /**
1511
+ * Rejoue les ancres document à travers un changement de document. Sans ça,
1512
+ * l'offset stocké désigne un autre caractère dès la première frappe en amont
1513
+ * et le widget dérive.
1514
+ *
1515
+ * Texte ancré supprimé : on DÉTACHE en viewport, à la place qu'occupe le widget
1516
+ * à l'écran. Il ne disparaît pas et ne se téléporte pas — l'utilisateur voit ce
1517
+ * qu'il a détruit. (Fermer le widget serait l'autre lecture défendable ; c'est
1518
+ * une décision produit, isolée à ces trois lignes.) Si le widget est hors du
1519
+ * pane, on ne peut pas le détacher « sur place » : il garde une ancre document,
1520
+ * au point d'effondrement — toujours valide, jamais hors bornes.
1521
+ */
1522
+ private remapAnchors;
1523
+ private position;
1524
+ /**
1525
+ * Replace les widgets document. Utile uniquement quand les COORDS DU TEXTE
1526
+ * changent (édition, reflux) — pas quand la mise en page se contente de
1527
+ * déplacer l'éditeur : le plan document vit dans le repère de `coordsAtPos`,
1528
+ * donc il glisse avec lui et les coords restent valables telles quelles.
1529
+ */
1530
+ /**
1531
+ * Un widget de marge : le NIVEAU vient du texte, la COLONNE vient du pane.
1532
+ *
1533
+ * C'est tout le contrat de ce mode — et c'est pour ça que la largeur est
1534
+ * réécrite ici à chaque placement plutôt que laissée au widget : elle est une
1535
+ * conséquence de l'ancre, pas un réglage. Marge trop étroite (fenêtre
1536
+ * rétrécie, colonne qui remplit le pane) → on masque, plutôt que de laisser
1537
+ * un widget déborder sur le texte ou sortir du pane.
1538
+ */
1539
+ private positionGutter;
1540
+ private repositionTexte;
1541
+ }
1542
+ /**
1543
+ *
1544
+ * WorkspaceItem (extends Events) un nœud de l'arbre, porte un élément DOM
1545
+ * ├─ WorkspaceParent peut avoir des enfants
1546
+ * │ ├─ WorkspaceSplit découpe horizontale / verticale
1547
+ * │ │ ├─ WorkspaceRoot la zone centrale
1548
+ * │ │ └─ WorkspaceSidedock les panneaux latéraux, repliables
1549
+ * │ └─ WorkspaceTabs empile des feuilles, une seule visible
1550
+ * └─ WorkspaceLeaf ★ héberge exactement une View
1551
+ */
1552
+ export declare class Workspace extends Events {
1553
+ app: App;
1554
+ /** La zone centrale. */
1555
+ rootSplit: WorkspaceRoot;
1556
+ /** Les panneaux latéraux — c'est ici qu'ira l'explorateur de fichiers. */
1557
+ leftSplit: WorkspaceSidedock;
1558
+ rightSplit: WorkspaceSidedock;
1559
+ /** §8.1 — la barre d'icônes verticale. HORS de l'arbre des splits : elle
1560
+ * le borde. Son contenu vient d'app.ribbonItems (addRibbonIcon, §11.3). */
1561
+ leftRibbon: WorkspaceRibbon;
1562
+ /** L'élément à insérer dans le DOM de l'application. */
1563
+ containerEl: HTMLElement;
1564
+ activeLeaf: WorkspaceLeaf | null;
1565
+ /** La pile qui affiche actuellement le trait d'insertion du drag, s'il y en
1566
+ * a une — pour l'éteindre quand le curseur change de pile ou sort. */
1567
+ private dropPile;
1568
+ /**
1569
+ * §6.3 — le layout n'est jamais écrit dans un chemin chaud. Chaque
1570
+ * addChild, removeChild, resize ou changement d'onglet le « demande ».
1571
+ */
1572
+ requestSaveLayout: () => void;
1573
+ /**
1574
+ * §16.1 — `false` jusqu'à la fin de loadLayout(). Deux rôles distincts :
1575
+ * l'invariant offert aux plugins (onLayoutReady), et le garde qui interdit
1576
+ * d'écrire un arbre en cours de reconstruction (saveLayout).
1577
+ */
1578
+ layoutReady: boolean;
1579
+ private layoutReadyCallbacks;
1580
+ /**
1581
+ * Le type de vue posé dans le panneau gauche quand aucun layout n'est
1582
+ * enregistré. Vide par défaut : le cœur ne connaît AUCUNE vue par son nom
1583
+ * (l'invariant de seedCore.ts — « compte les occurrences de 'markdown' dans
1584
+ * core/ : zéro »). C'est la couche d'assemblage qui le renseigne.
1585
+ */
1586
+ defaultSideViewType: string;
1587
+ constructor(app: App);
1588
+ iterateAllLeaves(cb: (leaf: WorkspaceLeaf) => void): void;
1589
+ /**
1590
+ * ★ `leaf.getViewType()` et NON `leaf.view.getViewType()` — la discipline
1591
+ * du §8.4. La vue peut être une coquille différée ou un placeholder ;
1592
+ * c'est la FEUILLE qui sait ce qu'elle affiche. Sans ça, un onglet
1593
+ * markdown restauré mais pas encore ouvert serait invisible à tout plugin
1594
+ * qui cherche ses vues.
1595
+ */
1596
+ getLeavesOfType(type: string): WorkspaceLeaf[];
1597
+ getLeavesOfFile(file: TFile): WorkspaceLeaf[];
1598
+ /**
1599
+ * Rend une feuille utilisable.
1600
+ *
1601
+ * `false` → réutilise la feuille active si elle n'est pas épinglée.
1602
+ * `'tab'` → nouvel onglet dans la même pile.
1603
+ * `'split'` → nouvelle division dans le rootSplit.
1604
+ */
1605
+ getLeaf(newLeaf?: boolean | PaneType): WorkspaceLeaf;
1606
+ /** Une feuille dans le panneau latéral — pour l'explorateur, la recherche… */
1607
+ getLeftLeaf(): WorkspaceLeaf;
1608
+ /**
1609
+ * Crée une feuille dans un parent donné — le nom et la signature de l'API
1610
+ * Obsidian (chez eux `index` est obligatoire ; notre défaut -1 = « à la
1611
+ * fin », la convention d'addChild).
1612
+ * Public : WorkspaceTabs (bouton « + ») en a besoin, et importer
1613
+ * WorkspaceLeaf en valeur là-bas créerait un cycle d'imports avec
1614
+ * WorkspaceLeaf → WorkspaceTabs (le instanceof de setViewState).
1615
+ */
1616
+ createLeafInParent(parent: WorkspaceParent, index?: number): WorkspaceLeaf;
1617
+ /**
1618
+ * §8.1 — une nouvelle feuille à CÔTÉ de `leaf`, dans un split.
1619
+ *
1620
+ * `'vertical'` = à droite (une colonne de plus), `'horizontal'` = en
1621
+ * dessous — le vocabulaire d'Obsidian, où le split VERTICAL est celui dont
1622
+ * la ligne de séparation est verticale. Pas l'intuition CSS, mais c'est la
1623
+ * convention que les thèmes et les plugins connaissent.
1624
+ *
1625
+ * ★ Ne fait que déléguer à `WorkspaceTabs.splitLeaf`, la mécanique déjà
1626
+ * éprouvée par le drag d'onglet. Même axe que le parent → pile sœur ;
1627
+ * axe perpendiculaire → wrapper. Rien n'est réimplémenté ici.
1628
+ */
1629
+ createLeafBySplit(leaf: WorkspaceLeaf, direction?: SplitDirection): WorkspaceLeaf | null;
1630
+ setActiveLeaf(leaf: WorkspaceLeaf | null): void;
1631
+ getActiveViewOfType<T>(ctor: abstract new (...args: never[]) => T): T | null;
1632
+ /**
1633
+ * §11.5 — l'éditeur de TEXTE de la vue active, s'il y en a un.
1634
+ *
1635
+ * C'est ce que lit le désucrage d'`editorCallback` (CommandManager) : sans
1636
+ * ce getter, une commande d'éditeur n'a aucun moyen d'atteindre sa cible.
1637
+ *
1638
+ * ★ DEUX gardes structurels, et aucun `instanceof MarkdownView` :
1639
+ *
1640
+ * 1. `porteUneSurface` — toutes les vues n'ont pas de surface de document
1641
+ * (l'explorateur de fichiers n'en a pas). La question porte sur ce qui
1642
+ * est là, pas sur un drapeau à tenir synchrone.
1643
+ * 2. `isEditable` — toutes les surfaces ne s'écrivent pas : le PDF a du
1644
+ * texte adressable (hasText) mais `editor:toggle-bold` n'a rien à y
1645
+ * faire. C'est l'étage du dessus qu'on exige.
1646
+ *
1647
+ * Le prix de l'`instanceof` aurait été un import de `MarkdownView` dans
1648
+ * `core/`, ce que l'invariant « le cœur ne connaît aucune vue par son
1649
+ * nom » interdit. Le bénéfice est le même que celui des gardes : une
1650
+ * future vue textuelle héritera de toutes les commandes d'éditeur sans
1651
+ * qu'une ligne de `core/` change.
1652
+ */
1653
+ get activeEditor(): Editor | null;
1654
+ /**
1655
+ * Pose mod-top-right-space sur LE groupe d'onglets qui touche le coin
1656
+ * haut-droit de la fenêtre : son header réserve la place des boutons
1657
+ * minimize/maximize/close (WindowControls, en overlay fixe). Les boutons
1658
+ * ne bougent jamais — c'est la classe qui migre avec le layout.
1659
+ *
1660
+ * Recalculé à chaque mutation de l'arbre (addChild/removeChild) et au
1661
+ * pli/dépli des sidedocks : la sidebar droite DÉPLIÉE touche le coin —
1662
+ * c'est elle qui s'écarte, pas le groupe haut-droit du root.
1663
+ */
1664
+ updateFrameSpacing(): void;
1665
+ /** Descend vers le coin haut-droit : DERNIER enfant d'un split en ligne
1666
+ * (mod-vertical → flex row), PREMIER d'un split en colonne. */
1667
+ private findTopRightTabs;
1668
+ /**
1669
+ * La pile dont le rectangle contient (x, y) — hit-test géométrique sur
1670
+ * l'arbre, dans l'esprit de findTopRightTabs. `null` hors de toute pile.
1671
+ * Le walk ne considère QUE les nœuds du workspace : le fantôme de drag
1672
+ * (sur document.body) et les overlays n'existent pas pour lui.
1673
+ */
1674
+ getTabsAtPoint(x: number, y: number): WorkspaceTabs | null;
1675
+ /** Met à jour le trait d'insertion : la pile sous (x, y) l'affiche, l'ancienne
1676
+ * l'éteint. Appelé à chaque pointermove d'un drag de feuille. */
1677
+ showDropIndicatorAt(x: number, y: number): void;
1678
+ /** Éteint le trait, où qu'il soit — fin du drag. */
1679
+ clearDropIndicator(): void;
1680
+ /**
1681
+ * Une pile vient de perdre son dernier onglet. Si elle est dans le root et
1682
+ * qu'il reste d'AUTRES panes, on la ferme ; sinon (dernier pane du root, ou
1683
+ * pile hors root) on rouvre un onglet vide. Le sidedock (« file bar ») ne
1684
+ * compte pas : countTabs ne balaie que rootSplit.
1685
+ */
1686
+ onPileEmptied(pile: WorkspaceTabs): void;
1687
+ /** Nombre de piles (WorkspaceTabs) dans un sous-arbre. */
1688
+ private countTabs;
1689
+ /**
1690
+ * Redimensionne un sidedock à la souris : on ajuste sa LARGEUR (--sidebar-
1691
+ * width), pas le flex-grow — un sidedock a une largeur propre, le centre
1692
+ * absorbe. La transition de largeur est coupée le temps du drag (classe
1693
+ * is-resizing) pour coller au curseur.
1694
+ */
1695
+ private attachSidebarResize;
1696
+ /** @internal — appelé par WorkspaceLeaf.detach(). */
1697
+ onLeafDetached(leaf: WorkspaceLeaf): void;
1698
+ /**
1699
+ * §8.5 — le chemin « clic sur un lien », entièrement public.
1700
+ * Aucune connaissance du markdown ici : on résout un chemin, on choisit une
1701
+ * feuille, on lui demande d'ouvrir. C'est le registre qui décide du reste.
1702
+ */
1703
+ openLinkText(linktext: string, sourcePath: string, newLeaf?: boolean | PaneType): Promise<void>;
1704
+ private rebuildPlaceholders;
1705
+ /** Le chemin du layout dans le coffre — relocalisable avec configDir. */
1706
+ private get layoutPath();
1707
+ /**
1708
+ * §8.2 — l'arbre entier, réduit à du JSON. Le Workspace ne connaît aucune
1709
+ * forme de nœud : il demande à ses trois racines, chacune sait le reste.
1710
+ */
1711
+ getLayout(): WorkspaceLayout;
1712
+ /**
1713
+ * §6.3 — l'écriture, débouncée par requestSaveLayout.
1714
+ *
1715
+ * ★ Le garde `layoutReady` n'est pas une optimisation, c'est ce qui empêche
1716
+ * la perte du layout. Pendant changeLayout(), CHAQUE addChild appelle
1717
+ * requestSaveLayout() ; sans le garde, le debounce de 2 s pourrait écrire
1718
+ * un arbre à moitié reconstruit par-dessus le fichier qu'on est en train
1719
+ * de lire. C'est aussi lui qui écarte les lectures de géométrie faites
1720
+ * pendant que l'arbre n'est pas encore posé à l'écran (voir
1721
+ * WorkspaceSplit.serialize).
1722
+ */
1723
+ private saveLayout;
1724
+ /**
1725
+ * §16.1 — reconstruit l'arbre depuis le disque, puis déclare le layout prêt.
1726
+ *
1727
+ * Tout ce qui peut mal tourner (fichier absent, JSON tronqué, format d'une
1728
+ * version antérieure) tombe sur le layout par défaut : un coffre qu'on
1729
+ * n'arrive pas à restaurer doit s'ouvrir comme un coffre neuf, jamais
1730
+ * refuser de démarrer.
1731
+ */
1732
+ loadLayout(): Promise<void>;
1733
+ /**
1734
+ * Le layout d'un coffre neuf : l'explorateur à gauche, le premier markdown
1735
+ * au centre. C'est exactement ce que main.tsx câblait en dur avant que la
1736
+ * restauration existe — ça vit ici désormais, à un seul endroit.
1737
+ */
1738
+ private defaultLayout;
1739
+ /**
1740
+ * §8.2 — l'inverse de getLayout(). Vide les trois racines et les remplit.
1741
+ *
1742
+ * Synchrone, et c'est le point : aucune vue n'est construite ici. Chaque
1743
+ * feuille reçoit son état par setDeferredState() et reste une coquille.
1744
+ */
1745
+ changeLayout(layout: WorkspaceLayout): void;
1746
+ /**
1747
+ * Vide un conteneur RACINE et le remplit depuis son nœud sérialisé. Les
1748
+ * racines (rootSplit, leftSplit, rightSplit) sont permanentes : on ne les
1749
+ * recrée jamais, on remplace leur contenu et on réapplique leurs propriétés.
1750
+ */
1751
+ private restoreInto;
1752
+ /** Construit récursivement un sous-arbre. Aucune vue n'est instanciée. */
1753
+ private buildNode;
1754
+ /** Repose les proportions lues au save, puis délègue à reflowGeometry — le
1755
+ * MÊME unique propriétaire de l'invariant que add/removeChild. Conséquence
1756
+ * gratuite : un workspace.json d'avant le fix (somme < 1, ex. 0.95 sur un
1757
+ * pane unique) est renormalisé au démarrage, donc plus de trou. reflowGeometry
1758
+ * lit le flex-grow inline, ça marche bien que le split soit encore détaché. */
1759
+ private applyGeometry;
1760
+ /**
1761
+ * §8.4 — matérialise ce qui est réellement à l'écran dans un sous-arbre :
1762
+ * l'onglet courant de chaque pile, sauf sous un dock replié. Tout le reste
1763
+ * attend d'être regardé.
1764
+ *
1765
+ * Public parce qu'un dock qu'on DÉPLIE doit pouvoir le redemander : après
1766
+ * une restauration où il était replié, ses feuilles sont des coquilles, et
1767
+ * le clic sur le bouton de repli n'est pas un clic sur un onglet — sans ça
1768
+ * la sidebar s'ouvrirait sur du vide.
1769
+ */
1770
+ materializeVisibleIn(racine: WorkspaceItem): Promise<void>;
1771
+ /** L'écran entier, à la fin de la restauration. */
1772
+ private materializeVisible;
1773
+ /**
1774
+ * L'invariant du §16.1 pour les plugins : ne pas toucher au layout avant
1775
+ * qu'il soit prêt. La file existe parce que les plugins sont chargés AVANT
1776
+ * loadLayout() — leur onload() s'abonne, et le rappel part plus tard.
1777
+ */
1778
+ onLayoutReady(cb: () => void): void;
1779
+ on(name: "active-leaf-change", callback: (leaf: WorkspaceLeaf | null) => unknown, ctx?: unknown): EventRef;
1780
+ on(name: "file-open", callback: (file: TFile | null) => unknown, ctx?: unknown): EventRef;
1781
+ on(name: "layout-change", callback: () => unknown, ctx?: unknown): EventRef;
1782
+ on(name: "resize", callback: () => unknown, ctx?: unknown): EventRef;
1783
+ /** §5.3 — le listener MUTE le menu reçu. Frappe éditeur : menu, éditeur, vue. */
1784
+ on(name: "editor-menu", callback: (menu: Menu, editor: unknown, view: View) => unknown, ctx?: unknown): EventRef;
1785
+ /** §5.3 — clic droit sur un fichier (explorateur, onglets…). */
1786
+ on(name: "file-menu", callback: (menu: Menu, file: TAbstractFile, source: string) => unknown, ctx?: unknown): EventRef;
1787
+ }
1788
+ export declare class WorkspaceLeaf extends WorkspaceItem {
1789
+ parent: WorkspaceParent | null;
1790
+ view: View;
1791
+ private requestedState;
1792
+ /**
1793
+ * Le fichier qu'on n'a PAS su ouvrir : aucune vue n'est enregistrée pour son
1794
+ * extension. C'est le pendant FICHIER de `requestedState` — sauf qu'ici il
1795
+ * n'y a aucun type à mémoriser, c'est l'extension qui est irrésolue. On
1796
+ * garde donc le fichier lui-même, et `rebuildView` refait la résolution.
1797
+ */
1798
+ private requestedFile;
1799
+ pinned: boolean;
1800
+ /**
1801
+ * §8.4 — la feuille connaît son état mais n'a pas construit sa vue.
1802
+ * Posé par la restauration du layout, levé par loadIfDeferred().
1803
+ */
1804
+ isDeferred: boolean;
1805
+ /** L'état éphémère mis en attente avec le différé, s'il y en a un. */
1806
+ private deferredEState;
1807
+ private static compteur;
1808
+ /**
1809
+ * L'identité de la feuille dans workspace.json. Stable pour la durée de vie
1810
+ * de l'objet, RE-POSÉE à la restauration pour que `active` continue de
1811
+ * désigner la bonne feuille d'une session à l'autre.
1812
+ */
1813
+ id: string;
1814
+ constructor(workspace: Workspace);
1815
+ /**
1816
+ * §8.4 — pose l'état SANS construire la vue. La feuille sait tout ce qu'il
1817
+ * faut pour se nommer et pour se sérialiser ; elle ne paiera l'instanciation
1818
+ * qu'au premier regard (loadIfDeferred).
1819
+ *
1820
+ * Volontairement synchrone et sans await : c'est tout l'intérêt. Restaurer
1821
+ * quarante onglets ne doit rien coûter d'autre que quarante coquilles.
1822
+ */
1823
+ setDeferredState(viewState: ViewState, eState?: unknown): void;
1824
+ /**
1825
+ * §8.4 — matérialise, si besoin. Idempotent et sûr à appeler partout : sur
1826
+ * une feuille déjà chargée, c'est un no-op.
1827
+ */
1828
+ loadIfDeferred(): Promise<void>;
1829
+ setViewState(viewState: ViewState, eState?: unknown): Promise<void>;
1830
+ /**
1831
+ * §8.4 — le type que la feuille VEUT afficher, pas celui qu'elle affiche.
1832
+ *
1833
+ * ★ C'est cette méthode que le cœur doit interroger, jamais
1834
+ * `leaf.view.getViewType()` : la vue peut être une coquille différée ou un
1835
+ * placeholder, et répondrait alors 'deferred' ou 'empty'. C'est la
1836
+ * discipline que le §8.4 impose — Obsidian l'a découverte en 1.7.2, après
1837
+ * coup, et l'a payée dans tout son écosystème.
1838
+ */
1839
+ getViewType(): string;
1840
+ /**
1841
+ * L'état sérialisable de la feuille.
1842
+ *
1843
+ * ★ Il se lit dans `requestedState`, PAS dans la vue montée. Une feuille en
1844
+ * placeholder (type inconnu, extension sans lecteur) porte une EmptyView :
1845
+ * interroger la vue sérialiserait `empty` et PERDRAIT ce que l'onglet
1846
+ * voulait ouvrir — un plugin désactivé au moment de quitter effacerait
1847
+ * silencieusement ses onglets du layout. Même raison pour une coquille
1848
+ * différée, qui n'a par définition jamais rien chargé.
1849
+ *
1850
+ * Quand la vue EST matérialisée, c'est elle qui a l'état frais (le fichier
1851
+ * a pu changer par navigation depuis la restauration) : on la préfère.
1852
+ */
1853
+ getViewState(): ViewState;
1854
+ /** §8.2 — la feuille dans workspace.json. */
1855
+ serialize(): LeafLayout;
1856
+ /**
1857
+ * Rejoue la demande en cours.
1858
+ *
1859
+ * Appelé par le Workspace quand un registre change : une feuille qui
1860
+ * affichait un placeholder peut alors devenir la vraie vue.
1861
+ */
1862
+ rebuildView(): Promise<void>;
1863
+ /**
1864
+ * La feuille attend-elle un type — ou une extension — pas encore enregistré ?
1865
+ *
1866
+ * ★ `!this.isDeferred` n'est pas une précaution, c'est la condition qui fait
1867
+ * marcher le §8.4. Une feuille différée satisfait les deux autres termes
1868
+ * (coquille + type demandé non vide) ; sans cette exclusion,
1869
+ * Workspace.rebuildPlaceholders() les matérialiserait TOUTES au premier
1870
+ * 'changed' du viewRegistry — c'est-à-dire au chargement du premier
1871
+ * plugin, donc systématiquement, et le report ne servirait jamais à rien.
1872
+ */
1873
+ isPlaceholder(): boolean;
1874
+ /**
1875
+ * Ouvre un fichier — le chemin complet du §8.3 :
1876
+ * extension → type de vue → fabrique → vue montée.
1877
+ *
1878
+ * ★ AUCUN REPLI. Une extension que personne n'a enregistrée donne un
1879
+ * placeholder, exactement comme un type de vue inconnu : « je ne sais pas
1880
+ * ouvrir ça » n'est pas une erreur, c'est l'état normal et transitoire du
1881
+ * §8.3. Le jour où un plugin enregistre 'pdf', l'onglet déjà ouvert
1882
+ * devient la vraie vue — sans rechargement (voir rebuildView).
1883
+ *
1884
+ * Le repli qui vivait ici (`?? extensionRegistry.get('md')`) n'avait rien
1885
+ * de neutre : il envoyait TOUT binaire dans une TextFileView, qui le lit
1886
+ * en UTF-8 avec perte (TextFileView.onLoadFile) puis le RÉÉCRIT sur le
1887
+ * disque en partant (onUnloadFile → save → vault.modify). Ouvrir puis
1888
+ * fermer un PDF suffisait à le détruire, sans frapper une touche.
1889
+ */
1890
+ openFile(file: TFile, openState?: OpenViewState): Promise<void>;
1891
+ /** Monte une vue déjà construite (chemin direct, sans passer par le registre). */
1892
+ open(view: View): Promise<View>;
1893
+ /**
1894
+ * Ferme la vue courante. `view.close()` rejoue toute la pile de nettoyages
1895
+ * accumulée par Component — aucune fuite à gérer ici.
1896
+ *
1897
+ * ★ Le loadFile(null) préalable comble un trou du cycle de vie : les hooks
1898
+ * onUnloadFile — et donc la SAUVEGARDE de TextFileView — ne sont déclenchés
1899
+ * que par un changement de fichier, pas par la fermeture de la vue. Sans
1900
+ * cette ligne, fermer un onglet dans les 2 s suivant la dernière frappe
1901
+ * perdrait le texte non écrit.
1902
+ */
1903
+ private detachView;
1904
+ /** Retire la feuille de l'arbre et détruit sa vue. */
1905
+ detach(): Promise<void>;
1906
+ getDisplayText(): string;
1907
+ getIcon(): string;
1908
+ /**
1909
+ * Le fichier ouvert, si la feuille en héberge un.
1910
+ *
1911
+ * ★ Le repli sur `requestedState` est ce qui rend le §8.4 invisible aux
1912
+ * appelants. Sans lui, une feuille différée qui tient bel et bien un
1913
+ * fichier répondrait `null` : Workspace.getLeavesOfFile() ne la
1914
+ * trouverait pas, et cliquer une note déjà ouverte dans un onglet non
1915
+ * encore matérialisé en ouvrirait un SECOND. Le fichier est résolu au
1916
+ * moment de l'usage, comme partout — le chemin vient d'un layout qui peut
1917
+ * dater.
1918
+ */
1919
+ get file(): TFile | null;
1920
+ setPinned(pinned: boolean): void;
1921
+ togglePinned(): void;
1922
+ }
1923
+ /**
1924
+ * Les codes de sortie — stables, documentés, testables.
1925
+ *
1926
+ * ★ Obsidian rend 0 même en cas d'erreur (« The CLI exits 0 even on errors, so
1927
+ * read what it printed »). C'est le seul point de sa CLI qu'on ne copie PAS :
1928
+ * un script shell qui ne peut pas tester `$?` n'est pas scriptable.
1929
+ */
1930
+ export declare const EXIT: {
1931
+ readonly OK: 0;
1932
+ /** Le handler a levé. La pile est dans `error`. */
1933
+ readonly HANDLER_ERROR: 1;
1934
+ /** `vault=` ne désigne aucune instance connue. */
1935
+ readonly UNKNOWN_VAULT: 2;
1936
+ /** Sous-commande inconnue du serveur. */
1937
+ readonly UNKNOWN_SUBCOMMAND: 4;
1938
+ /** Pas d'instance qui tourne, et impossible d'en lancer une. */
1939
+ readonly NO_INSTANCE: 5;
1940
+ /** Plusieurs instances, et pas de `vault=` pour choisir. */
1941
+ readonly AMBIGUOUS_VAULT: 6;
1942
+ /** `command id=` désigne une commande qui n'existe pas dans le registre. */
1943
+ readonly UNKNOWN_COMMAND: 7;
1944
+ /** `command id=` désigne une commande connue mais INDISPONIBLE ici (p.ex. aucun éditeur actif). */
1945
+ readonly UNAVAILABLE: 8;
1946
+ /** Ligne de commande mal formée. */
1947
+ readonly USAGE: 64;
1948
+ /** Le serveur n'a pas répondu à temps — convention de GNU timeout. */
1949
+ readonly TIMEOUT: 124;
1950
+ /** La fenêtre a disparu pendant la requête. */
1951
+ readonly RENDERER_GONE: 125;
1952
+ /** Jeton absent ou faux. */
1953
+ readonly REFUSED: 126;
1954
+ };
1955
+ /** L'OS courant, façon Obsidian : des booléens plutôt qu'une énumération. */
1956
+ export declare const Platform: {
1957
+ /** Fragment ne tourne que sur bureau. */
1958
+ isDesktop: boolean;
1959
+ isMobile: boolean;
1960
+ isDesktopApp: boolean;
1961
+ isMobileApp: boolean;
1962
+ isMacOS: boolean;
1963
+ isWin: boolean;
1964
+ isLinux: boolean;
1965
+ isIosApp: boolean;
1966
+ isAndroidApp: boolean;
1967
+ };
1968
+ /**
1969
+ * @param cb la fonction à retarder
1970
+ * @param timeout délai en ms
1971
+ * @param resetTimer si `true`, chaque appel REPART du délai complet (« attendre
1972
+ * que ça se calme ») ; si `false`, le premier appel arme le
1973
+ * minuteur et les suivants ne le repoussent pas (« au plus une
1974
+ * fois par fenêtre »). La frappe clavier veut `true`.
1975
+ */
1976
+ export declare function debounce<TArgs extends unknown[]>(cb: (...args: TArgs) => unknown, timeout?: number, resetTimer?: boolean): Debouncer<TArgs>;
1977
+ /**
1978
+ * @returns une fonction qui, pour un texte, rend son score et ses plages — ou
1979
+ * `null` si la requête n'y est pas. Une requête vide matche TOUT, avec
1980
+ * un score nul et aucune plage : c'est ce qu'une palette affiche avant
1981
+ * la première frappe.
1982
+ */
1983
+ export declare function prepareFuzzySearch(query: string): (text: string) => SearchResult | null;
1984
+ /**
1985
+ * Rend `text` dans `el`, en enveloppant chaque plage matchée d'un
1986
+ * `<span class="suggestion-highlight">` — la classe d'Obsidian, donc celle
1987
+ * que les thèmes stylent.
1988
+ *
1989
+ * @param offset décalage à soustraire aux plages : utile quand `text` est une
1990
+ * SOUS-CHAÎNE du texte qui a été cherché (la palette découpe
1991
+ * « Plugin: Commande » en deux spans mais cherche sur le tout).
1992
+ */
1993
+ export declare function renderMatches(el: HTMLElement, text: string, matches: SearchMatches | null, offset?: number): void;
1994
+ /**
1995
+ * Pose une icône dans un élément (§12.6). Signature d'Obsidian : `setIcon(el,
1996
+ * icon)` (A31) — le registre n'est plus passé en paramètre.
1997
+ *
1998
+ * TROIS COUCHES, interrogées dans l'ordre :
1999
+ *
2000
+ * 1. app.icons — le registre, atteint via `window.app` (posé au boot). Lu AU
2001
+ * MOMENT DE L'USAGE : un plugin peut AJOUTER une icône à lui… ou ÉCRASER
2002
+ * une icône des couches du dessous, puisque le registre passe avant.
2003
+ * ★ Lu DÉFENSIVEMENT : la fenêtre de gestion des coffres n'a pas d'App —
2004
+ * elle doit pouvoir poser une icône Lucide sans planter, elle n'a
2005
+ * simplement pas la couche custom (aucun plugin n'y tourne).
2006
+ * 2. les icônes internes — celles qu'Obsidian a en propre, hors Lucide.
2007
+ * 3. le jeu Lucide embarqué — la couche de base, ~1500 icônes, aucune
2008
+ * inscription nécessaire.
2009
+ *
2010
+ * Nom introuvable partout → point médian discret, pas d'erreur :
2011
+ * l'absence est un état normal, même philosophie que les vues (§8.3).
2012
+ */
2013
+ export declare function setIcon(el: HTMLElement, name: string): void;
2014
+ /** Une entrée du registre `app.cliHandlers`. */
2015
+ export interface CliHandlerSpec {
2016
+ /** L'id complet : `help`, `commands`, ou `templater:create-from-template`. */
2017
+ command: string;
2018
+ description: string;
2019
+ flags: CliFlags;
2020
+ handler: CliHandler;
2021
+ }
2022
+ /**
2023
+ * Les capacités CLI que le processus principal fournit au renderer. Injecté au
2024
+ * boot ; jamais construit par le cœur (qui n'a pas le droit de toucher
2025
+ * `window.electron`).
2026
+ */
2027
+ export interface CliHost {
2028
+ /**
2029
+ * Évalue `code` hors CSP et rend sa valeur de complétion sérialisée. Rend un
2030
+ * `CliResult` (A21) : `code` = EXIT.TIMEOUT si l'éval a dépassé `timeoutMs`,
2031
+ * EXIT.HANDLER_ERROR sinon — le même vocabulaire que toute la CLI.
2032
+ */
2033
+ eval(code: string, timeoutMs: number): Promise<CliResult>;
2034
+ /** Écrit le shim `fragment` et le met sur le PATH. */
2035
+ installShim(): Promise<CliInstallResult>;
2036
+ }
2037
+ /** Le résultat d'`installShim` — seul le message intéresse l'appelant. */
2038
+ export interface CliInstallResult {
2039
+ message: string;
2040
+ }
2041
+ /**
2042
+ * Une commande.
2043
+ * Dans chaque commande il y a 2 possibilités :
2044
+ * Soit on écrit un callback : lui il est tout le temps disponible
2045
+ * Soit on écrit un checkCallback : lui il prend un bool en entrée.
2046
+ * Dans tous les cas il va checker d'abord s'il peut s'appliquer (supprimer la node active par exemple)
2047
+ * puis selon le bool d'entrée, il va tester si il s'applique.
2048
+ *
2049
+ * Les quatre callbacks se recouvrent, du plus général au plus spécifique, et
2050
+ * chacun ÉCRASE les précédents :
2051
+ *
2052
+ * callback < checkCallback < editorCallback < editorCheckCallback
2053
+ *
2054
+ * En pratique on n'en écrit qu'un. Les deux variantes `editor*` sont désucrées
2055
+ * en `checkCallback` par CommandManager.addCommand — il n'existe donc qu'UNE
2056
+ * seule branche d'exécution dans toute l'app.
2057
+ */
2058
+ export interface Command {
2059
+ id: string;
2060
+ name: string;
2061
+ /** Nom d'icône Lucide, pour la palette et les barres d'outils. */
2062
+ icon?: string;
2063
+ /**
2064
+ * Maintenir le raccourci re-déclenche-t-il la commande ? Lu uniquement au
2065
+ * dispatch clavier (phase B) ; sans effet ailleurs.
2066
+ */
2067
+ repeatable?: boolean;
2068
+ /**
2069
+ * Commande réservée au mobile. Fragment est desktop-only : une commande
2070
+ * ainsi marquée n'est PAS enregistrée du tout. Le champ existe pour qu'un
2071
+ * plugin écrit contre l'API mobile reste typé.
2072
+ */
2073
+ mobileOnly?: boolean;
2074
+ /** Le cas simple : déclenchable partout, toujours disponible. */
2075
+ callback?: () => unknown;
2076
+ /**
2077
+ * Le micro-pattern §11.5 : un seul appel fusionne « est-elle disponible ? »
2078
+ * (checking = true, SANS effet de bord) et « exécute-la » (checking = false).
2079
+ *
2080
+ * ★ `checking = true` n'est appelé qu'au listage — donc à chaque ouverture
2081
+ * de palette, sur chaque commande enregistrée. Il doit rester bon marché.
2082
+ */
2083
+ checkCallback?: (checking: boolean) => boolean | void;
2084
+ /**
2085
+ * Ne se déclenche que lorsqu'un éditeur de texte a le focus, et reçoit cet
2086
+ * éditeur ainsi que la vue qui l'héberge.
2087
+ *
2088
+ * ★ Obsidian passe ici un `MarkdownView | MarkdownFileInfo`, parce que chez
2089
+ * lui un éditeur peut vivre HORS d'une vue (nœud texte de Canvas).
2090
+ * Fragment n'a pas ce cas : on passe la `View`. Le jour où il apparaît, ce
2091
+ * type s'élargit sans casser personne.
2092
+ */
2093
+ editorCallback?: (editor: Editor, ctx: View) => unknown;
2094
+ /** La variante `checking` de la précédente. Écrase toutes les autres. */
2095
+ editorCheckCallback?: (checking: boolean, editor: Editor, ctx: View) => boolean | void;
2096
+ /**
2097
+ * Raccourci par défaut. Lu par le HotkeyManager (phase B).
2098
+ *
2099
+ * Un plugin devrait s'abstenir d'en poser : l'espace des combinaisons est
2100
+ * une ressource partagée, et une personnalisation de l'utilisateur ne
2101
+ * « corrige » pas un conflit, elle le déplace.
2102
+ */
2103
+ hotkeys?: Hotkey[];
2104
+ /**
2105
+ * Dormant. Autoriserait la commande en mode lecture — qui n'existe pas
2106
+ * encore (postProcessors n'a aucun consommateur). Déclaré pour la
2107
+ * compatibilité de typage, sans garde correspondante : on n'écrit pas de
2108
+ * fausse protection autour d'un mode qui n'existe pas.
2109
+ */
2110
+ allowPreview?: boolean;
2111
+ /** Dormant. Voir allowPreview — l'éditeur de propriétés n'existe pas (§7.6). */
2112
+ allowProperties?: boolean;
2113
+ /** Dormant. Barre d'outils mobile ; Fragment est desktop-only. */
2114
+ showOnMobileToolbar?: boolean;
2115
+ }
2116
+ export interface DataAdapter {
2117
+ getName(): string;
2118
+ exists(path: string): Promise<boolean>;
2119
+ stat(path: string): Promise<Stat | null>;
2120
+ list(path: string): Promise<ListedFiles>;
2121
+ read(path: string): Promise<string>;
2122
+ readBinary(path: string): Promise<ArrayBuffer>;
2123
+ write(path: string, data: string, options?: DataWriteOptions): Promise<void>;
2124
+ writeBinary(path: string, data: ArrayBuffer, options?: DataWriteOptions): Promise<void>;
2125
+ append(path: string, data: string, options?: DataWriteOptions): Promise<void>;
2126
+ appendBinary(path: string, data: ArrayBuffer, options?: DataWriteOptions): Promise<void>;
2127
+ mkdir(path: string): Promise<void>;
2128
+ rename(path: string, newPath: string): Promise<void>;
2129
+ copy(path: string, newPath: string): Promise<void>;
2130
+ remove(path: string): Promise<void>;
2131
+ rmdir(path: string, recursive: boolean): Promise<void>;
2132
+ trashSystem(path: string): Promise<boolean>;
2133
+ trashLocal(path: string): Promise<void>;
2134
+ /**
2135
+ * Une URL que la PLATEFORME sait aller chercher toute seule — <img src>,
2136
+ * <video>, fetch, pdf.js. C'est l'inverse de readBinary : là où celui-ci
2137
+ * fait TRANSPORTER les octets par le renderer (IPC → structured clone →
2138
+ * ArrayBuffer dans le tas JS), ici on ne rend qu'une adresse, et Chromium
2139
+ * lit le fichier lui-même, en streaming, hors du thread JS.
2140
+ *
2141
+ * Synchrone, comme chez Obsidian : c'est ce qui permet de l'appeler dans un
2142
+ * rendu. Donc aucun aller-retour IPC ici — l'implémentation n'a le droit
2143
+ * que de fabriquer une chaîne.
2144
+ *
2145
+ * `cacheKey` est HORS API Obsidian — même justification que listWithStats.
2146
+ * Sans cache-buster, Chromium sert indéfiniment la version qu'il a mise en
2147
+ * cache et une image modifiée sur le disque ne se rafraîchit jamais à
2148
+ * l'écran. Obsidian le résout en concaténant le mtime côté Vault ; on le
2149
+ * passe explicitement, parce que la forme de l'URL appartient à l'adapter
2150
+ * (le Bridge du §6.1) : un adapter navigateur rendrait un blob:, où le
2151
+ * suffixe « ?mtime » n'aurait aucun sens. En le laissant facultatif, la
2152
+ * signature d'Obsidian reste appelable telle quelle.
2153
+ */
2154
+ getResourcePath(path: string, cacheKey?: string | number): string;
2155
+ onChange: ((type: "create" | "modify" | "delete", path: string) => void) | null;
2156
+ }
2157
+ /*
2158
+ * ── Ce qui a été retiré ici, et pourquoi c'est une bonne nouvelle ────────
2159
+ *
2160
+ * `ListedEntry` et `ListedFilesWithStats` vivaient à cet endroit. Ils
2161
+ * n'existaient QUE pour amortir la frontière IPC : ramener les stat avec le
2162
+ * listing faisait passer la construction de l'arbre de D+F allers-retours à D.
2163
+ * C'était notre seul écart de forme avec l'API d'Obsidian, et il était justifié
2164
+ * par une contrainte que nous n'avons plus.
2165
+ *
2166
+ * Depuis que le coffre est lu par `fs` dans le renderer, un `stat` coûte un
2167
+ * appel de fonction. La béquille n'a plus d'objet, et `ListedFiles` — mot pour
2168
+ * mot celle d'obsidian.d.ts — redevient la seule forme de listing.
2169
+ */
2170
+ export interface DataWriteOptions {
2171
+ ctime?: number;
2172
+ mtime?: number;
2173
+ }
2174
+ /**
2175
+ * L'utilitaire de coalescence (§6.3, §7.5).
2176
+ *
2177
+ * Le PDF insiste : « chaque frontière de sous-système a sa politique de
2178
+ * coalescence explicite ». requestSave d'un éditeur, requestSaveLayout du
2179
+ * workspace, requestSaveConfig des réglages — tous passent par ici. La règle
2180
+ * qui les gouverne : on n'écrit jamais le disque dans un chemin chaud, on le
2181
+ * « demande ».
2182
+ */
2183
+ export interface Debouncer<TArgs extends unknown[]> {
2184
+ (...args: TArgs): void;
2185
+ /** Exécute immédiatement si un appel est en attente, et annule le minuteur. */
2186
+ run(): void;
2187
+ /** Annule l'appel en attente sans l'exécuter. */
2188
+ cancel(): void;
2189
+ }
2190
+ /**
2191
+ * LE SOCLE — ce dont les layers vivent réellement, et RIEN de plus.
2192
+ *
2193
+ * ★ POURQUOI cette interface existe séparément d'`Editor` : parce que « document
2194
+ * affichable » et « éditeur de texte » ne sont pas la même chose. Une vue PDF
2195
+ * a une géométrie, une virtualisation et des surfaces de markers, mais peut
2196
+ * n'avoir aucun texte adressable (un scan est une suite d'images). Tout ce que
2197
+ * consomment `WidgetLayer`, `posVisibility`, les markers et la saisie de
2198
+ * dessin est ICI ; aucun d'eux n'a jamais appelé `getLine`.
2199
+ *
2200
+ * Le dire dans les types, c'est le faire vérifier par le compilateur plutôt
2201
+ * que par la discipline. `LayerContext.editor` est donc un `DocumentSurface` :
2202
+ * un layer ne PEUT pas dépendre d'un texte qui n'existera pas forcément.
2203
+ *
2204
+ * Une surface qui a du texte se reconnaît par le garde `hasText()` ci-dessous,
2205
+ * qui rétrécit le type — pas par un drapeau à tenir synchrone.
2206
+ *
2207
+ * Les coordonnées sont DOCUMENT-relatives (cf. `Rect`), sauf `posAtCoords`.
2208
+ */
2209
+ export interface DocumentSurface {
2210
+ coordsAtPos(offset: number): Rect | null;
2211
+ /** Coords CLIENT (issues d'un événement pointeur) → offset. Pour la saisie. */
2212
+ posAtCoords(x: number, y: number): number | null;
2213
+ /**
2214
+ * La fenêtre de VIRTUALISATION : ce que l'éditeur a monté. Elle inclut ce qui
2215
+ * est masqué (replié) — un offset peut y être sans être dessiné.
2216
+ */
2217
+ viewportRange(): EditorRange;
2218
+ /**
2219
+ * Les portions RÉELLEMENT dessinées : le viewport, MOINS ce qui est masqué.
2220
+ * Une plage repliée y laisse un TROU.
2221
+ *
2222
+ * ★ POURQUOI c'est distinct de `viewportRange` — et pourquoi les deux sont
2223
+ * nécessaires. « Pas visible » recouvre deux situations qui appellent des
2224
+ * réactions OPPOSÉES :
2225
+ *
2226
+ * - hors viewport (virtualisation) : l'objet existe, il est juste hors
2227
+ * écran. Il faut GARDER sa dernière position — recalculer depuis le
2228
+ * heightmap a été mesuré pire (cf. §8.4 du doc d'ancrage).
2229
+ * - dans un trou (replié) : le contenu est CACHÉ. Il faut MASQUER
2230
+ * l'objet. Le repositionner est un contresens : `coordsAtPos` répond
2231
+ * quand même, en rendant le point dessiné le plus proche — donc le
2232
+ * placeholder du repli. Toutes les annotations d'un bloc replié
2233
+ * s'empilaient ainsi sur son titre.
2234
+ *
2235
+ * Un seul membre ne pouvait pas distinguer les deux, d'où l'ajout.
2236
+ *
2237
+ * ⚠️ Un trou ne dit PAS « replié par l'utilisateur », il dit « non dessiné ».
2238
+ * Chez CM6 la virtualisation horizontale des lignes très longues (line
2239
+ * gaps) en produit aussi. Pour décider de masquer c'est équivalent — dans
2240
+ * les deux cas les coordonnées seraient fausses. Distinguer les deux
2241
+ * causes exigerait de connaître l'extension de repli, donc de perdre la
2242
+ * portabilité de la façade : on ne le fait pas.
2243
+ */
2244
+ renderedRanges(): readonly EditorRange[];
2245
+ addLayer(spec: LayerSurfaceSpec): () => void;
2246
+ /** Force un recalcul des layers — le "poke" quand une donnée EXTERNE change. */
2247
+ requestUpdate(): void;
2248
+ onChange(cb: (change: EditorChange) => void): () => void;
2249
+ readonly contentEl: HTMLElement;
2250
+ readonly scrollEl: HTMLElement;
2251
+ readonly cm: unknown;
2252
+ }
2253
+ /**
2254
+ * Le contrat d'un éditeur : une surface textuelle QUI S'ÉCRIT.
2255
+ *
2256
+ * ★ POURQUOI un troisième étage. Le PDF a du texte adressable — on y
2257
+ * sélectionne, on y mesure des plages, les layers s'y ancrent — mais il ne
2258
+ * s'édite pas. Le mettre au même étage que CodeMirror aurait forcé un choix
2259
+ * entre deux mensonges : des méthodes d'écriture qui ne font rien, ou
2260
+ * « Mettre en gras » proposé dans la palette sur un PDF.
2261
+ *
2262
+ * Trois étages, trois questions, trois gardes :
2263
+ *
2264
+ * DocumentSurface « y a-t-il une géométrie ? » (toute vue à layers)
2265
+ * TextSurface « y a-t-il du texte ? » hasText()
2266
+ * Editor « puis-je l'écrire ? » isEditable()
2267
+ *
2268
+ * C'est `Editor` — et lui seul — que les commandes `editor:*` reçoivent.
2269
+ * Même sens que chez Obsidian : un Editor, c'est ce qu'on édite.
2270
+ */
2271
+ export interface Editor extends TextSurface {
2272
+ getValue(): string;
2273
+ getRange(from: number, to: number): string;
2274
+ /** LA primitive d'écriture. Tout le reste est du sucre par-dessus. */
2275
+ transaction(spec: EditorTransactionSpec): void;
2276
+ /** Sucre : `transaction({ changes: [{ from, to, insert }] })`. */
2277
+ replaceRange(from: number, to: number, text: string): void;
2278
+ /** Sucre : `transaction({ selection: { anchor, head } })`. */
2279
+ setSelection(anchor: number, head?: number): void;
2280
+ focus(): void;
2281
+ hasFocus(): boolean;
2282
+ }
2283
+ /** Ce qui a bougé dans l'éditeur. `origin` est libre (ex. le poke de données externes). */
2284
+ export interface EditorChange {
2285
+ docChanged: boolean;
2286
+ selectionChanged: boolean;
2287
+ viewportChanged: boolean;
2288
+ /**
2289
+ * Remappe un offset de l'ANCIEN document vers le NOUVEAU.
2290
+ *
2291
+ * C'est LA pièce sans laquelle rien ne peut être ancré au texte : un offset
2292
+ * brut désigne un caractère différent dès qu'on édite au-dessus de lui. Tout
2293
+ * ce qui garde une position entre deux changements DOIT passer par ici.
2294
+ *
2295
+ * `assoc` dit de quel côté la position colle : `1` (défaut) = au caractère qui
2296
+ * SUIT — une insertion pile sur l'ancre pousse l'ancre avec son texte ; `-1` =
2297
+ * au caractère qui précède (l'ancre reste avant l'insertion).
2298
+ *
2299
+ * Identité quand `docChanged` est faux.
2300
+ */
2301
+ mapPos(pos: number, assoc?: -1 | 1): MappedPos;
2302
+ origin?: string;
2303
+ }
2304
+ /** Un changement : remplace `[from, to)` par `insert`. `to` absent = insertion. */
2305
+ export interface EditorChangeSpec {
2306
+ from: number;
2307
+ to?: number;
2308
+ insert: string;
2309
+ }
2310
+ /** Une position ligne/colonne (0-indexée), comme l'`Editor` d'Obsidian. */
2311
+ export interface EditorPosition {
2312
+ line: number;
2313
+ ch: number;
2314
+ }
2315
+ /** Une plage d'offsets absolus dans le document. */
2316
+ export interface EditorRange {
2317
+ from: number;
2318
+ to: number;
2319
+ }
2320
+ /**
2321
+ * Ce qu'une commande d'éditeur PRODUIT — et ce que `Editor.transaction` applique.
2322
+ *
2323
+ * ★ Les `changes` sont TOUS exprimés dans le document d'avant la transaction ;
2324
+ * c'est le moteur qui les compose. Une commande n'a donc jamais à recalculer
2325
+ * ses offsets après son propre premier changement — c'est ce qui rend
2326
+ * « un titre sur chaque ligne sélectionnée » écrivable en une passe.
2327
+ *
2328
+ * ★ La `selection`, elle, est dans le document d'APRÈS : c'est là que le
2329
+ * curseur atterrira.
2330
+ */
2331
+ export interface EditorTransactionSpec {
2332
+ changes?: EditorChangeSpec[];
2333
+ selection?: {
2334
+ anchor: number;
2335
+ head?: number;
2336
+ };
2337
+ /** Amène la sélection à l'écran. */
2338
+ scrollIntoView?: boolean;
2339
+ }
2340
+ export interface FileStats {
2341
+ ctime: number;
2342
+ mtime: number;
2343
+ size: number;
2344
+ }
2345
+ /** Ce que rend une FuzzySuggestModal : l'élément et son match. */
2346
+ export interface FuzzyMatch<T> {
2347
+ item: T;
2348
+ match: SearchResult;
2349
+ }
2350
+ /** Une combinaison. `key` est la valeur logique (`KeyboardEvent.key`) : 'P', 'F2', 'ArrowUp'. */
2351
+ export interface Hotkey {
2352
+ modifiers: Modifier[];
2353
+ key: string;
2354
+ }
2355
+ /**
2356
+ * Vue structurelle commune à Registry et OrderedRegistry, pour l'inspecteur d'UI.
2357
+ * Aucune des deux classes ne déclare l'implémenter — le typage structurel suffit.
2358
+ */
2359
+ export interface InspectableRegistry {
2360
+ name: string;
2361
+ label: string;
2362
+ readonly size: number;
2363
+ entries(): Array<{
2364
+ key: string;
2365
+ value: unknown;
2366
+ owner: string;
2367
+ }>;
2368
+ }
2369
+ export interface Instruction {
2370
+ /** La touche, telle qu'affichée : '↑↓', '↵', 'esc'. */
2371
+ command: string;
2372
+ purpose: string;
2373
+ }
2374
+ /**
2375
+ * Ce que le Keymap a déduit de la frappe, et qu'il passe aux handlers.
2376
+ *
2377
+ * ★ DEUX représentations de la touche, et il faut les deux :
2378
+ *
2379
+ * - `key` est la valeur LOGIQUE — ce que la disposition clavier produit.
2380
+ * Sur un AZERTY, la touche à droite de « L » donne `'m'`.
2381
+ * - `vkey` est la valeur PHYSIQUE (`KeyboardEvent.code`) — l'emplacement.
2382
+ * La même touche donne `'Semicolon'`, parce que c'est là qu'est le
2383
+ * point-virgule sur un QWERTY.
2384
+ *
2385
+ * Un raccourci enregistré sur `key` suit la disposition ; enregistré sur
2386
+ * `vkey`, il suit l'emplacement physique. `isMatch` accepte les deux, ce qui
2387
+ * permet à un raccourci de survivre à un changement de disposition sans que
2388
+ * l'auteur ait à choisir.
2389
+ */
2390
+ export interface KeymapContext {
2391
+ /** Forme canonique compilée : trié, joint par virgules. `"Ctrl,Shift"`. */
2392
+ modifiers: string;
2393
+ key: string;
2394
+ vkey: string;
2395
+ }
2396
+ /** Une liaison enregistrée dans un Scope. `null` vaut « n'importe lequel ». */
2397
+ export interface KeymapEventHandler {
2398
+ modifiers: string | null;
2399
+ key: string | null;
2400
+ func: KeymapEventListener;
2401
+ }
2402
+ export interface LayerContext {
2403
+ /**
2404
+ * La surface de document de la vue, ou `null` si elle n'en a pas. Un layer qui
2405
+ * veut ancrer au document doit gérer le cas null : sans surface, seuls les
2406
+ * overlays viewport (fixes à l'écran) sont possibles.
2407
+ *
2408
+ * ★ C'est un `DocumentSurface`, PAS un `Editor` : un layer n'a jamais eu besoin
2409
+ * des membres textuels, et toutes les vues n'en ont pas (un PDF scanné n'a
2410
+ * aucun texte). Le garde `hasText()` ouvre la moitié textuelle quand elle
2411
+ * existe — et le compilateur l'interdit partout ailleurs.
2412
+ */
2413
+ editor: DocumentSurface | null;
2414
+ view: View;
2415
+ file: TFile | null;
2416
+ app: App;
2417
+ /** Points de montage d'overlay view-level (viewport toujours ; document si éditeur). */
2418
+ overlays: OverlayHost;
2419
+ }
2420
+ /**
2421
+ * La déclaration d'un layer (fiche de catalogue), contribuée par un plugin.
2422
+ * Un layer n'est PAS un Component : `create` monte la contribution sur un éditeur
2423
+ * et rend un disposer ; c'est le plugin (Component) qui possède les ressources
2424
+ * partagées entre éditeurs (réseau/CRDT).
2425
+ */
2426
+ export interface LayerSpec {
2427
+ /** Clef unique dans le catalogue — identité de réconciliation. */
2428
+ id: string;
2429
+ /** Libellé pour le switcher. */
2430
+ name: string;
2431
+ icon?: string;
2432
+ /** Activé d'office à l'ouverture d'une vue applicable ? (défaut : non). */
2433
+ defaultEnabled?: boolean;
2434
+ /** Le layer concerne-t-il cette vue ? (défaut : toute vue éditrice). */
2435
+ appliesTo?(view: View): boolean;
2436
+ /** Monte le layer sur l'éditeur d'une vue ; rend le disposer. */
2437
+ create(ctx: LayerContext): () => void;
2438
+ }
2439
+ /**
2440
+ * Ce qu'un layer contribue, façon `layer()` de CM6 :
2441
+ * - `above` : devant (true) ou derrière (false) le texte.
2442
+ * - `markers` : recalculé au reflux ; ne calculer que sur `renderedRanges()`
2443
+ * (pas `viewportRange()` : ce qui est replié ne doit rien rendre).
2444
+ * - `update` : faut-il recalculer sur ce changement ? (défaut : sur docChanged).
2445
+ * Le reflux de scroll/géométrie est géré tout seul par l'impl.
2446
+ */
2447
+ export interface LayerSurfaceSpec {
2448
+ above: boolean;
2449
+ markers(surface: DocumentSurface): readonly Marker[];
2450
+ update?(change: EditorChange): boolean;
2451
+ }
2452
+ /**
2453
+ * §8.2 — la forme SÉRIALISÉE de l'arbre de fenêtrage, telle qu'elle atterrit
2454
+ * dans `.fragment/workspace.json`.
2455
+ *
2456
+ * Trois formes de nœud, une par classe du Composite. Aucune référence d'objet,
2457
+ * aucun état éphémère : ce fichier doit se relire après un redémarrage, et
2458
+ * c'est tout ce qu'il doit savoir faire. Le défilement et la sélection vivent
2459
+ * dans getEphemeralState() et n'entrent JAMAIS ici — c'est la définition même
2460
+ * de la dualité du §8.2.
2461
+ */
2462
+ /** La part de flex-grow d'un nœud dans son split parent. Posée par le PARENT. */
2463
+ export interface LayoutDimension {
2464
+ dimension?: number;
2465
+ }
2466
+ export interface LeafLayout extends LayoutDimension {
2467
+ type: "leaf";
2468
+ id: string;
2469
+ state: ViewState;
2470
+ }
2471
+ /**
2472
+ * Ce qui doit correspondre pour qu'une liaison se déclenche. `null` = indifférent.
2473
+ * Volontairement structurel : un `KeymapEventHandler` comme une liaison compilée
2474
+ * du HotkeyManager satisfont tous les deux cette forme.
2475
+ */
2476
+ export interface Liaison {
2477
+ modifiers: string | null;
2478
+ key: string | null;
2479
+ }
2480
+ /** Conforme à `ListedFiles` d'obsidian.d.ts — ne pas modifier. */
2481
+ export interface ListedFiles {
2482
+ files: string[];
2483
+ folders: string[];
2484
+ }
2485
+ /**
2486
+ * Le résultat d'un remappage de position. Les deux questions sont séparées à
2487
+ * dessein : « où est-ce maintenant ? » a TOUJOURS une réponse valide (sinon
2488
+ * l'appelant garderait un offset hors bornes, qui explose au premier usage),
2489
+ * « le texte a-t-il été supprimé ? » est une information EN PLUS, dont chacun
2490
+ * fait ce qu'il veut.
2491
+ */
2492
+ export interface MappedPos {
2493
+ /** La position dans le NOUVEAU document — toujours dans les bornes. */
2494
+ pos: number;
2495
+ /** Le texte ancré a-t-il disparu ? `pos` est alors le point d'effondrement. */
2496
+ deleted: boolean;
2497
+ }
2498
+ /**
2499
+ * Un marker — l'atome visuel, à la sémantique de `LayerMarker` de CM6 :
2500
+ * - `eq` : deux markers identiques → on réutilise le DOM (pas de redraw).
2501
+ * - `draw` : rend l'élément (avatar `<img>`, rectangle, trait `<svg>`…).
2502
+ * L'élément doit se POSITIONNER lui-même (position: absolute + coords
2503
+ * document-relatives, cf. `Rect`).
2504
+ * - `update` : met à jour un DOM existant en place ; `false` → CM redessine.
2505
+ */
2506
+ export interface Marker {
2507
+ eq(other: Marker): boolean;
2508
+ draw(): HTMLElement;
2509
+ update?(dom: HTMLElement, old: Marker): boolean;
2510
+ }
2511
+ /**
2512
+ * Les options d'une ouverture. `eState` porte l'état ÉPHÉMÈRE (§8.2) —
2513
+ * défilement, sélection, ligne à atteindre : ce qui survit à la navigation mais
2514
+ * n'a rien à faire dans un fichier de layout.
2515
+ */
2516
+ export interface OpenViewState {
2517
+ state?: Record<string, unknown>;
2518
+ eState?: Record<string, unknown>;
2519
+ active?: boolean;
2520
+ }
2521
+ /**
2522
+ * Ce que la vue offre aux layers pour poser des overlays, SANS exposer son DOM :
2523
+ * on monte un élément dans un plan, on récupère le retrait. Le layer POSITIONNE
2524
+ * lui-même l'élément (left/top) ; le host garantit seulement l'origine du plan :
2525
+ * - 'viewport' : coin du pane, NE scrolle pas (objets fixes à l'écran) ;
2526
+ * - 'document' : le repère de `coordsAtPos` lui-même — un élément posé aux
2527
+ * coords rendues par l'éditeur y atterrit juste, et suit le texte au scroll
2528
+ * comme à la remise en page, sans que personne n'ait à resynchroniser quoi
2529
+ * que ce soit.
2530
+ * Un layer ne peut ni restructurer les plans ni toucher les widgets d'un autre.
2531
+ */
2532
+ export interface OverlayHost {
2533
+ mount(el: HTMLElement, plane: OverlayPlane): () => void;
2534
+ /**
2535
+ * Convertit un point CLIENT en coordonnées du plan viewport, ou `null` si le
2536
+ * point est HORS du pane. Sert de hit-test (est-ce que ce drop me concerne ?)
2537
+ * ET de conversion, sans exposer le DOM.
2538
+ */
2539
+ clientToViewport(x: number, y: number): {
2540
+ x: number;
2541
+ y: number;
2542
+ } | null;
2543
+ /**
2544
+ * Le symétrique pour le plan 'document' : un point CLIENT → le repère de
2545
+ * `coordsAtPos`. C'est ce qui permet d'ancrer au texte SANS déplacer ce qu'on
2546
+ * ancre : on compare la position voulue et celle du glyphe dans le même
2547
+ * repère, et on garde l'écart. `null` sans plan document (vue sans éditeur).
2548
+ */
2549
+ clientToDocument(x: number, y: number): {
2550
+ x: number;
2551
+ y: number;
2552
+ } | null;
2553
+ /**
2554
+ * La bande de MARGE disponible d'un côté du texte, en coordonnées du plan
2555
+ * document — c'est-à-dire prête à être posée telle quelle sur un widget.
2556
+ *
2557
+ * C'est l'espace entre le bord du pane et le bord de la colonne de lecture.
2558
+ * Il dépend de la largeur du PANE, pas du document : c'est la seule géométrie
2559
+ * des plans qu'un widget ne peut pas déduire de l'éditeur, d'où sa présence
2560
+ * ici. `width` peut être négatif ou nul quand la colonne remplit le pane —
2561
+ * l'appelant décide alors quoi faire (typiquement : masquer).
2562
+ */
2563
+ gutterBand(side: GutterSide): {
2564
+ left: number;
2565
+ width: number;
2566
+ } | null;
2567
+ /**
2568
+ * Prévient quand la géométrie des plans change (pane ou colonne redimensionnés).
2569
+ *
2570
+ * ⚠️ À n'utiliser QUE pour ce qui dépend vraiment de la taille des plans — les
2571
+ * marges. La position d'un widget ancré au texte, elle, n'en dépend pas : le
2572
+ * plan document vit dans le repère de `coordsAtPos`, donc il glisse avec lui
2573
+ * (§2 du doc). S'abonner ici « au cas où » ferait revenir la valeur dérivée
2574
+ * qu'on a supprimée.
2575
+ */
2576
+ onGeometryChange(cb: () => void): () => void;
2577
+ }
2578
+ /** §11.1 — le manifest.json d'un plugin. */
2579
+ export interface PluginManifest {
2580
+ id: string;
2581
+ name: string;
2582
+ version: string;
2583
+ minAppVersion?: string;
2584
+ description?: string;
2585
+ author?: string;
2586
+ }
2587
+ /**
2588
+ * Un rectangle, MÊME shape que le `Rect` de CM6 (choix délibéré : re-export sans
2589
+ * conversion côté CmEditor).
2590
+ *
2591
+ * ★ Système de coordonnées : DOCUMENT-relatif — l'origine est le coin haut-gauche
2592
+ * du contenu, pas la fenêtre. C'est le repère dans lequel vivent les markers
2593
+ * d'un `addLayer` ; un marker positionné avec ces coordonnées atterrit donc au
2594
+ * bon endroit et suit le défilement gratuitement.
2595
+ */
2596
+ export interface Rect {
2597
+ left: number;
2598
+ top: number;
2599
+ right: number;
2600
+ bottom: number;
2601
+ }
2602
+ /**
2603
+ * §11.3 — une icône du ribbon (addRibbonIcon). `buttonEl` est posé par
2604
+ * WorkspaceRibbon au premier rendu et JAMAIS recréé : c'est la référence que
2605
+ * addRibbonIcon rend au plugin — elle doit rester l'élément vivant.
2606
+ */
2607
+ export interface RibbonItem {
2608
+ icon: string;
2609
+ title: string;
2610
+ callback: (evt: MouseEvent) => unknown;
2611
+ buttonEl?: HTMLElement;
2612
+ }
2613
+ export interface SearchResult {
2614
+ score: number;
2615
+ matches: SearchMatches;
2616
+ }
2617
+ export interface SplitLayout extends LayoutDimension {
2618
+ type: "split";
2619
+ direction: SplitDirection;
2620
+ children: LayoutNode[];
2621
+ /** Sidedocks seulement. */
2622
+ collapsed?: boolean;
2623
+ /** Sidedocks seulement — la valeur de --sidebar-width, en px. */
2624
+ width?: number;
2625
+ }
2626
+ // ═══════════════════════════════════════════════════════════════════════════
2627
+ // Ce fichier est AMBIANT (chargé via "types": ["./types"]) : tout ce qu'il
2628
+ // déclare est global, sans import. Conséquence : PAS d'`import … from` en tête
2629
+ // — la moindre ligne de ce genre en ferait un module et ferait disparaître la
2630
+ // portée globale de tout le reste.
2631
+ //
2632
+ // Pour réutiliser quand même les types du coffre, on passe par l'import EN
2633
+ // POSITION DE TYPE : `import('chemin').Type`. C'est la seule forme autorisée
2634
+ // ici, et elle n'affecte pas la nature globale du fichier.
2635
+ // ═══════════════════════════════════════════════════════════════════════════
2636
+ // ─── Stat vs FileStats : deux types voisins, à ne pas confondre ─────────────
2637
+ // Obsidian les distingue, et la raison est bonne.
2638
+ //
2639
+ // Stat — niveau ADAPTER/disque. Rendu par adapter.stat(chemin). Il PORTE
2640
+ // un discriminant `type`, parce qu'à ce niveau on n'a qu'un
2641
+ // chemin : rien ne dit si c'est un fichier ou un dossier.
2642
+ // FileStats — niveau OBJET du Vault. C'est le champ TFile.stat. PAS de `type`,
2643
+ // parce qu'être un TFile le dit déjà. Le discriminant serait
2644
+ // redondant, et donc une occasion de mentir.
2645
+ export interface Stat {
2646
+ type: "file" | "folder";
2647
+ ctime: number; // création (ms epoch)
2648
+ mtime: number; // dernière modif
2649
+ size: number;
2650
+ }
2651
+ export interface TabsLayout extends LayoutDimension {
2652
+ type: "tabs";
2653
+ children: LayoutNode[];
2654
+ currentTab: number;
2655
+ }
2656
+ /**
2657
+ * Le contrat d'un éditeur de TEXTE : le socle, plus ce qu'un document textuel
2658
+ * sait faire en propre. Mêmes membres qu'avant le découpage, même nom — vu de
2659
+ * l'extérieur, `Editor` n'a pas bougé. `CmEditor implements Editor` est inchangé.
2660
+ *
2661
+ * Ce qui vit ICI plutôt que dans le socle a un point commun : ça n'a de sens que
2662
+ * s'il existe un espace d'offsets de caractères. `coordsForRange` en fait partie
2663
+ * — un rect par rangée VISUELLE d'une plage de texte.
2664
+ */
2665
+ export interface TextSurface extends DocumentSurface {
2666
+ getLine(n: number): string;
2667
+ lineCount(): number;
2668
+ offsetToPos(offset: number): EditorPosition;
2669
+ posToOffset(pos: EditorPosition): number;
2670
+ getSelection(): EditorRange;
2671
+ coordsForRange(from: number, to: number): Rect[];
2672
+ }
2673
+ /** Une option d'une rangée (voir ToolbarItem.setOptions) : valeur + aspect. */
2674
+ export interface ToolbarOption {
2675
+ value: string;
2676
+ label: string;
2677
+ /** Aspect par icône Lucide… */
2678
+ icon?: string;
2679
+ /** …ou par pastille colorée… */
2680
+ color?: string;
2681
+ /** …dont on peut imposer le diamètre en px (l'épaisseur de trait). */
2682
+ size?: number;
2683
+ }
2684
+ /**
2685
+ * Un coffre connu, tel que la fenêtre de gestion et le sélecteur l'affichent.
2686
+ *
2687
+ * `name` est DÉRIVÉ du chemin côté main (basename), jamais persisté : renommer
2688
+ * le dossier sur le disque doit changer le nom affiché sans migration.
2689
+ *
2690
+ * `exists` est là exprès pour qu'un coffre introuvable — déplacé, disque
2691
+ * débranché — soit MONTRÉ comme cassé plutôt que caché. Un coffre qui disparaît
2692
+ * en silence, c'est la panique assurée.
2693
+ */
2694
+ export interface VaultInfo {
2695
+ path: string;
2696
+ name: string;
2697
+ exists: boolean;
2698
+ /** Celui qui est ouvert dans cette instance. */
2699
+ current: boolean;
2700
+ }
2701
+ /**
2702
+ *
2703
+ * WorkspaceItem (extends Events) un nœud de l'arbre, porte un élément DOM
2704
+ * ├─ WorkspaceParent peut avoir des enfants
2705
+ * │ ├─ WorkspaceSplit découpe horizontale / verticale
2706
+ * │ │ ├─ WorkspaceRoot la zone centrale
2707
+ * │ │ └─ WorkspaceSidedock les panneaux latéraux, repliables
2708
+ * │ └─ WorkspaceTabs empile des feuilles, une seule visible
2709
+ * └─ WorkspaceLeaf ★ héberge exactement une View
2710
+ *
2711
+ * Et dans la feuille, la hiérarchie de vues (§8.3) :
2712
+ *
2713
+ * View (extends Component) un panneau, containerEl
2714
+ * └─ ItemView + en-tête, actions, contentEl
2715
+ * └─ FileView + un TFile, onLoadFile/onUnloadFile
2716
+ * └─ EditableFileView marqueur : « celle-ci sait écrire »
2717
+ * └─ TextFileView + data, requestSave débouncé
2718
+ *
2719
+ */
2720
+ /**
2721
+ * L'état SÉRIALISABLE d'une feuille (§8.2). Ce qui finira dans workspace.json.
2722
+ *
2723
+ * Ne contient que des données : un type de vue par son nom, un état opaque
2724
+ * appartenant à la vue. Aucune référence d'objet — c'est ce qui permet de le
2725
+ * relire après un redémarrage.
2726
+ */
2727
+ export interface ViewState {
2728
+ type: string;
2729
+ state?: Record<string, unknown>;
2730
+ active?: boolean;
2731
+ pinned?: boolean;
2732
+ }
2733
+ /**
2734
+ * Rendu par View.setState(). `history: true` dit au Workspace que ce changement
2735
+ * mérite une entrée dans l'historique de navigation de la feuille — le bouton
2736
+ * « précédent ». Charger un fichier, oui ; restaurer un layout au démarrage, non.
2737
+ */
2738
+ export interface ViewStateResult {
2739
+ history: boolean;
2740
+ }
2741
+ export interface WidgetHandle {
2742
+ /** L'ancre COURANTE — remappée par les éditions. La source de vérité. */
2743
+ getAnchor(): WidgetAnchor;
2744
+ setAnchor(anchor: WidgetAnchor): void;
2745
+ remove(): void;
2746
+ }
2747
+ export interface Window {
2748
+ electron: {
2749
+ vault: VaultBridge;
2750
+ vaults: VaultsBridge;
2751
+ windowControls: WindowControlsBridge;
2752
+ requests: RequestsBridge;
2753
+ cli: CliBridge;
2754
+ };
2755
+ }
2756
+ /**
2757
+ * Le document entier. `main` / `left` / `right` sont les trois racines
2758
+ * permanentes du Workspace — elles ne sont jamais créées ni détruites par la
2759
+ * restauration, seulement remplies.
2760
+ */
2761
+ export interface WorkspaceLayout {
2762
+ version: 1;
2763
+ main: SplitLayout;
2764
+ left: SplitLayout;
2765
+ right: SplitLayout;
2766
+ /** L'id de la feuille active, ou null. */
2767
+ active: string | null;
2768
+ }
2769
+ export type CliBridge = {
2770
+ [K in keyof IpcInvokeMapping as StripCliPrefix<K>]: IpcInvokeMapping[K];
2771
+ };
2772
+ /**
2773
+ * Ce qu'un handler reçoit : les `clé=valeur` de la ligne, plus chaque
2774
+ * `--drapeau` sous la forme `drapeau: 'true'`.
2775
+ */
2776
+ export type CliData = Record<string, string>;
2777
+ /**
2778
+ * Ce qu'un handler ACCEPTE, pour `help` : nom → description. Un nom qui se
2779
+ * termine par `=` attend une valeur (`id=`), sinon c'est un drapeau (`total`).
2780
+ * Purement documentaire — rien n'est validé contre.
2781
+ */
2782
+ export type CliFlags = Record<string, string>;
2783
+ /**
2784
+ * Le handler. Il REND sa sortie — il n'écrit pas dans stdout, il n'a pas de
2785
+ * stdout. Ce qu'il rend est imprimé tel quel par le client.
2786
+ */
2787
+ export type CliHandler = (params: CliData) => string | Promise<string>;
2788
+ /** Ce que le serveur répond. */
2789
+ export type CliResult = {
2790
+ ok: true;
2791
+ output: string;
2792
+ } | {
2793
+ ok: false;
2794
+ error: string;
2795
+ code: number;
2796
+ };
2797
+ /** §10.2 — ```lang → UI riche. */
2798
+ export type CodeBlockProcessor = (source: string, el: HTMLElement, ctx: unknown) => unknown;
2799
+ export type DragData = {
2800
+ type: "leaf";
2801
+ leaf: WorkspaceLeaf;
2802
+ } | {
2803
+ type: "file";
2804
+ file: TFile;
2805
+ };
2806
+ export type DropZone = "reorder" | "split-top" | "split-bottom" | "split-left" | "split-right" | "center";
2807
+ /** De quel côté du texte se trouve une marge. */
2808
+ export type GutterSide = "left" | "right";
2809
+ // ─── Canaux requête/réponse : ipcRenderer.invoke ↔ ipcMain.handle ───────────
2810
+ // Une entrée par méthode du DataAdapter qui a besoin du disque.
2811
+ // Noter les absents et POURQUOI :
2812
+ // - getName() : l'adapter répond tout seul, aucun aller-retour.
2813
+ // - getResourcePath() : SYNCHRONE (une URL, pas une Promise) → simple
2814
+ // concaténation avec le protocole custom côté renderer.
2815
+ // - process() : vit sur le Vault (une closure ne traverse pas l'IPC).
2816
+ export type IpcInvokeMapping = {
2817
+ /**
2818
+ * ★ LE SEUL CANAL `vault:` QUI RESTE.
2819
+ *
2820
+ * Les vingt autres (exists, stat, list, read, write, mkdir, rename…) ont
2821
+ * été supprimés : le coffre est lu et écrit par `fs` dans le renderer,
2822
+ * comme chez Obsidian. Le pourquoi — 1 627 allers-retours et 23 secondes
2823
+ * de gel du processus principal sur un vrai coffre — est en tête de
2824
+ * src/ui/core/vault/FileSystemAdapter.ts.
2825
+ *
2826
+ * Celui-ci survit parce que `shell.trashItem` est une API d'Electron et
2827
+ * non de Node : le renderer ne peut pas l'appeler, quelle que soit son
2828
+ * intégration Node. Le critère de ce qui reste ici n'est donc pas « est-ce
2829
+ * du disque ? » mais « est-ce que ça exige le processus principal ? ».
2830
+ */
2831
+ "vault:trashSystem": (path: string) => Promise<boolean>;
2832
+ // ─── Gestion des COFFRES ────────────────────────────────────────────
2833
+ //
2834
+ // Préfixe `vaults:` et non `vault:`, et la nuance n'est pas cosmétique :
2835
+ // `vault:` c'est le DISQUE du coffre ouvert (le DataAdapter), `vaults:`
2836
+ // c'est le choix du coffre lui-même. Deux sujets, deux préfixes — et deux
2837
+ // bridges distincts côté preload, dérivés séparément (voir plus bas).
2838
+ //
2839
+ // Ces canaux sont les SEULS enregistrés avant que le coffre soit connu :
2840
+ // la fenêtre de gestion les invoque alors qu'aucun `vault:` n'existe encore.
2841
+ "vaults:list": () => Promise<VaultInfo[]>;
2842
+ /** Mémorise et ouvre. Avant le boot : débloque le démarrage. Après : relance l'app. */
2843
+ "vaults:switch": (path: string) => Promise<void>;
2844
+ /** Dialogue natif « ouvrir un dossier comme coffre ». `null` si annulé. */
2845
+ "vaults:pickFolder": () => Promise<string | null>;
2846
+ /** Idem, mais crée le dossier au besoin — « créer un coffre ». */
2847
+ "vaults:create": () => Promise<string | null>;
2848
+ /** Retire de la liste. NE SUPPRIME RIEN sur le disque. */
2849
+ "vaults:forget": (path: string) => Promise<void>;
2850
+ /** Ouvre la fenêtre de gestion depuis l'app (« Gérer les coffres… »). */
2851
+ "vaults:openManager": () => Promise<void>;
2852
+ // ─── Fenêtre : frame: false → l'OS ne dessine plus min/max/close, le
2853
+ // renderer les dessine (WindowControls) et pilote par ces canaux. ────
2854
+ "window:minimize": () => Promise<void>;
2855
+ "window:toggleMaximize": () => Promise<void>;
2856
+ "window:close": () => Promise<void>;
2857
+ /** L'état initial au montage — ensuite, window:maximizedChanged pousse. */
2858
+ "window:isMaximized": () => Promise<boolean>;
2859
+ // ─── Le retour de la TROISIÈME forme (voir IpcRequestMapping) ──────
2860
+ /** Le renderer rend la réponse à une 'ipc:request', corrélée par son id. */
2861
+ "ipc:reply": (id: string, result: unknown) => Promise<void>;
2862
+ /** Le renderer annonce que boot() est passé et que 'cli:exec' est posé. */
2863
+ "cli:ready": () => Promise<void>;
2864
+ /**
2865
+ * `fragment eval` : le renderer demande au main d'évaluer `code` DANS le
2866
+ * renderer lui-même, par webContents.executeJavaScript — qui n'est pas
2867
+ * soumis à la CSP de la page (script-src sans 'unsafe-eval'), et qui rend
2868
+ * la valeur de complétion du script, promesses attendues, comme les
2869
+ * devtools. Le résultat est déjà une chaîne : la sérialisation se fait
2870
+ * côté main, avant le structured clone du retour.
2871
+ */
2872
+ "cli:eval": (code: string, timeoutMs: number) => Promise<CliResult>;
2873
+ /**
2874
+ * Écrit le shim `fragment` et met son dossier sur le PATH utilisateur
2875
+ * (Windows). Une API d'Electron et du système, donc ici — voir
2876
+ * src/electron/cli/installShim.ts.
2877
+ */
2878
+ "cli:installShim": () => Promise<{
2879
+ shim: string;
2880
+ pathModifie: boolean;
2881
+ message: string;
2882
+ }>;
2883
+ };
2884
+ /**
2885
+ * Un handler de frappe.
2886
+ *
2887
+ * ★ La valeur de retour porte TROIS sens distincts, et c'est le cœur de la
2888
+ * propagation :
2889
+ *
2890
+ * false « j'ai consommé la frappe » → preventDefault + stopPropagation
2891
+ * true « j'ai traité, mais laisse passer »
2892
+ * undefined « pas pour moi » → on continue la chaîne
2893
+ */
2894
+ export type KeymapEventListener = (evt: KeyboardEvent, ctx: KeymapContext) => boolean | void;
2895
+ export type LayoutNode = SplitLayout | TabsLayout | LeafLayout;
2896
+ /** §10.2 — transforme le HTML rendu en mode lecture. */
2897
+ export type MarkdownPostProcessor = (el: HTMLElement, ctx: unknown) => unknown;
2898
+ /**
2899
+ * Un modificateur.
2900
+ *
2901
+ * ★ `Mod` est le seul qu'on devrait écrire dans du code de commande : il
2902
+ * ABSTRAIT Cmd sur macOS et Ctrl partout ailleurs. Écrire `Ctrl` en dur, c'est
2903
+ * fabriquer un raccourci qui sera faux sur un Mac — et l'utilisateur mac ne
2904
+ * pourra que le personnaliser, pas le réparer.
2905
+ */
2906
+ export type Modifier = "Mod" | "Ctrl" | "Meta" | "Shift" | "Alt";
2907
+ /**
2908
+ * Ce que la vue tend au layer à sa création — le « contrat exposé par la vue ».
2909
+ * `editor` est la façade (le layer ne touche jamais CM6) ; `file` dit sur QUEL
2910
+ * document on place (filtrer la présence, les surlignages…).
2911
+ */
2912
+ export type OverlayPlane = "viewport" | "document";
2913
+ /**
2914
+ * Où ouvrir (§8.5). Keymap.isModEvent() convertira un jour l'état des touches
2915
+ * de modification en une de ces valeurs — une seule convention d'UX, appliquée
2916
+ * partout.
2917
+ */
2918
+ export type PaneType = "tab" | "split" | "window";
2919
+ export type Plateforme = "macOS" | "autre";
2920
+ export type PluginConstructor = new (app: App, manifest: PluginManifest) => Plugin$1;
2921
+ /** §15 — fragment://<action>?params */
2922
+ export type ProtocolHandler = (params: Record<string, string>) => unknown;
2923
+ /**
2924
+ * La quatrième famille du pont : recevoir des requêtes du main. Pas de
2925
+ * préfixe à retirer ici — la seule dérivation utile est celle de
2926
+ * ipcRendererHandle (core/cli/ipcRendererHandle.ts), qui contraint `key`
2927
+ * et `handler` par IpcRequestMapping. Le pont, lui, reste non typé par clef :
2928
+ * il ne fait que corréler.
2929
+ */
2930
+ export type RequestsBridge = {
2931
+ /** Pose un handler pour une clef ; rend son retrait. */
2932
+ handle: (key: string, handler: (...args: unknown[]) => Promise<unknown>) => unsubscribeFunction;
2933
+ };
2934
+ /** Les plages `[from, to)` du texte qui ont matché, fusionnées si adjacentes. */
2935
+ export type SearchMatches = Array<[
2936
+ number,
2937
+ number
2938
+ ]>;
2939
+ export type SplitDirection = "horizontal" | "vertical";
2940
+ /** 'cli:ready' → 'ready', 'cli:eval' → 'eval'. Même dérivation que les autres familles. */
2941
+ export type StripCliPrefix<K> = K extends `cli:${infer Rest}` ? Rest : never;
2942
+ // ═══════════════════════════════════════════════════════════════════════════
2943
+ // La surface exposée au renderer par le preload.
2944
+ //
2945
+ // Elle est DÉRIVÉE de IpcInvokeMapping plutôt que réécrite à la main : un type
2946
+ // mappé avec remappage de clefs (`as`) retire le préfixe "vault:". Ainsi
2947
+ // 'vault:read' devient window.electron.vault.read, et il est IMPOSSIBLE que
2948
+ // les deux listes divergent — ajouter un canal ajoute la méthode, et inversement.
2949
+ // ═══════════════════════════════════════════════════════════════════════════
2950
+ /** 'vault:read' → 'read' — type littéral de gabarit + `infer`. */
2951
+ export type StripVaultPrefix<K> = K extends `vault:${infer Rest}` ? Rest : never;
2952
+ /** 'vaults:switch' → 'switch'. Distinct de StripVaultPrefix : 'vaults:list'
2953
+ * ne matche PAS `vault:${…}` (après "vault" vient 's', pas ':'), donc les deux
2954
+ * familles se séparent toutes seules — aucune clef ne tombe dans les deux. */
2955
+ export type StripVaultsPrefix<K> = K extends `vaults:${infer Rest}` ? Rest : never;
2956
+ /** Même dérivation que VaultBridge, pour les canaux window:. Les clefs vault:
2957
+ * tombent sur `never` et disparaissent du type mappé — et réciproquement. */
2958
+ export type StripWindowPrefix<K> = K extends `window:${infer Rest}` ? Rest : never;
2959
+ /**
2960
+ * La barre d'outils flottante — le pendant PERSISTANT du menu contextuel
2961
+ * (core/Menu.ts). Même builder impératif, même DOM pur, même contrat de
2962
+ * démontage ; ce qui change est la DURÉE DE VIE et, elle seule, entraîne tout
2963
+ * le reste.
2964
+ *
2965
+ * ★ POURQUOI un composant du cœur et pas un widget de layer : une toolbar n'est
2966
+ * pas ancrée au document. Elle appartient au geste en cours, pas au texte —
2967
+ * exactement comme le menu. Un plugin la construit, la peuple et la montre ;
2968
+ * il ne connaît ni le DOM de la vue ni les plans d'overlay.
2969
+ *
2970
+ * ★ POURQUOI un PARENT au constructeur : une toolbar arme un mode DANS une vue,
2971
+ * donc elle appartient à cette vue. Montée dans le body — ce qu'elle faisait —
2972
+ * elle se bornait au viewport de la FENÊTRE : traînée par sa poignée, elle
2973
+ * sortait de son pane pour se poser sur le split voisin, sur la sidebar ou sur
2974
+ * la barre d'onglets. Le parent est son territoire : on l'y monte, et on l'y
2975
+ * borne. Le défaut `document.body` redonne exactement l'ancien comportement —
2976
+ * le body ne scrolle pas, un `absolute` s'y résout contre le viewport.
2977
+ *
2978
+ * Le confinement appartient à la barre, pas à l'appelant : `moveTo` prend un
2979
+ * point CLIENT — celui que rendent `getBoundingClientRect` et les événements
2980
+ * pointeur — et fait seule la conversion vers le repère du parent.
2981
+ *
2982
+ * ★ POURQUOI extends Component : tout ce que la toolbar acquiert (listeners
2983
+ * globaux, DOM) est enregistré et rejoué à hide(). Elle ne peut pas fuir —
2984
+ * même mécanique que le menu et les vues.
2985
+ *
2986
+ * CE QU'ELLE FAIT DIFFÉREMMENT DU MENU, et pourquoi :
2987
+ *
2988
+ * - Elle NE se ferme PAS au clic extérieur. Un menu est une question à
2989
+ * laquelle on répond une fois ; une toolbar arme un mode. Dessiner sur la
2990
+ * feuille EST un clic extérieur — fermer là-dessus rendrait l'outil
2991
+ * inutilisable.
2992
+ * - Ses items ont un ÉTAT : setActive() et les groupes exclusifs (setGroup).
2993
+ * Un item de menu déclenche puis meurt ; un item de toolbar dit « c'est le
2994
+ * crayon qui est armé », et le dit tant que c'est vrai.
2995
+ * - Pas de showAtMouseEvent : la position ne vient pas du curseur mais de
2996
+ * l'appelant, qui sait où est sa vue.
2997
+ *
2998
+ * Échap ferme (comme le menu). `blur` NE ferme PAS : perdre le focus fenêtre —
2999
+ * alt-tab, capture d'écran — ne doit pas désarmer l'outil.
3000
+ *
3001
+ * const tb = new Toolbar(view.contentEl);
3002
+ * tb.addItem((i) => i.setIcon('pencil').setTooltip('Crayon')
3003
+ * .setGroup('outil').setActive(true)
3004
+ * .onClick(() => armer('crayon')));
3005
+ * tb.showAtPosition(x, y);
3006
+ */
3007
+ /** Le sens dans lequel la barre range ses items. */
3008
+ export type ToolbarOrientation = "horizontal" | "vertical";
3009
+ export type VaultBridge = {
3010
+ [K in keyof IpcInvokeMapping as StripVaultPrefix<K>]: IpcInvokeMapping[K];
3011
+ } & {
3012
+ /** Le canal poussé (§6.4). Rend son désabonnement — cf. Component.register(). */
3013
+ onChanged: (callback: (type: VaultChangeType, path: string) => void) => unsubscribeFunction;
3014
+ };
3015
+ export type VaultChangeType = "create" | "modify" | "delete";
3016
+ export type VaultsBridge = {
3017
+ [K in keyof IpcInvokeMapping as StripVaultsPrefix<K>]: IpcInvokeMapping[K];
3018
+ };
3019
+ /**
3020
+ * §8.3 — ce que le viewRegistry stocke : une FABRIQUE, pas une instance.
3021
+ *
3022
+ * (Les interfaces provisoires ViewLike/LeafLike qui tenaient cette place ont
3023
+ * disparu à l'étape 4 : les vraies classes existent, on les importe.)
3024
+ */
3025
+ export type ViewCreator = (leaf: WorkspaceLeaf) => View;
3026
+ export type WidgetAnchor = {
3027
+ mode: "viewport";
3028
+ x: number;
3029
+ y: number;
3030
+ } | {
3031
+ mode: "document";
3032
+ pos: number;
3033
+ dx: number;
3034
+ dy: number;
3035
+ } | {
3036
+ mode: "gutter";
3037
+ side: GutterSide;
3038
+ pos: number;
3039
+ dy: number;
3040
+ };
3041
+ export type WindowControlsBridge = {
3042
+ [K in keyof IpcInvokeMapping as StripWindowPrefix<K>]: IpcInvokeMapping[K];
3043
+ } & {
3044
+ onMaximizedChanged: (callback: (maximized: boolean) => void) => unsubscribeFunction;
3045
+ };
3046
+ export type unsubscribeFunction = () => void;
3047
+
3048
+ export {
3049
+ Plugin$1 as Plugin,
3050
+ };
3051
+
3052
+ export {};
3053
+
3054
+ // ═══════════════════════════════════════════════════════════════════════════
3055
+ // Pied de page ambient, concaténé en fin de dist/index.d.ts par le build.
3056
+ //
3057
+ // Le package expose le contrat comme module `@usefragment/core`, mais l'auteur
3058
+ // d'un plugin importe depuis 'fragment' (ou les alias 'promethium'/'obsidian')
3059
+ // qu'il marque en external — l'hôte les résout à l'exécution (resoudreModuleHote).
3060
+ // Ces re-déclarations ambiantes typent ces trois specifiers côté consommateur,
3061
+ // qui a @usefragment/core installé. Il ajoute `"types": ["@usefragment/core"]` (ou
3062
+ // un `import '@usefragment/core';`) pour charger ces blocs.
3063
+ // ═══════════════════════════════════════════════════════════════════════════
3064
+
3065
+ declare module 'fragment' {
3066
+ export * from '@usefragment/core';
3067
+ }
3068
+
3069
+ declare module 'promethium' {
3070
+ export * from '@usefragment/core';
3071
+ }
3072
+
3073
+ declare module 'obsidian' {
3074
+ export * from '@usefragment/core';
3075
+ }