diff --git a/.vscode/launch.json b/.vscode/launch.json index 3760f9d..4266a17 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,6 +11,14 @@ "skipFiles": ["/**"], "program": "${workspaceFolder}/dist/MakeMobiledoc.js", "outFiles": ["${workspaceFolder}/**/*.js"] + }, + { + "type": "pwa-node", + "request": "launch", + "name": "Markdown", + "skipFiles": ["/**"], + "program": "${workspaceFolder}/dist/Markdown.js", + "outFiles": ["${workspaceFolder}/**/*.js"] } ] } diff --git a/package-lock.json b/package-lock.json index 0fda809..c7651eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,10 +18,12 @@ "markdown-it-github-preamble": "^1.0.0", "markdown-it-multimd-table": "^4.1.3", "markdown-yaml-metadata-parser": "^3.0.0", - "mobiledoc-kit": "^0.14.0" + "mobiledoc-kit": "^0.14.0", + "yaml": "^2.1.1" }, "devDependencies": { "@types/node": "^14.14.31", + "@types/yaml": "^1.9.7", "ts-node": "^9.1.1", "typescript": "^4.3.2" }, @@ -64,6 +66,16 @@ "integrity": "sha512-x5W9s+8P4XteaxT/jKF0PSb7XEvo5VmqEWgsMlyeY4ZlLK8I6aH6g5TPPyDlLAep+GYf4kefb7HFyc7PAO3m+Q==", "dev": true }, + "node_modules/@types/yaml": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@types/yaml/-/yaml-1.9.7.tgz", + "integrity": "sha512-8WMXRDD1D+wCohjfslHDgICd2JtMATZU8CkhH8LVJqcJs6dyYj5TGptzP8wApbmEullGBSsCEzzap73DQ1HJaA==", + "deprecated": "This is a stub types definition. yaml provides its own type definitions, so you do not need this installed.", + "dev": true, + "dependencies": { + "yaml": "*" + } + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -502,6 +514,14 @@ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" }, + "node_modules/yaml": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.1.tgz", + "integrity": "sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==", + "engines": { + "node": ">= 14" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", @@ -548,6 +568,15 @@ "integrity": "sha512-x5W9s+8P4XteaxT/jKF0PSb7XEvo5VmqEWgsMlyeY4ZlLK8I6aH6g5TPPyDlLAep+GYf4kefb7HFyc7PAO3m+Q==", "dev": true }, + "@types/yaml": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@types/yaml/-/yaml-1.9.7.tgz", + "integrity": "sha512-8WMXRDD1D+wCohjfslHDgICd2JtMATZU8CkhH8LVJqcJs6dyYj5TGptzP8wApbmEullGBSsCEzzap73DQ1HJaA==", + "dev": true, + "requires": { + "yaml": "*" + } + }, "arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -888,6 +917,11 @@ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" }, + "yaml": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.1.tgz", + "integrity": "sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==" + }, "yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index 1a828c1..0d286d8 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "license": "Apache-2.0", "devDependencies": { "@types/node": "^14.14.31", + "@types/yaml": "^1.9.7", "ts-node": "^9.1.1", "typescript": "^4.3.2" }, @@ -38,6 +39,7 @@ "markdown-it-github-preamble": "^1.0.0", "markdown-it-multimd-table": "^4.1.3", "markdown-yaml-metadata-parser": "^3.0.0", - "mobiledoc-kit": "^0.14.0" + "mobiledoc-kit": "^0.14.0", + "yaml": "^2.1.1" } } diff --git a/src/Markdown.ts b/src/Markdown.ts index 65ddf52..4416872 100644 --- a/src/Markdown.ts +++ b/src/Markdown.ts @@ -2,109 +2,22 @@ * Load a file, parse it through markdonw-it and display its components */ -import fs from "fs"; -import MarkdownIt from "markdown-it"; -import Token from "markdown-it/lib/token"; -import metadataParser from "markdown-yaml-metadata-parser"; +import { Convertor } from "./convertor/Processors"; +const GhostAdminAPI = require("@tryghost/admin-api"); -//const tabler = require("markdown-it-multimd-table"); +let convertor = new Convertor("./src/data/markdown-it-example.md"); -interface Stack { - tag: string; - meta: any; -} +let mdoc: string = convertor.process(); -interface Process { - content: string; - token: Token; -} - -function processBlock(buffer: string[], t: Token): Process { - let tbuf: string[] = []; - if (t.map != null) - for (let i = t.map[0]; i < t.map[1]; i++) tbuf.push(buffer[i]); - - return { - content: tbuf.join("\n"), - token: t, - }; -} - -let v: string = fs.readFileSync("./src/data/markdown-it-example.md", { - encoding: "utf8", - flag: "r", +// Configure the client +const api = new GhostAdminAPI({ + url: "http://localhost:2368", + // Admin API key goes here + key: "62ac6f5ab1479d0001082bd4:73341f843a5be78647c6f7e47d43d6cb09ec323df6d5706915df4685b3d46ce7", + version: "v4.0", }); -const parsed_md = metadataParser(v); - -var src = MarkdownIt(); -//src.use(tabler); - -//import markdownItGithubPreamble from "markdown-it-github-preamble"; -//src.use(markdownItGithubPreamble); -let lines: string[] = parsed_md.content.split("\n"); -let tokens: Token[] = src.parse(parsed_md.content, { references: {} }); -let stack: Stack[] = []; - -//let tableBuffer: string[] = []; -let isTable = false; - -//console.log(v); -tokens.forEach((value: Token) => { - let components: string[] = value.type.split("_"); - let action: string | undefined = components.pop(); - let name = components.join("_"); - switch (action) { - case "open": - if (name === "table") isTable = true; - stack.push({ tag: value.tag, meta: value.meta } as never); - if (isTable) { - processBlock(lines, value); - } - break; - case "close": - if (name === "table") isTable = false; - stack.pop(); - break; - case "fence": - processBlock(lines, value); - break; - case "inline": - let t: string = ""; - let m: string = ""; - - stack.forEach((value) => { - t += "-" + value.tag; - m += ">" + JSON.stringify(value.meta); - }); - if (isTable) { - console.log("++++", JSON.stringify(value)); - } else { - console.log(t, m, value.content); - } - break; - // TODO: Make the 'FENCE' tag too! - default: - console.log("ERROR", components, action); - //throw new Error("Con't know tag" + action); - } - /* - console.log( - value.level, - "".padEnd(value.level, " "), - JSON.stringify({ - type: value.type, - //level: value.level, - markup: value.markup, - //meta: value.meta, - content: value.content, - block: value.block, - //map: value.map, - //tag: value.tag, - //nesting: value.nesting, - info: value.info, - //children: value.children, - }) - ); - */ -}); +api.posts + .add(JSON.parse(mdoc)) + .then((response: any) => console.log(JSON.stringify(response))) + .catch((error: any) => console.error(error)); diff --git a/src/Markdown2Mobiledoc.ts b/src/Markdown2Mobiledoc.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/convertor/Processors.ts b/src/convertor/Processors.ts new file mode 100644 index 0000000..ca14051 --- /dev/null +++ b/src/convertor/Processors.ts @@ -0,0 +1,179 @@ +import { PostNodeBuilder, Renderer } from "mobiledoc-kit"; +import fs from "fs"; +import MarkdownIt from "markdown-it"; +import Token from "markdown-it/lib/token"; +import metadataParser from "markdown-yaml-metadata-parser"; + +interface Stack { + tag: string; + meta: any; + token: Token; +} + +const renderer: any = Renderer; + +const predefinedBlocks = ["blockquote", "table", "bullet_list", "ordered_list"]; + +export class Convertor { + markups: Record = {}; + markers: Record = {}; + sections: any[] = []; + builder: any = new PostNodeBuilder(); + + // The markdown bits (preamble and content) + // + preamble: string = ""; + content: string = ""; + + // The line-by-line split of the markdown + // + lines: string[] = []; + tokens: Token[] = []; + + /** + * Constructor + * + * @param filename the file name + */ + constructor(filename: string) { + this.initialize(filename); + } + + /** + * Initialize/reset the convertor class. + * + * @param filename The file to be loaded + */ + public initialize(filename: string): Convertor { + this.builder = new PostNodeBuilder(); + let content = fs.readFileSync(filename, { + encoding: "utf8", + flag: "r", + }); + const parsed = metadataParser(content); + this.preamble = parsed.preamble; + this.content = parsed.content; + + // Split the lines + // + this.lines = this.content.split("\n"); + + const markdownProcessor = MarkdownIt("commonmark"); + this.tokens = markdownProcessor.parse(this.content, { references: {} }); + return this; + } + + public process(): string { + if (this.preamble === "" && this.content == "") { + throw new Error( + "Convertor not initialised. Please use initalize(...) first" + ); + } + + let stack: Stack[] = []; + let blockNesting = 0; + + // Loop through tokens + this.tokens.forEach((value: Token) => { + let components: string[] = value.type.split("_"); + let action: string | undefined = components.pop(); + let name = components.join("_"); + switch (action) { + case "open": + if (name in predefinedBlocks) blockNesting++; + stack.push({ + tag: value.tag, + meta: value.meta, + token: value, + } as never); + break; + case "close": + if (name in predefinedBlocks) blockNesting--; + stack.pop(); + break; + case "fence": + if (blockNesting == 0) { + console.log("Process fence..."); + } + break; + case "inline": + /* + if (blockNesting == 0) { + let t: string = ""; + let m: string = ""; + + stack.forEach((value) => { + t += "-" + value.tag; + m += ">" + JSON.stringify(value.meta); + }); + console.log(t, m, value.content); + } + */ + break; + // TODO: Make the 'FENCE' tag too! + default: + console.log("ERROR", components, action); + //throw new Error("Con't know tag" + action); + } + if (value.level == 0) { + switch (name) { + case "table": + this.processBlock(value); + break; + case "heading": + //this.sections.push(this.processHeader(value)); + break; + default: + this.processBlock(value); + } + } else if (blockNesting == 0 && action === "inline") { + console.log(value.level, stack[0].token.type); + switch (stack[0].token.type) { + case "heading_open": + this.sections.push(this.processHeader(stack, value)); + break; + default: + this.processInline(stack, value); + } + } + }); + + // Aggregate the sections into a new mobiledoc + let post = this.builder.createPost(this.sections); + let result = JSON.stringify({ + title: "A post", + mobiledoc: `${JSON.stringify(renderer.render(post, "0.3.1"))}`, + status: "draft", + author: "laur.ivan@gmail.com", + }); + console.log("BLOG ENTRY------------------------------------"); + console.log(result); + console.log("BLOG ENTRY------------------------------------"); + return result; + } + + private processInline(stack: Stack[], t: Token) { + console.log("*********************************************"); + console.log(JSON.stringify(t), stack.length); + } + + private processBlock(t: Token) { + let tbuf: string[] = []; + if (t.map != null) + for (let i = t.map[0]; i < t.map[1]; i++) tbuf.push(this.lines[i]); + //console.log("----------------------------------------------------"); + //console.log(JSON.stringify(t)); + return { + content: tbuf.join("\n"), + token: t, + }; + } + + private processHeader(stack: Stack[], t: Token): any { + console.log("+++++++++++++++++++++++++++++++++++++++++++++"); + console.log(JSON.stringify(t)); + + let marker = this.builder.createMarker(t.content, []); + return this.builder.createMarkupSection(stack[0].tag, [marker]); + } +} diff --git a/src/data/ghost-2.json b/src/data/ghost-2.json index d3a3bd6..5372aad 100644 --- a/src/data/ghost-2.json +++ b/src/data/ghost-2.json @@ -4,14 +4,14 @@ "uuid": "41ced005-2d7c-4965-907c-0c87f5bfe1af", "title": "A post", "slug": "a-post", - "mobiledoc": "{\"version\":\"0.3.1\",\"atoms\":[],\"cards\":[[\"markdown\",{\"markdown\":\"```js\\nlet value = 0\\n```\"}],[\"image\",{\"src\":\"http://localhost:2368/content/images/2022/06/ubuntu_black-orange_hex-1.png\",\"width\":758,\"height\":171,\"alt\":\"Ubuntu logo\"}]],\"markups\":[[\"strong\"]],\"sections\":[[1,\"p\",[[0,[0],0,\"Hi \"],[0,[],1,\" Bye\"]]],[10,0],[10,1],[1,\"p\",[]]],\"ghostVersion\":\"4.0\"}", + "mobiledoc": "{\"version\":\"0.3.1\",\"atoms\":[],\"cards\":[[\"markdown\",{\"markdown\":\"```js\\nlet value = 0\\n```\"}],[\"image\",{\"src\":\"http://localhost:2368/content/images/2022/06/ubuntu_black-orange_hex-1.png\",\"width\":758,\"height\":171,\"alt\":\"Ubuntu logo\"}]],\"markups\":[[\"strong\"]],\"sections\":[[1,\"p\",[[0,[0],0,\"Hi \"],[0,[],1,\" Bye\"]]],[10,0],[10,1],[1,\"h2\",[[0,[],0,\"A title\"]]],[1,\"h3\",[[0,[],0,\"A second title\"]]]],\"ghostVersion\":\"4.0\"}", "comment_id": "62b81b265fb4ca000119c894", "feature_image": "http://localhost:2368/content/images/2022/06/dragonfly.jpg", "featured": false, "status": "published", "visibility": "public", "created_at": "2022-06-26T08:39:02.000Z", - "updated_at": "2022-06-26T19:39:24.000Z", + "updated_at": "2022-06-28T13:31:24.000Z", "published_at": "2022-06-26T08:39:23.000Z", "custom_excerpt": null, "codeinjection_head": null, @@ -39,7 +39,7 @@ "canonical_url": null, "accent_color": null, "created_at": "2022-05-31T12:27:26.000Z", - "updated_at": "2022-06-26T19:39:24.000Z", + "updated_at": "2022-06-28T13:31:24.000Z", "url": "http://localhost:2368/tag/news/" } ], @@ -61,9 +61,9 @@ "meta_title": null, "meta_description": null, "tour": null, - "last_seen": "2022-06-26T19:50:38.000Z", + "last_seen": "2022-06-28T13:30:38.000Z", "created_at": "2022-05-31T12:27:26.000Z", - "updated_at": "2022-06-26T19:50:38.000Z", + "updated_at": "2022-06-28T13:30:38.000Z", "roles": [ { "id": "629609ae8b281d00012eb297", @@ -123,9 +123,9 @@ "meta_title": null, "meta_description": null, "tour": null, - "last_seen": "2022-06-26T19:50:38.000Z", + "last_seen": "2022-06-28T13:30:38.000Z", "created_at": "2022-05-31T12:27:26.000Z", - "updated_at": "2022-06-26T19:50:38.000Z", + "updated_at": "2022-06-28T13:30:38.000Z", "roles": [ { "id": "629609ae8b281d00012eb297", @@ -157,12 +157,12 @@ "canonical_url": null, "accent_color": null, "created_at": "2022-05-31T12:27:26.000Z", - "updated_at": "2022-06-26T19:39:24.000Z", + "updated_at": "2022-06-28T13:31:24.000Z", "url": "http://localhost:2368/tag/news/" }, "email_segment": "all", "url": "http://localhost:2368/a-post/", - "excerpt": "Hi Bye\n\nlet value = 0\n\n", + "excerpt": "Hi Bye\n\nlet value = 0\n\n\n\n\nA title\n\n\nA second title", "og_image": null, "og_title": null, "og_description": null,