/* THIS IS A GENERATED/BUNDLED FILE BY ROLLUP if you want to view the source visit the plugins github repository */ 'use strict'; var obsidian = require('obsidian'); /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; const SAFE_DB_FLUSH_INTERVAL = 5000; const DEFAULT_SETTINGS = { dbFileName: '.obsidian/plugins/remember-cursor-position/cursor-positions.json', delayAfterFileOpening: 100, saveTimer: SAFE_DB_FLUSH_INTERVAL, }; class RememberCursorPosition extends obsidian.Plugin { constructor() { super(...arguments); this.loadedLeafIdList = []; this.loadingFile = false; } onload() { return __awaiter(this, void 0, void 0, function* () { yield this.loadSettings(); try { this.db = yield this.readDb(); this.lastSavedDb = yield this.readDb(); } catch (e) { console.error("Remember Cursor Position plugin can\'t read database: " + e); this.db = {}; this.lastSavedDb = {}; } this.addSettingTab(new SettingTab(this.app, this)); this.registerEvent(this.app.workspace.on('file-open', (file) => this.restoreEphemeralState())); this.registerEvent(this.app.workspace.on('quit', () => { this.writeDb(this.db); })); this.registerEvent(this.app.vault.on('rename', (file, oldPath) => this.renameFile(file, oldPath))); this.registerEvent(this.app.vault.on('delete', (file) => this.deleteFile(file))); //todo: replace by scroll and mouse cursor move events this.registerInterval(window.setInterval(() => this.checkEphemeralStateChanged(), 100)); this.registerInterval(window.setInterval(() => this.writeDb(this.db), this.settings.saveTimer)); this.restoreEphemeralState(); }); } renameFile(file, oldPath) { let newName = file.path; let oldName = oldPath; this.db[newName] = this.db[oldName]; delete this.db[oldName]; } deleteFile(file) { let fileName = file.path; delete this.db[fileName]; } checkEphemeralStateChanged() { var _a; let fileName = (_a = this.app.workspace.getActiveFile()) === null || _a === void 0 ? void 0 : _a.path; //waiting for load new file if (!fileName || !this.lastLoadedFileName || fileName != this.lastLoadedFileName || this.loadingFile) return; let st = this.getEphemeralState(); if (!this.lastEphemeralState) this.lastEphemeralState = st; if (!isNaN(st.scroll) && !this.isEphemeralStatesEquals(st, this.lastEphemeralState)) { this.saveEphemeralState(st); this.lastEphemeralState = st; } } isEphemeralStatesEquals(state1, state2) { if (state1.cursor && !state2.cursor) return false; if (!state1.cursor && state2.cursor) return false; if (state1.cursor) { if (state1.cursor.from.ch != state2.cursor.from.ch) return false; if (state1.cursor.from.line != state2.cursor.from.line) return false; if (state1.cursor.to.ch != state2.cursor.to.ch) return false; if (state1.cursor.to.line != state2.cursor.to.line) return false; } if (state1.scroll && !state2.scroll) return false; if (!state1.scroll && state2.scroll) return false; if (state1.scroll && state1.scroll != state2.scroll) return false; return true; } saveEphemeralState(st) { var _a; return __awaiter(this, void 0, void 0, function* () { let fileName = (_a = this.app.workspace.getActiveFile()) === null || _a === void 0 ? void 0 : _a.path; if (fileName && fileName == this.lastLoadedFileName) { //do not save if file changed or was not loaded this.db[fileName] = st; } }); } restoreEphemeralState() { var _a, _b, _c; return __awaiter(this, void 0, void 0, function* () { let fileName = (_a = this.app.workspace.getActiveFile()) === null || _a === void 0 ? void 0 : _a.path; if (fileName && this.loadingFile && this.lastLoadedFileName == fileName) //if already started loading return; let activeLeaf = this.app.workspace.getMostRecentLeaf(); if (activeLeaf && this.loadedLeafIdList.includes(activeLeaf.id + ':' + activeLeaf.getViewState().state.file)) return; this.loadedLeafIdList = []; this.app.workspace.iterateAllLeaves((leaf) => { if (leaf.getViewState().type === "markdown") { this.loadedLeafIdList.push(leaf.id + ':' + leaf.getViewState().state.file); } }); this.loadingFile = true; if (this.lastLoadedFileName != fileName) { this.lastEphemeralState = {}; this.lastLoadedFileName = fileName; let st; if (fileName) { st = this.db[fileName]; if (st) { //waiting for load note yield this.delay(this.settings.delayAfterFileOpening); let scroll; for (let i = 0; i < 20; i++) { scroll = (_c = (_b = this.app.workspace.getActiveViewOfType(obsidian.MarkdownView)) === null || _b === void 0 ? void 0 : _b.currentMode) === null || _c === void 0 ? void 0 : _c.getScroll(); if (scroll !== null) break; yield this.delay(10); } // TODO: if note opened by link like [link](note.md#header), do not scroll it yield this.delay(10); this.setEphemeralState(st); } } this.lastEphemeralState = st; } this.loadingFile = false; }); } readDb() { return __awaiter(this, void 0, void 0, function* () { let db = {}; if (yield this.app.vault.adapter.exists(this.settings.dbFileName)) { let data = yield this.app.vault.adapter.read(this.settings.dbFileName); db = JSON.parse(data); } return db; }); } writeDb(db) { return __awaiter(this, void 0, void 0, function* () { //create folder for db file if not exist let newParentFolder = this.settings.dbFileName.substring(0, this.settings.dbFileName.lastIndexOf("/")); if (!(yield this.app.vault.adapter.exists(newParentFolder))) this.app.vault.adapter.mkdir(newParentFolder); if (JSON.stringify(this.db) !== JSON.stringify(this.lastSavedDb)) { this.app.vault.adapter.write(this.settings.dbFileName, JSON.stringify(db)); this.lastSavedDb = JSON.parse(JSON.stringify(db)); } }); } getEphemeralState() { // let state: EphemeralState = this.app.workspace.getActiveViewOfType(MarkdownView)?.getEphemeralState(); //doesn't work properly var _a, _b, _c; let state = {}; state.scroll = Number((_c = (_b = (_a = this.app.workspace.getActiveViewOfType(obsidian.MarkdownView)) === null || _a === void 0 ? void 0 : _a.currentMode) === null || _b === void 0 ? void 0 : _b.getScroll()) === null || _c === void 0 ? void 0 : _c.toFixed(4)); let editor = this.getEditor(); if (editor) { let from = editor.getCursor("anchor"); let to = editor.getCursor("head"); if (from && to) { state.cursor = { from: { ch: from.ch, line: from.line }, to: { ch: to.ch, line: to.line } }; } } return state; } setEphemeralState(state) { const view = this.app.workspace.getActiveViewOfType(obsidian.MarkdownView); if (state.cursor) { let editor = this.getEditor(); if (editor) { editor.setSelection(state.cursor.from, state.cursor.to); } } if (view && state.scroll) { view.setEphemeralState(state); // view.previewMode.applyScroll(state.scroll); // view.sourceMode.applyScroll(state.scroll); } } getEditor() { var _a; return (_a = this.app.workspace.getActiveViewOfType(obsidian.MarkdownView)) === null || _a === void 0 ? void 0 : _a.editor; } loadSettings() { return __awaiter(this, void 0, void 0, function* () { let settings = Object.assign({}, DEFAULT_SETTINGS, yield this.loadData()); if ((settings === null || settings === void 0 ? void 0 : settings.saveTimer) < SAFE_DB_FLUSH_INTERVAL) { settings.saveTimer = SAFE_DB_FLUSH_INTERVAL; } this.settings = settings; }); } saveSettings() { return __awaiter(this, void 0, void 0, function* () { yield this.saveData(this.settings); }); } delay(ms) { return __awaiter(this, void 0, void 0, function* () { return new Promise(resolve => setTimeout(resolve, ms)); }); } } class SettingTab extends obsidian.PluginSettingTab { constructor(app, plugin) { super(app, plugin); this.plugin = plugin; } display() { let { containerEl } = this; containerEl.empty(); containerEl.createEl('h2', { text: 'Remember cursor position - Settings' }); new obsidian.Setting(containerEl) .setName('Data file name') .setDesc('Save positions to this file') .addText((text) => text .setPlaceholder('Example: cursor-positions.json') .setValue(this.plugin.settings.dbFileName) .onChange((value) => __awaiter(this, void 0, void 0, function* () { this.plugin.settings.dbFileName = value; yield this.plugin.saveSettings(); }))); new obsidian.Setting(containerEl) .setName('Delay after opening a new note') .setDesc("This plugin shouldn't scroll if you used a link to the note header like [link](note.md#header). If it did, then increase the delay until everything works. If you are not using links to page sections, set the delay to zero (slider to the left). Slider values: 0-300 ms (default value: 100 ms).") .addSlider((text) => text .setLimits(0, 300, 10) .setValue(this.plugin.settings.delayAfterFileOpening) .onChange((value) => __awaiter(this, void 0, void 0, function* () { this.plugin.settings.delayAfterFileOpening = value; yield this.plugin.saveSettings(); }))); new obsidian.Setting(containerEl) .setName('Delay between saving the cursor position to file') .setDesc("Useful for multi-device users. If you don't want to wait until closing Obsidian to the cursor position been saved.") .addSlider((text) => text .setLimits(SAFE_DB_FLUSH_INTERVAL, SAFE_DB_FLUSH_INTERVAL * 10, 10) .setValue(this.plugin.settings.saveTimer) .onChange((value) => __awaiter(this, void 0, void 0, function* () { this.plugin.settings.saveTimer = value; yield this.plugin.saveSettings(); }))); } } module.exports = RememberCursorPosition; /* nosourcemap */