1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-23 18:53:36 +02:00

Finished converting plugins

This commit is contained in:
Laurent Cozic 2020-10-20 19:56:34 +01:00
parent fe90d92e01
commit 98bf3bde8d
19 changed files with 311 additions and 329 deletions

View File

@ -224,7 +224,14 @@ ReactNativeClient/lib/InMemoryCache.js
ReactNativeClient/lib/joplin-renderer/MarkupToHtml.js ReactNativeClient/lib/joplin-renderer/MarkupToHtml.js
ReactNativeClient/lib/joplin-renderer/MdToHtml.js ReactNativeClient/lib/joplin-renderer/MdToHtml.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/checkbox.js ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/checkbox.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/code_inline.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fountain.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/highlight_keywords.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/html_image.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/image.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/katex.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/link_open.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js
ReactNativeClient/lib/joplin-renderer/noteStyle.js ReactNativeClient/lib/joplin-renderer/noteStyle.js

7
.gitignore vendored
View File

@ -218,7 +218,14 @@ ReactNativeClient/lib/InMemoryCache.js
ReactNativeClient/lib/joplin-renderer/MarkupToHtml.js ReactNativeClient/lib/joplin-renderer/MarkupToHtml.js
ReactNativeClient/lib/joplin-renderer/MdToHtml.js ReactNativeClient/lib/joplin-renderer/MdToHtml.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/checkbox.js ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/checkbox.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/code_inline.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fountain.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/highlight_keywords.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/html_image.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/image.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/katex.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/link_open.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js
ReactNativeClient/lib/joplin-renderer/noteStyle.js ReactNativeClient/lib/joplin-renderer/noteStyle.js

View File

@ -167,7 +167,14 @@ ReactNativeClient/lib/InMemoryCache.js
ReactNativeClient/lib/joplin-renderer/MarkupToHtml.js ReactNativeClient/lib/joplin-renderer/MarkupToHtml.js
ReactNativeClient/lib/joplin-renderer/MdToHtml.js ReactNativeClient/lib/joplin-renderer/MdToHtml.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/checkbox.js ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/checkbox.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/code_inline.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fountain.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/highlight_keywords.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/html_image.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/image.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/katex.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/link_open.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js
ReactNativeClient/lib/joplin-renderer/noteStyle.js ReactNativeClient/lib/joplin-renderer/noteStyle.js

View File

@ -1,6 +1,6 @@
import { PluginStates } from 'lib/services/plugins/reducer'; import { PluginStates } from 'lib/services/plugins/reducer';
import contentScriptsToRendererRules from 'lib/services/plugins/utils/contentScriptsToRendererRules'; import contentScriptsToRendererRules from 'lib/services/plugins/utils/contentScriptsToRendererRules';
import { useCallback } from 'react'; import { useCallback, useMemo } from 'react';
import { ResourceInfos } from './types'; import { ResourceInfos } from './types';
import markupLanguageUtils from 'lib/markupLanguageUtils'; import markupLanguageUtils from 'lib/markupLanguageUtils';
import Setting from 'lib/models/Setting'; import Setting from 'lib/models/Setting';
@ -22,6 +22,13 @@ interface MarkupToHtmlOptions {
export default function useMarkupToHtml(deps:HookDependencies) { export default function useMarkupToHtml(deps:HookDependencies) {
const { themeId, customCss, plugins } = deps; const { themeId, customCss, plugins } = deps;
const markupToHtml = useMemo(() => {
return markupLanguageUtils.newMarkupToHtml({
resourceBaseUrl: `file://${Setting.value('resourceDir')}/`,
extraRendererRules: contentScriptsToRendererRules(plugins),
});
}, [plugins]);
return useCallback(async (markupLanguage: number, md: string, options: MarkupToHtmlOptions = null): Promise<any> => { return useCallback(async (markupLanguage: number, md: string, options: MarkupToHtmlOptions = null): Promise<any> => {
options = { options = {
replaceResourceInternalToExternalLinks: false, replaceResourceInternalToExternalLinks: false,
@ -42,11 +49,6 @@ export default function useMarkupToHtml(deps:HookDependencies) {
delete options.replaceResourceInternalToExternalLinks; delete options.replaceResourceInternalToExternalLinks;
const markupToHtml = markupLanguageUtils.newMarkupToHtml({
resourceBaseUrl: `file://${Setting.value('resourceDir')}/`,
extraRendererRules: contentScriptsToRendererRules(plugins),
});
const result = await markupToHtml.render(markupLanguage, md, theme, Object.assign({}, { const result = await markupToHtml.render(markupLanguage, md, theme, Object.assign({}, {
codeTheme: theme.codeThemeCss, codeTheme: theme.codeThemeCss,
userCss: customCss || '', userCss: customCss || '',
@ -57,5 +59,5 @@ export default function useMarkupToHtml(deps:HookDependencies) {
}, options)); }, options));
return result; return result;
}, [themeId, customCss, plugins]); }, [themeId, customCss, markupToHtml]);
} }

View File

@ -42,10 +42,6 @@ export default class MarkupToHtml {
return this.renderers_[markupLanguage]; return this.renderers_[markupLanguage];
} }
injectedJavaScript() {
return '';
}
stripMarkup(markupLanguage:MarkupLanguage, markup:string, options:any = null) { stripMarkup(markupLanguage:MarkupLanguage, markup:string, options:any = null) {
if (!markup) return ''; if (!markup) return '';

View File

@ -8,7 +8,6 @@ const md5 = require('md5');
interface RendererRule { interface RendererRule {
install(context:any, ruleOptions:any):any, install(context:any, ruleOptions:any):any,
assets?(theme:any):any, assets?(theme:any):any,
rule?: any, // TODO: remove
plugin?: any, plugin?: any,
} }
@ -29,18 +28,17 @@ interface RendererPlugins {
const rules:RendererRules = { const rules:RendererRules = {
fence: require('./MdToHtml/rules/fence').default, fence: require('./MdToHtml/rules/fence').default,
sanitize_html: require('./MdToHtml/rules/sanitize_html').default, sanitize_html: require('./MdToHtml/rules/sanitize_html').default,
image: require('./MdToHtml/rules/image'), image: require('./MdToHtml/rules/image').default,
checkbox: require('./MdToHtml/rules/checkbox').default, checkbox: require('./MdToHtml/rules/checkbox').default,
katex: require('./MdToHtml/rules/katex'), katex: require('./MdToHtml/rules/katex').default,
link_open: require('./MdToHtml/rules/link_open'), link_open: require('./MdToHtml/rules/link_open').default,
html_image: require('./MdToHtml/rules/html_image'), html_image: require('./MdToHtml/rules/html_image').default,
highlight_keywords: require('./MdToHtml/rules/highlight_keywords'), highlight_keywords: require('./MdToHtml/rules/highlight_keywords').default,
code_inline: require('./MdToHtml/rules/code_inline'), code_inline: require('./MdToHtml/rules/code_inline').default,
fountain: require('./MdToHtml/rules/fountain'), fountain: require('./MdToHtml/rules/fountain').default,
mermaid: require('./MdToHtml/rules/mermaid').default, mermaid: require('./MdToHtml/rules/mermaid').default,
}; };
// const eventManager = require('lib/eventManager').default;
const setupLinkify = require('./MdToHtml/setupLinkify'); const setupLinkify = require('./MdToHtml/setupLinkify');
const hljs = require('highlight.js'); const hljs = require('highlight.js');
const uslug = require('uslug'); const uslug = require('uslug');
@ -101,6 +99,7 @@ interface PluginContext {
css: any css: any
pluginAssets: any, pluginAssets: any,
cache: any, cache: any,
userData: any,
} }
interface RenderResultPluginAsset { interface RenderResultPluginAsset {
@ -119,9 +118,33 @@ export interface RuleOptions {
context: PluginContext, context: PluginContext,
theme: any, theme: any,
postMessageSyntax: string, postMessageSyntax: string,
ResourceModel: any,
resourceBaseUrl: string,
resources: any, // resourceId: Resource
// Used by checkboxes to specify how it should be rendered // Used by checkboxes to specify how it should be rendered
checkboxRenderingType?: number, checkboxRenderingType?: number,
// Used by the keyword highlighting plugin (mobile only)
highlightedKeywords?: any[],
// Use by resource-rendering logic to signify that it should be rendered
// as a plain HTML string without any attached JavaScript. Used for example
// when exporting to HTML.
plainResourceRendering?: boolean,
// Use in mobile app to enable long-pressing an image or a linkg
// to display a context menu. Used in `image.ts` and `link_open.ts`
enableLongPress?: boolean,
// Used in mobile app when enableLongPress = true. Tells for how long
// the resource should be pressed before the menu is shown.
longPressDelay?: number,
// Use by `link_open` rule.
// linkRenderingType = 1 is the regular rendering and clicking on it is handled via embedded JS (in onclick attribute)
// linkRenderingType = 2 gives a plain link with no JS. Caller needs to handle clicking on the link.
linkRenderingType?: number,
} }
export default class MdToHtml { export default class MdToHtml {
@ -129,7 +152,6 @@ export default class MdToHtml {
private resourceBaseUrl_:string; private resourceBaseUrl_:string;
private ResourceModel_:any; private ResourceModel_:any;
private contextCache_:any; private contextCache_:any;
private tempDir_:string;
private fsDriver_:any; private fsDriver_:any;
private cachedOutputs_:any = {}; private cachedOutputs_:any = {};
@ -139,8 +161,9 @@ export default class MdToHtml {
// Markdown-It plugin options (not Joplin plugin options) // Markdown-It plugin options (not Joplin plugin options)
private pluginOptions_:any = {}; private pluginOptions_:any = {};
private extraRendererRules_:RendererRules = {}; private extraRendererRules_:RendererRules = {};
private allProcessedAssets_:any = {};
constructor(options:Options = null) { public constructor(options:Options = null) {
if (!options) options = {}; if (!options) options = {};
// Must include last "/" // Must include last "/"
@ -150,7 +173,6 @@ export default class MdToHtml {
this.pluginOptions_ = options.pluginOptions ? options.pluginOptions : {}; this.pluginOptions_ = options.pluginOptions ? options.pluginOptions : {};
this.contextCache_ = inMemoryCache; this.contextCache_ = inMemoryCache;
this.tempDir_ = options.tempDir;
this.fsDriver_ = { this.fsDriver_ = {
writeFile: (/* path, content, encoding = 'base64'*/) => { throw new Error('writeFile not set'); }, writeFile: (/* path, content, encoding = 'base64'*/) => { throw new Error('writeFile not set'); },
exists: (/* path*/) => { throw new Error('exists not set'); }, exists: (/* path*/) => { throw new Error('exists not set'); },
@ -170,22 +192,18 @@ export default class MdToHtml {
} }
} }
fsDriver() { private fsDriver() {
return this.fsDriver_; return this.fsDriver_;
} }
tempDir() { public static pluginNames() {
return this.tempDir_;
}
static pluginNames() {
const output = []; const output = [];
for (const n in rules) output.push(n); for (const n in rules) output.push(n);
for (const n in plugins) output.push(n); for (const n in plugins) output.push(n);
return output; return output;
} }
pluginOptions(name:string) { private pluginOptions(name:string) {
let o = this.pluginOptions_[name] ? this.pluginOptions_[name] : {}; let o = this.pluginOptions_[name] ? this.pluginOptions_[name] : {};
o = Object.assign({ o = Object.assign({
enabled: true, enabled: true,
@ -193,7 +211,7 @@ export default class MdToHtml {
return o; return o;
} }
pluginEnabled(name:string) { private pluginEnabled(name:string) {
return this.pluginOptions(name).enabled; return this.pluginOptions(name).enabled;
} }
@ -203,7 +221,7 @@ export default class MdToHtml {
this.extraRendererRules_[id] = module; this.extraRendererRules_[id] = module;
} }
processPluginAssets(pluginAssets:PluginAssets):RenderResult { private processPluginAssets(pluginAssets:PluginAssets):RenderResult {
const files:RenderResultPluginAsset[] = []; const files:RenderResultPluginAsset[] = [];
const cssStrings = []; const cssStrings = [];
for (const pluginName in pluginAssets) { for (const pluginName in pluginAssets) {
@ -246,7 +264,13 @@ export default class MdToHtml {
}; };
} }
private allUnprocessedAssets(theme:any) { // This return all the assets for all the plugins. Since it is called
// on each render, the result is cached.
private allProcessedAssets(theme:any, codeTheme:string) {
const cacheKey:string = theme.cacheKey + codeTheme;
if (this.allProcessedAssets_[cacheKey]) return this.allProcessedAssets_[cacheKey];
const assets:any = {}; const assets:any = {};
for (const key in rules) { for (const key in rules) {
if (!this.pluginEnabled(key)) continue; if (!this.pluginEnabled(key)) continue;
@ -257,11 +281,19 @@ export default class MdToHtml {
} }
} }
return assets; assets['highlight.js'] = [{ name: codeTheme }];
const output = this.processPluginAssets(assets);
this.allProcessedAssets_ = {
[cacheKey]: output,
};
return output;
} }
// TODO: remove // This is similar to allProcessedAssets() but used only by the Rich Text editor
async allAssets(theme:any) { public async allAssets(theme:any) {
const assets:any = {}; const assets:any = {};
for (const key in rules) { for (const key in rules) {
if (!this.pluginEnabled(key)) continue; if (!this.pluginEnabled(key)) continue;
@ -278,7 +310,7 @@ export default class MdToHtml {
return output.pluginAssets; return output.pluginAssets;
} }
async outputAssetsToExternalAssets_(output:any) { private async outputAssetsToExternalAssets_(output:any) {
for (const cssString of output.cssStrings) { for (const cssString of output.cssStrings) {
output.pluginAssets.push(await this.fsDriver().cacheCssToFile(cssString)); output.pluginAssets.push(await this.fsDriver().cacheCssToFile(cssString));
} }
@ -286,7 +318,7 @@ export default class MdToHtml {
return output; return output;
} }
removeMarkdownItWrappingParagraph_(html:string) { private removeMarkdownItWrappingParagraph_(html:string) {
// <p></p>\n // <p></p>\n
if (html.length < 8) return html; if (html.length < 8) return html;
if (html.substr(0, 3) !== '<p>') return html; if (html.substr(0, 3) !== '<p>') return html;
@ -294,7 +326,7 @@ export default class MdToHtml {
return html.substring(3, html.length - 5); return html.substring(3, html.length - 5);
} }
clearCache() { public clearCache() {
this.cachedOutputs_ = {}; this.cachedOutputs_ = {};
} }
@ -338,7 +370,7 @@ export default class MdToHtml {
css: {}, css: {},
pluginAssets: {}, pluginAssets: {},
cache: this.contextCache_, cache: this.contextCache_,
// options: ruleOptions, userData: {},
}; };
const markdownIt = new MarkdownIt({ const markdownIt = new MarkdownIt({
@ -370,10 +402,6 @@ export default class MdToHtml {
this.cachedHighlightedCode_[cacheKey] = hlCode; this.cachedHighlightedCode_[cacheKey] = hlCode;
} }
context.pluginAssets['highlight.js'] = [
{ name: options.codeTheme },
];
outputCodeHtml = hlCode; outputCodeHtml = hlCode;
} catch (error) { } catch (error) {
outputCodeHtml = markdownIt.utils.escapeHtml(trimmedStr); outputCodeHtml = markdownIt.utils.escapeHtml(trimmedStr);
@ -417,19 +445,14 @@ export default class MdToHtml {
for (const key in allRules) { for (const key in allRules) {
if (!this.pluginEnabled(key)) continue; if (!this.pluginEnabled(key)) continue;
const rule = allRules[key];
if (rule.plugin) {
const pluginOptions = {
context: context,
...ruleOptions,
...(ruleOptions.plugins[key] ? ruleOptions.plugins[key] : {}),
};
markdownIt.use(rule.plugin, pluginOptions); const rule = allRules[key];
} else {
const ruleInstall:Function = rule.install ? rule.install : (rule as any); markdownIt.use(rule.plugin, {
markdownIt.use(ruleInstall(context, { ...ruleOptions })); context: context,
} ...ruleOptions,
...(ruleOptions.plugins[key] ? ruleOptions.plugins[key] : {}),
});
} }
markdownIt.use(markdownItAnchor, { slugify: slugify }); markdownIt.use(markdownItAnchor, { slugify: slugify });
@ -440,20 +463,13 @@ export default class MdToHtml {
} }
} }
// const extraPlugins = eventManager.filterEmit('mdToHtmlPlugins', {});
// for (const key in extraPlugins) {
// markdownIt.use(extraPlugins[key].module, extraPlugins[key].options);
// }
setupLinkify(markdownIt); setupLinkify(markdownIt);
const renderedBody = markdownIt.render(body, context); const renderedBody = markdownIt.render(body, context);
const pluginAssets = this.allUnprocessedAssets(theme);
let cssStrings = noteStyle(options.theme); let cssStrings = noteStyle(options.theme);
let output = this.processPluginAssets(pluginAssets); // context.pluginAssets); let output = { ...this.allProcessedAssets(theme, options.codeTheme) };
cssStrings = cssStrings.concat(output.cssStrings); cssStrings = cssStrings.concat(output.cssStrings);
if (options.userCss) cssStrings.push(options.userCss); if (options.userCss) cssStrings.push(options.userCss);
@ -485,7 +501,4 @@ export default class MdToHtml {
return output; return output;
} }
injectedJavaScript() {
return '';
}
} }

View File

@ -1,11 +1,11 @@
function installRule(markdownIt) { function plugin(markdownIt:any) {
const defaultRender = const defaultRender =
markdownIt.renderer.rules.code_inline || markdownIt.renderer.rules.code_inline ||
function(tokens, idx, options, env, self) { function(tokens:any, idx:any, options:any, _env:any, self:any) {
return self.renderToken(tokens, idx, options); return self.renderToken(tokens, idx, options);
}; };
markdownIt.renderer.rules.code_inline = (tokens, idx, options, env, self) => { markdownIt.renderer.rules.code_inline = (tokens:any[], idx:number, options:any, env:any, self:any) => {
const token = tokens[idx]; const token = tokens[idx];
let tokenClass = token.attrGet('class'); let tokenClass = token.attrGet('class');
if (!tokenClass) tokenClass = ''; if (!tokenClass) tokenClass = '';
@ -15,8 +15,6 @@ function installRule(markdownIt) {
}; };
} }
module.exports = function(context, ruleOptions) { export default {
return function(md, mdOptions) { plugin,
installRule(md, mdOptions, ruleOptions);
};
}; };

View File

@ -8,7 +8,7 @@
// So we modify the code below to allow highlight() to return an object that tells how to render // So we modify the code below to allow highlight() to return an object that tells how to render
// the code. // the code.
function installRule(markdownIt:any) { function plugin(markdownIt:any) {
// @ts-ignore: Keep the function signature as-is despite unusued arguments // @ts-ignore: Keep the function signature as-is despite unusued arguments
markdownIt.renderer.rules.fence = function(tokens:any[], idx:number, options:any, env:any, slf:any) { markdownIt.renderer.rules.fence = function(tokens:any[], idx:number, options:any, env:any, slf:any) {
let token = tokens[idx], let token = tokens[idx],
@ -63,8 +63,7 @@ function installRule(markdownIt:any) {
}; };
} }
export default function() { export default {
return function(md:any) { plugin,
installRule(md); };
};
}

View File

@ -1,6 +1,6 @@
const fountain = require('../../vendor/fountain.min.js'); const fountain = require('../../vendor/fountain.min.js');
const fountainCss = function() { const pluginAssets = function() {
return [ return [
{ {
inline: true, inline: true,
@ -102,7 +102,7 @@ const fountainCss = function() {
]; ];
}; };
function renderFountainScript(markdownIt, content) { function renderFountainScript(markdownIt:any, content:string) {
const result = fountain.parse(content); const result = fountain.parse(content);
return ` return `
@ -118,30 +118,19 @@ function renderFountainScript(markdownIt, content) {
`; `;
} }
function addContextAssets(context) { function plugin(markdownIt:any) {
if ('fountain' in context.pluginAssets) return; const defaultRender = markdownIt.renderer.rules.fence || function(tokens:any[], idx:number, options:any, _env:any, self:any) {
context.pluginAssets['fountain'] = fountainCss();
}
function installRule(markdownIt, mdOptions, ruleOptions, context) {
const defaultRender = markdownIt.renderer.rules.fence || function(tokens, idx, options, env, self) {
return self.renderToken(tokens, idx, options); return self.renderToken(tokens, idx, options);
}; };
markdownIt.renderer.rules.fence = function(tokens, idx, options, env, self) { markdownIt.renderer.rules.fence = function(tokens:any[], idx:number, options:any, env:any, self:any) {
const token = tokens[idx]; const token = tokens[idx];
if (token.info !== 'fountain') return defaultRender(tokens, idx, options, env, self); if (token.info !== 'fountain') return defaultRender(tokens, idx, options, env, self);
addContextAssets(context);
return renderFountainScript(markdownIt, token.content); return renderFountainScript(markdownIt, token.content);
}; };
} }
module.exports = { export default {
install: function(context, ruleOptions) { plugin,
return function(md, mdOptions) { assets: pluginAssets,
installRule(md, mdOptions, ruleOptions, context);
};
},
assets: fountainCss,
}; };

View File

@ -1,7 +1,11 @@
// This plugin is used only on mobile, to highlight search results.
import { RuleOptions } from "lib/joplin-renderer/MdToHtml";
const stringUtils = require('../../stringUtils.js'); const stringUtils = require('../../stringUtils.js');
const md5 = require('md5'); const md5 = require('md5');
function createHighlightedTokens(Token, splitted) { function createHighlightedTokens(Token:any, splitted:string[]) {
let token; let token;
const output = []; const output = [];
@ -30,10 +34,11 @@ function createHighlightedTokens(Token, splitted) {
return output; return output;
} }
function installRule(markdownIt, mdOptions, ruleOptions) { // function installRule(markdownIt, mdOptions, ruleOptions) {
function plugin(markdownIt:any, ruleOptions:RuleOptions) {
const divider = md5(Date.now().toString() + Math.random().toString()); const divider = md5(Date.now().toString() + Math.random().toString());
markdownIt.core.ruler.push('highlight_keywords', state => { markdownIt.core.ruler.push('highlight_keywords', (state:any) => {
const keywords = ruleOptions.highlightedKeywords; const keywords = ruleOptions.highlightedKeywords;
if (!keywords || !keywords.length) return; if (!keywords || !keywords.length) return;
@ -60,8 +65,6 @@ function installRule(markdownIt, mdOptions, ruleOptions) {
}); });
} }
module.exports = function(context, ruleOptions) { export default {
return function(md, mdOptions) { plugin,
installRule(md, mdOptions, ruleOptions); }
};
};

View File

@ -1,39 +1,40 @@
// const Resource = require('lib/models/Resource.js'); import { RuleOptions } from "lib/joplin-renderer/MdToHtml";
const htmlUtils = require('../../htmlUtils.js'); const htmlUtils = require('../../htmlUtils.js');
const utils = require('../../utils'); const utils = require('../../utils');
function renderImageHtml(before, src, after, ruleOptions) { function renderImageHtml(before:string, src:string, after:string, ruleOptions:RuleOptions) {
const r = utils.imageReplacement(ruleOptions.ResourceModel, src, ruleOptions.resources, ruleOptions.resourceBaseUrl); const r = utils.imageReplacement(ruleOptions.ResourceModel, src, ruleOptions.resources, ruleOptions.resourceBaseUrl);
if (typeof r === 'string') return r; if (typeof r === 'string') return r;
if (r) return `<img ${before} ${htmlUtils.attributesHtml(r)} ${after}/>`; if (r) return `<img ${before} ${htmlUtils.attributesHtml(r)} ${after}/>`;
return `[Image: ${src}]`; return `[Image: ${src}]`;
} }
function installRule(markdownIt, mdOptions, ruleOptions) { function plugin(markdownIt:any, ruleOptions:RuleOptions) {
const Resource = ruleOptions.ResourceModel; const Resource = ruleOptions.ResourceModel;
const htmlBlockDefaultRender = const htmlBlockDefaultRender =
markdownIt.renderer.rules.html_block || markdownIt.renderer.rules.html_block ||
function(tokens, idx, options, env, self) { function(tokens:any[], idx:number, options:any, _env:any, self:any) {
return self.renderToken(tokens, idx, options); return self.renderToken(tokens, idx, options);
}; };
const htmlInlineDefaultRender = const htmlInlineDefaultRender =
markdownIt.renderer.rules.html_inline || markdownIt.renderer.rules.html_inline ||
function(tokens, idx, options, env, self) { function(tokens:any[], idx:number, options:any, _env:any, self:any) {
return self.renderToken(tokens, idx, options); return self.renderToken(tokens, idx, options);
}; };
const imageRegex = /<img(.*?)src=["'](.*?)["'](.*?)>/gi; const imageRegex = /<img(.*?)src=["'](.*?)["'](.*?)>/gi;
const handleImageTags = function(defaultRender) { const handleImageTags = function(defaultRender:Function) {
return function(tokens, idx, options, env, self) { return function(tokens:any[], idx:number, options:any, env:any, self:any) {
const token = tokens[idx]; const token = tokens[idx];
const content = token.content; const content = token.content;
if (!content.match(imageRegex)) return defaultRender(tokens, idx, options, env, self); if (!content.match(imageRegex)) return defaultRender(tokens, idx, options, env, self);
return content.replace(imageRegex, (v, before, src, after) => { return content.replace(imageRegex, (_v:any, before:string, src:string, after:string) => {
if (!Resource.isResourceUrl(src)) return `<img${before}src="${src}"${after}>`; if (!Resource.isResourceUrl(src)) return `<img${before}src="${src}"${after}>`;
return renderImageHtml(before, src, after, ruleOptions); return renderImageHtml(before, src, after, ruleOptions);
}); });
@ -46,8 +47,4 @@ function installRule(markdownIt, mdOptions, ruleOptions) {
markdownIt.renderer.rules.html_inline = handleImageTags(htmlInlineDefaultRender); markdownIt.renderer.rules.html_inline = handleImageTags(htmlInlineDefaultRender);
} }
module.exports = function(context, ruleOptions) { export default { plugin }
return function(md, mdOptions) {
installRule(md, mdOptions, ruleOptions);
};
};

View File

@ -1,11 +1,13 @@
import { RuleOptions } from "lib/joplin-renderer/MdToHtml";
// const Resource = require('lib/models/Resource.js'); // const Resource = require('lib/models/Resource.js');
const utils = require('../../utils'); const utils = require('../../utils');
const htmlUtils = require('../../htmlUtils.js'); const htmlUtils = require('../../htmlUtils.js');
function installRule(markdownIt, mdOptions, ruleOptions) { function plugin(markdownIt:any, ruleOptions:RuleOptions) {
const defaultRender = markdownIt.renderer.rules.image; const defaultRender = markdownIt.renderer.rules.image;
markdownIt.renderer.rules.image = (tokens, idx, options, env, self) => { markdownIt.renderer.rules.image = (tokens:any[], idx:number, options:any, env:any, self:any) => {
const Resource = ruleOptions.ResourceModel; const Resource = ruleOptions.ResourceModel;
const token = tokens[idx]; const token = tokens[idx];
@ -19,12 +21,11 @@ function installRule(markdownIt, mdOptions, ruleOptions) {
if (r) { if (r) {
let js = ''; let js = '';
if (ruleOptions.enableLongPress) { if (ruleOptions.enableLongPress) {
const longPressDelay = ruleOptions.longPressDelay ? ruleOptions.longPressDelay : 500;
const id = r['data-resource-id']; const id = r['data-resource-id'];
const longPressHandler = `${ruleOptions.postMessageSyntax}('longclick:${id}')`; const longPressHandler = `${ruleOptions.postMessageSyntax}('longclick:${id}')`;
const touchStart = `t=setTimeout(()=>{t=null; ${longPressHandler};}, ${longPressDelay});`; const touchStart = `t=setTimeout(()=>{t=null; ${longPressHandler};}, ${ruleOptions.longPressDelay});`;
const touchEnd = 'if (!!t) clearTimeout(t); t=null'; const touchEnd = 'if (!!t) clearTimeout(t); t=null';
js = ` ontouchstart="${touchStart}" ontouchend="${touchEnd}"`; js = ` ontouchstart="${touchStart}" ontouchend="${touchEnd}"`;
@ -36,8 +37,4 @@ function installRule(markdownIt, mdOptions, ruleOptions) {
}; };
} }
module.exports = function(context, ruleOptions) { export default { plugin };
return function(md, mdOptions) {
installRule(md, mdOptions, ruleOptions);
};
};

View File

@ -1,8 +1,4 @@
/* eslint prefer-const: 0*/ import { RuleOptions } from "lib/joplin-renderer/MdToHtml";
// Based on https://github.com/waylonflinn/markdown-it-katex
'use strict';
let katex = require('katex'); let katex = require('katex');
const md5 = require('md5'); const md5 = require('md5');
@ -46,7 +42,7 @@ function katexStyle() {
// Test if potential opening or closing delimieter // Test if potential opening or closing delimieter
// Assumes that there is a "$" at state.src[pos] // Assumes that there is a "$" at state.src[pos]
function isValidDelim(state, pos) { function isValidDelim(state:any, pos:number) {
let prevChar, let prevChar,
nextChar, nextChar,
max = state.posMax, max = state.posMax,
@ -71,7 +67,7 @@ function isValidDelim(state, pos) {
}; };
} }
function math_inline(state, silent) { function math_inline(state:any, silent:boolean) {
let start, match, token, res, pos; let start, match, token, res, pos;
if (state.src[state.pos] !== '$') { if (state.src[state.pos] !== '$') {
@ -146,7 +142,7 @@ function math_inline(state, silent) {
return true; return true;
} }
function math_block(state, start, end, silent) { function math_block(state:any, start:number, end:number, silent:boolean) {
let firstLine, let firstLine,
lastLine, lastLine,
next, next,
@ -212,80 +208,71 @@ function math_block(state, start, end, silent) {
return true; return true;
} }
const cache_ = {}; const cache_:any = {};
module.exports = { function renderToStringWithCache(latex:string, katexOptions:any) {
install: function(context) { const cacheKey = md5(escape(latex) + escape(stringifySafe(katexOptions)));
if (cacheKey in cache_) {
return cache_[cacheKey];
} else {
const beforeMacros = stringifySafe(katexOptions.macros);
const output = katex.renderToString(latex, katexOptions);
const afterMacros = stringifySafe(katexOptions.macros);
// Don't cache the formulas that add macros, otherwise
// they won't be added on second run.
if (beforeMacros === afterMacros) cache_[cacheKey] = output;
return output;
}
}
export default {
plugin: function(markdownIt:any, options:RuleOptions) {
// Keep macros that persist across Katex blocks to allow defining a macro // Keep macros that persist across Katex blocks to allow defining a macro
// in one block and re-using it later in other blocks. // in one block and re-using it later in other blocks.
// https://github.com/laurent22/joplin/issues/1105 // https://github.com/laurent22/joplin/issues/1105
context.__katex = { macros: {} }; if (!options.context.userData.__katex) options.context.userData.__katex = { macros: {} };
const addContextAssets = () => { const katexOptions:any = {}
context.pluginAssets['katex'] = katexStyle(); katexOptions.macros = options.context.userData.__katex.macros;
}; katexOptions.trust = true;
function renderToStringWithCache(latex, options) { // set KaTeX as the renderer for markdown-it-simplemath
const cacheKey = md5(escape(latex) + escape(stringifySafe(options))); const katexInline = function(latex:string) {
if (cacheKey in cache_) { katexOptions.displayMode = false;
return cache_[cacheKey]; try {
} else { return `<span class="joplin-editable"><span class="joplin-source" data-joplin-language="katex" data-joplin-source-open="$" data-joplin-source-close="$">${markdownIt.utils.escapeHtml(latex)}</span>${renderToStringWithCache(latex, katexOptions)}</span>`;
const beforeMacros = stringifySafe(options.macros); } catch (error) {
const output = katex.renderToString(latex, options); console.error('Katex error for:', latex, error);
const afterMacros = stringifySafe(options.macros); return latex;
// Don't cache the formulas that add macros, otherwise
// they won't be added on second run.
if (beforeMacros === afterMacros) cache_[cacheKey] = output;
return output;
} }
}
return function(md, options) {
// Default options
options = options || {};
options.macros = context.__katex.macros;
options.trust = true;
// set KaTeX as the renderer for markdown-it-simplemath
const katexInline = function(latex) {
options.displayMode = false;
try {
return `<span class="joplin-editable"><span class="joplin-source" data-joplin-language="katex" data-joplin-source-open="$" data-joplin-source-close="$">${md.utils.escapeHtml(latex)}</span>${renderToStringWithCache(latex, options)}</span>`;
} catch (error) {
console.error('Katex error for:', latex, error);
return latex;
}
};
const inlineRenderer = function(tokens, idx) {
addContextAssets();
return katexInline(tokens[idx].content);
};
const katexBlock = function(latex) {
options.displayMode = true;
try {
return `<div class="joplin-editable"><pre class="joplin-source" data-joplin-language="katex" data-joplin-source-open="$$&#10;" data-joplin-source-close="&#10;$$&#10;">${md.utils.escapeHtml(latex)}</pre>${renderToStringWithCache(latex, options)}</div>`;
} catch (error) {
console.error('Katex error for:', latex, error);
return latex;
}
};
const blockRenderer = function(tokens, idx) {
addContextAssets();
return `${katexBlock(tokens[idx].content)}\n`;
};
md.inline.ruler.after('escape', 'math_inline', math_inline);
md.block.ruler.after('blockquote', 'math_block', math_block, {
alt: ['paragraph', 'reference', 'blockquote', 'list'],
});
md.renderer.rules.math_inline = inlineRenderer;
md.renderer.rules.math_block = blockRenderer;
}; };
const inlineRenderer = function(tokens:any[], idx:number) {
return katexInline(tokens[idx].content);
};
const katexBlock = function(latex:string) {
katexOptions.displayMode = true;
try {
return `<div class="joplin-editable"><pre class="joplin-source" data-joplin-language="katex" data-joplin-source-open="$$&#10;" data-joplin-source-close="&#10;$$&#10;">${markdownIt.utils.escapeHtml(latex)}</pre>${renderToStringWithCache(latex, katexOptions)}</div>`;
} catch (error) {
console.error('Katex error for:', latex, error);
return latex;
}
};
const blockRenderer = function(tokens:any[], idx:number) {
return `${katexBlock(tokens[idx].content)}\n`;
};
markdownIt.inline.ruler.after('escape', 'math_inline', math_inline);
markdownIt.block.ruler.after('blockquote', 'math_block', math_block, {
alt: ['paragraph', 'reference', 'blockquote', 'list'],
});
markdownIt.renderer.rules.math_inline = inlineRenderer;
markdownIt.renderer.rules.math_block = blockRenderer;
}, },
assets: katexStyle, assets: katexStyle,
}; };

View File

@ -1,18 +1,13 @@
import { RuleOptions } from "lib/joplin-renderer/MdToHtml";
const Entities = require('html-entities').AllHtmlEntities; const Entities = require('html-entities').AllHtmlEntities;
const htmlentities = new Entities().encode; const htmlentities = new Entities().encode;
const utils = require('../../utils'); const utils = require('../../utils');
const urlUtils = require('../../urlUtils.js'); const urlUtils = require('../../urlUtils.js');
const { getClassNameForMimeType } = require('font-awesome-filetypes'); const { getClassNameForMimeType } = require('font-awesome-filetypes');
function installRule(markdownIt, mdOptions, ruleOptions) { function plugin(markdownIt:any, ruleOptions:RuleOptions) {
const pluginOptions = { markdownIt.renderer.rules.link_open = function(tokens:any[], idx:number) {
// linkRenderingType = 1 is the regular rendering and clicking on it is handled via embedded JS (in onclick attribute)
// linkRenderingType = 2 gives a plain link with no JS. Caller needs to handle clicking on the link.
linkRenderingType: 1,
...ruleOptions.plugins['link_open'],
};
markdownIt.renderer.rules.link_open = function(tokens, idx) {
const token = tokens[idx]; const token = tokens[idx];
let href = utils.getAttr(token.attrs, 'href'); let href = utils.getAttr(token.attrs, 'href');
const resourceHrefInfo = urlUtils.parseResourceUrl(href); const resourceHrefInfo = urlUtils.parseResourceUrl(href);
@ -65,12 +60,10 @@ function installRule(markdownIt, mdOptions, ruleOptions) {
let js = `${ruleOptions.postMessageSyntax}(${JSON.stringify(href)}, { resourceId: ${JSON.stringify(resourceId)} }); return false;`; let js = `${ruleOptions.postMessageSyntax}(${JSON.stringify(href)}, { resourceId: ${JSON.stringify(resourceId)} }); return false;`;
if (ruleOptions.enableLongPress && !!resourceId) { if (ruleOptions.enableLongPress && !!resourceId) {
const longPressDelay = ruleOptions.longPressDelay ? ruleOptions.longPressDelay : 500;
const onClick = `${ruleOptions.postMessageSyntax}(${JSON.stringify(href)})`; const onClick = `${ruleOptions.postMessageSyntax}(${JSON.stringify(href)})`;
const onLongClick = `${ruleOptions.postMessageSyntax}("longclick:${resourceId}")`; const onLongClick = `${ruleOptions.postMessageSyntax}("longclick:${resourceId}")`;
const touchStart = `t=setTimeout(()=>{t=null; ${onLongClick};}, ${longPressDelay});`; const touchStart = `t=setTimeout(()=>{t=null; ${onLongClick};}, ${ruleOptions.longPressDelay});`;
const touchEnd = `if (!!t) {clearTimeout(t); t=null; ${onClick};}`; const touchEnd = `if (!!t) {clearTimeout(t); t=null; ${onClick};}`;
js = `ontouchstart='${touchStart}' ontouchend='${touchEnd}'`; js = `ontouchstart='${touchStart}' ontouchend='${touchEnd}'`;
@ -80,7 +73,7 @@ function installRule(markdownIt, mdOptions, ruleOptions) {
if (hrefAttr.indexOf('#') === 0 && href.indexOf('#') === 0) js = ''; // If it's an internal anchor, don't add any JS since the webview is going to handle navigating to the right place if (hrefAttr.indexOf('#') === 0 && href.indexOf('#') === 0) js = ''; // If it's an internal anchor, don't add any JS since the webview is going to handle navigating to the right place
if (ruleOptions.plainResourceRendering || pluginOptions.linkRenderingType === 2) { if (ruleOptions.plainResourceRendering || ruleOptions.linkRenderingType === 2) {
return `<a data-from-md ${resourceIdAttr} title='${htmlentities(title)}' href='${htmlentities(href)}' type='${htmlentities(mime)}'>`; return `<a data-from-md ${resourceIdAttr} title='${htmlentities(title)}' href='${htmlentities(href)}' type='${htmlentities(mime)}'>`;
} else { } else {
return `<a data-from-md ${resourceIdAttr} title='${htmlentities(title)}' href='${hrefAttr}' ${js} type='${htmlentities(mime)}'>${icon}`; return `<a data-from-md ${resourceIdAttr} title='${htmlentities(title)}' href='${hrefAttr}' ${js} type='${htmlentities(mime)}'>${icon}`;
@ -88,9 +81,4 @@ function installRule(markdownIt, mdOptions, ruleOptions) {
}; };
} }
module.exports = function(context, ruleOptions) { export default { plugin };
return function(md, mdOptions) {
installRule(md, mdOptions, ruleOptions);
};
};

View File

@ -1,49 +1,35 @@
function style() {
return [
{ name: 'mermaid.min.js' },
{ name: 'mermaid_render.js' },
{
inline: true,
// Note: Mermaid is buggy when rendering below a certain width (500px?)
// so set an arbitrarily high width here for the container. Once the
// diagram is rendered it will be reset to 100% in mermaid_render.js
text: '.mermaid { background-color: white; width: 640px; }',
mime: 'text/css',
},
];
}
function addContextAssets(context:any) {
if ('mermaid' in context.pluginAssets) return;
context.pluginAssets['mermaid'] = style();
}
// @ts-ignore: Keep the function signature as-is despite unusued arguments
function installRule(markdownIt:any, mdOptions:any, ruleOptions:any, context:any) {
const defaultRender:Function = markdownIt.renderer.rules.fence || function(tokens:any[], idx:number, options:any, env:any, self:any) {
return self.renderToken(tokens, idx, options, env, self);
};
markdownIt.renderer.rules.fence = function(tokens:any[], idx:number, options:{}, env:any, self:any) {
const token = tokens[idx];
if (token.info !== 'mermaid') return defaultRender(tokens, idx, options, env, self);
addContextAssets(context);
const contentHtml = markdownIt.utils.escapeHtml(token.content);
return `
<div class="joplin-editable">
<pre class="joplin-source" data-joplin-language="mermaid" data-joplin-source-open="\`\`\`mermaid&#10;" data-joplin-source-close="&#10;\`\`\`&#10;">${contentHtml}</pre>
<div class="mermaid">${contentHtml}</div>
</div>
`;
};
}
export default { export default {
install: function(context:any, ruleOptions:any) {
return function(md:any, mdOptions:any) { assets: function() {
installRule(md, mdOptions, ruleOptions, context); return [
{ name: 'mermaid.min.js' },
{ name: 'mermaid_render.js' },
{
inline: true,
// Note: Mermaid is buggy when rendering below a certain width (500px?)
// so set an arbitrarily high width here for the container. Once the
// diagram is rendered it will be reset to 100% in mermaid_render.js
text: '.mermaid { background-color: white; width: 640px; }',
mime: 'text/css',
},
];
},
plugin: function(markdownIt:any) {
const defaultRender:Function = markdownIt.renderer.rules.fence || function(tokens:any[], idx:number, options:any, env:any, self:any) {
return self.renderToken(tokens, idx, options, env, self);
};
markdownIt.renderer.rules.fence = function(tokens:any[], idx:number, options:{}, env:any, self:any) {
const token = tokens[idx];
if (token.info !== 'mermaid') return defaultRender(tokens, idx, options, env, self);
const contentHtml = markdownIt.utils.escapeHtml(token.content);
return `
<div class="joplin-editable">
<pre class="joplin-source" data-joplin-language="mermaid" data-joplin-source-open="\`\`\`mermaid&#10;" data-joplin-source-close="&#10;\`\`\`&#10;">${contentHtml}</pre>
<div class="mermaid">${contentHtml}</div>
</div>
`;
}; };
}, },
assets: style,
}; };

View File

@ -1,53 +1,50 @@
import { RuleOptions } from 'lib/joplin-renderer/MdToHtml';
const md5 = require('md5'); const md5 = require('md5');
const htmlUtils = require('../../htmlUtils'); const htmlUtils = require('../../htmlUtils');
// @ts-ignore: Keep the function signature as-is despite unusued arguments export default {
function installRule(markdownIt:any, mdOptions:any, ruleOptions:any, context:any) { plugin: function(markdownIt:any, ruleOptions:RuleOptions) {
markdownIt.core.ruler.push('sanitize_html', (state:any) => { markdownIt.core.ruler.push('sanitize_html', (state:any) => {
const tokens = state.tokens; const tokens = state.tokens;
const walkHtmlTokens = (tokens:any[]) => { const walkHtmlTokens = (tokens:any[]) => {
if (!tokens || !tokens.length) return; if (!tokens || !tokens.length) return;
for (const token of tokens) { for (const token of tokens) {
if (!['html_block', 'html_inline'].includes(token.type)) { if (!['html_block', 'html_inline'].includes(token.type)) {
walkHtmlTokens(token.children);
continue;
}
const cacheKey = md5(escape(token.content));
let sanitizedContent = ruleOptions.context.cache.value(cacheKey);
// For html_inline, the content is only a fragment of HTML, as it will be rendered, but
// it's not necessarily valid HTML. For example this HTML:
//
// <a href="#">Testing</a>
//
// will be rendered as three tokens:
//
// html_inline: <a href="#">
// text: Testing
// html_inline: </a>
//
// So the sanitizeHtml function must handle this kind of non-valid HTML.
if (!sanitizedContent) {
sanitizedContent = htmlUtils.sanitizeHtml(token.content, { addNoMdConvClass: true });
}
token.content = sanitizedContent;
ruleOptions.context.cache.setValue(cacheKey, sanitizedContent, 1000 * 60 * 60);
walkHtmlTokens(token.children); walkHtmlTokens(token.children);
continue;
} }
};
const cacheKey = md5(escape(token.content)); walkHtmlTokens(tokens);
let sanitizedContent = context.cache.value(cacheKey); });
},
// For html_inline, the content is only a fragment of HTML, as it will be rendered, but };
// it's not necessarily valid HTML. For example this HTML:
//
// <a href="#">Testing</a>
//
// will be rendered as three tokens:
//
// html_inline: <a href="#">
// text: Testing
// html_inline: </a>
//
// So the sanitizeHtml function must handle this kind of non-valid HTML.
if (!sanitizedContent) {
sanitizedContent = htmlUtils.sanitizeHtml(token.content, { addNoMdConvClass: true });
}
token.content = sanitizedContent;
context.cache.setValue(cacheKey, sanitizedContent, 1000 * 60 * 60);
walkHtmlTokens(token.children);
}
};
walkHtmlTokens(tokens);
});
}
export default function(context:any, ruleOptions:any) {
return function(md:any, mdOptions:any) {
installRule(md, mdOptions, ruleOptions, context);
};
}

View File

@ -322,20 +322,36 @@ export enum ContentScriptType {
* Registers a new Markdown-It plugin, which should follow this template: * Registers a new Markdown-It plugin, which should follow this template:
* *
* ```javascript * ```javascript
* // The module should export a function that takes a `pluginContext` as argument (currently unused) *
* module.exports = function(pluginContext) { * // The module should export an object like below:
* // That function should return an object with a number of properties: *
* return { * module.exports = {
* // Required: * default: {
* install: function(context, ruleOptions) { * // This is the actual Markdown-It plugin - check the [official doc](https://github.com/markdown-it/markdown-it) for more information
* return function(md, mdOptions) { * // The `options` parameter is of type [RuleOptions](https://github.com/laurent22/joplin/blob/dev/ReactNativeClient/lib/joplin-renderer/MdToHtml.ts), which
* installRule(md, mdOptions, ruleOptions, context); * // contains a number of options, mostly useful for Joplin's internal code.
* }; * plugin: function(markdownIt, options) {
*
* }, * },
*
* // You may also specify additional assets such as JS or CSS that should be loaded in the rendered HTML document.
* // Check for example the Joplin [Mermaid plugin](https://github.com/laurent22/joplin/blob/dev/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.ts) to
* // see how the data should be structured.
* assets: {}, * assets: {},
* } * }
* } * }
* ``` * ```
*
* To include a regular Markdown-It plugin, that doesn't make use of any Joplin-specific feature, you
* would simply create a file such as this:
*
* ```javascript
* module.exports = {
* default: {
* plugin: require('markdown-it-toc-done-right');
* }
* }
* ```
*/ */
MarkdownItPlugin = 'markdownItPlugin', MarkdownItPlugin = 'markdownItPlugin',
CodeMirrorPlugin = 'codeMirrorPlugin', CodeMirrorPlugin = 'codeMirrorPlugin',

View File

@ -1,8 +0,0 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
const uuid_1 = require('lib/uuid');
function viewIdGen(plugin) {
return `plugin-view-${plugin.id}-${uuid_1.default.createNano()}`;
}
exports.default = viewIdGen;
// # sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmlld0lkR2VuLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsidmlld0lkR2VuLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQ0EsbUNBQTRCO0FBRTVCLFNBQXdCLFNBQVMsQ0FBQyxNQUFhO0lBQzlDLE9BQU8sZUFBZSxNQUFNLENBQUMsRUFBRSxJQUFJLGNBQUksQ0FBQyxVQUFVLEVBQUUsRUFBRSxDQUFBO0FBQ3ZELENBQUM7QUFGRCw0QkFFQyJ9

View File

@ -393,6 +393,7 @@ function themeStyle(themeId:number) {
output = Object.assign({}, globalStyle, fontSizes, themes[themeId]); output = Object.assign({}, globalStyle, fontSizes, themes[themeId]);
output = addMissingProperties(output); output = addMissingProperties(output);
output = addExtraStyles(output); output = addExtraStyles(output);
output.cacheKey = cacheKey;
themeCache_[cacheKey] = output; themeCache_[cacheKey] = output;
return themeCache_[cacheKey]; return themeCache_[cacheKey];