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. 28
      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.
created-by = Created by [@chmllr](http://twitter.com/chmllr) created-by = Created by [@chmllr](http://twitter.com/chmllr)
loading = Loading... loading = Loading...
use-password = User password for editing:
preview = Preview preview = Preview
publish = Publish publish = Publish
published = Published 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 @@
(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;
}
})();

28
src-cljs/main.cljs

@ -7,13 +7,12 @@
; frequently used selectors ; frequently used selectors
(def $draft ($ :#draft)) (def $draft ($ :#draft))
(def $preview ($ :#preview)) (def $preview ($ :#preview))
(def $session-key ($ :#session-key)) (def $input-elems ($ :#input-elems))
(def $preview-start-line ($ :#preview-start-line)) (def $preview-start-line ($ :#preview-start-line))
(defn scroll-to ; Markdown Converter & Sanitizer instantiation
"scrolls to the given selector"
[$id] (def md-converter (Markdown/getSanitizingConverter))
(anim ($ :body) {:scrollTop ((js->clj (.offset $id)) "top")} 500))
; try to detect iOS ; try to detect iOS
(def ios-detected (.match (.-userAgent js/navigator) "(iPad|iPod|iPhone)")) (def ios-detected (.match (.-userAgent js/navigator) "(iPad|iPod|iPhone)"))
@ -28,26 +27,17 @@
(.focus $draft)))) (.focus $draft))))
; show the preview & publish buttons as soon as the user starts typing. ; 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] (fn [e]
(xhr [:post "/preview"]
{:draft (val $draft)}
(fn [json-map]
(let [m (js->clj (JSON/parse json-map))]
(do (do
(inner $preview (m "preview"))
(show $preview-start-line) (show $preview-start-line)
(scroll-to $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 ; when the publish button is clicked, compute the hash of the entered text and
; provided session key and assign to the field session-value ; provided session key and assign to the field session-value
(.click ($ :#publish-button) (.click ($ :#publish-button)
(fn [e] (fn [e]
(val ($ :#session-value) (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 @@
(html (html
(javascript-tag "var CLOSURE_NO_DEPS = true;") (javascript-tag "var CLOSURE_NO_DEPS = true;")
(include-js "https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js") (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")))]))) (include-js "/cljs/main.js")))])))
(defn layout (defn layout

11
src/NoteHub/views/css_generator.clj

@ -26,8 +26,6 @@
(def central-element (def central-element
(mixin (mixin
:width (px page-width) :width (px page-width)
:margin-top :5em
:margin-bottom :10em
:margin-left "auto" :margin-left "auto"
:margin-right "auto")) :margin-right "auto"))
@ -130,6 +128,7 @@
:margin :2em)) :margin :2em))
(rule "article" (rule "article"
central-element central-element
:margin-top :5em
:font-family text-fonts :font-family text-fonts
:text-align :justify :text-align :justify
:font-size :1.2em :font-size :1.2em
@ -153,8 +152,7 @@
:font-family :Courier :font-family :Courier
:font-size :1em :font-size :1em
:border :none :border :none
:height :500px :height :500px)
:margin-bottom :2em)
(rule ".hidden" (rule ".hidden"
:display :none) :display :none)
(rule ".button" (rule ".button"
@ -166,10 +164,13 @@
:background background) :background background)
(rule ".central-element" (rule ".central-element"
central-element) central-element)
(rule "fieldset"
:border :none)
(rule "h1" (rule "h1"
:font-size :2em) :font-size :2em)
(rule ".dashed-line" (rule ".dashed-line"
:border-bottom [:1px :dashed foreground-halftone] :border-bottom [:1px :dashed foreground-halftone]
:margin-bottom :5em) :margin-top :3em
:margin-bottom :3em)
(rule "h1, h2, h3, h4" (rule "h1, h2, h3, h4"
:font-family header-fonts)))) :font-family header-fonts))))

15
src/NoteHub/views/pages.clj

@ -88,19 +88,16 @@
; New Note Page ; New Note Page
(defpage "/new" {} (defpage "/new" {}
(layout {:js true} (get-message :new-note) (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"] (form-to [:post "/post-note"]
(hidden-field :session-key (create-session)) (hidden-field :session-key (create-session))
(hidden-field {:id :session-value} :session-value) (hidden-field {:id :session-value} :session-value)
(text-area {:class :max-width} :draft (get-message :loading)) (text-area {:class :max-width} :draft (get-message :loading))
[:div#buttons.hidden [:fieldset#input-elems.hidden
(submit-button {:style "float: left" (submit-button {:class "button ui-border"
:class "button ui-border" :id :publish-button} (get-message :publish))])]))
: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]))
; Displays the note ; Displays the note
(defpage "/:year/:month/:day/:title" {:keys [year month day title theme header-font text-font] :as params} (defpage "/:year/:month/:day/:title" {:keys [year month day title theme header-font text-font] :as params}

Loading…
Cancel
Save