diff --git a/packages/app-desktop/InteropServiceHelper.ts b/packages/app-desktop/InteropServiceHelper.ts index 7fc027f15d..795cd230a7 100644 --- a/packages/app-desktop/InteropServiceHelper.ts +++ b/packages/app-desktop/InteropServiceHelper.ts @@ -166,6 +166,7 @@ export default class InteropServiceHelper { exportOptions.format = module.format; // exportOptions.modulePath = module.path; if (options.plugins) exportOptions.plugins = options.plugins; + exportOptions.customCss = options.customCss; exportOptions.target = module.target; exportOptions.includeConflicts = !!options.includeConflicts; if (options.sourceFolderIds) exportOptions.sourceFolderIds = options.sourceFolderIds; diff --git a/packages/app-desktop/gui/MenuBar.tsx b/packages/app-desktop/gui/MenuBar.tsx index 9c359dc4c9..f7a12d8976 100644 --- a/packages/app-desktop/gui/MenuBar.tsx +++ b/packages/app-desktop/gui/MenuBar.tsx @@ -90,6 +90,7 @@ interface Props { ['spellChecker.enabled']: boolean; ['spellChecker.language']: string; plugins: PluginStates; + customCss: string; } const commandNames: string[] = menuCommandNames(); @@ -313,7 +314,10 @@ function useMenu(props: Props) { await InteropServiceHelper.export( (action: any) => props.dispatch(action), module, - { plugins: props.plugins } + { + plugins: props.plugins, + customCss: props.customCss, + } ); }, }); @@ -860,7 +864,7 @@ function useMenu(props: Props) { clearTimeout(timeoutId); timeoutId = null; }; - }, [props.routeName, props.pluginMenuItems, props.pluginMenus, keymapLastChangeTime, modulesLastChangeTime, props['spellChecker.language'], props['spellChecker.enabled'], props.plugins]); + }, [props.routeName, props.pluginMenuItems, props.pluginMenus, keymapLastChangeTime, modulesLastChangeTime, props['spellChecker.language'], props['spellChecker.enabled'], props.plugins, props.customCss]); useMenuStates(menu, props); @@ -917,6 +921,7 @@ const mapStateToProps = (state: AppState) => { ['spellChecker.language']: state.settings['spellChecker.language'], ['spellChecker.enabled']: state.settings['spellChecker.enabled'], plugins: state.pluginService.plugins, + customCss: state.customCss, }; }; diff --git a/packages/app-desktop/gui/MultiNoteActions.tsx b/packages/app-desktop/gui/MultiNoteActions.tsx index 8ead5e7d53..c6784a84f5 100644 --- a/packages/app-desktop/gui/MultiNoteActions.tsx +++ b/packages/app-desktop/gui/MultiNoteActions.tsx @@ -13,6 +13,7 @@ interface MultiNoteActionsProps { watchedNoteFiles: string[]; plugins: PluginStates; inConflictFolder: boolean; + customCss: string; } function styles_(props: MultiNoteActionsProps) { @@ -53,6 +54,7 @@ export default function MultiNoteActions(props: MultiNoteActionsProps) { watchedNoteFiles: props.watchedNoteFiles, plugins: props.plugins, inConflictFolder: props.inConflictFolder, + customCss: props.customCss, }); const itemComps = []; diff --git a/packages/app-desktop/gui/NoteContentPropertiesDialog.tsx b/packages/app-desktop/gui/NoteContentPropertiesDialog.tsx index 8fcea53a10..c1c52322f2 100644 --- a/packages/app-desktop/gui/NoteContentPropertiesDialog.tsx +++ b/packages/app-desktop/gui/NoteContentPropertiesDialog.tsx @@ -24,7 +24,7 @@ interface KeyToLabelMap { let markupToHtml_: any = null; function markupToHtml() { if (markupToHtml_) return markupToHtml_; - markupToHtml_ = markupLanguageUtils.newMarkupToHtml({}); + markupToHtml_ = markupLanguageUtils.newMarkupToHtml(); return markupToHtml_; } diff --git a/packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx b/packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx index 8d72e31009..fdc7245655 100644 --- a/packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx +++ b/packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx @@ -358,7 +358,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => { element.id = script.id; element.onload = () => { - resolve(); + resolve(null); }; document.getElementsByTagName('head')[0].appendChild(element); diff --git a/packages/app-desktop/gui/NoteEditor/NoteEditor.tsx b/packages/app-desktop/gui/NoteEditor/NoteEditor.tsx index 370943a7c6..c90021a5eb 100644 --- a/packages/app-desktop/gui/NoteEditor/NoteEditor.tsx +++ b/packages/app-desktop/gui/NoteEditor/NoteEditor.tsx @@ -156,10 +156,11 @@ function NoteEditor(props: NoteEditorProps) { const markupToHtml = markupLanguageUtils.newMarkupToHtml({}, { resourceBaseUrl: `file://${Setting.value('resourceDir')}/`, + customCss: props.customCss, }); return markupToHtml.allAssets(markupLanguage, theme); - }, [props.themeId]); + }, [props.themeId, props.customCss]); const handleProvisionalFlag = useCallback(() => { if (props.isProvisional) { @@ -458,6 +459,7 @@ function NoteEditor(props: NoteEditorProps) { watchedNoteFiles={props.watchedNoteFiles} plugins={props.plugins} inConflictFolder={props.selectedFolderId === Folder.conflictFolderId()} + customCss={props.customCss} />; } diff --git a/packages/app-desktop/gui/NoteEditor/utils/types.ts b/packages/app-desktop/gui/NoteEditor/utils/types.ts index b54bbeb5d3..2627b9ee86 100644 --- a/packages/app-desktop/gui/NoteEditor/utils/types.ts +++ b/packages/app-desktop/gui/NoteEditor/utils/types.ts @@ -2,6 +2,8 @@ import AsyncActionQueue from '@joplin/lib/AsyncActionQueue'; import { ToolbarButtonInfo } from '@joplin/lib/services/commands/ToolbarButtonUtils'; import { PluginStates } from '@joplin/lib/services/plugins/reducer'; +import { MarkupLanguage } from '@joplin/renderer'; +import { RenderResult, RenderResultPluginAsset } from '@joplin/renderer/MarkupToHtml'; export interface ToolbarButtonInfos { [key: string]: ToolbarButtonInfo; @@ -49,9 +51,9 @@ export interface NoteBodyEditorProps { onWillChange(event: any): void; onMessage(event: any): void; onScroll(event: any): void; - markupToHtml: Function; + markupToHtml: (markupLanguage: MarkupLanguage, markup: string, options: any)=> Promise; htmlToMarkdown: Function; - allAssets: Function; + allAssets: (markupLanguage: MarkupLanguage)=> Promise; disabled: boolean; dispatch: Function; noteToolbar: any; diff --git a/packages/app-desktop/gui/NoteEditor/utils/useMarkupToHtml.ts b/packages/app-desktop/gui/NoteEditor/utils/useMarkupToHtml.ts index 9b3adb4de2..bd31639f6f 100644 --- a/packages/app-desktop/gui/NoteEditor/utils/useMarkupToHtml.ts +++ b/packages/app-desktop/gui/NoteEditor/utils/useMarkupToHtml.ts @@ -24,6 +24,7 @@ export default function useMarkupToHtml(deps: HookDependencies) { const markupToHtml = useMemo(() => { return markupLanguageUtils.newMarkupToHtml(deps.plugins, { resourceBaseUrl: `file://${Setting.value('resourceDir')}/`, + customCss: customCss || '', }); }, [plugins]); @@ -49,7 +50,6 @@ export default function useMarkupToHtml(deps: HookDependencies) { const result = await markupToHtml.render(markupLanguage, md, theme, Object.assign({}, { codeTheme: theme.codeThemeCss, - userCss: customCss || '', resources: resources, postMessageSyntax: 'ipcProxySendToHost', splitted: true, diff --git a/packages/app-desktop/gui/NoteList/NoteList.tsx b/packages/app-desktop/gui/NoteList/NoteList.tsx index cf7ff2e8cd..58ebedefec 100644 --- a/packages/app-desktop/gui/NoteList/NoteList.tsx +++ b/packages/app-desktop/gui/NoteList/NoteList.tsx @@ -125,6 +125,7 @@ class NoteListComponent extends React.Component { watchedNoteFiles: this.props.watchedNoteFiles, plugins: this.props.plugins, inConflictFolder: this.props.selectedFolderId === Folder.conflictFolderId(), + customCss: this.props.customCss, }); menu.popup(bridge().window()); @@ -513,6 +514,7 @@ const mapStateToProps = (state: AppState) => { noteSortOrder: state.settings['notes.sortOrder.field'], highlightedWords: state.highlightedWords, plugins: state.pluginService.plugins, + customCss: state.customCss, }; }; diff --git a/packages/app-desktop/gui/NoteRevisionViewer.jsx b/packages/app-desktop/gui/NoteRevisionViewer.jsx index a22207c9fd..350faa125e 100644 --- a/packages/app-desktop/gui/NoteRevisionViewer.jsx +++ b/packages/app-desktop/gui/NoteRevisionViewer.jsx @@ -118,11 +118,11 @@ class NoteRevisionViewerComponent extends React.PureComponent { const markupToHtml = markupLanguageUtils.newMarkupToHtml({}, { resourceBaseUrl: `file://${Setting.value('resourceDir')}/`, + customCss: this.props.customCss ? this.props.customCss : '', }); const result = await markupToHtml.render(markupLanguage, noteBody, theme, { codeTheme: theme.codeThemeCss, - userCss: this.props.customCss ? this.props.customCss : '', resources: await shared.attachedResources(noteBody), postMessageSyntax: 'ipcProxySendToHost', }); diff --git a/packages/app-desktop/gui/utils/NoteListUtils.ts b/packages/app-desktop/gui/utils/NoteListUtils.ts index 5ee81ac50c..559aa2572a 100644 --- a/packages/app-desktop/gui/utils/NoteListUtils.ts +++ b/packages/app-desktop/gui/utils/NoteListUtils.ts @@ -22,6 +22,7 @@ interface ContextMenuProps { watchedNoteFiles: string[]; plugins: PluginStates; inConflictFolder: boolean; + customCss: string; } export default class NoteListUtils { @@ -158,6 +159,7 @@ export default class NoteListUtils { sourceNoteIds: noteIds, includeConflicts: props.inConflictFolder, plugins: props.plugins, + customCss: props.customCss, }); }, }) diff --git a/packages/app-desktop/plugins/GotoAnything.tsx b/packages/app-desktop/plugins/GotoAnything.tsx index cd6a6562f8..c5b0cbf367 100644 --- a/packages/app-desktop/plugins/GotoAnything.tsx +++ b/packages/app-desktop/plugins/GotoAnything.tsx @@ -246,7 +246,7 @@ class Dialog extends React.PureComponent { markupToHtml() { if (this.markupToHtml_) return this.markupToHtml_; - this.markupToHtml_ = markupLanguageUtils.newMarkupToHtml({}); + this.markupToHtml_ = markupLanguageUtils.newMarkupToHtml(); return this.markupToHtml_; } diff --git a/packages/app-desktop/utils/markupLanguageUtils.ts b/packages/app-desktop/utils/markupLanguageUtils.ts index ad881a8ded..87250949a0 100644 --- a/packages/app-desktop/utils/markupLanguageUtils.ts +++ b/packages/app-desktop/utils/markupLanguageUtils.ts @@ -1,11 +1,14 @@ import { MarkupLanguageUtils as BaseMarkupLanguageUtils } from '@joplin/lib/markupLanguageUtils'; import { PluginStates } from '@joplin/lib/services/plugins/reducer'; import { contentScriptsToRendererRules } from '@joplin/lib/services/plugins/utils/loadContentScripts'; +import { Options } from '@joplin/renderer/MarkupToHtml'; class MarkupLanguageUtils extends BaseMarkupLanguageUtils { - public newMarkupToHtml(plugins: PluginStates, options: any = null) { - return super.newMarkupToHtml({ + public newMarkupToHtml(plugins: PluginStates = null, options: Options = null) { + plugins = plugins || {}; + + return super.newMarkupToHtml(null, { extraRendererRules: contentScriptsToRendererRules(plugins), ...options, }); diff --git a/packages/lib/markupLanguageUtils.ts b/packages/lib/markupLanguageUtils.ts index aa0426c105..22f56177f1 100644 --- a/packages/lib/markupLanguageUtils.ts +++ b/packages/lib/markupLanguageUtils.ts @@ -1,10 +1,11 @@ import markdownUtils from './markdownUtils'; import Setting from './models/Setting'; import shim from './shim'; -import MarkupToHtml, { MarkupLanguage } from '@joplin/renderer/MarkupToHtml'; +import MarkupToHtml, { MarkupLanguage, Options } from '@joplin/renderer/MarkupToHtml'; import htmlUtils from './htmlUtils'; import Resource from './models/Resource'; +import { PluginStates } from './services/plugins/reducer'; export class MarkupLanguageUtils { @@ -20,7 +21,7 @@ export class MarkupLanguageUtils { // Create a new MarkupToHtml instance while injecting options specific to Joplin // desktop and mobile applications. - public newMarkupToHtml(options: any = null) { + public newMarkupToHtml(_plugins: PluginStates = null, options: Options = null) { const subValues = Setting.subValues('markdown.plugin', Setting.toPlainObject()); const pluginOptions: any = {}; for (const n in subValues) { diff --git a/packages/lib/services/interop/InteropService_Exporter_Html.ts b/packages/lib/services/interop/InteropService_Exporter_Html.ts index 9452d536ad..62a011725a 100644 --- a/packages/lib/services/interop/InteropService_Exporter_Html.ts +++ b/packages/lib/services/interop/InteropService_Exporter_Html.ts @@ -39,8 +39,9 @@ export default class InteropService_Exporter_Html extends InteropService_Exporte this.resourceDir_ = this.destDir_ ? `${this.destDir_}/_resources` : null; await shim.fsDriver().mkdir(this.destDir_); - this.markupToHtml_ = markupLanguageUtils.newMarkupToHtml({ + this.markupToHtml_ = markupLanguageUtils.newMarkupToHtml(null, { extraRendererRules: contentScriptsToRendererRules(options.plugins), + customCss: this.customCss_ || '', }); this.style_ = themeStyle(Setting.THEME_LIGHT); } @@ -105,7 +106,6 @@ export default class InteropService_Exporter_Html extends InteropService_Exporte const result = await this.markupToHtml_.render(item.markup_language, bodyMd, this.style_, { resources: this.resources_, plainResourceRendering: true, - userCss: this.customCss_, }); const noteContent = []; if (item.title) noteContent.push(`
${escapeHtml(item.title)}
`); diff --git a/packages/lib/services/interop/types.ts b/packages/lib/services/interop/types.ts index f2d4546d38..1a9635d2ee 100644 --- a/packages/lib/services/interop/types.ts +++ b/packages/lib/services/interop/types.ts @@ -96,6 +96,7 @@ export interface ExportOptions { target?: FileSystemItem; includeConflicts?: boolean; plugins?: PluginStates; + customCss?: string; } export interface ImportExportResult { diff --git a/packages/renderer/HtmlToHtml.ts b/packages/renderer/HtmlToHtml.ts index b93df48731..3e91c8f2df 100644 --- a/packages/renderer/HtmlToHtml.ts +++ b/packages/renderer/HtmlToHtml.ts @@ -6,6 +6,7 @@ import utils, { ItemIdToUrlHandler } from './utils'; // import Setting from '@joplin/lib/models/Setting'; // const { themeStyle } = require('@joplin/lib/theme'); import InMemoryCache from './InMemoryCache'; +import { RenderResult } from './MarkupToHtml'; const md5 = require('md5'); // Renderered notes can potentially be quite large (for example @@ -35,11 +36,6 @@ interface RenderOptions { itemIdToUrl?: ItemIdToUrlHandler; } -interface RenderResult { - html: string; - pluginAssets: any[]; -} - // https://github.com/es-shims/String.prototype.trimStart/blob/main/implementation.js function trimStart(s: string): string { // eslint-disable-next-line no-control-regex diff --git a/packages/renderer/MarkupToHtml.ts b/packages/renderer/MarkupToHtml.ts index 794956ec82..277d018ea6 100644 --- a/packages/renderer/MarkupToHtml.ts +++ b/packages/renderer/MarkupToHtml.ts @@ -24,7 +24,7 @@ export interface RenderResultPluginAsset { export interface RenderResult { html: string; pluginAssets: RenderResultPluginAsset[]; - cssStrings: string[]; + cssStrings?: string[]; } export interface OptionsResourceModel { @@ -33,7 +33,10 @@ export interface OptionsResourceModel { export interface Options { isSafeMode?: boolean; - ResourceModel: OptionsResourceModel; + ResourceModel?: OptionsResourceModel; + customCss?: string; + extraRendererRules?: any[]; + resourceBaseUrl?: string; } export default class MarkupToHtml { @@ -68,7 +71,7 @@ export default class MarkupToHtml { throw new Error(`Invalid markup language: ${markupLanguage}`); } - this.renderers_[markupLanguage] = new RendererClass(this.options_); + this.renderers_[markupLanguage] = new RendererClass(this.options_ as any); return this.renderers_[markupLanguage]; } diff --git a/packages/renderer/MdToHtml.ts b/packages/renderer/MdToHtml.ts index 44cb740c73..636d06ae75 100644 --- a/packages/renderer/MdToHtml.ts +++ b/packages/renderer/MdToHtml.ts @@ -86,6 +86,7 @@ export interface Options { tempDir?: string; fsDriver?: any; extraRendererRules?: ExtraRendererRule[]; + customCss?: string; } interface PluginAsset { @@ -167,6 +168,7 @@ export default class MdToHtml { private pluginOptions_: any = {}; private extraRendererRules_: RendererRules = {}; private allProcessedAssets_: any = {}; + private customCss_: string = ''; public constructor(options: Options = null) { if (!options) options = {}; @@ -195,6 +197,8 @@ export default class MdToHtml { this.loadExtraRendererRule(rule.id, rule.assetPath, rule.module); } } + + this.customCss_ = options.customCss || ''; } private fsDriver() { @@ -340,13 +344,15 @@ export default class MdToHtml { const processedAssets = this.processPluginAssets(assets); processedAssets.cssStrings.splice(0, 0, noteStyle(theme).join('\n')); + if (this.customCss_) processedAssets.cssStrings.push(this.customCss_); const output = await this.outputAssetsToExternalAssets_(processedAssets); return output.pluginAssets; } private async outputAssetsToExternalAssets_(output: any) { for (const cssString of output.cssStrings) { - output.pluginAssets.push(await this.fsDriver().cacheCssToFile(cssString)); + const filePath = await this.fsDriver().cacheCssToFile(cssString); + output.pluginAssets.push(filePath); } delete output.cssStrings; return output; @@ -524,7 +530,7 @@ export default class MdToHtml { let output = { ...this.allProcessedAssets(allRules, options.theme, options.codeTheme) }; cssStrings = cssStrings.concat(output.cssStrings); - if (options.userCss) cssStrings.push(options.userCss); + if (this.customCss_) cssStrings.push(this.customCss_); if (options.bodyOnly) { // Markdown-it wraps any content in

by default. There's a function to parse without