├── .gitignore ├── LICENSE ├── README.md ├── main.ts ├── manifest.json ├── package.json ├── rollup.config.js ├── tsconfig.json └── versions.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Intellij 2 | *.iml 3 | .idea 4 | 5 | # npm 6 | node_modules 7 | package-lock.json 8 | 9 | # build 10 | main.js 11 | *.js.map 12 | 13 | # obsidian 14 | data.json 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Till Hoffmann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Obsidian AutoView Plugin 2 | 3 | This is a plugin for Obsidian (https://obsidian.md). 4 | 5 | This plugin will auto-switch the active window to edit mode when a keystroke is detected, and then switch back to preview mode a delay after the last keystroke is detected. This quick switching allows for a kind of WYSIWYG. 6 | 7 | ## Local Installation 8 | The codebase is written in TypeScript and uses `rollup` / `node` for compilation; for a first time set up, all you 9 | should need to do is pull, install, and build: 10 | 11 | ```console 12 | foo@bar:~$ git clone https://github.com/mmhobi7/obsidian-autoview.git 13 | foo@bar:~$ cd obsidian-autoview 14 | foo@bar:~/obsidian-autoview$ npm install 15 | foo@bar:~/obsidian-autoview$ npm run dev 16 | ``` 17 | 18 | ## Settings 19 | 20 | * Timeout Duration: The amount of time in seconds that the plugin waits after the last keystroke before swtiching over to preview mode. 21 | * Scroll back to previous editing location: Whether the editor should scroll back to it's previous location when switching from edit to preview mode (verus just auto scrolling to where editing is happening) 22 | -------------------------------------------------------------------------------- /main.ts: -------------------------------------------------------------------------------- 1 | import {App, MarkdownView, Plugin, PluginSettingTab, Setting} from 'obsidian'; 2 | 3 | interface MyPluginSettings { 4 | timoutduration: number; 5 | rememberscroll: boolean; 6 | } 7 | 8 | const DEFAULT_SETTINGS: MyPluginSettings = { 9 | timoutduration: 10, 10 | rememberscroll: true 11 | } 12 | 13 | // https://raw.githubusercontent.com/derwish-pro/obsidian-remember-cursor-position/master/main.ts 14 | interface EphemeralState { 15 | cursor?: {from: {ch: number 16 | line: number 17 | }, 18 | to: { 19 | ch: number 20 | line: number 21 | } 22 | }, 23 | scroll?: number 24 | } 25 | 26 | export default class MyPlugin extends Plugin { 27 | settings: MyPluginSettings; 28 | db: {[file_path: string]: EphemeralState;}; 29 | lastTime: number; 30 | 31 | async onload() { 32 | console.log('loading plugin autoview'); 33 | this.lastTime = 1; 34 | this.db = {}; 35 | 36 | await this.loadSettings(); 37 | 38 | this.addSettingTab(new SampleSettingTab(this.app, this)); 39 | 40 | this.registerCodeMirror((cm: CodeMirror.Editor) => { 41 | console.log('codemirror', cm); 42 | }); 43 | 44 | this.registerDomEvent( 45 | document, 'keydown', 46 | (evt: KeyboardEvent) => this.modeHandler(null, evt)); 47 | 48 | this.registerDomEvent( 49 | document, 'mousedown', 50 | this.modeHandler); // TODO: find better way to find interaction with 51 | // editor 52 | } 53 | 54 | modeHandler(mouseevt?: MouseEvent, keyevt?: KeyboardEvent) { 55 | var markdownLeaves2 = this.app.workspace.getActiveViewOfType(MarkdownView); 56 | if (markdownLeaves2.getMode() == 'preview') { 57 | var curState = markdownLeaves2.getState(); 58 | curState.mode = 'source'; 59 | markdownLeaves2.setState(curState, theresult); 60 | this.restoreEphemeralState(); 61 | } 62 | this.lastTime++; 63 | this.backtopreview(this.lastTime, markdownLeaves2); 64 | } 65 | 66 | checkEphemeralStateChanged() { 67 | let fileName = this.app.workspace.getActiveFile()?.path; 68 | 69 | // waiting for load new file 70 | if (!fileName) return; 71 | 72 | let st = this.getEphemeralState(); 73 | 74 | this.saveEphemeralState(st); 75 | } 76 | 77 | getEphemeralState(): EphemeralState { 78 | // let state: EphemeralState = 79 | // this.app.workspace.getActiveViewOfType(MarkdownView)?.getEphemeralState(); 80 | // //doesnt work properly 81 | 82 | let state: EphemeralState = {}; 83 | state.scroll = Number(this.app.workspace.getActiveViewOfType(MarkdownView) 84 | ?.currentMode?.getScroll() 85 | ?.toFixed(4)); 86 | 87 | let editor = this.getEditor(); 88 | if (editor) { 89 | let from = editor.getCursor('anchor'); 90 | let to = editor.getCursor('head'); 91 | if (from && to) { 92 | state.cursor = { 93 | from: {ch: from.ch, line: from.line}, 94 | to: {ch: to.ch, line: to.line} 95 | } 96 | } 97 | } 98 | 99 | return state; 100 | } 101 | 102 | private getEditor(): CodeMirror.Editor { 103 | return this.app.workspace.getActiveViewOfType(MarkdownView) 104 | ?.sourceMode.cmEditor; 105 | } 106 | 107 | async saveEphemeralState(st: EphemeralState) { 108 | let fileName = this.app.workspace.getActiveFile()?.path; 109 | this.db[fileName] = st; 110 | } 111 | 112 | async backtopreview(a: number, markdownLeave: MarkdownView) { 113 | await this.delay(this.settings.timoutduration * 1000); 114 | if (a == this.lastTime) { 115 | if (markdownLeave.getMode() == 'source') { 116 | this.checkEphemeralStateChanged(); 117 | var curState = markdownLeave.getState(); 118 | curState.mode = 'preview'; 119 | markdownLeave.setState(curState, theresult); 120 | } 121 | } 122 | } 123 | 124 | onunload() { 125 | console.log('unloading plugin'); 126 | } 127 | 128 | setEphemeralState(state: EphemeralState) { 129 | const view = this.app.workspace.getActiveViewOfType(MarkdownView); 130 | 131 | if (state.cursor) { 132 | let editor = this.getEditor(); 133 | if (this.settings.rememberscroll) { 134 | if (editor) { 135 | editor.setSelection( 136 | state.cursor.from, state.cursor.to, {scroll: false}); 137 | editor.focus(); 138 | } 139 | if (view && state.scroll) { 140 | view.sourceMode.applyScroll(state.scroll); 141 | } 142 | } else { 143 | if (editor) { 144 | editor.setSelection( 145 | state.cursor.from, state.cursor.to, {scroll: true}); 146 | editor.focus(); 147 | } 148 | } 149 | } 150 | } 151 | async loadSettings() { 152 | this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); 153 | } 154 | 155 | async saveSettings() { 156 | await this.saveData(this.settings); 157 | } 158 | 159 | async delay(ms: number) { 160 | if (ms > 10) { 161 | return new Promise(resolve => setTimeout(resolve, ms)); 162 | } 163 | } 164 | 165 | async restoreEphemeralState() { 166 | let fileName = this.app.workspace.getActiveFile()?.path; 167 | 168 | if (fileName) { 169 | let st = this.db[fileName]; 170 | if (st) { 171 | // waiting for load note 172 | let scroll: number; 173 | for (let i = 0; i < 20; i++) { 174 | scroll = this.app.workspace.getActiveViewOfType(MarkdownView) 175 | ?.currentMode?.getScroll(); 176 | if (scroll !== null) break; 177 | await this.delay(10); 178 | } 179 | this.setEphemeralState(st); 180 | } 181 | } 182 | } 183 | } 184 | 185 | class SampleSettingTab extends PluginSettingTab { 186 | plugin: MyPlugin; 187 | 188 | constructor(app: App, plugin: MyPlugin) { 189 | super(app, plugin); 190 | this.plugin = plugin; 191 | } 192 | 193 | display(): void { 194 | let {containerEl} = this; 195 | 196 | containerEl.empty(); 197 | 198 | containerEl.createEl('h2', {text: 'Settings for AutoView.'}); 199 | 200 | new Setting(containerEl) 201 | .setName('Timeout Duration') 202 | .setDesc( 203 | 'The amount of time in seconds that the plugin waits after the last keystroke before switching over to preview mode. ') 204 | .addText( 205 | text => 206 | text.setPlaceholder('10') 207 | .setValue(this.plugin.settings.timoutduration.toString()) 208 | .onChange(async (value) => { 209 | if (Number(value) > 0) { 210 | this.plugin.settings.timoutduration = Number(value); 211 | await this.plugin.saveSettings(); 212 | } else { 213 | this.plugin.settings.timoutduration = 10; 214 | text.setValue(''); 215 | await this.plugin.saveSettings(); 216 | } 217 | })); 218 | 219 | new Setting(containerEl) 220 | .setName('Scroll back to previous editing location') 221 | .setDesc( 222 | 'Should the editor scroll back to it\'s previous location when switching from edit to preview mode? ') 223 | .addToggle( 224 | toggle => toggle.setValue(this.plugin.settings.rememberscroll) 225 | .onChange(async (value) => { 226 | this.plugin.settings.rememberscroll = value; 227 | await this.plugin.saveSettings(); 228 | })); 229 | } 230 | } 231 | function theresult(curState: any, tresult: any) { 232 | // console.log(tresult); 233 | } 234 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "obsidian-autoview", 3 | "name": "AutoView", 4 | "version": "1.0.2", 5 | "minAppVersion": "0.9.12", 6 | "description": "This plugin automatically switches the view from edit to preview after a timeout. Then, upon keystroke, switches back to edit mode. This is to allow seemless transition in a sort of WYSIWYG. ", 7 | "author": "mmhobi7", 8 | "authorUrl": "https://github.com/mmhobi7/obsidian-autoview", 9 | "isDesktopOnly": true 10 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obsidian-autoview", 3 | "version": "1.0.2", 4 | "description": "This plugin automatically switches the view from edit to preview after a timeout. Then, upon keystroke, switches back to edit mode. This is to allow seemless transition in a sort of WYSIWYG.", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "rollup --config rollup.config.js -w", 8 | "build": "rollup --config rollup.config.js --environment BUILD:production" 9 | }, 10 | "keywords": [], 11 | "author": "Muhamed Hobi", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "@rollup/plugin-commonjs": "^18.0.0", 15 | "@rollup/plugin-node-resolve": "^11.2.1", 16 | "@rollup/plugin-typescript": "^8.2.1", 17 | "@types/node": "^14.14.37", 18 | "obsidian": "^0.12.0", 19 | "rollup": "^2.32.1", 20 | "tslib": "^2.2.0", 21 | "typescript": "^4.2.4" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from '@rollup/plugin-typescript'; 2 | import {nodeResolve} from '@rollup/plugin-node-resolve'; 3 | import commonjs from '@rollup/plugin-commonjs'; 4 | 5 | const isProd = (process.env.BUILD === 'production'); 6 | 7 | const banner = 8 | `/* 9 | THIS IS A GENERATED/BUNDLED FILE BY ROLLUP 10 | if you want to view the source visit the plugins github repository 11 | */ 12 | `; 13 | 14 | export default { 15 | input: 'main.ts', 16 | output: { 17 | dir: '.', 18 | sourcemap: 'inline', 19 | sourcemapExcludeSources: isProd, 20 | format: 'cjs', 21 | exports: 'default', 22 | banner, 23 | }, 24 | external: ['obsidian'], 25 | plugins: [ 26 | typescript(), 27 | nodeResolve({browser: true}), 28 | commonjs(), 29 | ] 30 | }; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "inlineSourceMap": true, 5 | "inlineSources": true, 6 | "module": "ESNext", 7 | "target": "es6", 8 | "allowJs": true, 9 | "noImplicitAny": true, 10 | "moduleResolution": "node", 11 | "importHelpers": true, 12 | "lib": [ 13 | "dom", 14 | "es5", 15 | "scripthost", 16 | "es2015" 17 | ] 18 | }, 19 | "include": [ 20 | "**/*.ts" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.0.0": "0.9.7", 3 | "1.0.1": "0.9.12", 4 | "1.0.2": "0.9.12" 5 | } --------------------------------------------------------------------------------