From ce35c5c6563e223cc7099f731da9c0939d03e5c3 Mon Sep 17 00:00:00 2001 From: benweet Date: Sun, 26 May 2013 02:10:58 +0100 Subject: [PATCH] Added TOC support --- css/default.css | 8 +++ index.html | 11 ++- js/core.js | 18 +++-- js/extension-manager.js | 3 +- js/extensions/scroll-link.js | 2 +- js/extensions/toc.js | 127 +++++++++++++++++++++++++++++++++++ js/google-helper.js | 2 +- js/publisher.js | 4 +- js/synchronizer.js | 2 +- js/utils.js | 2 +- 10 files changed, 160 insertions(+), 19 deletions(-) create mode 100644 js/extensions/toc.js diff --git a/css/default.css b/css/default.css index 2c0603d4..4da4871f 100644 --- a/css/default.css +++ b/css/default.css @@ -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; } diff --git a/index.html b/index.html index 305c71a2..499db40b 100644 --- a/index.html +++ b/index.html @@ -309,8 +309,8 @@ data-dismiss="modal">
- NOTE: This will upload the local document firstly and - overwrite the existing file on the server. + NOTE: This will first upload the document and overwrite the + existing file on the server.
-
-
+
Create - your own extension... + href="https://github.com/benweet/stackedit/blob/master/doc/theming.md#stackedit-theming-guide">Create + your own extension...
diff --git a/js/core.js b/js/core.js index 5b6f2476..72d00da3 100644 --- a/js/core.js +++ b/js/core.js @@ -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 diff --git a/js/extension-manager.js b/js/extension-manager.js index cf5a8a03..17cf1e05 100644 --- a/js/extension-manager.js +++ b/js/extension-manager.js @@ -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 diff --git a/js/extensions/scroll-link.js b/js/extensions/scroll-link.js index 49cec285..4c08a67a 100644 --- a/js/extensions/scroll-link.js +++ b/js/extensions/scroll-link.js @@ -8,7 +8,7 @@ define( [ "jquery", "underscore" ], function($) { '

Binds together editor and preview scrollbars.

', '
NOTE: ', '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.', '' ].join("") }; diff --git a/js/extensions/toc.js b/js/extensions/toc.js new file mode 100644 index 00000000..2e041463 --- /dev/null +++ b/js/extensions/toc.js @@ -0,0 +1,127 @@ +define( [ "jquery", "underscore" ], function($) { + + var toc = { + extensionId: "toc", + extensionName: "Table Of Content", + optional: true, + settingsBloc: [ + '

Generates tables of content using the marker [TOC].

' + ].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 = "
    "; + _.each(this.children, function(child) { + result += child.toString(); + }); + result += "
"; + return result; + }; + TocElement.prototype.toString = function() { + var result = "
  • "; + if(this.anchor && this.text) { + result += '' + this.text + ''; + } + result += this.childrenToString() + "
  • "; + 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 '
      ' + elementList.toString() + '
    '; + } + + 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(/

    \[TOC\]<\/p>/g, toc); + $("#wmd-preview").html(html); + }); + }; + + return toc; +}); + diff --git a/js/google-helper.js b/js/google-helper.js index 7a107049..31a407a2 100644 --- a/js/google-helper.js +++ b/js/google-helper.js @@ -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; diff --git a/js/publisher.js b/js/publisher.js index 7112a508..df7165ad 100644 --- a/js/publisher.js +++ b/js/publisher.js @@ -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 diff --git a/js/synchronizer.js b/js/synchronizer.js index 55adfe88..896098ce 100644 --- a/js/synchronizer.js +++ b/js/synchronizer.js @@ -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"]; diff --git a/js/utils.js b/js/utils.js index dbc3724b..1ba5bb64 100644 --- a/js/utils.js +++ b/js/utils.js @@ -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