From 6c9e8317dd86e92b847d5ec4b13626e47796e4dc Mon Sep 17 00:00:00 2001 From: benweet Date: Fri, 26 Jul 2013 01:44:12 +0100 Subject: [PATCH] New partialRendering extension --- js/core.js | 19 +++ js/extensionMgr.js | 3 +- js/extensions/mathJax.js | 4 +- js/extensions/partialRendering.js | 231 ++++++++++++++++-------------- js/extensions/scrollLink.js | 32 ++--- js/libs/Markdown.Converter.js | 6 +- 6 files changed, 164 insertions(+), 131 deletions(-) diff --git a/js/core.js b/js/core.js index 3080e63c..843d94cd 100644 --- a/js/core.js +++ b/js/core.js @@ -252,6 +252,25 @@ define([ // Create the converter and the editor 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); // Custom insert link dialog editor.hooks.set("insertLinkDialog", function(callback) { diff --git a/js/extensionMgr.js b/js/extensionMgr.js index 2c8f52e5..27006b5c 100644 --- a/js/extensionMgr.js +++ b/js/extensionMgr.js @@ -147,6 +147,7 @@ define([ // Operations on PageDown addHook("onEditorConfigure"); + addHook("onSectionsCreated"); var onPreviewFinished = createHook("onPreviewFinished"); var onAsyncPreviewCallbackList = getExtensionCallbackList("onAsyncPreview"); @@ -162,7 +163,7 @@ define([ $("#preview-contents > .preview-content").each(function() { html += $(this).html(); }); - onPreviewFinished(html); + onPreviewFinished(utils.trim(html)); console.log("Preview time: " + (new Date().getTime() - window.previewStartTime)); } } diff --git a/js/extensions/mathJax.js b/js/extensions/mathJax.js index 7d9d4675..2229322c 100644 --- a/js/extensions/mathJax.js +++ b/js/extensions/mathJax.js @@ -210,7 +210,7 @@ define([ function RestartMJ() { pending = false; 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); } @@ -235,7 +235,7 @@ define([ // Create a preview refresh hook to handle starting MathJax. // mathJax.onEditorConfigure = function(editorObject) { - preview = document.getElementById("wmd-preview"); + preview = document.getElementById("preview-contents"); converter = editorObject.getConverter(); converter.hooks.chain("preConversion", removeMath); diff --git a/js/extensions/partialRendering.js b/js/extensions/partialRendering.js index c9809d92..c14fd257 100644 --- a/js/extensions/partialRendering.js +++ b/js/extensions/partialRendering.js @@ -9,14 +9,27 @@ define([ partialRendering.settingsBlock = partialRenderingSettingsBlockHTML; var converter = undefined; - var sectionIdGenerator = 0; - var sectionList = [ - { - text: "" + var sectionCounter = 0; + var sectionList = []; + var linkDefinition = ""; + 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 var leftIndex = sectionList.length; @@ -44,134 +57,140 @@ define([ // Create an array composed of left unmodified, modified, right // unmodified sections 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); + insertAfterSection = _.last(leftSections); sectionsToRemove = sectionList.slice(leftIndex, sectionList.length + rightIndex); sectionList = leftSections.concat(modifiedSections).concat(rightSections); } + var doFootnotes = false; var hasFootnotes = false; - function extractSections(text) { + partialRendering.onSectionsCreated = function(sectionListParam) { - text += "\n\n"; - - // Strip link definitions - var linkDefinition = ""; - text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*?(?=\s|$)[ \t]*\n?[ \t]*((\n*)["(](.+?)[")][ \t]*)?(?:\n+)/gm, function(wholeMatch) { - linkDefinition += wholeMatch; - return ""; - }); - - // And eventually footnotes... - hasFootnotes = false; - var doFootnotes = _.some(converter.extraExtensions, function(extension) { - return extension == "footnotes"; - }); - if(doFootnotes) { - text = text.replace(/\n[ ]{0,3}\[\^(.+?)\]\:[ \t]*\n?([\s\S]*?)\n{1,2}((?=\n[ ]{0,3}\S)|$)/g, function(wholeMatch) { - hasFootnotes = true; - linkDefinition += wholeMatch; - return ""; - }); - } - - // Look for titles var newSectionList = []; - var offset = 0; - 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({ - id: ++sectionIdGenerator, - text: text.substring(offset, matchOffset) + "\n" + linkDefinition - }); - offset = matchOffset; + var newLinkDefinition = ""; + hasFootnotes = false; + _.each(sectionListParam, function(text) { + text += "\n\n"; + + // Strip link definitions + text = text.replace(/^```.*\n[\s\S]*?\n```|^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*?(?=\s|$)[ \t]*\n?[ \t]*((\n*)["(](.+?)[")][ \t]*)?(?:\n+)/gm, function(wholeMatch, link) { + if(link) { + newLinkDefinition += wholeMatch; + return ""; } + return wholeMatch; + }); + + // And eventually footnotes... + if(doFootnotes) { + 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; + newLinkDefinition += wholeMatch; + return ""; + } + return wholeMatch; + }); + } + + // Skip space only sections + if(/\S/.test(text)) { + // Add section to the newSectionList + newSectionList.push({ + id: ++sectionCounter, + text: text + }); } - return ""; - }); - // Last section - newSectionList.push({ - id: ++sectionIdGenerator, - text: text.substring(offset, text.length) + linkDefinition }); - updateSectionList(newSectionList); - } + updateSectionList(newSectionList, newLinkDefinition); + }; + + var footnoteContainerElt = $('
'); + var footnoteList = {}; + function refreshSections() { - var isRendering = false; - var footnoteContainer = $('
'); - var footnoteContainerFirstChild = $('
').appendTo(footnoteContainer); - function renderSections() { - converter.eltList = []; // Remove outdated sections _.each(sectionsToRemove, function(section) { $("#wmd-preview-section-" + section.id).remove(); - footnoteContainer.find("#footnotes-section-" + section.id).remove(); }); - var footnoteContainerElt = $("#wmd-preview-section-footnotes"); - footnoteContainerElt.empty(); - - // Renders modified sections - isRendering = true; - var previousSectionElt = $("#wmd-preview"); - var previousFootnoteElt = footnoteContainerFirstChild; - _.each(sectionList, function(section) { - if(section.isConverted === true) { - previousSectionElt = $("#wmd-preview-section-" + section.id); - footnoteContainer.find("#footnotes-section-" + section.id).each(function() { - previousFootnoteElt = $(this); - }); - return; - } - var sectionHtml = converter.makeHtml(section.text); - var sectionElt = $('
').html(sectionHtml); - sectionElt.find("div.footnotes").each(function() { - var footnoteElt = $(this).attr("id", "footnotes-section-" + section.id); - previousFootnoteElt.after(footnoteElt); - previousFootnoteElt = footnoteElt; - }); - previousSectionElt.after(sectionElt); - previousSectionElt = sectionElt; - converter.eltList.push(sectionElt[0]); - section.isConverted = true; - }); - isRendering = false; - // Rewrite footnotes in the footer - if(hasFootnotes === true) { - // Recreate a footnote list - var footnoteElts = $("
    "); - footnoteContainer.find("div.footnotes > ol > li").each(function(index) { - hasFootnotes = true; - var elt = $(this); - footnoteElts.append(elt.clone()); - // Restore footnotes numbers - var refId = "#fnref\\:" + elt.attr("id").substring(3); - $(refId).text(index + 1); + var wmdPreviewElt = $("#wmd-preview"); + var insertAfterSectionElt = insertAfterSection === undefined ? wmdPreviewElt : $("#wmd-preview-section-" + insertAfterSection.id); + _.each(modifiedSections, function(section) { + var sectionElt = $('
    '); + _.some(wmdPreviewElt.children(), function(elt, index) { + elt = $(elt); + if(index !== 0 && elt.is(".wmd-title")) { + return true; + } + else if(elt.is("div.footnotes")) { + elt.find("ol > li").each(function() { + var footnoteElt = $(this).clone(); + var id = footnoteElt.attr("id").substring(3); + footnoteList[id] = footnoteElt; + }); + elt.remove(); + } + else { + sectionElt.append(elt); + } }); - // Append the whole footnotes at the end of the document - footnoteContainerElt.html($('
    ').append("
    ").append(footnoteElts)); + insertAfterSectionElt.after(sectionElt); + insertAfterSectionElt = sectionElt; + }); + + // Rewrite footnotes in the footer and update footnote numbers + footnoteContainerElt.empty(); + var usedFootnoteIds = []; + if(hasFootnotes === true) { + var footnoteElts = $("
      "); + $("#preview-contents a.footnote").each(function(index) { + var id=$(this).text(index + 1).attr("id").substring(6); + usedFootnoteIds.push(id); + footnoteElts.append(footnoteList[id].clone()); + }); + if(usedFootnoteIds.length > 0) { + // Append the whole footnotes at the end of the document + footnoteContainerElt.html($('
      ').append("
      ").append(footnoteElts)); + } + // Keep only used footnotes in our map + footnoteList = _.pick(footnoteList, usedFootnoteIds); } } partialRendering.onEditorConfigure = function(editor) { converter = editor.getConverter(); converter.hooks.chain("preConversion", function(text) { - if(isRendering === true) { - return text; - } - extractSections(text); - return ""; + var result = _.map(modifiedSections, function(section) { + return section.text; + }); + result.push(linkDefinition + "\n\n"); + return result.join(""); }); editor.hooks.chain("onPreviewRefresh", function() { - $("#wmd-preview").html(renderSections()); + refreshSections(); }); }; - - partialRendering.onReady = function() { - $("#preview-contents").append($('
      ')); + partialRendering.onPreviewFinished = function() { + $('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; diff --git a/js/extensions/scrollLink.js b/js/extensions/scrollLink.js index fde37257..c28d584d 100644 --- a/js/extensions/scrollLink.js +++ b/js/extensions/scrollLink.js @@ -9,6 +9,11 @@ define([ var scrollLink = new Extension("scrollLink", "Scroll Link", true); scrollLink.settingsBlock = scrollLinkSettingsBlockHTML; + + var sectionList = undefined; + scrollLink.onSectionsCreated = function(sectionListParam) { + sectionList = sectionListParam; + }; var mdSectionList = []; var htmlSectionList = []; @@ -28,7 +33,7 @@ define([ textareaElt.width(editorElt.width()); // Consider wmd-input top padding (will be used for 1st and last section) var padding = pxToFloat(editorElt.css('padding-top')); - var offset = 0, mdSectionOffset = 0; + var mdSectionOffset = 0; function addMdSection(sectionText) { var sectionHeight = padding; if(sectionText !== undefined) { @@ -44,25 +49,14 @@ define([ mdSectionOffset = newSectionOffset; padding = 0; } - // Create MD sections by finding title patterns (excluding gfm blocs) - var text = editorElt.val() + "\n\n"; - 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; + _.each(sectionList, function(sectionText, index) { + if(index === sectionList.length - 1) { + // Last section + // Consider wmd-input bottom padding and exclude \n\n previously added + padding += pxToFloat(editorElt.css('padding-bottom')); } - return ""; + addMdSection(sectionText); }); - // Last section - // Consider wmd-input bottom padding and exclude \n\n previously added - padding += pxToFloat(editorElt.css('padding-bottom')); - addMdSection(text.substring(offset, text.length - 2)); // Try to find corresponding sections in the preview var previewElt = $(".preview-container"); @@ -70,7 +64,7 @@ define([ var htmlSectionOffset = 0; var previewScrollTop = previewElt.scrollTop(); // 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 var newSectionOffset = $(this).position().top + previewScrollTop + pxToFloat($(this).css('margin-top')); htmlSectionList.push({ diff --git a/js/libs/Markdown.Converter.js b/js/libs/Markdown.Converter.js index f6bb6c89..527bb51c 100644 --- a/js/libs/Markdown.Converter.js +++ b/js/libs/Markdown.Converter.js @@ -754,11 +754,11 @@ else // -------- // text = text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm, - function (wholeMatch, m1) { return "

      " + _RunSpanGamut(m1) + "

      \n\n"; } + function (wholeMatch, m1) { return "

      " + _RunSpanGamut(m1) + "

      \n\n"; } ); text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm, - function (matchFound, m1) { return "

      " + _RunSpanGamut(m1) + "

      \n\n"; } + function (matchFound, m1) { return "

      " + _RunSpanGamut(m1) + "

      \n\n"; } ); // atx-style headers: @@ -783,7 +783,7 @@ else text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm, function (wholeMatch, m1, m2) { var h_level = m1.length; - return "" + _RunSpanGamut(m2) + "\n\n"; + return "" + _RunSpanGamut(m2) + "\n\n"; } );