New partialRendering extension

This commit is contained in:
benweet 2013-07-26 01:44:12 +01:00
parent ab06427a38
commit 6c9e8317dd
6 changed files with 164 additions and 131 deletions

View File

@ -252,6 +252,25 @@ define([
// Create the converter and the editor // Create the converter and the editor
var converter = new Markdown.Converter(); var converter = new Markdown.Converter();
// Create MD sections for extensions
converter.hooks.chain("preConversion", function(text) {
var tmpText = text + "\n\n";
var sectionList = [], offset = 0;
// Look for titles (exclude gfm blocs)
tmpText.replace(/^```.*\n[\s\S]*?\n```|(^.+[ \t]*\n=+[ \t]*\n+|^.+[ \t]*\n-+[ \t]*\n+|^\#{1,6}[ \t]*.+?[ \t]*\#*\n+)/gm, function(match, title, matchOffset) {
if(title && matchOffset > offset) {
// We just found a title which means end of the previous section
// Exclude last \n of the section
sectionList.push(tmpText.substring(offset, matchOffset - 1));
offset = matchOffset;
}
return "";
});
// Last section
sectionList.push(tmpText.substring(offset, text.length));
extensionMgr.onSectionsCreated(sectionList);
return text;
});
editor = new Markdown.Editor(converter); editor = new Markdown.Editor(converter);
// Custom insert link dialog // Custom insert link dialog
editor.hooks.set("insertLinkDialog", function(callback) { editor.hooks.set("insertLinkDialog", function(callback) {

View File

@ -147,6 +147,7 @@ define([
// Operations on PageDown // Operations on PageDown
addHook("onEditorConfigure"); addHook("onEditorConfigure");
addHook("onSectionsCreated");
var onPreviewFinished = createHook("onPreviewFinished"); var onPreviewFinished = createHook("onPreviewFinished");
var onAsyncPreviewCallbackList = getExtensionCallbackList("onAsyncPreview"); var onAsyncPreviewCallbackList = getExtensionCallbackList("onAsyncPreview");
@ -162,7 +163,7 @@ define([
$("#preview-contents > .preview-content").each(function() { $("#preview-contents > .preview-content").each(function() {
html += $(this).html(); html += $(this).html();
}); });
onPreviewFinished(html); onPreviewFinished(utils.trim(html));
console.log("Preview time: " + (new Date().getTime() - window.previewStartTime)); console.log("Preview time: " + (new Date().getTime() - window.previewStartTime));
} }
} }

View File

@ -210,7 +210,7 @@ define([
function RestartMJ() { function RestartMJ() {
pending = false; pending = false;
HUB.cancelTypeset = false; // won't need to do this in the future HUB.cancelTypeset = false; // won't need to do this in the future
HUB.Queue([ "Typeset", HUB, converter.eltList || preview ]); HUB.Queue([ "Typeset", HUB, preview ]);
HUB.Queue(afterRefreshCallback); HUB.Queue(afterRefreshCallback);
} }
@ -235,7 +235,7 @@ define([
// Create a preview refresh hook to handle starting MathJax. // Create a preview refresh hook to handle starting MathJax.
// //
mathJax.onEditorConfigure = function(editorObject) { mathJax.onEditorConfigure = function(editorObject) {
preview = document.getElementById("wmd-preview"); preview = document.getElementById("preview-contents");
converter = editorObject.getConverter(); converter = editorObject.getConverter();
converter.hooks.chain("preConversion", removeMath); converter.hooks.chain("preConversion", removeMath);

View File

@ -9,14 +9,27 @@ define([
partialRendering.settingsBlock = partialRenderingSettingsBlockHTML; partialRendering.settingsBlock = partialRenderingSettingsBlockHTML;
var converter = undefined; var converter = undefined;
var sectionIdGenerator = 0; var sectionCounter = 0;
var sectionList = [ var sectionList = [];
{ var linkDefinition = "";
text: "" var sectionsToRemove = [];
var modifiedSections = [];
var insertAfterSection = undefined;
var fileChanged = true;
function updateSectionList(newSectionList, newLinkDefinition) {
modifiedSections = [];
sectionsToRemove = [];
insertAfterSection = undefined;
// Render everything if file changed or linkDefinition changed
if(fileChanged === true || linkDefinition != newLinkDefinition) {
fileChanged = false;
linkDefinition = newLinkDefinition;
sectionsToRemove = sectionList;
sectionList = newSectionList;
modifiedSections = newSectionList;
return;
} }
];
var sectionsToRemove = undefined;
function updateSectionList(newSectionList) {
// Find modified sections starting from left // Find modified sections starting from left
var leftIndex = sectionList.length; var leftIndex = sectionList.length;
@ -44,134 +57,140 @@ define([
// Create an array composed of left unmodified, modified, right // Create an array composed of left unmodified, modified, right
// unmodified sections // unmodified sections
var leftSections = sectionList.slice(0, leftIndex); var leftSections = sectionList.slice(0, leftIndex);
var modifiedSections = newSectionList.slice(leftIndex, newSectionList.length + rightIndex); modifiedSections = newSectionList.slice(leftIndex, newSectionList.length + rightIndex);
var rightSections = sectionList.slice(sectionList.length + rightIndex, sectionList.length); var rightSections = sectionList.slice(sectionList.length + rightIndex, sectionList.length);
insertAfterSection = _.last(leftSections);
sectionsToRemove = sectionList.slice(leftIndex, sectionList.length + rightIndex); sectionsToRemove = sectionList.slice(leftIndex, sectionList.length + rightIndex);
sectionList = leftSections.concat(modifiedSections).concat(rightSections); sectionList = leftSections.concat(modifiedSections).concat(rightSections);
} }
var doFootnotes = false;
var hasFootnotes = false; var hasFootnotes = false;
function extractSections(text) { partialRendering.onSectionsCreated = function(sectionListParam) {
var newSectionList = [];
var newLinkDefinition = "";
hasFootnotes = false;
_.each(sectionListParam, function(text) {
text += "\n\n"; text += "\n\n";
// Strip link definitions // Strip link definitions
var linkDefinition = ""; text = text.replace(/^```.*\n[\s\S]*?\n```|^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?(?=\s|$)[ \t]*\n?[ \t]*((\n*)["(](.+?)[")][ \t]*)?(?:\n+)/gm, function(wholeMatch, link) {
text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?(?=\s|$)[ \t]*\n?[ \t]*((\n*)["(](.+?)[")][ \t]*)?(?:\n+)/gm, function(wholeMatch) { if(link) {
linkDefinition += wholeMatch; newLinkDefinition += wholeMatch;
return ""; return "";
}
return wholeMatch;
}); });
// And eventually footnotes... // And eventually footnotes...
hasFootnotes = false;
var doFootnotes = _.some(converter.extraExtensions, function(extension) {
return extension == "footnotes";
});
if(doFootnotes) { if(doFootnotes) {
text = text.replace(/\n[ ]{0,3}\[\^(.+?)\]\:[ \t]*\n?([\s\S]*?)\n{1,2}((?=\n[ ]{0,3}\S)|$)/g, function(wholeMatch) { text = text.replace(/^```.*\n[\s\S]*?\n```|\n[ ]{0,3}\[\^(.+?)\]\:[ \t]*\n?([\s\S]*?)\n{1,2}((?=\n[ ]{0,3}\S)|$)/g, function(wholeMatch, footnote) {
if(footnote) {
hasFootnotes = true; hasFootnotes = true;
linkDefinition += wholeMatch; newLinkDefinition += wholeMatch;
return ""; return "";
}
return wholeMatch;
}); });
} }
// Look for titles // Skip space only sections
var newSectionList = []; if(/\S/.test(text)) {
var offset = 0; // Add section to the newSectionList
text.replace(/^```.*\n[\s\S]*?\n```|(^.+[ \t]*\n=+[ \t]*\n+|^.+[ \t]*\n-+[ \t]*\n+|^\#{1,6}[ \t]*.+?[ \t]*\#*\n+)/gm, function(match, title, matchOffset) {
if(title) {
// We just found a title which means end of the previous section
if(matchOffset > offset) {
newSectionList.push({ newSectionList.push({
id: ++sectionIdGenerator, id: ++sectionCounter,
text: text.substring(offset, matchOffset) + "\n" + linkDefinition text: text
}); });
offset = matchOffset;
} }
}
return "";
});
// Last section
newSectionList.push({
id: ++sectionIdGenerator,
text: text.substring(offset, text.length) + linkDefinition
}); });
updateSectionList(newSectionList); updateSectionList(newSectionList, newLinkDefinition);
} };
var footnoteContainerElt = $('<div id="wmd-preview-section-footnotes" class="preview-content">');
var footnoteList = {};
function refreshSections() {
var isRendering = false;
var footnoteContainer = $('<div>');
var footnoteContainerFirstChild = $('<div>').appendTo(footnoteContainer);
function renderSections() {
converter.eltList = [];
// Remove outdated sections // Remove outdated sections
_.each(sectionsToRemove, function(section) { _.each(sectionsToRemove, function(section) {
$("#wmd-preview-section-" + section.id).remove(); $("#wmd-preview-section-" + section.id).remove();
footnoteContainer.find("#footnotes-section-" + section.id).remove();
}); });
var footnoteContainerElt = $("#wmd-preview-section-footnotes");
footnoteContainerElt.empty();
// Renders modified sections var wmdPreviewElt = $("#wmd-preview");
isRendering = true; var insertAfterSectionElt = insertAfterSection === undefined ? wmdPreviewElt : $("#wmd-preview-section-" + insertAfterSection.id);
var previousSectionElt = $("#wmd-preview"); _.each(modifiedSections, function(section) {
var previousFootnoteElt = footnoteContainerFirstChild; var sectionElt = $('<div id="wmd-preview-section-' + section.id + '" class="wmd-preview-section preview-content">');
_.each(sectionList, function(section) { _.some(wmdPreviewElt.children(), function(elt, index) {
if(section.isConverted === true) { elt = $(elt);
previousSectionElt = $("#wmd-preview-section-" + section.id); if(index !== 0 && elt.is(".wmd-title")) {
footnoteContainer.find("#footnotes-section-" + section.id).each(function() { return true;
previousFootnoteElt = $(this);
});
return;
} }
var sectionHtml = converter.makeHtml(section.text); else if(elt.is("div.footnotes")) {
var sectionElt = $('<div id="wmd-preview-section-' + section.id + '" class="preview-content">').html(sectionHtml); elt.find("ol > li").each(function() {
sectionElt.find("div.footnotes").each(function() { var footnoteElt = $(this).clone();
var footnoteElt = $(this).attr("id", "footnotes-section-" + section.id); var id = footnoteElt.attr("id").substring(3);
previousFootnoteElt.after(footnoteElt); footnoteList[id] = footnoteElt;
previousFootnoteElt = footnoteElt;
}); });
previousSectionElt.after(sectionElt); elt.remove();
previousSectionElt = sectionElt; }
converter.eltList.push(sectionElt[0]); else {
section.isConverted = true; sectionElt.append(elt);
}
});
insertAfterSectionElt.after(sectionElt);
insertAfterSectionElt = sectionElt;
}); });
isRendering = false;
// Rewrite footnotes in the footer // Rewrite footnotes in the footer and update footnote numbers
footnoteContainerElt.empty();
var usedFootnoteIds = [];
if(hasFootnotes === true) { if(hasFootnotes === true) {
// Recreate a footnote list
var footnoteElts = $("<ol>"); var footnoteElts = $("<ol>");
footnoteContainer.find("div.footnotes > ol > li").each(function(index) { $("#preview-contents a.footnote").each(function(index) {
hasFootnotes = true; var id=$(this).text(index + 1).attr("id").substring(6);
var elt = $(this); usedFootnoteIds.push(id);
footnoteElts.append(elt.clone()); footnoteElts.append(footnoteList[id].clone());
// Restore footnotes numbers
var refId = "#fnref\\:" + elt.attr("id").substring(3);
$(refId).text(index + 1);
}); });
if(usedFootnoteIds.length > 0) {
// Append the whole footnotes at the end of the document // Append the whole footnotes at the end of the document
footnoteContainerElt.html($('<div class="footnotes">').append("<hr>").append(footnoteElts)); footnoteContainerElt.html($('<div class="footnotes">').append("<hr>").append(footnoteElts));
} }
// Keep only used footnotes in our map
footnoteList = _.pick(footnoteList, usedFootnoteIds);
}
} }
partialRendering.onEditorConfigure = function(editor) { partialRendering.onEditorConfigure = function(editor) {
converter = editor.getConverter(); converter = editor.getConverter();
converter.hooks.chain("preConversion", function(text) { converter.hooks.chain("preConversion", function(text) {
if(isRendering === true) { var result = _.map(modifiedSections, function(section) {
return text; return section.text;
} });
extractSections(text); result.push(linkDefinition + "\n\n");
return ""; return result.join("");
}); });
editor.hooks.chain("onPreviewRefresh", function() { editor.hooks.chain("onPreviewRefresh", function() {
$("#wmd-preview").html(renderSections()); refreshSections();
}); });
}; };
partialRendering.onReady = function() { partialRendering.onPreviewFinished = function() {
$("#preview-contents").append($('<div id="wmd-preview-section-footnotes" class="preview-content">')); $('script[type="math/tex; mode=display"]').remove();
};
partialRendering.onReady = function() {
$("#preview-contents").append(footnoteContainerElt);
$("#wmd-preview").hide();
};
partialRendering.onFileClose = function() {
fileChanged = true;
};
partialRendering.onFileOpen = function() {
if(converter.extraExtensions) {
doFootnotes = _.some(converter.extraExtensions, function(extension) {
return extension == "footnotes";
});
}
}; };
return partialRendering; return partialRendering;

View File

@ -10,6 +10,11 @@ define([
var scrollLink = new Extension("scrollLink", "Scroll Link", true); var scrollLink = new Extension("scrollLink", "Scroll Link", true);
scrollLink.settingsBlock = scrollLinkSettingsBlockHTML; scrollLink.settingsBlock = scrollLinkSettingsBlockHTML;
var sectionList = undefined;
scrollLink.onSectionsCreated = function(sectionListParam) {
sectionList = sectionListParam;
};
var mdSectionList = []; var mdSectionList = [];
var htmlSectionList = []; var htmlSectionList = [];
function pxToFloat(px) { function pxToFloat(px) {
@ -28,7 +33,7 @@ define([
textareaElt.width(editorElt.width()); textareaElt.width(editorElt.width());
// Consider wmd-input top padding (will be used for 1st and last section) // Consider wmd-input top padding (will be used for 1st and last section)
var padding = pxToFloat(editorElt.css('padding-top')); var padding = pxToFloat(editorElt.css('padding-top'));
var offset = 0, mdSectionOffset = 0; var mdSectionOffset = 0;
function addMdSection(sectionText) { function addMdSection(sectionText) {
var sectionHeight = padding; var sectionHeight = padding;
if(sectionText !== undefined) { if(sectionText !== undefined) {
@ -44,25 +49,14 @@ define([
mdSectionOffset = newSectionOffset; mdSectionOffset = newSectionOffset;
padding = 0; padding = 0;
} }
// Create MD sections by finding title patterns (excluding gfm blocs) _.each(sectionList, function(sectionText, index) {
var text = editorElt.val() + "\n\n"; if(index === sectionList.length - 1) {
text.replace(/^```.*\n[\s\S]*?\n```|(^.+[ \t]*\n=+[ \t]*\n+|^.+[ \t]*\n-+[ \t]*\n+|^\#{1,6}[ \t]*.+?[ \t]*\#*\n+)/gm, function(match, title, matchOffset) {
if(title) {
// We just found a title which means end of the previous section
// Exclude last \n of the section
var sectionText = undefined;
if(matchOffset > offset) {
sectionText = text.substring(offset, matchOffset - 1);
}
addMdSection(sectionText);
offset = matchOffset;
}
return "";
});
// Last section // Last section
// Consider wmd-input bottom padding and exclude \n\n previously added // Consider wmd-input bottom padding and exclude \n\n previously added
padding += pxToFloat(editorElt.css('padding-bottom')); padding += pxToFloat(editorElt.css('padding-bottom'));
addMdSection(text.substring(offset, text.length - 2)); }
addMdSection(sectionText);
});
// Try to find corresponding sections in the preview // Try to find corresponding sections in the preview
var previewElt = $(".preview-container"); var previewElt = $(".preview-container");
@ -70,7 +64,7 @@ define([
var htmlSectionOffset = 0; var htmlSectionOffset = 0;
var previewScrollTop = previewElt.scrollTop(); var previewScrollTop = previewElt.scrollTop();
// Each title element is a section separator // Each title element is a section separator
$("#preview-contents > .preview-content").children("h1,h2,h3,h4,h5,h6").each(function() { $("#preview-contents > .preview-content").children(".wmd-title").each(function() {
// Consider div scroll position and header element top margin // Consider div scroll position and header element top margin
var newSectionOffset = $(this).position().top + previewScrollTop + pxToFloat($(this).css('margin-top')); var newSectionOffset = $(this).position().top + previewScrollTop + pxToFloat($(this).css('margin-top'));
htmlSectionList.push({ htmlSectionList.push({

View File

@ -754,11 +754,11 @@ else
// -------- // --------
// //
text = text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm, text = text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm,
function (wholeMatch, m1) { return "<h1>" + _RunSpanGamut(m1) + "</h1>\n\n"; } function (wholeMatch, m1) { return "<h1 class=\"wmd-title\">" + _RunSpanGamut(m1) + "</h1>\n\n"; }
); );
text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm, text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,
function (matchFound, m1) { return "<h2>" + _RunSpanGamut(m1) + "</h2>\n\n"; } function (matchFound, m1) { return "<h2 class=\"wmd-title\">" + _RunSpanGamut(m1) + "</h2>\n\n"; }
); );
// atx-style headers: // atx-style headers:
@ -783,7 +783,7 @@ else
text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm, text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,
function (wholeMatch, m1, m2) { function (wholeMatch, m1, m2) {
var h_level = m1.length; var h_level = m1.length;
return "<h" + h_level + ">" + _RunSpanGamut(m2) + "</h" + h_level + ">\n\n"; return "<h" + h_level + " class=\"wmd-title\">" + _RunSpanGamut(m2) + "</h" + h_level + ">\n\n";
} }
); );