Deduplicate code and fix preview modal

pull/1/head
yflory 4 years ago
parent 027bc37a25
commit 93ce3f5a83

@ -93,7 +93,13 @@
border: 1px solid #BBB; border: 1px solid #BBB;
} }
pre.mermaid { pre.markmap {
border: 1px solid #ddd;
svg {
height: 400px;
}
}
pre[data-plugin] {
svg { svg {
max-width: 100%; max-width: 100%;
cursor: pointer; cursor: pointer;

@ -150,6 +150,16 @@
overflow: unset; overflow: unset;
margin-bottom: 0; margin-bottom: 0;
} }
pre.markmap {
margin-bottom: 0;
max-height: 100%;
overflow: hidden;
display: flex;
flex-flow: column;
svg {
flex: 1;
}
}
.cp-spinner { .cp-spinner {
border-color: @colortheme_logo-1; border-color: @colortheme_logo-1;
border-top-color: transparent; border-top-color: transparent;

@ -2,7 +2,6 @@ define([
'jquery', 'jquery',
'/api/config', '/api/config',
'/bower_components/marked/marked.min.js', '/bower_components/marked/marked.min.js',
'/bower_components/MathJax/es5/tex-svg.js',
'/common/common-hash.js', '/common/common-hash.js',
'/common/common-util.js', '/common/common-util.js',
'/common/hyperscript.js', '/common/hyperscript.js',
@ -10,14 +9,17 @@ define([
'/common/media-tag.js', '/common/media-tag.js',
'/common/highlight/highlight.pack.js', '/common/highlight/highlight.pack.js',
'/customize/messages.js', '/customize/messages.js',
'/code/markmap/markmap-lib.transform.bundle.js', '/lib/markmap/transform.min.js',
'/code/markmap/markmap-lib.view.bundle.js', '/lib/markmap/view.min.js',
'/bower_components/MathJax/es5/tex-svg.js',
'/bower_components/diff-dom/diffDOM.js', '/bower_components/diff-dom/diffDOM.js',
'/bower_components/tweetnacl/nacl-fast.min.js', '/bower_components/tweetnacl/nacl-fast.min.js',
'css!/common/highlight/styles/github.css' 'css!/common/highlight/styles/github.css'
],function ($, ApiConfig, Marked, MathJax, Hash, Util, h, MT, MediaTag, Highlight, Messages, transform, Markmap) { ],function ($, ApiConfig, Marked, Hash, Util, h, MT, MediaTag, Highlight, Messages,
MarkMapTransform, Markmap) {
var DiffMd = {}; var DiffMd = {};
var MathJax = window.MathJax;
var DiffDOM = window.diffDOM; var DiffDOM = window.diffDOM;
var renderer = new Marked.Renderer(); var renderer = new Marked.Renderer();
var restrictedRenderer = new Marked.Renderer(); var restrictedRenderer = new Marked.Renderer();
@ -26,9 +28,6 @@ define([
init: function () {} init: function () {}
}; };
var MathJax = window.MathJax;
var mermaidThemeCSS = //".node rect { fill: #DDD; stroke: #AAA; } " + var mermaidThemeCSS = //".node rect { fill: #DDD; stroke: #AAA; } " +
"rect.task, rect.task0, rect.task2 { stroke-width: 1 !important; rx: 0 !important; } " + "rect.task, rect.task0, rect.task2 { stroke-width: 1 !important; rx: 0 !important; } " +
"g.grid g.tick line { opacity: 0.25; }" + "g.grid g.tick line { opacity: 0.25; }" +
@ -100,15 +99,10 @@ define([
var defaultCode = renderer.code; var defaultCode = renderer.code;
renderer.code = function (code, language) { renderer.code = function (code, language) {
if (language === 'mermaid' && code.match(/^(graph|pie|gantt|sequenceDiagram|classDiagram|gitGraph)/)) { if (language === 'mermaid' && code.match(/^(graph|pie|gantt|sequenceDiagram|classDiagram|gitGraph)/)) {
return '<pre class="mermaid">'+Util.fixHTML(code)+'</pre>'; return '<pre class="mermaid" data-plugin="mermaid">'+Util.fixHTML(code)+'</pre>';
} else if (language === 'markdown markmap') { } else if (language === 'markmap') {
return '<pre class="markmap">'+Util.fixHTML(code)+'</pre>'; return '<pre class="markmap" data-plugin="markmap">'+Util.fixHTML(code)+'</pre>';
/* } else if (language === 'mathjax') {
var data = transform.transform(code);
var svgEl = document.createElement("svg");
return '<pre class="markmap">'+ svgEl.outerHTML +'</pre>';
*/
} else if (language === 'mathjax') {
var svg = MathJax.tex2svg(code, {display: true}) var svg = MathJax.tex2svg(code, {display: true})
return '<pre class="mathjax">'+ svg.innerHTML.replace(/xlink:href/g, "href") +'</pre>'; return '<pre class="mathjax">'+ svg.innerHTML.replace(/xlink:href/g, "href") +'</pre>';
} else { } else {
@ -313,6 +307,8 @@ define([
return patch; return patch;
}; };
var plugins = {};
var removeMermaidClickables = function ($el) { var removeMermaidClickables = function ($el) {
// find all links in the tree and do the following for each one // find all links in the tree and do the following for each one
$el.find('a').each(function (index, a) { $el.find('a').each(function (index, a) {
@ -329,24 +325,74 @@ define([
// finally, find all 'clickable' items and remove the class // finally, find all 'clickable' items and remove the class
$el.find('.clickable').removeClass('clickable'); $el.find('.clickable').removeClass('clickable');
}; };
var renderMermaid = function ($el) {
Mermaid.init(undefined, $el); plugins.mermaid = {
// clickable elements in mermaid don't work well with our sandboxing setup name: 'mermaid',
// the function below strips clickable elements but still leaves behind some artifacts attr: 'mermaid-source',
// tippy tooltips might still be useful, so they're not removed. It would be render: function ($el) {
// preferable to just support links, but this covers up a rough edge in the meantime Mermaid.init(undefined, $el);
removeMermaidClickables($el); // clickable elements in mermaid don't work well with our sandboxing setup
// the function below strips clickable elements but still leaves behind some artifacts
// tippy tooltips might still be useful, so they're not removed. It would be
// preferable to just support links, but this covers up a rough edge in the meantime
removeMermaidClickables($el);
}
}; };
var renderMarkmap = function ($el) { var sfCommon;
var data = transform.transform($el[0].getAttribute("markmap-source")); var fixMathjaxClickables = function ($svg) {
$el[0].innerHTML = "<svg width='100%' height='600px' />" // find all links in the tree and do the following for each one
Markmap.markmap($el[0].firstChild, data) var onClick = function (e) {
removeMermaidClickables($el); e.preventDefault();
} e.stopImmediatePropagation();
var $el = $(e.target);
// Open links only from the preview modal
if (!sfCommon) { return void console.error('No sfCommon'); }
var href = $el.attr('href');
if (!href || !/^(https?:\/\/|\/)/.test(href)) { return; }
if (/^http/.test(href)) {
sfCommon.openUnsafeURL(href);
return;
}
sfCommon.openURL(href);
};
$svg.find('a').click(onClick);
// make sure the links added later by collapsing/expading the map are also safe
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.type === 'childList') {
var n, $a;
for (var i = 0; i < mutation.addedNodes.length; i++) {
n = mutation.addedNodes[i];
if (n.nodeName === "A") { return void n.addEventListener('click', onClick); }
$(n).find('a').click(onClick);
}
}
});
});
observer.observe($svg[0], {
childList: true,
subtree: true
});
};
plugins.markmap = {
name: 'markmap',
attr: 'markmap-source',
render: function ($el) {
var data = MarkMapTransform.transform($el[0].getAttribute("markmap-source"));
$el[0].innerHTML = "<svg width='100%' height='600'/>"
Markmap.markmap($el[0].firstChild, data)
fixMathjaxClickables($el);
}
};
DiffMd.apply = function (newHtml, $content, common) { DiffMd.apply = function (newHtml, $content, common) {
if (!sfCommon) { sfCommon = common; }
var contextMenu = common.importMediaTagMenu(); var contextMenu = common.importMediaTagMenu();
var id = $content.attr('id'); var id = $content.attr('id');
if (!id) { throw new Error("The element must have a valid id"); } if (!id) { throw new Error("The element must have a valid id"); }
@ -366,10 +412,10 @@ define([
var Dom = domFromHTML($('<div>').append($div).html()); var Dom = domFromHTML($('<div>').append($div).html());
$content[0].normalize(); $content[0].normalize();
var mermaid_source = []; Object.keys(plugins).forEach(function (id) {
var mermaid_cache = {}; plugins[id].source = [];
var markmap_source = []; plugins[id].cache = {};
var markmap_cache = {}; });
var canonicalizeMermaidSource = function (src) { var canonicalizeMermaidSource = function (src) {
// ignore changes to empty lines, since that won't affect // ignore changes to empty lines, since that won't affect
@ -377,21 +423,15 @@ define([
return src.replace(/\n[ \t]*\n*[ \t]*\n/g, '\n'); return src.replace(/\n[ \t]*\n*[ \t]*\n/g, '\n');
}; };
// iterate over the unrendered mermaid inputs, caching their source as you go // iterate over the unrendered mermaid and markmap inputs,
$(newDomFixed).find('pre.mermaid').each(function (index, el) { // caching their source as you go
$(newDomFixed).find('pre[data-plugin]').each(function (index, el) {
if (el.childNodes.length === 1 && el.childNodes[0].nodeType === 3) { if (el.childNodes.length === 1 && el.childNodes[0].nodeType === 3) {
var plugin = plugins[el.getAttribute('data-plugin')];
if (!plugin) { return; }
var src = canonicalizeMermaidSource(el.childNodes[0].wholeText); var src = canonicalizeMermaidSource(el.childNodes[0].wholeText);
el.setAttribute('mermaid-source', src); el.setAttribute(plugin.attr, src);
mermaid_source[index] = src; plugin.source[index] = src;
}
});
// iterate over the unrendered mermaid inputs, caching their source as you go
$(newDomFixed).find('pre.markmap').each(function (index, el) {
if (el.childNodes.length === 1 && el.childNodes[0].nodeType === 3) {
var src = canonicalizeMermaidSource(el.childNodes[0].wholeText);
el.setAttribute('markmap-source', src);
markmap_source[index] = src;
} }
}); });
@ -399,43 +439,21 @@ define([
var $parent = $content.parent(); var $parent = $content.parent();
var scrollTop = $parent.scrollTop(); var scrollTop = $parent.scrollTop();
// iterate over rendered mermaid charts // iterate over rendered mermaid charts
$content.find('pre.mermaid:not([processed="true"])').each(function (index, el) { $content.find('pre[data-plugin]:not([processed="true"])').each(function (index, el) {
// retrieve the attached source code which it was drawn var plugin = plugins[el.getAttribute('data-plugin')];
var src = el.getAttribute('mermaid-source'); if (!plugin) { return; }
/* The new source might have syntax errors that will prevent rendering.
It might be preferable to keep the existing state instead of removing it
if you don't have something better to display. Ideally we should display
the cause of the syntax error so that the user knows what to correct. */
//if (!Mermaid.parse(src)) { } // TODO
// check if that source exists in the set of charts which are about to be rendered
if (mermaid_source.indexOf(src) === -1) {
// if it's not, then you can remove it
if (el.parentNode && el.parentNode.children.length) {
el.parentNode.removeChild(el);
}
} else if (el.childNodes.length === 1 && el.childNodes[0].nodeType !== 3) {
// otherwise, confirm that the content of the rendered chart is not a text node
// and keep a copy of it
mermaid_cache[src] = el.childNodes[0];
}
});
// iterate over rendered markmap charts
$content.find('pre.markmap:not([processed="true"])').each(function (index, el) {
// retrieve the attached source code which it was drawn // retrieve the attached source code which it was drawn
var src = el.getAttribute('markmap-source'); var src = el.getAttribute(plugin.attr);
/* The new source might have syntax errors that will prevent rendering. /* The new source might have syntax errors that will prevent rendering.
It might be preferable to keep the existing state instead of removing it It might be preferable to keep the existing state instead of removing it
if you don't have something better to display. Ideally we should display if you don't have something better to display. Ideally we should display
the cause of the syntax error so that the user knows what to correct. */ the cause of the syntax error so that the user knows what to correct. */
//if (!Mermaid.parse(src)) { } // TODO //if (plugin.name === "mermaid" && !Mermaid.parse(src)) { } // TODO
// check if that source exists in the set of charts which are about to be rendered // check if that source exists in the set of charts which are about to be rendered
if (markmap_source.indexOf(src) === -1) { if (plugin.source.indexOf(src) === -1) {
// if it's not, then you can remove it // if it's not, then you can remove it
if (el.parentNode && el.parentNode.children.length) { if (el.parentNode && el.parentNode.children.length) {
el.parentNode.removeChild(el); el.parentNode.removeChild(el);
@ -443,47 +461,29 @@ define([
} else if (el.childNodes.length === 1 && el.childNodes[0].nodeType !== 3) { } else if (el.childNodes.length === 1 && el.childNodes[0].nodeType !== 3) {
// otherwise, confirm that the content of the rendered chart is not a text node // otherwise, confirm that the content of the rendered chart is not a text node
// and keep a copy of it // and keep a copy of it
markmap_cache[src] = el.childNodes[0]; plugin.cache[src] = el.childNodes[0];
} }
}); });
var oldDom = domFromHTML($content[0].outerHTML); var oldDom = domFromHTML($content[0].outerHTML);
var onPreview = function ($mt) { var onPreview = function ($mt) {
return function () { return function () {
var isSvg = $mt.is('pre.mermaid') || $mt.is('pre.markmap');
var mts = []; var mts = [];
$content.find('media-tag, pre.mermaid').each(function (i, el) { // Get all previewable elements from the doc
$content.find('media-tag, pre[data-plugin]').each(function (i, el) {
if (el.nodeName.toLowerCase() === "pre") { if (el.nodeName.toLowerCase() === "pre") {
var clone = el.cloneNode(); var clone = el.cloneNode();
return void mts.push({ var plugin = plugins[el.getAttribute('data-plugin')];
svg: clone, if (!plugin) { return; }
render: function () {
var $el = $(clone);
$el.text(clone.getAttribute('mermaid-source'));
$el.attr('data-processed', '');
renderMermaid($el);
}
});
}
var $el = $(el);
mts.push({
src: $el.attr('src'),
key: $el.attr('data-crypto-key')
});
});
$content.find('media-tag, pre.markmap').each(function (i, el) {
if (el.nodeName.toLowerCase() === "pre") {
var clone = el.cloneNode();
return void mts.push({ return void mts.push({
svg: clone, svg: clone,
render: function () { render: function () {
var $el = $(clone); var $el = $(clone);
$el.text(clone.getAttribute('markmap-source')); $el.text(clone.getAttribute(plugin.attr));
$el.attr('data-processed', ''); $el.attr('data-processed', '');
renderMermaid($el); plugin.render($el);
} }
}); });
} }
@ -494,44 +494,36 @@ define([
}); });
}); });
// Find initial position // Find initial position
// If the element is a mermaid or markmap svg, get the corresponding attribute
var isSvg = $mt.is('pre[data-plugin]');
var plugin = isSvg && plugins[$mt.attr('data-plugin')];
console.log(plugin);
// Get initial idx
var idx = -1; var idx = -1;
mts.some(function (obj, i) { mts.some(function (obj, i) {
if (isSvg && $mt.attr('mermaid-source') === $(obj.svg).attr('mermaid-source')) { if (isSvg && $mt.attr(plugin.attr) === $(obj.svg).attr(plugin.attr)) {
idx = i; idx = i;
return true; return true;
} }
if (isSvg && $mt.attr('markmap-source') === $(obj.svg).attr('markmap-source')) {
idx = i;
return true;
}
if (!isSvg && obj.src === $mt.attr('src')) { if (!isSvg && obj.src === $mt.attr('src')) {
idx = i; idx = i;
return true; return true;
} }
}); });
// Not found, re-render
if (idx === -1) { if (idx === -1) {
if (isSvg && $mt.attr('mermaid-source')) { if (isSvg && $mt.attr(plugin.attr)) {
var clone = $mt[0].cloneNode();
mts.unshift({
svg: clone,
render: function () {
var $el = $(clone);
$el.text(clone.getAttribute('mermaid-source'));
$el.attr('data-processed', '');
renderMermaid($el);
}
});
} else if (isSvg && $mt.attr('markmap-source')) {
var clone = $mt[0].cloneNode(); var clone = $mt[0].cloneNode();
mts.unshift({ mts.unshift({
svg: clone, svg: clone,
render: function () { render: function () {
var $el = $(clone); var $el = $(clone);
$el.text(clone.getAttribute('markmap-source')); $el.text(clone.getAttribute(plugin.attr));
$el.attr('data-processed', ''); $el.attr('data-processed', '');
renderMarkmap($el); plugin.render($el);
} }
}); });
} else { } else {
@ -608,47 +600,9 @@ define([
}); });
// loop over mermaid elements in the rendered content // loop over mermaid elements in the rendered content
$content.find('pre.mermaid').each(function (index, el) { $content.find('pre[data-plugin]').each(function (index, el) {
var $el = $(el); var plugin = plugins[el.getAttribute('data-plugin')];
$el.off('contextmenu').on('contextmenu', function (e) { if (!plugin) { return; }
e.preventDefault();
$(contextMenu.menu).data('mediatag', $el);
$(contextMenu.menu).find('li:not(.cp-svg)').hide();
contextMenu.show(e);
});
$el.off('dblclick click preview');
$el.on('preview', onPreview($el));
$el.on('dblclick click', function () {
$el.trigger('preview');
});
// since you've simply drawn the content that was supplied via markdown
// you can assume that the index of your rendered charts matches that
// of those in the markdown source.
var src = mermaid_source[index];
el.setAttribute('mermaid-source', src);
var cached = mermaid_cache[src];
// check if you had cached a pre-rendered instance of the supplied source
if (typeof(cached) !== 'object') {
try {
renderMermaid($el);
} catch (e) { console.error(e); }
return;
}
// if there's a cached rendering, empty out the contained source code
// which would otherwise be drawn again.
// apparently this is the fastest way to empty out an element
while (el.firstChild) { el.removeChild(el.firstChild); } //el.innerHTML = '';
// insert the cached graph
el.appendChild(cached);
// and set a flag indicating that this graph need not be reprocessed
el.setAttribute('data-processed', true);
});
// loop over markmap elements in the rendered content
$content.find('pre.markmap').each(function (index, el) {
var $el = $(el); var $el = $(el);
$el.off('contextmenu').on('contextmenu', function (e) { $el.off('contextmenu').on('contextmenu', function (e) {
e.preventDefault(); e.preventDefault();
@ -665,14 +619,14 @@ define([
// since you've simply drawn the content that was supplied via markdown // since you've simply drawn the content that was supplied via markdown
// you can assume that the index of your rendered charts matches that // you can assume that the index of your rendered charts matches that
// of those in the markdown source. // of those in the markdown source.
var src = markmap_source[index]; var src = plugin.source[index];
el.setAttribute('markmap-source', src); el.setAttribute(plugin.attr, src);
var cached = markmap_cache[src]; var cached = plugin.cache[src];
// check if you had cached a pre-rendered instance of the supplied source // check if you had cached a pre-rendered instance of the supplied source
if (typeof(cached) !== 'object') { if (typeof(cached) !== 'object') {
try { try {
renderMarkmap($el); plugin.render($el);
} catch (e) { console.error(e); } } catch (e) { console.error(e); }
return; return;
} }
@ -686,10 +640,6 @@ define([
// and set a flag indicating that this graph need not be reprocessed // and set a flag indicating that this graph need not be reprocessed
el.setAttribute('data-processed', true); el.setAttribute('data-processed', true);
}); });
} }
// recover the previous scroll position to avoid jank // recover the previous scroll position to avoid jank
$parent.scrollTop(scrollTop); $parent.scrollTop(scrollTop);

@ -256,7 +256,7 @@ define([
} }
// Reset modal // Reset modal
$inner.find('media-tag, pre.mermaid').detach(); $inner.find('media-tag, pre[data-plugin]').detach();
$spinner.show(); $spinner.show();
// Check src and cryptkey // Check src and cryptkey

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save