New partialRendering extension

This commit is contained in:
benweet 2013-07-28 18:14:42 +01:00
parent 0d6d329e6a
commit 539f39a2d9
10 changed files with 149 additions and 79 deletions

View File

@ -153,22 +153,26 @@ define([
var onAsyncPreviewCallbackList = getExtensionCallbackList("onAsyncPreview");
// The number of times we expect tryFinished to be called
var nbAsyncPreviewCallback = onAsyncPreviewCallbackList.length + 1;
var previewContentsElt = undefined;
var previewContentsJQElt = undefined;
extensionMgr["onAsyncPreview"] = function() {
logger.log("onAsyncPreview");
// Call onPreviewFinished callbacks when all async preview are finished
var counter = 0;
function tryFinished() {
if(++counter === nbAsyncPreviewCallback) {
var html = "";
$("#preview-contents > .preview-content").each(function() {
html += $(this).html();
_.defer(function() {
var html = "";
_.each(previewContentsElt.children, function(elt) {
html += elt.innerHTML;
});
onPreviewFinished(utils.trim(html));
logger.log("Preview time: " + (new Date() - extensionMgr.previewStartTime));
});
onPreviewFinished(utils.trim(html));
logger.log("Preview time: " + (new Date() - extensionMgr.previewStartTime));
}
}
// We assume images are loading in the preview
$("#preview-contents").waitForImages(tryFinished);
previewContentsJQElt.waitForImages(tryFinished);
_.each(onAsyncPreviewCallbackList, function(asyncPreviewCallback) {
asyncPreviewCallback(tryFinished);
});
@ -184,6 +188,9 @@ define([
}
extensionMgr["onReady"] = function() {
previewContentsElt = document.getElementById('preview-contents');
previewContentsJQElt = $(previewContentsElt);
// Create accordion in settings dialog
_.chain(extensionList).sortBy(function(extension) {
return extension.extensionName.toLowerCase();

View File

@ -30,6 +30,7 @@ define([
selectedFileDesc = fileDesc;
};
var textareaElt = undefined;
buttonHtmlCode.onPreviewFinished = function(html) {
try {
var htmlCode = _.template(buttonHtmlCode.config.template, {
@ -37,7 +38,7 @@ define([
documentMarkdown: selectedFileDesc.content,
documentHTML: html
});
$("#input-html-code").val(htmlCode);
textareaElt.value = htmlCode;
}
catch(e) {
extensionMgr.onError(e);
@ -46,6 +47,7 @@ define([
};
buttonHtmlCode.onReady = function() {
textareaElt = document.getElementById('input-html-code');
$(".action-html-code").click(function() {
_.defer(function() {
$("#input-html-code").each(function() {

View File

@ -43,12 +43,29 @@ define([
buttonStat.onCreatePreviewButton = function() {
return $(_.template(buttonStatHTML, buttonStat.config));
};
var previewContentsElt = undefined;
var value1Elt = undefined;
var value2Elt = undefined;
var value3Elt = undefined;
buttonStat.onReady = function() {
previewContentsElt = document.getElementById('preview-contents');
value1Elt = document.getElementById('span-stat-value1');
value2Elt = document.getElementById('span-stat-value2');
value3Elt = document.getElementById('span-stat-value3');
};
buttonStat.onPreviewFinished = function() {
var text = $("#preview-contents").clone().find("script").remove().end().text();
$("#span-stat-value1").text((text.match(new RegExp(buttonStat.config.value1, "g")) || []).length);
$("#span-stat-value2").text((text.match(new RegExp(buttonStat.config.value2, "g")) || []).length);
$("#span-stat-value3").text((text.match(new RegExp(buttonStat.config.value3, "g")) || []).length);
var previewContentsEltClone = previewContentsElt.cloneNode(true);
var scriptEltList = previewContentsEltClone.getElementsByTagName('script');
for(var i = scriptEltList.length-1; i >= 0; i--) {
var scriptElt = scriptEltList[i];
scriptElt.parentNode.removeChild(scriptElt);
}
var text = previewContentsEltClone.textContent;
value1Elt.textContent = (text.match(new RegExp(buttonStat.config.value1, "g")) || []).length;
value2Elt.textContent = (text.match(new RegExp(buttonStat.config.value2, "g")) || []).length;
value3Elt.textContent = (text.match(new RegExp(buttonStat.config.value3, "g")) || []).length;
};
return buttonStat;

View File

@ -10,12 +10,14 @@ define([
var libraries = {
"Bootstrap": "http://twitter.github.io/bootstrap/",
"crel": "https://github.com/KoryNunn/crel",
"CSS Browser Selector": "https://github.com/rafaelp/css_browser_selector/",
"Dropbox-js": "https://github.com/dropbox/dropbox-js",
"FileSaver.js": "https://github.com/eligrey/FileSaver.js/",
"Gatekeeper": "https://github.com/prose/gatekeeper",
"Github.js": "https://github.com/michael/github",
"Glyphicons": "http://glyphicons.com/",
"Highlight.js": "http://softwaremaniacs.org/soft/highlight/en/",
"jGrowl": "https://github.com/stanlemon/jGrowl/",
"jQuery": "http://jquery.com/",
"jQuery Mouse Wheel Plugin": "https://github.com/brandonaaron/jquery-mousewheel",
@ -34,6 +36,7 @@ define([
var projects = {
"StackEdit Download Proxy": "https://github.com/benweet/stackedit-download-proxy",
"StackEdit Picasa Proxy": "https://github.com/benweet/stackedit-picasa-proxy",
"StackEdit SSH Proxy": "https://github.com/benweet/stackedit-ssh-proxy",
"StackEdit Tumblr Proxy": "https://github.com/benweet/stackedit-tumblr-proxy",
"StackEdit WordPress Proxy": "https://github.com/benweet/stackedit-wordpress-proxy",

View File

@ -1,10 +1,11 @@
define([
"jquery",
"underscore",
"utils",
"classes/Extension",
"text!html/markdownExtraSettingsBlock.html",
"libs/Markdown.Extra",
], function($, utils, Extension, markdownExtraSettingsBlockHTML) {
], function($, _, utils, Extension, markdownExtraSettingsBlockHTML) {
var markdownExtra = new Extension("markdownExtra", "Markdown Extra", true);
markdownExtra.settingsBlock = markdownExtraSettingsBlockHTML;
@ -50,9 +51,10 @@ define([
};
if(markdownExtra.config.highlighter == "highlight") {
options.highlighter = "prettify";
var previewContentsElt = document.getElementById('preview-contents');
editor.hooks.chain("onPreviewRefresh", function() {
$('.prettyprint').each(function(i, e) {
hljs.highlightBlock(e);
_.each(previewContentsElt.querySelectorAll('.prettyprint'), function(elt) {
hljs.highlightBlock(elt);
});
});
}
@ -61,7 +63,7 @@ define([
editor.hooks.chain("onPreviewRefresh", prettyPrint);
}
Markdown.Extra.init(converter, options);
// Store extensions list in converter for partialRendering
converter.extraExtensions = markdownExtra.config.extensions;
};

View File

@ -1,9 +1,9 @@
define([
"jquery",
"underscore",
"crel",
"classes/Extension",
"text!html/partialRenderingSettingsBlock.html",
], function($, _, Extension, partialRenderingSettingsBlockHTML) {
], function(_, crel, Extension, partialRenderingSettingsBlockHTML) {
var partialRendering = new Extension("partialRendering", "Partial Rendering", true);
partialRendering.settingsBlock = partialRenderingSettingsBlockHTML;
@ -14,12 +14,12 @@ define([
var linkDefinition = undefined;
var sectionsToRemove = [];
var modifiedSections = [];
var insertAfterSection = undefined;
var insertBeforeSection = undefined;
var fileChanged = false;
function updateSectionList(newSectionList, newLinkDefinition) {
modifiedSections = [];
sectionsToRemove = [];
insertAfterSection = undefined;
insertBeforeSection = undefined;
// Render everything if file or linkDefinition changed
if(fileChanged === true || linkDefinition != newLinkDefinition) {
@ -59,7 +59,7 @@ define([
var leftSections = sectionList.slice(0, leftIndex);
modifiedSections = newSectionList.slice(leftIndex, newSectionList.length + rightIndex);
var rightSections = sectionList.slice(sectionList.length + rightIndex, sectionList.length);
insertAfterSection = _.last(leftSections);
insertBeforeSection = _.first(rightSections);
sectionsToRemove = sectionList.slice(leftIndex, sectionList.length + rightIndex);
sectionList = leftSections.concat(modifiedSections).concat(rightSections);
}
@ -72,17 +72,8 @@ define([
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+?)>?(?=\s|$)[ \t]*\n?[ \t]*((\n*)["(](.+?)[")][ \t]*)?(?:\n+)/gm, function(wholeMatch, link) {
if(link) {
newLinkDefinition += wholeMatch;
return "";
}
return wholeMatch;
});
// And footnotes eventually
// Strip 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) {
@ -94,6 +85,15 @@ define([
});
}
// Strip link definitions
text = text.replace(/^```.*\n[\s\S]*?\n```|^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?(?=\s|$)[ \t]*\n?[ \t]*((\n*)["(](.+?)[")][ \t]*)?(?:\n+)/gm, function(wholeMatch, link) {
if(link) {
newLinkDefinition += wholeMatch;
return "";
}
return wholeMatch;
});
// Skip space only sections
if(/\S/.test(text)) {
// Add section to the newSectionList
@ -107,56 +107,73 @@ define([
updateSectionList(newSectionList, newLinkDefinition);
};
var footnoteContainerElt = $('<div id="wmd-preview-section-footnotes" class="preview-content">');
var footnoteList = {};
var footnoteContainerElt = undefined;
var previewContentsElt = undefined;
var footnoteMap = {};
function refreshSections() {
// Remove outdated sections
_.each(sectionsToRemove, function(section) {
$("#wmd-preview-section-" + section.id).remove();
var sectionElt = document.getElementById("wmd-preview-section-" + section.id);
previewContentsElt.removeChild(sectionElt);
});
var wmdPreviewElt = $("#wmd-preview");
var insertAfterSectionElt = insertAfterSection === undefined ? wmdPreviewElt : $("#wmd-preview-section-" + insertAfterSection.id);
var wmdPreviewElt = document.getElementById("wmd-preview");
var markdownEltList = Array.prototype.slice.call(wmdPreviewElt.childNodes);
wmdPreviewElt.innerHTML = '';
var newSectionEltList = document.createDocumentFragment();
_.each(modifiedSections, function(section) {
var sectionElt = $('<div id="wmd-preview-section-' + section.id + '" class="wmd-preview-section preview-content">');
_.some(wmdPreviewElt.contents(), function(elt, index) {
elt = $(elt);
if(index !== 0 && elt.is(".wmd-title")) {
return true;
var sectionElt = crel('div', {
id: 'wmd-preview-section-' + section.id,
class: 'wmd-preview-section preview-content'
});
var isFirst = true;
while(markdownEltList.length !== 0) {
var elt = markdownEltList[0];
if(isFirst === false && /(^| )wmd-title($| )/.test(elt.className)) {
// Stop when encountered the next wmd-title
break;
}
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;
isFirst = false;
if(elt.tagName == 'DIV' && elt.className == 'footnotes') {
_.each(elt.querySelectorAll("ol > li"), function(footnoteElt) {
// Store each footnote in our footnote map
var id = footnoteElt.id.substring(3);
footnoteMap[id] = footnoteElt;
});
elt.remove();
}
else {
sectionElt.append(elt);
sectionElt.appendChild(elt);
}
});
insertAfterSectionElt.after(sectionElt);
insertAfterSectionElt = sectionElt;
markdownEltList.shift();
};
newSectionEltList.appendChild(sectionElt);
});
var insertBeforeElt = footnoteContainerElt;
if(insertBeforeSection !== undefined) {
insertBeforeElt = document.getElementById("wmd-preview-section-" + insertBeforeSection.id);
}
previewContentsElt.insertBefore(newSectionEltList, insertBeforeElt);
// Rewrite footnotes in the footer and update footnote numbers
footnoteContainerElt.empty();
footnoteContainerElt.innerHTML = '';
var usedFootnoteIds = [];
if(hasFootnotes === true) {
var footnoteElts = $("<ol>");
$("#preview-contents a.footnote").each(function(index) {
var id=$(this).text(index + 1).attr("id").substring(6);
var footnoteElts = crel('ol');
_.each(previewContentsElt.querySelectorAll('a.footnote'), function(elt, index) {
elt.textContent = index + 1;
var id = elt.id.substring(6);
usedFootnoteIds.push(id);
footnoteElts.append(footnoteList[id].clone());
footnoteElts.appendChild(footnoteMap[id].cloneNode(true));
});
if(usedFootnoteIds.length > 0) {
// Append the whole footnotes at the end of the document
footnoteContainerElt.html($('<div class="footnotes">').append("<hr>").append(footnoteElts));
footnoteContainerElt.appendChild(crel('div', {
class: 'footnotes'
}, crel('hr'), footnoteElts));
}
// Keep used footnotes only in our map
footnoteList = _.pick(footnoteList, usedFootnoteIds);
footnoteMap = _.pick(footnoteMap, usedFootnoteIds);
}
}
@ -175,12 +192,18 @@ define([
};
partialRendering.onReady = function() {
$("#preview-contents").append(footnoteContainerElt);
$("#wmd-preview").hide();
footnoteContainerElt = crel('div', {
id: 'wmd-preview-section-footnotes',
class: 'preview-content'
});
previewContentsElt = document.getElementById("preview-contents");
previewContentsElt.appendChild(footnoteContainerElt);
};
partialRendering.onFileSelected = function() {
fileChanged = true;
};
partialRendering.onFileOpen = function() {
if(converter.extraExtensions) {
doFootnotes = _.some(converter.extraExtensions, function(extension) {

View File

@ -15,6 +15,9 @@ define([
sectionList = sectionListParam;
};
var editorElt = undefined;
var previewElt = undefined;
var textareaElt = undefined;
var mdSectionList = [];
var htmlSectionList = [];
function pxToFloat(px) {
@ -25,10 +28,7 @@ define([
var buildSections = _.debounce(function() {
// Try to find Markdown sections by looking for titles
var editorElt = $("#wmd-input");
mdSectionList = [];
// This textarea is used to measure sections height
var textareaElt = $("#md-section-helper");
// It has to be the same width as wmd-input
textareaElt.width(editorElt.width());
// Consider wmd-input top padding (will be used for 1st and last
@ -65,14 +65,14 @@ define([
});
// Try to find corresponding sections in the preview
var previewElt = $(".preview-container");
htmlSectionList = [];
var htmlSectionOffset = 0;
var previewScrollTop = previewElt.scrollTop();
// Each title element is a section separator
$("#preview-contents > .preview-content").children(".wmd-title").each(function() {
previewElt.find(".preview-content > .wmd-title").each(function() {
var titleElt = $(this);
// Consider div scroll position and header element top margin
var newSectionOffset = $(this).position().top + previewScrollTop + pxToFloat($(this).css('margin-top'));
var newSectionOffset = titleElt.position().top + previewScrollTop + pxToFloat(titleElt.css('margin-top'));
htmlSectionList.push({
startOffset: htmlSectionOffset,
endOffset: newSectionOffset,
@ -100,9 +100,7 @@ define([
if(mdSectionList.length === 0 || mdSectionList.length !== htmlSectionList.length) {
return;
}
var editorElt = $("#wmd-input");
var editorScrollTop = editorElt.scrollTop();
var previewElt = $(".preview-container");
var previewScrollTop = previewElt.scrollTop();
function animate(srcScrollTop, srcSectionList, destElt, destSectionList, currentDestScrollTop, callback) {
// Find the section corresponding to the offset
@ -160,6 +158,12 @@ define([
};
scrollLink.onLayoutCreated = function() {
editorElt = $("#wmd-input");
previewElt = $(".preview-container");
// This textarea is used to measure sections height
textareaElt = $("#md-section-helper");
$(".preview-container").bind("keyup mouseup mousewheel", function() {
isScrollPreview = true;
isScrollEditor = false;
@ -172,19 +176,20 @@ define([
});
};
var previewContentsElt = undefined;
scrollLink.onEditorConfigure = function(editor) {
previewContentsElt = $("#preview-contents");
editor.getConverter().hooks.chain("postConversion", function(text) {
// To avoid losing scrolling position before elements are fully
// loaded
var previewElt = $("#preview-contents");
previewElt.height(previewElt.height());
previewContentsElt.height(previewContentsElt.height());
return text;
});
};
scrollLink.onPreviewFinished = function() {
// Now set the correct height
$("#preview-contents").height("auto");
previewContentsElt.height("auto");
isScrollEditor = true;
buildSections();
};

View File

@ -90,10 +90,11 @@ define([
}
// Build the TOC
var previewContentsElt = undefined;
function buildToc() {
var anchorList = {};
function createAnchor(element) {
var id = element.prop("id") || utils.slugify(element.text());
var id = element.id || utils.slugify(element.textContent);
var anchor = id;
var index = 0;
while (_.has(anchorList, anchor)) {
@ -101,31 +102,35 @@ define([
}
anchorList[anchor] = true;
// Update the id of the element
element.prop("id", anchor);
element.id = anchor;
return anchor;
}
var elementList = [];
$("#preview-contents > .preview-content").children("h1, h2, h3, h4, h5, h6").each(function() {
elementList.push(new TocElement($(this).prop("tagName"), createAnchor($(this)), $(this).text()));
_.each(previewContentsElt.querySelectorAll('.preview-content > .wmd-title'), function(elt) {
elementList.push(new TocElement(elt.tagName, createAnchor(elt), elt.textContent));
});
elementList = groupTags(elementList);
return '<div class="toc">\n<ul>\n' + elementList.join("") + '</ul>\n</div>\n';
}
toc.onEditorConfigure = function(editor) {
var tocExp = new RegExp("^" + toc.config.marker + "$", "g")
previewContentsElt = document.getElementById('preview-contents');
var tocEltList = document.querySelectorAll('.table-of-contents');
var tocExp = new RegExp("^" + toc.config.marker + "$", "g");
// Run TOC generation when conversion is finished directly on HTML
editor.hooks.chain("onPreviewRefresh", function() {
var htmlToc = buildToc();
// Replace toc paragraphs
$("#preview-contents p").each(function() {
if(tocExp.test($(this).html())) {
$(this).html(htmlToc);
_.each(previewContentsElt.getElementsByTagName('p'), function(elt) {
if(tocExp.test(elt.innerHTML)) {
elt.innerHTML = htmlToc;
}
});
// Add toc in the TOC button
$(".table-of-contents").html(htmlToc);
_.each(tocEltList, function(elt) {
elt.innerHTML = htmlToc;
});
});
};

View File

@ -482,6 +482,9 @@
// Find and convert footnotes references.
Markdown.Extra.prototype.doFootnotes = function(text) {
var self = this;
if(self.isConvertingFootnote === true) {
return text;
}
var footnoteCounter = 0;
text = text.replace(/\[\^(.+?)\]/g, function(wholeMatch, m1) {
@ -512,7 +515,9 @@
for(var i=0; i<self.usedFootnotes.length; i++) {
var id = self.usedFootnotes[i];
var footnote = self.footnotes[id];
self.isConvertingFootnote = true;
var formattedfootnote = convertSpans(footnote, self);
delete self.isConvertingFootnote;
text += '<li id="fn:'
+ id
+ '">'

View File

@ -4,6 +4,7 @@ requirejs.config({
paths: {
"jquery": "libs/jquery",
"underscore": "libs/underscore",
"crel": "libs/crel",
"jgrowl": "libs/jgrowl",
"mousetrap": "libs/mousetrap",
"toMarkdown": "libs/to-markdown",