diff --git a/esbuild.config.mjs b/esbuild.config.mjs index f1fe201..a36a07d 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -3,7 +3,7 @@ import process from "process"; import builtins from 'builtin-modules' const banner = -`/* + `/* THIS IS A GENERATED/BUNDLED FILE BY ESBUILD if you want to view the source, please visit the github repository of this plugin */ @@ -15,7 +15,7 @@ esbuild.build({ banner: { js: banner, }, - entryPoints: ['main.ts'], + entryPoints: ['src/main.ts'], bundle: true, external: [ 'obsidian', diff --git a/main.ts b/main.ts deleted file mode 100644 index 9b24b2f..0000000 --- a/main.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian'; - -// Remember to rename these classes and interfaces! - -interface MyPluginSettings { - mySetting: string; -} - -const DEFAULT_SETTINGS: MyPluginSettings = { - mySetting: 'default' -} - -export default class MyPlugin extends Plugin { - settings: MyPluginSettings; - - async onload() { - await this.loadSettings(); - - // This creates an icon in the left ribbon. - const 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. - const 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 - const 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; - } - } - }); - - // 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)); - } - - onunload() { - - } - - 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() { - const {contentEl} = this; - contentEl.setText('Woah!'); - } - - onClose() { - const {contentEl} = this; - contentEl.empty(); - } -} - -class SampleSettingTab extends PluginSettingTab { - plugin: MyPlugin; - - constructor(app: App, plugin: MyPlugin) { - super(app, plugin); - this.plugin = plugin; - } - - display(): void { - const {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/manifest.json b/manifest.json index 30ec656..8b4a965 100644 --- a/manifest.json +++ b/manifest.json @@ -1,10 +1,10 @@ { - "id": "obsidian-sample-plugin", - "name": "Sample Plugin", - "version": "1.0.1", + "id": "obsidian-ghost-publish", + "name": "Obsidian Ghost Publish", + "version": "1.0.0", "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", + "description": "Single click to publish to Ghost", + "author": "@jaynguyens ", + "authorUrl": "https://nguyens.co", "isDesktopOnly": false -} +} \ No newline at end of file diff --git a/package.json b/package.json index fd44f15..ddf93d6 100644 --- a/package.json +++ b/package.json @@ -1,24 +1,32 @@ { - "name": "obsidian-sample-plugin", - "version": "1.0.1", - "description": "This is a sample plugin for Obsidian (https://obsidian.md)", - "main": "main.js", + "name": "obsidian-ghost-publish", + "version": "1.0.0", + "description": "Obsidian plugin for easy publish to ghost with a single click.", + "main": "src/main.js", "scripts": { "dev": "node esbuild.config.mjs", "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", "version": "node version-bump.mjs && git add manifest.json versions.json" }, "keywords": [], - "author": "", + "author": { + "name": "Jay Nguyen", + "email": "jay@nguyens.co", + "url": "https://github.com/jaynguyens/obsidian-ghost-publish" + }, "license": "MIT", "devDependencies": { + "@types/jsonwebtoken": "^8.5.8", + "@types/markdown-it": "^12.2.3", "@types/node": "^16.11.6", "@typescript-eslint/eslint-plugin": "^5.2.0", "@typescript-eslint/parser": "^5.2.0", "builtin-modules": "^3.2.0", "esbuild": "0.13.12", - "obsidian": "latest", + "obsidian": "^0.14.4", "tslib": "2.3.1", - "typescript": "4.4.4" + "typescript": "4.4.4", + "jsonwebtoken": "^8.5.1", + "markdown-it": "^12.3.2" } -} +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..745ab0d --- /dev/null +++ b/src/main.ts @@ -0,0 +1,45 @@ +import { MarkdownView, Plugin } from 'obsidian'; + +import { DEFAULT_SETTINGS, SettingsProp } from './types/index'; +import { SettingTab } from './settingTab' +import { publishPost } from './methods/publishPost'; + + +export default class MyPlugin extends Plugin { + settings: SettingsProp; + + async onload() { + // load the settings first + await this.loadSettings(); + + // 2 ways to publish post: + + console.log('this.settings', this.settings) + + // 1. Click on the ghost icon on the left + this.addRibbonIcon('ghost', 'Publish Ghost', () => { + const view = this.app.workspace.getActiveViewOfType(MarkdownView); + + publishPost(view, this.settings) + }); + + // 2. Run the by command + P + this.addCommand({ + id: 'publish', + name: 'Publish current document', + editorCallback: (_, view: MarkdownView) => { + publishPost(view, this.settings) + } + }); + + // This adds a settings tab so the user can configure various aspects of the plugin + this.addSettingTab(new SettingTab(this.app, this)); + + // 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)); + } + + onunload() { } + async loadSettings() { this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()) } + async saveSettings() { await this.saveData(this.settings) } +} \ No newline at end of file diff --git a/src/methods/publishPost.ts b/src/methods/publishPost.ts new file mode 100644 index 0000000..144e5b8 --- /dev/null +++ b/src/methods/publishPost.ts @@ -0,0 +1,71 @@ +import { Notice, request } from "obsidian"; + +import { sign } from 'jsonwebtoken'; +const MarkdownIt = require("markdown-it") + +const md = new MarkdownIt() + +export const publishPost = async (view: any, settings: any) => { + + const content = { + title: view.file.basename, + data: view.data + } + + const version = 'v4' + + console.log('content', content) + console.log('settings', settings) + + // Admin API key goes here + const key = settings.adminToken; + + // Split the key into ID and SECRET + const [id, secret] = key.split(':'); + + // Create the token (including decoding secret) + const token = sign({}, Buffer.from(secret, 'hex'), { + keyid: id, + algorithm: 'HS256', + expiresIn: '5m', + audience: `/${version}/admin/` + }); + + + const contentPost = (content: any, settings: any) => ({ + "posts": [{ + "title": content.title, + "html": md.render(content.data), + "status": settings.publishStatus + }] + }) + + const body = contentPost(content, settings); + + + const result = await request({ + url: `${settings.url}/ghost/api/${version}/admin/posts/?source=html`, + method: "POST", + contentType: "application/json", + headers: { + 'Access-Control-Allow-Origin': 'app://obsidian.md', + 'Access-Control-Allow-Methods': 'POST', + 'Content-Type': 'application/json;charset=utf-8', + 'Authorization': `Ghost ${token}` + }, + body: JSON.stringify(body) + }) + + + const json = JSON.parse(result) + + if (json?.errors) { + new Notice(`${json.errors[0].type}! ${json.errors[0].message}`) + } + + if (json?.posts) { + new Notice(`${json.posts[0].settings.title} has been ${json.posts[0].settings.status} successful!`) + } + + return result +} \ No newline at end of file diff --git a/src/settingTab/index.ts b/src/settingTab/index.ts new file mode 100644 index 0000000..a398d46 --- /dev/null +++ b/src/settingTab/index.ts @@ -0,0 +1,51 @@ +import { App, PluginSettingTab, Setting } from 'obsidian'; +import MyPlugin from 'src/main'; + +export class SettingTab extends PluginSettingTab { + plugin: MyPlugin; + + constructor(app: App, plugin: MyPlugin) { + super(app, plugin); + this.plugin = plugin; + } + + display(): void { + const { containerEl } = this; + + containerEl.empty(); + containerEl.createEl('h2', { text: 'Obsidian Ghost Integration' }); + + new Setting(containerEl) + .setName('API URL') + .addText(text => text + .setPlaceholder('nguyens.co') + .setValue(this.plugin.settings.url) + .onChange(async (value) => { + console.log('Blog URL: ' + value); + this.plugin.settings.url = value; + await this.plugin.saveSettings(); + })); + + new Setting(containerEl) + .setName('Admin API Key') + .addText(text => text + .setPlaceholder('6251555c94ca6') + .setValue(this.plugin.settings.adminToken) + .onChange(async (value) => { + console.log('admin api key: ' + value); + this.plugin.settings.adminToken = value; + await this.plugin.saveSettings(); + })); + + new Setting(containerEl) + .setName('Post publish status') + .addDropdown(dropdown => dropdown + .addOption('draft', 'Draft') + .addOption('published', 'Publish') + .setValue(this.plugin.settings.publishStatus) + .onChange(async (value) => { + this.plugin.settings.publishStatus = value; + await this.plugin.saveSettings(); + })) + } +} diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..ca7fae8 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,12 @@ +export interface SettingsProp { + url: string, + adminToken: string, + publishStatus: string +} + +export const DEFAULT_SETTINGS: SettingsProp = { + url: '', + adminToken: '', + publishStatus: 'draft' +} + diff --git a/styles.css b/styles.css index cfd0fd7..e69de29 100644 --- a/styles.css +++ b/styles.css @@ -1,4 +0,0 @@ -/* Sets all the text color to red! */ -body { - color: red; -}