diff --git a/.eslintignore b/.eslintignore index d43364d04..8cad0864f 100644 --- a/.eslintignore +++ b/.eslintignore @@ -139,6 +139,9 @@ packages/app-cli/tests/services_CommandService.js.map packages/app-cli/tests/services_InteropService.d.ts packages/app-cli/tests/services_InteropService.js packages/app-cli/tests/services_InteropService.js.map +packages/app-cli/tests/services_InteropService_Exporter_Html.d.ts +packages/app-cli/tests/services_InteropService_Exporter_Html.js +packages/app-cli/tests/services_InteropService_Exporter_Html.js.map packages/app-cli/tests/services_PluginService.d.ts packages/app-cli/tests/services_PluginService.js packages/app-cli/tests/services_PluginService.js.map diff --git a/.gitignore b/.gitignore index 7ab25e472..600d98eb3 100644 --- a/.gitignore +++ b/.gitignore @@ -126,6 +126,9 @@ packages/app-cli/tests/services_CommandService.js.map packages/app-cli/tests/services_InteropService.d.ts packages/app-cli/tests/services_InteropService.js packages/app-cli/tests/services_InteropService.js.map +packages/app-cli/tests/services_InteropService_Exporter_Html.d.ts +packages/app-cli/tests/services_InteropService_Exporter_Html.js +packages/app-cli/tests/services_InteropService_Exporter_Html.js.map packages/app-cli/tests/services_PluginService.d.ts packages/app-cli/tests/services_PluginService.js packages/app-cli/tests/services_PluginService.js.map diff --git a/packages/app-cli/jest.config.js b/packages/app-cli/jest.config.js index dfe652749..1b26ec411 100644 --- a/packages/app-cli/jest.config.js +++ b/packages/app-cli/jest.config.js @@ -37,6 +37,7 @@ module.exports = { '/tests/test-utils-synchronizer.js', '/tests/file_api_driver.js', '/tests/tmp/', + '/tests/test data/', ], // To avoid this warning: diff --git a/packages/app-cli/tests/services_InteropService_Exporter_Html.ts b/packages/app-cli/tests/services_InteropService_Exporter_Html.ts new file mode 100644 index 000000000..7f48a2b08 --- /dev/null +++ b/packages/app-cli/tests/services_InteropService_Exporter_Html.ts @@ -0,0 +1,98 @@ +import InteropService from '@joplin/lib/services/interop/InteropService'; +import { PluginStates } from '@joplin/lib/services/plugins/reducer'; +import { setupDatabaseAndSynchronizer, switchClient, exportDir } from './test-utils'; +import Folder from '@joplin/lib/models/Folder'; +import Note from '@joplin/lib/models/Note'; +import * as fs from 'fs-extra'; +import { tempFilePath } from './test-utils'; +import { ContentScriptType } from '@joplin/lib/services/plugins/api/types'; + +async function recreateExportDir() { + const dir = exportDir(); + await fs.remove(dir); + await fs.mkdirp(dir); +} + +describe('services_InteropService_Exporter_Html', function() { + + beforeEach(async (done) => { + await setupDatabaseAndSynchronizer(1); + await switchClient(1); + await recreateExportDir(); + done(); + }); + + test('should export HTML file', (async () => { + const service = InteropService.instance(); + const folder1 = await Folder.save({ title: 'folder1' }); + await Note.save({ body: '**ma note**', parent_id: folder1.id }); + const filePath = `${exportDir()}/test.html`; + + await service.export({ + path: filePath, + format: 'html', + }); + + const content = await fs.readFile(filePath, 'utf8'); + expect(content).toContain('ma note'); + })); + + test('should export plugin assets', (async () => { + const service = InteropService.instance(); + const folder1 = await Folder.save({ title: 'folder1' }); + await Note.save({ body: '**ma note**', parent_id: folder1.id }); + const filePath = `${exportDir()}/test.html`; + + const contentScriptPath = tempFilePath('js'); + + await fs.writeFile(contentScriptPath, `module.exports = { + default: function(_context) { + return { + plugin: function (markdownIt, _options) { + + }, + assets: function() { + return [ + { name: 'fence.css' } + ]; + }, + } + }, + }`); + + const assetPath = `${require('path').dirname(contentScriptPath)}/fence.css`; + const fenceContent = 'strong { color: red; }'; + await fs.writeFile(assetPath, fenceContent); + + const plugins: PluginStates = { + 'test': { + id: 'test', + contentScripts: { + [ContentScriptType.MarkdownItPlugin]: [ + { + id: 'mdContentScript', + path: contentScriptPath, + }, + ], + }, + views: {}, + }, + }; + + await service.export({ + path: filePath, + format: 'html', + plugins, + }); + + + const fenceRelativePath = 'pluginAssets/mdContentScript/fence.css'; + + const content = await fs.readFile(filePath, 'utf8'); + expect(content).toContain(fenceRelativePath); + + const readFenceContent = await fs.readFile(`${exportDir()}/${fenceRelativePath}`, 'utf8'); + expect(readFenceContent).toBe(fenceContent); + })); + +}); diff --git a/packages/lib/services/interop/InteropService_Exporter_Html.ts b/packages/lib/services/interop/InteropService_Exporter_Html.ts index 6d4bf3613..9452d536a 100644 --- a/packages/lib/services/interop/InteropService_Exporter_Html.ts +++ b/packages/lib/services/interop/InteropService_Exporter_Html.ts @@ -117,7 +117,7 @@ export default class InteropService_Exporter_Html extends InteropService_Exporte // The source path is a bit hard-coded but shouldn't change. for (let i = 0; i < result.pluginAssets.length; i++) { const asset = result.pluginAssets[i]; - const filePath = `${libRootPath}/node_modules/@joplin/renderer/assets/${asset.name}`; + const filePath = asset.pathIsAbsolute ? asset.path : `${libRootPath}/node_modules/@joplin/renderer/assets/${asset.name}`; const destPath = `${dirname(noteFilePath)}/pluginAssets/${asset.name}`; await shim.fsDriver().mkdir(dirname(destPath)); await shim.fsDriver().copy(filePath, destPath); diff --git a/packages/renderer/MarkupToHtml.ts b/packages/renderer/MarkupToHtml.ts index 6d183b5d4..b2b294af4 100644 --- a/packages/renderer/MarkupToHtml.ts +++ b/packages/renderer/MarkupToHtml.ts @@ -10,8 +10,15 @@ export enum MarkupLanguage { export interface RenderResultPluginAsset { name: string; - path: string; mime: string; + path: string; + + // For built-in Mardown-it plugins, the asset path is relative (and can be + // found inside the @joplin/renderer package), while for external plugins + // (content scripts), the path is absolute. We use this property to tell if + // it's relative or absolute, as that will inform how it's loaded in various + // places. + pathIsAbsolute: boolean; } export interface RenderResult { diff --git a/packages/renderer/MdToHtml.ts b/packages/renderer/MdToHtml.ts index ba28c3baf..44cb740c7 100644 --- a/packages/renderer/MdToHtml.ts +++ b/packages/renderer/MdToHtml.ts @@ -14,6 +14,7 @@ interface RendererRule { assets?(theme: any): any; plugin?: any; assetPath?: string; + assetPathIsAbsolute?: boolean; } interface RendererRules { @@ -231,6 +232,7 @@ export default class MdToHtml { this.extraRendererRules_[id] = { ...module, assetPath, + assetPathIsAbsolute: true, }; } @@ -282,6 +284,7 @@ export default class MdToHtml { files.push(Object.assign({}, asset, { name: name, path: assetPath, + pathIsAbsolute: !!rule && !!rule.assetPathIsAbsolute, mime: mime, })); }