From 8fafd1cd4a6fbdf3528a95cd04b6dbbe3b74e800 Mon Sep 17 00:00:00 2001 From: Zef Hemel Date: Sat, 9 Apr 2022 14:28:41 +0200 Subject: [PATCH] Basic ghost plugin --- common/tree.ts | 38 +++-- package.json | 21 ++- plugos/environments/node_sandbox.ts | 2 +- plugos/environments/node_worker.ts | 5 +- plugos/package.json | 2 + plugos/syscalls/jwt.ts | 22 +++ plugos/yarn.lock | 91 +++++++++++- plugs/core/core.plug.yaml | 5 + plugs/core/page.ts | 4 + plugs/ghost/ghost.plug.yaml | 15 ++ plugs/ghost/ghost.ts | 219 ++++++++++++++++++++++++++++ plugs/lib/tree.test.ts | 29 +++- plugs/lib/tree.ts | 28 ++-- plugs/package.json | 4 +- server/api_server.ts | 6 +- tsconfig.json | 2 +- webapp/components/top_bar.tsx | 18 +-- webapp/parser.ts | 22 ++- yarn.lock | 114 ++++++++++++++- 19 files changed, 594 insertions(+), 53 deletions(-) create mode 100644 plugos/syscalls/jwt.ts create mode 100644 plugs/ghost/ghost.plug.yaml create mode 100644 plugs/ghost/ghost.ts diff --git a/common/tree.ts b/common/tree.ts index 62513ba2..9fd00dd3 100644 --- a/common/tree.ts +++ b/common/tree.ts @@ -9,7 +9,7 @@ export type MarkdownTree = { children?: MarkdownTree[]; }; -function treeToAST(text: string, n: SyntaxNode): MarkdownTree { +function treeToAST(text: string, n: SyntaxNode, offset = 0): MarkdownTree { let children: MarkdownTree[] = []; let nodeText: string | undefined; let child = n.firstChild; @@ -21,8 +21,8 @@ function treeToAST(text: string, n: SyntaxNode): MarkdownTree { if (children.length === 0) { children = [ { - from: n.from, - to: n.to, + from: n.from + offset, + to: n.to + offset, text: text.substring(n.from, n.to), }, ]; @@ -33,8 +33,8 @@ function treeToAST(text: string, n: SyntaxNode): MarkdownTree { let s = text.substring(index, child.from); if (s) { newChildren.push({ - from: index, - to: child.from, + from: index + offset, + to: child.from! + offset, text: s, }); } @@ -43,15 +43,15 @@ function treeToAST(text: string, n: SyntaxNode): MarkdownTree { } let s = text.substring(index, n.to); if (s) { - newChildren.push({ from: index, to: n.to, text: s }); + newChildren.push({ from: index + offset, to: n.to + offset, text: s }); } children = newChildren; } let result: MarkdownTree = { type: n.name, - from: n.from, - to: n.to, + from: n.from + offset, + to: n.to + offset, }; if (children.length > 0) { result.children = children; @@ -63,5 +63,25 @@ function treeToAST(text: string, n: SyntaxNode): MarkdownTree { } export function parse(text: string): MarkdownTree { - return treeToAST(text, wikiMarkdownLang.parser.parse(text).topNode); + let tree = treeToAST(text, wikiMarkdownLang.parser.parse(text).topNode); + // replaceNodesMatching(tree, (n): MarkdownTree | undefined | null => { + // if (n.type === "FencedCode") { + // let infoN = findNodeMatching(n, (n) => n.type === "CodeInfo"); + // let language = infoN!.children![0].text; + // let textN = findNodeMatching(n, (n) => n.type === "CodeText"); + // let text = textN!.children![0].text!; + // + // console.log(language, text); + // switch (language) { + // case "yaml": + // let parsed = StreamLanguage.define(yaml).parser.parse(text); + // let subTree = treeToAST(text, parsed.topNode, n.from); + // // console.log(JSON.stringify(subTree, null, 2)); + // subTree.type = "yaml"; + // return subTree; + // } + // } + // return; + // }); + return tree; } diff --git a/package.json b/package.json index 6c51e4d2..f4c9c375 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,11 @@ "context": "node" }, "test": { - "source": ["plugs/lib/tree.test.ts", "common/spaces/sync.test.ts"], + "source": [ + "plugs/lib/tree.test.ts", + "common/spaces/sync.test.ts" + ], + "includeNodeModules": ["@codemirror/legacy-modes"], "outputFormat": "commonjs", "isLibrary": true, "context": "node" @@ -45,27 +49,32 @@ "@codemirror/closebrackets": "^0.19.1", "@codemirror/collab": "^0.19.0", "@codemirror/commands": "^0.19.8", + "@codemirror/highlight": "^0.19.0", "@codemirror/history": "^0.19.2", + "@codemirror/lang-javascript": "^0.19.7", "@codemirror/lang-markdown": "^0.19.6", + "@codemirror/language": "^0.19.0", + "@codemirror/legacy-modes": "^0.19.1", "@codemirror/matchbrackets": "^0.19.4", "@codemirror/search": "^0.19.9", "@codemirror/state": "^0.19.7", + "@codemirror/stream-parser": "^0.19.9", "@codemirror/view": "^0.19.42", "@fortawesome/fontawesome-svg-core": "1.3.0", "@fortawesome/free-solid-svg-icons": "6.0.0", "@fortawesome/react-fontawesome": "0.1.17", - "@codemirror/highlight": "^0.19.0", - "@codemirror/language": "^0.19.0", - "@lezer/markdown": "^0.15.0", "@jest/globals": "^27.5.1", + "@lezer/markdown": "^0.15.0", "better-sqlite3": "^7.5.0", "body-parser": "^1.19.2", "buffer": "^6.0.3", "cors": "^2.8.5", + "dexie": "^3.2.1", "events": "^3.3.0", "express": "^4.17.3", - "dexie": "^3.2.1", + "fake-indexeddb": "^3.1.7", "jest": "^27.5.1", + "jsonwebtoken": "^8.5.1", "knex": "^1.0.4", "node-cron": "^3.0.0", "node-fetch": "2", @@ -76,8 +85,6 @@ "supertest": "^6.2.2", "vm2": "^3.9.9", "yaml": "^1.10.2", - "fake-indexeddb": "^3.1.7", - "yargs": "^17.3.1" }, "devDependencies": { diff --git a/plugos/environments/node_sandbox.ts b/plugos/environments/node_sandbox.ts index 2dcdd2fa..1a67bd47 100644 --- a/plugos/environments/node_sandbox.ts +++ b/plugos/environments/node_sandbox.ts @@ -11,8 +11,8 @@ import fs from "fs"; class NodeWorkerWrapper implements WorkerLike { onMessage?: (message: any) => Promise; - private worker: Worker; ready: Promise; + private worker: Worker; constructor(worker: Worker) { this.worker = worker; diff --git a/plugos/environments/node_worker.ts b/plugos/environments/node_worker.ts index e3c60960..ba5747d5 100644 --- a/plugos/environments/node_worker.ts +++ b/plugos/environments/node_worker.ts @@ -1,4 +1,5 @@ const { parentPort, workerData } = require("worker_threads"); +// @ts-ignore let vm2 = `${workerData}/vm2`; const { VM, VMScript } = require(vm2); @@ -15,9 +16,11 @@ let pendingRequests = new Map< let syscallReqId = 0; +// console.log("Here's crypto", crypto); + let vm = new VM({ sandbox: { - console: console, + console, self: { syscall: (name: string, ...args: any[]) => { return new Promise((resolve, reject) => { diff --git a/plugos/package.json b/plugos/package.json index c785d27b..dcdab16e 100644 --- a/plugos/package.json +++ b/plugos/package.json @@ -41,6 +41,7 @@ "@jest/globals": "^27.5.1", "@types/cors": "^2.8.12", "@types/express": "^4.17.13", + "@types/jsonwebtoken": "^8.5.8", "better-sqlite3": "^7.5.0", "body-parser": "^1.19.2", "cors": "^2.8.5", @@ -49,6 +50,7 @@ "express": "^4.17.3", "fake-indexeddb": "^3.1.7", "jest": "^27.5.1", + "jsonwebtoken": "^8.5.1", "knex": "^1.0.4", "node-cron": "^3.0.0", "node-fetch": "2", diff --git a/plugos/syscalls/jwt.ts b/plugos/syscalls/jwt.ts new file mode 100644 index 00000000..07c61e09 --- /dev/null +++ b/plugos/syscalls/jwt.ts @@ -0,0 +1,22 @@ +import jwt, { Algorithm } from "jsonwebtoken"; +import { SysCallMapping } from "../system"; + +export function jwtSyscalls(): SysCallMapping { + return { + "jwt.jwt": ( + ctx, + hexSecret: string, + id: string, + algorithm: Algorithm, + expiry: string, + audience: string + ): string => { + return jwt.sign({}, Buffer.from(hexSecret, "hex"), { + keyid: id, + algorithm: algorithm, + expiresIn: expiry, + audience: audience, + }); + }, + }; +} diff --git a/plugos/yarn.lock b/plugos/yarn.lock index 10ac9354..81353e5b 100644 --- a/plugos/yarn.lock +++ b/plugos/yarn.lock @@ -1260,6 +1260,13 @@ jest-matcher-utils "^27.0.0" pretty-format "^27.0.0" +"@types/jsonwebtoken@^8.5.8": + version "8.5.8" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.5.8.tgz#01b39711eb844777b7af1d1f2b4cf22fda1c0c44" + integrity sha512-zm6xBQpFDIDM6o9r6HSgDeIcLy82TKWctCXEPbJJcXb5AKmi5BNNdLXneixK4lplX3PqIVcwLBCGE/kAGnlD4A== + dependencies: + "@types/node" "*" + "@types/mime@^1": version "1.3.2" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" @@ -1675,6 +1682,11 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= + buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" @@ -2243,6 +2255,13 @@ dotenv@^7.0.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-7.0.0.tgz#a2be3cd52736673206e8a85fb5210eea29628e7c" integrity sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g== +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -3709,6 +3728,39 @@ json5@^2.1.2, json5@^2.2.0: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== +jsonwebtoken@^8.5.1: + version "8.5.1" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" + integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^5.6.0" + +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + kleur@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" @@ -3774,11 +3826,46 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= + lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= + lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" @@ -3914,7 +4001,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3: +ms@2.1.3, ms@^2.1.1: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -4754,7 +4841,7 @@ saxes@^5.0.1: dependencies: xmlchars "^2.2.0" -semver@^5.7.0, semver@^5.7.1: +semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== diff --git a/plugs/core/core.plug.yaml b/plugs/core/core.plug.yaml index 111e8ff9..0896c0e8 100644 --- a/plugs/core/core.plug.yaml +++ b/plugs/core/core.plug.yaml @@ -60,3 +60,8 @@ functions: path: ./materialized_queries.ts:updateMaterializedQueriesCommand command: name: "Materialized Queries: Update" + + parseCommand: + path: ./page.ts:parsePage + command: + name: Parse Document diff --git a/plugs/core/page.ts b/plugs/core/page.ts index 6342a96d..65905f4a 100644 --- a/plugs/core/page.ts +++ b/plugs/core/page.ts @@ -182,3 +182,7 @@ export async function clearPageIndex(page: string) { console.log("Clearing page index for page", page); await clearPageIndexForPage(page); } + +export async function parsePage() { + console.log(await parseMarkdown(await getText())); +} diff --git a/plugs/ghost/ghost.plug.yaml b/plugs/ghost/ghost.plug.yaml new file mode 100644 index 00000000..cde86b65 --- /dev/null +++ b/plugs/ghost/ghost.plug.yaml @@ -0,0 +1,15 @@ +functions: + downloadAllPostsCommand: + path: "./ghost.ts:downloadAllPostsCommand" + command: + name: "Ghost: Download Posts" + downloadAllPosts: + path: "./ghost.ts:downloadAllPosts" + env: server + publishPostCommand: + path: "./ghost.ts:publishPostCommand" + command: + name: "Ghost: Publish Post" + publishPost: + path: "./ghost.ts:publishPost" + env: server diff --git a/plugs/ghost/ghost.ts b/plugs/ghost/ghost.ts new file mode 100644 index 00000000..0bd2e494 --- /dev/null +++ b/plugs/ghost/ghost.ts @@ -0,0 +1,219 @@ +import { readPage, writePage } from "plugos-silverbullet-syscall/space"; +import { json } from "plugos-syscall/fetch"; +import YAML from "yaml"; +import { invokeFunction } from "plugos-silverbullet-syscall/system"; +import { getCurrentPage, getText } from "plugos-silverbullet-syscall/editor"; + +type Post = { + id: string; + uuid: string; + title: string; + slug: string; + mobiledoc: string; + status: "draft" | "published"; + visibility: string; + created_at: string; + upblished_at: string; + updated_at: string; + tags: Tag[]; + primary_tag: Tag; + url: string; + excerpt: string; +}; + +type Tag = { + id: string; + name: string; + slug: string; + description: string | null; +}; + +type MobileDoc = { + version: string; + atoms: any[]; + cards: Card[]; +}; + +type Card = any[]; + +function mobileDocToMarkdown(doc: string): string | null { + let mobileDoc = JSON.parse(doc) as MobileDoc; + if (mobileDoc.cards.length > 0 && mobileDoc.cards[0][0] === "markdown") { + return mobileDoc.cards[0][1].markdown; + } + return null; +} + +function markdownToMobileDoc(text: string): string { + return JSON.stringify({ + version: "0.3.1", + atoms: [], + cards: [["markdown", { markdown: text }]], + markups: [], + sections: [ + [10, 0], + [1, "p", []], + ], + }); +} + +class GhostAdmin { + private token?: string; + + constructor(private url: string, private key: string) {} + + async init() { + const [id, secret] = this.key.split(":"); + + this.token = await self.syscall( + "jwt.jwt", + secret, + id, + "HS256", + "5m", + "/v3/admin/" + ); + } + + async listPosts(): Promise { + let result = await json( + `${this.url}/ghost/api/v3/admin/posts?order=published_at+DESC`, + { + headers: { + Authorization: `Ghost ${this.token}`, + }, + } + ); + + return result.posts; + } + + async listMarkdownPosts(): Promise { + let markdownPosts: Post[] = []; + for (let post of await this.listPosts()) { + let mobileDoc = JSON.parse(post.mobiledoc) as MobileDoc; + if (mobileDoc.cards.length > 0 && mobileDoc.cards[0][0] === "markdown") { + markdownPosts.push(post); + } + } + return markdownPosts; + } + + async createPost(post: Partial): Promise { + let result = await json(`${this.url}/ghost/api/v3/admin/posts`, { + method: "POST", + headers: { + Authorization: `Ghost ${this.token}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + posts: [post], + }), + }); + return result.posts[0]; + } + + async updatePost(post: Partial): Promise { + let oldPost = await json( + `${this.url}/ghost/api/v3/admin/posts/${post.id}`, + { + headers: { + Authorization: `Ghost ${this.token}`, + "Content-Type": "application/json", + }, + } + ); + post.updated_at = oldPost.posts[0].updated_at; + let result = await json(`${this.url}/ghost/api/v3/admin/posts/${post.id}`, { + method: "PUT", + headers: { + Authorization: `Ghost ${this.token}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + posts: [post], + }), + }); + return result.posts[0]; + } +} + +type GhostConfig = { + url: string; + adminKey: string; + pagePrefix: string; +}; + +function postToMarkdown(post: Post): string { + let text = mobileDocToMarkdown(post.mobiledoc); + return `\n# ${post.title}\n${text}`; +} + +const publishedPostRegex = + /\n#\s*([^\n]+)\n([^$]+)$/; +const newPostRegex = /#\s*([^\n]+)\n([^$]+)$/; + +function markdownToPost(text: string): Partial { + let match = publishedPostRegex.exec(text); + if (match) { + let [, id, title, content] = match; + return { + id, + title, + mobiledoc: markdownToMobileDoc(content), + }; + } + match = newPostRegex.exec(text); + if (match) { + let [, title, content] = match; + return { + title, + status: "draft", + mobiledoc: markdownToMobileDoc(content), + }; + } + throw Error("Not a valid ghost post"); +} + +async function getConfig(): Promise { + let configPage = await readPage("ghost-config"); + return YAML.parse(configPage.text) as GhostConfig; +} + +export async function downloadAllPostsCommand() { + await invokeFunction("server", "downloadAllPosts"); +} +export async function downloadAllPosts() { + let config = await getConfig(); + let admin = new GhostAdmin(config.url, config.adminKey); + await admin.init(); + let allPosts = await admin.listMarkdownPosts(); + for (let post of allPosts) { + let text = mobileDocToMarkdown(post.mobiledoc); + text = `\n# ${post.title}\n${text}`; + await writePage(`${config.pagePrefix}${post.slug}`, text); + } +} +export async function publishPostCommand() { + await invokeFunction( + "server", + "publishPost", + await getCurrentPage(), + await getText() + ); +} + +export async function publishPost(name: string, text: string) { + let config = await getConfig(); + let admin = new GhostAdmin(config.url, config.adminKey); + await admin.init(); + let post = markdownToPost(text); + post.slug = name.substring(config.pagePrefix.length); + if (post.id) { + await admin.updatePost(post); + } else { + let newPost = await admin.createPost(post); + text = `\n${text}`; + await writePage(name, text); + } +} diff --git a/plugs/lib/tree.test.ts b/plugs/lib/tree.test.ts index d524eeb8..d9998c5c 100644 --- a/plugs/lib/tree.test.ts +++ b/plugs/lib/tree.test.ts @@ -1,6 +1,14 @@ import { expect, test } from "@jest/globals"; import { parse } from "../../common/tree"; -import { addParentPointers, collectNodesMatching, findParentMatching, nodeAtPos, renderMarkdown } from "./tree"; +import { + addParentPointers, + collectNodesMatching, + findParentMatching, + nodeAtPos, + removeParentPointers, + renderMarkdown, + replaceNodesMatching +} from "./tree"; const mdTest1 = ` # Heading @@ -31,6 +39,12 @@ Hello Sup`; +const mdTest3 = ` +\`\`\`yaml +name: something +\`\`\` +`; + test("Run a Node sandbox", async () => { let mdTree = parse(mdTest1); addParentPointers(mdTree); @@ -47,6 +61,15 @@ test("Run a Node sandbox", async () => { // Render back into markdown should be equivalent expect(renderMarkdown(mdTree)).toBe(mdTest1); - let mdTree2 = parse(mdTest2); - console.log(JSON.stringify(mdTree2, null, 2)); + removeParentPointers(mdTree); + replaceNodesMatching(mdTree, (n) => { + if (n.type === "Task") { + return { + type: "Tosk", + }; + } + }); + console.log(JSON.stringify(mdTree, null, 2)); + let mdTree3 = parse(mdTest3); + console.log(JSON.stringify(mdTree3, null, 2)); }); diff --git a/plugs/lib/tree.ts b/plugs/lib/tree.ts index d5a5dffb..70e1ff64 100644 --- a/plugs/lib/tree.ts +++ b/plugs/lib/tree.ts @@ -62,22 +62,20 @@ export function replaceNodesMatching( mdTree: MarkdownTree, substituteFn: (mdTree: MarkdownTree) => MarkdownTree | null | undefined ) { - let subst = substituteFn(mdTree); - if (subst !== undefined) { - if (!mdTree.parent) { - throw Error("Need parent pointers for this"); - } - let parentChildren = mdTree.parent.children!; - let pos = parentChildren.indexOf(mdTree); - if (subst) { - parentChildren.splice(pos, 1, subst); - } else { - // null = delete - parentChildren.splice(pos, 1); - } - } else if (mdTree.children) { + if (mdTree.children) { for (let child of mdTree.children) { - replaceNodesMatching(child, substituteFn); + let subst = substituteFn(child); + if (subst !== undefined) { + let pos = mdTree.children.indexOf(child); + if (subst) { + mdTree.children.splice(pos, 1, subst); + } else { + // null = delete + mdTree.children.splice(pos, 1); + } + } else { + replaceNodesMatching(child, substituteFn); + } } } } diff --git a/plugs/package.json b/plugs/package.json index f970caf0..79adb0e8 100644 --- a/plugs/package.json +++ b/plugs/package.json @@ -3,7 +3,9 @@ "version": "1.0.0", "dependencies": { "@jest/globals": "^27.5.1", + "@types/yaml": "^1.9.7", "plugos-silverbullet-syscall": "file:../plugos-silverbullet-syscall", - "plugos-syscall": "file:../plugos-syscall" + "plugos-syscall": "file:../plugos-syscall", + "yaml": "^2.0.0" } } diff --git a/server/api_server.ts b/server/api_server.ts index fe2603bb..217d59b4 100644 --- a/server/api_server.ts +++ b/server/api_server.ts @@ -19,6 +19,8 @@ import { EventedSpacePrimitives } from "../common/spaces/evented_space_primitive import { Space } from "../common/spaces/space"; import { safeRun } from "../webapp/util"; import { createSandbox } from "../plugos/environments/node_sandbox"; +import { jwtSyscalls } from "../plugos/syscalls/jwt"; +import { fetchSyscalls } from "../plugos/syscalls/fetch.node"; export class ExpressServer { app: Express; @@ -65,6 +67,8 @@ export class ExpressServer { system.registerSyscalls([], spaceSyscalls(this.space)); system.registerSyscalls([], eventSyscalls(this.eventHook)); system.registerSyscalls([], markdownSyscalls()); + system.registerSyscalls([], fetchSyscalls()); + system.registerSyscalls([], jwtSyscalls()); system.addHook(new EndpointHook(app, "/_/")); this.space.on({ @@ -220,7 +224,7 @@ export class ExpressServer { return res.send(`Plug ${plugName} not found`); } try { - console.log("Invoking", name, "with args", args); + console.log("Invoking", name); const result = await plug.invoke(name, args); res.status(200); res.send(result); diff --git a/tsconfig.json b/tsconfig.json index 50985554..02c2abff 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { - "include": ["webapp/**/*", "server/**/*", "plugos/**/*", "plugs/**/*"], + "include": ["webapp/**/*", "server/**/*", "plugos/**/*", "plugs/**/*", "common/**/*"], "compilerOptions": { "target": "esnext", "strict": true, diff --git a/webapp/components/top_bar.tsx b/webapp/components/top_bar.tsx index dd97d124..58ca47ef 100644 --- a/webapp/components/top_bar.tsx +++ b/webapp/components/top_bar.tsx @@ -33,15 +33,15 @@ export function TopBar({ {prettyName(pageName)} - + {/* {*/} + {/* // @ts-ignore*/} + {/* window.syncer();*/} + {/* e.stopPropagation();*/} + {/* }}*/} + {/*>*/} + {/* Sync*/} + {/**/} {notifications.length > 0 && (
{notifications.map((notification) => ( diff --git a/webapp/parser.ts b/webapp/parser.ts index 45b2495d..0b6a8c6b 100644 --- a/webapp/parser.ts +++ b/webapp/parser.ts @@ -1,7 +1,11 @@ import { styleTags, tags as t } from "@codemirror/highlight"; -import { BlockContext, LeafBlock, LeafBlockParser, MarkdownConfig, TaskList } from "@lezer/markdown"; -import { commonmark, mkLang } from "./markdown/markdown"; +import { BlockContext, LeafBlock, LeafBlockParser, MarkdownConfig, parseCode, TaskList } from "@lezer/markdown"; +import { commonmark, getCodeParser, mkLang } from "./markdown/markdown"; import * as ct from "./customtags"; +import { LanguageDescription, LanguageSupport } from "@codemirror/language"; +import { StreamLanguage } from "@codemirror/stream-parser"; +import { yaml } from "@codemirror/legacy-modes/mode/yaml"; +import { javascriptLanguage } from "@codemirror/lang-javascript"; export const pageLinkRegexPrefix = /^\[\[([\w\s\/:,\.@\-]+)\]\]/; @@ -126,6 +130,7 @@ const TagLink: MarkdownConfig = { }, ], }; + const WikiMarkdown = commonmark.configure([ WikiLink, AtMention, @@ -133,6 +138,19 @@ const WikiMarkdown = commonmark.configure([ TaskList, UnmarkedUrl, Comment, + parseCode({ + codeParser: getCodeParser([ + LanguageDescription.of({ + name: "yaml", + support: new LanguageSupport(StreamLanguage.define(yaml)), + }), + LanguageDescription.of({ + name: "javascript", + alias: ["js"], + support: new LanguageSupport(javascriptLanguage), + }), + ]), + }), { props: [ styleTags({ diff --git a/yarn.lock b/yarn.lock index 25eaa54c..6fae41cb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -402,6 +402,19 @@ "@codemirror/view" "^0.19.0" "@lezer/javascript" "^0.15.1" +"@codemirror/lang-javascript@^0.19.7": + version "0.19.7" + resolved "https://registry.yarnpkg.com/@codemirror/lang-javascript/-/lang-javascript-0.19.7.tgz#84581ef6abf2a16d78f017ffc96c2d6227de5eb5" + integrity sha512-DL9f3JLqOEHH9cIwEqqjnP5bkjdVXeECksLtV+/MbPm+l4H+AG+PkwZaJQ2oR1GfPZKh8MVSIE94aGWNkJP8WQ== + dependencies: + "@codemirror/autocomplete" "^0.19.0" + "@codemirror/highlight" "^0.19.7" + "@codemirror/language" "^0.19.0" + "@codemirror/lint" "^0.19.0" + "@codemirror/state" "^0.19.0" + "@codemirror/view" "^0.19.0" + "@lezer/javascript" "^0.15.1" + "@codemirror/lang-markdown@^0.19.6": version "0.19.6" resolved "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-0.19.6.tgz" @@ -426,6 +439,13 @@ "@lezer/common" "^0.15.5" "@lezer/lr" "^0.15.0" +"@codemirror/legacy-modes@^0.19.1": + version "0.19.1" + resolved "https://registry.yarnpkg.com/@codemirror/legacy-modes/-/legacy-modes-0.19.1.tgz#7dc3b5df1842060648f75764ab6919fcfce3ea1a" + integrity sha512-vYPLsD/ON+3SXhlGj9Qb3fpFNNU3Ya/AtDiv/g3OyqVzhh5vs5rAnOvk8xopGWRwppdhlNPD9VyXjiOmZUQtmQ== + dependencies: + "@codemirror/stream-parser" "^0.19.0" + "@codemirror/lint@^0.19.0": version "0.19.6" resolved "https://registry.npmjs.org/@codemirror/lint/-/lint-0.19.6.tgz" @@ -483,6 +503,18 @@ dependencies: "@codemirror/text" "^0.19.0" +"@codemirror/stream-parser@^0.19.0", "@codemirror/stream-parser@^0.19.9": + version "0.19.9" + resolved "https://registry.yarnpkg.com/@codemirror/stream-parser/-/stream-parser-0.19.9.tgz#34955ea91a8047cf72abebd5ce28f0d332aeca48" + integrity sha512-WTmkEFSRCetpk8xIOvV2yyXdZs3DgYckM0IP7eFi4ewlxWnJO/H4BeJZLs4wQaydWsAqTQoDyIwNH1BCzK5LUQ== + dependencies: + "@codemirror/highlight" "^0.19.0" + "@codemirror/language" "^0.19.0" + "@codemirror/state" "^0.19.0" + "@codemirror/text" "^0.19.0" + "@lezer/common" "^0.15.0" + "@lezer/lr" "^0.15.0" + "@codemirror/text@^0.19.0", "@codemirror/text@^0.19.2", "@codemirror/text@^0.19.6": version "0.19.6" resolved "https://registry.npmjs.org/@codemirror/text/-/text-0.19.6.tgz" @@ -1995,6 +2027,11 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= + buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" @@ -2661,6 +2698,13 @@ duplexer3@^0.1.4: resolved "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz" integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" @@ -4106,6 +4150,39 @@ json5@^2.1.2, json5@^2.2.0: dependencies: minimist "^1.2.5" +jsonwebtoken@^8.5.1: + version "8.5.1" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" + integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^5.6.0" + +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + keyv@^3.0.0: version "3.1.0" resolved "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz" @@ -4185,11 +4262,46 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= + lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= + lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz" @@ -5319,7 +5431,7 @@ semver-diff@^3.1.1: dependencies: semver "^6.3.0" -semver@^5.7.0, semver@^5.7.1: +semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==