diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/LICENSE b/LICENSE new file mode 100755 index 0000000..00d2e13 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to \ No newline at end of file diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 9e4fe9c..ac372eb --- a/README.md +++ b/README.md @@ -1,57 +1,44 @@ -## Obsidian Sample Plugin +# ObsidianTweaks -This is a sample plugin for Obsidian (https://obsidian.md). +This is a small plugin that implements some features of Obsidian that I believe +should've been (perhaps in the future they will be? ❤️). -This project uses Typescript to provide type checking and documentation. -The repo depends on the latest plugin API (obsidian.d.ts) in Typescript Definition format, which contains TSDoc comments describing what it does. +## Features -**Note:** The Obsidian API is still in early alpha and is subject to change at any time! +Below is a list of the current functionality that has been implemented. -This sample plugin demonstrates some of the basic functionality the plugin API can do. -- Changes the default font color to red using `styles.css`. -- Adds a ribbon icon, which shows a Notice when clicked. -- Adds a command "Open Sample Modal" which opens a Modal. -- Adds a plugin setting tab to the settings page. -- Registers a global click event and output 'click' to the console. -- Registers a global interval which logs 'setInterval' to the console. +Note that hotkeys cannot be 'disabled', but they can be set to Blank. +They are all set to blank by default. -### First time developing plugins? +### Hotkeys +| Recommended hotkey | Action | +| --- | --- | +| Shift + Alt + | Copy Current Line(s) Down | +| Shift + Alt + | Copy Current Line(s) Up | +| Ctrl + Shift + A | Select Current Line(s) | +| Ctrl + Alt + 1 | Toggle Heading - H1 | +| Ctrl + Alt + 2 | Toggle Heading - H2 | +| Ctrl + Alt + 3 | Toggle Heading - H3 | +| Ctrl + Alt + 4 | Toggle Heading - H4 | +| Ctrl + Alt + 5 | Toggle Heading - H5 | +| Ctrl + Alt + 6 | Toggle Heading - H6 | -Quick starting guide for new plugin devs: +## Philosophy -- Make a copy of this repo as a template with the "Use this template" button (login to GitHub if you don't see it). -- Clone your repo to a local development folder. For convenience, you can place this folder in your `.obsidian/plugins/your-plugin-name` folder. -- Install NodeJS, then run `npm i` in the command line under your repo folder. -- Run `npm run dev` to compile your plugin from `main.ts` to `main.js`. -- Make changes to `main.ts` (or create new `.ts` files). Those changes should be automatically compiled into `main.js`. -- Reload Obsidian to load the new version of your plugin. -- Enable plugin in settings window. -- For updates to the Obsidian API run `npm update` in the command line under your repo folder. +- By default no features are enabled. You can enable the features you want. + - Thus all features can also be disabled. +- All code is released into public domain without any requirement for attribution using the [Unlicense](https://unlicense.org/). -### Releasing new releases +## Why Unlicense? -- Update your `manifest.json` with your new version number, such as `1.0.1`, and the minimum Obsidian version required for your latest release. -- Update your `versions.json` file with `"new-plugin-version": "minimum-obsidian-version"` so older versions of Obsidian can download an older version of your plugin that's compatible. -- Create new GitHub release using your new version number as the "Tag version". Use the exact version number, don't include a prefix `v`. See here for an example: https://github.com/obsidianmd/obsidian-sample-plugin/releases -- Upload the files `manifest.json`, `main.js`, `styles.css` as binary attachments. -- Publish the release. +In case the developers of Obsidian want to just straight copy some code into Obsidian, they can do that without worrying about licensing at all. -### Adding your plugin to the community plugin list +**Note that any contributions to ObsidianTweaks will thus also be Unlicensed.** -- Publish an initial version. -- Make sure you have a `README.md` file in the root of your repo. -- Make a pull request at https://github.com/obsidianmd/obsidian-releases to add your plugin. +## Honorable Mentions -### How to use +I initally planned to contribute to the [Hotkeys++](https://github.com/argenos/hotkeysplus-obsidian) plugin, but since I don't necessarily want ObsidianTweaks to be limited to hotkeys in the future, I decided to make my own. -- Clone this repo. -- `npm i` or `yarn` to install dependencies -- `npm run dev` to start compilation in watch mode. +## Contributors -### Manually installing the plugin - -- Copy over `main.js`, `styles.css`, `manifest.json` to your vault `VaultFolder/.obsidian/plugins/your-plugin-id/`. - -### API Documentation - -See https://github.com/obsidianmd/obsidian-api +- [Jeppe Klitgaard](https://github.com/JeppeKlitgaard) \ No newline at end of file diff --git a/main.ts b/main.ts deleted file mode 100644 index eeb3dda..0000000 --- a/main.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { App, Modal, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian'; - -interface MyPluginSettings { - mySetting: string; -} - -const DEFAULT_SETTINGS: MyPluginSettings = { - mySetting: 'default' -} - -export default class MyPlugin extends Plugin { - settings: MyPluginSettings; - - async onload() { - console.log('loading plugin'); - - await this.loadSettings(); - - this.addRibbonIcon('dice', 'Sample Plugin', () => { - new Notice('This is a notice!'); - }); - - this.addStatusBarItem().setText('Status Bar Text'); - - this.addCommand({ - id: 'open-sample-modal', - name: 'Open Sample Modal', - // callback: () => { - // console.log('Simple Callback'); - // }, - checkCallback: (checking: boolean) => { - let leaf = this.app.workspace.activeLeaf; - if (leaf) { - if (!checking) { - new SampleModal(this.app).open(); - } - return true; - } - return false; - } - }); - - this.addSettingTab(new SampleSettingTab(this.app, this)); - - this.registerCodeMirror((cm: CodeMirror.Editor) => { - console.log('codemirror', cm); - }); - - this.registerDomEvent(document, 'click', (evt: MouseEvent) => { - console.log('click', evt); - }); - - this.registerInterval(window.setInterval(() => console.log('setInterval'), 5 * 60 * 1000)); - } - - onunload() { - console.log('unloading plugin'); - } - - async loadSettings() { - this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); - } - - async saveSettings() { - await this.saveData(this.settings); - } -} - -class SampleModal extends Modal { - constructor(app: App) { - super(app); - } - - onOpen() { - let {contentEl} = this; - contentEl.setText('Woah!'); - } - - onClose() { - let {contentEl} = this; - contentEl.empty(); - } -} - -class SampleSettingTab extends PluginSettingTab { - plugin: MyPlugin; - - constructor(app: App, plugin: MyPlugin) { - super(app, plugin); - this.plugin = plugin; - } - - display(): void { - let {containerEl} = this; - - containerEl.empty(); - - containerEl.createEl('h2', {text: 'Settings for my awesome plugin.'}); - - new Setting(containerEl) - .setName('Setting #1') - .setDesc('It\'s a secret') - .addText(text => text - .setPlaceholder('Enter your secret') - .setValue('') - .onChange(async (value) => { - console.log('Secret: ' + value); - this.plugin.settings.mySetting = value; - await this.plugin.saveSettings(); - })); - } -} diff --git a/manifest.json b/manifest.json old mode 100644 new mode 100755 index 4ca4889..4be0f43 --- a/manifest.json +++ b/manifest.json @@ -1,10 +1,10 @@ { - "id": "obsidian-sample-plugin", - "name": "Sample Plugin", - "version": "1.0.1", - "minAppVersion": "0.9.12", - "description": "This is a sample plugin for Obsidian. This plugin demonstrates some of the capabilities of the Obsidian API.", - "author": "Obsidian", - "authorUrl": "https://obsidian.md/about", + "id": "obsidian-tweaks", + "name": "Obsidian Tweaks", + "version": "0.1.0", + "minAppVersion": "0.12.3", + "description": "A small plugin that implements some sorely missed features.", + "author": "Jeppe Klitgaard", + "authorUrl": "jeppe.science", "isDesktopOnly": false } diff --git a/package.json b/package.json old mode 100644 new mode 100755 index 29e2406..df3012d --- a/package.json +++ b/package.json @@ -1,15 +1,15 @@ { - "name": "obsidian-sample-plugin", - "version": "0.12.0", - "description": "This is a sample plugin for Obsidian (https://obsidian.md)", + "name": "obsidian-tweaks", + "version": "0.1.0", + "description": "A small plugin that implements some sorely missed features.", "main": "main.js", "scripts": { "dev": "rollup --config rollup.config.js -w", "build": "rollup --config rollup.config.js --environment BUILD:production" }, - "keywords": [], - "author": "", - "license": "MIT", + "keywords": ["obsidian"], + "author": "Jeppe Klitgaard", + "license": "Unlicense", "devDependencies": { "@rollup/plugin-commonjs": "^18.0.0", "@rollup/plugin-node-resolve": "^11.2.1", diff --git a/rollup.config.js b/rollup.config.js old mode 100644 new mode 100755 index dd4d041..851225e --- a/rollup.config.js +++ b/rollup.config.js @@ -4,7 +4,7 @@ import commonjs from '@rollup/plugin-commonjs'; const isProd = (process.env.BUILD === 'production'); -const banner = +const banner = `/* THIS IS A GENERATED/BUNDLED FILE BY ROLLUP if you want to view the source visit the plugins github repository @@ -12,7 +12,7 @@ if you want to view the source visit the plugins github repository `; export default { - input: 'main.ts', + input: 'src/main.ts', output: { dir: '.', sourcemap: 'inline', diff --git a/src/DirectionalCopy.ts b/src/DirectionalCopy.ts new file mode 100755 index 0000000..b08f428 --- /dev/null +++ b/src/DirectionalCopy.ts @@ -0,0 +1,65 @@ +import { App, EditableFileView, Editor, MarkdownView } from "obsidian"; +import ObsidianTweaksPlugin from "./main"; + +export enum Direction { + Up, + Down, +} + +export class DirectionalCopy { + public app: App; + private plugin: ObsidianTweaksPlugin; + + constructor(app: App, plugin: ObsidianTweaksPlugin) { + this.app = app; + this.plugin = plugin; + } + + directionalCopy(direction: Direction): void { + const activeView = this.app.workspace.getActiveViewOfType(MarkdownView); + if (!activeView) { + return; + } + + var anchor = activeView.editor.getCursor("from"); + var head = activeView.editor.getCursor("to"); + + var headLength = activeView.editor.getLine(head.line).length; + + var textToCopy = activeView.editor.getRange( + {line: anchor.line, ch: 0}, + {line: head.line, ch: headLength} + ) + + // Copy up + if (direction === Direction.Up) { + activeView.editor.replaceRange( + textToCopy + '\n', + {line: anchor.line, ch: 0} + ); + activeView.editor.setSelection( + {line: anchor.line, ch: anchor.ch}, + {line: head.line, ch: head.ch}, + ); + } + // Copy down + else if (direction === Direction.Down) { + var addedLines = head.line - anchor.line; + console.log(addedLines); + + activeView.editor.replaceRange( + '\n' + textToCopy, + {line: head.line, ch: headLength} + ); + activeView.editor.setSelection( + {line: anchor.line + addedLines + 1, ch: anchor.ch}, + {line: head.line + addedLines + 1, ch: head.ch}, + ); + } + else { + console.log("Something went wrong..."); + } + + return; + } +} \ No newline at end of file diff --git a/src/SelectLine.ts b/src/SelectLine.ts new file mode 100755 index 0000000..a9252a1 --- /dev/null +++ b/src/SelectLine.ts @@ -0,0 +1,34 @@ +import { App, MarkdownView } from "obsidian"; +import ObsidianTweaksPlugin from "./main"; + +export class SelectLine { + public app: App; + private plugin: ObsidianTweaksPlugin; + + constructor(app: App, plugin: ObsidianTweaksPlugin) { + this.app = app; + this.plugin = plugin; + } + + selectLine(): void { + const activeView = this.app.workspace.getActiveViewOfType(MarkdownView); + if (!activeView) { + return; + } + + var anchor = activeView.editor.getCursor("from"); + var head = activeView.editor.getCursor("to"); + + var headLength = activeView.editor.getLine(head.line).length; + + // Modifying and passing the EditorPosition objects does not work + // reliably. + // Use this approach instead. + activeView.editor.setSelection( + {line: anchor.line, ch: 0}, + {line: head.line, ch: headLength} + ) + + return; + } +} \ No newline at end of file diff --git a/src/ToggleHeading.ts b/src/ToggleHeading.ts new file mode 100755 index 0000000..0d44698 --- /dev/null +++ b/src/ToggleHeading.ts @@ -0,0 +1,69 @@ +import { App, EditableFileView, Editor, MarkdownView } from "obsidian"; +import ObsidianTweaksPlugin from "./main"; + +export enum Heading { + H1 = 1, + H2, + H3, + H4, + H5, + H6, +} + +export class ToggleHeading { + public app: App; + private plugin: ObsidianTweaksPlugin; + + constructor(app: App, plugin: ObsidianTweaksPlugin) { + this.app = app; + this.plugin = plugin; + } + + toggleHeading(heading: Heading): void { + // Three possibilities: + // 1: Currently no heading text. Set the heading. + // 2: Currently this heading text. Remove the heading. + // 3: Currently some other heading. Remove the heading and set this heading. + + const activeView = this.app.workspace.getActiveViewOfType(MarkdownView); + if (!activeView) { + return; + } + + var headingText = "#".repeat(heading); + var anchor = activeView.editor.getCursor("from"); + + var lineText = activeView.editor.getLine(anchor.line); + + var regex: RegExp = /^((#*) )?.*/; + var matches = regex.exec(lineText); + + if (matches[2] !== undefined) { + // Some heading + if (matches[2] === headingText) { + // Option 2 + activeView.editor.replaceRange( + '', + {line: anchor.line, ch: 0}, + {line: anchor.line, ch: matches[2].length + 1} + ) + return; + } else { + // Option 3 + activeView.editor.replaceRange( + headingText + " ", + {line: anchor.line, ch: 0}, + {line: anchor.line, ch: matches[2].length + 1} + ) + return; + + } + } + + activeView.editor.replaceRange( + headingText + " ", + {line: anchor.line, ch: 0}, + ) + return; + } +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts new file mode 100755 index 0000000..466fe37 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,127 @@ +import { App, Modal, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian'; +import { SelectLine } from 'SelectLine'; +import { DirectionalCopy, Direction } from 'DirectionalCopy'; +import { ToggleHeading, Heading } from 'ToggleHeading'; + +interface ObsidianTweaksSettings { +} + +const DEFAULT_SETTINGS: ObsidianTweaksSettings = { +} + +export default class ObsidianTweaksPlugin extends Plugin { + public settings: ObsidianTweaksSettings; + + private selectLine: SelectLine; + private directionalCopy: DirectionalCopy; + private toggleHeading: ToggleHeading; + + async onload() { + await this.loadSettings(); + + this.selectLine = new SelectLine(this.app, this); + this.directionalCopy = new DirectionalCopy(this.app, this); + this.toggleHeading = new ToggleHeading(this.app, this); + + // Select line + this.addCommand({ + id: 'select-line', + name: 'Select Current Line(s)', + callback: () => { + this.selectLine.selectLine(); + } + }); + + // Directional Copy + this.addCommand({ + id: 'copy-line-up', + name: 'Copy Current Line(s) Up', + callback: () => { + this.directionalCopy.directionalCopy(Direction.Up); + } + }); + this.addCommand({ + id: 'copy-line-down', + name: 'Copy Current Line(s) Down', + callback: () => { + this.directionalCopy.directionalCopy(Direction.Down); + } + }); + + // Set heading + this.addCommand({ + id: 'toggle-heading-1', + name: 'Toggle Heading - H1', + callback: () => { + this.toggleHeading.toggleHeading(Heading.H1); + } + }); + this.addCommand({ + id: 'toggle-heading-2', + name: 'Toggle Heading - H2', + callback: () => { + this.toggleHeading.toggleHeading(Heading.H2); + } + }); + this.addCommand({ + id: 'toggle-heading-3', + name: 'Toggle Heading - H3', + callback: () => { + this.toggleHeading.toggleHeading(Heading.H3); + } + }); + this.addCommand({ + id: 'toggle-heading-4', + name: 'Toggle Heading - H4', + callback: () => { + this.toggleHeading.toggleHeading(Heading.H4); + } + }); + this.addCommand({ + id: 'toggle-heading-5', + name: 'Toggle Heading - H5', + callback: () => { + this.toggleHeading.toggleHeading(Heading.H5); + } + }); + this.addCommand({ + id: 'toggle-heading-6', + name: 'Toggle Heading - H6', + callback: () => { + this.toggleHeading.toggleHeading(Heading.H6); + } + }); + + this.addSettingTab(new SampleSettingTab(this.app, this)); + } + + onunload() { + } + + async loadSettings() { + this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); + } + + async saveSettings() { + await this.saveData(this.settings); + } +} + +class SampleSettingTab extends PluginSettingTab { + plugin: ObsidianTweaksPlugin; + + constructor(app: App, plugin: ObsidianTweaksPlugin) { + super(app, plugin); + this.plugin = plugin; + } + + display(): void { + let {containerEl} = this; + + containerEl.empty(); + + containerEl.createEl('h2', {text: 'Obsidian Tweaks'}); + + containerEl.createEl('p', {text: 'Currently nothing here!'}); + } +} diff --git a/styles.css b/styles.css old mode 100644 new mode 100755 index cfd0fd7..e69de29 --- a/styles.css +++ b/styles.css @@ -1,4 +0,0 @@ -/* Sets all the text color to red! */ -body { - color: red; -} diff --git a/tsconfig.json b/tsconfig.json old mode 100644 new mode 100755 index 09cf7ec..4cdb3c8 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "baseUrl": ".", + "baseUrl": "src", "inlineSourceMap": true, "inlineSources": true, "module": "ESNext", @@ -17,6 +17,6 @@ ] }, "include": [ - "**/*.ts" + "src/**/*.ts" ] } diff --git a/versions.json b/versions.json old mode 100644 new mode 100755 index ba14785..906f859 --- a/versions.json +++ b/versions.json @@ -1,4 +1,3 @@ { - "1.0.1": "0.9.12", - "1.0.0": "0.9.7" + "0.1.0": "0.12.3" }