Browse Source

interactive preview added

master
Christian Mueller 14 years ago
parent
commit
e53fad2319
  1. 1
      messages
  2. 1332
      resources/public/js/pagedown/Markdown.Converter.js
  3. 108
      resources/public/js/pagedown/Markdown.Sanitizer.js
  4. 32
      src-cljs/main.cljs
  5. 2
      src/NoteHub/views/common.clj
  6. 11
      src/NoteHub/views/css_generator.clj
  7. 15
      src/NoteHub/views/pages.clj

1
messages

@ -9,6 +9,7 @@ status-500 = OMG, Server Exploded. @@ -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

1332
resources/public/js/pagedown/Markdown.Converter.js

File diff suppressed because it is too large Load Diff

108
resources/public/js/pagedown/Markdown.Sanitizer.js

@ -0,0 +1,108 @@ @@ -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;
}
})();

32
src-cljs/main.cljs

@ -7,13 +7,12 @@ @@ -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 @@ @@ -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)))))))

2
src/NoteHub/views/common.clj

@ -48,6 +48,8 @@ @@ -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

11
src/NoteHub/views/css_generator.clj

@ -26,8 +26,6 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -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))))

15
src/NoteHub/views/pages.clj

@ -88,19 +88,16 @@ @@ -88,19 +88,16 @@
; New Note Page
(defpage "/new" {}
(layout {:js true} (get-message :new-note)
[:div.central-element
[:article#preview "&nbsp;"]
[: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}

Loading…
Cancel
Save