comparison src/main.ts @ 21:815b93d13e0f

Improve typescript compliance
author Ludovic Chabant <ludovic@chabant.com>
date Fri, 18 Mar 2022 19:25:18 -0700
parents a50ef39473b6
children f7e0926c2500
comparison
equal deleted inserted replaced
20:18ff216ce0c4 21:815b93d13e0f
1 import { 1 import {
2 Editor, 2 App,
3 Editor,
3 MarkdownView, 4 MarkdownView,
4 OpenViewState, 5 OpenViewState,
5 Plugin, 6 Plugin,
6 View 7 TAbstractFile,
8 TFile,
9 View,
10 WorkspaceLeaf
7 } from 'obsidian'; 11 } from 'obsidian';
12
13 import {
14 EditorView
15 } from '@codemirror/view';
8 16
9 import { 17 import {
10 EditorState, 18 EditorState,
11 EditorSelection 19 EditorSelection
12 } from '@codemirror/state'; 20 } from '@codemirror/state';
19 DEFAULT_SETTINGS, 27 DEFAULT_SETTINGS,
20 RememberFileStatePluginSettings, 28 RememberFileStatePluginSettings,
21 RememberFileStatePluginSettingTab 29 RememberFileStatePluginSettingTab
22 } from './settings'; 30 } from './settings';
23 31
32 declare var app: App;
33
34 // Interface for CM6 editor view
35 interface EditorWithCM6 extends Editor {
36 cm: EditorView
37 };
38
39 // View with unique ID
40 interface ViewWithID extends View {
41 __uniqueId: number
42 };
43
44 // Scroll info interface
45 interface ScrollInfo {
46 top: number, left: number
47 };
48
49 interface StateData {
50 selection: EditorSelection,
51 scrollInfo: ScrollInfo
52 };
53
24 // Interface for a file state. 54 // Interface for a file state.
25 interface RememberedFileState { 55 interface RememberedFileState {
26 path: string; 56 path: string;
27 lastSavedTime: number; 57 lastSavedTime: number;
28 stateData: Object; 58 stateData: StateData;
29 } 59 }
30 60
31 // Interface for all currently remembered file states. 61 // Interface for all currently remembered file states.
32 interface RememberFileStatePluginData { 62 interface RememberFileStatePluginData {
33 rememberedFiles: Record<string, RememberedFileState>; 63 rememberedFiles: Record<string, RememberedFileState>;
46 private _suppressNextFileOpen: boolean = false; 76 private _suppressNextFileOpen: boolean = false;
47 // Next unique ID to identify views without keeping references to them. 77 // Next unique ID to identify views without keeping references to them.
48 private _nextUniqueViewId: number = 0; 78 private _nextUniqueViewId: number = 0;
49 79
50 // Functions to unregister any monkey-patched view hooks on plugin unload. 80 // Functions to unregister any monkey-patched view hooks on plugin unload.
51 private _viewUninstallers = {}; 81 private _viewUninstallers: Record<string, Function> = {};
52 // Functions to unregister any global callbacks on plugin unload. 82 // Functions to unregister any global callbacks on plugin unload.
53 private _globalUninstallers = []; 83 private _globalUninstallers: Function[] = [];
54 84
55 async onload() { 85 async onload() {
56 console.log("Loading RememberFileState plugin"); 86 console.log("Loading RememberFileState plugin");
57 87
58 await this.loadSettings(); 88 await this.loadSettings();
62 this.registerEvent(this.app.workspace.on('file-open', this.onFileOpen)); 92 this.registerEvent(this.app.workspace.on('file-open', this.onFileOpen));
63 this.registerEvent(this.app.vault.on('rename', this.onFileRename)); 93 this.registerEvent(this.app.vault.on('rename', this.onFileRename));
64 this.registerEvent(this.app.vault.on('delete', this.onFileDelete)); 94 this.registerEvent(this.app.vault.on('delete', this.onFileDelete));
65 95
66 this.app.workspace.getLeavesOfType("markdown").forEach( 96 this.app.workspace.getLeavesOfType("markdown").forEach(
67 (leaf) => { this.registerOnUnloadFile(leaf.view); }); 97 (leaf: WorkspaceLeaf) => { this.registerOnUnloadFile(leaf.view as MarkdownView); });
68 98
69 const _this = this; 99 const _this = this;
70 var uninstall = around(this.app.workspace, { 100 var uninstall = around(this.app.workspace, {
71 openLinkText: function(next) { 101 openLinkText: function(next) {
72 return async function( 102 return async function(
89 119
90 onunload() { 120 onunload() {
91 // Run view uninstallers on all current views. 121 // Run view uninstallers on all current views.
92 var numViews: number = 0; 122 var numViews: number = 0;
93 this.app.workspace.getLeavesOfType("markdown").forEach( 123 this.app.workspace.getLeavesOfType("markdown").forEach(
94 (leaf) => { 124 (leaf: WorkspaceLeaf) => {
95 const filePath = leaf.view.file.path; 125 const filePath = (leaf.view as MarkdownView).file.path;
96 const viewId = this.getUniqueViewId(leaf.view); 126 const viewId = this.getUniqueViewId(leaf.view as ViewWithID);
97 if (viewId != undefined) { 127 if (viewId != undefined) {
98 var uninstaller = this._viewUninstallers[viewId]; 128 var uninstaller = this._viewUninstallers[viewId];
99 if (uninstaller) { 129 if (uninstaller) {
100 console.debug(`Uninstalling hooks for view ${viewId}`, filePath); 130 console.debug(`Uninstalling hooks for view ${viewId}`, filePath);
101 uninstaller(leaf.view); 131 uninstaller(leaf.view);
103 } else { 133 } else {
104 console.debug("Found markdown view without an uninstaller!", filePath); 134 console.debug("Found markdown view without an uninstaller!", filePath);
105 } 135 }
106 // Clear the ID so we don't get confused if the plugin 136 // Clear the ID so we don't get confused if the plugin
107 // is re-enabled later. 137 // is re-enabled later.
108 this.clearUniqueViewId(leaf.view); 138 this.clearUniqueViewId(leaf.view as ViewWithID);
109 } else { 139 } else {
110 console.debug("Found markdown view without an ID!", filePath); 140 console.debug("Found markdown view without an ID!", filePath);
111 } 141 }
112 }); 142 });
113 console.debug(`Unregistered ${numViews} view callbacks`); 143 console.debug(`Unregistered ${numViews} view callbacks`);
123 153
124 async saveSettings() { 154 async saveSettings() {
125 await this.saveData(this.settings); 155 await this.saveData(this.settings);
126 } 156 }
127 157
128 private readonly registerOnUnloadFile = function(view) { 158 private readonly registerOnUnloadFile = function(view: MarkdownView) {
129 var filePath = view.file.path; 159 var filePath = view.file.path;
130 var viewId = this.getUniqueViewId(view, true); 160 var viewId = this.getUniqueViewId(view as unknown as ViewWithID, true);
131 if (viewId in this._viewUninstallers) { 161 if (viewId in this._viewUninstallers) {
132 console.debug(`View ${viewId} is already registered`, filePath); 162 console.debug(`View ${viewId} is already registered`, filePath);
133 return; 163 return;
134 } 164 }
135 165
147 177
148 view.register(() => { 178 view.register(() => {
149 // Don't hold a reference to this plugin here because this callback 179 // Don't hold a reference to this plugin here because this callback
150 // will outlive it if it gets deactivated. So let's find it, and 180 // will outlive it if it gets deactivated. So let's find it, and
151 // do nothing if we don't find it. 181 // do nothing if we don't find it.
152 var plugin = app.plugins.getPlugin("obsidian-remember-file-state"); 182 // @ts-ignore
183 var plugin: RememberFileStatePlugin = app.plugins.getPlugin("obsidian-remember-file-state");
153 if (plugin) { 184 if (plugin) {
154 console.debug(`Unregistering view ${viewId} callback`, filePath); 185 console.debug(`Unregistering view ${viewId} callback`, filePath);
155 delete plugin._viewUninstallers[viewId]; 186 delete plugin._viewUninstallers[viewId];
156 uninstall(); 187 uninstall();
157 } else { 188 } else {
165 openedFile: TFile 196 openedFile: TFile
166 ): Promise<void> => { 197 ): Promise<void> => {
167 // If `openedFile` is null, it's because the last pane was closed 198 // If `openedFile` is null, it's because the last pane was closed
168 // and there is now an empty pane. 199 // and there is now an empty pane.
169 if (openedFile) { 200 if (openedFile) {
170 var activeView = this.app.workspace.getActiveViewOfType(MarkdownView); 201 var activeView: MarkdownView = this.app.workspace.getActiveViewOfType(MarkdownView);
171 if (activeView) { 202 if (activeView) {
172 this.registerOnUnloadFile(activeView); 203 this.registerOnUnloadFile(activeView);
173 204
174 if (!this._suppressNextFileOpen) { 205 if (!this._suppressNextFileOpen) {
175 this.restoreFileState(openedFile, activeView); 206 this.restoreFileState(openedFile, activeView);
179 210
180 this._suppressNextFileOpen = false; 211 this._suppressNextFileOpen = false;
181 } 212 }
182 } 213 }
183 214
184 private readonly rememberFileState = async (file: TFile, view: View): Promise<void> => { 215 private readonly rememberFileState = async (file: TFile, view: MarkdownView): Promise<void> => {
185 const scrollInfo = view.editor.getScrollInfo(); 216 const scrollInfo = view.editor.getScrollInfo();
186 const stateSelection = view.editor.cm.state.selection; 217 const cm6editor = view.editor as EditorWithCM6;
218 const stateSelection: EditorSelection = cm6editor.cm.state.selection;
187 if (stateSelection == undefined) { 219 if (stateSelection == undefined) {
188 // Legacy editor is in use, let's ignore 220 // Legacy editor is in use, let's ignore
189 return; 221 return;
190 } 222 }
191 const stateSelectionJSON = stateSelection.toJSON(); 223 const stateSelectionJSON = stateSelection.toJSON();
208 this.forgetExcessFiles(); 240 this.forgetExcessFiles();
209 } 241 }
210 console.debug("Remember file state for:", file.path); 242 console.debug("Remember file state for:", file.path);
211 } 243 }
212 244
213 private readonly restoreFileState = function(file: TFile, view: View) { 245 private readonly restoreFileState = function(file: TFile, view: MarkdownView) {
214 const existingFile = this.data.rememberedFiles[file.path]; 246 const existingFile = this.data.rememberedFiles[file.path];
215 if (existingFile) { 247 if (existingFile) {
216 console.debug("Restoring file state for:", file.path); 248 console.debug("Restoring file state for:", file.path);
217 const stateData = existingFile.stateData; 249 const stateData = existingFile.stateData;
218 view.editor.scrollTo(stateData.scrollInfo.left, stateData.scrollInfo.top); 250 view.editor.scrollTo(stateData.scrollInfo.left, stateData.scrollInfo.top);
219 var transaction = view.editor.cm.state.update({ 251 const cm6editor = view.editor as EditorWithCM6;
252 var transaction = cm6editor.cm.state.update({
220 selection: EditorSelection.fromJSON(stateData.selection)}) 253 selection: EditorSelection.fromJSON(stateData.selection)})
221 view.editor.cm.dispatch(transaction); 254
255 cm6editor.cm.dispatch(transaction);
222 } 256 }
223 } 257 }
224 258
225 private readonly forgetExcessFiles = function() { 259 private readonly forgetExcessFiles = function() {
226 const keepMax = this.settings.rememberMaxFiles; 260 const keepMax = this.settings.rememberMaxFiles;
227 if (keepMax <= 0) { 261 if (keepMax <= 0) {
228 return; 262 return;
229 } 263 }
230 264
231 // Sort newer files first, older files last. 265 // Sort newer files first, older files last.
232 var filesData = Object.values(this.data.rememberedFiles); 266 var filesData: RememberedFileState[] = Object.values(this.data.rememberedFiles);
233 filesData.sort((a, b) => { 267 filesData.sort((a, b) => {
234 if (a.lastSavedTime > b.lastSavedTime) return -1; // a before b 268 if (a.lastSavedTime > b.lastSavedTime) return -1; // a before b
235 if (a.lastSavedTime < b.lastSavedTime) return 1; // b before a 269 if (a.lastSavedTime < b.lastSavedTime) return 1; // b before a
236 return 0; 270 return 0;
237 }); 271 });
241 var fileData = filesData[i]; 275 var fileData = filesData[i];
242 delete this.data.rememberedFiles[fileData.path]; 276 delete this.data.rememberedFiles[fileData.path];
243 } 277 }
244 } 278 }
245 279
246 private readonly getUniqueViewId = function(view: View, autocreateId: boolean = false) { 280 private readonly getUniqueViewId = function(view: ViewWithID, autocreateId: boolean = false) {
247 if (view.__uniqueId == undefined) { 281 if (view.__uniqueId == undefined) {
248 if (!autocreateId) { 282 if (!autocreateId) {
249 return -1; 283 return -1;
250 } 284 }
251 view.__uniqueId = (this._nextUniqueViewId++); 285 view.__uniqueId = (this._nextUniqueViewId++);
252 return view.__uniqueId; 286 return view.__uniqueId;
253 } 287 }
254 return view.__uniqueId; 288 return view.__uniqueId;
255 } 289 }
256 290
257 private readonly clearUniqueViewId = function(view: View) { 291 private readonly clearUniqueViewId = function(view: ViewWithID) {
258 delete view["__uniqueId"]; 292 delete view["__uniqueId"];
259 } 293 }
260 294
261 private readonly onFileRename = async ( 295 private readonly onFileRename = async (
262 file: TAbstractFile, 296 file: TAbstractFile,
263 oldPath: string, 297 oldPath: string,
264 ): Promise<void> => { 298 ): Promise<void> => {
265 const existingFile = this.data.rememberedFiles[oldPath]; 299 const existingFile: RememberedFileState = this.data.rememberedFiles[oldPath];
266 if (existingFile) { 300 if (existingFile) {
267 existingFile.path = file.path; 301 existingFile.path = file.path;
268 delete this.data.rememberedFiles[oldPath]; 302 delete this.data.rememberedFiles[oldPath];
269 this.data.rememberedFiles[file.path] = existingFile; 303 this.data.rememberedFiles[file.path] = existingFile;
270 } 304 }