Skip to content

Commit

Permalink
first working commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Eric Gradman committed Sep 18, 2021
1 parent 8a1f90e commit 9c3d0cd
Show file tree
Hide file tree
Showing 4 changed files with 202 additions and 151 deletions.
74 changes: 29 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
@@ -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!
<pre>
```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
```
</pre>

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
268 changes: 168 additions & 100 deletions main.ts
Original file line number Diff line number Diff line change
@@ -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("&");
}
10 changes: 5 additions & 5 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -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
}
1 change: 0 additions & 1 deletion styles.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* Sets all the text color to red! */
body {
color: red;
}

0 comments on commit 9c3d0cd

Please sign in to comment.