interactive preview added
This commit is contained in:
@@ -9,6 +9,7 @@ status-500 = OMG, Server Exploded.
|
||||
created-by = Created by [@chmllr](http://twitter.com/chmllr)
|
||||
|
||||
loading = Loading...
|
||||
use-password = User password for editing:
|
||||
preview = Preview
|
||||
publish = Publish
|
||||
published = Published
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,108 @@
|
||||
(function () {
|
||||
var output, Converter;
|
||||
if (typeof exports === "object" && typeof require === "function") { // we're in a CommonJS (e.g. Node.js) module
|
||||
output = exports;
|
||||
Converter = require("./Markdown.Converter").Converter;
|
||||
} else {
|
||||
output = window.Markdown;
|
||||
Converter = output.Converter;
|
||||
}
|
||||
|
||||
output.getSanitizingConverter = function () {
|
||||
var converter = new Converter();
|
||||
converter.hooks.chain("postConversion", sanitizeHtml);
|
||||
converter.hooks.chain("postConversion", balanceTags);
|
||||
return converter;
|
||||
}
|
||||
|
||||
function sanitizeHtml(html) {
|
||||
return html.replace(/<[^>]*>?/gi, sanitizeTag);
|
||||
}
|
||||
|
||||
// (tags that can be opened/closed) | (tags that stand alone)
|
||||
var basic_tag_whitelist = /^(<\/?(b|blockquote|code|del|dd|dl|dt|em|h1|h2|h3|i|kbd|li|ol|p|pre|s|sup|sub|strong|strike|ul)>|<(br|hr)\s?\/?>)$/i;
|
||||
// <a href="url..." optional title>|</a>
|
||||
var a_white = /^(<a\shref="((https?|ftp):\/\/|\/)[-A-Za-z0-9+&@#\/%?=~_|!:,.;\(\)]+"(\stitle="[^"<>]+")?\s?>|<\/a>)$/i;
|
||||
|
||||
// <img src="url..." optional width optional height optional alt optional title
|
||||
var img_white = /^(<img\ssrc="(https?:\/\/|\/)[-A-Za-z0-9+&@#\/%?=~_|!:,.;\(\)]+"(\swidth="\d{1,3}")?(\sheight="\d{1,3}")?(\salt="[^"<>]*")?(\stitle="[^"<>]*")?\s?\/?>)$/i;
|
||||
|
||||
function sanitizeTag(tag) {
|
||||
if (tag.match(basic_tag_whitelist) || tag.match(a_white) || tag.match(img_white))
|
||||
return tag;
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// attempt to balance HTML tags in the html string
|
||||
/// by removing any unmatched opening or closing tags
|
||||
/// IMPORTANT: we *assume* HTML has *already* been
|
||||
/// sanitized and is safe/sane before balancing!
|
||||
///
|
||||
/// adapted from CODESNIPPET: A8591DBA-D1D3-11DE-947C-BA5556D89593
|
||||
/// </summary>
|
||||
function balanceTags(html) {
|
||||
|
||||
if (html == "")
|
||||
return "";
|
||||
|
||||
var re = /<\/?\w+[^>]*(\s|$|>)/g;
|
||||
// convert everything to lower case; this makes
|
||||
// our case insensitive comparisons easier
|
||||
var tags = html.toLowerCase().match(re);
|
||||
|
||||
// no HTML tags present? nothing to do; exit now
|
||||
var tagcount = (tags || []).length;
|
||||
if (tagcount == 0)
|
||||
return html;
|
||||
|
||||
var tagname, tag;
|
||||
var ignoredtags = "<p><img><br><li><hr>";
|
||||
var match;
|
||||
var tagpaired = [];
|
||||
var tagremove = [];
|
||||
var needsRemoval = false;
|
||||
|
||||
// loop through matched tags in forward order
|
||||
for (var ctag = 0; ctag < tagcount; ctag++) {
|
||||
tagname = tags[ctag].replace(/<\/?(\w+).*/, "$1");
|
||||
// skip any already paired tags
|
||||
// and skip tags in our ignore list; assume they're self-closed
|
||||
if (tagpaired[ctag] || ignoredtags.search("<" + tagname + ">") > -1)
|
||||
continue;
|
||||
|
||||
tag = tags[ctag];
|
||||
match = -1;
|
||||
|
||||
if (!/^<\//.test(tag)) {
|
||||
// this is an opening tag
|
||||
// search forwards (next tags), look for closing tags
|
||||
for (var ntag = ctag + 1; ntag < tagcount; ntag++) {
|
||||
if (!tagpaired[ntag] && tags[ntag] == "</" + tagname + ">") {
|
||||
match = ntag;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (match == -1)
|
||||
needsRemoval = tagremove[ctag] = true; // mark for removal
|
||||
else
|
||||
tagpaired[match] = true; // mark paired
|
||||
}
|
||||
|
||||
if (!needsRemoval)
|
||||
return html;
|
||||
|
||||
// delete all orphaned tags from the string
|
||||
|
||||
var ctag = 0;
|
||||
html = html.replace(re, function (match) {
|
||||
var res = tagremove[ctag] ? "" : match;
|
||||
ctag++;
|
||||
return res;
|
||||
});
|
||||
return html;
|
||||
}
|
||||
})();
|
||||
+11
-21
@@ -7,13 +7,12 @@
|
||||
; frequently used selectors
|
||||
(def $draft ($ :#draft))
|
||||
(def $preview ($ :#preview))
|
||||
(def $session-key ($ :#session-key))
|
||||
(def $input-elems ($ :#input-elems))
|
||||
(def $preview-start-line ($ :#preview-start-line))
|
||||
|
||||
(defn scroll-to
|
||||
"scrolls to the given selector"
|
||||
[$id]
|
||||
(anim ($ :body) {:scrollTop ((js->clj (.offset $id)) "top")} 500))
|
||||
; Markdown Converter & Sanitizer instantiation
|
||||
|
||||
(def md-converter (Markdown/getSanitizingConverter))
|
||||
|
||||
; try to detect iOS
|
||||
(def ios-detected (.match (.-userAgent js/navigator) "(iPad|iPod|iPhone)"))
|
||||
@@ -28,26 +27,17 @@
|
||||
(.focus $draft))))
|
||||
|
||||
; show the preview & publish buttons as soon as the user starts typing.
|
||||
(.keypress $draft
|
||||
(.keyup $draft
|
||||
(fn [e]
|
||||
(css ($ :#buttons) {:display :block})))
|
||||
|
||||
; on a preview button click, transform markdown to html, put it
|
||||
; to the preview layer and scroll to it
|
||||
(.click ($ :#preview-button)
|
||||
(fn [e]
|
||||
(xhr [:post "/preview"]
|
||||
{:draft (val $draft)}
|
||||
(fn [json-map]
|
||||
(let [m (js->clj (JSON/parse json-map))]
|
||||
(do
|
||||
(inner $preview (m "preview"))
|
||||
(show $preview-start-line)
|
||||
(scroll-to $preview-start-line)))))))
|
||||
(do
|
||||
(show $preview-start-line)
|
||||
(show $input-elems)
|
||||
(inner $preview
|
||||
(.makeHtml md-converter (val $draft))))))
|
||||
|
||||
; when the publish button is clicked, compute the hash of the entered text and
|
||||
; provided session key and assign to the field session-value
|
||||
(.click ($ :#publish-button)
|
||||
(fn [e]
|
||||
(val ($ :#session-value)
|
||||
(lib/hash #(.charCodeAt % 0) (str (val $draft) (val $session-key))))))
|
||||
(lib/hash #(.charCodeAt % 0) (str (val $draft) (val ($ :#session-key)))))))
|
||||
|
||||
@@ -48,6 +48,8 @@
|
||||
(html
|
||||
(javascript-tag "var CLOSURE_NO_DEPS = true;")
|
||||
(include-js "https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js")
|
||||
(include-js "/js/pagedown/Markdown.Converter.js")
|
||||
(include-js "/js/pagedown/Markdown.Sanitizer.js")
|
||||
(include-js "/cljs/main.js")))])))
|
||||
|
||||
(defn layout
|
||||
|
||||
@@ -26,8 +26,6 @@
|
||||
(def central-element
|
||||
(mixin
|
||||
:width (px page-width)
|
||||
:margin-top :5em
|
||||
:margin-bottom :10em
|
||||
:margin-left "auto"
|
||||
:margin-right "auto"))
|
||||
|
||||
@@ -130,6 +128,7 @@
|
||||
:margin :2em))
|
||||
(rule "article"
|
||||
central-element
|
||||
:margin-top :5em
|
||||
:font-family text-fonts
|
||||
:text-align :justify
|
||||
:font-size :1.2em
|
||||
@@ -153,8 +152,7 @@
|
||||
:font-family :Courier
|
||||
:font-size :1em
|
||||
:border :none
|
||||
:height :500px
|
||||
:margin-bottom :2em)
|
||||
:height :500px)
|
||||
(rule ".hidden"
|
||||
:display :none)
|
||||
(rule ".button"
|
||||
@@ -166,10 +164,13 @@
|
||||
:background background)
|
||||
(rule ".central-element"
|
||||
central-element)
|
||||
(rule "fieldset"
|
||||
:border :none)
|
||||
(rule "h1"
|
||||
:font-size :2em)
|
||||
(rule ".dashed-line"
|
||||
:border-bottom [:1px :dashed foreground-halftone]
|
||||
:margin-bottom :5em)
|
||||
:margin-top :3em
|
||||
:margin-bottom :3em)
|
||||
(rule "h1, h2, h3, h4"
|
||||
:font-family header-fonts))))
|
||||
|
||||
@@ -88,19 +88,16 @@
|
||||
; New Note Page
|
||||
(defpage "/new" {}
|
||||
(layout {:js true} (get-message :new-note)
|
||||
[:div.central-element
|
||||
[:article#preview " "]
|
||||
[:div#preview-start-line.dashed-line.hidden]
|
||||
[:div.central-element {:style "margin-bottom: 3em"}
|
||||
(form-to [:post "/post-note"]
|
||||
(hidden-field :session-key (create-session))
|
||||
(hidden-field {:id :session-value} :session-value)
|
||||
(text-area {:class :max-width} :draft (get-message :loading))
|
||||
[:div#buttons.hidden
|
||||
(submit-button {:style "float: left"
|
||||
:class "button ui-border"
|
||||
:id :publish-button} (get-message :publish))
|
||||
[:button#preview-button.button.ui-border {:type :button
|
||||
:style "float: right"} (get-message :preview)]])]
|
||||
[:div#preview-start-line.dashed-line.hidden]
|
||||
[:article#preview]))
|
||||
[:fieldset#input-elems.hidden
|
||||
(submit-button {:class "button ui-border"
|
||||
:id :publish-button} (get-message :publish))])]))
|
||||
|
||||
; Displays the note
|
||||
(defpage "/:year/:month/:day/:title" {:keys [year month day title theme header-font text-font] :as params}
|
||||
|
||||
Reference in New Issue
Block a user