From 7ad983ed2b0f0bd0d0ac11693efe9d16d429196f Mon Sep 17 00:00:00 2001 From: Ross Keenan Date: Wed, 20 Oct 2021 17:00:14 +0200 Subject: [PATCH] feat: :sparkles: Initial working plugin! --- .github/workflows/release.yml | 34 ++++++ SampleSettingTab.ts | 32 ++++++ main.ts | 201 +++++++++++++++++----------------- manifest.json | 16 +-- package.json | 6 +- styles.css | 3 - tsconfig.json | 33 +++--- 7 files changed, 193 insertions(+), 132 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 SampleSettingTab.ts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..43aea3e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,34 @@ +name: Publish plugin + +on: + push: + # Sequence of patterns matched against refs/tags + tags: + - "*" # Push events to matching any tag format, i.e. 1.0, 20.15.10 + +env: + PLUGIN_NAME: ${{ github.event.repository.name }} + RELEASE_VER: ${{ github.ref }} + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Create release and Upload + id: release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${RELEASE_VER##*/} + mkdir "${PLUGIN_NAME}" + assets=() + for f in main.js manifest.json styles.css; do + if [[ -f $f ]]; then + cp $f "${PLUGIN_NAME}/" + assets+=(-a "$f") + fi + done + zip -r "$PLUGIN_NAME".zip "$PLUGIN_NAME" + hub release create "${assets[@]}" -a "$PLUGIN_NAME".zip -m "$TAG_NAME" "$TAG_NAME" diff --git a/SampleSettingTab.ts b/SampleSettingTab.ts new file mode 100644 index 0000000..12f5070 --- /dev/null +++ b/SampleSettingTab.ts @@ -0,0 +1,32 @@ +import { App, PluginSettingTab, Setting } from "obsidian"; +import MyPlugin from "./main"; + +export class SettingTab 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(this.plugin.settings.mySetting) + // .onChange(async (value) => { + // console.log("Secret: " + value); + // this.plugin.settings.mySetting = value; + // await this.plugin.saveSettings(); + // }) + // ); + } +} diff --git a/main.ts b/main.ts index f1705a3..2ab6066 100644 --- a/main.ts +++ b/main.ts @@ -1,87 +1,49 @@ -import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian'; - -interface MyPluginSettings { +import { + App, + Editor, + EditorPosition, + EditorSelectionOrCaret, + Modal, + Plugin, + View, +} from "obsidian"; +import { SettingTab } from "./SampleSettingTab"; + +interface Settings { mySetting: string; } -const DEFAULT_SETTINGS: MyPluginSettings = { - mySetting: 'default' -} +const DEFAULT_SETTINGS: Settings = { + mySetting: "default", +}; export default class MyPlugin extends Plugin { - settings: MyPluginSettings; + settings: Settings; async onload() { await this.loadSettings(); - // This creates an icon in the left ribbon. - let ribbonIconEl = this.addRibbonIcon('dice', 'Sample Plugin', (evt: MouseEvent) => { - // Called when the user clicks the icon. - new Notice('This is a notice!'); - }); - // Perform additional things with the ribbon - ribbonIconEl.addClass('my-plugin-ribbon-class'); - - // This adds a status bar item to the bottom of the app. Does not work on mobile apps. - let statusBarItemEl = this.addStatusBarItem(); - statusBarItemEl.setText('Status Bar Text'); - // This adds a simple command that can be triggered anywhere this.addCommand({ - id: 'open-sample-modal-simple', - name: 'Open sample modal (simple)', - callback: () => { - new SampleModal(this.app).open(); - } - }); - // This adds an editor command that can perform some operation on the current editor instance - this.addCommand({ - id: 'sample-editor-command', - name: 'Sample editor command', - editorCallback: (editor: Editor, view: MarkdownView) => { - console.log(editor.getSelection()); - editor.replaceSelection('Sample Editor Command'); - } - }); - // This adds a complex command that can check whether the current state of the app allows execution of the command - this.addCommand({ - id: 'open-sample-modal-complex', - name: 'Open sample modal (complex)', - checkCallback: (checking: boolean) => { - // Conditions to check - let markdownView = this.app.workspace.getActiveViewOfType(MarkdownView); - if (markdownView) { - // If checking is true, we're simply "checking" if the command can be run. - // If checking is false, then we want to actually perform the operation. - if (!checking) { - new SampleModal(this.app).open(); - } - - // This command will only show up in Command Palette when the check function returns true - return true; - } - } + id: "open-sample-modal-simple", + name: "Open sample modal (simple)", + editorCallback: (editor: Editor, view: View) => { + const query = "test"; + new CursorsModal(this.app, editor).open(); + }, }); - // This adds a settings tab so the user can configure various aspects of the plugin - this.addSettingTab(new SampleSettingTab(this.app, this)); - - // If the plugin hooks up any global DOM events (on parts of the app that doesn't belong to this plugin) - // Using this function will automatically remove the event listener when this plugin is disabled. - this.registerDomEvent(document, 'click', (evt: MouseEvent) => { - console.log('click', evt); - }); - - // When registering intervals, this function will automatically clear the interval when the plugin is disabled. - this.registerInterval(window.setInterval(() => console.log('setInterval'), 5 * 60 * 1000)); + this.addSettingTab(new SettingTab(this.app, this)); } - onunload() { - - } + onunload() {} async loadSettings() { - this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); + this.settings = Object.assign( + {}, + DEFAULT_SETTINGS, + await this.loadData() + ); } async saveSettings() { @@ -89,47 +51,90 @@ export default class MyPlugin extends Plugin { } } -class SampleModal extends Modal { - constructor(app: App) { +class CursorsModal extends Modal { + editor: Editor; + constructor(app: App, editor: Editor) { super(app); + this.editor = editor; } - onOpen() { - let {contentEl} = this; - contentEl.setText('Woah!'); - } - - onClose() { - let {contentEl} = this; - contentEl.empty(); + async getSelectionAndOffset() { + const selection = this.editor.getSelection(); + const offset = this.editor.getCursor("from").line; + if (selection !== "") { + return { selection, offset }; + } else { + const currFile = this.app.workspace.getActiveFile(); + const content = await this.app.vault.cachedRead(currFile); + return { selection: content, offset: 0 }; + } } -} -class SampleSettingTab extends PluginSettingTab { - plugin: MyPlugin; + getSelectionsFromQuery(content: string, offset: number, query: string) { + const regex = new RegExp(query, "g"); + const lines = content.split("\n"); + const selections: EditorSelectionOrCaret[] = []; + + lines.forEach((line, i) => { + const matches = line.matchAll(regex); + const matchesArr = [...matches]; + + matchesArr.forEach((matchArr) => { + const from = matchArr.index; + if (from !== undefined) { + const anchor: EditorPosition = { + ch: from, + line: i + offset, + }; + const head: EditorPosition = { + ch: from + matchArr[0].length, + line: i + offset, + }; + selections.push({ anchor, head }); + } + }); + }); - constructor(app: App, plugin: MyPlugin) { - super(app, plugin); - this.plugin = plugin; + return selections; } - display(): void { - let {containerEl} = this; + async onOpen() { + let { contentEl } = this; - containerEl.empty(); + const { selection, offset } = await this.getSelectionAndOffset(); + console.log({ selection }); + + const inputEl = contentEl.createEl("input", { + type: "text", + title: "Search Query", + attr: { placeholder: "Search Query" }, + }); - containerEl.createEl('h2', {text: 'Settings for my awesome plugin.'}); + const submitButton = contentEl.createEl( + "input", + { + type: "submit", + text: "submit", + }, + (submitEl) => { + submitEl.addEventListener("click", async () => { + const query = inputEl.value; + const selections = this.getSelectionsFromQuery( + selection, + offset, + query + ); + + console.log({ selections }); + this.editor.setSelections(selections); + this.close(); + }); + } + ); + } - new Setting(containerEl) - .setName('Setting #1') - .setDesc('It\'s a secret') - .addText(text => text - .setPlaceholder('Enter your secret') - .setValue(this.plugin.settings.mySetting) - .onChange(async (value) => { - console.log('Secret: ' + value); - this.plugin.settings.mySetting = value; - await this.plugin.saveSettings(); - })); + onClose() { + let { contentEl } = this; + contentEl.empty(); } } diff --git a/manifest.json b/manifest.json index 30ec656..3132164 100644 --- a/manifest.json +++ b/manifest.json @@ -1,10 +1,10 @@ { - "id": "obsidian-sample-plugin", - "name": "Sample Plugin", - "version": "1.0.1", - "minAppVersion": "0.12.0", - "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", + "id": "advanced-cursors", + "name": "Advanced Cursors", + "version": "0.0.1", + "minAppVersion": "0.12.18", + "description": "Use multiple cursors even more powerfully.", + "author": "SkepticMystic", + "authorUrl": "https://github.com/SkepticMystic/advanced-cursors", "isDesktopOnly": false -} +} \ No newline at end of file diff --git a/package.json b/package.json index edac7d7..d3880ba 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "obsidian-sample-plugin", - "version": "0.12.0", - "description": "This is a sample plugin for Obsidian (https://obsidian.md)", + "name": "advanced-cursors", + "version": "0.0.1", + "description": "Use multiple cursors even more powerfully", "main": "main.js", "scripts": { "dev": "esbuild main.ts --bundle --external:obsidian --outdir=. --target=es2016 --format=cjs --sourcemap=inline --watch", diff --git a/styles.css b/styles.css index cfd0fd7..74b77f6 100644 --- a/styles.css +++ b/styles.css @@ -1,4 +1 @@ /* Sets all the text color to red! */ -body { - color: red; -} diff --git a/tsconfig.json b/tsconfig.json index 44b8f99..f872dee 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,22 +1,15 @@ { - "compilerOptions": { - "baseUrl": ".", - "inlineSourceMap": true, - "inlineSources": true, - "module": "ESNext", - "target": "ES6", - "allowJs": true, - "noImplicitAny": true, - "moduleResolution": "node", - "importHelpers": true, - "lib": [ - "dom", - "es5", - "scripthost", - "es2015" - ] - }, - "include": [ - "**/*.ts" - ] + "compilerOptions": { + "baseUrl": ".", + "inlineSourceMap": true, + "inlineSources": true, + "module": "ESNext", + "target": "ES6", + "allowJs": true, + "noImplicitAny": true, + "moduleResolution": "node", + "importHelpers": true, + "lib": ["dom", "es5", "scripthost", "es2015"] + }, + "include": ["**/*.ts"] }