@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.
- package/LICENSE +21 -0
- package/README.md +54 -0
- package/dist/index.d.ts +3075 -0
- package/package.json +48 -0
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|