@salesforcedevs/sfdocs-remark-include-plugin 0.0.4-alpha → 0.0.6-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -42
- package/dist/index.d.ts +1 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -30
- package/dist/index.js.map +1 -1
- package/jest.config.js +15 -0
- package/package.json +2 -7
- package/src/__tests__/index.test.ts +108 -0
- package/src/index.ts +73 -0
- package/tsconfig.json +8 -0
package/README.md
CHANGED
|
@@ -1,49 +1,13 @@
|
|
|
1
|
-
# Remark Include
|
|
1
|
+
# Remark Include Plugin
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Examples
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Include another markdown file:
|
|
6
6
|
|
|
7
|
-
Example:
|
|
8
|
-
|
|
9
|
-
```markdown
|
|
10
|
-
::include{src="../shared/snippet.md"}
|
|
11
7
|
```
|
|
12
|
-
|
|
13
|
-
When the included file path contains `/shared/`, image URLs in that content are rewritten to absolute paths using the content directory.
|
|
14
|
-
|
|
15
|
-
## Usage
|
|
16
|
-
|
|
17
|
-
Use with `remark-directive` in your unified pipeline:
|
|
18
|
-
|
|
19
|
-
```javascript
|
|
20
|
-
import unified from 'unified';
|
|
21
|
-
import remarkParse from 'remark-parse';
|
|
22
|
-
import remarkDirective from 'remark-directive';
|
|
23
|
-
import includePlugin from '@salesforcedevs/sfdocs-remark-include-plugin';
|
|
24
|
-
|
|
25
|
-
unified()
|
|
26
|
-
.use(remarkParse)
|
|
27
|
-
.use(remarkDirective)
|
|
28
|
-
.use(includePlugin())
|
|
29
|
-
.processSync(markdown);
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
Optional custom renderer (for custom HTML wrapping of included content):
|
|
33
|
-
|
|
34
|
-
```javascript
|
|
35
|
-
includePlugin({
|
|
36
|
-
renderInclude(metadata) {
|
|
37
|
-
const root = metadata.get('root');
|
|
38
|
-
const node = metadata.get('node');
|
|
39
|
-
const out = new Map();
|
|
40
|
-
out.set('outputDom', `<div class="include">${root}</div>`);
|
|
41
|
-
return out;
|
|
42
|
-
},
|
|
43
|
-
});
|
|
8
|
+
::include{src="../shared/snippet.md"}
|
|
44
9
|
```
|
|
45
10
|
|
|
46
|
-
|
|
11
|
+
The plugin reads the target file, parses it, and inserts its children into the current document.
|
|
47
12
|
|
|
48
|
-
|
|
49
|
-
- `yarn test` — run tests
|
|
13
|
+
If the included file path contains `/shared/`, relative image URLs in that included content are rewritten to absolute content paths.
|
package/dist/index.d.ts
CHANGED
|
@@ -6,11 +6,8 @@ import { Transformer } from 'unified';
|
|
|
6
6
|
* with the included content. Must be used after remark-directive.
|
|
7
7
|
* When the included file path contains /shared/, image URLs in that content are rewritten to absolute paths using the content directory.
|
|
8
8
|
*
|
|
9
|
-
* @param customRenderFunctions - Optional { renderInclude } for custom HTML rendering (same as genericDirective)
|
|
10
9
|
* @returns Function that returns a Remark transformer
|
|
11
10
|
*/
|
|
12
|
-
declare function includePlugin(
|
|
13
|
-
renderInclude?: (metadata: Map<string, unknown>) => Map<string, string>;
|
|
14
|
-
}): () => Transformer;
|
|
11
|
+
declare function includePlugin(): () => Transformer;
|
|
15
12
|
export default includePlugin;
|
|
16
13
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAoCtC;;;;;;;;GAQG;AACH,iBAAS,aAAa,UACmB,WAAW,CAuBnD;AAED,eAAe,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@ const INCLUDE_DIRECTIVE = 'include';
|
|
|
11
11
|
const LEAF_DIRECTIVE_TYPE = 'leafDirective';
|
|
12
12
|
const CONTENT_FOLDER = '/content/';
|
|
13
13
|
const SHARED_PATH_SEGMENT = '/shared/';
|
|
14
|
+
const MEDIA_FOLDER = '/media';
|
|
14
15
|
function getContentDirPath(curFilePath) {
|
|
15
16
|
const position = curFilePath.indexOf(CONTENT_FOLDER);
|
|
16
17
|
if (position !== -1) {
|
|
@@ -19,13 +20,8 @@ function getContentDirPath(curFilePath) {
|
|
|
19
20
|
return '';
|
|
20
21
|
}
|
|
21
22
|
function getSubstringAfterMedia(folderPath) {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
return folderPath.substring(idx + 1);
|
|
25
|
-
const idx2 = folderPath.indexOf('media');
|
|
26
|
-
if (idx2 !== -1)
|
|
27
|
-
return folderPath.substring(idx2);
|
|
28
|
-
return folderPath;
|
|
23
|
+
const index = folderPath.indexOf(MEDIA_FOLDER);
|
|
24
|
+
return folderPath.substring(index);
|
|
29
25
|
}
|
|
30
26
|
function transformImageLinksInTree(root, contentDir) {
|
|
31
27
|
if (!contentDir)
|
|
@@ -46,10 +42,9 @@ function transformImageLinksInTree(root, contentDir) {
|
|
|
46
42
|
* with the included content. Must be used after remark-directive.
|
|
47
43
|
* When the included file path contains /shared/, image URLs in that content are rewritten to absolute paths using the content directory.
|
|
48
44
|
*
|
|
49
|
-
* @param customRenderFunctions - Optional { renderInclude } for custom HTML rendering (same as genericDirective)
|
|
50
45
|
* @returns Function that returns a Remark transformer
|
|
51
46
|
*/
|
|
52
|
-
function includePlugin(
|
|
47
|
+
function includePlugin() {
|
|
53
48
|
return function includePluginImpl() {
|
|
54
49
|
const processor = this;
|
|
55
50
|
return function transformer(tree, file) {
|
|
@@ -58,32 +53,16 @@ function includePlugin(customRenderFunctions) {
|
|
|
58
53
|
if (node.type !== LEAF_DIRECTIVE_TYPE || node.name !== INCLUDE_DIRECTIVE || !parent)
|
|
59
54
|
return;
|
|
60
55
|
const src = (_a = node.attributes) === null || _a === void 0 ? void 0 : _a.src;
|
|
61
|
-
if (!src) {
|
|
62
|
-
file.fail('Include directive requires a src attribute', node);
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
56
|
const filename = path_1.default.resolve(file.dirname || '', src);
|
|
66
|
-
if (!fs_1.default.existsSync(filename)) {
|
|
67
|
-
file.fail(`File not found ${filename}`, node);
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
57
|
try {
|
|
71
58
|
const content = fs_1.default.readFileSync(filename);
|
|
72
59
|
const includeFile = (0, vfile_1.default)({ path: filename, contents: content });
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const out = customRenderFunctions.renderInclude(metadata).get('outputDom');
|
|
77
|
-
parent.children.splice(i, 1, { type: 'html', value: out !== null && out !== void 0 ? out : '' });
|
|
78
|
-
}
|
|
79
|
-
else {
|
|
80
|
-
let root = processor.parse(includeFile);
|
|
81
|
-
if (filename.includes(SHARED_PATH_SEGMENT)) {
|
|
82
|
-
transformImageLinksInTree(root, getContentDirPath(filename));
|
|
83
|
-
}
|
|
84
|
-
const { children } = root;
|
|
85
|
-
parent.children.splice(i + 1, 0, ...(Array.isArray(children) ? children : []));
|
|
60
|
+
let root = processor.parse(includeFile);
|
|
61
|
+
if (filename.includes(SHARED_PATH_SEGMENT)) {
|
|
62
|
+
transformImageLinksInTree(root, getContentDirPath(filename));
|
|
86
63
|
}
|
|
64
|
+
const { children } = root;
|
|
65
|
+
parent.children.splice(i + 1, 0, ...(Array.isArray(children) ? children : []));
|
|
87
66
|
}
|
|
88
67
|
catch (err) {
|
|
89
68
|
file.fail((err === null || err === void 0 ? void 0 : err.message) || `Failed to read file ${filename}`, node);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;AAAA,wEAAqC;AAErC,gDAAwB;AACxB,4CAAoB;AACpB,kDAAqC;AAErC,MAAM,iBAAiB,GAAG,SAAS,CAAC;AACpC,MAAM,mBAAmB,GAAG,eAAe,CAAC;AAC5C,MAAM,cAAc,GAAG,WAAW,CAAC;AACnC,MAAM,mBAAmB,GAAG,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const BASE_CONFIG = require('../../scripts/jest/common.config');
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
...BASE_CONFIG,
|
|
5
|
+
collectCoverageFrom: ['<rootDir>/**/src/*.{ts,js}'],
|
|
6
|
+
displayName: 'remark-include-plugin',
|
|
7
|
+
coveragePathIgnorePatterns: ['/node_modules/', '/dist/'],
|
|
8
|
+
testMatch: ['<rootDir>/**/__tests__/**/*.{test,spec}.ts'],
|
|
9
|
+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
|
10
|
+
preset: 'ts-jest',
|
|
11
|
+
testEnvironment: 'node',
|
|
12
|
+
transform: {
|
|
13
|
+
'^.+\\.ts$': 'ts-jest',
|
|
14
|
+
}
|
|
15
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.0.
|
|
2
|
+
"version": "0.0.6-alpha",
|
|
3
3
|
"name": "@salesforcedevs/sfdocs-remark-include-plugin",
|
|
4
|
-
"description": "SFDocs remark plugin for the include directive",
|
|
4
|
+
"description": "SFDocs remark plugin for the include directive (::include)",
|
|
5
5
|
"author": "SFDocs Team",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"sfdocs-plugin",
|
|
@@ -9,11 +9,6 @@
|
|
|
9
9
|
],
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"main": "dist/index.js",
|
|
12
|
-
"files": [
|
|
13
|
-
"dist/*",
|
|
14
|
-
"package.json",
|
|
15
|
-
"README.md"
|
|
16
|
-
],
|
|
17
12
|
"scripts": {
|
|
18
13
|
"build": "yarn compile",
|
|
19
14
|
"compile": "tsc -p tsconfig.json",
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import stripIndent from 'strip-indent';
|
|
2
|
+
import vfile from 'vfile';
|
|
3
|
+
import unified from 'unified';
|
|
4
|
+
import remarkGfm from 'remark-gfm';
|
|
5
|
+
import parse from 'remark-parse';
|
|
6
|
+
import directive from 'remark-directive';
|
|
7
|
+
import remark2rehype from 'remark-rehype';
|
|
8
|
+
import html from 'rehype-stringify';
|
|
9
|
+
import rawHtml from 'rehype-raw';
|
|
10
|
+
import fs from 'fs';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import includePlugin from '../index';
|
|
13
|
+
|
|
14
|
+
const processMarkdown = (md: any): any => {
|
|
15
|
+
return unified()
|
|
16
|
+
.use(parse)
|
|
17
|
+
.use(directive)
|
|
18
|
+
.use(includePlugin())
|
|
19
|
+
.use(remark2rehype, { allowDangerousHtml: true })
|
|
20
|
+
.use(rawHtml)
|
|
21
|
+
.use(remarkGfm)
|
|
22
|
+
.use(html)
|
|
23
|
+
.processSync(md);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
describe('Include plugin test', () => {
|
|
27
|
+
let testRoot = '';
|
|
28
|
+
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
testRoot = path.join(
|
|
31
|
+
process.cwd(),
|
|
32
|
+
`.tmp-remark-include-${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
|
33
|
+
);
|
|
34
|
+
fs.mkdirSync(testRoot, { recursive: true });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
fs.rmSync(testRoot, { recursive: true, force: true });
|
|
39
|
+
jest.restoreAllMocks();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('includes markdown content from a shared file', async () => {
|
|
43
|
+
fs.mkdirSync(path.join(testRoot, 'abc/xyz/guides'), { recursive: true });
|
|
44
|
+
fs.mkdirSync(path.join(testRoot, 'abc/xyz/shared'), { recursive: true });
|
|
45
|
+
fs.writeFileSync(
|
|
46
|
+
path.join(testRoot, 'abc/xyz/shared/test_include.md'),
|
|
47
|
+
stripIndent(`
|
|
48
|
+
## Include file
|
|
49
|
+
shared content
|
|
50
|
+
`),
|
|
51
|
+
);
|
|
52
|
+
const content = stripIndent(`
|
|
53
|
+
::include{src="../shared/test_include.md"}
|
|
54
|
+
`);
|
|
55
|
+
const file = vfile({ path: path.join(testRoot, 'abc/xyz/guides/parent.md'), contents: content });
|
|
56
|
+
const transformedHtml = await processMarkdown(file);
|
|
57
|
+
|
|
58
|
+
expect(transformedHtml.contents).toContain('<h2>Include file</h2>');
|
|
59
|
+
expect(transformedHtml.contents).toContain('<p>shared content</p>');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('rewrites shared include media image URL to content media path', async () => {
|
|
63
|
+
fs.mkdirSync(path.join(testRoot, 'abc/xyz/content/guides'), { recursive: true });
|
|
64
|
+
fs.mkdirSync(path.join(testRoot, 'abc/xyz/content/shared'), { recursive: true });
|
|
65
|
+
fs.writeFileSync(
|
|
66
|
+
path.join(testRoot, 'abc/xyz/content/shared/test_include.md'),
|
|
67
|
+
stripIndent(`
|
|
68
|
+
## Include file
|
|
69
|
+
shared content
|
|
70
|
+

|
|
71
|
+
`),
|
|
72
|
+
);
|
|
73
|
+
const content = stripIndent(`
|
|
74
|
+
::include{src="../shared/test_include.md"}
|
|
75
|
+
`);
|
|
76
|
+
const file = vfile({
|
|
77
|
+
path: path.join(testRoot, 'abc/xyz/content/guides/parent.md'),
|
|
78
|
+
contents: content,
|
|
79
|
+
});
|
|
80
|
+
const transformedHtml = await processMarkdown(file);
|
|
81
|
+
|
|
82
|
+
expect(transformedHtml.contents).toContain('<h2>Include file</h2>');
|
|
83
|
+
expect(transformedHtml.contents).toContain(path.join(testRoot, 'abc/xyz/content/media/test.jpeg'));
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('rewrites localized shared include media image URL to root content media path', async () => {
|
|
87
|
+
fs.mkdirSync(path.join(testRoot, 'abc/content/ja-jp/guides'), { recursive: true });
|
|
88
|
+
fs.mkdirSync(path.join(testRoot, 'abc/content/ja-jp/shared'), { recursive: true });
|
|
89
|
+
fs.writeFileSync(
|
|
90
|
+
path.join(testRoot, 'abc/content/ja-jp/shared/test_include.md'),
|
|
91
|
+
stripIndent(`
|
|
92
|
+
## Include file
|
|
93
|
+
shared content
|
|
94
|
+

|
|
95
|
+
`),
|
|
96
|
+
);
|
|
97
|
+
const content = stripIndent(`
|
|
98
|
+
::include{src="../shared/test_include.md"}
|
|
99
|
+
`);
|
|
100
|
+
const file = vfile({
|
|
101
|
+
path: path.join(testRoot, 'abc/content/ja-jp/guides/parent.md'),
|
|
102
|
+
contents: content,
|
|
103
|
+
});
|
|
104
|
+
const transformedHtml = await processMarkdown(file);
|
|
105
|
+
|
|
106
|
+
expect(transformedHtml.contents).toContain(path.join(testRoot, 'abc/content/media/test.jpeg'));
|
|
107
|
+
});
|
|
108
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import visit from 'unist-util-visit';
|
|
2
|
+
import { Transformer } from 'unified';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import vfile, { VFile } from 'vfile';
|
|
6
|
+
|
|
7
|
+
const INCLUDE_DIRECTIVE = 'include';
|
|
8
|
+
const LEAF_DIRECTIVE_TYPE = 'leafDirective';
|
|
9
|
+
const CONTENT_FOLDER = '/content/';
|
|
10
|
+
const SHARED_PATH_SEGMENT = '/shared/';
|
|
11
|
+
const MEDIA_FOLDER = '/media';
|
|
12
|
+
|
|
13
|
+
function getContentDirPath(curFilePath: string): string {
|
|
14
|
+
const position = curFilePath.indexOf(CONTENT_FOLDER);
|
|
15
|
+
if (position !== -1) {
|
|
16
|
+
return curFilePath.slice(0, position + CONTENT_FOLDER.length);
|
|
17
|
+
}
|
|
18
|
+
return '';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function getSubstringAfterMedia(folderPath: string) {
|
|
22
|
+
const index = folderPath.indexOf(MEDIA_FOLDER);
|
|
23
|
+
return folderPath.substring(index);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function transformImageLinksInTree(root: any, contentDir: string): void {
|
|
27
|
+
if (!contentDir) return;
|
|
28
|
+
visit(root, 'image', (node: any) => {
|
|
29
|
+
const filePath = node.url;
|
|
30
|
+
const isRelativeUrl = !filePath?.match(/^(https?:\/\/|www\.).*/);
|
|
31
|
+
if (isRelativeUrl && filePath) {
|
|
32
|
+
const afterMedia = getSubstringAfterMedia(filePath);
|
|
33
|
+
node.url = path.join(contentDir, afterMedia);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* SFDocs Include Plugin
|
|
40
|
+
* Processes include directives (::include{src="path/to/file.md"}) by reading the file,
|
|
41
|
+
* parsing or processing it through the unified pipeline, and replacing the directive
|
|
42
|
+
* with the included content. Must be used after remark-directive.
|
|
43
|
+
* When the included file path contains /shared/, image URLs in that content are rewritten to absolute paths using the content directory.
|
|
44
|
+
*
|
|
45
|
+
* @returns Function that returns a Remark transformer
|
|
46
|
+
*/
|
|
47
|
+
function includePlugin() {
|
|
48
|
+
return function includePluginImpl(): Transformer {
|
|
49
|
+
const processor = this;
|
|
50
|
+
return function transformer(tree: any, file: VFile): void {
|
|
51
|
+
visit(tree, (node: any, i: number, parent: any) => {
|
|
52
|
+
if (node.type !== LEAF_DIRECTIVE_TYPE || node.name !== INCLUDE_DIRECTIVE || !parent) return;
|
|
53
|
+
const src = node.attributes?.src;
|
|
54
|
+
const filename = path.resolve(file.dirname || '', src);
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const content = fs.readFileSync(filename);
|
|
58
|
+
const includeFile = vfile({ path: filename, contents: content });
|
|
59
|
+
let root = processor.parse(includeFile);
|
|
60
|
+
if (filename.includes(SHARED_PATH_SEGMENT)) {
|
|
61
|
+
transformImageLinksInTree(root, getContentDirPath(filename));
|
|
62
|
+
}
|
|
63
|
+
const { children } = root;
|
|
64
|
+
parent.children.splice(i + 1, 0, ...(Array.isArray(children) ? children : []));
|
|
65
|
+
} catch (err: any) {
|
|
66
|
+
file.fail(err?.message || `Failed to read file ${filename}`, node);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export default includePlugin;
|