use frontmatter as a setting

This commit is contained in:
Jay Nguyen
2022-04-17 20:56:53 +01:00
parent 7609bd1752
commit f6ecf4027f
8 changed files with 319 additions and 135 deletions

View File

@@ -1,12 +1,14 @@
## Obsidian & Ghost
# Obsidian & Ghost
A simple plugin for Obsidian to publish to Ghost site with a single click.
## How to use
- Create a custom integration follow this [link](https://ghost.org/integrations/custom-integrations/). You would need an **Admin API Key** and **API URL**.
- Once you install the plugin, you would find an option "Obsidian Ghost Publish" under "Plugin options" and copy-paste it in the textbox.
- That's it! you now are able to publish by click on the ghost icon on the left hand or use the command pallete.
### How to use
### How to run on dev
- Clone this repo.
- `npm i` or `yarn` to install dependencies
@@ -16,7 +18,6 @@ A simple plugin for Obsidian to publish to Ghost site with a single click.
- Copy over `main.js`, `styles.css`, `manifest.json` to your vault `VaultFolder/.obsidian/plugins/your-plugin-id/`.
### Issues & Requests
- For feature requests, please take use of Discussions.

View File

@@ -1,10 +1,10 @@
{
"id": "obsidian-ghost-publish",
"name": "Obsidian Ghost Publish",
"version": "1.0.0",
"version": "1.2.0",
"minAppVersion": "0.12.0",
"description": "Single click to publish to Ghost",
"author": "@jaynguyens <jay@nguyens.co>",
"authorUrl": "https://nguyens.co",
"isDesktopOnly": false
}
}

173
package-lock.json generated
View File

@@ -8,6 +8,9 @@
"name": "obsidian-ghost-publish",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"gray-matter": "^4.0.3"
},
"devDependencies": {
"@types/jsonwebtoken": "^8.5.8",
"@types/markdown-it": "^12.2.3",
@@ -1058,6 +1061,18 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"bin": {
"esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/esquery": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
@@ -1121,6 +1136,17 @@
"node": ">=0.10.0"
}
},
"node_modules/extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dependencies": {
"is-extendable": "^0.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -1308,6 +1334,40 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/gray-matter": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
"integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
"dependencies": {
"js-yaml": "^3.13.1",
"kind-of": "^6.0.2",
"section-matter": "^1.0.0",
"strip-bom-string": "^1.0.0"
},
"engines": {
"node": ">=6.0"
}
},
"node_modules/gray-matter/node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dependencies": {
"sprintf-js": "~1.0.2"
}
},
"node_modules/gray-matter/node_modules/js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -1372,6 +1432,14 @@
"dev": true,
"peer": true
},
"node_modules/is-extendable": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
"integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -1488,6 +1556,14 @@
"safe-buffer": "^5.0.1"
}
},
"node_modules/kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -1873,6 +1949,18 @@
}
]
},
"node_modules/section-matter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
"integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
"dependencies": {
"extend-shallow": "^2.0.1",
"kind-of": "^6.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/semver": {
"version": "7.3.6",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.6.tgz",
@@ -1920,6 +2008,11 @@
"node": ">=8"
}
},
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
@@ -1933,6 +2026,14 @@
"node": ">=8"
}
},
"node_modules/strip-bom-string": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
"integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@@ -2836,6 +2937,11 @@
"eslint-visitor-keys": "^3.3.0"
}
},
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
},
"esquery": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
@@ -2885,6 +2991,14 @@
"dev": true,
"peer": true
},
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"requires": {
"is-extendable": "^0.1.0"
}
},
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -3038,6 +3152,36 @@
"slash": "^3.0.0"
}
},
"gray-matter": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
"integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
"requires": {
"js-yaml": "^3.13.1",
"kind-of": "^6.0.2",
"section-matter": "^1.0.0",
"strip-bom-string": "^1.0.0"
},
"dependencies": {
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"requires": {
"sprintf-js": "~1.0.2"
}
},
"js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
}
}
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -3087,6 +3231,11 @@
"dev": true,
"peer": true
},
"is-extendable": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
"integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik="
},
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -3186,6 +3335,11 @@
"safe-buffer": "^5.0.1"
}
},
"kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="
},
"levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -3462,6 +3616,15 @@
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true
},
"section-matter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
"integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
"requires": {
"extend-shallow": "^2.0.1",
"kind-of": "^6.0.0"
}
},
"semver": {
"version": "7.3.6",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.6.tgz",
@@ -3494,6 +3657,11 @@
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"dev": true
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
@@ -3504,6 +3672,11 @@
"ansi-regex": "^5.0.1"
}
},
"strip-bom-string": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
"integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI="
},
"strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "obsidian-ghost-publish",
"version": "1.0.0",
"version": "1.2.0",
"description": "Obsidian plugin for easy publish to ghost with a single click.",
"main": "src/main.js",
"scripts": {
@@ -23,10 +23,11 @@
"@typescript-eslint/parser": "^5.2.0",
"builtin-modules": "^3.2.0",
"esbuild": "0.13.12",
"jsonwebtoken": "^8.5.1",
"markdown-it": "^12.3.2",
"obsidian": "^0.14.4",
"tslib": "2.3.1",
"typescript": "4.4.4",
"jsonwebtoken": "^8.5.1",
"markdown-it": "^12.3.2"
"gray-matter": "^4.0.3"
}
}
}

View File

@@ -1,10 +1,8 @@
import { MarkdownView, Plugin } from 'obsidian';
import { DEFAULT_SETTINGS, SettingsProp } from './types/index';
import { SettingTab } from './settingTab'
import { publishPost } from './methods/publishPost';
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;
@@ -15,29 +13,39 @@ export default class MyPlugin extends Plugin {
// 2 ways to publish post:
// 1. Click on the ghost icon on the left
this.addRibbonIcon('ghost', 'Publish Ghost', () => {
this.addRibbonIcon("ghost", "Publish Ghost", () => {
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
publishPost(view, this.settings)
// matter
publishPost(view, this.settings);
});
// 2. Run the by command + P
this.addCommand({
id: 'publish',
name: 'Publish current document',
id: "publish",
name: "Publish current document",
editorCallback: (_, view: MarkdownView) => {
publishPost(view, this.settings)
}
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));
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) }
}
onunload() {}
async loadSettings() {
this.settings = Object.assign(
{},
DEFAULT_SETTINGS,
await this.loadData()
);
}
async saveSettings() {
await this.saveData(this.settings);
}
}

View File

@@ -1,67 +1,78 @@
import { SettingsProp, ViewProp, ContentProp } from './../types/index';
import { SettingsProp, ViewProp, ContentProp } from "./../types/index";
import { Notice, request } from "obsidian";
import { sign } from 'jsonwebtoken';
const MarkdownIt = require("markdown-it")
import { sign } from "jsonwebtoken";
const md = new MarkdownIt()
const matter = require("gray-matter");
const MarkdownIt = require("markdown-it");
const md = new MarkdownIt();
export const publishPost = async (view: ViewProp, settings: SettingsProp) => {
const version = "v4";
const content = {
title: view.file.basename,
data: view.data
}
// Admin API key goes here
const key = settings.adminToken;
const version = 'v4'
// Split the key into ID and SECRET
const [id, secret] = key.split(":");
// Admin API key goes here
const key = settings.adminToken;
// Create the token (including decoding secret)
const token = sign({}, Buffer.from(secret, "hex"), {
keyid: id,
algorithm: "HS256",
expiresIn: "5m",
audience: `/${version}/admin/`,
});
// Split the key into ID and SECRET
const [id, secret] = key.split(':');
// get frontmatter
const m = matter(`${view.data}`);
// Create the token (including decoding secret)
const token = sign({}, Buffer.from(secret, 'hex'), {
keyid: id,
algorithm: 'HS256',
expiresIn: '5m',
audience: `/${version}/admin/`
});
const frontmatter = {
title: m.data.title || view.file.basename,
tags: m.data.tags || undefined,
featured: m.data.featured || false,
status: m.data.status || "draft",
excerpt: m.data.excerpt || undefined,
feature_image: m.data.feature_image || undefined,
};
const contentPost = (content: ContentProp, settings: SettingsProp) => ({
"posts": [{
"title": content.title,
"html": md.render(content.data),
"status": settings.publishStatus
}]
})
const contentPost = (frontmatter: ContentProp) => ({
posts: [
{
...frontmatter,
html: md.render(m.content),
},
],
});
const body = contentPost(content, settings);
const body = contentPost(frontmatter);
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 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);
const json = JSON.parse(result)
if (json?.posts) {
new Notice(
`"${json?.posts?.[0]?.title}" has been ${json?.posts?.[0]?.status} successful!`
);
} else {
new Notice(`${json.errors[0].context || json.errors[0].message}`);
new Notice(
`${json.errors[0]?.details[0].message} - ${json.errors[0]?.details[0].params.allowedValues}`
);
}
if (json?.errors) {
new Notice(`${json.errors[0].type}! ${json.errors[0].message}`)
}
if (json?.posts) {
new Notice(`"${json?.posts?.[0]?.title}" has been ${json?.posts?.[0]?.status} successful!`)
}
return json
}
return json;
};

View File

@@ -1,51 +1,40 @@
import { App, PluginSettingTab, Setting } from 'obsidian';
import MyPlugin from 'src/main';
import { App, PluginSettingTab, Setting } from "obsidian";
import MyPlugin from "src/main";
export class SettingTab extends PluginSettingTab {
plugin: MyPlugin;
plugin: MyPlugin;
constructor(app: App, plugin: MyPlugin) {
super(app, plugin);
this.plugin = plugin;
}
constructor(app: App, plugin: MyPlugin) {
super(app, plugin);
this.plugin = plugin;
}
display(): void {
const { containerEl } = this;
display(): void {
const { containerEl } = this;
containerEl.empty();
containerEl.createEl('h2', { text: 'Obsidian Ghost Integration' });
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("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();
}))
}
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();
})
);
}
}

View File

@@ -1,24 +1,25 @@
export interface SettingsProp {
url: string,
adminToken: string,
publishStatus: string
url: string;
adminToken: string;
}
export const DEFAULT_SETTINGS: SettingsProp = {
url: '',
adminToken: '',
publishStatus: 'draft'
}
url: "",
adminToken: "",
};
export interface ViewProp {
file: {
basename: string
},
data: string
file: {
basename: string;
};
data: string;
}
export interface ContentProp {
title: string,
data: string
title: string;
tags: string[];
featured: boolean;
status: "published" | "draft";
excerpt: string;
feature_image: string;
}