mirror of https://github.com/laurent22/joplin.git
Simplified how Markdown-It plugins are created
parent
47c7b864cb
commit
fe90d92e01
|
@ -59,7 +59,7 @@ describe('MdToHtml', function() {
|
||||||
if (mdFilename === 'checkbox_alternative.md') {
|
if (mdFilename === 'checkbox_alternative.md') {
|
||||||
mdToHtmlOptions.plugins = {
|
mdToHtmlOptions.plugins = {
|
||||||
checkbox: {
|
checkbox: {
|
||||||
renderingType: 2,
|
checkboxRenderingType: 2,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,6 @@ module.exports = function(pluginContext) {
|
||||||
installRule(md, mdOptions, ruleOptions, context);
|
installRule(md, mdOptions, ruleOptions, context);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
style: {},
|
assets: {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,6 @@ module.exports = function(pluginContext) {
|
||||||
installRule(md, mdOptions, ruleOptions, context);
|
installRule(md, mdOptions, ruleOptions, context);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
style: {},
|
assets: {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ function markupRenderOptions(override:any = null) {
|
||||||
return {
|
return {
|
||||||
plugins: {
|
plugins: {
|
||||||
checkbox: {
|
checkbox: {
|
||||||
renderingType: 2,
|
checkboxRenderingType: 2,
|
||||||
},
|
},
|
||||||
link_open: {
|
link_open: {
|
||||||
linkRenderingType: 2,
|
linkRenderingType: 2,
|
||||||
|
|
|
@ -10,7 +10,7 @@ export interface Props {
|
||||||
onMessage:Function,
|
onMessage:Function,
|
||||||
pluginId:string,
|
pluginId:string,
|
||||||
viewId:string,
|
viewId:string,
|
||||||
themeId:string,
|
themeId:number,
|
||||||
minWidth?: number,
|
minWidth?: number,
|
||||||
minHeight?: number,
|
minHeight?: number,
|
||||||
fitToContent?: boolean,
|
fitToContent?: boolean,
|
||||||
|
|
|
@ -6,7 +6,7 @@ const { camelCaseToDash, formatCssSize } = require('lib/string-utils');
|
||||||
|
|
||||||
interface HookDependencies {
|
interface HookDependencies {
|
||||||
pluginId: string,
|
pluginId: string,
|
||||||
themeId: string,
|
themeId: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
function themeToCssVariables(theme:any) {
|
function themeToCssVariables(theme:any) {
|
||||||
|
|
|
@ -8,6 +8,8 @@ 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,
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RendererRules {
|
interface RendererRules {
|
||||||
|
@ -113,6 +115,15 @@ interface RenderResult {
|
||||||
cssStrings: string[],
|
cssStrings: string[],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RuleOptions {
|
||||||
|
context: PluginContext,
|
||||||
|
theme: any,
|
||||||
|
postMessageSyntax: string,
|
||||||
|
|
||||||
|
// Used by checkboxes to specify how it should be rendered
|
||||||
|
checkboxRenderingType?: number,
|
||||||
|
}
|
||||||
|
|
||||||
export default class MdToHtml {
|
export default class MdToHtml {
|
||||||
|
|
||||||
private resourceBaseUrl_:string;
|
private resourceBaseUrl_:string;
|
||||||
|
@ -235,6 +246,21 @@ export default class MdToHtml {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private allUnprocessedAssets(theme:any) {
|
||||||
|
const assets:any = {};
|
||||||
|
for (const key in rules) {
|
||||||
|
if (!this.pluginEnabled(key)) continue;
|
||||||
|
const rule = rules[key];
|
||||||
|
|
||||||
|
if (rule.assets) {
|
||||||
|
assets[key] = rule.assets(theme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return assets;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: remove
|
||||||
async allAssets(theme:any) {
|
async allAssets(theme:any) {
|
||||||
const assets:any = {};
|
const assets:any = {};
|
||||||
for (const key in rules) {
|
for (const key in rules) {
|
||||||
|
@ -303,17 +329,18 @@ export default class MdToHtml {
|
||||||
const cachedOutput = this.cachedOutputs_[cacheKey];
|
const cachedOutput = this.cachedOutputs_[cacheKey];
|
||||||
if (cachedOutput) return cachedOutput;
|
if (cachedOutput) return cachedOutput;
|
||||||
|
|
||||||
const context:PluginContext = {
|
|
||||||
css: {},
|
|
||||||
pluginAssets: {},
|
|
||||||
cache: this.contextCache_,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ruleOptions = Object.assign({}, options, {
|
const ruleOptions = Object.assign({}, options, {
|
||||||
resourceBaseUrl: this.resourceBaseUrl_,
|
resourceBaseUrl: this.resourceBaseUrl_,
|
||||||
ResourceModel: this.ResourceModel_,
|
ResourceModel: this.ResourceModel_,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const context:PluginContext = {
|
||||||
|
css: {},
|
||||||
|
pluginAssets: {},
|
||||||
|
cache: this.contextCache_,
|
||||||
|
// options: ruleOptions,
|
||||||
|
};
|
||||||
|
|
||||||
const markdownIt = new MarkdownIt({
|
const markdownIt = new MarkdownIt({
|
||||||
breaks: !this.pluginEnabled('softbreaks'),
|
breaks: !this.pluginEnabled('softbreaks'),
|
||||||
typographer: this.pluginEnabled('typographer'),
|
typographer: this.pluginEnabled('typographer'),
|
||||||
|
@ -391,9 +418,19 @@ 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];
|
const rule = allRules[key];
|
||||||
|
if (rule.plugin) {
|
||||||
|
const pluginOptions = {
|
||||||
|
context: context,
|
||||||
|
...ruleOptions,
|
||||||
|
...(ruleOptions.plugins[key] ? ruleOptions.plugins[key] : {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
markdownIt.use(rule.plugin, pluginOptions);
|
||||||
|
} else {
|
||||||
const ruleInstall:Function = rule.install ? rule.install : (rule as any);
|
const ruleInstall:Function = rule.install ? rule.install : (rule as any);
|
||||||
markdownIt.use(ruleInstall(context, { ...ruleOptions }));
|
markdownIt.use(ruleInstall(context, { ...ruleOptions }));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
markdownIt.use(markdownItAnchor, { slugify: slugify });
|
markdownIt.use(markdownItAnchor, { slugify: slugify });
|
||||||
|
|
||||||
|
@ -410,11 +447,13 @@ export default class MdToHtml {
|
||||||
|
|
||||||
setupLinkify(markdownIt);
|
setupLinkify(markdownIt);
|
||||||
|
|
||||||
const renderedBody = markdownIt.render(body);
|
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(context.pluginAssets);
|
let output = this.processPluginAssets(pluginAssets); // context.pluginAssets);
|
||||||
cssStrings = cssStrings.concat(output.cssStrings);
|
cssStrings = cssStrings.concat(output.cssStrings);
|
||||||
|
|
||||||
if (options.userCss) cssStrings.push(options.userCss);
|
if (options.userCss) cssStrings.push(options.userCss);
|
||||||
|
|
|
@ -1,24 +1,21 @@
|
||||||
|
import { RuleOptions } from '../../MdToHtml';
|
||||||
|
|
||||||
let checkboxIndex_ = -1;
|
let checkboxIndex_ = -1;
|
||||||
|
|
||||||
const pluginAssets:Function[] = [];
|
function pluginAssets(theme:any) {
|
||||||
|
|
||||||
pluginAssets[1] = function() {
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
inline: true,
|
inline: true,
|
||||||
mime: 'text/css',
|
mime: 'text/css',
|
||||||
text: `
|
text: `
|
||||||
|
/*
|
||||||
|
FOR THE MARKDOWN EDITOR
|
||||||
|
*/
|
||||||
|
|
||||||
/* Remove the indentation from the checkboxes at the root of the document
|
/* Remove the indentation from the checkboxes at the root of the document
|
||||||
(otherwise they are too far right), but keep it for their children to allow
|
(otherwise they are too far right), but keep it for their children to allow
|
||||||
nested lists. Make sure this value matches the UL margin. */
|
nested lists. Make sure this value matches the UL margin. */
|
||||||
|
|
||||||
/*
|
|
||||||
.md-checkbox .checkbox-wrapper {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
li.md-checkbox {
|
li.md-checkbox {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
}
|
}
|
||||||
|
@ -26,30 +23,16 @@ pluginAssets[1] = function() {
|
||||||
li.md-checkbox input[type=checkbox] {
|
li.md-checkbox input[type=checkbox] {
|
||||||
margin-left: -1.71em;
|
margin-left: -1.71em;
|
||||||
margin-right: 0.7em;
|
margin-right: 0.7em;
|
||||||
}`,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
pluginAssets[2] = function(theme:any) {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
inline: true,
|
|
||||||
mime: 'text/css',
|
|
||||||
text: `
|
|
||||||
/* https://stackoverflow.com/questions/7478336/only-detect-click-event-on-pseudo-element#comment39751366_7478344 */
|
|
||||||
/* Not doing this trick anymore. See Modules/TinyMCE/JoplinLists/src/main/ts/ui/Buttons.ts */
|
|
||||||
|
|
||||||
/*
|
|
||||||
ul.joplin-checklist li {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
ul.joplin-checklist {
|
ul.joplin-checklist {
|
||||||
list-style:none;
|
list-style:none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
FOR THE RICH TEXT EDITOR
|
||||||
|
*/
|
||||||
|
|
||||||
ul.joplin-checklist li::before {
|
ul.joplin-checklist li::before {
|
||||||
content:"\\f14a";
|
content:"\\f14a";
|
||||||
font-family:"Font Awesome 5 Free";
|
font-family:"Font Awesome 5 Free";
|
||||||
|
@ -68,7 +51,7 @@ pluginAssets[2] = function(theme:any) {
|
||||||
}`,
|
}`,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
};
|
}
|
||||||
|
|
||||||
function createPrefixTokens(Token:any, id:string, checked:boolean, label:string, postMessageSyntax:string, sourceToken:any):any[] {
|
function createPrefixTokens(Token:any, id:string, checked:boolean, label:string, postMessageSyntax:string, sourceToken:any):any[] {
|
||||||
let token = null;
|
let token = null;
|
||||||
|
@ -129,9 +112,8 @@ function createSuffixTokens(Token:any):any[] {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore: Keep the function signature as-is despite unusued arguments
|
function checkboxPlugin(markdownIt:any, options:RuleOptions) {
|
||||||
function installRule(markdownIt:any, mdOptions:any, ruleOptions:any, context:any) {
|
const renderingType = options.checkboxRenderingType || 1;
|
||||||
const pluginOptions = { renderingType: 1, ...ruleOptions.plugins['checkbox'] };
|
|
||||||
|
|
||||||
markdownIt.core.ruler.push('checkbox', (state:any) => {
|
markdownIt.core.ruler.push('checkbox', (state:any) => {
|
||||||
const tokens = state.tokens;
|
const tokens = state.tokens;
|
||||||
|
@ -180,14 +162,14 @@ function installRule(markdownIt:any, mdOptions:any, ruleOptions:any, context:any
|
||||||
|
|
||||||
const currentList = lists[lists.length - 1];
|
const currentList = lists[lists.length - 1];
|
||||||
|
|
||||||
if (pluginOptions.renderingType === 1) {
|
if (renderingType === 1) {
|
||||||
checkboxIndex_++;
|
checkboxIndex_++;
|
||||||
const id = `md-checkbox-${checkboxIndex_}`;
|
const id = `md-checkbox-${checkboxIndex_}`;
|
||||||
|
|
||||||
// Prepend the text content with the checkbox markup and the opening <label> tag
|
// Prepend the text content with the checkbox markup and the opening <label> tag
|
||||||
// then append the </label> tag at the end of the text content.
|
// then append the </label> tag at the end of the text content.
|
||||||
|
|
||||||
const prefix = createPrefixTokens(Token, id, checked, label, ruleOptions.postMessageSyntax, token);
|
const prefix = createPrefixTokens(Token, id, checked, label, options.postMessageSyntax, token);
|
||||||
const suffix = createSuffixTokens(Token);
|
const suffix = createSuffixTokens(Token);
|
||||||
|
|
||||||
token.children = markdownIt.utils.arrayReplaceAt(token.children, 0, prefix);
|
token.children = markdownIt.utils.arrayReplaceAt(token.children, 0, prefix);
|
||||||
|
@ -214,20 +196,12 @@ function installRule(markdownIt:any, mdOptions:any, ruleOptions:any, context:any
|
||||||
currentListItem.attrSet('class', (`${currentListItem.attrGet('class') || ''} checked`).trim());
|
currentListItem.attrSet('class', (`${currentListItem.attrGet('class') || ''} checked`).trim());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!('checkbox' in context.pluginAssets)) {
|
|
||||||
context.pluginAssets['checkbox'] = pluginAssets[pluginOptions.renderingType](ruleOptions.theme);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
install: function(context:any, ruleOptions:any) {
|
plugin: checkboxPlugin,
|
||||||
return function(md:any, mdOptions:any) {
|
assets: pluginAssets,
|
||||||
installRule(md, mdOptions, ruleOptions, context);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
assets: pluginAssets[2],
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -49,6 +49,17 @@ export default class JoplinPlugins {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a new content script. Unlike regular plugin code, which runs in a separate process, content scripts run within the main process code
|
||||||
|
* and thus allow improved performances and more customisations in specific cases. It can be used for example to load a Markdown or editor plugin.
|
||||||
|
*
|
||||||
|
* Note that registering a content script in itself will do nothing - it will only be loaded in specific cases by the relevant app modules
|
||||||
|
* (eg. the Markdown renderer or the code editor). So it is not a way to inject and run arbitrary code in the app, which for safety and performance reasons is not supported.
|
||||||
|
*
|
||||||
|
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/CliClient/tests/support/plugins/content_script)
|
||||||
|
*
|
||||||
|
* @param scriptPath Must be a path relative to the plugin main script. For example, if your file content_script.js is next to your index.ts file, you would set `scriptPath` to `"./content_script.js`.
|
||||||
|
*/
|
||||||
async registerContentScript(type:ContentScriptType, id:string, scriptPath:string) {
|
async registerContentScript(type:ContentScriptType, id:string, scriptPath:string) {
|
||||||
return this.plugin.registerContentScript(type, id, scriptPath);
|
return this.plugin.registerContentScript(type, id, scriptPath);
|
||||||
}
|
}
|
||||||
|
|
|
@ -318,6 +318,25 @@ export type Path = string[];
|
||||||
// =================================================================
|
// =================================================================
|
||||||
|
|
||||||
export enum ContentScriptType {
|
export enum ContentScriptType {
|
||||||
|
/**
|
||||||
|
* Registers a new Markdown-It plugin, which should follow this template:
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* // The module should export a function that takes a `pluginContext` as argument (currently unused)
|
||||||
|
* module.exports = function(pluginContext) {
|
||||||
|
* // That function should return an object with a number of properties:
|
||||||
|
* return {
|
||||||
|
* // Required:
|
||||||
|
* install: function(context, ruleOptions) {
|
||||||
|
* return function(md, mdOptions) {
|
||||||
|
* installRule(md, mdOptions, ruleOptions, context);
|
||||||
|
* };
|
||||||
|
* },
|
||||||
|
* assets: {},
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
MarkdownItPlugin = 'markdownItPlugin',
|
MarkdownItPlugin = 'markdownItPlugin',
|
||||||
CodeMirrorPlugin = 'codeMirrorPlugin',
|
CodeMirrorPlugin = 'codeMirrorPlugin',
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,8 @@ import theme_solarizedDark from './themes/solarizedDark';
|
||||||
import theme_nord from './themes/nord';
|
import theme_nord from './themes/nord';
|
||||||
import theme_aritimDark from './themes/aritimDark';
|
import theme_aritimDark from './themes/aritimDark';
|
||||||
import theme_oledDark from './themes/oledDark';
|
import theme_oledDark from './themes/oledDark';
|
||||||
|
import Setting from 'lib/models/Setting';
|
||||||
|
|
||||||
const Setting = require('lib/models/Setting').default;
|
|
||||||
const Color = require('color');
|
const Color = require('color');
|
||||||
|
|
||||||
const themes:any = {
|
const themes:any = {
|
||||||
|
@ -364,13 +364,13 @@ function addExtraStyles(style:any) {
|
||||||
|
|
||||||
const themeCache_:any = {};
|
const themeCache_:any = {};
|
||||||
|
|
||||||
function themeStyle(theme:any) {
|
function themeStyle(themeId:number) {
|
||||||
if (!theme) throw new Error('Theme must be specified');
|
if (!themeId) throw new Error('Theme must be specified');
|
||||||
|
|
||||||
const zoomRatio = 1; // Setting.value('style.zoom') / 100;
|
const zoomRatio = 1; // Setting.value('style.zoom') / 100;
|
||||||
const editorFontSize = Setting.value('style.editor.fontSize');
|
const editorFontSize = Setting.value('style.editor.fontSize');
|
||||||
|
|
||||||
const cacheKey = [theme, zoomRatio, editorFontSize].join('-');
|
const cacheKey = [themeId, zoomRatio, editorFontSize].join('-');
|
||||||
if (themeCache_[cacheKey]) return themeCache_[cacheKey];
|
if (themeCache_[cacheKey]) return themeCache_[cacheKey];
|
||||||
|
|
||||||
// Font size are not theme specific, but they must be referenced
|
// Font size are not theme specific, but they must be referenced
|
||||||
|
@ -390,7 +390,7 @@ function themeStyle(theme:any) {
|
||||||
|
|
||||||
// All theme are based on the light style, and just override the
|
// All theme are based on the light style, and just override the
|
||||||
// relevant properties
|
// relevant properties
|
||||||
output = Object.assign({}, globalStyle, fontSizes, themes[theme]);
|
output = Object.assign({}, globalStyle, fontSizes, themes[themeId]);
|
||||||
output = addMissingProperties(output);
|
output = addMissingProperties(output);
|
||||||
output = addExtraStyles(output);
|
output = addExtraStyles(output);
|
||||||
|
|
||||||
|
@ -406,7 +406,7 @@ const cachedStyles_:any = {
|
||||||
// cacheKey must be a globally unique key, and must change whenever
|
// cacheKey must be a globally unique key, and must change whenever
|
||||||
// the dependencies of the style change. If the style depends only
|
// the dependencies of the style change. If the style depends only
|
||||||
// on the theme, a static string can be provided as a cache key.
|
// on the theme, a static string can be provided as a cache key.
|
||||||
function buildStyle(cacheKey:any, themeId:string, callback:Function) {
|
function buildStyle(cacheKey:any, themeId:number, callback:Function) {
|
||||||
cacheKey = Array.isArray(cacheKey) ? cacheKey.join('_') : cacheKey;
|
cacheKey = Array.isArray(cacheKey) ? cacheKey.join('_') : cacheKey;
|
||||||
|
|
||||||
// We clear the cache whenever switching themes
|
// We clear the cache whenever switching themes
|
||||||
|
|
Loading…
Reference in New Issue