mirror of
https://github.com/laurent22/joplin.git
synced 2024-11-24 08:12:24 +02:00
Simplified how Markdown-It plugins are created
This commit is contained in:
parent
47c7b864cb
commit
fe90d92e01
@ -59,7 +59,7 @@ describe('MdToHtml', function() {
|
||||
if (mdFilename === 'checkbox_alternative.md') {
|
||||
mdToHtmlOptions.plugins = {
|
||||
checkbox: {
|
||||
renderingType: 2,
|
||||
checkboxRenderingType: 2,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -17,6 +17,6 @@ module.exports = function(pluginContext) {
|
||||
installRule(md, mdOptions, ruleOptions, context);
|
||||
};
|
||||
},
|
||||
style: {},
|
||||
assets: {},
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,6 @@ module.exports = function(pluginContext) {
|
||||
installRule(md, mdOptions, ruleOptions, context);
|
||||
};
|
||||
},
|
||||
style: {},
|
||||
assets: {},
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ function markupRenderOptions(override:any = null) {
|
||||
return {
|
||||
plugins: {
|
||||
checkbox: {
|
||||
renderingType: 2,
|
||||
checkboxRenderingType: 2,
|
||||
},
|
||||
link_open: {
|
||||
linkRenderingType: 2,
|
||||
|
@ -10,7 +10,7 @@ export interface Props {
|
||||
onMessage:Function,
|
||||
pluginId:string,
|
||||
viewId:string,
|
||||
themeId:string,
|
||||
themeId:number,
|
||||
minWidth?: number,
|
||||
minHeight?: number,
|
||||
fitToContent?: boolean,
|
||||
|
@ -6,7 +6,7 @@ const { camelCaseToDash, formatCssSize } = require('lib/string-utils');
|
||||
|
||||
interface HookDependencies {
|
||||
pluginId: string,
|
||||
themeId: string,
|
||||
themeId: number,
|
||||
}
|
||||
|
||||
function themeToCssVariables(theme:any) {
|
||||
|
@ -8,6 +8,8 @@ const md5 = require('md5');
|
||||
interface RendererRule {
|
||||
install(context:any, ruleOptions:any):any,
|
||||
assets?(theme:any):any,
|
||||
rule?: any, // TODO: remove
|
||||
plugin?: any,
|
||||
}
|
||||
|
||||
interface RendererRules {
|
||||
@ -113,6 +115,15 @@ interface RenderResult {
|
||||
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 {
|
||||
|
||||
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) {
|
||||
const assets:any = {};
|
||||
for (const key in rules) {
|
||||
@ -303,17 +329,18 @@ export default class MdToHtml {
|
||||
const cachedOutput = this.cachedOutputs_[cacheKey];
|
||||
if (cachedOutput) return cachedOutput;
|
||||
|
||||
const context:PluginContext = {
|
||||
css: {},
|
||||
pluginAssets: {},
|
||||
cache: this.contextCache_,
|
||||
};
|
||||
|
||||
const ruleOptions = Object.assign({}, options, {
|
||||
resourceBaseUrl: this.resourceBaseUrl_,
|
||||
ResourceModel: this.ResourceModel_,
|
||||
});
|
||||
|
||||
const context:PluginContext = {
|
||||
css: {},
|
||||
pluginAssets: {},
|
||||
cache: this.contextCache_,
|
||||
// options: ruleOptions,
|
||||
};
|
||||
|
||||
const markdownIt = new MarkdownIt({
|
||||
breaks: !this.pluginEnabled('softbreaks'),
|
||||
typographer: this.pluginEnabled('typographer'),
|
||||
@ -391,8 +418,18 @@ export default class MdToHtml {
|
||||
for (const key in allRules) {
|
||||
if (!this.pluginEnabled(key)) continue;
|
||||
const rule = allRules[key];
|
||||
const ruleInstall:Function = rule.install ? rule.install : (rule as any);
|
||||
markdownIt.use(ruleInstall(context, { ...ruleOptions }));
|
||||
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);
|
||||
markdownIt.use(ruleInstall(context, { ...ruleOptions }));
|
||||
}
|
||||
}
|
||||
|
||||
markdownIt.use(markdownItAnchor, { slugify: slugify });
|
||||
@ -410,11 +447,13 @@ export default class MdToHtml {
|
||||
|
||||
setupLinkify(markdownIt);
|
||||
|
||||
const renderedBody = markdownIt.render(body);
|
||||
const renderedBody = markdownIt.render(body, context);
|
||||
|
||||
const pluginAssets = this.allUnprocessedAssets(theme);
|
||||
|
||||
let cssStrings = noteStyle(options.theme);
|
||||
|
||||
let output = this.processPluginAssets(context.pluginAssets);
|
||||
let output = this.processPluginAssets(pluginAssets); // context.pluginAssets);
|
||||
cssStrings = cssStrings.concat(output.cssStrings);
|
||||
|
||||
if (options.userCss) cssStrings.push(options.userCss);
|
||||
|
@ -1,24 +1,21 @@
|
||||
import { RuleOptions } from '../../MdToHtml';
|
||||
|
||||
let checkboxIndex_ = -1;
|
||||
|
||||
const pluginAssets:Function[] = [];
|
||||
|
||||
pluginAssets[1] = function() {
|
||||
function pluginAssets(theme:any) {
|
||||
return [
|
||||
{
|
||||
inline: true,
|
||||
mime: 'text/css',
|
||||
text: `
|
||||
/*
|
||||
FOR THE MARKDOWN EDITOR
|
||||
*/
|
||||
|
||||
/* 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
|
||||
nested lists. Make sure this value matches the UL margin. */
|
||||
|
||||
/*
|
||||
.md-checkbox .checkbox-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
*/
|
||||
|
||||
li.md-checkbox {
|
||||
list-style-type: none;
|
||||
}
|
||||
@ -26,30 +23,16 @@ pluginAssets[1] = function() {
|
||||
li.md-checkbox input[type=checkbox] {
|
||||
margin-left: -1.71em;
|
||||
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 {
|
||||
list-style:none;
|
||||
}
|
||||
|
||||
/*
|
||||
FOR THE RICH TEXT EDITOR
|
||||
*/
|
||||
|
||||
ul.joplin-checklist li::before {
|
||||
content:"\\f14a";
|
||||
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[] {
|
||||
let token = null;
|
||||
@ -129,9 +112,8 @@ function createSuffixTokens(Token:any):any[] {
|
||||
];
|
||||
}
|
||||
|
||||
// @ts-ignore: Keep the function signature as-is despite unusued arguments
|
||||
function installRule(markdownIt:any, mdOptions:any, ruleOptions:any, context:any) {
|
||||
const pluginOptions = { renderingType: 1, ...ruleOptions.plugins['checkbox'] };
|
||||
function checkboxPlugin(markdownIt:any, options:RuleOptions) {
|
||||
const renderingType = options.checkboxRenderingType || 1;
|
||||
|
||||
markdownIt.core.ruler.push('checkbox', (state:any) => {
|
||||
const tokens = state.tokens;
|
||||
@ -180,14 +162,14 @@ function installRule(markdownIt:any, mdOptions:any, ruleOptions:any, context:any
|
||||
|
||||
const currentList = lists[lists.length - 1];
|
||||
|
||||
if (pluginOptions.renderingType === 1) {
|
||||
if (renderingType === 1) {
|
||||
checkboxIndex_++;
|
||||
const id = `md-checkbox-${checkboxIndex_}`;
|
||||
|
||||
// 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.
|
||||
|
||||
const prefix = createPrefixTokens(Token, id, checked, label, ruleOptions.postMessageSyntax, token);
|
||||
const prefix = createPrefixTokens(Token, id, checked, label, options.postMessageSyntax, token);
|
||||
const suffix = createSuffixTokens(Token);
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
if (!('checkbox' in context.pluginAssets)) {
|
||||
context.pluginAssets['checkbox'] = pluginAssets[pluginOptions.renderingType](ruleOptions.theme);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
install: function(context:any, ruleOptions:any) {
|
||||
return function(md:any, mdOptions:any) {
|
||||
installRule(md, mdOptions, ruleOptions, context);
|
||||
};
|
||||
},
|
||||
assets: pluginAssets[2],
|
||||
plugin: checkboxPlugin,
|
||||
assets: pluginAssets,
|
||||
};
|
||||
|
@ -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) {
|
||||
return this.plugin.registerContentScript(type, id, scriptPath);
|
||||
}
|
||||
|
@ -318,6 +318,25 @@ export type Path = string[];
|
||||
// =================================================================
|
||||
|
||||
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',
|
||||
CodeMirrorPlugin = 'codeMirrorPlugin',
|
||||
}
|
||||
|
@ -8,8 +8,8 @@ import theme_solarizedDark from './themes/solarizedDark';
|
||||
import theme_nord from './themes/nord';
|
||||
import theme_aritimDark from './themes/aritimDark';
|
||||
import theme_oledDark from './themes/oledDark';
|
||||
import Setting from 'lib/models/Setting';
|
||||
|
||||
const Setting = require('lib/models/Setting').default;
|
||||
const Color = require('color');
|
||||
|
||||
const themes:any = {
|
||||
@ -364,13 +364,13 @@ function addExtraStyles(style:any) {
|
||||
|
||||
const themeCache_:any = {};
|
||||
|
||||
function themeStyle(theme:any) {
|
||||
if (!theme) throw new Error('Theme must be specified');
|
||||
function themeStyle(themeId:number) {
|
||||
if (!themeId) throw new Error('Theme must be specified');
|
||||
|
||||
const zoomRatio = 1; // Setting.value('style.zoom') / 100;
|
||||
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];
|
||||
|
||||
// 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
|
||||
// relevant properties
|
||||
output = Object.assign({}, globalStyle, fontSizes, themes[theme]);
|
||||
output = Object.assign({}, globalStyle, fontSizes, themes[themeId]);
|
||||
output = addMissingProperties(output);
|
||||
output = addExtraStyles(output);
|
||||
|
||||
@ -406,7 +406,7 @@ const cachedStyles_:any = {
|
||||
// cacheKey must be a globally unique key, and must change whenever
|
||||
// the dependencies of the style change. If the style depends only
|
||||
// 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;
|
||||
|
||||
// We clear the cache whenever switching themes
|
||||
|
Loading…
Reference in New Issue
Block a user