ni_nitdoc: Rewritting of Github plugin for nitdoc
authorAlexandre Terrasa <alexandre@moz-code.org>
Tue, 27 Aug 2013 17:49:00 +0000 (13:49 -0400)
committerAlexandre Terrasa <alexandre@moz-code.org>
Tue, 27 Aug 2013 17:49:00 +0000 (13:49 -0400)
Signed-off-by: Alexandre Terrasa <alexandre@moz-code.org>

share/ni_nitdoc/resources/icons/github-icon-w.png
share/ni_nitdoc/scripts/Markdown.Converter.js [new file with mode: 0644]
share/ni_nitdoc/scripts/base64.js
share/ni_nitdoc/scripts/github.js
share/ni_nitdoc/styles/github.css
share/ni_nitdoc/styles/main.css
share/nitdoc/styles/main.css
src/ni_nitdoc.nit

index ba6a6b1..98ae072 100644 (file)
Binary files a/share/ni_nitdoc/resources/icons/github-icon-w.png and b/share/ni_nitdoc/resources/icons/github-icon-w.png differ
diff --git a/share/ni_nitdoc/scripts/Markdown.Converter.js b/share/ni_nitdoc/scripts/Markdown.Converter.js
new file mode 100644 (file)
index 0000000..1a60277
--- /dev/null
@@ -0,0 +1,1412 @@
+var Markdown;\r
+\r
+if (typeof exports === "object" && typeof require === "function") // we're in a CommonJS (e.g. Node.js) module\r
+    Markdown = exports;\r
+else\r
+    Markdown = {};\r
+\r
+// The following text is included for historical reasons, but should\r
+// be taken with a pinch of salt; it's not all true anymore.\r
+\r
+//\r
+// Wherever possible, Showdown is a straight, line-by-line port\r
+// of the Perl version of Markdown.\r
+//\r
+// This is not a normal parser design; it's basically just a\r
+// series of string substitutions.  It's hard to read and\r
+// maintain this way,  but keeping Showdown close to the original\r
+// design makes it easier to port new features.\r
+//\r
+// More importantly, Showdown behaves like markdown.pl in most\r
+// edge cases.  So web applications can do client-side preview\r
+// in Javascript, and then build identical HTML on the server.\r
+//\r
+// This port needs the new RegExp functionality of ECMA 262,\r
+// 3rd Edition (i.e. Javascript 1.5).  Most modern web browsers\r
+// should do fine.  Even with the new regular expression features,\r
+// We do a lot of work to emulate Perl's regex functionality.\r
+// The tricky changes in this file mostly have the "attacklab:"\r
+// label.  Major or self-explanatory changes don't.\r
+//\r
+// Smart diff tools like Araxis Merge will be able to match up\r
+// this file with markdown.pl in a useful way.  A little tweaking\r
+// helps: in a copy of markdown.pl, replace "#" with "//" and\r
+// replace "$text" with "text".  Be sure to ignore whitespace\r
+// and line endings.\r
+//\r
+\r
+\r
+//\r
+// Usage:\r
+//\r
+//   var text = "Markdown *rocks*.";\r
+//\r
+//   var converter = new Markdown.Converter();\r
+//   var html = converter.makeHtml(text);\r
+//\r
+//   alert(html);\r
+//\r
+// Note: move the sample code to the bottom of this\r
+// file before uncommenting it.\r
+//\r
+\r
+(function () {\r
+\r
+    function identity(x) { return x; }\r
+    function returnFalse(x) { return false; }\r
+\r
+    function HookCollection() { }\r
+\r
+    HookCollection.prototype = {\r
+\r
+        chain: function (hookname, func) {\r
+            var original = this[hookname];\r
+            if (!original)\r
+                throw new Error("unknown hook " + hookname);\r
+\r
+            if (original === identity)\r
+                this[hookname] = func;\r
+            else\r
+                this[hookname] = function (text) {\r
+                    var args = Array.prototype.slice.call(arguments, 0);\r
+                    args[0] = original.apply(null, args);\r
+                    return func.apply(null, args);\r
+                };\r
+        },\r
+        set: function (hookname, func) {\r
+            if (!this[hookname])\r
+                throw new Error("unknown hook " + hookname);\r
+            this[hookname] = func;\r
+        },\r
+        addNoop: function (hookname) {\r
+            this[hookname] = identity;\r
+        },\r
+        addFalse: function (hookname) {\r
+            this[hookname] = returnFalse;\r
+        }\r
+    };\r
+\r
+    Markdown.HookCollection = HookCollection;\r
+\r
+    // g_urls and g_titles allow arbitrary user-entered strings as keys. This\r
+    // caused an exception (and hence stopped the rendering) when the user entered\r
+    // e.g. [push] or [__proto__]. Adding a prefix to the actual key prevents this\r
+    // (since no builtin property starts with "s_"). See\r
+    // http://meta.stackoverflow.com/questions/64655/strange-wmd-bug\r
+    // (granted, switching from Array() to Object() alone would have left only __proto__\r
+    // to be a problem)\r
+    function SaveHash() { }\r
+    SaveHash.prototype = {\r
+        set: function (key, value) {\r
+            this["s_" + key] = value;\r
+        },\r
+        get: function (key) {\r
+            return this["s_" + key];\r
+        }\r
+    };\r
+\r
+    Markdown.Converter = function () {\r
+        var pluginHooks = this.hooks = new HookCollection();\r
+        \r
+        // given a URL that was encountered by itself (without markup), should return the link text that's to be given to this link\r
+        pluginHooks.addNoop("plainLinkText");\r
+        \r
+        // called with the orignal text as given to makeHtml. The result of this plugin hook is the actual markdown source that will be cooked\r
+        pluginHooks.addNoop("preConversion");\r
+        \r
+        // called with the text once all normalizations have been completed (tabs to spaces, line endings, etc.), but before any conversions have\r
+        pluginHooks.addNoop("postNormalization");\r
+        \r
+        // Called with the text before / after creating block elements like code blocks and lists. Note that this is called recursively\r
+        // with inner content, e.g. it's called with the full text, and then only with the content of a blockquote. The inner\r
+        // call will receive outdented text.\r
+        pluginHooks.addNoop("preBlockGamut");\r
+        pluginHooks.addNoop("postBlockGamut");\r
+        \r
+        // called with the text of a single block element before / after the span-level conversions (bold, code spans, etc.) have been made\r
+        pluginHooks.addNoop("preSpanGamut");\r
+        pluginHooks.addNoop("postSpanGamut");\r
+        \r
+        // called with the final cooked HTML code. The result of this plugin hook is the actual output of makeHtml\r
+        pluginHooks.addNoop("postConversion");\r
+\r
+        //\r
+        // Private state of the converter instance:\r
+        //\r
+\r
+        // Global hashes, used by various utility routines\r
+        var g_urls;\r
+        var g_titles;\r
+        var g_html_blocks;\r
+\r
+        // Used to track when we're inside an ordered or unordered list\r
+        // (see _ProcessListItems() for details):\r
+        var g_list_level;\r
+\r
+        this.makeHtml = function (text) {\r
+\r
+            //\r
+            // Main function. The order in which other subs are called here is\r
+            // essential. Link and image substitutions need to happen before\r
+            // _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the <a>\r
+            // and <img> tags get encoded.\r
+            //\r
+\r
+            // This will only happen if makeHtml on the same converter instance is called from a plugin hook.\r
+            // Don't do that.\r
+            if (g_urls)\r
+                throw new Error("Recursive call to converter.makeHtml");\r
+        \r
+            // Create the private state objects.\r
+            g_urls = new SaveHash();\r
+            g_titles = new SaveHash();\r
+            g_html_blocks = [];\r
+            g_list_level = 0;\r
+\r
+            text = pluginHooks.preConversion(text);\r
+\r
+            // attacklab: Replace ~ with ~T\r
+            // This lets us use tilde as an escape char to avoid md5 hashes\r
+            // The choice of character is arbitray; anything that isn't\r
+            // magic in Markdown will work.\r
+            text = text.replace(/~/g, "~T");\r
+\r
+            // attacklab: Replace $ with ~D\r
+            // RegExp interprets $ as a special character\r
+            // when it's in a replacement string\r
+            text = text.replace(/\$/g, "~D");\r
+\r
+            // Standardize line endings\r
+            text = text.replace(/\r\n/g, "\n"); // DOS to Unix\r
+            text = text.replace(/\r/g, "\n"); // Mac to Unix\r
+\r
+            // Make sure text begins and ends with a couple of newlines:\r
+            text = "\n\n" + text + "\n\n";\r
+\r
+            // Convert all tabs to spaces.\r
+            text = _Detab(text);\r
+\r
+            // Strip any lines consisting only of spaces and tabs.\r
+            // This makes subsequent regexen easier to write, because we can\r
+            // match consecutive blank lines with /\n+/ instead of something\r
+            // contorted like /[ \t]*\n+/ .\r
+            text = text.replace(/^[ \t]+$/mg, "");\r
+            \r
+            text = pluginHooks.postNormalization(text);\r
+\r
+            // Turn block-level HTML blocks into hash entries\r
+            text = _HashHTMLBlocks(text);\r
+\r
+            // Strip link definitions, store in hashes.\r
+            text = _StripLinkDefinitions(text);\r
+\r
+            text = _RunBlockGamut(text);\r
+\r
+            text = _UnescapeSpecialChars(text);\r
+\r
+            // attacklab: Restore dollar signs\r
+            text = text.replace(/~D/g, "$$");\r
+\r
+            // attacklab: Restore tildes\r
+            text = text.replace(/~T/g, "~");\r
+\r
+            text = pluginHooks.postConversion(text);\r
+\r
+            g_html_blocks = g_titles = g_urls = null;\r
+\r
+            return text;\r
+        };\r
+\r
+        function _StripLinkDefinitions(text) {\r
+            //\r
+            // Strips link definitions from text, stores the URLs and titles in\r
+            // hash references.\r
+            //\r
+\r
+            // Link defs are in the form: ^[id]: url "optional title"\r
+\r
+            /*\r
+            text = text.replace(/\r
+                ^[ ]{0,3}\[(.+)\]:  // id = $1  attacklab: g_tab_width - 1\r
+                [ \t]*\r
+                \n?                 // maybe *one* newline\r
+                [ \t]*\r
+                <?(\S+?)>?          // url = $2\r
+                (?=\s|$)            // lookahead for whitespace instead of the lookbehind removed below\r
+                [ \t]*\r
+                \n?                 // maybe one newline\r
+                [ \t]*\r
+                (                   // (potential) title = $3\r
+                    (\n*)           // any lines skipped = $4 attacklab: lookbehind removed\r
+                    [ \t]+\r
+                    ["(]\r
+                    (.+?)           // title = $5\r
+                    [")]\r
+                    [ \t]*\r
+                )?                  // title is optional\r
+                (?:\n+|$)\r
+            /gm, function(){...});\r
+            */\r
+\r
+            text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?(?=\s|$)[ \t]*\n?[ \t]*((\n*)["(](.+?)[")][ \t]*)?(?:\n+)/gm,\r
+                function (wholeMatch, m1, m2, m3, m4, m5) {\r
+                    m1 = m1.toLowerCase();\r
+                    g_urls.set(m1, _EncodeAmpsAndAngles(m2));  // Link IDs are case-insensitive\r
+                    if (m4) {\r
+                        // Oops, found blank lines, so it's not a title.\r
+                        // Put back the parenthetical statement we stole.\r
+                        return m3;\r
+                    } else if (m5) {\r
+                        g_titles.set(m1, m5.replace(/"/g, "&quot;"));\r
+                    }\r
+\r
+                    // Completely remove the definition from the text\r
+                    return "";\r
+                }\r
+            );\r
+\r
+            return text;\r
+        }\r
+\r
+        function _HashHTMLBlocks(text) {\r
+\r
+            // Hashify HTML blocks:\r
+            // We only want to do this for block-level HTML tags, such as headers,\r
+            // lists, and tables. That's because we still want to wrap <p>s around\r
+            // "paragraphs" that are wrapped in non-block-level tags, such as anchors,\r
+            // phrase emphasis, and spans. The list of tags we're looking for is\r
+            // hard-coded:\r
+            var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del"\r
+            var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math"\r
+\r
+            // First, look for nested blocks, e.g.:\r
+            //   <div>\r
+            //     <div>\r
+            //     tags for inner block must be indented.\r
+            //     </div>\r
+            //   </div>\r
+            //\r
+            // The outermost tags must start at the left margin for this to match, and\r
+            // the inner nested divs must be indented.\r
+            // We need to do this before the next, more liberal match, because the next\r
+            // match will start at the first `<div>` and stop at the first `</div>`.\r
+\r
+            // attacklab: This regex can be expensive when it fails.\r
+\r
+            /*\r
+            text = text.replace(/\r
+                (                       // save in $1\r
+                    ^                   // start of line  (with /m)\r
+                    <($block_tags_a)    // start tag = $2\r
+                    \b                  // word break\r
+                                        // attacklab: hack around khtml/pcre bug...\r
+                    [^\r]*?\n           // any number of lines, minimally matching\r
+                    </\2>               // the matching end tag\r
+                    [ \t]*              // trailing spaces/tabs\r
+                    (?=\n+)             // followed by a newline\r
+                )                       // attacklab: there are sentinel newlines at end of document\r
+            /gm,function(){...}};\r
+            */\r
+            text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm, hashElement);\r
+\r
+            //\r
+            // Now match more liberally, simply from `\n<tag>` to `</tag>\n`\r
+            //\r
+\r
+            /*\r
+            text = text.replace(/\r
+                (                       // save in $1\r
+                    ^                   // start of line  (with /m)\r
+                    <($block_tags_b)    // start tag = $2\r
+                    \b                  // word break\r
+                                        // attacklab: hack around khtml/pcre bug...\r
+                    [^\r]*?             // any number of lines, minimally matching\r
+                    .*</\2>             // the matching end tag\r
+                    [ \t]*              // trailing spaces/tabs\r
+                    (?=\n+)             // followed by a newline\r
+                )                       // attacklab: there are sentinel newlines at end of document\r
+            /gm,function(){...}};\r
+            */\r
+            text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm, hashElement);\r
+\r
+            // Special case just for <hr />. It was easier to make a special case than\r
+            // to make the other regex more complicated.  \r
+\r
+            /*\r
+            text = text.replace(/\r
+                \n                  // Starting after a blank line\r
+                [ ]{0,3}\r
+                (                   // save in $1\r
+                    (<(hr)          // start tag = $2\r
+                        \b          // word break\r
+                        ([^<>])*?\r
+                    \/?>)           // the matching end tag\r
+                    [ \t]*\r
+                    (?=\n{2,})      // followed by a blank line\r
+                )\r
+            /g,hashElement);\r
+            */\r
+            text = text.replace(/\n[ ]{0,3}((<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g, hashElement);\r
+\r
+            // Special case for standalone HTML comments:\r
+\r
+            /*\r
+            text = text.replace(/\r
+                \n\n                                            // Starting after a blank line\r
+                [ ]{0,3}                                        // attacklab: g_tab_width - 1\r
+                (                                               // save in $1\r
+                    <!\r
+                    (--(?:|(?:[^>-]|-[^>])(?:[^-]|-[^-])*)--)   // see http://www.w3.org/TR/html-markup/syntax.html#comments and http://meta.stackoverflow.com/q/95256\r
+                    >\r
+                    [ \t]*\r
+                    (?=\n{2,})                                  // followed by a blank line\r
+                )\r
+            /g,hashElement);\r
+            */\r
+            text = text.replace(/\n\n[ ]{0,3}(<!(--(?:|(?:[^>-]|-[^>])(?:[^-]|-[^-])*)--)>[ \t]*(?=\n{2,}))/g, hashElement);\r
+\r
+            // PHP and ASP-style processor instructions (<?...?> and <%...%>)\r
+\r
+            /*\r
+            text = text.replace(/\r
+                (?:\r
+                    \n\n            // Starting after a blank line\r
+                )\r
+                (                   // save in $1\r
+                    [ ]{0,3}        // attacklab: g_tab_width - 1\r
+                    (?:\r
+                        <([?%])     // $2\r
+                        [^\r]*?\r
+                        \2>\r
+                    )\r
+                    [ \t]*\r
+                    (?=\n{2,})      // followed by a blank line\r
+                )\r
+            /g,hashElement);\r
+            */\r
+            text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g, hashElement);\r
+\r
+            return text;\r
+        }\r
+\r
+        function hashElement(wholeMatch, m1) {\r
+            var blockText = m1;\r
+\r
+            // Undo double lines\r
+            blockText = blockText.replace(/^\n+/, "");\r
+\r
+            // strip trailing blank lines\r
+            blockText = blockText.replace(/\n+$/g, "");\r
+\r
+            // Replace the element text with a marker ("~KxK" where x is its key)\r
+            blockText = "\n\n~K" + (g_html_blocks.push(blockText) - 1) + "K\n\n";\r
+\r
+            return blockText;\r
+        }\r
+        \r
+        var blockGamutHookCallback = function (t) { return _RunBlockGamut(t); }\r
+\r
+        function _RunBlockGamut(text, doNotUnhash) {\r
+            //\r
+            // These are all the transformations that form block-level\r
+            // tags like paragraphs, headers, and list items.\r
+            //\r
+            \r
+            text = pluginHooks.preBlockGamut(text, blockGamutHookCallback);\r
+            \r
+            text = _DoHeaders(text);\r
+\r
+            // Do Horizontal Rules:\r
+            var replacement = "<hr />\n";\r
+            text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm, replacement);\r
+            text = text.replace(/^[ ]{0,2}([ ]?-[ ]?){3,}[ \t]*$/gm, replacement);\r
+            text = text.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm, replacement);\r
+\r
+            text = _DoLists(text);\r
+            text = _DoCodeBlocks(text);\r
+            text = _DoBlockQuotes(text);\r
+            \r
+            text = pluginHooks.postBlockGamut(text, blockGamutHookCallback);\r
+\r
+            // We already ran _HashHTMLBlocks() before, in Markdown(), but that\r
+            // was to escape raw HTML in the original Markdown source. This time,\r
+            // we're escaping the markup we've just created, so that we don't wrap\r
+            // <p> tags around block-level tags.\r
+            text = _HashHTMLBlocks(text);\r
+            text = _FormParagraphs(text, doNotUnhash);\r
+\r
+            return text;\r
+        }\r
+\r
+        function _RunSpanGamut(text) {\r
+            //\r
+            // These are all the transformations that occur *within* block-level\r
+            // tags like paragraphs, headers, and list items.\r
+            //\r
+\r
+            text = pluginHooks.preSpanGamut(text);\r
+            \r
+            text = _DoCodeSpans(text);\r
+            text = _EscapeSpecialCharsWithinTagAttributes(text);\r
+            text = _EncodeBackslashEscapes(text);\r
+\r
+            // Process anchor and image tags. Images must come first,\r
+            // because ![foo][f] looks like an anchor.\r
+            text = _DoImages(text);\r
+            text = _DoAnchors(text);\r
+\r
+            // Make links out of things like `<http://example.com/>`\r
+            // Must come after _DoAnchors(), because you can use < and >\r
+            // delimiters in inline links like [this](<url>).\r
+            text = _DoAutoLinks(text);\r
+            \r
+            text = text.replace(/~P/g, "://"); // put in place to prevent autolinking; reset now\r
+            \r
+            text = _EncodeAmpsAndAngles(text);\r
+            text = _DoItalicsAndBold(text);\r
+\r
+            // Do hard breaks:\r
+            text = text.replace(/  +\n/g, " <br>\n");\r
+            \r
+            text = pluginHooks.postSpanGamut(text);\r
+\r
+            return text;\r
+        }\r
+\r
+        function _EscapeSpecialCharsWithinTagAttributes(text) {\r
+            //\r
+            // Within tags -- meaning between < and > -- encode [\ ` * _] so they\r
+            // don't conflict with their use in Markdown for code, italics and strong.\r
+            //\r
+\r
+            // Build a regex to find HTML tags and comments.  See Friedl's \r
+            // "Mastering Regular Expressions", 2nd Ed., pp. 200-201.\r
+\r
+            // SE: changed the comment part of the regex\r
+\r
+            var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--(?:|(?:[^>-]|-[^>])(?:[^-]|-[^-])*)--)>)/gi;\r
+\r
+            text = text.replace(regex, function (wholeMatch) {\r
+                var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g, "$1`");\r
+                tag = escapeCharacters(tag, wholeMatch.charAt(1) == "!" ? "\\`*_/" : "\\`*_"); // also escape slashes in comments to prevent autolinking there -- http://meta.stackoverflow.com/questions/95987\r
+                return tag;\r
+            });\r
+\r
+            return text;\r
+        }\r
+\r
+        function _DoAnchors(text) {\r
+            //\r
+            // Turn Markdown link shortcuts into XHTML <a> tags.\r
+            //\r
+            //\r
+            // First, handle reference-style links: [link text] [id]\r
+            //\r
+\r
+            /*\r
+            text = text.replace(/\r
+                (                           // wrap whole match in $1\r
+                    \[\r
+                    (\r
+                        (?:\r
+                            \[[^\]]*\]      // allow brackets nested one level\r
+                            |\r
+                            [^\[]           // or anything else\r
+                        )*\r
+                    )\r
+                    \]\r
+\r
+                    [ ]?                    // one optional space\r
+                    (?:\n[ ]*)?             // one optional newline followed by spaces\r
+\r
+                    \[\r
+                    (.*?)                   // id = $3\r
+                    \]\r
+                )\r
+                ()()()()                    // pad remaining backreferences\r
+            /g, writeAnchorTag);\r
+            */\r
+            text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeAnchorTag);\r
+\r
+            //\r
+            // Next, inline-style links: [link text](url "optional title")\r
+            //\r
+\r
+            /*\r
+            text = text.replace(/\r
+                (                           // wrap whole match in $1\r
+                    \[\r
+                    (\r
+                        (?:\r
+                            \[[^\]]*\]      // allow brackets nested one level\r
+                            |\r
+                            [^\[\]]         // or anything else\r
+                        )*\r
+                    )\r
+                    \]\r
+                    \(                      // literal paren\r
+                    [ \t]*\r
+                    ()                      // no id, so leave $3 empty\r
+                    <?(                     // href = $4\r
+                        (?:\r
+                            \([^)]*\)       // allow one level of (correctly nested) parens (think MSDN)\r
+                            |\r
+                            [^()\s]\r
+                        )*?\r
+                    )>?                \r
+                    [ \t]*\r
+                    (                       // $5\r
+                        (['"])              // quote char = $6\r
+                        (.*?)               // Title = $7\r
+                        \6                  // matching quote\r
+                        [ \t]*              // ignore any spaces/tabs between closing quote and )\r
+                    )?                      // title is optional\r
+                    \)\r
+                )\r
+            /g, writeAnchorTag);\r
+            */\r
+\r
+            text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?((?:\([^)]*\)|[^()\s])*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeAnchorTag);\r
+\r
+            //\r
+            // Last, handle reference-style shortcuts: [link text]\r
+            // These must come last in case you've also got [link test][1]\r
+            // or [link test](/foo)\r
+            //\r
+\r
+            /*\r
+            text = text.replace(/\r
+                (                   // wrap whole match in $1\r
+                    \[\r
+                    ([^\[\]]+)      // link text = $2; can't contain '[' or ']'\r
+                    \]\r
+                )\r
+                ()()()()()          // pad rest of backreferences\r
+            /g, writeAnchorTag);\r
+            */\r
+            text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag);\r
+\r
+            return text;\r
+        }\r
+\r
+        function writeAnchorTag(wholeMatch, m1, m2, m3, m4, m5, m6, m7) {\r
+            if (m7 == undefined) m7 = "";\r
+            var whole_match = m1;\r
+            var link_text = m2.replace(/:\/\//g, "~P"); // to prevent auto-linking withing the link. will be converted back after the auto-linker runs\r
+            var link_id = m3.toLowerCase();\r
+            var url = m4;\r
+            var title = m7;\r
+\r
+            if (url == "") {\r
+                if (link_id == "") {\r
+                    // lower-case and turn embedded newlines into spaces\r
+                    link_id = link_text.toLowerCase().replace(/ ?\n/g, " ");\r
+                }\r
+                url = "#" + link_id;\r
+\r
+                if (g_urls.get(link_id) != undefined) {\r
+                    url = g_urls.get(link_id);\r
+                    if (g_titles.get(link_id) != undefined) {\r
+                        title = g_titles.get(link_id);\r
+                    }\r
+                }\r
+                else {\r
+                    if (whole_match.search(/\(\s*\)$/m) > -1) {\r
+                        // Special case for explicit empty url\r
+                        url = "";\r
+                    } else {\r
+                        return whole_match;\r
+                    }\r
+                }\r
+            }\r
+            url = encodeProblemUrlChars(url);\r
+            url = escapeCharacters(url, "*_");\r
+            var result = "<a href=\"" + url + "\"";\r
+\r
+            if (title != "") {\r
+                title = attributeEncode(title);\r
+                title = escapeCharacters(title, "*_");\r
+                result += " title=\"" + title + "\"";\r
+            }\r
+\r
+            result += ">" + link_text + "</a>";\r
+\r
+            return result;\r
+        }\r
+\r
+        function _DoImages(text) {\r
+            //\r
+            // Turn Markdown image shortcuts into <img> tags.\r
+            //\r
+\r
+            //\r
+            // First, handle reference-style labeled images: ![alt text][id]\r
+            //\r
+\r
+            /*\r
+            text = text.replace(/\r
+                (                   // wrap whole match in $1\r
+                    !\[\r
+                    (.*?)           // alt text = $2\r
+                    \]\r
+\r
+                    [ ]?            // one optional space\r
+                    (?:\n[ ]*)?     // one optional newline followed by spaces\r
+\r
+                    \[\r
+                    (.*?)           // id = $3\r
+                    \]\r
+                )\r
+                ()()()()            // pad rest of backreferences\r
+            /g, writeImageTag);\r
+            */\r
+            text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeImageTag);\r
+\r
+            //\r
+            // Next, handle inline images:  ![alt text](url "optional title")\r
+            // Don't forget: encode * and _\r
+\r
+            /*\r
+            text = text.replace(/\r
+                (                   // wrap whole match in $1\r
+                    !\[\r
+                    (.*?)           // alt text = $2\r
+                    \]\r
+                    \s?             // One optional whitespace character\r
+                    \(              // literal paren\r
+                    [ \t]*\r
+                    ()              // no id, so leave $3 empty\r
+                    <?(\S+?)>?      // src url = $4\r
+                    [ \t]*\r
+                    (               // $5\r
+                        (['"])      // quote char = $6\r
+                        (.*?)       // title = $7\r
+                        \6          // matching quote\r
+                        [ \t]*\r
+                    )?              // title is optional\r
+                    \)\r
+                )\r
+            /g, writeImageTag);\r
+            */\r
+            text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()<?(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeImageTag);\r
+\r
+            return text;\r
+        }\r
+        \r
+        function attributeEncode(text) {\r
+            // unconditionally replace angle brackets here -- what ends up in an attribute (e.g. alt or title)\r
+            // never makes sense to have verbatim HTML in it (and the sanitizer would totally break it)\r
+            return text.replace(/>/g, "&gt;").replace(/</g, "&lt;").replace(/"/g, "&quot;");\r
+        }\r
+\r
+        function writeImageTag(wholeMatch, m1, m2, m3, m4, m5, m6, m7) {\r
+            var whole_match = m1;\r
+            var alt_text = m2;\r
+            var link_id = m3.toLowerCase();\r
+            var url = m4;\r
+            var title = m7;\r
+\r
+            if (!title) title = "";\r
+\r
+            if (url == "") {\r
+                if (link_id == "") {\r
+                    // lower-case and turn embedded newlines into spaces\r
+                    link_id = alt_text.toLowerCase().replace(/ ?\n/g, " ");\r
+                }\r
+                url = "#" + link_id;\r
+\r
+                if (g_urls.get(link_id) != undefined) {\r
+                    url = g_urls.get(link_id);\r
+                    if (g_titles.get(link_id) != undefined) {\r
+                        title = g_titles.get(link_id);\r
+                    }\r
+                }\r
+                else {\r
+                    return whole_match;\r
+                }\r
+            }\r
+            \r
+            alt_text = escapeCharacters(attributeEncode(alt_text), "*_[]()");\r
+            url = escapeCharacters(url, "*_");\r
+            var result = "<img src=\"" + url + "\" alt=\"" + alt_text + "\"";\r
+\r
+            // attacklab: Markdown.pl adds empty title attributes to images.\r
+            // Replicate this bug.\r
+\r
+            //if (title != "") {\r
+            title = attributeEncode(title);\r
+            title = escapeCharacters(title, "*_");\r
+            result += " title=\"" + title + "\"";\r
+            //}\r
+\r
+            result += " />";\r
+\r
+            return result;\r
+        }\r
+\r
+        function _DoHeaders(text) {\r
+\r
+            // Setext-style headers:\r
+            //  Header 1\r
+            //  ========\r
+            //  \r
+            //  Header 2\r
+            //  --------\r
+            //\r
+            text = text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm,\r
+                function (wholeMatch, m1) { return "<h1>" + _RunSpanGamut(m1) + "</h1>\n\n"; }\r
+            );\r
+\r
+            text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,\r
+                function (matchFound, m1) { return "<h2>" + _RunSpanGamut(m1) + "</h2>\n\n"; }\r
+            );\r
+\r
+            // atx-style headers:\r
+            //  # Header 1\r
+            //  ## Header 2\r
+            //  ## Header 2 with closing hashes ##\r
+            //  ...\r
+            //  ###### Header 6\r
+            //\r
+\r
+            /*\r
+            text = text.replace(/\r
+                ^(\#{1,6})      // $1 = string of #'s\r
+                [ \t]*\r
+                (.+?)           // $2 = Header text\r
+                [ \t]*\r
+                \#*             // optional closing #'s (not counted)\r
+                \n+\r
+            /gm, function() {...});\r
+            */\r
+\r
+            text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,\r
+                function (wholeMatch, m1, m2) {\r
+                    var h_level = m1.length;\r
+                    return "<h" + h_level + ">" + _RunSpanGamut(m2) + "</h" + h_level + ">\n\n";\r
+                }\r
+            );\r
+\r
+            return text;\r
+        }\r
+\r
+        function _DoLists(text, isInsideParagraphlessListItem) {\r
+            //\r
+            // Form HTML ordered (numbered) and unordered (bulleted) lists.\r
+            //\r
+\r
+            // attacklab: add sentinel to hack around khtml/safari bug:\r
+            // http://bugs.webkit.org/show_bug.cgi?id=11231\r
+            text += "~0";\r
+\r
+            // Re-usable pattern to match any entirel ul or ol list:\r
+\r
+            /*\r
+            var whole_list = /\r
+                (                                   // $1 = whole list\r
+                    (                               // $2\r
+                        [ ]{0,3}                    // attacklab: g_tab_width - 1\r
+                        ([*+-]|\d+[.])              // $3 = first list item marker\r
+                        [ \t]+\r
+                    )\r
+                    [^\r]+?\r
+                    (                               // $4\r
+                        ~0                          // sentinel for workaround; should be $\r
+                        |\r
+                        \n{2,}\r
+                        (?=\S)\r
+                        (?!                         // Negative lookahead for another list item marker\r
+                            [ \t]*\r
+                            (?:[*+-]|\d+[.])[ \t]+\r
+                        )\r
+                    )\r
+                )\r
+            /g\r
+            */\r
+            var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;\r
+\r
+            if (g_list_level) {\r
+                text = text.replace(whole_list, function (wholeMatch, m1, m2) {\r
+                    var list = m1;\r
+                    var list_type = (m2.search(/[*+-]/g) > -1) ? "ul" : "ol";\r
+\r
+                    var result = _ProcessListItems(list, list_type, isInsideParagraphlessListItem);\r
+\r
+                    // Trim any trailing whitespace, to put the closing `</$list_type>`\r
+                    // up on the preceding line, to get it past the current stupid\r
+                    // HTML block parser. This is a hack to work around the terrible\r
+                    // hack that is the HTML block parser.\r
+                    result = result.replace(/\s+$/, "");\r
+                    result = "<" + list_type + ">" + result + "</" + list_type + ">\n";\r
+                    return result;\r
+                });\r
+            } else {\r
+                whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;\r
+                text = text.replace(whole_list, function (wholeMatch, m1, m2, m3) {\r
+                    var runup = m1;\r
+                    var list = m2;\r
+\r
+                    var list_type = (m3.search(/[*+-]/g) > -1) ? "ul" : "ol";\r
+                    var result = _ProcessListItems(list, list_type);\r
+                    result = runup + "<" + list_type + ">\n" + result + "</" + list_type + ">\n";\r
+                    return result;\r
+                });\r
+            }\r
+\r
+            // attacklab: strip sentinel\r
+            text = text.replace(/~0/, "");\r
+\r
+            return text;\r
+        }\r
+\r
+        var _listItemMarkers = { ol: "\\d+[.]", ul: "[*+-]" };\r
+\r
+        function _ProcessListItems(list_str, list_type, isInsideParagraphlessListItem) {\r
+            //\r
+            //  Process the contents of a single ordered or unordered list, splitting it\r
+            //  into individual list items.\r
+            //\r
+            //  list_type is either "ul" or "ol".\r
+\r
+            // The $g_list_level global keeps track of when we're inside a list.\r
+            // Each time we enter a list, we increment it; when we leave a list,\r
+            // we decrement. If it's zero, we're not in a list anymore.\r
+            //\r
+            // We do this because when we're not inside a list, we want to treat\r
+            // something like this:\r
+            //\r
+            //    I recommend upgrading to version\r
+            //    8. Oops, now this line is treated\r
+            //    as a sub-list.\r
+            //\r
+            // As a single paragraph, despite the fact that the second line starts\r
+            // with a digit-period-space sequence.\r
+            //\r
+            // Whereas when we're inside a list (or sub-list), that line will be\r
+            // treated as the start of a sub-list. What a kludge, huh? This is\r
+            // an aspect of Markdown's syntax that's hard to parse perfectly\r
+            // without resorting to mind-reading. Perhaps the solution is to\r
+            // change the syntax rules such that sub-lists must start with a\r
+            // starting cardinal number; e.g. "1." or "a.".\r
+\r
+            g_list_level++;\r
+\r
+            // trim trailing blank lines:\r
+            list_str = list_str.replace(/\n{2,}$/, "\n");\r
+\r
+            // attacklab: add sentinel to emulate \z\r
+            list_str += "~0";\r
+\r
+            // In the original attacklab showdown, list_type was not given to this function, and anything\r
+            // that matched /[*+-]|\d+[.]/ would just create the next <li>, causing this mismatch:\r
+            //\r
+            //  Markdown          rendered by WMD        rendered by MarkdownSharp\r
+            //  ------------------------------------------------------------------\r
+            //  1. first          1. first               1. first\r
+            //  2. second         2. second              2. second\r
+            //  - third           3. third                   * third\r
+            //\r
+            // We changed this to behave identical to MarkdownSharp. This is the constructed RegEx,\r
+            // with {MARKER} being one of \d+[.] or [*+-], depending on list_type:\r
+        \r
+            /*\r
+            list_str = list_str.replace(/\r
+                (^[ \t]*)                       // leading whitespace = $1\r
+                ({MARKER}) [ \t]+               // list marker = $2\r
+                ([^\r]+?                        // list item text   = $3\r
+                    (\n+)\r
+                )\r
+                (?=\r
+                    (~0 | \2 ({MARKER}) [ \t]+)\r
+                )\r
+            /gm, function(){...});\r
+            */\r
+\r
+            var marker = _listItemMarkers[list_type];\r
+            var re = new RegExp("(^[ \\t]*)(" + marker + ")[ \\t]+([^\\r]+?(\\n+))(?=(~0|\\1(" + marker + ")[ \\t]+))", "gm");\r
+            var last_item_had_a_double_newline = false;\r
+            list_str = list_str.replace(re,\r
+                function (wholeMatch, m1, m2, m3) {\r
+                    var item = m3;\r
+                    var leading_space = m1;\r
+                    var ends_with_double_newline = /\n\n$/.test(item);\r
+                    var contains_double_newline = ends_with_double_newline || item.search(/\n{2,}/) > -1;\r
+\r
+                    if (contains_double_newline || last_item_had_a_double_newline) {\r
+                        item = _RunBlockGamut(_Outdent(item), /* doNotUnhash = */true);\r
+                    }\r
+                    else {\r
+                        // Recursion for sub-lists:\r
+                        item = _DoLists(_Outdent(item), /* isInsideParagraphlessListItem= */ true);\r
+                        item = item.replace(/\n$/, ""); // chomp(item)\r
+                        if (!isInsideParagraphlessListItem) // only the outer-most item should run this, otherwise it's run multiple times for the inner ones\r
+                            item = _RunSpanGamut(item);\r
+                    }\r
+                    last_item_had_a_double_newline = ends_with_double_newline;\r
+                    return "<li>" + item + "</li>\n";\r
+                }\r
+            );\r
+\r
+            // attacklab: strip sentinel\r
+            list_str = list_str.replace(/~0/g, "");\r
+\r
+            g_list_level--;\r
+            return list_str;\r
+        }\r
+\r
+        function _DoCodeBlocks(text) {\r
+            //\r
+            //  Process Markdown `<pre><code>` blocks.\r
+            //  \r
+\r
+            /*\r
+            text = text.replace(/\r
+                (?:\n\n|^)\r
+                (                               // $1 = the code block -- one or more lines, starting with a space/tab\r
+                    (?:\r
+                        (?:[ ]{4}|\t)           // Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width\r
+                        .*\n+\r
+                    )+\r
+                )\r
+                (\n*[ ]{0,3}[^ \t\n]|(?=~0))    // attacklab: g_tab_width\r
+            /g ,function(){...});\r
+            */\r
+\r
+            // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug\r
+            text += "~0";\r
+\r
+            text = text.replace(/(?:\n\n|^\n?)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,\r
+                function (wholeMatch, m1, m2) {\r
+                    var codeblock = m1;\r
+                    var nextChar = m2;\r
+\r
+                    codeblock = _EncodeCode(_Outdent(codeblock));\r
+                    codeblock = _Detab(codeblock);\r
+                    codeblock = codeblock.replace(/^\n+/g, ""); // trim leading newlines\r
+                    codeblock = codeblock.replace(/\n+$/g, ""); // trim trailing whitespace\r
+\r
+                    codeblock = "<pre><code>" + codeblock + "\n</code></pre>";\r
+\r
+                    return "\n\n" + codeblock + "\n\n" + nextChar;\r
+                }\r
+            );\r
+\r
+            // attacklab: strip sentinel\r
+            text = text.replace(/~0/, "");\r
+\r
+            return text;\r
+        }\r
+\r
+        function hashBlock(text) {\r
+            text = text.replace(/(^\n+|\n+$)/g, "");\r
+            return "\n\n~K" + (g_html_blocks.push(text) - 1) + "K\n\n";\r
+        }\r
+\r
+        function _DoCodeSpans(text) {\r
+            //\r
+            // * Backtick quotes are used for <code></code> spans.\r
+            // \r
+            // * You can use multiple backticks as the delimiters if you want to\r
+            //   include literal backticks in the code span. So, this input:\r
+            //     \r
+            //      Just type ``foo `bar` baz`` at the prompt.\r
+            //     \r
+            //   Will translate to:\r
+            //     \r
+            //      <p>Just type <code>foo `bar` baz</code> at the prompt.</p>\r
+            //     \r
+            //   There's no arbitrary limit to the number of backticks you\r
+            //   can use as delimters. If you need three consecutive backticks\r
+            //   in your code, use four for delimiters, etc.\r
+            //\r
+            // * You can use spaces to get literal backticks at the edges:\r
+            //     \r
+            //      ... type `` `bar` `` ...\r
+            //     \r
+            //   Turns to:\r
+            //     \r
+            //      ... type <code>`bar`</code> ...\r
+            //\r
+\r
+            /*\r
+            text = text.replace(/\r
+                (^|[^\\])       // Character before opening ` can't be a backslash\r
+                (`+)            // $2 = Opening run of `\r
+                (               // $3 = The code block\r
+                    [^\r]*?\r
+                    [^`]        // attacklab: work around lack of lookbehind\r
+                )\r
+                \2              // Matching closer\r
+                (?!`)\r
+            /gm, function(){...});\r
+            */\r
+\r
+            text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,\r
+                function (wholeMatch, m1, m2, m3, m4) {\r
+                    var c = m3;\r
+                    c = c.replace(/^([ \t]*)/g, ""); // leading whitespace\r
+                    c = c.replace(/[ \t]*$/g, ""); // trailing whitespace\r
+                    c = _EncodeCode(c);\r
+                    c = c.replace(/:\/\//g, "~P"); // to prevent auto-linking. Not necessary in code *blocks*, but in code spans. Will be converted back after the auto-linker runs.\r
+                    return m1 + "<code>" + c + "</code>";\r
+                }\r
+            );\r
+\r
+            return text;\r
+        }\r
+\r
+        function _EncodeCode(text) {\r
+            //\r
+            // Encode/escape certain characters inside Markdown code runs.\r
+            // The point is that in code, these characters are literals,\r
+            // and lose their special Markdown meanings.\r
+            //\r
+            // Encode all ampersands; HTML entities are not\r
+            // entities within a Markdown code span.\r
+            text = text.replace(/&/g, "&amp;");\r
+\r
+            // Do the angle bracket song and dance:\r
+            text = text.replace(/</g, "&lt;");\r
+            text = text.replace(/>/g, "&gt;");\r
+\r
+            // Now, escape characters that are magic in Markdown:\r
+            text = escapeCharacters(text, "\*_{}[]\\", false);\r
+\r
+            // jj the line above breaks this:\r
+            //---\r
+\r
+            //* Item\r
+\r
+            //   1. Subitem\r
+\r
+            //            special char: *\r
+            //---\r
+\r
+            return text;\r
+        }\r
+\r
+        function _DoItalicsAndBold(text) {\r
+\r
+            // <strong> must go first:\r
+            text = text.replace(/([\W_]|^)(\*\*|__)(?=\S)([^\r]*?\S[\*_]*)\2([\W_]|$)/g,\r
+            "$1<strong>$3</strong>$4");\r
+\r
+            text = text.replace(/([\W_]|^)(\*|_)(?=\S)([^\r\*_]*?\S)\2([\W_]|$)/g,\r
+            "$1<em>$3</em>$4");\r
+\r
+            return text;\r
+        }\r
+\r
+        function _DoBlockQuotes(text) {\r
+\r
+            /*\r
+            text = text.replace(/\r
+                (                           // Wrap whole match in $1\r
+                    (\r
+                        ^[ \t]*>[ \t]?      // '>' at the start of a line\r
+                        .+\n                // rest of the first line\r
+                        (.+\n)*             // subsequent consecutive lines\r
+                        \n*                 // blanks\r
+                    )+\r
+                )\r
+            /gm, function(){...});\r
+            */\r
+\r
+            text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,\r
+                function (wholeMatch, m1) {\r
+                    var bq = m1;\r
+\r
+                    // attacklab: hack around Konqueror 3.5.4 bug:\r
+                    // "----------bug".replace(/^-/g,"") == "bug"\r
+\r
+                    bq = bq.replace(/^[ \t]*>[ \t]?/gm, "~0"); // trim one level of quoting\r
+\r
+                    // attacklab: clean up hack\r
+                    bq = bq.replace(/~0/g, "");\r
+\r
+                    bq = bq.replace(/^[ \t]+$/gm, "");     // trim whitespace-only lines\r
+                    bq = _RunBlockGamut(bq);             // recurse\r
+\r
+                    bq = bq.replace(/(^|\n)/g, "$1  ");\r
+                    // These leading spaces screw with <pre> content, so we need to fix that:\r
+                    bq = bq.replace(\r
+                            /(\s*<pre>[^\r]+?<\/pre>)/gm,\r
+                        function (wholeMatch, m1) {\r
+                            var pre = m1;\r
+                            // attacklab: hack around Konqueror 3.5.4 bug:\r
+                            pre = pre.replace(/^  /mg, "~0");\r
+                            pre = pre.replace(/~0/g, "");\r
+                            return pre;\r
+                        });\r
+\r
+                    return hashBlock("<blockquote>\n" + bq + "\n</blockquote>");\r
+                }\r
+            );\r
+            return text;\r
+        }\r
+\r
+        function _FormParagraphs(text, doNotUnhash) {\r
+            //\r
+            //  Params:\r
+            //    $text - string to process with html <p> tags\r
+            //\r
+\r
+            // Strip leading and trailing lines:\r
+            text = text.replace(/^\n+/g, "");\r
+            text = text.replace(/\n+$/g, "");\r
+\r
+            var grafs = text.split(/\n{2,}/g);\r
+            var grafsOut = [];\r
+            \r
+            var markerRe = /~K(\d+)K/;\r
+\r
+            //\r
+            // Wrap <p> tags.\r
+            //\r
+            var end = grafs.length;\r
+            for (var i = 0; i < end; i++) {\r
+                var str = grafs[i];\r
+\r
+                // if this is an HTML marker, copy it\r
+                if (markerRe.test(str)) {\r
+                    grafsOut.push(str);\r
+                }\r
+                else if (/\S/.test(str)) {\r
+                    str = _RunSpanGamut(str);\r
+                    str = str.replace(/^([ \t]*)/g, "<p>");\r
+                    str += "</p>"\r
+                    grafsOut.push(str);\r
+                }\r
+\r
+            }\r
+            //\r
+            // Unhashify HTML blocks\r
+            //\r
+            if (!doNotUnhash) {\r
+                end = grafsOut.length;\r
+                for (var i = 0; i < end; i++) {\r
+                    var foundAny = true;\r
+                    while (foundAny) { // we may need several runs, since the data may be nested\r
+                        foundAny = false;\r
+                        grafsOut[i] = grafsOut[i].replace(/~K(\d+)K/g, function (wholeMatch, id) {\r
+                            foundAny = true;\r
+                            return g_html_blocks[id];\r
+                        });\r
+                    }\r
+                }\r
+            }\r
+            return grafsOut.join("\n\n");\r
+        }\r
+\r
+        function _EncodeAmpsAndAngles(text) {\r
+            // Smart processing for ampersands and angle brackets that need to be encoded.\r
+\r
+            // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:\r
+            //   http://bumppo.net/projects/amputator/\r
+            text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g, "&amp;");\r
+\r
+            // Encode naked <'s\r
+            text = text.replace(/<(?![a-z\/?!]|~D)/gi, "&lt;");\r
+\r
+            return text;\r
+        }\r
+\r
+        function _EncodeBackslashEscapes(text) {\r
+            //\r
+            //   Parameter:  String.\r
+            //   Returns:    The string, with after processing the following backslash\r
+            //               escape sequences.\r
+            //\r
+\r
+            // attacklab: The polite way to do this is with the new\r
+            // escapeCharacters() function:\r
+            //\r
+            //     text = escapeCharacters(text,"\\",true);\r
+            //     text = escapeCharacters(text,"`*_{}[]()>#+-.!",true);\r
+            //\r
+            // ...but we're sidestepping its use of the (slow) RegExp constructor\r
+            // as an optimization for Firefox.  This function gets called a LOT.\r
+\r
+            text = text.replace(/\\(\\)/g, escapeCharacters_callback);\r
+            text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g, escapeCharacters_callback);\r
+            return text;\r
+        }\r
+\r
+        var charInsideUrl = "[-A-Z0-9+&@#/%?=~_|[\\]()!:,.;]",\r
+            charEndingUrl = "[-A-Z0-9+&@#/%=~_|[\\])]",\r
+            autoLinkRegex = new RegExp("(=\"|<)?\\b(https?|ftp)(://" + charInsideUrl + "*" + charEndingUrl + ")(?=$|\\W)", "gi"),\r
+            endCharRegex = new RegExp(charEndingUrl, "i");\r
+\r
+        function handleTrailingParens(wholeMatch, lookbehind, protocol, link) {\r
+            if (lookbehind)\r
+                return wholeMatch;\r
+            if (link.charAt(link.length - 1) !== ")")\r
+                return "<" + protocol + link + ">";\r
+            var parens = link.match(/[()]/g);\r
+            var level = 0;\r
+            for (var i = 0; i < parens.length; i++) {\r
+                if (parens[i] === "(") {\r
+                    if (level <= 0)\r
+                        level = 1;\r
+                    else\r
+                        level++;\r
+                }\r
+                else {\r
+                    level--;\r
+                }\r
+            }\r
+            var tail = "";\r
+            if (level < 0) {\r
+                var re = new RegExp("\\){1," + (-level) + "}$");\r
+                link = link.replace(re, function (trailingParens) {\r
+                    tail = trailingParens;\r
+                    return "";\r
+                });\r
+            }\r
+            if (tail) {\r
+                var lastChar = link.charAt(link.length - 1);\r
+                if (!endCharRegex.test(lastChar)) {\r
+                    tail = lastChar + tail;\r
+                    link = link.substr(0, link.length - 1);\r
+                }\r
+            }\r
+            return "<" + protocol + link + ">" + tail;\r
+        }\r
+        \r
+        function _DoAutoLinks(text) {\r
+\r
+            // note that at this point, all other URL in the text are already hyperlinked as <a href=""></a>\r
+            // *except* for the <http://www.foo.com> case\r
+\r
+            // automatically add < and > around unadorned raw hyperlinks\r
+            // must be preceded by a non-word character (and not by =" or <) and followed by non-word/EOF character\r
+            // simulating the lookbehind in a consuming way is okay here, since a URL can neither and with a " nor\r
+            // with a <, so there is no risk of overlapping matches.\r
+            text = text.replace(autoLinkRegex, handleTrailingParens);\r
+\r
+            //  autolink anything like <http://example.com>\r
+            \r
+            var replacer = function (wholematch, m1) { return "<a href=\"" + m1 + "\">" + pluginHooks.plainLinkText(m1) + "</a>"; }\r
+            text = text.replace(/<((https?|ftp):[^'">\s]+)>/gi, replacer);\r
+\r
+            // Email addresses: <address@domain.foo>\r
+            /*\r
+            text = text.replace(/\r
+                <\r
+                (?:mailto:)?\r
+                (\r
+                    [-.\w]+\r
+                    \@\r
+                    [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+\r
+                )\r
+                >\r
+            /gi, _DoAutoLinks_callback());\r
+            */\r
+\r
+            /* disabling email autolinking, since we don't do that on the server, either\r
+            text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,\r
+                function(wholeMatch,m1) {\r
+                    return _EncodeEmailAddress( _UnescapeSpecialChars(m1) );\r
+                }\r
+            );\r
+            */\r
+            return text;\r
+        }\r
+\r
+        function _UnescapeSpecialChars(text) {\r
+            //\r
+            // Swap back in all the special characters we've hidden.\r
+            //\r
+            text = text.replace(/~E(\d+)E/g,\r
+                function (wholeMatch, m1) {\r
+                    var charCodeToReplace = parseInt(m1);\r
+                    return String.fromCharCode(charCodeToReplace);\r
+                }\r
+            );\r
+            return text;\r
+        }\r
+\r
+        function _Outdent(text) {\r
+            //\r
+            // Remove one level of line-leading tabs or spaces\r
+            //\r
+\r
+            // attacklab: hack around Konqueror 3.5.4 bug:\r
+            // "----------bug".replace(/^-/g,"") == "bug"\r
+\r
+            text = text.replace(/^(\t|[ ]{1,4})/gm, "~0"); // attacklab: g_tab_width\r
+\r
+            // attacklab: clean up hack\r
+            text = text.replace(/~0/g, "")\r
+\r
+            return text;\r
+        }\r
+\r
+        function _Detab(text) {\r
+            if (!/\t/.test(text))\r
+                return text;\r
+\r
+            var spaces = ["    ", "   ", "  ", " "],\r
+            skew = 0,\r
+            v;\r
+\r
+            return text.replace(/[\n\t]/g, function (match, offset) {\r
+                if (match === "\n") {\r
+                    skew = offset + 1;\r
+                    return match;\r
+                }\r
+                v = (offset - skew) % 4;\r
+                skew = offset + 1;\r
+                return spaces[v];\r
+            });\r
+        }\r
+\r
+        //\r
+        //  attacklab: Utility functions\r
+        //\r
+\r
+        var _problemUrlChars = /(?:["'*()[\]:]|~D)/g;\r
+\r
+        // hex-encodes some unusual "problem" chars in URLs to avoid URL detection problems \r
+        function encodeProblemUrlChars(url) {\r
+            if (!url)\r
+                return "";\r
+\r
+            var len = url.length;\r
+\r
+            return url.replace(_problemUrlChars, function (match, offset) {\r
+                if (match == "~D") // escape for dollar\r
+                    return "%24";\r
+                if (match == ":") {\r
+                    if (offset == len - 1 || /[0-9\/]/.test(url.charAt(offset + 1)))\r
+                        return ":"\r
+                }\r
+                return "%" + match.charCodeAt(0).toString(16);\r
+            });\r
+        }\r
+\r
+\r
+        function escapeCharacters(text, charsToEscape, afterBackslash) {\r
+            // First we have to escape the escape characters so that\r
+            // we can build a character class out of them\r
+            var regexString = "([" + charsToEscape.replace(/([\[\]\\])/g, "\\$1") + "])";\r
+\r
+            if (afterBackslash) {\r
+                regexString = "\\\\" + regexString;\r
+            }\r
+\r
+            var regex = new RegExp(regexString, "g");\r
+            text = text.replace(regex, escapeCharacters_callback);\r
+\r
+            return text;\r
+        }\r
+\r
+\r
+        function escapeCharacters_callback(wholeMatch, m1) {\r
+            var charCodeToEscape = m1.charCodeAt(0);\r
+            return "~E" + charCodeToEscape + "E";\r
+        }\r
+\r
+    }; // end of the Markdown.Converter constructor\r
+\r
+})();\r
index 6b648b9..1c23d21 100644 (file)
-/*
-* Base64
-*/
-base64 = {};
-base64.PADCHAR = '=';
-base64.ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
-base64.getbyte64 = function(s,i) {
-    // This is oddly fast, except on Chrome/V8.
-    //  Minimal or no improvement in performance by using a
-    //   object with properties mapping chars to value (eg. 'A': 0)
-    var idx = base64.ALPHA.indexOf(s.charAt(i));
-    if (idx == -1) {
-    throw "Cannot decode base64";
-    }
-    return idx;
-}
+var Base64 = {
 
-base64.decode = function(s) {
-    // convert to string
-    s = "" + s;
-    var getbyte64 = base64.getbyte64;
-    var pads, i, b10;
-    var imax = s.length
-    if (imax == 0) {
-        return s;
-    }
-
-    if (imax % 4 != 0) {
-    throw "Cannot decode base64";
-    }
-
-    pads = 0
-    if (s.charAt(imax -1) == base64.PADCHAR) {
-        pads = 1;
-        if (s.charAt(imax -2) == base64.PADCHAR) {
-            pads = 2;
-        }
-        // either way, we want to ignore this last block
-        imax -= 4;
-    }
-
-    var x = [];
-    for (i = 0; i < imax; i += 4) {
-        b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) |
-            (getbyte64(s,i+2) << 6) | getbyte64(s,i+3);
-        x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff, b10 & 0xff));
-    }
-
-    switch (pads) {
-    case 1:
-        b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) | (getbyte64(s,i+2) << 6)
-        x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff));
-        break;
-    case 2:
-        b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12);
-        x.push(String.fromCharCode(b10 >> 16));
-        break;
-    }
-    return x.join('');
-}
+       // private property
+       _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
 
-base64.getbyte = function(s,i) {
-    var x = s.charCodeAt(i);
-    if (x > 255) {
-        throw "INVALID_CHARACTER_ERR: DOM Exception 5";
-    }
-    return x;
-}
+       // public method for encoding
+       encode : function (input) {
+               var output = "";
+               var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
+               var i = 0;
+
+               input = Base64._utf8_encode(input);
+
+               while (i < input.length) {
+
+                       chr1 = input.charCodeAt(i++);
+                       chr2 = input.charCodeAt(i++);
+                       chr3 = input.charCodeAt(i++);
+
+                       enc1 = chr1 >> 2;
+                       enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
+                       enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
+                       enc4 = chr3 & 63;
+
+                       if (isNaN(chr2)) {
+                               enc3 = enc4 = 64;
+                       } else if (isNaN(chr3)) {
+                               enc4 = 64;
+                       }
+
+                       output = output +
+                       this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
+                       this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
+
+               }
+
+               return output;
+       },
+
+       // public method for decoding
+       decode : function (input) {
+               var output = "";
+               var chr1, chr2, chr3;
+               var enc1, enc2, enc3, enc4;
+               var i = 0;
+
+               input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
+
+               while (i < input.length) {
+
+                       enc1 = this._keyStr.indexOf(input.charAt(i++));
+                       enc2 = this._keyStr.indexOf(input.charAt(i++));
+                       enc3 = this._keyStr.indexOf(input.charAt(i++));
+                       enc4 = this._keyStr.indexOf(input.charAt(i++));
+
+                       chr1 = (enc1 << 2) | (enc2 >> 4);
+                       chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
+                       chr3 = ((enc3 & 3) << 6) | enc4;
+
+                       output = output + String.fromCharCode(chr1);
 
+                       if (enc3 != 64) {
+                               output = output + String.fromCharCode(chr2);
+                       }
+                       if (enc4 != 64) {
+                               output = output + String.fromCharCode(chr3);
+                       }
 
-base64.encode = function(s) {
-    if (arguments.length != 1) {
-    throw "SyntaxError: Not enough arguments";
-    }
-    var padchar = base64.PADCHAR;
-    var alpha   = base64.ALPHA;
-    var getbyte = base64.getbyte;
-
-    var i, b10;
-    var x = [];
-
-    // convert to string
-    s = "" + s;
-
-    var imax = s.length - s.length % 3;
-
-    if (s.length == 0) {
-        return s;
-    }
-    for (i = 0; i < imax; i += 3) {
-        b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8) | getbyte(s,i+2);
-        x.push(alpha.charAt(b10 >> 18));
-        x.push(alpha.charAt((b10 >> 12) & 0x3F));
-        x.push(alpha.charAt((b10 >> 6) & 0x3f));
-        x.push(alpha.charAt(b10 & 0x3f));
-    }
-    switch (s.length - imax) {
-    case 1:
-        b10 = getbyte(s,i) << 16;
-        x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
-               padchar + padchar);
-        break;
-    case 2:
-        b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8);
-        x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
-               alpha.charAt((b10 >> 6) & 0x3f) + padchar);
-        break;
-    }
-    return x.join('');
+               }
+
+               output = Base64._utf8_decode(output);
+
+               return output;
+
+       },
+
+       // private method for UTF-8 encoding
+       _utf8_encode : function (string) {
+               string = string.replace(/\r\n/g,"\n");
+               var utftext = "";
+
+               for (var n = 0; n < string.length; n++) {
+
+                       var c = string.charCodeAt(n);
+
+                       if (c < 128) {
+                               utftext += String.fromCharCode(c);
+                       }
+                       else if((c > 127) && (c < 2048)) {
+                               utftext += String.fromCharCode((c >> 6) | 192);
+                               utftext += String.fromCharCode((c & 63) | 128);
+                       }
+                       else {
+                               utftext += String.fromCharCode((c >> 12) | 224);
+                               utftext += String.fromCharCode(((c >> 6) & 63) | 128);
+                               utftext += String.fromCharCode((c & 63) | 128);
+                       }
+
+               }
+
+               return utftext;
+       },
+
+       // private method for UTF-8 decoding
+       _utf8_decode : function (utftext) {
+               var string = "";
+               var i = 0;
+               var c = c1 = c2 = 0;
+
+               while ( i < utftext.length ) {
+
+                       c = utftext.charCodeAt(i);
+
+                       if (c < 128) {
+                               string += String.fromCharCode(c);
+                               i++;
+                       }
+                       else if((c > 191) && (c < 224)) {
+                               c2 = utftext.charCodeAt(i+1);
+                               string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
+                               i += 2;
+                       }
+                       else {
+                               c2 = utftext.charCodeAt(i+1);
+                               c3 = utftext.charCodeAt(i+2);
+                               string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
+                               i += 3;
+                       }
+
+               }
+
+               return string;
+       }
 }
+
index f8346a8..6ab4f39 100644 (file)
-// User\r
-var userB64 = null;\r
-var userName = "";\r
-var password = "";\r
-var sessionStarted = false;\r
-var editComment = 0;\r
-var currentfileContent = '';\r
-var originalFileContent = '';\r
-var addNewComment = false;\r
-var commentLineStart;\r
-var commentLineEnd;\r
-\r
-// SHA GitHub\r
-var shaLastCommit = "";\r
-var shaBaseTree;\r
-var shaNewTree;\r
-var shaNewCommit;\r
-var shaBlob;\r
-var shaMaster;\r
-var repoExist = false;\r
-var branchExist = false;\r
-var githubRepo;\r
-var loginProcess = false;\r
-var signedOff = '';\r
-var userEmail = '';\r
-var commitMessage = '';\r
-var numComment = '';\r
-var showcomment = false;\r
+$(document).ready(function() {\r
+       // set ui elements\r
+       ui = new GitHubUI();\r
+       ui.init();\r
+\r
+       // check cookie at startup\r
+       sessionCookie = new SessionCookie("nitdoc_github_session");\r
+       var session = sessionCookie.getSession();\r
+       //checkCookie()\r
+       if(session) {\r
+               githubAPI = new GitHubAPI(session.user, session.password, session.repo)\r
+               ui.activate();\r
+               console.log("Session started from cookie (head: "+ $("body").attr("data-github-head") +", head: "+ $("body").attr("data-github-base") +")");\r
 \r
-// Spinner vars\r
-var opts = {\r
-         lines: 11, // The number of lines to draw\r
-         length: 7, // The length of each line\r
-         width: 4, // The line thickness\r
-         radius: 10, // The radius of the inner circle\r
-         corners: 1, // Corner roundness (0..1)\r
-         rotate: 0, // The rotation offset\r
-         color: '#FFF', // #rgb or #rrggbb\r
-         speed: 1, // Rounds per second\r
-         trail: 60, // Afterglow percentage\r
-         shadow: false, // Whether to render a shadow\r
-         hwaccel: false, // Whether to use hardware acceleration\r
-         className: 'spinner', // The CSS class to assign to the spinner\r
-         zIndex: 99999, // The z-index (defaults to 2000000000)\r
-         top: '300', // Top position relative to parent in px\r
-         left: 'auto' // Left position relative to parent in px\r
-       };\r
-var targetSpinner = document.getElementById('waitCommit');\r
-var spinner = new Spinner(opts).spin(targetSpinner);\r
+       } else {\r
+               console.log("No cookie found");\r
+       }\r
+});\r
 \r
 // Check if a comment is editing\r
 window.onbeforeunload = function() {\r
-       if(editComment > 0){\r
+       if(ui.openedComments > 0){\r
        return 'Are you sure you want to leave this page?';\r
        }\r
 };\r
 \r
-$(document).ready(function() {\r
-       createLoginBox();\r
-       // Hide edit tags\r
-       $('textarea').hide();\r
-       $('a[id=commitBtn]').hide();\r
-       $('a[id=cancelBtn]').hide();\r
-       // Display Login modal\r
-       $("#logGitHub").click(function(){ toggleLoginBox(); });\r
-       // Update display\r
-       updateDisplaying();\r
-       // If cookie existing the session is opened\r
-       if(sessionStarted){ userB64 = "Basic " + getUserPass("logginNitdoc"); }\r
-\r
-       // Sign In an github user or Log out him\r
-       $("#signIn").click(function(){\r
-               if(!sessionStarted){\r
-                       if($('#loginGit').val() == "" || $('#passwordGit').val() == ""){ displayMessage('Please informed login/password field!', 40, 45); }\r
-                       else\r
-                       {\r
-                               userName = $('#loginGit').val();\r
-                               password = $('#passwordGit').val();\r
-                               githubRepo = $('#repositoryGit').val();\r
-                               branchName = $('#branchGit').val();\r
-                               userB64 = "Basic " +  base64.encode(userName+':'+password);\r
-                               if(checkSignIn()){\r
-                                       // Check if repo exist\r
-                                       isRepoExisting();\r
-                                       if(repoExist){\r
-                                               $.when(isBranchExisting()).done(function(){\r
-                                                       loginProcess = true;\r
-                                                       if(branchExist){\r
-                                                               setCookie("logginNitdoc", base64.encode(userName+':'+password+':'+githubRepo+':'+branchName), 1);\r
-                                                               $('#loginGit').val("");\r
-                                                               $('#passwordGit').val("");\r
-                                                               reloadComment();\r
-                                                       }\r
-                                               });\r
-                                       }\r
-                               }\r
+/* GitHub API */\r
+\r
+function GitHubAPI(login, password, repo) {\r
+       this.login = login;\r
+       this.password = password;\r
+       this.repo = repo;\r
+       this.auth = "Basic " +  Base64.encode(login + ':' + password);\r
+\r
+       /* GitHub Account */\r
+\r
+       // try to login to github API\r
+       this.tryLogin = function() {\r
+               var res = false;\r
+               $.ajax({\r
+                       beforeSend: function (xhr) {\r
+                               xhr.setRequestHeader ("Authorization", githubAPI.auth);\r
+                       },\r
+                       type: "GET",\r
+                       url: "https://api.github.com/repos/" + this.login+ "/" + this.repo,\r
+                       async: false,\r
+                       dataType: 'json',\r
+                       success: function() {\r
+                               res = true;\r
                        }\r
-               }\r
-               else\r
-               {\r
-                       // Delete cookie and reset settings\r
-                       del_cookie("logginNitdoc");\r
-                       closeAllCommentInEdtiting();\r
-               }\r
-               toggleLoginBox();\r
-       });\r
-\r
-       // Activate edit mode\r
-       $('pre[class=text_label]').click(function(){\r
-               // the customer is loggued ?\r
-               if(!sessionStarted || userName == ""){\r
-                       // No => nothing happen\r
-                       return;\r
-               }\r
-               else{\r
-                       numComment = $(this).attr('title');\r
-                       var arrayNew = $(this).text().split('\n');\r
-                       var lNew = arrayNew.length - 1;\r
-                       var adapt = "";\r
+               });\r
+               return res;\r
+       }\r
 \r
-                       for (var i = 0; i < lNew; i++) {\r
-                               adapt += arrayNew[i];\r
-                               if(i < lNew-1){ adapt += "\n"; }\r
+       this.getUserInfos = function() {\r
+               var res = false;\r
+               $.ajax({\r
+                       beforeSend: function (xhr) {\r
+                               xhr.setRequestHeader ("Authorization", githubAPI.auth);\r
+                       },\r
+                       type: "GET",\r
+                       url: "https://api.github.com/users/" + this.login,\r
+                       async: false,\r
+                       dataType: 'json',\r
+                       success: function(response) {\r
+                               res = response;\r
+                       },\r
+                       error: function(response) {\r
+                               res = response;\r
                        }\r
-                       editComment += 1;\r
-                       getCommentOfFunction($(this));\r
-                       // hide comment\r
-                       $(this).hide();\r
-                       // Show edit box\r
-                       $(this).next().show();\r
-                       // Show cancel button\r
-                       $(this).next().next().show();\r
-                       // Show commit button\r
-                       $(this).next().next().next().show();\r
-                       // Add text in edit box\r
-                       if($(this).next().val() == "" || $(this).next().val() != adapt){ $(this).next().val(adapt); }\r
-                       // Resize edit box\r
-                       $(this).next().height($(this).next().prop("scrollHeight"));\r
-                       resizeTextarea($(this).next());\r
-                       // Select it\r
-                       $(this).next().select();\r
-                       preElement = $(this);\r
-               }\r
-       });\r
+               });\r
+               return res;\r
+       }\r
 \r
-       // Disable the edit mode\r
-       $('a[id=cancelBtn]').click(function(){\r
-               $(this).parent().prev().children('#lblDiffCommit').text("");\r
-               showcomment = false;\r
-               closeEditing($(this));\r
-       });\r
+       this.getSignedOff = function() {\r
+               var infos = this.getUserInfos();\r
+               return infos.name + " &lt;" + infos.email + "&gt;";\r
+       }\r
 \r
-       // Display commit form\r
-       $('a[id=commitBtn]').click(function(){\r
-               updateComment = $(this).prev().prev().val();\r
-               commentType = $(this).prev().prev().prev().attr('type');\r
+       /* GitHub Repos */\r
 \r
-               if(updateComment == ""){ displayMessage('The comment field is empty!', 40, 45); }\r
-               else{\r
-                       if(!sessionStarted){\r
-                               displayMessage("You need to be loggued before commit something", 45, 40);\r
-                               toggleLoginBox();\r
-                               return;\r
+       this.getFile = function(path, branch){\r
+               var res = false;\r
+               $.ajax({\r
+               type: "GET",\r
+                       url: "https://api.github.com/repos/" + this.login + "/" + this.repo + "/contents/" + path,\r
+                       data: {\r
+                               ref: branch\r
+                       },\r
+                       async: false,\r
+               dataType: 'json',\r
+               success: function(response) {\r
+                               res = response;\r
+                       },\r
+                       error: function(response) {\r
+                               res = response;\r
                        }\r
+       });\r
+               return res;\r
+       }\r
 \r
-                       // Create the commit message\r
-                       commitMessage = 'Wikidoc: modified comment in ' + $(this).parent().prev().prev().html().split(' ')[1];\r
-                       $('#commitMessage').text(commitMessage);\r
-                       $('#commitMessage').css({'display': 'block'});\r
-                       pathFile = $(this).prev().prev().prev().attr('tag');\r
-                       $('#modal').show().prepend('<a class="close"><img src="resources/icons/close.png" class="btn_close" title="Close" alt="Close" /></a>');\r
-                       $('body').append('<div id="fade"></div>');\r
-                       $('#fade').css({'filter' : 'alpha(opacity=80)'}).fadeIn();\r
-               }\r
-        });\r
-\r
-       // Close commit form\r
-       $('.btn_close').click(function(){\r
-               $(this).hide();\r
-               $(this).next().hide();\r
-               if(editComment > 0){ editComment -= 1; }\r
-               $('#chkSignedOff').attr('checked', false);\r
-               removeSignedOff();\r
-        });\r
+       /* GitHub commits */\r
 \r
-       //Close Popups and Fade Layer\r
-       $('body').on('click', 'a.close, #fade', function() {\r
-               if(editComment > 0){ editComment -= 1; }\r
-               $('#fade , #modal').fadeOut(function() {\r
-                       $('#fade, a.close').remove();\r
-               });\r
-               $('#modalQuestion').hide();\r
-               $('#chkSignedOff').attr('checked', false);\r
-               removeSignedOff();\r
+       // get the latest commit on `branchName`\r
+       this.getLastCommit = function(branchName) {\r
+               var res = false;\r
+               $.ajax({\r
+               beforeSend: function (xhr) {\r
+                               xhr.setRequestHeader ("Authorization", githubAPI.auth);\r
+               },\r
+               type: "GET",\r
+               url: "https://api.github.com/repos/" + this.login + "/" + this.repo + "/git/refs/heads/" + branchName,\r
+                       async: false,\r
+               dataType: 'json',\r
+               success: function(response) {\r
+                               res = response.object;\r
+                       },\r
+                       error: function(response) {\r
+                               res = response;\r
+                       }\r
        });\r
+               return res;\r
+        }\r
 \r
-       $('#loginAction').click(function(){\r
-               var text;\r
-               var url;\r
-               var line;\r
-               // Look if the customer is logged\r
-               if(!sessionStarted){\r
-                       displayMessage("You need to be loggued before commit something", 100, 40);\r
-                       $('.popover').show();\r
-                       return;\r
-               }\r
-               else{ userB64 = "Basic " + getUserPass("logginNitdoc"); }\r
-               // Check if repo exist\r
-               isRepoExisting();\r
-               if(repoExist){\r
-                       isBranchExisting();\r
-                       if(branchExist){\r
-                               editComment -= 1;\r
-                               commitMessage = $('#commitMessage').val().replace(/\r?\n/g, '\\n').replace(/\t/g, '\\t').replace(/\"/g,'\\"');\r
-                               if(commitMessage == ""){ commitMessage = "New commit";}\r
-                               if(sessionStarted){\r
-                                       if ($.trim(updateComment) == ''){ this.value = (this.defaultValue ? this.defaultValue : ''); }\r
-                                       else{\r
-                                               displaySpinner();\r
-                                               startCommitProcess();\r
-                                       }\r
-                               }\r
-                               $('#modal, #modalQuestion').fadeOut(function() {\r
-                                       $('#login').val("");\r
-                                       $('#password').val("");\r
-                                       $('textarea').hide();\r
-                                       $('textarea').prev().show();\r
-                               });\r
-                               $('a[id=cancelBtn]').hide();\r
-                               $('a[id=commitBtn]').hide();\r
-                               $('a[id=lblDiffCommit]').text("");\r
-                               showcomment = false;\r
-                               // Re-load all comment\r
-                               reloadComment();\r
+       // get the base tree for commit\r
+       this.getTree = function(sha) {\r
+               var res = false;\r
+               $.ajax({\r
+               beforeSend: function (xhr) {\r
+                               xhr.setRequestHeader ("Authorization", githubAPI.auth);\r
+               },\r
+               type: "GET",\r
+               url: "https://api.github.com/repos/" + this.login + "/" + this.repo + "/git/trees/" + sha + "?recursive=1",\r
+                       async: false,\r
+               dataType: 'json',\r
+               success: function(response) {\r
+                               res = response;\r
+                       },\r
+                       error: function(response) {\r
+                               res = response;\r
                        }\r
-               }\r
-               else{ editComment -= 1; }\r
-               $('#chkSignedOff').attr('checked', false);\r
        });\r
+               return res;\r
+        }\r
 \r
-       // Cancel creating branch\r
-       $('#btnCancelBranch').click(function(){\r
-               editComment -= 1;\r
-               $('#modalQuestion').hide();\r
-               $('#fade , #modal').fadeOut(function() { $('#fade, a.close').remove(); });\r
-               return;\r
+       // create a new blob\r
+       this.createBlob = function(content) {\r
+               var res = false;\r
+               $.ajax({\r
+               beforeSend: function (xhr) {\r
+                               xhr.setRequestHeader ("Authorization", githubAPI.auth);\r
+               },\r
+               type: "POST",\r
+               url: "https://api.github.com/repos/" + this.login + "/" + this.repo + "/git/blobs",\r
+                       async: false,\r
+               dataType: 'json',\r
+                       data: JSON.stringify({\r
+                               content: Base64.encode(content),\r
+                               encoding: "base64"\r
+                       }),\r
+               success: function(response) {\r
+                               res = response;\r
+                       },\r
+                       error: function(response) {\r
+                               res = response;\r
+                       }\r
        });\r
+               return res;\r
+        }\r
 \r
-       // Create new branch and continu\r
-       $('#btnCreateBranch').click(function(){\r
-               $('#modalQuestion').hide();\r
-               if($('#btnCreateBranch').text() != 'Ok'){\r
-                       // Create the branch\r
-                       createBranch();\r
-                       commitMessage = $('#commitMessage').val().replace(/\r?\n/g, '\\n').replace(/\t/g, '\\t').replace(/\"/g,'\\"');\r
-                       if(commitMessage == ""){ commitMessage = "New commit"; }\r
-                       if(userB64 != ""){\r
-                       if(loginProcess){\r
-                                       setCookie("logginNitdoc", base64.encode(userName+':'+password+':'+githubRepo+':'+branchName), 1);\r
-                                       $('#loginGit').val("");\r
-                                       $('#passwordGit').val("");\r
-                                       loginProcess = false;\r
-                                       toggleLoginBox();\r
-                       }\r
-                       else{\r
-                                       if ($.trim(updateComment) == ''){ this.value = (this.defaultValue ? this.defaultValue : ''); }\r
-                                       else{ startCommitProcess(); }\r
-                               }\r
-                   }\r
-               }\r
-               else\r
-               {\r
-                       $('#fade , #modalQuestion, #modal').fadeOut(function() { $('#fade, a.close').remove(); });\r
-               }\r
+       // create a new tree from a base tree\r
+       this.createTree = function(baseTree, path, blob) {\r
+               var res = false;\r
+               $.ajax({\r
+               beforeSend: function (xhr) {\r
+                               xhr.setRequestHeader ("Authorization", githubAPI.auth);\r
+               },\r
+               type: "POST",\r
+               url: "https://api.github.com/repos/" + this.login + "/" + this.repo + "/git/trees",\r
+                       data: JSON.stringify({\r
+                               base_tree: baseTree.sha,\r
+                               tree: [{\r
+                                       path: path,\r
+                                       mode: 100644, // file (blob)\r
+                                       type: "blob",\r
+                                       sha: blob.sha\r
+                               }]\r
+                       }),\r
+                       async: false,\r
+               dataType: 'json',\r
+               success: function(response) {\r
+                               res = response;\r
+                       },\r
+                       error: function(response) {\r
+                               res = response;\r
+                       }\r
        });\r
+               return res;\r
+       }\r
 \r
-       $('a[class=newComment]').click(function(){\r
-               addNewComment = true;\r
-               editComment += 1;\r
-               // hide comment\r
-               $(this).hide();\r
-               // Show edit box\r
-               $(this).next().show();\r
-               // Show cancel button\r
-               $(this).next().next().show();\r
-               // Show commit button\r
-               $(this).next().next().next().show();\r
-               // Resize edit box\r
-               $(this).next().height($(this).next().prop("scrollHeight"));\r
-               resizeTextarea($(this).next());\r
-               // Select it\r
-               $(this).next().select();\r
-               preElement = $(this);\r
-        });\r
-\r
-       $("#dropBranches").change(function () {\r
-               $("#dropBranches option:selected").each(function () {\r
-                       if(branchName != $(this).text()){\r
-                               branchName = $(this).text();\r
+       // create a new commit\r
+       this.createCommit = function(message, parentCommit, tree) {\r
+               var res = false;\r
+               $.ajax({\r
+               beforeSend: function (xhr) {\r
+                               xhr.setRequestHeader ("Authorization", githubAPI.auth);\r
+               },\r
+               type: "POST",\r
+               url: "https://api.github.com/repos/" + this.login + "/" + this.repo + "/git/commits",\r
+                       data: JSON.stringify({\r
+                               message: message,\r
+                               parents: parentCommit,\r
+                               tree: tree.sha,\r
+                       }),\r
+                       async: false,\r
+               dataType: 'json',\r
+               success: function(response) {\r
+                               res = response;\r
+                       },\r
+                       error: function(response) {\r
+                               res = response;\r
                        }\r
-               });\r
-               $.when(updateCookie(userName, password, githubRepo, branchName)).done(function(){\r
-                       closeAllCommentInEdtiting();\r
-                       reloadComment();\r
-               });\r
        });\r
+               return res;\r
+       }\r
 \r
-       $("pre").hover(\r
-               function () {\r
-                       if(sessionStarted == true){\r
-                               $(this).css({'cursor' : 'hand'});\r
-                       }\r
-                       else{\r
-                               $(this).css({'cursor' : ''});\r
-                       }\r
+       // create a pull request\r
+       this.createPullRequest = function(title, body, base, head) {\r
+               var res = false;\r
+               $.ajax({\r
+               beforeSend: function (xhr) {\r
+                               xhr.setRequestHeader ("Authorization", githubAPI.auth);\r
                },\r
-               function () {\r
-                       if(sessionStarted == true){\r
-                               $(this).css({'cursor' : 'pointer'});\r
+               type: "POST",\r
+               url: "https://api.github.com/repos/" + this.login + "/" + this.repo + "/pulls",\r
+                       data: JSON.stringify({\r
+                               title: title,\r
+                               body: body,\r
+                               base: base,\r
+                               head: head\r
+                       }),\r
+                       async: false,\r
+               dataType: 'json',\r
+               success: function(response) {\r
+                               res = response;\r
+                       },\r
+                       error: function(response) {\r
+                               res = response;\r
                        }\r
-                       else{\r
-                               $(this).css({'cursor' : ''});\r
-                       }\r
-               }\r
-       );\r
-\r
-       $('#chkSignedOff').click(function(){\r
-               if($(this).is(':checked')){ addSignedOff(); }\r
-               else{ removeSignedOff(); }\r
-       })\r
-\r
-       $('a[id=lblDiffCommit]').click(function(){\r
-               showComment($(this));\r
        });\r
-});\r
-\r
-// Init process to commit the new comment\r
-function startCommitProcess()\r
-{\r
-       if($('#chkSignedOff').is(':checked')){\r
-               var numL = preElement.attr("title");\r
-               commentLineStart = numL.split('-')[0] - 1;\r
-               if(addNewComment) { commentLineStart++; }\r
-               commentLineEnd = (commentLineStart + preElement.text().split('\n').length) - 1;\r
-               state = true;\r
-               replaceComment(updateComment, currentfileContent);\r
-               getLastCommit();\r
-               getBaseTree();\r
-               editComment = false;\r
+               return res;\r
        }\r
-       else{\r
-               displayMessage('Please sign this commit', 40, 40);\r
-       }\r
-}\r
 \r
-function updateDisplaying(){\r
-       if (checkCookie())\r
-       {\r
-               userB64 = "Basic " + getUserPass("logginNitdoc");\r
-               $('#loginGit').hide();\r
-               $('#passwordGit').hide();\r
-               $('#lbpasswordGit').hide();\r
-               $('#lbloginGit').hide();\r
-               $('#repositoryGit').hide();\r
-               $('#lbrepositoryGit').hide();\r
-               $('#lbbranchGit').hide();\r
-               $('#branchGit').hide();\r
-               $('#listBranches').show();\r
-               $('#divGitHubRepoDisplay').show();\r
-               $("#liGitHub").attr("class", "current");\r
-               $("#imgGitHub").attr("src", "resources/icons/github-icon-w.png");\r
-               $('#nickName').text(userName);\r
-               $('#githubAccount').attr("href", "https://github.com/"+userName);\r
-               $('#logginMessage').css({'display' : 'block'});\r
-               $('#logginMessage').css({'text-align' : 'center'});\r
-               $('.popover').css({'height' : '190px'});\r
-               $('#signIn').text("Sign out");\r
-               $('#githubRepoDisplay').text(githubRepo);\r
-               sessionStarted = true;\r
-               reloadComment();\r
-       }\r
-       else\r
-       {\r
-               sessionStarted = false;\r
-               $('#logginMessage').css({'display' : 'none'});\r
-               $("#liGitHub").attr("class", "");\r
-               $("#imgGitHub").attr("src", "resources/icons/github-icon.png");\r
-               $('#loginGit').val("");\r
-               $('#passwordGit').val("");\r
-               $('#nickName').text("");\r
-               $('.popover').css({'height' : '325px'});\r
-               $('#logginMessage').css({'display' : 'none'});\r
-               $('#repositoryGit').val($('#repoName').attr('name'));\r
-               $('#branchGit').val('wikidoc');\r
-               $('#signIn').text("Sign In");\r
-               $('#loginGit').show();\r
-               $('#passwordGit').show();\r
-               $('#lbpasswordGit').show();\r
-               $('#lbloginGit').show();\r
-               $('#repositoryGit').show();\r
-               $('#lbrepositoryGit').show();\r
-               $('#lbbranchGit').show();\r
-               $('#branchGit').show();\r
-               $('#listBranches').hide();\r
-               $('#divGitHubRepoDisplay').hide();\r
+       // update a pull request\r
+       this.updatePullRequest = function(title, body, state, number) {\r
+               var res = false;\r
+               $.ajax({\r
+               beforeSend: function (xhr) {\r
+                               xhr.setRequestHeader ("Authorization", githubAPI.auth);\r
+               },\r
+               type: "PATCH",\r
+               url: "https://api.github.com/repos/" + this.login + "/" + this.repo + "/pulls/" + number,\r
+                       data: JSON.stringify({\r
+                               title: title,\r
+                               body: body,\r
+                               state: state\r
+                       }),\r
+                       async: false,\r
+               dataType: 'json',\r
+               success: function(response) {\r
+                               res = response;\r
+                       },\r
+                       error: function(response) {\r
+                               res = response;\r
+                       }\r
+       });\r
+               return res;\r
        }\r
 }\r
+var githubAPI;\r
 \r
-function setCookie(c_name, value, exdays)\r
-{\r
-       var exdate=new Date();\r
-       exdate.setDate(exdate.getDate() + exdays);\r
-       var c_value=escape(value) + ((exdays==null) ? "" : "; expires="+exdate.toUTCString());\r
-       document.cookie=c_name + "=" + c_value;\r
-}\r
+/* GitHub cookie management */\r
 \r
-function del_cookie(c_name)\r
-{\r
-    document.cookie = c_name + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;';\r
-}\r
+function SessionCookie(cookieName) {\r
+       this.cookieName = cookieName\r
 \r
-function updateCookie(user, pwd, repo, branch){\r
-       if(checkCookie()){\r
-               branchName = branch;\r
-               setCookie("logginNitdoc", base64.encode(user+':'+pwd+':'+repo+':'+branch), 1);\r
+       this.setSession = function (user, password, repo) {\r
+               var value = Base64.encode(JSON.stringify({\r
+                       user: user,\r
+                       password: password,\r
+                       repo: repo\r
+               }));\r
+               var exdate = new Date();\r
+               exdate.setDate(exdate.getDate() + 1);\r
+               document.cookie = this.cookieName + "=" + value + "; expires=" + exdate.toUTCString();\r
        }\r
-}\r
 \r
-function getCookie(c_name)\r
-{\r
-       var c_value = document.cookie;\r
-       var c_start = c_value.indexOf(" " + c_name + "=");\r
-       if (c_start == -1) { c_start = c_value.indexOf(c_name + "="); }\r
-       if (c_start == -1) { c_value = null; }\r
-       else\r
-       {\r
-               c_start = c_value.indexOf("=", c_start) + 1;\r
-               var c_end = c_value.indexOf(";", c_start);\r
-               if (c_end == -1) { c_end = c_value.length; }\r
-               c_value = unescape(c_value.substring(c_start,c_end));\r
+       this.delSession = function() {\r
+           document.cookie = this.cookieName + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;';\r
        }\r
-       return c_value;\r
-}\r
 \r
-function getUserPass(c_name){\r
-       var cookie = base64.decode(getCookie(c_name));\r
-       return base64.encode(cookie.split(':')[0] + ':' + cookie.split(':')[1]);\r
-}\r
-\r
-function checkCookie()\r
-{\r
-       var cookie=getCookie("logginNitdoc");\r
-       if (cookie!=null && cookie!="")\r
-       {\r
-               cookie = base64.decode(cookie);\r
-               userName = cookie.split(':')[0];\r
-               password = cookie.split(':')[1];\r
-               githubRepo = cookie.split(':')[2];\r
-               branchName = cookie.split(':')[3];\r
-               return true;\r
+       this.getCookieDatas = function() {\r
+               var c_name = this.cookieName;\r
+               var c_value = document.cookie;\r
+               var c_start = c_value.indexOf(" " + c_name + "=");\r
+               if (c_start == -1) { c_start = c_value.indexOf(c_name + "="); }\r
+               if (c_start == -1) {\r
+                       c_value = null;\r
+               } else {\r
+                       c_start = c_value.indexOf("=", c_start) + 1;\r
+                       var c_end = c_value.indexOf(";", c_start);\r
+                       if (c_end == -1) { c_end = c_value.length; }\r
+                       c_value = unescape(c_value.substring(c_start,c_end));\r
+               }\r
+               return c_value;\r
        }\r
-       else { return false; }\r
-}\r
 \r
-function getLastCommit()\r
-{\r
-       var urlHead = '';\r
-       if(sessionStarted){ urlHead = "https://api.github.com/repos/"+userName+"/"+githubRepo+"/git/refs/heads/"+branchName;}\r
-       else{\r
-               // TODO: get url of the original repo.\r
-               return;\r
+       this.getSession = function() {\r
+               var cookie = this.getCookieDatas();\r
+               if (!!cookie) {\r
+                       return JSON.parse(Base64.decode(cookie));\r
+               }\r
+               return false;\r
        }\r
-\r
-    $.ajax({\r
-        beforeSend: function (xhr) {\r
-            if (userB64 != ""){ xhr.setRequestHeader ("Authorization", userB64); }\r
-        },\r
-        type: "GET",\r
-        url: urlHead,\r
-        dataType:"json",\r
-        async: false,\r
-        success: function(success)\r
-        {\r
-            shaLastCommit = success.object.sha;\r
-        }\r
-    });\r
-}\r
-\r
-function getBaseTree()\r
-{\r
-    $.ajax({\r
-        beforeSend: function (xhr) {\r
-            if (userB64 != ""){ xhr.setRequestHeader ("Authorization", userB64); }\r
-        },\r
-        type: "GET",\r
-        url: "https://api.github.com/repos/"+userName+"/"+githubRepo+"/git/commits/" + shaLastCommit,\r
-        dataType:"json",\r
-        async: false,\r
-        success: function(success)\r
-        {\r
-            shaBaseTree = success.tree.sha;\r
-            if (state){ setBlob(); }\r
-            else{ return; }\r
-        },\r
-        error: function(){\r
-       return;\r
-        }\r
-    });\r
 }\r
+var sessionCookie;\r
 \r
-function setNewTree()\r
-{\r
-    $.ajax({\r
-        beforeSend: function (xhr) { xhr.setRequestHeader ("Authorization", userB64); },\r
-        type: "POST",\r
-        url: "https://api.github.com/repos/"+userName+"/"+githubRepo+"/git/trees",\r
-        async: false,\r
-        dataType:'json',\r
-        data:'{ "base_tree" : "'+shaBaseTree+'", '+\r
-                '"tree":[{ '+\r
-                    '"path":"'+ pathFile +'",'+\r
-                    '"mode":"100644",'+\r
-                    '"type":"blob",'+\r
-                    '"sha": "'+ shaBlob +'"'+\r
-                '}] '+\r
-            '}',\r
-        success: function(success)\r
-        { // si l'appel a bien fonctionné\r
-            shaNewTree = success.sha;\r
-            setNewCommit();\r
-        },\r
-        error: function(){\r
-       return;\r
-        }\r
-    });\r
-}\r
-\r
-function setNewCommit()\r
-{\r
-    addSignedOff();\r
-    $.ajax({\r
-        beforeSend: function (xhr) { xhr.setRequestHeader ("Authorization", userB64); },\r
-        type: "POST",\r
-        url: "https://api.github.com/repos/"+userName+"/"+githubRepo+"/git/commits",\r
-        async: false,\r
-        dataType:'json',\r
-        data:'{ "message" : "'+ commitMessage +'", '+\r
-                '"parents" :"'+shaLastCommit+'",'+\r
-                '"tree": "'+shaNewTree+'"'+\r
-             '}',\r
-        success: function(success)\r
-        {\r
-            shaNewCommit = success.sha;\r
-            commit();\r
-        },\r
-        error: function(){\r
-       return;\r
-        }\r
-    });\r
-}\r
-\r
-//Create a commit\r
-function commit()\r
-{\r
-    $.ajax({\r
-        beforeSend: function (xhr) { xhr.setRequestHeader ("Authorization", userB64); },\r
-        type: "POST",\r
-        url: "https://api.github.com/repos/"+userName+"/"+githubRepo+"/git/refs/heads/"+branchName,\r
-        dataType:'json',\r
-        data:'{ "sha" : "'+shaNewCommit+'", '+\r
-                '"force" :"true"'+\r
-             '}',\r
-        success: function(success) { displayMessage('Commit created successfully', 40, 40); },\r
-        error:function(error){ displayMessage('Error ' + error.object.message, 40, 40); }\r
-    });\r
-}\r
+/* GitHub login box */\r
 \r
-// Create a blob\r
-function setBlob()\r
-{\r
-    $.ajax({\r
-        beforeSend: function (xhr) { xhr.setRequestHeader ("Authorization",  userB64); },\r
-        type: "POST",\r
-        url: "https://api.github.com/repos/"+userName+"/"+githubRepo+"/git/blobs",\r
-        async: false,\r
-        dataType:'json',\r
-        data:'{ "content" : "'+text.replace(/\r?\n/g, '\\n').replace(/\t/g, '\\t').replace(/\"/g,'\\"')+'", '+\r
-                '"encoding" :"utf-8"'+\r
-            '}',\r
-        success: function(success)\r
-        {\r
-            shaBlob = success.sha;\r
-            setNewTree();\r
-        },\r
-        error:function(error){\r
-       displayMessage('Error : Problem parsing JSON', 40, 40);\r
-       return;\r
-       }\r
-    });\r
-}\r
-\r
-// Display file content\r
-function getFileContent(urlFile, newComment)\r
-{\r
-    $.ajax({\r
-        beforeSend: function (xhr) {\r
-            xhr.setRequestHeader ("Accept",  "application/vnd.github-blob.raw");\r
-            if (userB64 != ""){ xhr.setRequestHeader ("Authorization", userB64); }\r
-        },\r
-        type: "GET",\r
-        url: urlFile,\r
-        async:false,\r
-        success: function(success)\r
-        {\r
-            state = true;\r
-            replaceComment(newComment, success);\r
-        }\r
-    });\r
-}\r
+function LoginBox() {\r
+       // Add login box\r
+       $("nav.main ul").append(\r
+               $("<li id='liGitHub'></li>")\r
+               .append(\r
+                       $("<a class='btn' id='logGitHub'><img id='imgGitHub' src='resources/icons/github-icon.png' alt='GitHub'/></a>")\r
+                       .click(function() { ui.loginBox.toggle() })\r
+               )\r
+               .append(\r
+                       "  <div id='loginBox' style='display: none;'>" +\r
+                       "    <div class='arrow'>&nbsp;</div>" +\r
+                       "      <h3>Github Sign In</h3>" +\r
+                       "        <div id='signedIn' style='display: none'>" +\r
+                       "          <label id='logginMessage'>Hello " +\r
+                       "            <a id='githubAccount'><strong id='nickName'></strong></a>!" +\r
+                       "          </label>" +\r
+                       "          <label for='github-repo'>Repo</label>" +\r
+                       "          <input id='github-repo' disabled='disabled' type='text'/>" +\r
+                       "          <label for='github-head'>Head</label>" +\r
+                       "          <input id='github-head' disabled='disabled' type='text'/>" +\r
+                       "          <label for='github-base'>Base</label>" +\r
+                       "          <input id='github-base' disabled='disabled' type='text'/>" +\r
+                       "          <button class='signIn github'><img src='resources/icons/github-icon.png'/>Sign Off</button>" +\r
+                       "        </div>" +\r
+                       "        <div id='signedOff'>" +\r
+                       "         <form>" +\r
+                       "          <label for='loginGit'>Username</label>" +\r
+                       "          <input id='loginGit' type='text'/>" +\r
+                       "          <label for='passwordGit'>Password</label>" +\r
+                       "          <input id='passwordGit' type='password'/>" +\r
+                       "          <label for='repositoryGit'>Repository</label>" +\r
+                       "          <input id='repositoryGit' type='text'/>" +\r
+                       "          <button class='signIn github'><img src='resources/icons/github-icon.png'/>Sign In</button>" +\r
+                       "         </form>" +\r
+                       "      </div>" +\r
+                       "    </div>" +\r
+                       "  </div>"\r
+               )\r
+       );\r
 \r
-function replaceComment(newComment, fileContent){\r
-       var arrayNew = newComment.split('\n');\r
-       var lNew = arrayNew.length;\r
-       text = "";\r
-       var lines = fileContent.split("\n");\r
-       for (var i = 0; i < lines.length; i++) {\r
-               if(i == commentLineStart){\r
-                       if(addNewComment){\r
-                               for(var indexLine=0; indexLine < lines[i+1].length; indexxLine++){\r
-                                       if(lines[i+1].substr(indexLine,1) == "\t" || lines[i+1].substr(indexLine,1) == "#"){ text += lines[i+1].substr(indexLine,1); }\r
-                                       else{ break;}\r
-                               }\r
-                               text += lines[i] + "\n";\r
-                       }\r
-                       // We change the comment\r
-                       for(var j = 0; j < lNew; j++){\r
-                               if(commentType == 1){ text += "\t# " + arrayNew[j] + "\n"; }\r
-                               else{\r
-                                       if(arrayNew[j] == ""){ text += "#"+"\n"; }\r
-                                       else{ text += "# " + arrayNew[j] + "\n"; }\r
+       // Login with github user or logout current session\r
+       $("#loginBox .signIn").click(function(){\r
+               if($('#signedIn').is(':hidden')){\r
+                       if(!$('#loginGit').val() || !$('#passwordGit').val() || !$('#repositoryGit').val()) {\r
+                               ui.openModalBox("Login incorrect!", "Please enter your username, password and repository.", true);\r
+                       } else {\r
+                               githubAPI = new GitHubAPI($('#loginGit').val(),  $('#passwordGit').val(), $('#repositoryGit').val());\r
+                               if(githubAPI.tryLogin()) {\r
+                                       // open session and set cookie\r
+                                       sessionCookie.setSession(githubAPI.login, githubAPI.password, githubAPI.repo);\r
+                                       ui.activate();\r
+                               } else {\r
+                                       githubAPI = false;\r
+                                       ui.openModalBox("Login incorrect!", "Your login information was incorrect!", true);\r
                                }\r
                        }\r
+               } else {\r
+                       ui.disactivate();\r
+                       ui.loginBox.toggle();\r
                }\r
-               else if(i < commentLineStart || i >= commentLineEnd){\r
-                       if(i == lines.length-1){ text += lines[i]; }\r
-                       else{ text += lines[i] + "\n"; }\r
-               }\r
-       }\r
-       if(addNewComment){\r
-               addNewComment = false;\r
-       }\r
-}\r
+               return false;\r
+       });\r
 \r
-function getCommentLastCommit(path, origin){\r
-       var urlRaw;\r
-       var bkBranch = '';\r
-       if(origin){// We want to get the original file\r
-               bkBranch = branchName;\r
-               branchName = "master";\r
-       }\r
-       getLastCommit();\r
-       if(shaLastCommit != ""){\r
-               if (checkCookie()) {\r
-                       urlRaw="https://rawgithub.com/"+ userName +"/"+ githubRepo +"/" + shaLastCommit + "/" + path;\r
-                       $.ajax({\r
-                           type: "GET",\r
-                           url: urlRaw,\r
-                           async: false,\r
-                           success: function(success)\r
-                           {\r
-                                   if(origin){ originalFileContent = success; }\r
-                                   else{ currentfileContent = success; }\r
-                           }\r
-                       });\r
+       this.toggle = function() {\r
+               if ($('#loginBox').is(':hidden')) {\r
+                       $('#loginBox').show();\r
+                       if (!$('#loginGit').is(':hidden')) { $('#loginGit').focus(); }\r
+               } else {\r
+                       $('#loginBox').hide();\r
                }\r
        }\r
-       if(origin){ branchName = bkBranch; }\r
 }\r
 \r
-function displayMessage(msg, widthDiv, margModal){\r
-       spinner.stop();\r
-       $('#modal').hide();\r
-       $('#btnCancelBranch').hide();\r
-       $('#modalQuestion').show().prepend('<a class="close"><img src="resources/icons/close.png" class="btnCloseQuestion" title="Close" alt="Close" /></a>');\r
-       $('#txtQuestion').text(msg);\r
-       $('#btnCreateBranch').text("Ok");\r
-       var xModal = $('#modalQuestion').css('width').split('px')[0];\r
-       var yModal = $('#modalQuestion').css('height').split('px')[0];\r
-       var x = $(document).width/2 - xModal/2;\r
-       var y = $(document).height/2 - yModal/2;\r
-       var xBtnBranch = $('#btnCreateBranch').css('width').split('px')[0];\r
-       $('#modalQuestion').css({'left' : x, 'top' : y});\r
-       $('#modalQuestion').show();\r
-       $('#btnCreateBranch').css('margin-left', xModal/2 - xBtnBranch);\r
-       $('body').append('<div id="fade"></div>');\r
-       $('#fade').css({'filter' : 'alpha(opacity=80)'}).fadeIn();\r
-}\r
+/* Comment edition UI */\r
 \r
-function displaySpinner(){\r
-       spinner.spin(targetSpinner);\r
-       $("#waitCommit").show();\r
-}\r
+function GitHubUI() {\r
+       this.loginBox = new LoginBox();\r
+       this.openedComments = 0;\r
 \r
-// Check if the repo already exist\r
-function isRepoExisting(){\r
-       $.ajax({\r
-        beforeSend: function (xhr) {\r
-            if (userB64 != "") { xhr.setRequestHeader ("Authorization", userB64); }\r
-        },\r
-        type: "GET",\r
-        url: "https://api.github.com/repos/"+userName+"/"+githubRepo,\r
-        async:false,\r
-        dataType:'json',\r
-        success: function(){ repoExist = true; },\r
-        error: function()\r
-        {\r
-       displayMessage('Repo not found !', 35, 45);\r
-       repoExist = false;\r
-        }\r
-    });\r
-}\r
-\r
-// Check if the branch already exist\r
-function isBranchExisting(){\r
-       $.ajax({\r
-               beforeSend: function (xhr) {\r
-                       if (userB64 != "") { xhr.setRequestHeader ("Authorization", userB64); }\r
-               },\r
-               type: "GET",\r
-               url: "https://api.github.com/repos/"+userName+"/"+githubRepo+"/git/refs/heads/"+branchName,\r
-               async:false,\r
-               dataType:'json',\r
-               success: function(){ branchExist = true; },\r
-               error: function()\r
-               {\r
-                       branchExist = false;\r
-                       editComment -= 1;\r
-                       $('#modal').hide();\r
-                       $('#txtQuestion').text("Are you sure you want to create that branch ?");\r
-                       $('#btnCancelBranch').show();\r
-                       $('#btnCreateBranch').text("Yes");\r
-                       $('#modalQuestion').show();\r
-                       $('#modalQuestion').show().prepend('<a class="close"><img src="resources/icons/close.png" class="btnCloseQuestion" title="Close" alt="Close" /></a>');\r
-                       $('body').append('<div id="fade"></div>');\r
-                       $('#fade').css({'filter' : 'alpha(opacity=80)'}).fadeIn();\r
-               }\r
-       });\r
-}\r
-\r
-function getMasterSha()\r
-{\r
-    $.ajax({\r
-        beforeSend: function (xhr) {\r
-            if (userB64 != ""){ xhr.setRequestHeader ("Authorization", userB64); }\r
-        },\r
-        type: "GET",\r
-        url: "https://api.github.com/repos/"+userName+"/"+githubRepo+"/git/refs/heads/master",\r
-        dataType:"json",\r
-        async: false,\r
-        success: function(success) { shaMaster = success.object.sha; }\r
-    });\r
-}\r
+       this.init = function() {\r
+               $("body").append("<div id='modal'></div>");\r
+               $('body').append('<div id="fade"></div>');\r
+       }\r
 \r
-function createBranch(){\r
+       this.disactivate = function() {\r
+               // close session and purge cookie\r
+               sessionCookie.delSession();\r
+               localStorage.clear();\r
+               window.location.reload();\r
+       }\r
 \r
-       getMasterSha();\r
+       this.activate = function() {\r
+               // get lastest commit\r
+               var latest = githubAPI.getLastCommit($("body").attr("data-github-head"));\r
+               if(!latest || !latest.sha) {\r
+                       this.openModalBox("Head branch not found!", latest.status + ": " + latest.statusText, true)\r
+                       return;\r
+               }\r
+               if(localStorage.latestCommit != latest.sha) {\r
+                       console.log("Latest commit changed: cleaned cache");\r
+                       localStorage.requests = "[]";\r
+                       localStorage.latestCommit = latest.sha;\r
+               }\r
+               console.log("Latest commit sha: " + localStorage.latestCommit);\r
 \r
-       $.ajax({\r
-        beforeSend: function (xhr) { xhr.setRequestHeader ("Authorization", userB64); },\r
-        type: "POST",\r
-        url: "https://api.github.com/repos/"+userName+"/"+githubRepo+"/git/refs",\r
-        data:'{ "ref" : "refs/heads/'+branchName+'",'+\r
-               '"sha" : "'+shaMaster+'"'+\r
-            '}',\r
-        success: function(){ return; },\r
-        error: function(){\r
-       editComment -= 1;\r
-       displayMessage('Impossible to create the new branch : ' + branchName, 40, 40);\r
-        }\r
-    });\r
-}\r
+               // reload loginBox\r
+               $('#signedOff').hide();\r
+               $('#signedIn').show();\r
+               $("#imgGitHub").attr("src", "resources/icons/github-icon-w.png");\r
+               $("#liGitHub").addClass("current");\r
+\r
+               // login form values\r
+               $('#nickName').text(githubAPI.login);\r
+               $('#githubAccount').attr("href", "https://github.com/" + githubAPI.login);\r
+               $('#github-repo').val(githubAPI.repo);\r
+               $('#github-base').val($("body").attr("data-github-base"));\r
+               $('#github-head').val($("body").attr("data-github-head"));\r
+\r
+               // Activate edit mode\r
+\r
+               // Add hidden <pre> to empty commits\r
+               $("span.noComment").each(function() {\r
+                       var baseComment = $(this).parent().prev();\r
+                       var location = ui.parseLocation(baseComment.attr("data-comment-location"));\r
+                       location.lend = location.lstart;\r
+                       var locString = "../" + location.path + ":" + location.lstart + "," + location.tabpos + "--" + location.lend + ",0";\r
+                       baseComment.attr("data-comment-location", locString);\r
+                       $(this).html("<a class='noComment'>add comment</a> for ");\r
+               });\r
+               $('span.noComment a').each(function() {\r
+                       $(this).css("cursor", "pointer")\r
+                       $(this).click(function() {\r
+                               $(this).parent().hide();\r
+                               ui.openCommentBox($(this).parent().parent().prev());\r
+                       });\r
+               });\r
+               $('.description div.comment').each(function() {\r
+                       $(this).css("cursor", "pointer")\r
+                       $(this).click(function() {\r
+                               ui.openCommentBox($(this).prev());\r
+                               $(this).hide();\r
+                       });\r
+               });\r
 \r
-$.fn.spin = function(opts) {\r
-  this.each(function() {\r
-    var $this = $(this),\r
-        data = $this.data();\r
+               // load comment from current branch\r
+               this.reloadComments();\r
+       }\r
 \r
-    if (data.spinner) {\r
-      data.spinner.stop();\r
-      delete data.spinner;\r
-    }\r
-    if (opts !== false) {\r
-      data.spinner = new Spinner($.extend({color: $this.css('color')}, opts)).spin(this);\r
-    }\r
-  });\r
-  return this;\r
-};\r
+       this.openModalBox = function(title, msg, isError) {\r
+               $('#fade').show();\r
+               $('#modal')\r
+                       .empty()\r
+                       .append($('<a class="close"><img src="resources/icons/close.png" class="btnClose" title="Close" alt="Close"/></a>').click(function() {ui.closeModalBox()}))\r
+                       .append("<h3>" + title + "</h3>")\r
+                       .append("<div>" + msg + "</div>")\r
+                       .append(\r
+                               $("<div class='buttonArea'>")\r
+                               .append($("<button>Ok</button>").click(function() {ui.closeModalBox()}))\r
+                       )\r
+                       .show()\r
+                       .css("top", "50%")\r
+                       .css("margin-top", -($('#modal').outerHeight() / 2) + "px")\r
+                       .css("left", "50%")\r
+                       .css("margin-left", -($('#modal').outerWidth() / 2) + "px");\r
+               if(isError) {\r
+                       $("#modal h3").addClass("error");\r
+               }\r
+       }\r
 \r
-function reloadComment(){\r
-       var path = $('pre[class=text_label]').attr('tag');\r
-       $.when(getCommentLastCommit(path, false)).done(function(){\r
-               if(sessionStarted){ getCommentLastCommit(path, true); }\r
-               $('pre[class=text_label]').each(function(){ getCommentOfFunction($(this)); });\r
-       });\r
-}\r
+       this.closeModalBox = function() {\r
+               $('#fade , #modal').hide();\r
+       }\r
 \r
-function getCommentOfFunction(element){\r
-       var textC = "";\r
-       var numL = element.attr("title");\r
-       if(numL != null){\r
-               commentLineStart = numL-1;\r
-               commentLineEnd = element.attr('name').split(numL)[1].split('-')[1]-1;\r
-               var lines = currentfileContent.split("\n");\r
-               for (var i = 0; i < lines.length; i++) {\r
-                       if(i >= commentLineStart-1 && i <= commentLineEnd+1){\r
-                               if (lines[i].substr(1,1) == "#"){ textC += lines[i].substr(3,lines[i].length) + "\n";}\r
-                               else if(lines[i].substr(0,1) == '#'){ textC += lines[i].substr(2,lines[i].length) + "\n"; }\r
-               }\r
-           }\r
-           if(textC != element.text){element.text(textC);}\r
-           if (textC != "" && editComment > 0){\r
-               var originContent = originalFileContent.split("\n");\r
-               var origin = '';\r
-               var lblDiff = element.parent().prev().children('#lblDiffCommit');\r
-               var preSave = element.parent().children('#preSave');\r
-               for (var i = 0; i < originContent.length; i++) {\r
-                       if(i >= commentLineStart-1 && i <= commentLineEnd+1){\r
-                               if (originContent[i].substr(1,1) == "#"){ origin += originContent[i].substr(3,originContent[i].length) + "\n";}\r
-                               else if(originContent[i].substr(0,1) == '#'){ origin += originContent[i].substr(2,originContent[i].length) + "\n"; }\r
-                       }\r
+       this.openCommentBox = function(baseArea) {\r
+               console.log(baseArea);\r
+               this.openedComments += 1;\r
+               // get text and format it\r
+               var formated = "";\r
+               var len = 1;\r
+               var commentLines = baseArea.text().split('\n');\r
+               for (var i = 0; i < commentLines.length; i++) {\r
+                       formated += commentLines[i];\r
+                       if(i < commentLines.length - 2){ formated += "\n"; }\r
                }\r
-               if(textC != origin && numL == numComment){\r
-                       // The comment is different compare to the original\r
-                       if(showcomment == false){ lblDiff.text("Show original comment"); }\r
-                       preSave.text(origin);\r
+               len = commentLines.length - 1;\r
+\r
+               // create comment box\r
+               var tarea = $("<textarea rows='" + len + "'>" + formated + "</textarea>");\r
+               var width = width = baseArea.parent().innerWidth() - 13;\r
+               tarea.css("width", width + "px");\r
+               tarea.css("display", "block");\r
+               tarea.keyup(function(event) {\r
+                       $(event.target).css("height", (event.target.value.split(/\r|\n/).length * 16) + "px");\r
+                       var baseComment =  $(event.target).parents("div.description").find("textarea.baseComment").text();\r
+                       if ($(event.target).val() != baseComment) {\r
+                               $(event.target).parent().find("button.commit").removeAttr("disabled");\r
+                       } else {\r
+                               $(event.target).parent().find("button.commit").attr("disabled", "disabled");\r
+                       }\r
+               });\r
+               tarea.keydown(function(event) {\r
+                       if(event.keyCode == 13){\r
+                               $(event.target).css("height", ($(event.target).outerHeight() + 6) + "px");\r
+                       }\r
+               });\r
+               var commentBox = $("<div class='commentBox'></div>")\r
+                       .attr("data-comment-namespace", baseArea.attr("data-comment-namespace"))\r
+                       .attr("data-comment-location", baseArea.attr("data-comment-location"))\r
+                       .append(tarea)\r
+                       .append(\r
+                               $("<a class='preview'>preview</a>")\r
+                               .click(function() {\r
+                                       var converter = new Markdown.Converter()\r
+                                       var html = converter.makeHtml(tarea.val());\r
+                                       ui.openModalBox("Preview", html, false);\r
+                               })\r
+                       )\r
+                       .append(\r
+                               $("<button class='commit'>Commit</button>")\r
+                               .click(function() {\r
+                                       ui.openCommitBox($(this).parent());\r
+                               })\r
+                       )\r
+                       .append(\r
+                               $("<button class='cancel'>Cancel</button>")\r
+                               .click(function() {ui.closeCommentBox($(this).parent())})\r
+                       );\r
+               if(!baseArea.text()) {\r
+                       commentBox.addClass("newComment");\r
                }\r
-               else if (numL == numComment){ lblDiff.text(""); }\r
-           }\r
+               baseArea.after(commentBox);\r
+               tarea.trigger("keyup");\r
        }\r
-}\r
 \r
-// Get list of branches\r
-function getListBranches()\r
-{\r
-       cleanListBranches();\r
-    $.ajax({\r
-        beforeSend: function (xhr) {\r
-            if ($("#login").val() != ""){ xhr.setRequestHeader ("Authorization", userB64); }\r
-        },\r
-        type: "GET",\r
-        url: "https://api.github.com/repos/"+userName+"/"+githubRepo+"/branches",\r
-        async:false,\r
-        dataType:'json',\r
-        success: function(success)\r
-        {\r
-            for(var branch in success) {\r
-       var selected = '';\r
-       if(branchName == success[branch].name){\r
-               selected = 'selected';\r
+       this.closeCommentBox = function(commentBox) {\r
+               this.openedComments -= 1;\r
+               if(!!commentBox.parent().find(".baseComment").text()) {\r
+                       commentBox.parent().find("div.comment").show();\r
+               } else if(commentBox.hasClass("newComment")) {\r
+                       commentBox.next().find("span.noComment").show();\r
+               }\r
+               commentBox.remove();\r
        }\r
-       $('#dropBranches').append('<option value="" '+ selected +'>' + success[branch].name + '</option>');\r
-            }\r
-        }\r
-    });\r
-}\r
 \r
-// Delete all option in the list\r
-function cleanListBranches(){\r
-       $('#dropBranches').children("option").remove();\r
-}\r
+       this.openCommitBox = function(commentBox) {\r
+               $('#fade').show();\r
+               $('#modal')\r
+                       .empty()\r
+                       .append($('<a class="close"><img src="resources/icons/close.png" class="btnClose" title="Close" alt="Close"/></a>').click(function() {ui.closeModalBox()}))\r
+                       .append("<h3>Commit changes</h3><br/>")\r
+                       .append("<label for='message'>Message:</label><br/>")\r
+                       .append("<textarea id='message'>Wikidoc: " + (commentBox.hasClass("newComment") ? "added" : "modified") + " comment for " + commentBox.attr("data-comment-namespace") + "</textarea><br/>")\r
+                       .append("<input id='signOff' type='checkbox' value='Signed-off-by: " + githubAPI.getSignedOff() + "'/>")\r
+                               .change(function(e) {\r
+                                       if ($(e.target).is(':checked')) {\r
+                                               $("#commitBtn").removeAttr("disabled");\r
+                                       } else {\r
+                                               $("#commitBtn").attr("disabled", "disabled");\r
+                                       }\r
+                               })\r
+                       .append("<label for='signOff'> Signed-off-by: " + githubAPI.getSignedOff() + "</label>")\r
+                       .append(\r
+                               $("<div class='buttonArea'>")\r
+                               .append(\r
+                                       $("<button id='commitBtn' disabled='disabled' class='github'><img src='resources/icons/github-icon.png'/>Commit</button>")\r
+                                       .mousedown(function() {\r
+                                               $(this).text("Commiting...");\r
+                                       })\r
+                                       .mouseup(function() {\r
+                                               ui.commit($(this).parent().parent(), commentBox)\r
+                                       })\r
+                               )\r
+                       )\r
+                       .show()\r
+                       .css("top", "50%")\r
+                       .css("margin-top", -($('#modal').outerHeight() / 2) + "px")\r
+                       .css("left", "50%")\r
+                       .css("margin-left", -($('#modal').outerWidth() / 2) + "px");\r
+       }\r
 \r
-function closeAllCommentInEdtiting(){\r
-       $('a[id=cancelBtn]').each(function(){\r
-               closeEditing($(this));\r
-       });\r
-}\r
 \r
-function closeEditing(tag){\r
-       if(editComment > 0){ editComment -= 1; }\r
-       // Hide itself\r
-       tag.hide();\r
-       // Hide commitBtn\r
-       tag.next().hide();\r
-       // Hide Textarea\r
-       tag.prev().hide();\r
-       // Show comment\r
-       tag.prev().prev().show();\r
-}\r
+       this.commit = function(commitBox, commentBox) {\r
+               // get comments datas\r
+               var location = this.parseLocation(commentBox.attr("data-comment-location"));\r
+               var comment = commentBox.find("textarea").val();\r
 \r
-function checkSignIn(){\r
-       var response = false;\r
-       $.ajax({\r
-        beforeSend: function (xhr) {\r
-            if ($("#login").val() != ""){ xhr.setRequestHeader ("Authorization", userB64); }\r
-        },\r
-        type: "GET",\r
-        url: "https://api.github.com/repos/"+userName+"/"+githubRepo,\r
-        async:false,\r
-        dataType:'json',\r
-        success: function(success)\r
-        {\r
-       getUserInfo();\r
-       response = true;\r
-       displayMessage('You are now logged in');\r
-        },\r
-        error: function()\r
-        {\r
-       displayMessage('Error : Wrong username or password');\r
-       response = false;\r
-        }\r
-    });\r
-    return response;\r
-}\r
-\r
-function getUserInfo(){\r
-       $.ajax({\r
-        beforeSend: function (xhr) {\r
-            if ($("#login").val() != ""){ xhr.setRequestHeader ("Authorization", userB64); }\r
-        },\r
-        type: "GET",\r
-        url: "https://api.github.com/user/emails",\r
-        async:false,\r
-        dataType:'json',\r
-        success: function(success)\r
-        {\r
-       userEmail = success;\r
-        }\r
-    });\r
-}\r
+               // get file content from github\r
+               var origFile = githubAPI.getFile(location.path, $('#github-head').val());\r
+               if(!origFile.content) {\r
+                       this.openModalBox("Unable to locate source file!", origFile.status + ": " + origFile.statusText);\r
+                       return;\r
+               }\r
+               var base64Content = origFile.content.substring(0, origFile.content.length - 1)\r
+               var fileContent = Base64.decode(base64Content);\r
+\r
+               // commit\r
+               var newContent = this.mergeComment(fileContent, comment, location);\r
+               var message = commitBox.find("#message").val() + "\n\n" + commitBox.find("#signOff").val();\r
+               var response = this.pushComment($('#github-base').val(), $('#github-head').val(), location.path, newContent, message)\r
+               if(!response) {\r
+                       // abort procedure\r
+                       return;\r
+               }\r
 \r
-function getSignedOff(){\r
-       $.ajax({\r
-        beforeSend: function (xhr) {\r
-            if ($("#login").val() != ""){ xhr.setRequestHeader ("Authorization", userB64); }\r
-        },\r
-        type: "GET",\r
-        url: "https://api.github.com/users/"+userName,\r
-        async:false,\r
-        dataType:'json',\r
-        success: function(success)\r
-        {\r
-       signedOff = success.name;\r
-        }\r
-    });\r
-}\r
+               // save pull request in cookie\r
+               var requests = [];\r
+               if(!!localStorage.requests) {requests = JSON.parse(localStorage.requests)}\r
+               requests[requests.length] = {\r
+                       request: response,\r
+                       location: commentBox.attr("data-comment-location"),\r
+                       comment: Base64.encode(comment)\r
+               };\r
+               localStorage.requests = JSON.stringify(requests);\r
+               // close boxes\r
+               this.closeModalBox()\r
+               this.closeCommentBox(commentBox);\r
+               // reload comments\r
+               this.reloadComments();\r
+       }\r
 \r
-function addSignedOff(){\r
-       $.when(getUserInfo()).done(function(){\r
-               $.when(getSignedOff()).done(function(){\r
-                       $('#commitMessage').val($('#commitMessage').val() + "\n\nSigned-off-by: "+signedOff+" <"+userEmail+">");\r
-               });\r
-       });\r
-       resizeTextarea($('#commitMessage'));\r
-}\r
+       /*\r
+          Creating a new pull request with the new comment take 5 steps:\r
+               1. get the base tree from latest commit\r
+               2. create a new blob with updated file content\r
+               3. post a new tree from base tree and blob\r
+               4. post the new commit with new tree\r
+               5. create the pull request\r
+       */\r
+       this.pushComment = function(base, branch, path, content, message) {\r
+               var baseTree = githubAPI.getTree(localStorage.latestCommit);\r
+               if(!baseTree.sha) {\r
+                       this.openModalBox("Unable to locate base tree!", baseTree.status + ": " + baseTree.statusText, true);\r
+                       return false;\r
+               }\r
+               console.log("Base tree: " + baseTree.url);\r
+               var newBlob = githubAPI.createBlob(content);\r
+               if(!newBlob.sha) {\r
+                       this.openModalBox("Unable to create new blob!", newBlob.status + ": " + newBlob.statusText, true);\r
+                       return false;\r
+               }\r
+               console.log("New blob: " + newBlob.url);\r
+               var newTree = githubAPI.createTree(baseTree, path, newBlob);\r
+               if(!newTree.sha) {\r
+                       this.openModalBox("Unable to create new tree!", newTree.status + ": " + newTree.statusText, true);\r
+                       return false;\r
+               }\r
+               console.log("New tree: " + newTree.url);\r
+               var newCommit = githubAPI.createCommit(message, localStorage.latestCommit, newTree);\r
+               if(!newCommit.sha) {\r
+                       this.openModalBox("Unable to create new commit!", newCommit.status + ": " + newCommit.statusText, true);\r
+                       return false;\r
+               }\r
+               console.log("New commit: " + newCommit.url);\r
+               var pullRequest = githubAPI.createPullRequest(message.split("\n\n")[0], message, base, newCommit.sha);\r
+               if(!pullRequest.number) {\r
+                       this.openModalBox("Unable to create pull request!", pullRequest.status + ": " + pullRequest.statusText, true);\r
+                       return false;\r
+               }\r
+               console.log("New pull request: " + pullRequest.url);\r
+               return pullRequest;\r
+       }\r
 \r
-function removeSignedOff(){\r
-       $('#commitMessage').val(commitMessage);\r
-       resizeTextarea($('#commitMessage'));\r
-}\r
+       this.reloadComments = function() {\r
+               if(!localStorage.requests){ return; }\r
+               var requests = JSON.parse(localStorage.requests);\r
+               var converter = new Markdown.Converter();\r
+               // Look for modified comments in page\r
+               for(i in requests) {\r
+                       var request = requests[i];\r
+                       $("textarea[data-comment-location=\"" + request.location + "\"]").each(function () {\r
+                               var div = $(this).next();\r
+                               if(request.isClosed) {\r
+                                       if(div.is("div.comment.newComment")) {\r
+                                               // hide empty comment\r
+                                               div.next().remove();\r
+                                               div.next().find("span.noComment").show();\r
+                                               div.remove();\r
+                                       } else if(div.is("div.comment.locked")) {\r
+                                               // unlock comment\r
+                                               div.removeClass("locked");\r
+                                               div.css("cursor", "pointer")\r
+                                               div.click(function() {\r
+                                                       ui.openCommentBox(div.prev());\r
+                                               });\r
+                                               div.next().remove();\r
+                                       }\r
+                               } else {\r
+                                       // create div for the new coment\r
+                                       if(!div.is("div.comment")) {\r
+                                               $(this).after("<div class='comment newComment'></div>");\r
+                                               div = $(this).next();\r
+                                       }\r
+                                       // lock modified comment\r
+                                       if(!div.hasClass("locked")) {\r
+                                               // convert modified comment to markdown\r
+                                               div.empty()\r
+                                               div.append(converter.makeHtml(Base64.decode(request.comment)));\r
+                                               // lock click\r
+                                               div.css("cursor", "auto");\r
+                                               div.unbind("click");\r
+                                               div.addClass("locked");\r
+                                               div.after(\r
+                                                       $("<p class='locked inheritance'>")\r
+                                                       .text("comment modified in ")\r
+                                                       .append("<a href='"+ request.request.html_url +"' target='_blank' title='Review on GitHub'>pull request #"+ request.request.number +"</a>")\r
+                                                       .append(" ")\r
+                                                       .append(\r
+                                                               $("<a data-pullrequest-number='"+ request.request.number +"' class='cancel'>cancel</a>")\r
+                                                               .click(function (){\r
+                                                                       ui.closePullRequest($(this).attr("data-pullrequest-number"));\r
+                                                               })\r
+                                                       )\r
+                                               );\r
+                                       }\r
+                                       // hide "add comment" link\r
+                                       if(div.hasClass("newComment")) {\r
+                                               div.next().next().find("span.noComment").hide();\r
+                                       }\r
+                               }\r
 \r
-function resizeTextarea(element){\r
-       var nLines = element.val().split('\n').length + 1;\r
-       element.attr('rows', nLines);\r
-}\r
+                       });\r
+               }\r
+       }\r
 \r
-function showComment(element){\r
-       // Display the original comment\r
-       if (showcomment == true){\r
-               showcomment = false;\r
-               element.text("Show original comment");\r
+       this.closePullRequest = function(number) {\r
+               // close pull request\r
+               var res = githubAPI.updatePullRequest("Canceled from Wikidoc", "", "closed", number);\r
+               if(!res.id) {\r
+                       this.openModalBox("Unable to close pull request!", res.status + ": " + res.statusText, true);\r
+                       return false;\r
+               }\r
+               // remove from localstorage\r
+               var requests = JSON.parse(localStorage.requests);\r
+               for(i in requests) {\r
+                       if(requests[i].request.number == number) {\r
+                               requests[i].isClosed = true;\r
+                       }\r
+               }\r
+               localStorage.requests = JSON.stringify(requests);\r
+               ui.reloadComments()\r
        }\r
-       else{\r
-               // Show the comment updated in user's repo\r
-               showcomment = true;\r
-               element.text("Comment changed in "+githubRepo+" / "+branchName);\r
+\r
+       /* Utility */\r
+\r
+       // Extract infos from string location "../lib/standard/collection/array.nit:457,1--458,0"\r
+       this.parseLocation = function(location) {\r
+               var parts = location.split(":");\r
+               var loc = new Object();\r
+               loc.path = parts[0].substr(3, parts[0].length);\r
+               loc.lstart = parseInt(parts[1].split("--")[0].split(",")[0]);\r
+               loc.tabpos = parseInt(parts[1].split("--")[0].split(",")[1]);\r
+               loc.lend = parseInt(parts[1].split("--")[1].split(",")[0]);\r
+               return loc;\r
        }\r
-       var parent = element.parent().next(".description");\r
-       var textarea = parent.children('#fileContent');\r
-       var text = textarea.val();\r
-       var preSave = parent.children('#preSave');\r
-       textarea.val(preSave.text());\r
-       preSave.text(text);\r
-       // Resize edit box\r
-       textarea.height(textarea.prop("scrollHeight"));\r
-       resizeTextarea(textarea);\r
-}\r
 \r
-/* GitHub login box management */\r
+       // Meld modified comment into file content\r
+       this.mergeComment = function(fileContent, comment, location) {\r
+               // replace comment in file content\r
+               var res = new String();\r
+               var lines = fileContent.split("\n");\r
+               // copy lines fron 0 to lstart\r
+               for(var i = 0; i < location.lstart - 1; i++) {\r
+                       res += lines[i] + "\n";\r
+               }\r
+               // set comment\r
+               if(comment && comment != "") {\r
+                       var commentLines = comment.split("\n");\r
+                       for(var i = 0; i < commentLines.length; i++) {\r
+                               var line = commentLines[i];\r
+                               var tab = location.tabpos > 1 ? "\t" : "";\r
+                               res += tab + "# " + line + "\n";\r
+                       }\r
+               }\r
+               // copy lines fron lend to end\r
+               for(var i = location.lend - 1; i < lines.length; i++) {\r
+                       res += lines[i];\r
+                       if(i < lines.length - 1) { res += "\n"; }\r
+               }\r
+               return res;\r
+       }\r
 \r
-function createLoginBox() {\r
-       $("nav.main ul").append(\r
-               "<li id='liGitHub'>" +\r
-               "  <a class='btn' id='logGitHub'>" +\r
-               "    <img id='imgGitHub' src='resources/icons/github-icon.png' alt='GitHub'/>" +\r
-               "  </a>" +\r
-               "  <div class='popover bottom' style='display: none;'>" +\r
-               "    <div class='arrow'>&nbsp;</div>" +\r
-               "      <div class='githubTitle'>" +\r
-               "        <h3>Github Sign In</h3>" +\r
-               "      </div>" +\r
-               "      <div>" +\r
-               "        <label id='lbloginGit'>Username</label>" +\r
-               "        <input id='loginGit' name='login' type='text'/>" +\r
-               "        <label id='logginMessage'>Hello " +\r
-               "          <a id='githubAccount'><strong id='nickName'></strong></a>" +\r
-               "        </label>" +\r
-               "      </div>" +\r
-               "      <div>" +\r
-               "        <label id='lbpasswordGit'>Password</label>" +\r
-               "        <input id='passwordGit' name='password' type='password'/>" +\r
-               "        <div id='listBranches'>" +\r
-               "          <label id='lbBranches'>Branch</label>" +\r
-               "          <select class='dropdown' id='dropBranches' name='dropBranches' tabindex='1'></select>" +\r
-               "        </div>" +\r
-               "      </div>" +\r
-               "      <div>" +\r
-               "        <label id='lbrepositoryGit'>Repository</label>" +\r
-               "        <input id='repositoryGit' name='repository' type='text'/>" +\r
-               "      </div>" +\r
-               "      <div>" +\r
-               "        <label id='lbbranchGit'>Branch</label>" +\r
-               "        <input id='branchGit' name='branch' type='text'/>" +\r
-               "      </div>" +\r
-               "      <div>" +\r
-               "        <a id='signIn'>Sign In</a>" +\r
-               "      </div>" +\r
-               "    </div>" +\r
-               "  </div>" +\r
-               "</li>"\r
-       );\r
 }\r
+var ui;\r
 \r
-function toggleLoginBox(){\r
-       if ($('.popover').is(':hidden')) {\r
-               if(sessionStarted){ getListBranches(); }\r
-               $('.popover').show();\r
-       } else {\r
-               $('.popover').hide();\r
-       }\r
-       updateDisplaying();\r
-}\r
index aab4ac5..c66eb18 100644 (file)
@@ -221,7 +221,11 @@ button.github img {
 \r
 /* Comment editing */\r
 \r
-.description textarea {\r
+.commentBox {\r
+       text-align: right;\r
+}\r
+\r
+.commentBox textarea {\r
        font-family: monospace;\r
        font-size: 1em;\r
        width: 100%;\r
@@ -231,11 +235,12 @@ button.github img {
        overflow-y: hidden;\r
 }\r
 \r
-.commentBox {\r
-       text-align: right;\r
+.commentBox .preview {\r
+       margin: 0 15px;\r
+       cursor: pointer;\r
 }\r
 \r
-.commentBox .cancel{\r
+.commentBox .cancel {\r
        background-color: #b33630;\r
        background-image: -webkit-gradient(linear, left top, left bottom, from(#b33630), to(#9f312c)); /* Saf4+, Chrome */\r
        background-image: -webkit-linear-gradient(top, #b33630, #9f312c); /* Chrome 10+, Saf5.1+ */\r
@@ -250,11 +255,11 @@ button.github img {
 span.noComment a {\r
        color: #0D8921;\r
 }\r
-a.newComment: hover {\r
+span.noComment a:hover {\r
        color: #333;\r
 }\r
 \r
-pre.locked {\r
+div.comment.locked {\r
        color: gray;\r
 }\r
 p.locked {\r
index 05375d7..5be5003 100644 (file)
@@ -18,23 +18,22 @@ ul {
        list-style-type: square;\r
 }\r
 \r
-pre, code {\r
+pre, code, .description > div {\r
        white-space: pre;\r
        font-family: monospace;\r
        font-size: 1em;\r
 }\r
 \r
-pre {\r
+.description div.comment {\r
        background: #EEE;\r
-       padding: 5px;\r
        color: black;\r
        overflow: auto;\r
-       padding-left: 12px;\r
-       margin-bottom:0px;\r
+       padding: 0 0 0 12px;\r
+       margin: 1em 0 0 0;\r
 }\r
 \r
-pre.noComment {\r
-       background: transparent;\r
+.description textarea.baseComment {\r
+       display: none;\r
 }\r
 \r
 hr {\r
@@ -364,7 +363,8 @@ article .info .code {
 \r
 .content section.concerns {\r
        padding: 10px;\r
-       background: #EEE;\r
+       border: 1px solid #ccc;\r
+\r
 }\r
 \r
 .content section.concerns h2 {\r
@@ -395,10 +395,6 @@ article .info .code {
        color: #999;\r
 }\r
 \r
-.noComment {\r
-       background-color: transparent;\r
-}\r
-\r
 .show-code {\r
        margin: 0;\r
 }\r
@@ -512,22 +508,3 @@ nav h3 a.fold {
        background-color: #E0E0E0;\r
 }\r
 \r
-/* New comments style */\r
-\r
-.content .nitdoc {\r
-  background: #F7F7F7;\r
-  padding: 5px;\r
-  color: black;\r
-  overflow: auto;\r
-}\r
-\r
-.content .nitdoc pre {\r
-  background: #EEE;\r
-  border-radius: 8px;\r
-}\r
-\r
-.content .nitdoc code {\r
-  background: #DDD;\r
-  padding: 0 1px;\r
-  border-radius: 5px;\r
-}\r
index 5b5b24f..d45e1cc 100644 (file)
@@ -314,7 +314,7 @@ article .info .code {
 \r
 .content section.concerns {\r
        padding: 10px;\r
-       background: #EEE;\r
+       border: 1px solid #ccc;\r
 }\r
 \r
 .content section.concerns h2 {\r
index 017f09f..7fa887c 100644 (file)
@@ -46,6 +46,9 @@ class NitdocContext
        private var opt_custom_overview_text: OptionString = new OptionString("Text displayed as introduction of Overview page before the modules list", "--custom-overview-text")
        private var opt_custom_footer_text: OptionString = new OptionString("Text displayed as footer of all pages", "--custom-footer-text")
 
+       private var opt_github_base: OptionString = new OptionString("The branch (or git ref) edited commits will be pulled into (ex: octocat:master)", "--github-base")
+       private var opt_github_head: OptionString = new OptionString("The reference branch name used to create pull requests (ex: master)", "--github-head")
+
        init do
                toolcontext.option_context.add_option(opt_dir)
                toolcontext.option_context.add_option(opt_source)
@@ -56,6 +59,8 @@ class NitdocContext
                toolcontext.option_context.add_option(opt_custom_footer_text)
                toolcontext.option_context.add_option(opt_custom_overview_text)
                toolcontext.option_context.add_option(opt_custom_menu_items)
+               toolcontext.option_context.add_option(opt_github_base)
+               toolcontext.option_context.add_option(opt_github_head)
                toolcontext.process_options
                self.arguments = toolcontext.option_context.rest
 
@@ -205,6 +210,7 @@ abstract class NitdocPage
                append("<meta charset='utf-8'/>")
                append("<script type='text/javascript' src='scripts/jquery-1.7.1.min.js'></script>")
                append("<script type='text/javascript' src='scripts/ZeroClipboard.min.js'></script>")
+               append("<script type='text/javascript' src='scripts/Markdown.Converter.js'></script>")
                append("<script type='text/javascript' src='quicksearch-list.js'></script>")
                append("<script type='text/javascript' src='scripts/base64.js'></script>")
                append("<script type='text/javascript' src='scripts/github.js'></script>")
@@ -283,7 +289,12 @@ abstract class NitdocPage
                append("<head>")
                head
                append("</head>")
-               append("<body>")
+               append("<body")
+               if not ctx.opt_github_base.value == null and not ctx.opt_github_head.value == null then
+                       append(" data-github-base='{ctx.opt_github_base.value.as(not null)}'")
+                       append(" data-github-head='{ctx.opt_github_head.value.as(not null)}'")
+               end
+               append(">")
                header
                append("<div class='page'>")
                content
@@ -525,7 +536,7 @@ class NitdocModule
        end
 
        redef fun title do
-               if mbuilder.mmodule2nmodule.has_key(mmodule) then
+               if mbuilder.mmodule2nmodule.has_key(mmodule) and not mbuilder.mmodule2nmodule[mmodule].short_comment.is_empty then
                        var nmodule = mbuilder.mmodule2nmodule[mmodule]
                        return "{mmodule.name} module | {nmodule.short_comment}"
                else
@@ -616,11 +627,7 @@ class NitdocModule
                mmodule.html_signature(self)
                append("</div>")
                # comment
-               var nmodule = ctx.mbuilder.mmodule2nmodule[mmodule]
-               append("<section class='description'>")
-               if not nmodule.full_comment.is_empty then append("<div>{nmodule.full_comment}</div>")
-               process_generate_dot
-               append("</section>")
+               mmodule.html_comment(self)
                # classes
                var class_sorter = new MClassNameSorter
                # intro
@@ -907,9 +914,7 @@ class NitdocClass
                mclass.html_namespace(self)
                append("{mclass.html_short_signature}</div>")
                # comment
-               var nclass = ctx.mbuilder.mclassdef2nclassdef[mclass.intro]
-               append("<section class='description'>")
-               if nclass isa AStdClassdef and not nclass.full_comment.is_empty then append("<div>{nclass.full_comment}</div>")
+               mclass.html_comment(self)
                process_generate_dot
                append("</section>")
                # concerns
@@ -964,6 +969,7 @@ class NitdocClass
                # properties
                var prop_sorter = new MPropDefNameSorter
                var lmmodule = new List[MModule]
+               var nclass = ctx.mbuilder.mclassdef2nclassdef[mclass.intro]
                # virtual and formal types
                var local_vtypes = new Array[MVirtualTypeDef]
                for vt in vtypes do if not inherited.has(vt) then local_vtypes.add(vt)
@@ -1197,12 +1203,23 @@ redef class MModule
        end
 
        # Return the full comment of the module decorated with html
-       fun html_full_comment(page: NitdocPage) do
+       private fun html_comment(page: NitdocPage) do
+               page.append("<div class='description'>")
                if page.ctx.mbuilder.mmodule2nmodule.has_key(self) then
-                       page.append("<div id='description'>")
-                       page.append("<div>{page.ctx.mbuilder.mmodule2nmodule[self].full_comment}</div>")
-                       page.append("</div>")
+                       var nmodule = page.ctx.mbuilder.mmodule2nmodule[self]
+                       page.append("<textarea class='baseComment' data-comment-namespace='{full_name}' data-comment-location='{nmodule.doc_location.to_s}'>{nmodule.full_comment}</textarea>")
+                       if nmodule.full_comment == "" then
+                               page.append("<p class='info inheritance'>")
+                               page.append("<span class=\"noComment\">no comment for </span>")
+                       else
+                               page.append("<div class='comment'>{nmodule.full_markdown}</div>")
+                               page.append("<p class='info inheritance'>")
+                       end
+                       page.append("definition in ")
+                       self.html_full_namespace(page)
+                       page.append(" {page.show_source(nmodule.location)}</p>")
                end
+               page.append("</div>")
        end
 
        private fun has_mclassdef_for(mclass: MClass): Bool do
@@ -1369,35 +1386,56 @@ redef class MClass
                page.append("{html_short_signature}</div>")
        end
 
-       private fun html_comment(page: NitdocModule) do
-               page.mmodule.linearize_mclassdefs(mclassdefs)
+       private fun html_comment(page: NitdocPage) do
                page.append("<div class='description'>")
-               # comments for each mclassdef contained in current mmodule
-               for mclassdef in mclassdefs do
-                       if not mclassdef.is_intro and not page.mmodule.mclassdefs.has(mclassdef) then continue
-                       if page.ctx.mbuilder.mclassdef2nclassdef.has_key(mclassdef) then
-                               var nclass = page.ctx.mbuilder.mclassdef2nclassdef[mclassdef]
+               if page isa NitdocModule then
+                       page.mmodule.linearize_mclassdefs(mclassdefs)
+                       # comments for each mclassdef contained in current mmodule
+                       for mclassdef in mclassdefs do
+                               if not mclassdef.is_intro and not page.mmodule.mclassdefs.has(mclassdef) then continue
+                               if page.ctx.mbuilder.mclassdef2nclassdef.has_key(mclassdef) then
+                                       var nclass = page.ctx.mbuilder.mclassdef2nclassdef[mclassdef]
+                                       if nclass isa AStdClassdef then
+                                               page.append("<textarea class='baseComment' data-comment-namespace='{mclassdef.mmodule.full_name}::{name}' data-comment-location='{nclass.doc_location.to_s}'>{nclass.full_comment}</textarea>")
+                                               if nclass.full_comment == "" then
+                                                       page.append("<p class='info inheritance'>")
+                                                       page.append("<span class=\"noComment\">no comment for </span>")
+                                               else
+                                                       page.append("<div class='comment'>{nclass.full_markdown}</div>")
+                                                       page.append("<p class='info inheritance'>")
+                                               end
+                                               if mclassdef.is_intro then
+                                                       page.append("introduction in ")
+                                               else
+                                                       page.append("refinement in ")
+                                               end
+                                               mclassdef.mmodule.html_full_namespace(page)
+                                               page.append(" {page.show_source(nclass.location)}</p>")
+                                       end
+                               end
+                       end
+               else
+                       # comments for intro
+                       if page.ctx.mbuilder.mclassdef2nclassdef.has_key(intro) then
+                               var nclass = page.ctx.mbuilder.mclassdef2nclassdef[intro]
                                if nclass isa AStdClassdef then
+                                       page.append("<textarea class='baseComment' data-comment-namespace='{intro.mmodule.full_name}::{name}' data-comment-location='{nclass.doc_location.to_s}'>{nclass.full_comment}</textarea>")
                                        if nclass.full_comment == "" then
                                                page.append("<p class='info inheritance'>")
                                                page.append("<span class=\"noComment\">no comment for </span>")
                                        else
-                                               page.append("<div>{nclass.full_comment}</div>")
+                                               page.append("<div class='comment'>{nclass.full_markdown}</div>")
                                                page.append("<p class='info inheritance'>")
                                        end
-                                       if mclassdef.is_intro then
-                                               page.append("introduction in ")
-                                       else
-                                               page.append("refinement in ")
-                                       end
-                                       mclassdef.mmodule.html_full_namespace(page)
+                                       page.append("introduction in ")
+                                       intro.mmodule.html_full_namespace(page)
                                        page.append(" {page.show_source(nclass.location)}</p>")
                                end
                        end
                end
                page.append("</div>")
        end
-       
+
        private fun html_redefs(page: NitdocModule) do
                page.mmodule.linearize_mclassdefs(mclassdefs)
                page.append("<div class='refinements'>")
@@ -1536,11 +1574,12 @@ redef class MPropDef
                if not is_intro then
                        if page.ctx.mbuilder.mpropdef2npropdef.has_key(mproperty.intro) then
                                var intro_nprop = page.ctx.mbuilder.mpropdef2npropdef[mproperty.intro]
+                               page.append("<textarea class='baseComment' data-comment-namespace='{mproperty.intro.mclassdef.mmodule.full_name}::{mproperty.intro.mclassdef.mclass.name}::{mproperty.name}' data-comment-location='{intro_nprop.doc_location.to_s}'>{intro_nprop.full_comment}</textarea>")
                                if intro_nprop.full_comment.is_empty then
                                        page.append("<p class='info inheritance'>")
                                        page.append("<span class=\"noComment\">no comment for </span>")
                                else
-                                       page.append("<div>{intro_nprop.full_comment}</div>")
+                                       page.append("<div class='comment'>{intro_nprop.full_markdown}</div>")
                                        page.append("<p class='info inheritance'>")
                                end
                                page.append("introduction in ")
@@ -1550,16 +1589,17 @@ redef class MPropDef
                end
                if page.ctx.mbuilder.mpropdef2npropdef.has_key(self) then
                        var nprop = page.ctx.mbuilder.mpropdef2npropdef[self]
+                       page.append("<textarea class='baseComment' data-comment-namespace='{mclassdef.mmodule.full_name}::{mclassdef.mclass.name}::{mproperty.name}' data-comment-location='{nprop.doc_location.to_s}'>{nprop.full_comment}</textarea>")
                        if nprop.full_comment == "" then
                                page.append("<p class='info inheritance'>")
                                page.append("<span class=\"noComment\">no comment for </span>")
                        else
-                               page.append("<div>{nprop.full_comment}</div>")
+                               page.append("<div class='comment'>{nprop.full_markdown}</div>")
                                page.append("<p class='info inheritance'>")
                        end
                        if is_intro then
                                page.append("introduction in ")
-                       else 
+                       else
                                page.append("redefinition in ")
                        end
                        mclassdef.html_namespace(page)
@@ -1596,7 +1636,7 @@ redef class MMethodDef
                html_comment(page)
                page.append("</article>")
        end
-       
+
        redef fun html_info(page, ctx) do
                page.append("<div class='info'>")
                if mproperty.visibility < public_visibility then page.append("{mproperty.visibility.to_s} ")
@@ -1678,45 +1718,97 @@ end
 # Nodes redefs
 #
 
+redef class ADoc
+       private fun short_comment: String do
+               return n_comment.first.text.substring_from(2).replace("\n", "").html_escape
+       end
+
+       private fun full_comment: String do
+               var res = new Buffer
+               for t in n_comment do
+                       var text = t.text
+                       text = text.substring_from(1)
+                       if text.first == ' ' then text = text.substring_from(1)
+                       res.append(text.html_escape)
+               end
+               return res.to_s
+       end
+end
+
 redef class AModule
        private fun short_comment: String do
                if n_moduledecl != null and n_moduledecl.n_doc != null then
-                       return n_moduledecl.n_doc.n_comment.first.text.substring_from(2).replace("\n", "").html_escape
+                       return n_moduledecl.n_doc.short_comment
                end
                return ""
        end
 
        private fun full_comment: String do
                if n_moduledecl != null and n_moduledecl.n_doc != null then
+                       return n_moduledecl.n_doc.full_comment
+               end
+               return ""
+       end
+
+       private fun full_markdown: String do
+               if n_moduledecl != null and n_moduledecl.n_doc != null then
                        return n_moduledecl.n_doc.full_markdown.html
                end
                return ""
        end
+
+       private fun doc_location: Location do
+               if n_moduledecl != null and n_moduledecl.n_doc != null then
+                       return n_moduledecl.n_doc.location
+               end
+               return location
+       end
 end
 
 redef class AStdClassdef
        private fun short_comment: String do
-               if n_doc != null then return n_doc.n_comment.first.text.substring_from(2).replace("\n", "").html_escape
+               if n_doc != null then return n_doc.short_comment
                return ""
        end
 
        private fun full_comment: String do
+               if n_doc != null then return n_doc.full_comment
+               return ""
+       end
+
+       private fun full_markdown: String do
                if n_doc != null then return n_doc.full_markdown.html
                return ""
        end
+
+       private fun doc_location: Location do
+               if n_doc != null then return n_doc.location
+               return location
+       end
 end
 
 redef class APropdef
        private fun short_comment: String do
-               if n_doc != null then return n_doc.n_comment.first.text.substring_from(2).replace("\n", "").html_escape
+               if n_doc != null then return n_doc.short_comment
                return ""
        end
 
        private fun full_comment: String do
+               if n_doc != null then return n_doc.full_comment
+               return ""
+       end
+
+       private fun full_markdown: String do
                if n_doc != null then return n_doc.full_markdown.html
                return ""
        end
+
+       private fun doc_location: Location do
+               if n_doc != null then return n_doc.location
+               return location
+       end
 end
 
+
 var nitdoc = new NitdocContext
 nitdoc.generate_nitdoc