Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 17 additions & 11 deletions docs/userGuide/usingPlugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,22 +152,28 @@ During the `preRender` and `postRender` stages however, plugins may do custom pr
source file types, as parsed from the raw Markdown, typically requiring rebuilding the site.

Hence, to add custom source files to watch, you can implement the `getSources()` method.
- `getSources(content, pluginContext, frontMatter)`: Returns an array of source file paths to watch. Called **before** a Markdown file's `preRender` function is called.
- `content`: The raw Markdown of the current Markdown file (`.md`, `.mbd`, etc.).
- `pluginContext`: User provided parameters for the plugin. This can be specified in the `site.json`.
- `frontMatter`: The frontMatter of the page being processed, in case any frontMatter data is required.

Example usage of `getSources` from the PlantUML plugin:
`getSources(content, pluginContext, frontMatter)`: Called _before_ a Markdown file's `preRender` function is called.
- `content`: The raw Markdown of the current Markdown file (`.md`, `.mbd`, etc.).
- `pluginContext`: User provided parameters for the plugin. This can be specified in the `site.json`.
- `frontMatter`: The frontMatter of the page being processed, in case any frontMatter data is required.

It should return an object, consisting of _at least one of the following fields_:
- `tagMap`: An array consisting of `['tag name', 'source attribute name']` key value pairs.
- MarkBind will automatically search for matching tags with the source attributes, and watch them.
- For relative file paths, _if the tag is part of some included content_ ( eg. `<include />` tags ), it will be resolved against the included page. Otherwise, it is resolved against the page being processed.
- `sources`: An array of source file paths to watch, where relative file paths are resolved only against the page being processed.
- You can also directly return an array of source file paths to watch. ( ie. the `sources` field ) ___(deprecated)___

Example usage of `getSources` from the PlantUML plugin, which allows insertion of PlantUML diagrams using `<puml src="..." >` tags.
This allows files specified by the `src` attributes of `<puml>` tags to be watched:

```js
{
...
getSources: (content) => {
// Add all src attributes in <puml> tags to watch list
const $ = cheerio.load(content, { xmlMode: true });

return $('puml').map((i, tag) => tag.attribs.src).get();
},
getSources: () => ({
tagMap: [['puml', 'src']],
})
}
```

Expand Down
82 changes: 76 additions & 6 deletions src/Page.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ const Promise = require('bluebird');

const _ = {};
_.isString = require('lodash/isString');
_.isObject = require('lodash/isObject');
_.isArray = require('lodash/isArray');

const CyclicReferenceError = require('./lib/markbind/src/handlers/cyclicReferenceError.js');
const { ensurePosix } = require('./lib/markbind/src/utils');
const FsUtil = require('./util/fsUtil');
const logger = require('./util/logger');
const MarkBind = require('./lib/markbind/src/parser');
const md = require('./lib/markbind/src/lib/markdown-it');
const utils = require('./lib/markbind/src/utils');

const CLI_VERSION = require('../package.json').version;

Expand Down Expand Up @@ -864,14 +867,81 @@ Page.prototype.getPluginConfig = function () {
};

/**
* Collects file sources provided by plugins for the page
* Collects file sources provided by plugins for the page for live reloading
*/
Page.prototype.collectPluginSources = function (content) {
Object.entries(this.plugins).forEach(([pluginName, plugin]) => {
if (plugin.getSources) {
const sources = plugin.getSources(content, this.pluginsContext[pluginName] || {},
this.frontMatter, this.getPluginConfig());
sources.forEach(src => this.pluginSourceFiles.add(path.resolve(ensurePosix(src))));
const self = this;

Object.entries(self.plugins).forEach(([pluginName, plugin]) => {
if (!plugin.getSources) {
return;
}

const result = plugin.getSources(content, self.pluginsContext[pluginName] || {},
self.frontMatter, self.getPluginConfig());

let pageContextSources;
let domTagSourcesMap;

if (_.isArray(result)) {
pageContextSources = result;
} else if (_.isObject(result)) {
pageContextSources = result.sources;
domTagSourcesMap = result.tagMap;
} else {
logger.warn(`${pluginName} returned unsupported type for ${self.sourcePath}`);
return;
}

if (pageContextSources) {
pageContextSources.forEach((src) => {
if (src === undefined || src === '' || utils.isUrl(src)) {
return;
} else if (utils.isAbsolutePath(src)) {
self.pluginSourceFiles.add(path.resolve(src));
return;
}

// Resolve relative paths from the current page source
const originalSrcFolder = path.dirname(self.sourcePath);
const resolvedResourcePath = path.resolve(originalSrcFolder, src);

self.pluginSourceFiles.add(resolvedResourcePath);
});
}

if (domTagSourcesMap) {
const $ = cheerio.load(content, { xmlMode: true });

domTagSourcesMap.forEach(([tagName, attrName]) => {
if (!_.isString(tagName) || !_.isString(attrName)) {
logger.warn(`Invalid tag or attribute provided in tagMap by ${pluginName} plugin.`);
return;
}

const selector = `${tagName}[${attrName}]`;
$(selector).each((i, el) => {
const elem = $(el);

let src = elem.attr(attrName);

src = ensurePosix(src);
if (src === '' || utils.isUrl(src)) {
return;
} else if (utils.isAbsolutePath(src)) {
self.pluginSourceFiles.add(path.resolve(src));
return;
}

// Resolve relative paths from the include page source, or current page source otherwise
const firstParent = elem.closest('div[data-included-from], span[data-included-from]');
const originalSrc = firstParent.attr('data-included-from') || self.sourcePath;
const originalSrcFolder = path.dirname(originalSrc);
const resolvedResourcePath = path.resolve(originalSrcFolder, src);

self.pluginSourceFiles.add(resolvedResourcePath);
});
});
}
});

Expand Down
9 changes: 3 additions & 6 deletions src/plugins/default/markbind-plugin-plantuml.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,7 @@ module.exports = {

return $.html();
},
getSources: (content) => {
// Add all src attributes in <puml> tags to watch list
const $ = cheerio.load(content, { xmlMode: true });

return $('puml').map((i, tag) => tag.attribs.src).get();
},
getSources: () => ({
tagMap: [['puml', 'src']],
}),
};
28 changes: 28 additions & 0 deletions test/unit/Page.test.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
const path = require('path');
const {
COLLECT_PLUGIN_SOURCES,
COLLECT_PLUGIN_TEST_PLUGIN,
} = require('./utils/pageData');
const Page = require('../../src/Page');

test('Page#collectIncludedFiles collects included files from 1 dependency object', () => {
Expand All @@ -20,3 +25,26 @@ test('Page#collectIncludedFiles collects nothing', () => {

expect(page.includedFiles).toEqual(new Set());
});

test('Page#collectPluginSources collects correct sources', () => {
const page = new Page({
sourcePath: path.resolve('/root/index.md'),
plugins: { testPlugin: COLLECT_PLUGIN_TEST_PLUGIN },
pluginsContext: { testPlugin: {} },
});
page.collectPluginSources(COLLECT_PLUGIN_SOURCES);

const EXPECTED_SOURCE_FILES = new Set([
// source files from { sources: [...] }
path.resolve('/root/paths/here/should/be/resolved/relative/to/processed/page.cpp'),
path.resolve('/except/absolute/sources.c'),
// source files found from provided { tagMap: [[tag, srcAttr], ...] }
path.resolve('/root/images/sample1.png'),
path.resolve('/root/subdir/images/sample2.png'),
path.resolve('/root/subdir2/sample3.png'),
path.resolve('/root/images/sample4.png'),
path.resolve('/absolute/paths/should/not/be/rewritten.png'),
]);

expect(page.pluginSourceFiles).toEqual(EXPECTED_SOURCE_FILES);
});
37 changes: 37 additions & 0 deletions test/unit/utils/pageData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module.exports.COLLECT_PLUGIN_SOURCES = `
<custom-tag-one src="images/sample1.png">
Lorem ipsum dolor sit amet, Ut enim ad minim veniam,
<div data-included-from="/root/subdir/includedpage.md">
consectetur adipiscing elit,
<div>
<custom-tag-two srcattr="images/sample2.png">
sed do eiusmod tempor incididunt ut labore
<div data-included-from="/root/subdir2/includedpage.md">
<custom-tag-two srcattr="sample3.png">
et dolore
</custom-tag-two>
<custom-tag-two incorrectattr="should/not/be/returned.png">
magna aliqua.
</custom-tag-two>
</div>
</custom-tag-two>
</div>
</div>
quis nostrud exercitation ut
<custom-tag-two srcattr="images/sample4.png">Lorem ipsum</custom-tag-two>
<!-- urls should not be included -->
<custom-tag-one src="https://www.google.com">ullamco laboris nisi </custom-tag-one>
<custom-tag-one src="/absolute/paths/should/not/be/rewritten.png">
aliquip ex ea commodo consequat.
</custom-tag-one>
</custom-tag-one>`;

module.exports.COLLECT_PLUGIN_TEST_PLUGIN = {
getSources: () => ({
tagMap: [['custom-tag-one', 'src'], ['custom-tag-two', 'srcattr']],
sources: [
'paths/here/should/be/resolved/relative/to/processed/page.cpp',
'/except/absolute/sources.c',
],
}),
};