Added TOC support
This commit is contained in:
parent
c6244d3190
commit
ce35c5c656
@ -191,6 +191,14 @@ h1 {
|
|||||||
margin: 30px 0 30px;
|
margin: 30px 0 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h4, h5, h6 {
|
||||||
|
line-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc ul {
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
p, pre, blockquote {
|
p, pre, blockquote {
|
||||||
margin: 0 0 20px;
|
margin: 0 0 20px;
|
||||||
}
|
}
|
||||||
|
@ -309,8 +309,8 @@
|
|||||||
data-dismiss="modal"><i class="icon-ok"></i></a>
|
data-dismiss="modal"><i class="icon-ok"></i></a>
|
||||||
</div>
|
</div>
|
||||||
<blockquote class="muted">
|
<blockquote class="muted">
|
||||||
<b>NOTE:</b> This will upload the local document firstly and
|
<b>NOTE:</b> This will first upload the document and overwrite the
|
||||||
overwrite the existing file on the server.
|
existing file on the server.
|
||||||
</blockquote>
|
</blockquote>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
@ -627,8 +627,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane" id="tabpane-settings-extensions">
|
<div class="tab-pane" id="tabpane-settings-extensions">
|
||||||
<div class="accordion" id="accordion-extensions">
|
<div class="accordion" id="accordion-extensions"></div>
|
||||||
</div>
|
|
||||||
<span class="help-block pull-right"><a target="_blank"
|
<span class="help-block pull-right"><a target="_blank"
|
||||||
href="https://github.com/benweet/stackedit/blob/master/doc/theming.md#stackedit-theming-guide">Create
|
href="https://github.com/benweet/stackedit/blob/master/doc/theming.md#stackedit-theming-guide">Create
|
||||||
your own extension...</a></span>
|
your own extension...</a></span>
|
||||||
|
18
js/core.js
18
js/core.js
@ -288,7 +288,7 @@ define(
|
|||||||
// Custom insert link dialog
|
// Custom insert link dialog
|
||||||
editor.hooks.set("insertLinkDialog", function (callback) {
|
editor.hooks.set("insertLinkDialog", function (callback) {
|
||||||
insertLinkCallback = callback;
|
insertLinkCallback = callback;
|
||||||
core.resetModalInputs();
|
utils.resetModalInputs();
|
||||||
$("#modal-insert-link").modal();
|
$("#modal-insert-link").modal();
|
||||||
_.defer(function() {
|
_.defer(function() {
|
||||||
$("#input-insert-link").focus();
|
$("#input-insert-link").focus();
|
||||||
@ -298,7 +298,7 @@ define(
|
|||||||
// Custom insert image dialog
|
// Custom insert image dialog
|
||||||
editor.hooks.set("insertImageDialog", function (callback) {
|
editor.hooks.set("insertImageDialog", function (callback) {
|
||||||
insertLinkCallback = callback;
|
insertLinkCallback = callback;
|
||||||
core.resetModalInputs();
|
utils.resetModalInputs();
|
||||||
$("#modal-insert-image").modal();
|
$("#modal-insert-image").modal();
|
||||||
_.defer(function() {
|
_.defer(function() {
|
||||||
$("#input-insert-image").focus();
|
$("#input-insert-image").focus();
|
||||||
@ -316,15 +316,21 @@ define(
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
if(core.settings.lazyRendering === true) {
|
if(core.settings.lazyRendering === true) {
|
||||||
|
var lastRefresh = 0;
|
||||||
previewWrapper = function(makePreview) {
|
previewWrapper = function(makePreview) {
|
||||||
var debouncedMakePreview = _.debounce(makePreview, 500);
|
//var debouncedMakePreview = _.debounce(makePreview, 500);
|
||||||
return function() {
|
return function() {
|
||||||
if(firstChange === true) {
|
if(firstChange === true) {
|
||||||
makePreview();
|
makePreview();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
onTextChange();
|
onTextChange();
|
||||||
debouncedMakePreview();
|
var currentDate = new Date().getTime();
|
||||||
|
if(currentDate - lastRefresh > 500) {
|
||||||
|
makePreview();
|
||||||
|
lastRefresh = currentDate;
|
||||||
|
}
|
||||||
|
//debouncedMakePreview();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -519,7 +525,7 @@ define(
|
|||||||
html: true,
|
html: true,
|
||||||
container: '#modal-settings',
|
container: '#modal-settings',
|
||||||
placement: 'right',
|
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({
|
$(".tooltip-template").tooltip({
|
||||||
html: true,
|
html: true,
|
||||||
@ -550,7 +556,7 @@ define(
|
|||||||
|
|
||||||
// Reset inputs
|
// Reset inputs
|
||||||
$(".action-reset-input").click(function() {
|
$(".action-reset-input").click(function() {
|
||||||
core.resetModalInputs();
|
utils.resetModalInputs();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Do periodic tasks
|
// Do periodic tasks
|
||||||
|
@ -5,6 +5,7 @@ define( [
|
|||||||
"bootstrap",
|
"bootstrap",
|
||||||
"extensions/notifications",
|
"extensions/notifications",
|
||||||
"extensions/markdown-extra",
|
"extensions/markdown-extra",
|
||||||
|
"extensions/toc",
|
||||||
"extensions/math-jax",
|
"extensions/math-jax",
|
||||||
"extensions/scroll-link"
|
"extensions/scroll-link"
|
||||||
], function($, utils) {
|
], function($, utils) {
|
||||||
@ -71,7 +72,7 @@ define( [
|
|||||||
extensionSettings = extensionSettings || {};
|
extensionSettings = extensionSettings || {};
|
||||||
_.each(extensionList, function(extension) {
|
_.each(extensionList, function(extension) {
|
||||||
extension.config = _.extend({}, extension.defaultConfig, extensionSettings[extension.extensionId]);
|
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
|
// Create accordion in settings dialog
|
||||||
|
@ -8,7 +8,7 @@ define( [ "jquery", "underscore" ], function($) {
|
|||||||
'<p>Binds together editor and preview scrollbars.</p>',
|
'<p>Binds together editor and preview scrollbars.</p>',
|
||||||
'<blockquote class="muted"><b>NOTE:</b> ',
|
'<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. ',
|
'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>'
|
'</bloquote>'
|
||||||
].join("")
|
].join("")
|
||||||
};
|
};
|
||||||
|
127
js/extensions/toc.js
Normal file
127
js/extensions/toc.js
Normal 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;
|
||||||
|
});
|
||||||
|
|
@ -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 connected = false;
|
||||||
var authenticated = false;
|
var authenticated = false;
|
||||||
|
@ -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"],
|
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 = {};
|
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();
|
$('div[class*=" modal-publish-"]').hide().filter(".modal-publish-" + provider.providerId).show();
|
||||||
|
|
||||||
// Reset fields
|
// Reset fields
|
||||||
core.resetModalInputs();
|
utils.resetModalInputs();
|
||||||
$("input:radio[name=radio-publish-format][value=" + defaultPublishFormat + "]").prop("checked", true);
|
$("input:radio[name=radio-publish-format][value=" + defaultPublishFormat + "]").prop("checked", true);
|
||||||
|
|
||||||
// Load preferences
|
// Load preferences
|
||||||
|
@ -240,7 +240,7 @@ define(["jquery", "core", "utils", "dropbox-provider", "gdrive-provider", "under
|
|||||||
function initExportDialog(provider) {
|
function initExportDialog(provider) {
|
||||||
|
|
||||||
// Reset fields
|
// Reset fields
|
||||||
core.resetModalInputs();
|
utils.resetModalInputs();
|
||||||
|
|
||||||
// Load preferences
|
// Load preferences
|
||||||
var serializedPreferences = localStorage[provider.providerId + ".exportPreferences"];
|
var serializedPreferences = localStorage[provider.providerId + ".exportPreferences"];
|
||||||
|
@ -104,7 +104,7 @@ define([ "jquery", "underscore" ], function($) {
|
|||||||
|
|
||||||
// Basic trim function
|
// Basic trim function
|
||||||
utils.trim = function(str) {
|
utils.trim = function(str) {
|
||||||
return str.replace(/^\s+|\s+$/g, '');
|
return $.trim(str);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check an URL
|
// Check an URL
|
||||||
|
Loading…
Reference in New Issue
Block a user