feat(developer): markdown conversion for description in package editor

This commit is contained in:
Marc Durdin 2023-08-17 10:17:52 +07:00
parent 47e05a7b05
commit 72efe0cd5c
9 changed files with 124 additions and 34 deletions

View file

@ -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",

View file

@ -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;
}

View file

@ -23,7 +23,7 @@
"languageName": "Khmer"
}
},
"description": "Khmer Unicode keyboard layout based on the NiDA keyboard layout. Automatically corrects many common keying errors.",
"description": "<p>Khmer Unicode keyboard layout based on the NiDA keyboard layout. Automatically corrects many common keying errors.</p>",
"related": {
"khmer10": {
"deprecates": true

View file

@ -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": "*",

View file

@ -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) {

View file

@ -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 {
// <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 <string> avoids tsc complaints here.
const html = <string> (allowHTML ? marked : markedStripHtml).parse(markdown.trim());
return html;
}

View file

@ -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": "<h1>Khmer Angkor</h1>\n<p>Khmer Unicode keyboard layout based on the NiDA keyboard layout. Automatically corrects many common keying errors, including:</p>\n<ul>\n<li>Using wrong vowel combination</li>\n<li>Typing clusters out of order</li>\n<li>Typing wrong mark for consonant shifters</li>\n<li>And more!</li>\n</ul>"
}
},
"files": [

View file

@ -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, `<h1>heading</h1>\n<p><strong>bold</strong> and <em>beautiful</em></p>\n`);
});
it('should strip inline html if asked to do so', function() {
const html = markdownToHTML(`# heading\n\n<script>alert('gotcha')</script>\n\n**bold** and _beautiful_`, false);
assert.equal(html, `<h1>heading</h1>\n<p><strong>bold</strong> and <em>beautiful</em></p>\n`);
});
it('should keep inline html if asked to do so', function() {
const html = markdownToHTML(`# heading\n\n<script>alert('gotcha')</script>\n\n**bold** and _beautiful_`, true);
assert.equal(html, `<h1>heading</h1>\n<script>alert('gotcha')</script>\n\n<p><strong>bold</strong> and <em>beautiful</em></p>\n`);
});
});

36
package-lock.json generated
View file

@ -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": {