From 72efe0cd5cc935f0d23ce89b8639fb264fa08c31 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 17 Aug 2023 10:17:52 +0700 Subject: [PATCH] feat(developer): markdown conversion for description in package editor --- developer/src/kmc-keyboard-info/package.json | 2 +- developer/src/kmc-keyboard-info/src/index.ts | 7 +-- .../build/khmer_angkor.keyboard_info | 2 +- developer/src/kmc-package/package.json | 3 +- .../kmc-package/src/compiler/kmp-compiler.ts | 36 +++++++------ .../src/kmc-package/src/compiler/markdown.ts | 50 +++++++++++++++++++ .../test/fixtures/kmp_2.0/kmp.json | 2 +- .../src/kmc-package/test/test-markdown.ts | 20 ++++++++ package-lock.json | 36 +++++++++---- 9 files changed, 124 insertions(+), 34 deletions(-) create mode 100644 developer/src/kmc-package/src/compiler/markdown.ts create mode 100644 developer/src/kmc-package/test/test-markdown.ts diff --git a/developer/src/kmc-keyboard-info/package.json b/developer/src/kmc-keyboard-info/package.json index 4f96e31c7a..ad3572ec1e 100644 --- a/developer/src/kmc-keyboard-info/package.json +++ b/developer/src/kmc-keyboard-info/package.json @@ -28,7 +28,7 @@ "@keymanapp/kmc-package": "*" }, "devDependencies": { - "@types/chai": "^4.1.7", + "@types/chai": "^4.3.5", "@types/mocha": "^5.2.7", "@types/node": "^20.4.1", "c8": "^7.12.0", diff --git a/developer/src/kmc-keyboard-info/src/index.ts b/developer/src/kmc-keyboard-info/src/index.ts index f221754b08..8da0ef34da 100644 --- a/developer/src/kmc-keyboard-info/src/index.ts +++ b/developer/src/kmc-keyboard-info/src/index.ts @@ -152,7 +152,7 @@ export class KeyboardInfoCompiler { // description if(sources.kmpJsonData.info.description?.description) { - keyboard_info.description = markDownToHTML(sources.kmpJsonData.info.description?.description); + keyboard_info.description = sources.kmpJsonData.info.description?.description.trim(); } // extract the language identifiers from the language metadata arrays for @@ -438,10 +438,5 @@ export class KeyboardInfoCompiler { ); } } - } -function markDownToHTML(markdown: string): string { - // TODO - return markdown; -} diff --git a/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/build/khmer_angkor.keyboard_info b/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/build/khmer_angkor.keyboard_info index 0b854bda12..7d85bbfb6c 100644 --- a/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/build/khmer_angkor.keyboard_info +++ b/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/build/khmer_angkor.keyboard_info @@ -23,7 +23,7 @@ "languageName": "Khmer" } }, - "description": "Khmer Unicode keyboard layout based on the NiDA keyboard layout. Automatically corrects many common keying errors.", + "description": "

Khmer Unicode keyboard layout based on the NiDA keyboard layout. Automatically corrects many common keying errors.

", "related": { "khmer10": { "deprecates": true diff --git a/developer/src/kmc-package/package.json b/developer/src/kmc-package/package.json index 5ae24b58b1..8a9882acb1 100644 --- a/developer/src/kmc-package/package.json +++ b/developer/src/kmc-package/package.json @@ -31,7 +31,8 @@ }, "dependencies": { "@keymanapp/common-types": "*", - "jszip": "^3.7.0" + "jszip": "^3.7.0", + "marked": "^7.0.0" }, "devDependencies": { "@keymanapp/developer-test-helpers": "*", diff --git a/developer/src/kmc-package/src/compiler/kmp-compiler.ts b/developer/src/kmc-package/src/compiler/kmp-compiler.ts index 9ffde6e69a..c384ded31a 100644 --- a/developer/src/kmc-package/src/compiler/kmp-compiler.ts +++ b/developer/src/kmc-package/src/compiler/kmp-compiler.ts @@ -11,6 +11,7 @@ import { transcodeToCP1252 } from './cp1252.js'; import { MIN_LM_FILEVERSION_KMP_JSON, PackageVersionValidator } from './package-version-validator.js'; import { PackageKeyboardTargetValidator } from './package-keyboard-target-validator.js'; import { PackageMetadataUpdater } from './package-metadata-updater.js'; +import { markdownToHTML } from './markdown.js'; const KMP_JSON_FILENAME = 'kmp.json'; const KMP_INF_FILENAME = 'kmp.inf'; @@ -246,26 +247,33 @@ export class KmpCompiler { // Helper functions - private kpsInfoToKmpInfo(info: KpsFile.KpsFileInfo): KmpJsonFile.KmpJsonFileInfo { - let ni: KmpJsonFile.KmpJsonFileInfo = {}; + private kpsInfoToKmpInfo(kpsInfo: KpsFile.KpsFileInfo): KmpJsonFile.KmpJsonFileInfo { + let kmpInfo: KmpJsonFile.KmpJsonFileInfo = {}; - const keys: [(keyof KpsFile.KpsFileInfo), (keyof KmpJsonFile.KmpJsonFileInfo)][] = [ - ['author','author'], - ['copyright','copyright'], - ['name','name'], - ['version','version'], - ['webSite','website'], - ['description','description'], + const keys: [(keyof KpsFile.KpsFileInfo), (keyof KmpJsonFile.KmpJsonFileInfo), boolean][] = [ + ['author','author',false], + ['copyright','copyright',false], + ['name','name',false], + ['version','version',false], + ['webSite','website',false], + ['description','description',true], ]; - for (let [src,dst] of keys) { - if (info[src]) { - ni[dst] = {description: (info[src]._ ?? (typeof info[src] == 'string' ? info[src].toString() : '').trim())}; - if(info[src].$ && info[src].$.URL) ni[dst].url = info[src].$.URL.trim(); + for (let [src,dst,isMarkdown] of keys) { + if (kpsInfo[src]) { + kmpInfo[dst] = { + description: (kpsInfo[src]._ ?? (typeof kpsInfo[src] == 'string' ? kpsInfo[src].toString() : '')).trim() + }; + if(isMarkdown) { + kmpInfo[dst].description = markdownToHTML(kmpInfo[dst].description, false).trim(); + } + if(kpsInfo[src].$?.URL) { + kmpInfo[dst].url = kpsInfo[src].$.URL.trim(); + } } } - return ni; + return kmpInfo; }; private arrayWrap(a: unknown) { diff --git a/developer/src/kmc-package/src/compiler/markdown.ts b/developer/src/kmc-package/src/compiler/markdown.ts new file mode 100644 index 0000000000..75ed984fa6 --- /dev/null +++ b/developer/src/kmc-package/src/compiler/markdown.ts @@ -0,0 +1,50 @@ +/** + * Markdown transform for our `description` field. Tweaked to disable all inline + * HTML, because we want descriptions to be short and sweet, and don't need any + * of the more complex formatting that inline HTML affords. + */ + +// +// Note: using marked 7.0.0. +// https://github.com/markedjs/marked/issues/2926 +// +// Version 7.0.1 introduced a TypeScript 5.0+ feature `export type *` which causes: +// +// ../../../node_modules/marked/lib/marked.d.ts:722:5 - error TS1383: Only named exports may use 'export type'. +// 722 export type * from "MarkedOptions"; +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// https://github.com/markedjs/marked/compare/v7.0.0...v7.0.1#diff-32d87a2bc59f429470ccf7afc8ae8818914d4d45c3f7c5b1767c6a0a240b55c9R449-R451 +// +// When we move to TS 5.0, we can upgrade marked. +// +import { Marked } from 'marked'; + +/* + Markdown rendering: we don't want to use the global object, because this + pollutes the settings for all modules. So we construct our own instance, + so that we can strip all inline HTML; we don't need any +*/ + +const renderer = { + html(_html:string, _block:boolean) { + // we don't allow inline HTML + return ''; + } +} +const markedStripHtml = new Marked({renderer}); +const marked = new Marked(); + +/** + * + * @param markdown + * @param allowHTML + * @returns + */ +export function markdownToHTML(markdown: string, allowHTML: boolean): string { + // : .parse can return a Promise if async=true. We don't pass ths + // option, and this sync usage isn't separated out in the types for + // Marked.prototype.parse, so avoids tsc complaints here. + const html = (allowHTML ? marked : markedStripHtml).parse(markdown.trim()); + return html; +} diff --git a/developer/src/kmc-package/test/fixtures/kmp_2.0/kmp.json b/developer/src/kmc-package/test/fixtures/kmp_2.0/kmp.json index bcfc7e8d35..8cc2f03cd6 100644 --- a/developer/src/kmc-package/test/fixtures/kmp_2.0/kmp.json +++ b/developer/src/kmc-package/test/fixtures/kmp_2.0/kmp.json @@ -26,7 +26,7 @@ "url": "https://keyman.com/keyboards/khmer_angkor" }, "description": { - "description": "# Khmer Angkor\r\n\r\nKhmer Unicode keyboard layout based on the NiDA keyboard layout. Automatically corrects many common keying errors, including:\r\n\r\n* Using wrong vowel combination\r\n* Typing clusters out of order\r\n* Typing wrong mark for consonant shifters\r\n* And more!" + "description": "

Khmer Angkor

\n

Khmer Unicode keyboard layout based on the NiDA keyboard layout. Automatically corrects many common keying errors, including:

\n
    \n
  • Using wrong vowel combination
  • \n
  • Typing clusters out of order
  • \n
  • Typing wrong mark for consonant shifters
  • \n
  • And more!
  • \n
" } }, "files": [ diff --git a/developer/src/kmc-package/test/test-markdown.ts b/developer/src/kmc-package/test/test-markdown.ts new file mode 100644 index 0000000000..348d6426a7 --- /dev/null +++ b/developer/src/kmc-package/test/test-markdown.ts @@ -0,0 +1,20 @@ +import { assert } from 'chai'; +import 'mocha'; +import { markdownToHTML } from '../src/compiler/markdown.js'; + +describe('markdownToHTML', function () { + it('should convert markdown into HTML', function() { + const html = markdownToHTML('# heading\n\n**bold** and _beautiful_', true); + assert.equal(html, `

heading

\n

bold and beautiful

\n`); + }); + + it('should strip inline html if asked to do so', function() { + const html = markdownToHTML(`# heading\n\n\n\n**bold** and _beautiful_`, false); + assert.equal(html, `

heading

\n

bold and beautiful

\n`); + }); + + it('should keep inline html if asked to do so', function() { + const html = markdownToHTML(`# heading\n\n\n\n**bold** and _beautiful_`, true); + assert.equal(html, `

heading

\n\n\n

bold and beautiful

\n`); + }); +}); diff --git a/package-lock.json b/package-lock.json index 405d2154cf..7caa96d15a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -997,10 +997,11 @@ "name": "@keymanapp/kmc-keyboard-info", "license": "MIT", "dependencies": { - "@keymanapp/common-types": "*" + "@keymanapp/common-types": "*", + "@keymanapp/kmc-package": "*" }, "devDependencies": { - "@types/chai": "^4.1.7", + "@types/chai": "^4.3.5", "@types/mocha": "^5.2.7", "@types/node": "^20.4.1", "c8": "^7.12.0", @@ -2138,7 +2139,8 @@ "license": "MIT", "dependencies": { "@keymanapp/common-types": "*", - "jszip": "^3.7.0" + "jszip": "^3.7.0", + "marked": "^7.0.0" }, "devDependencies": { "@keymanapp/developer-test-helpers": "*", @@ -7392,8 +7394,9 @@ } }, "node_modules/https-proxy-agent": { - "version": "5.0.0", - "license": "MIT", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dependencies": { "agent-base": "6", "debug": "4" @@ -8512,6 +8515,17 @@ "markdown-it": "bin/markdown-it.js" } }, + "node_modules/marked": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-7.0.0.tgz", + "integrity": "sha512-7Gv1Ry8tqR352ElQOQfxdGpIh8kNZh/yYjNCxAQCN1DDbY4bCTG3qDCSkZWlRElSseeEILDxkY/G9w7cgziBNw==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 16" + } + }, "node_modules/mdurl": { "version": "1.0.1", "dev": true, @@ -9756,8 +9770,9 @@ } }, "node_modules/punycode": { - "version": "2.1.1", - "license": "MIT", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", "engines": { "node": ">=6" } @@ -11290,14 +11305,15 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.5.0", - "license": "MIT", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", "engines": { "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": {