You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	Desktop: Pass custom CSS property to all export handlers and renderers
This commit is contained in:
		| @@ -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; | ||||
|   | ||||
| @@ -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, | ||||
| 	}; | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -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 = []; | ||||
|   | ||||
| @@ -24,7 +24,7 @@ interface KeyToLabelMap { | ||||
| let markupToHtml_: any = null; | ||||
| function markupToHtml() { | ||||
| 	if (markupToHtml_) return markupToHtml_; | ||||
| 	markupToHtml_ = markupLanguageUtils.newMarkupToHtml({}); | ||||
| 	markupToHtml_ = markupLanguageUtils.newMarkupToHtml(); | ||||
| 	return markupToHtml_; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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} | ||||
| 		/>; | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -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<RenderResult>; | ||||
| 	htmlToMarkdown: Function; | ||||
| 	allAssets: Function; | ||||
| 	allAssets: (markupLanguage: MarkupLanguage)=> Promise<RenderResultPluginAsset[]>; | ||||
| 	disabled: boolean; | ||||
| 	dispatch: Function; | ||||
| 	noteToolbar: any; | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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, | ||||
| 	}; | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -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', | ||||
| 		}); | ||||
|   | ||||
| @@ -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, | ||||
| 							}); | ||||
| 						}, | ||||
| 					}) | ||||
|   | ||||
| @@ -246,7 +246,7 @@ class Dialog extends React.PureComponent<Props, State> { | ||||
|  | ||||
| 	markupToHtml() { | ||||
| 		if (this.markupToHtml_) return this.markupToHtml_; | ||||
| 		this.markupToHtml_ = markupLanguageUtils.newMarkupToHtml({}); | ||||
| 		this.markupToHtml_ = markupLanguageUtils.newMarkupToHtml(); | ||||
| 		return this.markupToHtml_; | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -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, | ||||
| 		}); | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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(`<div class="exported-note-title">${escapeHtml(item.title)}</div>`); | ||||
|   | ||||
| @@ -96,6 +96,7 @@ export interface ExportOptions { | ||||
| 	target?: FileSystemItem; | ||||
| 	includeConflicts?: boolean; | ||||
| 	plugins?: PluginStates; | ||||
| 	customCss?: string; | ||||
| } | ||||
|  | ||||
| export interface ImportExportResult { | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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]; | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -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 <p></p> by default. There's a function to parse without | ||||
|   | ||||
		Reference in New Issue
	
	Block a user