import InteropService_Exporter_Base from './InteropService_Exporter_Base'; import BaseModel from '../../BaseModel'; import shim from '../../shim'; import markupLanguageUtils from '../../markupLanguageUtils'; import Folder from '../../models/Folder'; import Note from '../../models/Note'; import Setting from '../../models/Setting'; import { MarkupToHtml } from '@joplin/renderer'; import { NoteEntity, ResourceEntity } from '../database/types'; import { contentScriptsToRendererRules } from '../plugins/utils/loadContentScripts'; import { basename, friendlySafeFilename, rtrimSlashes, dirname } from '../../path-utils'; import htmlpack from '@joplin/htmlpack'; const { themeStyle } = require('../../theme'); const { escapeHtml } = require('../../string-utils.js'); const { assetsToHeaders } = require('@joplin/renderer'); export default class InteropService_Exporter_Html extends InteropService_Exporter_Base { private customCss_: string; private destDir_: string; private filePath_: string; private createdDirs_: string[] = []; private resourceDir_: string; private markupToHtml_: MarkupToHtml; private resources_: ResourceEntity[] = []; private style_: any; private packIntoSingleFile_ = false; public async init(path: string, options: any = {}) { this.customCss_ = options.customCss ? options.customCss : ''; if (this.metadata().target === 'file') { this.destDir_ = dirname(path); this.filePath_ = path; this.packIntoSingleFile_ = 'packIntoSingleFile' in options ? options.packIntoSingleFile : true; } else { this.destDir_ = path; this.filePath_ = null; } this.resourceDir_ = this.destDir_ ? `${this.destDir_}/_resources` : null; await shim.fsDriver().mkdir(this.destDir_); this.markupToHtml_ = markupLanguageUtils.newMarkupToHtml(null, { extraRendererRules: contentScriptsToRendererRules(options.plugins), customCss: this.customCss_ || '', }); this.style_ = themeStyle(Setting.THEME_LIGHT); } private async makeDirPath_(item: NoteEntity, pathPart: string = null) { let output = ''; while (true) { if (item.type_ === BaseModel.TYPE_FOLDER) { if (pathPart) { output = `${pathPart}/${output}`; } else { output = `${friendlySafeFilename(item.title)}/${output}`; output = await shim.fsDriver().findUniqueFilename(output); } } if (!item.parent_id) return output; item = await Folder.load(item.parent_id); } } private async processNoteResources_(item: NoteEntity) { const target = this.metadata().target; const linkedResourceIds = await Note.linkedResourceIds(item.body); const relativePath = target === 'directory' ? rtrimSlashes(await this.makeDirPath_(item, '..')) : ''; const resourcePaths = this.context() && this.context().resourcePaths ? this.context().resourcePaths : {}; let newBody = item.body; for (let i = 0; i < linkedResourceIds.length; i++) { const id = linkedResourceIds[i]; // Skip the resources which haven't been downloaded yet if (!resourcePaths[id]) { continue; } const resourceContent = `${relativePath ? `${relativePath}/` : ''}_resources/${basename(resourcePaths[id])}`; newBody = newBody.replace(new RegExp(`:/${id}`, 'g'), resourceContent); } return newBody; } public async processItem(_itemType: number, item: any) { if ([BaseModel.TYPE_NOTE, BaseModel.TYPE_FOLDER].indexOf(item.type_) < 0) return; let dirPath = ''; if (!this.filePath_) { dirPath = `${this.destDir_}/${await this.makeDirPath_(item)}`; if (this.createdDirs_.indexOf(dirPath) < 0) { await shim.fsDriver().mkdir(dirPath); this.createdDirs_.push(dirPath); } } if (item.type_ === BaseModel.TYPE_NOTE) { let noteFilePath = ''; if (this.filePath_) { noteFilePath = this.filePath_; } else { noteFilePath = `${dirPath}/${friendlySafeFilename(item.title)}.html`; noteFilePath = await shim.fsDriver().findUniqueFilename(noteFilePath); } const bodyMd = await this.processNoteResources_(item); const result = await this.markupToHtml_.render(item.markup_language, bodyMd, this.style_, { resources: this.resources_, plainResourceRendering: true, }); const noteContent = []; if (item.title) noteContent.push(`