import { RenderResultPluginAsset } from '@joplin/renderer/types'; type PluginAssetRecord = { element: HTMLElement; }; const pluginAssetsAdded_: Record = {}; // Note that this function keeps track of what's been added so as not to // add the same CSS files multiple times. // // Shared with app-desktop/gui-note-viewer. // // TODO: If possible, refactor such that this function is not duplicated. const addPluginAssets = (assets: RenderResultPluginAsset[]) => { if (!assets) return; const pluginAssetsContainer = document.getElementById('joplin-container-pluginAssetsContainer'); const processedAssetIds = []; for (let i = 0; i < assets.length; i++) { const asset = assets[i]; // # and ? can be used in valid paths and shouldn't be treated as the start of a query or fragment const encodedPath = asset.path .replace(/#/g, '%23') .replace(/\?/g, '%3F'); const assetId = asset.name ? asset.name : encodedPath; processedAssetIds.push(assetId); if (pluginAssetsAdded_[assetId]) continue; let element = null; if (asset.mime === 'application/javascript') { element = document.createElement('script'); element.src = encodedPath; pluginAssetsContainer.appendChild(element); } else if (asset.mime === 'text/css') { element = document.createElement('link'); element.rel = 'stylesheet'; element.href = encodedPath; pluginAssetsContainer.appendChild(element); } pluginAssetsAdded_[assetId] = { element, }; } // Once we have added the relevant assets, we also remove those that // are no longer needed. It's necessary in particular for the CSS // generated by noteStyle - if we don't remove it, we might end up // with two or more stylesheet and that will create conflicts. // // It was happening for example when automatically switching from // light to dark theme, and then back to light theme - in that case // the viewer would remain dark because it would use the dark // stylesheet that would still be in the DOM. for (const [assetId, asset] of Object.entries(pluginAssetsAdded_)) { if (!processedAssetIds.includes(assetId)) { try { asset.element.remove(); } catch (error) { // We don't throw an exception but we log it since // it shouldn't happen console.warn('Tried to remove an asset but got an error', error); } pluginAssetsAdded_[assetId] = null; } } }; export default addPluginAssets;