Skip to content

Commit 651af2f

Browse files
authored
Merge pull request #9476 from keymanapp/feat/developer/keyboard-info-markdown
feat(developer): convert markdown description to html 🎺
2 parents 6e8cc16 + fcf2b51 commit 651af2f

File tree

9 files changed

+124
-34
lines changed

9 files changed

+124
-34
lines changed

developer/src/kmc-keyboard-info/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"@keymanapp/kmc-package": "*"
2929
},
3030
"devDependencies": {
31-
"@types/chai": "^4.1.7",
31+
"@types/chai": "^4.3.5",
3232
"@types/mocha": "^5.2.7",
3333
"@types/node": "^20.4.1",
3434
"c8": "^7.12.0",

developer/src/kmc-keyboard-info/src/index.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ export class KeyboardInfoCompiler {
152152
// description
153153

154154
if(sources.kmpJsonData.info.description?.description) {
155-
keyboard_info.description = markDownToHTML(sources.kmpJsonData.info.description?.description);
155+
keyboard_info.description = sources.kmpJsonData.info.description?.description.trim();
156156
}
157157

158158
// extract the language identifiers from the language metadata arrays for
@@ -438,10 +438,5 @@ export class KeyboardInfoCompiler {
438438
);
439439
}
440440
}
441-
442441
}
443442

444-
function markDownToHTML(markdown: string): string {
445-
// TODO
446-
return markdown;
447-
}

developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/build/khmer_angkor.keyboard_info

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"languageName": "Khmer"
2424
}
2525
},
26-
"description": "Khmer Unicode keyboard layout based on the NiDA keyboard layout. Automatically corrects many common keying errors.",
26+
"description": "<p>Khmer Unicode keyboard layout based on the NiDA keyboard layout. Automatically corrects many common keying errors.</p>",
2727
"related": {
2828
"khmer10": {
2929
"deprecates": true

developer/src/kmc-package/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
},
3232
"dependencies": {
3333
"@keymanapp/common-types": "*",
34-
"jszip": "^3.7.0"
34+
"jszip": "^3.7.0",
35+
"marked": "^7.0.0"
3536
},
3637
"devDependencies": {
3738
"@keymanapp/developer-test-helpers": "*",

developer/src/kmc-package/src/compiler/kmp-compiler.ts

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { transcodeToCP1252 } from './cp1252.js';
1111
import { MIN_LM_FILEVERSION_KMP_JSON, PackageVersionValidator } from './package-version-validator.js';
1212
import { PackageKeyboardTargetValidator } from './package-keyboard-target-validator.js';
1313
import { PackageMetadataUpdater } from './package-metadata-updater.js';
14+
import { markdownToHTML } from './markdown.js';
1415

1516
const KMP_JSON_FILENAME = 'kmp.json';
1617
const KMP_INF_FILENAME = 'kmp.inf';
@@ -246,26 +247,33 @@ export class KmpCompiler {
246247

247248
// Helper functions
248249

249-
private kpsInfoToKmpInfo(info: KpsFile.KpsFileInfo): KmpJsonFile.KmpJsonFileInfo {
250-
let ni: KmpJsonFile.KmpJsonFileInfo = {};
250+
private kpsInfoToKmpInfo(kpsInfo: KpsFile.KpsFileInfo): KmpJsonFile.KmpJsonFileInfo {
251+
let kmpInfo: KmpJsonFile.KmpJsonFileInfo = {};
251252

252-
const keys: [(keyof KpsFile.KpsFileInfo), (keyof KmpJsonFile.KmpJsonFileInfo)][] = [
253-
['author','author'],
254-
['copyright','copyright'],
255-
['name','name'],
256-
['version','version'],
257-
['webSite','website'],
258-
['description','description'],
253+
const keys: [(keyof KpsFile.KpsFileInfo), (keyof KmpJsonFile.KmpJsonFileInfo), boolean][] = [
254+
['author','author',false],
255+
['copyright','copyright',false],
256+
['name','name',false],
257+
['version','version',false],
258+
['webSite','website',false],
259+
['description','description',true],
259260
];
260261

261-
for (let [src,dst] of keys) {
262-
if (info[src]) {
263-
ni[dst] = {description: (info[src]._ ?? (typeof info[src] == 'string' ? info[src].toString() : '').trim())};
264-
if(info[src].$ && info[src].$.URL) ni[dst].url = info[src].$.URL.trim();
262+
for (let [src,dst,isMarkdown] of keys) {
263+
if (kpsInfo[src]) {
264+
kmpInfo[dst] = {
265+
description: (kpsInfo[src]._ ?? (typeof kpsInfo[src] == 'string' ? kpsInfo[src].toString() : '')).trim()
266+
};
267+
if(isMarkdown) {
268+
kmpInfo[dst].description = markdownToHTML(kmpInfo[dst].description, false).trim();
269+
}
270+
if(kpsInfo[src].$?.URL) {
271+
kmpInfo[dst].url = kpsInfo[src].$.URL.trim();
272+
}
265273
}
266274
}
267275

268-
return ni;
276+
return kmpInfo;
269277
};
270278

271279
private arrayWrap(a: unknown) {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* Markdown transform for our `description` field. Tweaked to disable all inline
3+
* HTML, because we want descriptions to be short and sweet, and don't need any
4+
* of the more complex formatting that inline HTML affords.
5+
*/
6+
7+
//
8+
// Note: using marked 7.0.0.
9+
// https://github.com/markedjs/marked/issues/2926
10+
//
11+
// Version 7.0.1 introduced a TypeScript 5.0+ feature `export type *` which causes:
12+
//
13+
// ../../../node_modules/marked/lib/marked.d.ts:722:5 - error TS1383: Only named exports may use 'export type'.
14+
// 722 export type * from "MarkedOptions";
15+
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16+
//
17+
// https://github.com/markedjs/marked/compare/v7.0.0...v7.0.1#diff-32d87a2bc59f429470ccf7afc8ae8818914d4d45c3f7c5b1767c6a0a240b55c9R449-R451
18+
//
19+
// When we move to TS 5.0, we can upgrade marked.
20+
//
21+
import { Marked } from 'marked';
22+
23+
/*
24+
Markdown rendering: we don't want to use the global object, because this
25+
pollutes the settings for all modules. So we construct our own instance,
26+
so that we can strip all inline HTML; we don't need any
27+
*/
28+
29+
const renderer = {
30+
html(_html:string, _block:boolean) {
31+
// we don't allow inline HTML
32+
return '';
33+
}
34+
}
35+
const markedStripHtml = new Marked({renderer});
36+
const marked = new Marked();
37+
38+
/**
39+
*
40+
* @param markdown
41+
* @param allowHTML
42+
* @returns
43+
*/
44+
export function markdownToHTML(markdown: string, allowHTML: boolean): string {
45+
// <string>: .parse can return a Promise if async=true. We don't pass ths
46+
// option, and this sync usage isn't separated out in the types for
47+
// Marked.prototype.parse, so <string> avoids tsc complaints here.
48+
const html = <string> (allowHTML ? marked : markedStripHtml).parse(markdown.trim());
49+
return html;
50+
}

developer/src/kmc-package/test/fixtures/kmp_2.0/kmp.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"url": "https://keyman.com/keyboards/khmer_angkor"
2727
},
2828
"description": {
29-
"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!"
29+
"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>"
3030
}
3131
},
3232
"files": [
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { assert } from 'chai';
2+
import 'mocha';
3+
import { markdownToHTML } from '../src/compiler/markdown.js';
4+
5+
describe('markdownToHTML', function () {
6+
it('should convert markdown into HTML', function() {
7+
const html = markdownToHTML('# heading\n\n**bold** and _beautiful_', true);
8+
assert.equal(html, `<h1>heading</h1>\n<p><strong>bold</strong> and <em>beautiful</em></p>\n`);
9+
});
10+
11+
it('should strip inline html if asked to do so', function() {
12+
const html = markdownToHTML(`# heading\n\n<script>alert('gotcha')</script>\n\n**bold** and _beautiful_`, false);
13+
assert.equal(html, `<h1>heading</h1>\n<p><strong>bold</strong> and <em>beautiful</em></p>\n`);
14+
});
15+
16+
it('should keep inline html if asked to do so', function() {
17+
const html = markdownToHTML(`# heading\n\n<script>alert('gotcha')</script>\n\n**bold** and _beautiful_`, true);
18+
assert.equal(html, `<h1>heading</h1>\n<script>alert('gotcha')</script>\n\n<p><strong>bold</strong> and <em>beautiful</em></p>\n`);
19+
});
20+
});

package-lock.json

Lines changed: 26 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)