Merge branch 'lazy-preview-components' into staging

pull/1/head
ansuz 4 years ago
commit f421a7099d

@ -15,6 +15,7 @@ www/common/onlyoffice/v2*
server.js server.js
www/common/old-media-tag.js www/common/old-media-tag.js
www/scratch www/scratch
www/lib
www/common/toolbar.js www/common/toolbar.js
www/common/hyperscript.js www/common/hyperscript.js

@ -49,7 +49,8 @@
"saferphore": "^0.0.1", "saferphore": "^0.0.1",
"jszip": "Stuk/jszip#^3.1.5", "jszip": "Stuk/jszip#^3.1.5",
"requirejs-plugins": "^1.0.3", "requirejs-plugins": "^1.0.3",
"dragula.js": "3.7.2" "dragula.js": "3.7.2",
"MathJax": "3.0.5"
}, },
"resolutions": { "resolutions": {
"bootstrap": "^v4.0.0", "bootstrap": "^v4.0.0",

@ -102,7 +102,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;

@ -297,6 +297,8 @@ define([
} }
}); });
DiffMd.onPluginLoaded(drawPreview);
return { return {
forceDraw: forceDrawPreview, forceDraw: forceDrawPreview,
draw: drawPreview, draw: drawPreview,

@ -19,22 +19,121 @@ define([
var renderer = new Marked.Renderer(); var renderer = new Marked.Renderer();
var restrictedRenderer = new Marked.Renderer(); var restrictedRenderer = new Marked.Renderer();
var Mermaid = { var pluginLoaded = Util.mkEvent();
init: function () {} DiffMd.onPluginLoaded = pluginLoaded.reg;
};
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; }" +
"g.today line { stroke: red; stroke-width: 1; stroke-dasharray: 3; opacity: 0.5; }"; "g.today line { stroke: red; stroke-width: 1; stroke-dasharray: 3; opacity: 0.5; }";
require(['mermaid', 'css!/code/mermaid-new.css'], function (_Mermaid) { var Mermaid = {
Mermaid = _Mermaid; __stubbed: true,
Mermaid.initialize({ init: function () {
gantt: { axisFormat: '%m-%d', }, var args = Util.slice(arguments);
"themeCSS": mermaidThemeCSS, require([
'mermaid',
'css!/code/mermaid-new.css'
], function (_Mermaid) {
console.debug("loaded mermaid");
if (Mermaid.__stubbed) {
Mermaid = _Mermaid;
Mermaid.initialize({
gantt: { axisFormat: '%m-%d', },
"themeCSS": mermaidThemeCSS,
});
}
Mermaid.init.call(args);
pluginLoaded.fire();
});
}
};
var Mathjax = {
__stubbed: true,
tex2svg: function (a, b) {
require([
'/bower_components/MathJax/es5/tex-svg.js',
], function () {
console.debug("Loaded mathjax");
if (Mathjax.__stubbed) {
Mathjax = window.MathJax;
}
Mathjax.tex2svg(a, b);
pluginLoaded.fire();
});
}
};
var drawMarkmap;
var MarkMapTransform;
var Markmap;
var markmapLoaded = false;
var loadMarkmap = function ($el) {
require([
'/lib/markmap/transform.min.js',
'/lib/markmap/view.min.js',
], function (_Transform, _View) {
if (!markmapLoaded) {
console.debug("Loaded markmap");
MarkMapTransform = _Transform;
Markmap = _View;
markmapLoaded = true;
}
drawMarkmap($el);
pluginLoaded.fire();
}); });
}); };
var sfCommon;
var fixMathjaxClickables = function ($svg) {
// find all links in the tree and do the following for each one
var onClick = function (e) {
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;
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
});
};
drawMarkmap = function ($el) {
if (!markmapLoaded) { return void loadMarkmap($el); }
if (!$el) { return console.error("no element provided"); }
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);
};
var highlighter = function () { var highlighter = function () {
return function(code, lang) { return function(code, lang) {
@ -94,7 +193,15 @@ 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 === 'markmap') {
return '<pre class="markmap" data-plugin="markmap">'+Util.fixHTML(code)+'</pre>';
} else if (language === 'mathjax') {
var svg = Mathjax.tex2svg(code, {display: true});
if (!svg) {
return defaultCode.apply(renderer, arguments);
}
return '<pre class="mathjax">'+ svg.innerHTML.replace(/xlink:href/g, "href") +'</pre>';
} else { } else {
return defaultCode.apply(renderer, arguments); return defaultCode.apply(renderer, arguments);
} }
@ -199,7 +306,6 @@ define([
return renderParagraph(p); return renderParagraph(p);
}; };
var MutationObserver = window.MutationObserver;
var forbiddenTags = [ var forbiddenTags = [
'SCRIPT', 'SCRIPT',
'IFRAME', 'IFRAME',
@ -297,6 +403,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) {
@ -313,17 +421,31 @@ 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);
}
}; };
plugins.markmap = {
name: 'markmap',
attr: 'markmap-source',
render: function ($el) {
drawMarkmap($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"); }
@ -343,8 +465,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 = [];
plugins[id].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
@ -352,12 +476,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;
} }
}); });
@ -365,18 +492,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) {
var plugin = plugins[el.getAttribute('data-plugin')];
if (!plugin) { return; }
// retrieve the attached source code which it was drawn // retrieve the attached source code which it was drawn
var src = el.getAttribute('mermaid-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 (mermaid_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);
@ -384,26 +514,30 @@ 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
mermaid_cache[src] = el.childNodes[0]; plugin.cache[src] = el.childNodes[0];
} }
}); });
var oldDom = domFromHTML($content[0].outerHTML); var oldDom = domFromHTML($content[0].outerHTML);
var MutationObserver = window.MutationObserver;
var onPreview = function ($mt) { var onPreview = function ($mt) {
return function () { return function () {
var isSvg = $mt.is('pre.mermaid');
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();
var plugin = plugins[el.getAttribute('data-plugin')];
if (!plugin) { return; }
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('mermaid-source')); $el.text(clone.getAttribute(plugin.attr));
$el.attr('data-processed', ''); $el.attr('data-processed', '');
renderMermaid($el); plugin.render($el);
} }
}); });
} }
@ -415,9 +549,15 @@ 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')];
// 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;
} }
@ -426,16 +566,17 @@ define([
return true; return true;
} }
}); });
// Not found, re-render
if (idx === -1) { if (idx === -1) {
if (isSvg) { if (isSvg && $mt.attr(plugin.attr)) {
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('mermaid-source')); $el.text(clone.getAttribute(plugin.attr));
$el.attr('data-processed', ''); $el.attr('data-processed', '');
renderMermaid($el); plugin.render($el);
} }
}); });
} else { } else {
@ -512,7 +653,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 plugin = plugins[el.getAttribute('data-plugin')];
if (!plugin) { return; }
var $el = $(el); var $el = $(el);
$el.off('contextmenu').on('contextmenu', function (e) { $el.off('contextmenu').on('contextmenu', function (e) {
e.preventDefault(); e.preventDefault();
@ -529,14 +672,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 = mermaid_source[index]; var src = plugin.source[index];
el.setAttribute('mermaid-source', src); el.setAttribute(plugin.attr, src);
var cached = mermaid_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 {
renderMermaid($el); plugin.render($el);
} catch (e) { console.error(e); } } catch (e) { console.error(e); }
return; return;
} }

@ -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