--- /dev/null
+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, """));\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, ">").replace(/</g, "<").replace(/"/g, """);\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, "&");\r
+\r
+ // Do the angle bracket song and dance:\r
+ text = text.replace(/</g, "<");\r
+ text = text.replace(/>/g, ">");\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, "&");\r
+\r
+ // Encode naked <'s\r
+ text = text.replace(/<(?![a-z\/?!]|~D)/gi, "<");\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
-/*
-* 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;
+ }
}
+
-// 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 + " <" + infos.email + ">";\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'> </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'> </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
\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
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
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
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
\r
.content section.concerns {\r
padding: 10px;\r
- background: #EEE;\r
+ border: 1px solid #ccc;\r
+\r
}\r
\r
.content section.concerns h2 {\r
color: #999;\r
}\r
\r
-.noComment {\r
- background-color: transparent;\r
-}\r
-\r
.show-code {\r
margin: 0;\r
}\r
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
\r
.content section.concerns {\r
padding: 10px;\r
- background: #EEE;\r
+ border: 1px solid #ccc;\r
}\r
\r
.content section.concerns h2 {\r
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)
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
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>")
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
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
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
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
# 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)
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
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'>")
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 ")
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)
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} ")
# 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