diff --git a/funnel/assets/js/utils/initembed.js b/funnel/assets/js/utils/initembed.js index f65712f25..83b9ed4c5 100644 --- a/funnel/assets/js/utils/initembed.js +++ b/funnel/assets/js/utils/initembed.js @@ -4,10 +4,10 @@ import MarkmapEmbed from './markmap'; import MermaidEmbed from './mermaid'; import PrismEmbed from './prism'; -export default function initEmbed(markdownElem = '') { +export default function initEmbed(parentContainer = '') { addVegaSupport(); - if (markdownElem) TypeformEmbed.init(markdownElem); - MarkmapEmbed.init(); - MermaidEmbed.init(); - PrismEmbed.init(markdownElem); + if (parentContainer) TypeformEmbed.init(parentContainer); + MarkmapEmbed.init(parentContainer); + MermaidEmbed.init(parentContainer); + PrismEmbed.init(parentContainer); } diff --git a/funnel/assets/js/utils/markmap.js b/funnel/assets/js/utils/markmap.js index addc79c10..8b21e81e0 100644 --- a/funnel/assets/js/utils/markmap.js +++ b/funnel/assets/js/utils/markmap.js @@ -1,12 +1,21 @@ const MarkmapEmbed = { addMarkmap() { - $('.md-embed-markmap').each(function embedMarkmap() { - $(this).find('.embed-content').addClass('markmap'); - }); - window.markmap.autoLoader.renderAll(); - $('.md-embed-markmap').each(function embedMarkmap() { - $(this).addClass('activated'); - }); + const self = this; + self.container + .find('.md-embed-markmap:not(.activating):not(.activated)') + .each(function embedMarkmap() { + $(this).addClass('activating'); + const current = $(this).find('.embed-container'); + current + .addClass('markmap') + .append( + `` + ); + window.markmap.autoLoader.renderAllUnder(this); + $(this).addClass('activated').removeClass('activating'); + }); }, resizeTimer: null, resizeMarkmapContainers() { @@ -65,9 +74,12 @@ const MarkmapEmbed = { self.addMarkmap(); } }, - init(containerDiv) { - this.containerDiv = containerDiv; - if ($('.md-embed-markmap:not(.activated)').length > 0) { + init(container) { + this.container = $(container || 'body'); + if ( + this.container.find('.md-embed-markmap:not(.activated):not(.activated)').length > + 0 + ) { this.loadMarkmap(); } }, diff --git a/funnel/assets/js/utils/mermaid.js b/funnel/assets/js/utils/mermaid.js index 65db6473a..a269333f4 100644 --- a/funnel/assets/js/utils/mermaid.js +++ b/funnel/assets/js/utils/mermaid.js @@ -1,14 +1,18 @@ const MermaidEmbed = { addMermaid() { - const instances = $('.md-embed-mermaid:not(.activating):not(.activated)'); + const self = this; let idCount = $('.md-embed-mermaid.activating, .md-embed-mermaid.activated').length; const idMarker = 'mermaid_elem_'; + const instances = self.container.find( + '.md-embed-mermaid:not(.activating):not(.activated)' + ); instances.each(function embedMarkmap() { const root = $(this); root.addClass('activating'); - const elem = root.find('.embed-content'); - const definition = elem.text(); - let elemId = elem.attr('id'); + const contentElem = root.find('.embed-content'); + const containerElem = root.find('.embed-container'); + const definition = contentElem.text(); + let elemId = containerElem.attr('id'); if (!elemId) { elemId = `${idMarker}${idCount}`; do { @@ -16,9 +20,8 @@ const MermaidEmbed = { } while ($(`#${idMarker}${idCount}`).length > 0); } window.mermaid.render(elemId, definition, (svg) => { - elem.html(svg); - root.addClass('activated'); - root.removeClass('activating'); + containerElem.html(svg); + root.addClass('activated').removeClass('activating'); }); }); }, @@ -37,8 +40,12 @@ const MermaidEmbed = { self.addMermaid(); } }, - init() { - if ($('.md-embed-mermaid:not(.activated)').length > 0) { + init(container) { + this.container = $(container || 'body'); + if ( + this.container.find('.md-embed-mermaid:not(.activating):not(.activated)').length > + 0 + ) { this.loadMermaid(); } }, diff --git a/funnel/assets/js/utils/vegaembed.js b/funnel/assets/js/utils/vegaembed.js index 7daa75b40..eeb982bbe 100644 --- a/funnel/assets/js/utils/vegaembed.js +++ b/funnel/assets/js/utils/vegaembed.js @@ -1,28 +1,35 @@ /* global vegaEmbed */ -function addVegaChart() { - $('.md-embed-vega-lite:not(.activated)').each(async function embedVegaChart() { - const root = $(this); - const embedded = await vegaEmbed( - this, - JSON.parse($(this).find('.embed-content').text()), - { - renderer: 'svg', - actions: { - source: false, - editor: false, - compiled: false, - }, - } - ); - embedded.view.runAfter(() => { - root.addClass('activated'); +function addVegaChart(parentElement) { + parentElement + .find('.md-embed-vega-lite:not(.activating):not(.activated)') + .each(async function embedVegaChart() { + const root = $(this); + root.addClass('activating'); + const embedded = await vegaEmbed( + root.find('.embed-container')[0], + JSON.parse(root.find('.embed-content').text()), + { + renderer: 'svg', + actions: { + source: false, + editor: false, + compiled: false, + }, + } + ); + embedded.view.runAfter(() => { + root.addClass('activated').removeClass('activating'); + }); }); - }); } -function addVegaSupport() { - if ($('.md-embed-vega-lite:not(.activated)').length > 0) { +function addVegaSupport(container) { + const parentElement = $(container || 'body'); + if ( + parentElement.find('.md-embed-vega-lite:not(.activating):not(.activated)').length > + 0 + ) { const vegaliteCDN = [ 'https://cdn.jsdelivr.net/npm/vega@5', 'https://cdn.jsdelivr.net/npm/vega-lite@5', @@ -42,13 +49,13 @@ function addVegaSupport() { } // Once all vega js is loaded, initialize vega visualization on all pre tags with class 'md-embed-vega-lite' if (vegaliteUrl === vegaliteCDN.length) { - addVegaChart(); + addVegaChart(parentElement); } }); }; loadVegaScript(); } else { - addVegaChart(); + addVegaChart(parentElement); } } } diff --git a/funnel/assets/sass/_layout.scss b/funnel/assets/sass/_layout.scss index 967ce99dd..9dca0cca3 100644 --- a/funnel/assets/sass/_layout.scss +++ b/funnel/assets/sass/_layout.scss @@ -861,24 +861,45 @@ } .md-embed { - visibility: hidden; border: 1px solid black; background: linear-gradient(45deg, #bbb, #eee); - canvas, - svg { - width: 100% !important; - max-width: 100% !important; - background-color: transparent !important; - } .embed-content { - white-space: pre; + display: none; + } + .embed-loading { + padding: 5px 2.5%; + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + align-content: center; + } + .embed-container { + display: none; + canvas, + svg { + width: 100% !important; + max-width: 100% !important; + background-color: transparent !important; + } + } + &.activating .embed-container { + visibility: hidden; + display: block; } &.activated { - visibility: visible; + .embed-loading { + display: none; + } + .embed-container { + visibility: visible; + display: block; + } } &.md-embed-markmap { margin-bottom: 0px; - .embed-content { + .embed-container { width: 100%; aspect-ratio: 4 / 3; svg { @@ -886,7 +907,7 @@ } } } - &.md-embed-vega-lite.vega-embed { + &.md-embed-vega-lite .vega-embed { padding: 2.5%; display: block !important; svg.marks { diff --git a/funnel/utils/markdown/mdit_plugins/embeds.py b/funnel/utils/markdown/mdit_plugins/embeds.py index 70f867c3d..0fb4c61cb 100644 --- a/funnel/utils/markdown/mdit_plugins/embeds.py +++ b/funnel/utils/markdown/mdit_plugins/embeds.py @@ -12,6 +12,12 @@ from markdown_it.common.utils import charCodeAt from markdown_it.rules_block import StateBlock +LOADING_TEXT = { + 'markmap': 'mindmap', + 'mermaid': 'visualization', + 'vega-lite': 'visualization', +} + def embeds_plugin( md: MarkdownIt, @@ -25,13 +31,13 @@ def validate(params: str, *args): def render(self, tokens, idx, _options, env): token = tokens[idx] content = md.utils.escapeHtml(token.content) - + loading = LOADING_TEXT.get(name, '') return ( - '
' + f'
' + + '
' + + f'Loading {loading}…
' + content - + '
\n' + + '
\n' ) min_markers = 3