Added TOC support

This commit is contained in:
benweet 2013-05-26 02:10:58 +01:00
parent c6244d3190
commit ce35c5c656
10 changed files with 160 additions and 19 deletions

View File

@ -191,6 +191,14 @@ h1 {
margin: 30px 0 30px;
}
h4, h5, h6 {
line-height: 40px;
}
.toc ul {
list-style-type: none;
}
p, pre, blockquote {
margin: 0 0 20px;
}

View File

@ -309,8 +309,8 @@
data-dismiss="modal"><i class="icon-ok"></i></a>
</div>
<blockquote class="muted">
<b>NOTE:</b> This will upload the local document firstly and
overwrite the existing file on the server.
<b>NOTE:</b> This will first upload the document and overwrite the
existing file on the server.
</blockquote>
</div>
<div class="modal-footer">
@ -627,11 +627,10 @@
</div>
</div>
<div class="tab-pane" id="tabpane-settings-extensions">
<div class="accordion" id="accordion-extensions">
</div>
<div class="accordion" id="accordion-extensions"></div>
<span class="help-block pull-right"><a target="_blank"
href="https://github.com/benweet/stackedit/blob/master/doc/theming.md#stackedit-theming-guide">Create
your own extension...</a></span>
href="https://github.com/benweet/stackedit/blob/master/doc/theming.md#stackedit-theming-guide">Create
your own extension...</a></span>
</div>
<div class="tab-pane" id="tabpane-settings-utils">
<div style="width: 200px; margin: 10px auto;">

View File

@ -288,7 +288,7 @@ define(
// Custom insert link dialog
editor.hooks.set("insertLinkDialog", function (callback) {
insertLinkCallback = callback;
core.resetModalInputs();
utils.resetModalInputs();
$("#modal-insert-link").modal();
_.defer(function() {
$("#input-insert-link").focus();
@ -298,7 +298,7 @@ define(
// Custom insert image dialog
editor.hooks.set("insertImageDialog", function (callback) {
insertLinkCallback = callback;
core.resetModalInputs();
utils.resetModalInputs();
$("#modal-insert-image").modal();
_.defer(function() {
$("#input-insert-image").focus();
@ -316,15 +316,21 @@ define(
};
};
if(core.settings.lazyRendering === true) {
var lastRefresh = 0;
previewWrapper = function(makePreview) {
var debouncedMakePreview = _.debounce(makePreview, 500);
//var debouncedMakePreview = _.debounce(makePreview, 500);
return function() {
if(firstChange === true) {
makePreview();
}
else {
onTextChange();
debouncedMakePreview();
var currentDate = new Date().getTime();
if(currentDate - lastRefresh > 500) {
makePreview();
lastRefresh = currentDate;
}
//debouncedMakePreview();
}
};
};
@ -519,7 +525,7 @@ define(
html: true,
container: '#modal-settings',
placement: 'right',
title: 'Thank you for supporting StackEdit by adding a backlink in your documents!'
title: 'Thanks for supporting StackEdit by adding a backlink in your documents!'
});
$(".tooltip-template").tooltip({
html: true,
@ -550,7 +556,7 @@ define(
// Reset inputs
$(".action-reset-input").click(function() {
core.resetModalInputs();
utils.resetModalInputs();
});
// Do periodic tasks

View File

@ -5,6 +5,7 @@ define( [
"bootstrap",
"extensions/notifications",
"extensions/markdown-extra",
"extensions/toc",
"extensions/math-jax",
"extensions/scroll-link"
], function($, utils) {
@ -71,7 +72,7 @@ define( [
extensionSettings = extensionSettings || {};
_.each(extensionList, function(extension) {
extension.config = _.extend({}, extension.defaultConfig, extensionSettings[extension.extensionId]);
extension.config.enabled = !extension.optional || extension.config.enabled;
extension.config.enabled = !extension.optional || extension.config.enabled === undefined || extension.config.enabled === true;
});
// Create accordion in settings dialog

View File

@ -8,7 +8,7 @@ define( [ "jquery", "underscore" ], function($) {
'<p>Binds together editor and preview scrollbars.</p>',
'<blockquote class="muted"><b>NOTE:</b> ',
'The mapping between Markdown and HTML is based on the position of the title elements (h1, h2, ...) in the page. ',
'Therefore, if your document does not contain any title, the mapping will be linear and consequently less efficient.',
'Therefore, if your document does not contain any title, the mapping will be linear and consequently less accurate.',
'</bloquote>'
].join("")
};

127
js/extensions/toc.js Normal file
View File

@ -0,0 +1,127 @@
define( [ "jquery", "underscore" ], function($) {
var toc = {
extensionId: "toc",
extensionName: "Table Of Content",
optional: true,
settingsBloc: [
'<p>Generates tables of content using the marker [TOC].</p>'
].join("")
};
// Used to generate an anchor
function slugify(text)
{
return text.toLowerCase()
.replace(/\s+/g, '-') // Replace spaces with -
.replace(/[^\w\-]+/g, '') // Remove all non-word chars
.replace(/\-\-+/g, '-') // Replace multiple - with single -
.replace(/^-+/, '') // Trim - from start of text
.replace(/-+$/, ''); // Trim - from end of text
}
// TOC element description
function TocElement(tagName, anchor, text) {
this.tagName = tagName;
this.anchor = anchor;
this.text = text;
this.children = [];
}
TocElement.prototype.childrenToString = function() {
if(this.children.length === 0) {
return "";
}
var result = "<ul>";
_.each(this.children, function(child) {
result += child.toString();
});
result += "</ul>";
return result;
};
TocElement.prototype.toString = function() {
var result = "<li>";
if(this.anchor && this.text) {
result += '<a href="#' + this.anchor + '">' + this.text + '</a>';
}
result += this.childrenToString() + "</li>";
return result;
};
// Transform flat list of TocElement into a tree
function groupTags(array, level) {
level = level || 1;
var tagName = "H" + level;
var result = [];
var currentElement = undefined;
function pushCurrentElement() {
if(currentElement !== undefined) {
if(currentElement.children.length > 0) {
currentElement.children = groupTags(currentElement.children, level + 1);
}
result.push(currentElement);
}
}
_.each(array, function(element, index) {
if(element.tagName != tagName) {
if(currentElement === undefined) {
currentElement = new TocElement();
}
currentElement.children.push(element);
}
else {
pushCurrentElement();
currentElement = element;
}
});
pushCurrentElement();
return result;
}
// Build the TOC
function buildToc() {
var anchorList = {};
function createAnchor(element) {
var id = element.prop("id") || slugify(element.text());
var anchor = id;
var index = 0;
while(_.has(anchorList, anchor)) {
anchor = id + "-" + (++index);
}
anchorList[anchor] = true;
// Update the id of the element
element.prop("id", anchor);
return anchor;
}
var elementList = [];
$("#wmd-preview > h1," +
"#wmd-preview > h2," +
"#wmd-preview > h3," +
"#wmd-preview > h4," +
"#wmd-preview > h5," +
"#wmd-preview > h6").each(function() {
elementList.push(new TocElement(
$(this).prop("tagName"),
createAnchor($(this)),
$(this).text()
));
});
elementList = groupTags(elementList);
return '<div class="toc"><ul>' + elementList.toString() + '</ul></div>';
}
toc.onEditorConfigure = function(editor) {
// Run TOC generation when conversion is finished directly on HTML
editor.hooks.chain("onPreviewRefresh", function() {
var toc = buildToc();
var html = $("#wmd-preview").html();
html = html.replace(/<p>\[TOC\]<\/p>/g, toc);
$("#wmd-preview").html(html);
});
};
return toc;
});

View File

@ -1,4 +1,4 @@
define(["jquery", "core", "async-runner"], function($, core, asyncRunner) {
define(["jquery", "core", "utils", "async-runner"], function($, core, utils, asyncRunner) {
var connected = false;
var authenticated = false;

View File

@ -1,5 +1,5 @@
define(["jquery", "core", "utils", "sharing", "blogger-provider", "dropbox-provider", "gist-provider", "github-provider", "gdrive-provider", "ssh-provider", "tumblr-provider", "wordpress-provider", "underscore"],
function($, core, sharing, utils) {
function($, core, utils, sharing) {
var publisher = {};
@ -148,7 +148,7 @@ define(["jquery", "core", "utils", "sharing", "blogger-provider", "dropbox-provi
$('div[class*=" modal-publish-"]').hide().filter(".modal-publish-" + provider.providerId).show();
// Reset fields
core.resetModalInputs();
utils.resetModalInputs();
$("input:radio[name=radio-publish-format][value=" + defaultPublishFormat + "]").prop("checked", true);
// Load preferences

View File

@ -240,7 +240,7 @@ define(["jquery", "core", "utils", "dropbox-provider", "gdrive-provider", "under
function initExportDialog(provider) {
// Reset fields
core.resetModalInputs();
utils.resetModalInputs();
// Load preferences
var serializedPreferences = localStorage[provider.providerId + ".exportPreferences"];

View File

@ -104,7 +104,7 @@ define([ "jquery", "underscore" ], function($) {
// Basic trim function
utils.trim = function(str) {
return str.replace(/^\s+|\s+$/g, '');
return $.trim(str);
};
// Check an URL