diff --git a/README.md b/README.md index 9e4fe9c..2d03089 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,41 @@ -## Obsidian Sample Plugin +## Scales and Chords -This is a sample plugin for Obsidian (https://obsidian.md). +This is a plugin to allow you to capture guitar and piano tabs in your Obsidian vault. -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. +To use this plugin, use a fenced code block like this: -**Note:** The Obsidian API is still in early alpha and is subject to change at any time! +
+```tab
+Verse
+G         Am             G/B     G %
+Blackbird singing in the dead of night
+C          C#dim       D    D#dim    Em   D# %
+Take these broken wings and learn to fly
+D   C#dim   C    Cm %
+All your    life
+G/B             A7               D7         G %
+You were only waiting for this moment to arise
+```
+
-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. +The `%` at the end of a line indicates that the line contains chords. In preview mode, each chord is displayed in bold, and is clickable. Clicking a chord brings up a modal containing a visual rendition of the chord (as supplied by [ScalesChords](https://www.scales-chords.com/)). -### First time developing plugins? +Lines that don't end in `%` will be rendered unmodified. -Quick starting guide for new plugin devs: +In addition, every chord captured by the plugin will be rendered in a big stack below the tab block. This allows you to open two edit views and keep all the song's chords in view. -- 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. +You can change the instrument in the settings. Just type in one of: +- `guitar` +- `piano` +- `ukelele` +- `mandolin` +- `banjo` -### Releasing new releases +## Caveats -- 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. +- This is my first plugin. +- I can't play piano or guitar. I'm learning, and writing a plugin is how I procrastinate practice. -### Adding your plugin to the community plugin list +Thanks to the fine folks at [ScalesChords](https://www.scales-chords.com/) who provided an API for generating these images! -- 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. -### How to use - -- Clone this repo. -- `npm i` or `yarn` to install dependencies -- `npm run dev` to start compilation in watch mode. - -### 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 diff --git a/main.ts b/main.ts index eeb3dda..f15febc 100644 --- a/main.ts +++ b/main.ts @@ -1,112 +1,180 @@ -import { App, Modal, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian'; +import { App, MarkdownPostProcessor, MarkdownPostProcessorContext, MarkdownPreviewRenderer, MarkdownRenderer, Modal, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian'; -interface MyPluginSettings { - mySetting: string; + +interface ScalesChordsPluginSettings { + instrument: string; +} + +const DEFAULT_SETTINGS: ScalesChordsPluginSettings = { + instrument: 'piano' } -const DEFAULT_SETTINGS: MyPluginSettings = { - mySetting: 'default' +export default class ScalesChordsPlugin extends Plugin { + settings: ScalesChordsPluginSettings; + + async onload() { + const chordRegex = new RegExp("\[:(.*?):\]"); + console.log('loading ScalesChords'); + + //@ts-ignore + window.scales_chords_api_debug = true; + + await this.loadSettings(); + + this.addSettingTab(new SettingTab(this.app, this)); + + this.registerMarkdownCodeBlockProcessor("tab", (source, el, ctx)=>{ + let chords = new Set(); + let pre = document.createElement("pre") + + let lines = source.split("\n") + for (var line of lines) { + // parse tab lines out into separate tokens, preserving white space + if (line[line.length-1] == "%") { + let tokens = []; + var cur_token = ''; + var last_char = ''; + for (var char of line.split("")) { + if (char == "%") char = " "; + if ((last_char == ' ' && char != ' ') || (last_char != ' ' && char == ' ')) { + tokens.push(cur_token); + cur_token = ''; + } + cur_token += char; + last_char = char; + } + tokens.push(cur_token); + let div = document.createElement('div'); + for (var token of tokens) { + if (token[0] != ' ') { + chords.add(token); + let e = document.createElement('b'); + e.innerHTML = token; + let _token = token; + this.registerDomEvent(e, 'click', (evt)=>{ + new TabModal(this.app, _token, this.settings.instrument).open(); + }); + div.appendChild(e); + } else { + div.appendChild(document.createTextNode(token)); + } + } + pre.appendChild(div); + } else { + let line_elem = document.createTextNode(line+"\n"); + pre.appendChild(line_elem); + } + } + + el.appendChild(pre) + for (var chord of chords) { + if (chord == '') continue; + append_chord_image(el, chord, this.settings.instrument); + } + }); + } + + onunload() { + console.log('unloading plugin'); + } + + async loadSettings() { + this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); + } + + async saveSettings() { + await this.saveData(this.settings); + } } -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 TabModal extends Modal { + chord: string; + constructor(app: App, _chord: string, _instrument: string) { + super(app); + this.instrument = _instrument; + this.chord = _chord; + } + + onOpen() { + let {contentEl} = this; + append_chord_image(contentEl, this.chord, this.instrument); + } + + onClose() { + let {contentEl} = this; + contentEl.empty(); + } +} + +class SettingTab extends PluginSettingTab { + plugin: ScalesChordsPlugin; + + constructor(app: App, plugin: MyPlugin) { + super(app, plugin); + this.plugin = plugin; + } + + display(): void { + let {containerEl} = this; + + containerEl.empty(); + + containerEl.createEl('h2', {text: 'Settings for Scales and Chords'}); + + new Setting(containerEl) + .setName('instrument') + .setDesc('musical instrument to render') + .addText(text => text + .setPlaceholder('Enter instrument') + .setValue(this.plugin.settings.instrument) + .onChange(async (value) => { + this.plugin.settings.instrument = value; + await this.plugin.saveSettings(); + })); + } } -class SampleModal extends Modal { - constructor(app: App) { - super(app); - } - onOpen() { - let {contentEl} = this; - contentEl.setText('Woah!'); - } - onClose() { - let {contentEl} = this; - contentEl.empty(); - } +function append_chord_image(el: Any, chord: string, instrument: string) { + postData( + "https://www.scales-chords.com/api/scapi.1.3.php", + { + 'id': 'scapiobjid1', + 'class': 'scales_chords_api', + 'chord': chord, + 'instrument': instrument + } + ) + .then(res=>res.text()) + .then(text=>{ + let arr = text.split("###RAWR###"); + let inner = document.createElement("div"); + inner.innerHTML = arr[arr.length-1]; + el.appendChild(inner); + }); +} + +function postData(url = '', data = {}) { + // Default options are marked with * + const response = fetch(url, { + method: 'POST', // *GET, POST, PUT, DELETE, etc. + mode: 'cors', // no-cors, *cors, same-origin + cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached + credentials: 'same-origin', // include, *same-origin, omit + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + redirect: 'follow', // manual, *follow, error + referrerPolicy: 'no-referrer', + body: serialize(data) // body data type must match "Content-Type" header + }); + return response; } -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(); - })); - } +function serialize(obj: Any) { + var str = []; + for(var p in obj) + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); + return str.join("&"); } diff --git a/manifest.json b/manifest.json index 4ca4889..d2c18b1 100644 --- a/manifest.json +++ b/manifest.json @@ -1,10 +1,10 @@ { - "id": "obsidian-sample-plugin", - "name": "Sample Plugin", + "id": "scales-chords", + "name": "Scales and Chords", "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", + "description": "Use this plugin to capture musical tab notation in your Obsidian vault. Chords will become clickable links to modal images (provided by scales-chords.com)", + "author": "egradman", + "authorUrl": "https://www.gradman.com", "isDesktopOnly": false } diff --git a/styles.css b/styles.css index cfd0fd7..06d778f 100644 --- a/styles.css +++ b/styles.css @@ -1,4 +1,3 @@ /* Sets all the text color to red! */ body { - color: red; }