Skip to content

Commit 549189b

Browse files
authored
Support for single language code tabs (#75)
* Improved custom code renderer to support default markdown fenced code block with explicitly specified language. * Bump version to 0.4.6 * Fix tests and address comments * Added exhausting comment commenting apifyMarked
1 parent 9370539 commit 549189b

File tree

4 files changed

+143
-35
lines changed

4 files changed

+143
-35
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1+
0.4.6 / 2020/09/29
2+
==================
3+
- Improved custom code renderer to support default markdown fenced code block with explicitly specified language.
4+
15
0.4.5 / 2020/09/09
6+
==================
27
- Fixed custom code renderer to support the use case of rendering code tabs.
38

49
0.4.4 / 2020/09/01

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "apify-shared",
3-
"version": "0.4.5",
3+
"version": "0.4.6",
44
"description": "Tools and constants shared across Apify projects.",
55
"main": "build/index.js",
66
"keywords": [
@@ -27,6 +27,7 @@
2727
"scripts": {
2828
"build": "rm -rf ./build && babel src --out-dir build && cp src/*.json build",
2929
"build-doc": "npm run clean && npm run build && node ./node_modules/jsdoc/jsdoc.js --package ./package.json -c ./jsdoc/conf.json -d docs",
30+
"build-local-dev": "npm run build && cp package.json build && pushd build/ && npm i && popd",
3031
"test": "npm run build && mocha --timeout 5000 --require @babel/register --recursive",
3132
"test-cov": "npm run build && babel-node node_modules/isparta/bin/isparta cover --report text --report html node_modules/mocha/bin/_mocha -- --reporter dot",
3233
"prepublishOnly": "test $RUNNING_FROM_SCRIPT || (echo \"You must use publish.sh instead of 'npm publish' directly!\"; exit 1)",

src/marked.js

Lines changed: 108 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,31 @@ import matchAll from 'match-all';
33
import { customHeadingRenderer } from './markdown_renderers';
44

55

6+
/**
7+
* Map from the language of a fenced code block to the title of corresponding tab.
8+
* The language is a string provided by the default marked tokenizer.
9+
* Note that not all of the languages (such as python2) might be possible at the moment
10+
* in the default marked tokenizer. We anyway include them here for
11+
* robustness to potential future improvements of marked.
12+
* In case tab title can't be resolved from language using this mapping, the language itself is used as a tab title.
13+
*/
14+
const LANGUAGE_TO_TAB_TITLE = {
15+
js: 'Node.JS',
16+
javascript: 'Node.js',
17+
nodejs: 'Node.js',
18+
bash: 'Bash',
19+
curl: 'cURL',
20+
dockerfile: 'Dockerfile',
21+
php: 'PHP',
22+
json: 'JSON',
23+
xml: 'XML',
24+
python: 'Python',
25+
python2: 'Python 2',
26+
python3: 'Python 3',
27+
yml: 'YAML',
28+
yaml: 'YAML',
29+
};
30+
631
const APIFY_CODE_TABS = 'apify-code-tabs';
732
const DEFAULT_MARKED_RENDERER = new marked.Renderer();
833

@@ -29,39 +54,106 @@ const codeTabObjectFromCodeTabMarkdown = (markdown) => {
2954

3055

3156
/**
57+
* This custom function is used in the same context as default `marked` function.
58+
*
59+
* It parses the given markdown and treats some headings and code blocks in a custom way
60+
* -----------------------------------------------------------------------------------------------
61+
* 0. Heading with {custom-id} in text will have id="custom-id" property on reasulting <h...> tag.
62+
* E.g.
63+
* # Welcome to Apify {welcome-title-id}
64+
* is turned to
65+
* <h1 id="welcome-title-id">Welcome to Apify</h1>
66+
* -----------------------------------------------------------------------------------------------
67+
* 1. Fenced code block with explicit language which is in the mapping LANGUAGE_TO_TAB_TITLE
68+
* ```my-lang
69+
* my-code
70+
* ```
71+
* This block is turned into [apify-code-tabs]$INDEX[/apify-code-tabs] in returned HTML
72+
* and returned codeTabsObjectPerIndex contains key $INDEX with value
73+
* {
74+
* LANGUAGE_TO_TAB_TITLE[my-lang]: { lang: 'my-lang', code: 'my-code' }
75+
* }
76+
* -----------------------------------------------------------------------------------------------
77+
* 2. Fenced code block with explicit language which is NOT in the mapping LANGUAGE_TO_TAB_TITLE
78+
* ```my-lang-not-in-mapping
79+
* my-code
80+
* ```
81+
* This block is turned into [apify-code-tabs]$INDEX[/apify-code-tabs] in returned HTML
82+
* and returned codeTabsObjectPerIndex contains key $INDEX with value
83+
* {
84+
* my-lang-not-in-mapping: { lang: 'my-lang-not-in-mapping', code: 'my-code' }
85+
* }
86+
* -----------------------------------------------------------------------------------------------
87+
* 3. Fenced code block with no language
88+
* ```
89+
* my-code
90+
* ```
91+
*
92+
* is handled by default marked package and returned in HTML already parsed to <code> block.
93+
* -----------------------------------------------------------------------------------------------
94+
* 4. Indented code block
95+
* my-code
96+
*
97+
* is handled by default marked package and returned in HTML already parsed to <code> block.
98+
* -----------------------------------------------------------------------------------------------
99+
* 5. Special marked-tabs code fence
100+
* Each code block of following form
101+
* ```marked-tabs
102+
* <marked-tab header="Node.js" lang="javascript">
103+
* js-code
104+
* </marked-tab>
105+
*
106+
* <marked-tab header="Python" lang="python">
107+
* python-code
108+
* </marked-tab>
109+
* ```
110+
* is replaced by [apify-code-tabs]$INDEX[/apify-code-tabs] in the returned HTML where $INDEX is
111+
* an unique integer, to allow multiple marked-tabs components on the same page.
112+
*
113+
* For the example above codeTabsObjectPerIndex would contain key $INDEX with the following value
114+
* {
115+
* 'Node.js': {lang: 'javascript', code: 'js-code'},
116+
* 'Python': {lang: 'python', code: 'python-code'}
117+
* }
118+
*
119+
* i.e. each <marked-tab header="HEADER" lang="LANG">CODE</marked-tab> is turned into
120+
* HEADER: {lang: LANG, code: CODE} entry.
121+
*
122+
* Note that you have to use double quotation marks around HEADER and LANG, otherwise, the expression will not be matched
123+
* which results in unexpected and hard to debug errors.
124+
*
125+
* Each [apify-code-tabs]$INDEX[/apify-code-tabs] is meant to be later replaced be a react component
126+
* rendering the appropriate codeTabBlockObject returned by this function.
32127
* @param {string} markdown
33128
* @return {{ html: string, codeTabsObjectPerIndex: Object.<number, Object.<string, {language: string, code: string}>> }}
34129
*/
35130
export const apifyMarked = (markdown) => {
36131
const renderer = new marked.Renderer();
37132
renderer.heading = customHeadingRenderer;
38133
renderer.code = (code, language) => {
39-
if (language === 'marked-tabs') {
134+
if (language) {
40135
return code;
41136
}
42137
return DEFAULT_MARKED_RENDERER.code(code, language);
43138
};
44139
const tokens = marked.lexer(markdown);
45140

46-
/**
47-
* Each code block of following form
48-
* ```marked-tabs
49-
* ... some code
50-
* ```
51-
* is replaced by [apify-code-tabs]INDEX[/apify-code-tabs] where index is
52-
* an increasing integer starting at 0, to allow multiple marked-tabs components
53-
* on the same page.
54-
*
55-
* [apify-code-tabs]INDEX[/apify-code-tabs] is meant to be later replaced be a react component
56-
* rendering the appropriate codeTabBlockObject returned by this function.
57-
*/
58141
let markedTabTokenIndex = 0;
59142
const codeTabsObjectPerIndex = {};
60143
tokens.forEach((token) => {
61-
if (token.type === 'code' && token.lang === 'marked-tabs') {
62-
codeTabsObjectPerIndex[markedTabTokenIndex] = codeTabObjectFromCodeTabMarkdown(token.text);
144+
if (token.type === 'code' && token.lang) {
145+
if (token.lang === 'marked-tabs') {
146+
codeTabsObjectPerIndex[markedTabTokenIndex] = codeTabObjectFromCodeTabMarkdown(token.text);
147+
} else {
148+
const tabTitle = LANGUAGE_TO_TAB_TITLE[token.lang] || token.lang;
149+
codeTabsObjectPerIndex[markedTabTokenIndex] = {
150+
[tabTitle]: {
151+
language: token.lang,
152+
code: token.text,
153+
},
154+
};
155+
}
63156
token.text = `[${APIFY_CODE_TABS}]${markedTabTokenIndex}[/${APIFY_CODE_TABS}]`;
64-
65157
markedTabTokenIndex++;
66158
}
67159
});

test/marked.js

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const MARKDOWN_UNDER_TEST = `
1010
1111
## Code block with tabs
1212
\`\`\`marked-tabs
13-
<marked-tab header="NodeJS" lang="javascript">
13+
<marked-tab header="Node.js" lang="javascript">
1414
console.log('Some JS code');
1515
</marked-tab>
1616
@@ -22,7 +22,7 @@ if count >= 1:
2222
print('Some python code on next line');
2323
</marked-tab>
2424
25-
<marked-tab header="Curl" lang="bash">
25+
<marked-tab header="Bash" lang="bash">
2626
echo "Some bash code"
2727
</marked-tab>
2828
\`\`\`
@@ -32,12 +32,18 @@ echo "Some bash code"
3232
console.log('Your standard javascript code block')
3333
\`\`\`
3434
35+
\`\`\`
36+
console.log('Fenced block with no language')
37+
\`\`\`
38+
39+
console.log('Tab indented block')
40+
3541
## Second block with tabs
3642
\`\`\`marked-tabs
37-
<marked-tab header="NodeJS" lang="javascript">
43+
<marked-tab header="Custom title" lang="javascript">
3844
console.log('Some JS code 2');
3945
</marked-tab>
40-
<marked-tab header="Curl" lang="bash">
46+
<marked-tab header="Bash" lang="bash">
4147
echo "Some bash code 2"
4248
</marked-tab>
4349
\`\`\`
@@ -49,9 +55,10 @@ This is footer text.
4955
const EXPECTED_HTML = '\n' +
5056
' <h1 id="title">Title</h1>\n' +
5157
' <h2 id="code-block-with-tabs">Code block with tabs</h2>[apify-code-tabs]0[/apify-code-tabs]\n' +
52-
' <h2 id="code-block-without-tabs">Code block without tabs</h2><pre><code class="language-javascript">console.log(&#39;Your standard javascript code block&#39;)</code></pre>\n' +
58+
' <h2 id="code-block-without-tabs">Code block without tabs</h2>[apify-code-tabs]1[/apify-code-tabs]<pre><code>console.log(&#39;Fenced block with no language&#39;)</code></pre>\n' +
59+
'<pre><code>console.log(&#39;Tab indented block&#39;)</code></pre>\n' +
5360
'\n' +
54-
' <h2 id="second-block-with-tabs">Second block with tabs</h2>[apify-code-tabs]1[/apify-code-tabs]\n' +
61+
' <h2 id="second-block-with-tabs">Second block with tabs</h2>[apify-code-tabs]2[/apify-code-tabs]\n' +
5562
' <h2 id="footer">Footer</h2><p>This is footer text.</p>\n';
5663

5764
describe('apifyMarked custom renderer works', () => {
@@ -62,20 +69,23 @@ describe('apifyMarked custom renderer works', () => {
6269
expect(codeTabsObjectPerIndex).to.eql(
6370
{
6471
'0': {
65-
NodeJS: { language: 'javascript', code: "console.log('Some JS code');" },
66-
Python: {
67-
language: 'python',
68-
code: "print('Some python code');\n" +
69-
'count = 1\n' +
70-
'if count >= 1:\n' +
71-
" print('Some intended python code');\n" +
72-
"print('Some python code on next line');"
73-
},
74-
Curl: { language: 'bash', code: 'echo "Some bash code"' }
72+
'Node.js': { language: 'javascript', code: "console.log('Some JS code');" },
73+
Python: {
74+
language: 'python',
75+
code: "print('Some python code');\n" +
76+
'count = 1\n' +
77+
'if count >= 1:\n' +
78+
" print('Some intended python code');\n" +
79+
"print('Some python code on next line');"
80+
},
81+
'Bash': { language: 'bash', code: 'echo "Some bash code"' }
7582
},
7683
'1': {
77-
NodeJS: { language: 'javascript', code: "console.log('Some JS code 2');" },
78-
Curl: { language: 'bash', code: 'echo "Some bash code 2"' }
84+
'Node.js': { language: 'javascript', code: "console.log('Your standard javascript code block')" },
85+
},
86+
'2': {
87+
'Custom title': { language: 'javascript', code: "console.log('Some JS code 2');" },
88+
Bash: { language: 'bash', code: 'echo "Some bash code 2"' }
7989
}
8090
}
8191
);

0 commit comments

Comments
 (0)