From: Alexandre Terrasa Date: Tue, 27 Aug 2013 17:49:00 +0000 (-0400) Subject: ni_nitdoc: Rewritting of Github plugin for nitdoc X-Git-Tag: v0.6.1^2~12 X-Git-Url: http://nitlanguage.org ni_nitdoc: Rewritting of Github plugin for nitdoc Signed-off-by: Alexandre Terrasa --- diff --git a/share/ni_nitdoc/resources/icons/github-icon-w.png b/share/ni_nitdoc/resources/icons/github-icon-w.png index ba6a6b1..98ae072 100644 Binary files a/share/ni_nitdoc/resources/icons/github-icon-w.png and b/share/ni_nitdoc/resources/icons/github-icon-w.png differ diff --git a/share/ni_nitdoc/scripts/Markdown.Converter.js b/share/ni_nitdoc/scripts/Markdown.Converter.js new file mode 100644 index 0000000..1a60277 --- /dev/null +++ b/share/ni_nitdoc/scripts/Markdown.Converter.js @@ -0,0 +1,1412 @@ +var Markdown; + +if (typeof exports === "object" && typeof require === "function") // we're in a CommonJS (e.g. Node.js) module + Markdown = exports; +else + Markdown = {}; + +// The following text is included for historical reasons, but should +// be taken with a pinch of salt; it's not all true anymore. + +// +// Wherever possible, Showdown is a straight, line-by-line port +// of the Perl version of Markdown. +// +// This is not a normal parser design; it's basically just a +// series of string substitutions. It's hard to read and +// maintain this way, but keeping Showdown close to the original +// design makes it easier to port new features. +// +// More importantly, Showdown behaves like markdown.pl in most +// edge cases. So web applications can do client-side preview +// in Javascript, and then build identical HTML on the server. +// +// This port needs the new RegExp functionality of ECMA 262, +// 3rd Edition (i.e. Javascript 1.5). Most modern web browsers +// should do fine. Even with the new regular expression features, +// We do a lot of work to emulate Perl's regex functionality. +// The tricky changes in this file mostly have the "attacklab:" +// label. Major or self-explanatory changes don't. +// +// Smart diff tools like Araxis Merge will be able to match up +// this file with markdown.pl in a useful way. A little tweaking +// helps: in a copy of markdown.pl, replace "#" with "//" and +// replace "$text" with "text". Be sure to ignore whitespace +// and line endings. +// + + +// +// Usage: +// +// var text = "Markdown *rocks*."; +// +// var converter = new Markdown.Converter(); +// var html = converter.makeHtml(text); +// +// alert(html); +// +// Note: move the sample code to the bottom of this +// file before uncommenting it. +// + +(function () { + + function identity(x) { return x; } + function returnFalse(x) { return false; } + + function HookCollection() { } + + HookCollection.prototype = { + + chain: function (hookname, func) { + var original = this[hookname]; + if (!original) + throw new Error("unknown hook " + hookname); + + if (original === identity) + this[hookname] = func; + else + this[hookname] = function (text) { + var args = Array.prototype.slice.call(arguments, 0); + args[0] = original.apply(null, args); + return func.apply(null, args); + }; + }, + set: function (hookname, func) { + if (!this[hookname]) + throw new Error("unknown hook " + hookname); + this[hookname] = func; + }, + addNoop: function (hookname) { + this[hookname] = identity; + }, + addFalse: function (hookname) { + this[hookname] = returnFalse; + } + }; + + Markdown.HookCollection = HookCollection; + + // g_urls and g_titles allow arbitrary user-entered strings as keys. This + // caused an exception (and hence stopped the rendering) when the user entered + // e.g. [push] or [__proto__]. Adding a prefix to the actual key prevents this + // (since no builtin property starts with "s_"). See + // http://meta.stackoverflow.com/questions/64655/strange-wmd-bug + // (granted, switching from Array() to Object() alone would have left only __proto__ + // to be a problem) + function SaveHash() { } + SaveHash.prototype = { + set: function (key, value) { + this["s_" + key] = value; + }, + get: function (key) { + return this["s_" + key]; + } + }; + + Markdown.Converter = function () { + var pluginHooks = this.hooks = new HookCollection(); + + // given a URL that was encountered by itself (without markup), should return the link text that's to be given to this link + pluginHooks.addNoop("plainLinkText"); + + // called with the orignal text as given to makeHtml. The result of this plugin hook is the actual markdown source that will be cooked + pluginHooks.addNoop("preConversion"); + + // called with the text once all normalizations have been completed (tabs to spaces, line endings, etc.), but before any conversions have + pluginHooks.addNoop("postNormalization"); + + // Called with the text before / after creating block elements like code blocks and lists. Note that this is called recursively + // with inner content, e.g. it's called with the full text, and then only with the content of a blockquote. The inner + // call will receive outdented text. + pluginHooks.addNoop("preBlockGamut"); + pluginHooks.addNoop("postBlockGamut"); + + // called with the text of a single block element before / after the span-level conversions (bold, code spans, etc.) have been made + pluginHooks.addNoop("preSpanGamut"); + pluginHooks.addNoop("postSpanGamut"); + + // called with the final cooked HTML code. The result of this plugin hook is the actual output of makeHtml + pluginHooks.addNoop("postConversion"); + + // + // Private state of the converter instance: + // + + // Global hashes, used by various utility routines + var g_urls; + var g_titles; + var g_html_blocks; + + // Used to track when we're inside an ordered or unordered list + // (see _ProcessListItems() for details): + var g_list_level; + + this.makeHtml = function (text) { + + // + // Main function. The order in which other subs are called here is + // essential. Link and image substitutions need to happen before + // _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the + // and tags get encoded. + // + + // This will only happen if makeHtml on the same converter instance is called from a plugin hook. + // Don't do that. + if (g_urls) + throw new Error("Recursive call to converter.makeHtml"); + + // Create the private state objects. + g_urls = new SaveHash(); + g_titles = new SaveHash(); + g_html_blocks = []; + g_list_level = 0; + + text = pluginHooks.preConversion(text); + + // attacklab: Replace ~ with ~T + // This lets us use tilde as an escape char to avoid md5 hashes + // The choice of character is arbitray; anything that isn't + // magic in Markdown will work. + text = text.replace(/~/g, "~T"); + + // attacklab: Replace $ with ~D + // RegExp interprets $ as a special character + // when it's in a replacement string + text = text.replace(/\$/g, "~D"); + + // Standardize line endings + text = text.replace(/\r\n/g, "\n"); // DOS to Unix + text = text.replace(/\r/g, "\n"); // Mac to Unix + + // Make sure text begins and ends with a couple of newlines: + text = "\n\n" + text + "\n\n"; + + // Convert all tabs to spaces. + text = _Detab(text); + + // Strip any lines consisting only of spaces and tabs. + // This makes subsequent regexen easier to write, because we can + // match consecutive blank lines with /\n+/ instead of something + // contorted like /[ \t]*\n+/ . + text = text.replace(/^[ \t]+$/mg, ""); + + text = pluginHooks.postNormalization(text); + + // Turn block-level HTML blocks into hash entries + text = _HashHTMLBlocks(text); + + // Strip link definitions, store in hashes. + text = _StripLinkDefinitions(text); + + text = _RunBlockGamut(text); + + text = _UnescapeSpecialChars(text); + + // attacklab: Restore dollar signs + text = text.replace(/~D/g, "$$"); + + // attacklab: Restore tildes + text = text.replace(/~T/g, "~"); + + text = pluginHooks.postConversion(text); + + g_html_blocks = g_titles = g_urls = null; + + return text; + }; + + function _StripLinkDefinitions(text) { + // + // Strips link definitions from text, stores the URLs and titles in + // hash references. + // + + // Link defs are in the form: ^[id]: url "optional title" + + /* + text = text.replace(/ + ^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1 + [ \t]* + \n? // maybe *one* newline + [ \t]* + ? // url = $2 + (?=\s|$) // lookahead for whitespace instead of the lookbehind removed below + [ \t]* + \n? // maybe one newline + [ \t]* + ( // (potential) title = $3 + (\n*) // any lines skipped = $4 attacklab: lookbehind removed + [ \t]+ + ["(] + (.+?) // title = $5 + [")] + [ \t]* + )? // title is optional + (?:\n+|$) + /gm, function(){...}); + */ + + text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*?(?=\s|$)[ \t]*\n?[ \t]*((\n*)["(](.+?)[")][ \t]*)?(?:\n+)/gm, + function (wholeMatch, m1, m2, m3, m4, m5) { + m1 = m1.toLowerCase(); + g_urls.set(m1, _EncodeAmpsAndAngles(m2)); // Link IDs are case-insensitive + if (m4) { + // Oops, found blank lines, so it's not a title. + // Put back the parenthetical statement we stole. + return m3; + } else if (m5) { + g_titles.set(m1, m5.replace(/"/g, """)); + } + + // Completely remove the definition from the text + return ""; + } + ); + + return text; + } + + function _HashHTMLBlocks(text) { + + // Hashify HTML blocks: + // We only want to do this for block-level HTML tags, such as headers, + // lists, and tables. That's because we still want to wrap

s around + // "paragraphs" that are wrapped in non-block-level tags, such as anchors, + // phrase emphasis, and spans. The list of tags we're looking for is + // hard-coded: + var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del" + var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math" + + // First, look for nested blocks, e.g.: + //

+ //
+ // tags for inner block must be indented. + //
+ //
+ // + // The outermost tags must start at the left margin for this to match, and + // the inner nested divs must be indented. + // We need to do this before the next, more liberal match, because the next + // match will start at the first `
` and stop at the first `
`. + + // attacklab: This regex can be expensive when it fails. + + /* + text = text.replace(/ + ( // save in $1 + ^ // start of line (with /m) + <($block_tags_a) // start tag = $2 + \b // word break + // attacklab: hack around khtml/pcre bug... + [^\r]*?\n // any number of lines, minimally matching + // the matching end tag + [ \t]* // trailing spaces/tabs + (?=\n+) // followed by a newline + ) // attacklab: there are sentinel newlines at end of document + /gm,function(){...}}; + */ + 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); + + // + // Now match more liberally, simply from `\n` to `\n` + // + + /* + text = text.replace(/ + ( // save in $1 + ^ // start of line (with /m) + <($block_tags_b) // start tag = $2 + \b // word break + // attacklab: hack around khtml/pcre bug... + [^\r]*? // any number of lines, minimally matching + .* // the matching end tag + [ \t]* // trailing spaces/tabs + (?=\n+) // followed by a newline + ) // attacklab: there are sentinel newlines at end of document + /gm,function(){...}}; + */ + 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); + + // Special case just for
. It was easier to make a special case than + // to make the other regex more complicated. + + /* + text = text.replace(/ + \n // Starting after a blank line + [ ]{0,3} + ( // save in $1 + (<(hr) // start tag = $2 + \b // word break + ([^<>])*? + \/?>) // the matching end tag + [ \t]* + (?=\n{2,}) // followed by a blank line + ) + /g,hashElement); + */ + text = text.replace(/\n[ ]{0,3}((<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g, hashElement); + + // Special case for standalone HTML comments: + + /* + text = text.replace(/ + \n\n // Starting after a blank line + [ ]{0,3} // attacklab: g_tab_width - 1 + ( // save in $1 + -]|-[^>])(?:[^-]|-[^-])*)--) // see http://www.w3.org/TR/html-markup/syntax.html#comments and http://meta.stackoverflow.com/q/95256 + > + [ \t]* + (?=\n{2,}) // followed by a blank line + ) + /g,hashElement); + */ + text = text.replace(/\n\n[ ]{0,3}(-]|-[^>])(?:[^-]|-[^-])*)--)>[ \t]*(?=\n{2,}))/g, hashElement); + + // PHP and ASP-style processor instructions ( and <%...%>) + + /* + text = text.replace(/ + (?: + \n\n // Starting after a blank line + ) + ( // save in $1 + [ ]{0,3} // attacklab: g_tab_width - 1 + (?: + <([?%]) // $2 + [^\r]*? + \2> + ) + [ \t]* + (?=\n{2,}) // followed by a blank line + ) + /g,hashElement); + */ + text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g, hashElement); + + return text; + } + + function hashElement(wholeMatch, m1) { + var blockText = m1; + + // Undo double lines + blockText = blockText.replace(/^\n+/, ""); + + // strip trailing blank lines + blockText = blockText.replace(/\n+$/g, ""); + + // Replace the element text with a marker ("~KxK" where x is its key) + blockText = "\n\n~K" + (g_html_blocks.push(blockText) - 1) + "K\n\n"; + + return blockText; + } + + var blockGamutHookCallback = function (t) { return _RunBlockGamut(t); } + + function _RunBlockGamut(text, doNotUnhash) { + // + // These are all the transformations that form block-level + // tags like paragraphs, headers, and list items. + // + + text = pluginHooks.preBlockGamut(text, blockGamutHookCallback); + + text = _DoHeaders(text); + + // Do Horizontal Rules: + var replacement = "
\n"; + text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm, replacement); + text = text.replace(/^[ ]{0,2}([ ]?-[ ]?){3,}[ \t]*$/gm, replacement); + text = text.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm, replacement); + + text = _DoLists(text); + text = _DoCodeBlocks(text); + text = _DoBlockQuotes(text); + + text = pluginHooks.postBlockGamut(text, blockGamutHookCallback); + + // We already ran _HashHTMLBlocks() before, in Markdown(), but that + // was to escape raw HTML in the original Markdown source. This time, + // we're escaping the markup we've just created, so that we don't wrap + //

tags around block-level tags. + text = _HashHTMLBlocks(text); + text = _FormParagraphs(text, doNotUnhash); + + return text; + } + + function _RunSpanGamut(text) { + // + // These are all the transformations that occur *within* block-level + // tags like paragraphs, headers, and list items. + // + + text = pluginHooks.preSpanGamut(text); + + text = _DoCodeSpans(text); + text = _EscapeSpecialCharsWithinTagAttributes(text); + text = _EncodeBackslashEscapes(text); + + // Process anchor and image tags. Images must come first, + // because ![foo][f] looks like an anchor. + text = _DoImages(text); + text = _DoAnchors(text); + + // Make links out of things like `` + // Must come after _DoAnchors(), because you can use < and > + // delimiters in inline links like [this](). + text = _DoAutoLinks(text); + + text = text.replace(/~P/g, "://"); // put in place to prevent autolinking; reset now + + text = _EncodeAmpsAndAngles(text); + text = _DoItalicsAndBold(text); + + // Do hard breaks: + text = text.replace(/ +\n/g, "
\n"); + + text = pluginHooks.postSpanGamut(text); + + return text; + } + + function _EscapeSpecialCharsWithinTagAttributes(text) { + // + // Within tags -- meaning between < and > -- encode [\ ` * _] so they + // don't conflict with their use in Markdown for code, italics and strong. + // + + // Build a regex to find HTML tags and comments. See Friedl's + // "Mastering Regular Expressions", 2nd Ed., pp. 200-201. + + // SE: changed the comment part of the regex + + var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|-]|-[^>])(?:[^-]|-[^-])*)--)>)/gi; + + text = text.replace(regex, function (wholeMatch) { + var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g, "$1`"); + tag = escapeCharacters(tag, wholeMatch.charAt(1) == "!" ? "\\`*_/" : "\\`*_"); // also escape slashes in comments to prevent autolinking there -- http://meta.stackoverflow.com/questions/95987 + return tag; + }); + + return text; + } + + function _DoAnchors(text) { + // + // Turn Markdown link shortcuts into XHTML
tags. + // + // + // First, handle reference-style links: [link text] [id] + // + + /* + text = text.replace(/ + ( // wrap whole match in $1 + \[ + ( + (?: + \[[^\]]*\] // allow brackets nested one level + | + [^\[] // or anything else + )* + ) + \] + + [ ]? // one optional space + (?:\n[ ]*)? // one optional newline followed by spaces + + \[ + (.*?) // id = $3 + \] + ) + ()()()() // pad remaining backreferences + /g, writeAnchorTag); + */ + text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeAnchorTag); + + // + // Next, inline-style links: [link text](url "optional title") + // + + /* + text = text.replace(/ + ( // wrap whole match in $1 + \[ + ( + (?: + \[[^\]]*\] // allow brackets nested one level + | + [^\[\]] // or anything else + )* + ) + \] + \( // literal paren + [ \t]* + () // no id, so leave $3 empty + ? + [ \t]* + ( // $5 + (['"]) // quote char = $6 + (.*?) // Title = $7 + \6 // matching quote + [ \t]* // ignore any spaces/tabs between closing quote and ) + )? // title is optional + \) + ) + /g, writeAnchorTag); + */ + + text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeAnchorTag); + + // + // Last, handle reference-style shortcuts: [link text] + // These must come last in case you've also got [link test][1] + // or [link test](/foo) + // + + /* + text = text.replace(/ + ( // wrap whole match in $1 + \[ + ([^\[\]]+) // link text = $2; can't contain '[' or ']' + \] + ) + ()()()()() // pad rest of backreferences + /g, writeAnchorTag); + */ + text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag); + + return text; + } + + function writeAnchorTag(wholeMatch, m1, m2, m3, m4, m5, m6, m7) { + if (m7 == undefined) m7 = ""; + var whole_match = m1; + var link_text = m2.replace(/:\/\//g, "~P"); // to prevent auto-linking withing the link. will be converted back after the auto-linker runs + var link_id = m3.toLowerCase(); + var url = m4; + var title = m7; + + if (url == "") { + if (link_id == "") { + // lower-case and turn embedded newlines into spaces + link_id = link_text.toLowerCase().replace(/ ?\n/g, " "); + } + url = "#" + link_id; + + if (g_urls.get(link_id) != undefined) { + url = g_urls.get(link_id); + if (g_titles.get(link_id) != undefined) { + title = g_titles.get(link_id); + } + } + else { + if (whole_match.search(/\(\s*\)$/m) > -1) { + // Special case for explicit empty url + url = ""; + } else { + return whole_match; + } + } + } + url = encodeProblemUrlChars(url); + url = escapeCharacters(url, "*_"); + var result = ""; + + return result; + } + + function _DoImages(text) { + // + // Turn Markdown image shortcuts into tags. + // + + // + // First, handle reference-style labeled images: ![alt text][id] + // + + /* + text = text.replace(/ + ( // wrap whole match in $1 + !\[ + (.*?) // alt text = $2 + \] + + [ ]? // one optional space + (?:\n[ ]*)? // one optional newline followed by spaces + + \[ + (.*?) // id = $3 + \] + ) + ()()()() // pad rest of backreferences + /g, writeImageTag); + */ + text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeImageTag); + + // + // Next, handle inline images: ![alt text](url "optional title") + // Don't forget: encode * and _ + + /* + text = text.replace(/ + ( // wrap whole match in $1 + !\[ + (.*?) // alt text = $2 + \] + \s? // One optional whitespace character + \( // literal paren + [ \t]* + () // no id, so leave $3 empty + ? // src url = $4 + [ \t]* + ( // $5 + (['"]) // quote char = $6 + (.*?) // title = $7 + \6 // matching quote + [ \t]* + )? // title is optional + \) + ) + /g, writeImageTag); + */ + text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeImageTag); + + return text; + } + + function attributeEncode(text) { + // unconditionally replace angle brackets here -- what ends up in an attribute (e.g. alt or title) + // never makes sense to have verbatim HTML in it (and the sanitizer would totally break it) + return text.replace(/>/g, ">").replace(/" + _RunSpanGamut(m1) + "\n\n"; } + ); + + text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm, + function (matchFound, m1) { return "

" + _RunSpanGamut(m1) + "

\n\n"; } + ); + + // atx-style headers: + // # Header 1 + // ## Header 2 + // ## Header 2 with closing hashes ## + // ... + // ###### Header 6 + // + + /* + text = text.replace(/ + ^(\#{1,6}) // $1 = string of #'s + [ \t]* + (.+?) // $2 = Header text + [ \t]* + \#* // optional closing #'s (not counted) + \n+ + /gm, function() {...}); + */ + + text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm, + function (wholeMatch, m1, m2) { + var h_level = m1.length; + return "" + _RunSpanGamut(m2) + "\n\n"; + } + ); + + return text; + } + + function _DoLists(text, isInsideParagraphlessListItem) { + // + // Form HTML ordered (numbered) and unordered (bulleted) lists. + // + + // attacklab: add sentinel to hack around khtml/safari bug: + // http://bugs.webkit.org/show_bug.cgi?id=11231 + text += "~0"; + + // Re-usable pattern to match any entirel ul or ol list: + + /* + var whole_list = / + ( // $1 = whole list + ( // $2 + [ ]{0,3} // attacklab: g_tab_width - 1 + ([*+-]|\d+[.]) // $3 = first list item marker + [ \t]+ + ) + [^\r]+? + ( // $4 + ~0 // sentinel for workaround; should be $ + | + \n{2,} + (?=\S) + (?! // Negative lookahead for another list item marker + [ \t]* + (?:[*+-]|\d+[.])[ \t]+ + ) + ) + ) + /g + */ + var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm; + + if (g_list_level) { + text = text.replace(whole_list, function (wholeMatch, m1, m2) { + var list = m1; + var list_type = (m2.search(/[*+-]/g) > -1) ? "ul" : "ol"; + + var result = _ProcessListItems(list, list_type, isInsideParagraphlessListItem); + + // Trim any trailing whitespace, to put the closing `` + // up on the preceding line, to get it past the current stupid + // HTML block parser. This is a hack to work around the terrible + // hack that is the HTML block parser. + result = result.replace(/\s+$/, ""); + result = "<" + list_type + ">" + result + "\n"; + return result; + }); + } else { + whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g; + text = text.replace(whole_list, function (wholeMatch, m1, m2, m3) { + var runup = m1; + var list = m2; + + var list_type = (m3.search(/[*+-]/g) > -1) ? "ul" : "ol"; + var result = _ProcessListItems(list, list_type); + result = runup + "<" + list_type + ">\n" + result + "\n"; + return result; + }); + } + + // attacklab: strip sentinel + text = text.replace(/~0/, ""); + + return text; + } + + var _listItemMarkers = { ol: "\\d+[.]", ul: "[*+-]" }; + + function _ProcessListItems(list_str, list_type, isInsideParagraphlessListItem) { + // + // Process the contents of a single ordered or unordered list, splitting it + // into individual list items. + // + // list_type is either "ul" or "ol". + + // The $g_list_level global keeps track of when we're inside a list. + // Each time we enter a list, we increment it; when we leave a list, + // we decrement. If it's zero, we're not in a list anymore. + // + // We do this because when we're not inside a list, we want to treat + // something like this: + // + // I recommend upgrading to version + // 8. Oops, now this line is treated + // as a sub-list. + // + // As a single paragraph, despite the fact that the second line starts + // with a digit-period-space sequence. + // + // Whereas when we're inside a list (or sub-list), that line will be + // treated as the start of a sub-list. What a kludge, huh? This is + // an aspect of Markdown's syntax that's hard to parse perfectly + // without resorting to mind-reading. Perhaps the solution is to + // change the syntax rules such that sub-lists must start with a + // starting cardinal number; e.g. "1." or "a.". + + g_list_level++; + + // trim trailing blank lines: + list_str = list_str.replace(/\n{2,}$/, "\n"); + + // attacklab: add sentinel to emulate \z + list_str += "~0"; + + // In the original attacklab showdown, list_type was not given to this function, and anything + // that matched /[*+-]|\d+[.]/ would just create the next
  • , causing this mismatch: + // + // Markdown rendered by WMD rendered by MarkdownSharp + // ------------------------------------------------------------------ + // 1. first 1. first 1. first + // 2. second 2. second 2. second + // - third 3. third * third + // + // We changed this to behave identical to MarkdownSharp. This is the constructed RegEx, + // with {MARKER} being one of \d+[.] or [*+-], depending on list_type: + + /* + list_str = list_str.replace(/ + (^[ \t]*) // leading whitespace = $1 + ({MARKER}) [ \t]+ // list marker = $2 + ([^\r]+? // list item text = $3 + (\n+) + ) + (?= + (~0 | \2 ({MARKER}) [ \t]+) + ) + /gm, function(){...}); + */ + + var marker = _listItemMarkers[list_type]; + var re = new RegExp("(^[ \\t]*)(" + marker + ")[ \\t]+([^\\r]+?(\\n+))(?=(~0|\\1(" + marker + ")[ \\t]+))", "gm"); + var last_item_had_a_double_newline = false; + list_str = list_str.replace(re, + function (wholeMatch, m1, m2, m3) { + var item = m3; + var leading_space = m1; + var ends_with_double_newline = /\n\n$/.test(item); + var contains_double_newline = ends_with_double_newline || item.search(/\n{2,}/) > -1; + + if (contains_double_newline || last_item_had_a_double_newline) { + item = _RunBlockGamut(_Outdent(item), /* doNotUnhash = */true); + } + else { + // Recursion for sub-lists: + item = _DoLists(_Outdent(item), /* isInsideParagraphlessListItem= */ true); + item = item.replace(/\n$/, ""); // chomp(item) + if (!isInsideParagraphlessListItem) // only the outer-most item should run this, otherwise it's run multiple times for the inner ones + item = _RunSpanGamut(item); + } + last_item_had_a_double_newline = ends_with_double_newline; + return "
  • " + item + "
  • \n"; + } + ); + + // attacklab: strip sentinel + list_str = list_str.replace(/~0/g, ""); + + g_list_level--; + return list_str; + } + + function _DoCodeBlocks(text) { + // + // Process Markdown `
    ` blocks.
    +            //  
    +
    +            /*
    +            text = text.replace(/
    +                (?:\n\n|^)
    +                (                               // $1 = the code block -- one or more lines, starting with a space/tab
    +                    (?:
    +                        (?:[ ]{4}|\t)           // Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
    +                        .*\n+
    +                    )+
    +                )
    +                (\n*[ ]{0,3}[^ \t\n]|(?=~0))    // attacklab: g_tab_width
    +            /g ,function(){...});
    +            */
    +
    +            // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
    +            text += "~0";
    +
    +            text = text.replace(/(?:\n\n|^\n?)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
    +                function (wholeMatch, m1, m2) {
    +                    var codeblock = m1;
    +                    var nextChar = m2;
    +
    +                    codeblock = _EncodeCode(_Outdent(codeblock));
    +                    codeblock = _Detab(codeblock);
    +                    codeblock = codeblock.replace(/^\n+/g, ""); // trim leading newlines
    +                    codeblock = codeblock.replace(/\n+$/g, ""); // trim trailing whitespace
    +
    +                    codeblock = "
    " + codeblock + "\n
    "; + + return "\n\n" + codeblock + "\n\n" + nextChar; + } + ); + + // attacklab: strip sentinel + text = text.replace(/~0/, ""); + + return text; + } + + function hashBlock(text) { + text = text.replace(/(^\n+|\n+$)/g, ""); + return "\n\n~K" + (g_html_blocks.push(text) - 1) + "K\n\n"; + } + + function _DoCodeSpans(text) { + // + // * Backtick quotes are used for spans. + // + // * You can use multiple backticks as the delimiters if you want to + // include literal backticks in the code span. So, this input: + // + // Just type ``foo `bar` baz`` at the prompt. + // + // Will translate to: + // + //

    Just type foo `bar` baz at the prompt.

    + // + // There's no arbitrary limit to the number of backticks you + // can use as delimters. If you need three consecutive backticks + // in your code, use four for delimiters, etc. + // + // * You can use spaces to get literal backticks at the edges: + // + // ... type `` `bar` `` ... + // + // Turns to: + // + // ... type `bar` ... + // + + /* + text = text.replace(/ + (^|[^\\]) // Character before opening ` can't be a backslash + (`+) // $2 = Opening run of ` + ( // $3 = The code block + [^\r]*? + [^`] // attacklab: work around lack of lookbehind + ) + \2 // Matching closer + (?!`) + /gm, function(){...}); + */ + + text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm, + function (wholeMatch, m1, m2, m3, m4) { + var c = m3; + c = c.replace(/^([ \t]*)/g, ""); // leading whitespace + c = c.replace(/[ \t]*$/g, ""); // trailing whitespace + c = _EncodeCode(c); + 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. + return m1 + "" + c + ""; + } + ); + + return text; + } + + function _EncodeCode(text) { + // + // Encode/escape certain characters inside Markdown code runs. + // The point is that in code, these characters are literals, + // and lose their special Markdown meanings. + // + // Encode all ampersands; HTML entities are not + // entities within a Markdown code span. + text = text.replace(/&/g, "&"); + + // Do the angle bracket song and dance: + text = text.replace(//g, ">"); + + // Now, escape characters that are magic in Markdown: + text = escapeCharacters(text, "\*_{}[]\\", false); + + // jj the line above breaks this: + //--- + + //* Item + + // 1. Subitem + + // special char: * + //--- + + return text; + } + + function _DoItalicsAndBold(text) { + + // must go first: + text = text.replace(/([\W_]|^)(\*\*|__)(?=\S)([^\r]*?\S[\*_]*)\2([\W_]|$)/g, + "$1$3$4"); + + text = text.replace(/([\W_]|^)(\*|_)(?=\S)([^\r\*_]*?\S)\2([\W_]|$)/g, + "$1$3$4"); + + return text; + } + + function _DoBlockQuotes(text) { + + /* + text = text.replace(/ + ( // Wrap whole match in $1 + ( + ^[ \t]*>[ \t]? // '>' at the start of a line + .+\n // rest of the first line + (.+\n)* // subsequent consecutive lines + \n* // blanks + )+ + ) + /gm, function(){...}); + */ + + text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm, + function (wholeMatch, m1) { + var bq = m1; + + // attacklab: hack around Konqueror 3.5.4 bug: + // "----------bug".replace(/^-/g,"") == "bug" + + bq = bq.replace(/^[ \t]*>[ \t]?/gm, "~0"); // trim one level of quoting + + // attacklab: clean up hack + bq = bq.replace(/~0/g, ""); + + bq = bq.replace(/^[ \t]+$/gm, ""); // trim whitespace-only lines + bq = _RunBlockGamut(bq); // recurse + + bq = bq.replace(/(^|\n)/g, "$1 "); + // These leading spaces screw with
     content, so we need to fix that:
    +                    bq = bq.replace(
    +                            /(\s*
    [^\r]+?<\/pre>)/gm,
    +                        function (wholeMatch, m1) {
    +                            var pre = m1;
    +                            // attacklab: hack around Konqueror 3.5.4 bug:
    +                            pre = pre.replace(/^  /mg, "~0");
    +                            pre = pre.replace(/~0/g, "");
    +                            return pre;
    +                        });
    +
    +                    return hashBlock("
    \n" + bq + "\n
    "); + } + ); + return text; + } + + function _FormParagraphs(text, doNotUnhash) { + // + // Params: + // $text - string to process with html

    tags + // + + // Strip leading and trailing lines: + text = text.replace(/^\n+/g, ""); + text = text.replace(/\n+$/g, ""); + + var grafs = text.split(/\n{2,}/g); + var grafsOut = []; + + var markerRe = /~K(\d+)K/; + + // + // Wrap

    tags. + // + var end = grafs.length; + for (var i = 0; i < end; i++) { + var str = grafs[i]; + + // if this is an HTML marker, copy it + if (markerRe.test(str)) { + grafsOut.push(str); + } + else if (/\S/.test(str)) { + str = _RunSpanGamut(str); + str = str.replace(/^([ \t]*)/g, "

    "); + str += "

    " + grafsOut.push(str); + } + + } + // + // Unhashify HTML blocks + // + if (!doNotUnhash) { + end = grafsOut.length; + for (var i = 0; i < end; i++) { + var foundAny = true; + while (foundAny) { // we may need several runs, since the data may be nested + foundAny = false; + grafsOut[i] = grafsOut[i].replace(/~K(\d+)K/g, function (wholeMatch, id) { + foundAny = true; + return g_html_blocks[id]; + }); + } + } + } + return grafsOut.join("\n\n"); + } + + function _EncodeAmpsAndAngles(text) { + // Smart processing for ampersands and angle brackets that need to be encoded. + + // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin: + // http://bumppo.net/projects/amputator/ + text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g, "&"); + + // Encode naked <'s + text = text.replace(/<(?![a-z\/?!]|~D)/gi, "<"); + + return text; + } + + function _EncodeBackslashEscapes(text) { + // + // Parameter: String. + // Returns: The string, with after processing the following backslash + // escape sequences. + // + + // attacklab: The polite way to do this is with the new + // escapeCharacters() function: + // + // text = escapeCharacters(text,"\\",true); + // text = escapeCharacters(text,"`*_{}[]()>#+-.!",true); + // + // ...but we're sidestepping its use of the (slow) RegExp constructor + // as an optimization for Firefox. This function gets called a LOT. + + text = text.replace(/\\(\\)/g, escapeCharacters_callback); + text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g, escapeCharacters_callback); + return text; + } + + var charInsideUrl = "[-A-Z0-9+&@#/%?=~_|[\\]()!:,.;]", + charEndingUrl = "[-A-Z0-9+&@#/%=~_|[\\])]", + autoLinkRegex = new RegExp("(=\"|<)?\\b(https?|ftp)(://" + charInsideUrl + "*" + charEndingUrl + ")(?=$|\\W)", "gi"), + endCharRegex = new RegExp(charEndingUrl, "i"); + + function handleTrailingParens(wholeMatch, lookbehind, protocol, link) { + if (lookbehind) + return wholeMatch; + if (link.charAt(link.length - 1) !== ")") + return "<" + protocol + link + ">"; + var parens = link.match(/[()]/g); + var level = 0; + for (var i = 0; i < parens.length; i++) { + if (parens[i] === "(") { + if (level <= 0) + level = 1; + else + level++; + } + else { + level--; + } + } + var tail = ""; + if (level < 0) { + var re = new RegExp("\\){1," + (-level) + "}$"); + link = link.replace(re, function (trailingParens) { + tail = trailingParens; + return ""; + }); + } + if (tail) { + var lastChar = link.charAt(link.length - 1); + if (!endCharRegex.test(lastChar)) { + tail = lastChar + tail; + link = link.substr(0, link.length - 1); + } + } + return "<" + protocol + link + ">" + tail; + } + + function _DoAutoLinks(text) { + + // note that at this point, all other URL in the text are already hyperlinked as
    + // *except* for the case + + // automatically add < and > around unadorned raw hyperlinks + // must be preceded by a non-word character (and not by =" or <) and followed by non-word/EOF character + // simulating the lookbehind in a consuming way is okay here, since a URL can neither and with a " nor + // with a <, so there is no risk of overlapping matches. + text = text.replace(autoLinkRegex, handleTrailingParens); + + // autolink anything like + + var replacer = function (wholematch, m1) { return "" + pluginHooks.plainLinkText(m1) + ""; } + text = text.replace(/<((https?|ftp):[^'">\s]+)>/gi, replacer); + + // Email addresses: + /* + text = text.replace(/ + < + (?:mailto:)? + ( + [-.\w]+ + \@ + [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ + ) + > + /gi, _DoAutoLinks_callback()); + */ + + /* disabling email autolinking, since we don't do that on the server, either + text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi, + function(wholeMatch,m1) { + return _EncodeEmailAddress( _UnescapeSpecialChars(m1) ); + } + ); + */ + return text; + } + + function _UnescapeSpecialChars(text) { + // + // Swap back in all the special characters we've hidden. + // + text = text.replace(/~E(\d+)E/g, + function (wholeMatch, m1) { + var charCodeToReplace = parseInt(m1); + return String.fromCharCode(charCodeToReplace); + } + ); + return text; + } + + function _Outdent(text) { + // + // Remove one level of line-leading tabs or spaces + // + + // attacklab: hack around Konqueror 3.5.4 bug: + // "----------bug".replace(/^-/g,"") == "bug" + + text = text.replace(/^(\t|[ ]{1,4})/gm, "~0"); // attacklab: g_tab_width + + // attacklab: clean up hack + text = text.replace(/~0/g, "") + + return text; + } + + function _Detab(text) { + if (!/\t/.test(text)) + return text; + + var spaces = [" ", " ", " ", " "], + skew = 0, + v; + + return text.replace(/[\n\t]/g, function (match, offset) { + if (match === "\n") { + skew = offset + 1; + return match; + } + v = (offset - skew) % 4; + skew = offset + 1; + return spaces[v]; + }); + } + + // + // attacklab: Utility functions + // + + var _problemUrlChars = /(?:["'*()[\]:]|~D)/g; + + // hex-encodes some unusual "problem" chars in URLs to avoid URL detection problems + function encodeProblemUrlChars(url) { + if (!url) + return ""; + + var len = url.length; + + return url.replace(_problemUrlChars, function (match, offset) { + if (match == "~D") // escape for dollar + return "%24"; + if (match == ":") { + if (offset == len - 1 || /[0-9\/]/.test(url.charAt(offset + 1))) + return ":" + } + return "%" + match.charCodeAt(0).toString(16); + }); + } + + + function escapeCharacters(text, charsToEscape, afterBackslash) { + // First we have to escape the escape characters so that + // we can build a character class out of them + var regexString = "([" + charsToEscape.replace(/([\[\]\\])/g, "\\$1") + "])"; + + if (afterBackslash) { + regexString = "\\\\" + regexString; + } + + var regex = new RegExp(regexString, "g"); + text = text.replace(regex, escapeCharacters_callback); + + return text; + } + + + function escapeCharacters_callback(wholeMatch, m1) { + var charCodeToEscape = m1.charCodeAt(0); + return "~E" + charCodeToEscape + "E"; + } + + }; // end of the Markdown.Converter constructor + +})(); diff --git a/share/ni_nitdoc/scripts/base64.js b/share/ni_nitdoc/scripts/base64.js index 6b648b9..1c23d21 100644 --- a/share/ni_nitdoc/scripts/base64.js +++ b/share/ni_nitdoc/scripts/base64.js @@ -1,110 +1,135 @@ -/* -* 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; + } } + diff --git a/share/ni_nitdoc/scripts/github.js b/share/ni_nitdoc/scripts/github.js index f8346a8..6ab4f39 100644 --- a/share/ni_nitdoc/scripts/github.js +++ b/share/ni_nitdoc/scripts/github.js @@ -1,1041 +1,813 @@ -// User -var userB64 = null; -var userName = ""; -var password = ""; -var sessionStarted = false; -var editComment = 0; -var currentfileContent = ''; -var originalFileContent = ''; -var addNewComment = false; -var commentLineStart; -var commentLineEnd; - -// SHA GitHub -var shaLastCommit = ""; -var shaBaseTree; -var shaNewTree; -var shaNewCommit; -var shaBlob; -var shaMaster; -var repoExist = false; -var branchExist = false; -var githubRepo; -var loginProcess = false; -var signedOff = ''; -var userEmail = ''; -var commitMessage = ''; -var numComment = ''; -var showcomment = false; +$(document).ready(function() { + // set ui elements + ui = new GitHubUI(); + ui.init(); + + // check cookie at startup + sessionCookie = new SessionCookie("nitdoc_github_session"); + var session = sessionCookie.getSession(); + //checkCookie() + if(session) { + githubAPI = new GitHubAPI(session.user, session.password, session.repo) + ui.activate(); + console.log("Session started from cookie (head: "+ $("body").attr("data-github-head") +", head: "+ $("body").attr("data-github-base") +")"); -// Spinner vars -var opts = { - lines: 11, // The number of lines to draw - length: 7, // The length of each line - width: 4, // The line thickness - radius: 10, // The radius of the inner circle - corners: 1, // Corner roundness (0..1) - rotate: 0, // The rotation offset - color: '#FFF', // #rgb or #rrggbb - speed: 1, // Rounds per second - trail: 60, // Afterglow percentage - shadow: false, // Whether to render a shadow - hwaccel: false, // Whether to use hardware acceleration - className: 'spinner', // The CSS class to assign to the spinner - zIndex: 99999, // The z-index (defaults to 2000000000) - top: '300', // Top position relative to parent in px - left: 'auto' // Left position relative to parent in px - }; -var targetSpinner = document.getElementById('waitCommit'); -var spinner = new Spinner(opts).spin(targetSpinner); + } else { + console.log("No cookie found"); + } +}); // Check if a comment is editing window.onbeforeunload = function() { - if(editComment > 0){ + if(ui.openedComments > 0){ return 'Are you sure you want to leave this page?'; } }; -$(document).ready(function() { - createLoginBox(); - // Hide edit tags - $('textarea').hide(); - $('a[id=commitBtn]').hide(); - $('a[id=cancelBtn]').hide(); - // Display Login modal - $("#logGitHub").click(function(){ toggleLoginBox(); }); - // Update display - updateDisplaying(); - // If cookie existing the session is opened - if(sessionStarted){ userB64 = "Basic " + getUserPass("logginNitdoc"); } - - // Sign In an github user or Log out him - $("#signIn").click(function(){ - if(!sessionStarted){ - if($('#loginGit').val() == "" || $('#passwordGit').val() == ""){ displayMessage('Please informed login/password field!', 40, 45); } - else - { - userName = $('#loginGit').val(); - password = $('#passwordGit').val(); - githubRepo = $('#repositoryGit').val(); - branchName = $('#branchGit').val(); - userB64 = "Basic " + base64.encode(userName+':'+password); - if(checkSignIn()){ - // Check if repo exist - isRepoExisting(); - if(repoExist){ - $.when(isBranchExisting()).done(function(){ - loginProcess = true; - if(branchExist){ - setCookie("logginNitdoc", base64.encode(userName+':'+password+':'+githubRepo+':'+branchName), 1); - $('#loginGit').val(""); - $('#passwordGit').val(""); - reloadComment(); - } - }); - } - } +/* GitHub API */ + +function GitHubAPI(login, password, repo) { + this.login = login; + this.password = password; + this.repo = repo; + this.auth = "Basic " + Base64.encode(login + ':' + password); + + /* GitHub Account */ + + // try to login to github API + this.tryLogin = function() { + var res = false; + $.ajax({ + beforeSend: function (xhr) { + xhr.setRequestHeader ("Authorization", githubAPI.auth); + }, + type: "GET", + url: "https://api.github.com/repos/" + this.login+ "/" + this.repo, + async: false, + dataType: 'json', + success: function() { + res = true; } - } - else - { - // Delete cookie and reset settings - del_cookie("logginNitdoc"); - closeAllCommentInEdtiting(); - } - toggleLoginBox(); - }); - - // Activate edit mode - $('pre[class=text_label]').click(function(){ - // the customer is loggued ? - if(!sessionStarted || userName == ""){ - // No => nothing happen - return; - } - else{ - numComment = $(this).attr('title'); - var arrayNew = $(this).text().split('\n'); - var lNew = arrayNew.length - 1; - var adapt = ""; + }); + return res; + } - for (var i = 0; i < lNew; i++) { - adapt += arrayNew[i]; - if(i < lNew-1){ adapt += "\n"; } + this.getUserInfos = function() { + var res = false; + $.ajax({ + beforeSend: function (xhr) { + xhr.setRequestHeader ("Authorization", githubAPI.auth); + }, + type: "GET", + url: "https://api.github.com/users/" + this.login, + async: false, + dataType: 'json', + success: function(response) { + res = response; + }, + error: function(response) { + res = response; } - editComment += 1; - getCommentOfFunction($(this)); - // hide comment - $(this).hide(); - // Show edit box - $(this).next().show(); - // Show cancel button - $(this).next().next().show(); - // Show commit button - $(this).next().next().next().show(); - // Add text in edit box - if($(this).next().val() == "" || $(this).next().val() != adapt){ $(this).next().val(adapt); } - // Resize edit box - $(this).next().height($(this).next().prop("scrollHeight")); - resizeTextarea($(this).next()); - // Select it - $(this).next().select(); - preElement = $(this); - } - }); + }); + return res; + } - // Disable the edit mode - $('a[id=cancelBtn]').click(function(){ - $(this).parent().prev().children('#lblDiffCommit').text(""); - showcomment = false; - closeEditing($(this)); - }); + this.getSignedOff = function() { + var infos = this.getUserInfos(); + return infos.name + " <" + infos.email + ">"; + } - // Display commit form - $('a[id=commitBtn]').click(function(){ - updateComment = $(this).prev().prev().val(); - commentType = $(this).prev().prev().prev().attr('type'); + /* GitHub Repos */ - if(updateComment == ""){ displayMessage('The comment field is empty!', 40, 45); } - else{ - if(!sessionStarted){ - displayMessage("You need to be loggued before commit something", 45, 40); - toggleLoginBox(); - return; + this.getFile = function(path, branch){ + var res = false; + $.ajax({ + type: "GET", + url: "https://api.github.com/repos/" + this.login + "/" + this.repo + "/contents/" + path, + data: { + ref: branch + }, + async: false, + dataType: 'json', + success: function(response) { + res = response; + }, + error: function(response) { + res = response; } + }); + return res; + } - // Create the commit message - commitMessage = 'Wikidoc: modified comment in ' + $(this).parent().prev().prev().html().split(' ')[1]; - $('#commitMessage').text(commitMessage); - $('#commitMessage').css({'display': 'block'}); - pathFile = $(this).prev().prev().prev().attr('tag'); - $('#modal').show().prepend('Close'); - $('body').append('
    '); - $('#fade').css({'filter' : 'alpha(opacity=80)'}).fadeIn(); - } - }); - - // Close commit form - $('.btn_close').click(function(){ - $(this).hide(); - $(this).next().hide(); - if(editComment > 0){ editComment -= 1; } - $('#chkSignedOff').attr('checked', false); - removeSignedOff(); - }); + /* GitHub commits */ - //Close Popups and Fade Layer - $('body').on('click', 'a.close, #fade', function() { - if(editComment > 0){ editComment -= 1; } - $('#fade , #modal').fadeOut(function() { - $('#fade, a.close').remove(); - }); - $('#modalQuestion').hide(); - $('#chkSignedOff').attr('checked', false); - removeSignedOff(); + // get the latest commit on `branchName` + this.getLastCommit = function(branchName) { + var res = false; + $.ajax({ + beforeSend: function (xhr) { + xhr.setRequestHeader ("Authorization", githubAPI.auth); + }, + type: "GET", + url: "https://api.github.com/repos/" + this.login + "/" + this.repo + "/git/refs/heads/" + branchName, + async: false, + dataType: 'json', + success: function(response) { + res = response.object; + }, + error: function(response) { + res = response; + } }); + return res; + } - $('#loginAction').click(function(){ - var text; - var url; - var line; - // Look if the customer is logged - if(!sessionStarted){ - displayMessage("You need to be loggued before commit something", 100, 40); - $('.popover').show(); - return; - } - else{ userB64 = "Basic " + getUserPass("logginNitdoc"); } - // Check if repo exist - isRepoExisting(); - if(repoExist){ - isBranchExisting(); - if(branchExist){ - editComment -= 1; - commitMessage = $('#commitMessage').val().replace(/\r?\n/g, '\\n').replace(/\t/g, '\\t').replace(/\"/g,'\\"'); - if(commitMessage == ""){ commitMessage = "New commit";} - if(sessionStarted){ - if ($.trim(updateComment) == ''){ this.value = (this.defaultValue ? this.defaultValue : ''); } - else{ - displaySpinner(); - startCommitProcess(); - } - } - $('#modal, #modalQuestion').fadeOut(function() { - $('#login').val(""); - $('#password').val(""); - $('textarea').hide(); - $('textarea').prev().show(); - }); - $('a[id=cancelBtn]').hide(); - $('a[id=commitBtn]').hide(); - $('a[id=lblDiffCommit]').text(""); - showcomment = false; - // Re-load all comment - reloadComment(); + // get the base tree for commit + this.getTree = function(sha) { + var res = false; + $.ajax({ + beforeSend: function (xhr) { + xhr.setRequestHeader ("Authorization", githubAPI.auth); + }, + type: "GET", + url: "https://api.github.com/repos/" + this.login + "/" + this.repo + "/git/trees/" + sha + "?recursive=1", + async: false, + dataType: 'json', + success: function(response) { + res = response; + }, + error: function(response) { + res = response; } - } - else{ editComment -= 1; } - $('#chkSignedOff').attr('checked', false); }); + return res; + } - // Cancel creating branch - $('#btnCancelBranch').click(function(){ - editComment -= 1; - $('#modalQuestion').hide(); - $('#fade , #modal').fadeOut(function() { $('#fade, a.close').remove(); }); - return; + // create a new blob + this.createBlob = function(content) { + var res = false; + $.ajax({ + beforeSend: function (xhr) { + xhr.setRequestHeader ("Authorization", githubAPI.auth); + }, + type: "POST", + url: "https://api.github.com/repos/" + this.login + "/" + this.repo + "/git/blobs", + async: false, + dataType: 'json', + data: JSON.stringify({ + content: Base64.encode(content), + encoding: "base64" + }), + success: function(response) { + res = response; + }, + error: function(response) { + res = response; + } }); + return res; + } - // Create new branch and continu - $('#btnCreateBranch').click(function(){ - $('#modalQuestion').hide(); - if($('#btnCreateBranch').text() != 'Ok'){ - // Create the branch - createBranch(); - commitMessage = $('#commitMessage').val().replace(/\r?\n/g, '\\n').replace(/\t/g, '\\t').replace(/\"/g,'\\"'); - if(commitMessage == ""){ commitMessage = "New commit"; } - if(userB64 != ""){ - if(loginProcess){ - setCookie("logginNitdoc", base64.encode(userName+':'+password+':'+githubRepo+':'+branchName), 1); - $('#loginGit').val(""); - $('#passwordGit').val(""); - loginProcess = false; - toggleLoginBox(); - } - else{ - if ($.trim(updateComment) == ''){ this.value = (this.defaultValue ? this.defaultValue : ''); } - else{ startCommitProcess(); } - } - } - } - else - { - $('#fade , #modalQuestion, #modal').fadeOut(function() { $('#fade, a.close').remove(); }); - } + // create a new tree from a base tree + this.createTree = function(baseTree, path, blob) { + var res = false; + $.ajax({ + beforeSend: function (xhr) { + xhr.setRequestHeader ("Authorization", githubAPI.auth); + }, + type: "POST", + url: "https://api.github.com/repos/" + this.login + "/" + this.repo + "/git/trees", + data: JSON.stringify({ + base_tree: baseTree.sha, + tree: [{ + path: path, + mode: 100644, // file (blob) + type: "blob", + sha: blob.sha + }] + }), + async: false, + dataType: 'json', + success: function(response) { + res = response; + }, + error: function(response) { + res = response; + } }); + return res; + } - $('a[class=newComment]').click(function(){ - addNewComment = true; - editComment += 1; - // hide comment - $(this).hide(); - // Show edit box - $(this).next().show(); - // Show cancel button - $(this).next().next().show(); - // Show commit button - $(this).next().next().next().show(); - // Resize edit box - $(this).next().height($(this).next().prop("scrollHeight")); - resizeTextarea($(this).next()); - // Select it - $(this).next().select(); - preElement = $(this); - }); - - $("#dropBranches").change(function () { - $("#dropBranches option:selected").each(function () { - if(branchName != $(this).text()){ - branchName = $(this).text(); + // create a new commit + this.createCommit = function(message, parentCommit, tree) { + var res = false; + $.ajax({ + beforeSend: function (xhr) { + xhr.setRequestHeader ("Authorization", githubAPI.auth); + }, + type: "POST", + url: "https://api.github.com/repos/" + this.login + "/" + this.repo + "/git/commits", + data: JSON.stringify({ + message: message, + parents: parentCommit, + tree: tree.sha, + }), + async: false, + dataType: 'json', + success: function(response) { + res = response; + }, + error: function(response) { + res = response; } - }); - $.when(updateCookie(userName, password, githubRepo, branchName)).done(function(){ - closeAllCommentInEdtiting(); - reloadComment(); - }); }); + return res; + } - $("pre").hover( - function () { - if(sessionStarted == true){ - $(this).css({'cursor' : 'hand'}); - } - else{ - $(this).css({'cursor' : ''}); - } + // create a pull request + this.createPullRequest = function(title, body, base, head) { + var res = false; + $.ajax({ + beforeSend: function (xhr) { + xhr.setRequestHeader ("Authorization", githubAPI.auth); }, - function () { - if(sessionStarted == true){ - $(this).css({'cursor' : 'pointer'}); + type: "POST", + url: "https://api.github.com/repos/" + this.login + "/" + this.repo + "/pulls", + data: JSON.stringify({ + title: title, + body: body, + base: base, + head: head + }), + async: false, + dataType: 'json', + success: function(response) { + res = response; + }, + error: function(response) { + res = response; } - else{ - $(this).css({'cursor' : ''}); - } - } - ); - - $('#chkSignedOff').click(function(){ - if($(this).is(':checked')){ addSignedOff(); } - else{ removeSignedOff(); } - }) - - $('a[id=lblDiffCommit]').click(function(){ - showComment($(this)); }); -}); - -// Init process to commit the new comment -function startCommitProcess() -{ - if($('#chkSignedOff').is(':checked')){ - var numL = preElement.attr("title"); - commentLineStart = numL.split('-')[0] - 1; - if(addNewComment) { commentLineStart++; } - commentLineEnd = (commentLineStart + preElement.text().split('\n').length) - 1; - state = true; - replaceComment(updateComment, currentfileContent); - getLastCommit(); - getBaseTree(); - editComment = false; + return res; } - else{ - displayMessage('Please sign this commit', 40, 40); - } -} -function updateDisplaying(){ - if (checkCookie()) - { - userB64 = "Basic " + getUserPass("logginNitdoc"); - $('#loginGit').hide(); - $('#passwordGit').hide(); - $('#lbpasswordGit').hide(); - $('#lbloginGit').hide(); - $('#repositoryGit').hide(); - $('#lbrepositoryGit').hide(); - $('#lbbranchGit').hide(); - $('#branchGit').hide(); - $('#listBranches').show(); - $('#divGitHubRepoDisplay').show(); - $("#liGitHub").attr("class", "current"); - $("#imgGitHub").attr("src", "resources/icons/github-icon-w.png"); - $('#nickName').text(userName); - $('#githubAccount').attr("href", "https://github.com/"+userName); - $('#logginMessage').css({'display' : 'block'}); - $('#logginMessage').css({'text-align' : 'center'}); - $('.popover').css({'height' : '190px'}); - $('#signIn').text("Sign out"); - $('#githubRepoDisplay').text(githubRepo); - sessionStarted = true; - reloadComment(); - } - else - { - sessionStarted = false; - $('#logginMessage').css({'display' : 'none'}); - $("#liGitHub").attr("class", ""); - $("#imgGitHub").attr("src", "resources/icons/github-icon.png"); - $('#loginGit').val(""); - $('#passwordGit').val(""); - $('#nickName').text(""); - $('.popover').css({'height' : '325px'}); - $('#logginMessage').css({'display' : 'none'}); - $('#repositoryGit').val($('#repoName').attr('name')); - $('#branchGit').val('wikidoc'); - $('#signIn').text("Sign In"); - $('#loginGit').show(); - $('#passwordGit').show(); - $('#lbpasswordGit').show(); - $('#lbloginGit').show(); - $('#repositoryGit').show(); - $('#lbrepositoryGit').show(); - $('#lbbranchGit').show(); - $('#branchGit').show(); - $('#listBranches').hide(); - $('#divGitHubRepoDisplay').hide(); + // update a pull request + this.updatePullRequest = function(title, body, state, number) { + var res = false; + $.ajax({ + beforeSend: function (xhr) { + xhr.setRequestHeader ("Authorization", githubAPI.auth); + }, + type: "PATCH", + url: "https://api.github.com/repos/" + this.login + "/" + this.repo + "/pulls/" + number, + data: JSON.stringify({ + title: title, + body: body, + state: state + }), + async: false, + dataType: 'json', + success: function(response) { + res = response; + }, + error: function(response) { + res = response; + } + }); + return res; } } +var githubAPI; -function setCookie(c_name, value, exdays) -{ - var exdate=new Date(); - exdate.setDate(exdate.getDate() + exdays); - var c_value=escape(value) + ((exdays==null) ? "" : "; expires="+exdate.toUTCString()); - document.cookie=c_name + "=" + c_value; -} +/* GitHub cookie management */ -function del_cookie(c_name) -{ - document.cookie = c_name + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;'; -} +function SessionCookie(cookieName) { + this.cookieName = cookieName -function updateCookie(user, pwd, repo, branch){ - if(checkCookie()){ - branchName = branch; - setCookie("logginNitdoc", base64.encode(user+':'+pwd+':'+repo+':'+branch), 1); + this.setSession = function (user, password, repo) { + var value = Base64.encode(JSON.stringify({ + user: user, + password: password, + repo: repo + })); + var exdate = new Date(); + exdate.setDate(exdate.getDate() + 1); + document.cookie = this.cookieName + "=" + value + "; expires=" + exdate.toUTCString(); } -} -function getCookie(c_name) -{ - var c_value = document.cookie; - var c_start = c_value.indexOf(" " + c_name + "="); - if (c_start == -1) { c_start = c_value.indexOf(c_name + "="); } - if (c_start == -1) { c_value = null; } - else - { - c_start = c_value.indexOf("=", c_start) + 1; - var c_end = c_value.indexOf(";", c_start); - if (c_end == -1) { c_end = c_value.length; } - c_value = unescape(c_value.substring(c_start,c_end)); + this.delSession = function() { + document.cookie = this.cookieName + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;'; } - return c_value; -} -function getUserPass(c_name){ - var cookie = base64.decode(getCookie(c_name)); - return base64.encode(cookie.split(':')[0] + ':' + cookie.split(':')[1]); -} - -function checkCookie() -{ - var cookie=getCookie("logginNitdoc"); - if (cookie!=null && cookie!="") - { - cookie = base64.decode(cookie); - userName = cookie.split(':')[0]; - password = cookie.split(':')[1]; - githubRepo = cookie.split(':')[2]; - branchName = cookie.split(':')[3]; - return true; + this.getCookieDatas = function() { + var c_name = this.cookieName; + var c_value = document.cookie; + var c_start = c_value.indexOf(" " + c_name + "="); + if (c_start == -1) { c_start = c_value.indexOf(c_name + "="); } + if (c_start == -1) { + c_value = null; + } else { + c_start = c_value.indexOf("=", c_start) + 1; + var c_end = c_value.indexOf(";", c_start); + if (c_end == -1) { c_end = c_value.length; } + c_value = unescape(c_value.substring(c_start,c_end)); + } + return c_value; } - else { return false; } -} -function getLastCommit() -{ - var urlHead = ''; - if(sessionStarted){ urlHead = "https://api.github.com/repos/"+userName+"/"+githubRepo+"/git/refs/heads/"+branchName;} - else{ - // TODO: get url of the original repo. - return; + this.getSession = function() { + var cookie = this.getCookieDatas(); + if (!!cookie) { + return JSON.parse(Base64.decode(cookie)); + } + return false; } - - $.ajax({ - beforeSend: function (xhr) { - if (userB64 != ""){ xhr.setRequestHeader ("Authorization", userB64); } - }, - type: "GET", - url: urlHead, - dataType:"json", - async: false, - success: function(success) - { - shaLastCommit = success.object.sha; - } - }); -} - -function getBaseTree() -{ - $.ajax({ - beforeSend: function (xhr) { - if (userB64 != ""){ xhr.setRequestHeader ("Authorization", userB64); } - }, - type: "GET", - url: "https://api.github.com/repos/"+userName+"/"+githubRepo+"/git/commits/" + shaLastCommit, - dataType:"json", - async: false, - success: function(success) - { - shaBaseTree = success.tree.sha; - if (state){ setBlob(); } - else{ return; } - }, - error: function(){ - return; - } - }); } +var sessionCookie; -function setNewTree() -{ - $.ajax({ - beforeSend: function (xhr) { xhr.setRequestHeader ("Authorization", userB64); }, - type: "POST", - url: "https://api.github.com/repos/"+userName+"/"+githubRepo+"/git/trees", - async: false, - dataType:'json', - data:'{ "base_tree" : "'+shaBaseTree+'", '+ - '"tree":[{ '+ - '"path":"'+ pathFile +'",'+ - '"mode":"100644",'+ - '"type":"blob",'+ - '"sha": "'+ shaBlob +'"'+ - '}] '+ - '}', - success: function(success) - { // si l'appel a bien fonctionné - shaNewTree = success.sha; - setNewCommit(); - }, - error: function(){ - return; - } - }); -} - -function setNewCommit() -{ - addSignedOff(); - $.ajax({ - beforeSend: function (xhr) { xhr.setRequestHeader ("Authorization", userB64); }, - type: "POST", - url: "https://api.github.com/repos/"+userName+"/"+githubRepo+"/git/commits", - async: false, - dataType:'json', - data:'{ "message" : "'+ commitMessage +'", '+ - '"parents" :"'+shaLastCommit+'",'+ - '"tree": "'+shaNewTree+'"'+ - '}', - success: function(success) - { - shaNewCommit = success.sha; - commit(); - }, - error: function(){ - return; - } - }); -} - -//Create a commit -function commit() -{ - $.ajax({ - beforeSend: function (xhr) { xhr.setRequestHeader ("Authorization", userB64); }, - type: "POST", - url: "https://api.github.com/repos/"+userName+"/"+githubRepo+"/git/refs/heads/"+branchName, - dataType:'json', - data:'{ "sha" : "'+shaNewCommit+'", '+ - '"force" :"true"'+ - '}', - success: function(success) { displayMessage('Commit created successfully', 40, 40); }, - error:function(error){ displayMessage('Error ' + error.object.message, 40, 40); } - }); -} +/* GitHub login box */ -// Create a blob -function setBlob() -{ - $.ajax({ - beforeSend: function (xhr) { xhr.setRequestHeader ("Authorization", userB64); }, - type: "POST", - url: "https://api.github.com/repos/"+userName+"/"+githubRepo+"/git/blobs", - async: false, - dataType:'json', - data:'{ "content" : "'+text.replace(/\r?\n/g, '\\n').replace(/\t/g, '\\t').replace(/\"/g,'\\"')+'", '+ - '"encoding" :"utf-8"'+ - '}', - success: function(success) - { - shaBlob = success.sha; - setNewTree(); - }, - error:function(error){ - displayMessage('Error : Problem parsing JSON', 40, 40); - return; - } - }); -} - -// Display file content -function getFileContent(urlFile, newComment) -{ - $.ajax({ - beforeSend: function (xhr) { - xhr.setRequestHeader ("Accept", "application/vnd.github-blob.raw"); - if (userB64 != ""){ xhr.setRequestHeader ("Authorization", userB64); } - }, - type: "GET", - url: urlFile, - async:false, - success: function(success) - { - state = true; - replaceComment(newComment, success); - } - }); -} +function LoginBox() { + // Add login box + $("nav.main ul").append( + $("
  • ") + .append( + $("GitHub") + .click(function() { ui.loginBox.toggle() }) + ) + .append( + " " + + " " + ) + ); -function replaceComment(newComment, fileContent){ - var arrayNew = newComment.split('\n'); - var lNew = arrayNew.length; - text = ""; - var lines = fileContent.split("\n"); - for (var i = 0; i < lines.length; i++) { - if(i == commentLineStart){ - if(addNewComment){ - for(var indexLine=0; indexLine < lines[i+1].length; indexxLine++){ - if(lines[i+1].substr(indexLine,1) == "\t" || lines[i+1].substr(indexLine,1) == "#"){ text += lines[i+1].substr(indexLine,1); } - else{ break;} - } - text += lines[i] + "\n"; - } - // We change the comment - for(var j = 0; j < lNew; j++){ - if(commentType == 1){ text += "\t# " + arrayNew[j] + "\n"; } - else{ - if(arrayNew[j] == ""){ text += "#"+"\n"; } - else{ text += "# " + arrayNew[j] + "\n"; } + // Login with github user or logout current session + $("#loginBox .signIn").click(function(){ + if($('#signedIn').is(':hidden')){ + if(!$('#loginGit').val() || !$('#passwordGit').val() || !$('#repositoryGit').val()) { + ui.openModalBox("Login incorrect!", "Please enter your username, password and repository.", true); + } else { + githubAPI = new GitHubAPI($('#loginGit').val(), $('#passwordGit').val(), $('#repositoryGit').val()); + if(githubAPI.tryLogin()) { + // open session and set cookie + sessionCookie.setSession(githubAPI.login, githubAPI.password, githubAPI.repo); + ui.activate(); + } else { + githubAPI = false; + ui.openModalBox("Login incorrect!", "Your login information was incorrect!", true); } } + } else { + ui.disactivate(); + ui.loginBox.toggle(); } - else if(i < commentLineStart || i >= commentLineEnd){ - if(i == lines.length-1){ text += lines[i]; } - else{ text += lines[i] + "\n"; } - } - } - if(addNewComment){ - addNewComment = false; - } -} + return false; + }); -function getCommentLastCommit(path, origin){ - var urlRaw; - var bkBranch = ''; - if(origin){// We want to get the original file - bkBranch = branchName; - branchName = "master"; - } - getLastCommit(); - if(shaLastCommit != ""){ - if (checkCookie()) { - urlRaw="https://rawgithub.com/"+ userName +"/"+ githubRepo +"/" + shaLastCommit + "/" + path; - $.ajax({ - type: "GET", - url: urlRaw, - async: false, - success: function(success) - { - if(origin){ originalFileContent = success; } - else{ currentfileContent = success; } - } - }); + this.toggle = function() { + if ($('#loginBox').is(':hidden')) { + $('#loginBox').show(); + if (!$('#loginGit').is(':hidden')) { $('#loginGit').focus(); } + } else { + $('#loginBox').hide(); } } - if(origin){ branchName = bkBranch; } } -function displayMessage(msg, widthDiv, margModal){ - spinner.stop(); - $('#modal').hide(); - $('#btnCancelBranch').hide(); - $('#modalQuestion').show().prepend('Close'); - $('#txtQuestion').text(msg); - $('#btnCreateBranch').text("Ok"); - var xModal = $('#modalQuestion').css('width').split('px')[0]; - var yModal = $('#modalQuestion').css('height').split('px')[0]; - var x = $(document).width/2 - xModal/2; - var y = $(document).height/2 - yModal/2; - var xBtnBranch = $('#btnCreateBranch').css('width').split('px')[0]; - $('#modalQuestion').css({'left' : x, 'top' : y}); - $('#modalQuestion').show(); - $('#btnCreateBranch').css('margin-left', xModal/2 - xBtnBranch); - $('body').append('
    '); - $('#fade').css({'filter' : 'alpha(opacity=80)'}).fadeIn(); -} +/* Comment edition UI */ -function displaySpinner(){ - spinner.spin(targetSpinner); - $("#waitCommit").show(); -} +function GitHubUI() { + this.loginBox = new LoginBox(); + this.openedComments = 0; -// Check if the repo already exist -function isRepoExisting(){ - $.ajax({ - beforeSend: function (xhr) { - if (userB64 != "") { xhr.setRequestHeader ("Authorization", userB64); } - }, - type: "GET", - url: "https://api.github.com/repos/"+userName+"/"+githubRepo, - async:false, - dataType:'json', - success: function(){ repoExist = true; }, - error: function() - { - displayMessage('Repo not found !', 35, 45); - repoExist = false; - } - }); -} - -// Check if the branch already exist -function isBranchExisting(){ - $.ajax({ - beforeSend: function (xhr) { - if (userB64 != "") { xhr.setRequestHeader ("Authorization", userB64); } - }, - type: "GET", - url: "https://api.github.com/repos/"+userName+"/"+githubRepo+"/git/refs/heads/"+branchName, - async:false, - dataType:'json', - success: function(){ branchExist = true; }, - error: function() - { - branchExist = false; - editComment -= 1; - $('#modal').hide(); - $('#txtQuestion').text("Are you sure you want to create that branch ?"); - $('#btnCancelBranch').show(); - $('#btnCreateBranch').text("Yes"); - $('#modalQuestion').show(); - $('#modalQuestion').show().prepend('Close'); - $('body').append('
    '); - $('#fade').css({'filter' : 'alpha(opacity=80)'}).fadeIn(); - } - }); -} - -function getMasterSha() -{ - $.ajax({ - beforeSend: function (xhr) { - if (userB64 != ""){ xhr.setRequestHeader ("Authorization", userB64); } - }, - type: "GET", - url: "https://api.github.com/repos/"+userName+"/"+githubRepo+"/git/refs/heads/master", - dataType:"json", - async: false, - success: function(success) { shaMaster = success.object.sha; } - }); -} + this.init = function() { + $("body").append(""); + $('body').append('
    '); + } -function createBranch(){ + this.disactivate = function() { + // close session and purge cookie + sessionCookie.delSession(); + localStorage.clear(); + window.location.reload(); + } - getMasterSha(); + this.activate = function() { + // get lastest commit + var latest = githubAPI.getLastCommit($("body").attr("data-github-head")); + if(!latest || !latest.sha) { + this.openModalBox("Head branch not found!", latest.status + ": " + latest.statusText, true) + return; + } + if(localStorage.latestCommit != latest.sha) { + console.log("Latest commit changed: cleaned cache"); + localStorage.requests = "[]"; + localStorage.latestCommit = latest.sha; + } + console.log("Latest commit sha: " + localStorage.latestCommit); - $.ajax({ - beforeSend: function (xhr) { xhr.setRequestHeader ("Authorization", userB64); }, - type: "POST", - url: "https://api.github.com/repos/"+userName+"/"+githubRepo+"/git/refs", - data:'{ "ref" : "refs/heads/'+branchName+'",'+ - '"sha" : "'+shaMaster+'"'+ - '}', - success: function(){ return; }, - error: function(){ - editComment -= 1; - displayMessage('Impossible to create the new branch : ' + branchName, 40, 40); - } - }); -} + // reload loginBox + $('#signedOff').hide(); + $('#signedIn').show(); + $("#imgGitHub").attr("src", "resources/icons/github-icon-w.png"); + $("#liGitHub").addClass("current"); + + // login form values + $('#nickName').text(githubAPI.login); + $('#githubAccount').attr("href", "https://github.com/" + githubAPI.login); + $('#github-repo').val(githubAPI.repo); + $('#github-base').val($("body").attr("data-github-base")); + $('#github-head').val($("body").attr("data-github-head")); + + // Activate edit mode + + // Add hidden
     to empty commits
    +		$("span.noComment").each(function() {
    +			var baseComment = $(this).parent().prev();
    +			var location = ui.parseLocation(baseComment.attr("data-comment-location"));
    +			location.lend = location.lstart;
    +			var locString = "../" + location.path + ":" + location.lstart + "," + location.tabpos + "--" + location.lend + ",0";
    +			baseComment.attr("data-comment-location", locString);
    +			$(this).html("add comment for ");
    +		});
    +		$('span.noComment a').each(function() {
    +			$(this).css("cursor", "pointer")
    +			$(this).click(function() {
    +				$(this).parent().hide();
    +				ui.openCommentBox($(this).parent().parent().prev());
    +			});
    +		});
    +		$('.description div.comment').each(function() {
    +			$(this).css("cursor", "pointer")
    +			$(this).click(function() {
    +				ui.openCommentBox($(this).prev());
    +				$(this).hide();
    +			});
    +		});
     
    -$.fn.spin = function(opts) {
    -  this.each(function() {
    -    var $this = $(this),
    -        data = $this.data();
    +		// load comment from current branch
    +		this.reloadComments();
    +	}
     
    -    if (data.spinner) {
    -      data.spinner.stop();
    -      delete data.spinner;
    -    }
    -    if (opts !== false) {
    -      data.spinner = new Spinner($.extend({color: $this.css('color')}, opts)).spin(this);
    -    }
    -  });
    -  return this;
    -};
    +	this.openModalBox = function(title, msg, isError) {
    +		$('#fade').show();
    +		$('#modal')
    +			.empty()
    +			.append($('Close').click(function() {ui.closeModalBox()}))
    +			.append("

    " + title + "

    ") + .append("
    " + msg + "
    ") + .append( + $("
    ") + .append($("").click(function() {ui.closeModalBox()})) + ) + .show() + .css("top", "50%") + .css("margin-top", -($('#modal').outerHeight() / 2) + "px") + .css("left", "50%") + .css("margin-left", -($('#modal').outerWidth() / 2) + "px"); + if(isError) { + $("#modal h3").addClass("error"); + } + } -function reloadComment(){ - var path = $('pre[class=text_label]').attr('tag'); - $.when(getCommentLastCommit(path, false)).done(function(){ - if(sessionStarted){ getCommentLastCommit(path, true); } - $('pre[class=text_label]').each(function(){ getCommentOfFunction($(this)); }); - }); -} + this.closeModalBox = function() { + $('#fade , #modal').hide(); + } -function getCommentOfFunction(element){ - var textC = ""; - var numL = element.attr("title"); - if(numL != null){ - commentLineStart = numL-1; - commentLineEnd = element.attr('name').split(numL)[1].split('-')[1]-1; - var lines = currentfileContent.split("\n"); - for (var i = 0; i < lines.length; i++) { - if(i >= commentLineStart-1 && i <= commentLineEnd+1){ - if (lines[i].substr(1,1) == "#"){ textC += lines[i].substr(3,lines[i].length) + "\n";} - else if(lines[i].substr(0,1) == '#'){ textC += lines[i].substr(2,lines[i].length) + "\n"; } - } - } - if(textC != element.text){element.text(textC);} - if (textC != "" && editComment > 0){ - var originContent = originalFileContent.split("\n"); - var origin = ''; - var lblDiff = element.parent().prev().children('#lblDiffCommit'); - var preSave = element.parent().children('#preSave'); - for (var i = 0; i < originContent.length; i++) { - if(i >= commentLineStart-1 && i <= commentLineEnd+1){ - if (originContent[i].substr(1,1) == "#"){ origin += originContent[i].substr(3,originContent[i].length) + "\n";} - else if(originContent[i].substr(0,1) == '#'){ origin += originContent[i].substr(2,originContent[i].length) + "\n"; } - } + this.openCommentBox = function(baseArea) { + console.log(baseArea); + this.openedComments += 1; + // get text and format it + var formated = ""; + var len = 1; + var commentLines = baseArea.text().split('\n'); + for (var i = 0; i < commentLines.length; i++) { + formated += commentLines[i]; + if(i < commentLines.length - 2){ formated += "\n"; } } - if(textC != origin && numL == numComment){ - // The comment is different compare to the original - if(showcomment == false){ lblDiff.text("Show original comment"); } - preSave.text(origin); + len = commentLines.length - 1; + + // create comment box + var tarea = $(""); + var width = width = baseArea.parent().innerWidth() - 13; + tarea.css("width", width + "px"); + tarea.css("display", "block"); + tarea.keyup(function(event) { + $(event.target).css("height", (event.target.value.split(/\r|\n/).length * 16) + "px"); + var baseComment = $(event.target).parents("div.description").find("textarea.baseComment").text(); + if ($(event.target).val() != baseComment) { + $(event.target).parent().find("button.commit").removeAttr("disabled"); + } else { + $(event.target).parent().find("button.commit").attr("disabled", "disabled"); + } + }); + tarea.keydown(function(event) { + if(event.keyCode == 13){ + $(event.target).css("height", ($(event.target).outerHeight() + 6) + "px"); + } + }); + var commentBox = $("
    ") + .attr("data-comment-namespace", baseArea.attr("data-comment-namespace")) + .attr("data-comment-location", baseArea.attr("data-comment-location")) + .append(tarea) + .append( + $("preview") + .click(function() { + var converter = new Markdown.Converter() + var html = converter.makeHtml(tarea.val()); + ui.openModalBox("Preview", html, false); + }) + ) + .append( + $("") + .click(function() { + ui.openCommitBox($(this).parent()); + }) + ) + .append( + $("") + .click(function() {ui.closeCommentBox($(this).parent())}) + ); + if(!baseArea.text()) { + commentBox.addClass("newComment"); } - else if (numL == numComment){ lblDiff.text(""); } - } + baseArea.after(commentBox); + tarea.trigger("keyup"); } -} -// Get list of branches -function getListBranches() -{ - cleanListBranches(); - $.ajax({ - beforeSend: function (xhr) { - if ($("#login").val() != ""){ xhr.setRequestHeader ("Authorization", userB64); } - }, - type: "GET", - url: "https://api.github.com/repos/"+userName+"/"+githubRepo+"/branches", - async:false, - dataType:'json', - success: function(success) - { - for(var branch in success) { - var selected = ''; - if(branchName == success[branch].name){ - selected = 'selected'; + this.closeCommentBox = function(commentBox) { + this.openedComments -= 1; + if(!!commentBox.parent().find(".baseComment").text()) { + commentBox.parent().find("div.comment").show(); + } else if(commentBox.hasClass("newComment")) { + commentBox.next().find("span.noComment").show(); + } + commentBox.remove(); } - $('#dropBranches').append(''); - } - } - }); -} -// Delete all option in the list -function cleanListBranches(){ - $('#dropBranches').children("option").remove(); -} + this.openCommitBox = function(commentBox) { + $('#fade').show(); + $('#modal') + .empty() + .append($('Close').click(function() {ui.closeModalBox()})) + .append("

    Commit changes


    ") + .append("
    ") + .append("
    ") + .append("") + .change(function(e) { + if ($(e.target).is(':checked')) { + $("#commitBtn").removeAttr("disabled"); + } else { + $("#commitBtn").attr("disabled", "disabled"); + } + }) + .append("") + .append( + $("
    ") + .append( + $("") + .mousedown(function() { + $(this).text("Commiting..."); + }) + .mouseup(function() { + ui.commit($(this).parent().parent(), commentBox) + }) + ) + ) + .show() + .css("top", "50%") + .css("margin-top", -($('#modal').outerHeight() / 2) + "px") + .css("left", "50%") + .css("margin-left", -($('#modal').outerWidth() / 2) + "px"); + } -function closeAllCommentInEdtiting(){ - $('a[id=cancelBtn]').each(function(){ - closeEditing($(this)); - }); -} -function closeEditing(tag){ - if(editComment > 0){ editComment -= 1; } - // Hide itself - tag.hide(); - // Hide commitBtn - tag.next().hide(); - // Hide Textarea - tag.prev().hide(); - // Show comment - tag.prev().prev().show(); -} + this.commit = function(commitBox, commentBox) { + // get comments datas + var location = this.parseLocation(commentBox.attr("data-comment-location")); + var comment = commentBox.find("textarea").val(); -function checkSignIn(){ - var response = false; - $.ajax({ - beforeSend: function (xhr) { - if ($("#login").val() != ""){ xhr.setRequestHeader ("Authorization", userB64); } - }, - type: "GET", - url: "https://api.github.com/repos/"+userName+"/"+githubRepo, - async:false, - dataType:'json', - success: function(success) - { - getUserInfo(); - response = true; - displayMessage('You are now logged in'); - }, - error: function() - { - displayMessage('Error : Wrong username or password'); - response = false; - } - }); - return response; -} - -function getUserInfo(){ - $.ajax({ - beforeSend: function (xhr) { - if ($("#login").val() != ""){ xhr.setRequestHeader ("Authorization", userB64); } - }, - type: "GET", - url: "https://api.github.com/user/emails", - async:false, - dataType:'json', - success: function(success) - { - userEmail = success; - } - }); -} + // get file content from github + var origFile = githubAPI.getFile(location.path, $('#github-head').val()); + if(!origFile.content) { + this.openModalBox("Unable to locate source file!", origFile.status + ": " + origFile.statusText); + return; + } + var base64Content = origFile.content.substring(0, origFile.content.length - 1) + var fileContent = Base64.decode(base64Content); + + // commit + var newContent = this.mergeComment(fileContent, comment, location); + var message = commitBox.find("#message").val() + "\n\n" + commitBox.find("#signOff").val(); + var response = this.pushComment($('#github-base').val(), $('#github-head').val(), location.path, newContent, message) + if(!response) { + // abort procedure + return; + } -function getSignedOff(){ - $.ajax({ - beforeSend: function (xhr) { - if ($("#login").val() != ""){ xhr.setRequestHeader ("Authorization", userB64); } - }, - type: "GET", - url: "https://api.github.com/users/"+userName, - async:false, - dataType:'json', - success: function(success) - { - signedOff = success.name; - } - }); -} + // save pull request in cookie + var requests = []; + if(!!localStorage.requests) {requests = JSON.parse(localStorage.requests)} + requests[requests.length] = { + request: response, + location: commentBox.attr("data-comment-location"), + comment: Base64.encode(comment) + }; + localStorage.requests = JSON.stringify(requests); + // close boxes + this.closeModalBox() + this.closeCommentBox(commentBox); + // reload comments + this.reloadComments(); + } -function addSignedOff(){ - $.when(getUserInfo()).done(function(){ - $.when(getSignedOff()).done(function(){ - $('#commitMessage').val($('#commitMessage').val() + "\n\nSigned-off-by: "+signedOff+" <"+userEmail+">"); - }); - }); - resizeTextarea($('#commitMessage')); -} + /* + Creating a new pull request with the new comment take 5 steps: + 1. get the base tree from latest commit + 2. create a new blob with updated file content + 3. post a new tree from base tree and blob + 4. post the new commit with new tree + 5. create the pull request + */ + this.pushComment = function(base, branch, path, content, message) { + var baseTree = githubAPI.getTree(localStorage.latestCommit); + if(!baseTree.sha) { + this.openModalBox("Unable to locate base tree!", baseTree.status + ": " + baseTree.statusText, true); + return false; + } + console.log("Base tree: " + baseTree.url); + var newBlob = githubAPI.createBlob(content); + if(!newBlob.sha) { + this.openModalBox("Unable to create new blob!", newBlob.status + ": " + newBlob.statusText, true); + return false; + } + console.log("New blob: " + newBlob.url); + var newTree = githubAPI.createTree(baseTree, path, newBlob); + if(!newTree.sha) { + this.openModalBox("Unable to create new tree!", newTree.status + ": " + newTree.statusText, true); + return false; + } + console.log("New tree: " + newTree.url); + var newCommit = githubAPI.createCommit(message, localStorage.latestCommit, newTree); + if(!newCommit.sha) { + this.openModalBox("Unable to create new commit!", newCommit.status + ": " + newCommit.statusText, true); + return false; + } + console.log("New commit: " + newCommit.url); + var pullRequest = githubAPI.createPullRequest(message.split("\n\n")[0], message, base, newCommit.sha); + if(!pullRequest.number) { + this.openModalBox("Unable to create pull request!", pullRequest.status + ": " + pullRequest.statusText, true); + return false; + } + console.log("New pull request: " + pullRequest.url); + return pullRequest; + } -function removeSignedOff(){ - $('#commitMessage').val(commitMessage); - resizeTextarea($('#commitMessage')); -} + this.reloadComments = function() { + if(!localStorage.requests){ return; } + var requests = JSON.parse(localStorage.requests); + var converter = new Markdown.Converter(); + // Look for modified comments in page + for(i in requests) { + var request = requests[i]; + $("textarea[data-comment-location=\"" + request.location + "\"]").each(function () { + var div = $(this).next(); + if(request.isClosed) { + if(div.is("div.comment.newComment")) { + // hide empty comment + div.next().remove(); + div.next().find("span.noComment").show(); + div.remove(); + } else if(div.is("div.comment.locked")) { + // unlock comment + div.removeClass("locked"); + div.css("cursor", "pointer") + div.click(function() { + ui.openCommentBox(div.prev()); + }); + div.next().remove(); + } + } else { + // create div for the new coment + if(!div.is("div.comment")) { + $(this).after("
    "); + div = $(this).next(); + } + // lock modified comment + if(!div.hasClass("locked")) { + // convert modified comment to markdown + div.empty() + div.append(converter.makeHtml(Base64.decode(request.comment))); + // lock click + div.css("cursor", "auto"); + div.unbind("click"); + div.addClass("locked"); + div.after( + $("

    ") + .text("comment modified in ") + .append("pull request #"+ request.request.number +"") + .append(" ") + .append( + $("cancel") + .click(function (){ + ui.closePullRequest($(this).attr("data-pullrequest-number")); + }) + ) + ); + } + // hide "add comment" link + if(div.hasClass("newComment")) { + div.next().next().find("span.noComment").hide(); + } + } -function resizeTextarea(element){ - var nLines = element.val().split('\n').length + 1; - element.attr('rows', nLines); -} + }); + } + } -function showComment(element){ - // Display the original comment - if (showcomment == true){ - showcomment = false; - element.text("Show original comment"); + this.closePullRequest = function(number) { + // close pull request + var res = githubAPI.updatePullRequest("Canceled from Wikidoc", "", "closed", number); + if(!res.id) { + this.openModalBox("Unable to close pull request!", res.status + ": " + res.statusText, true); + return false; + } + // remove from localstorage + var requests = JSON.parse(localStorage.requests); + for(i in requests) { + if(requests[i].request.number == number) { + requests[i].isClosed = true; + } + } + localStorage.requests = JSON.stringify(requests); + ui.reloadComments() } - else{ - // Show the comment updated in user's repo - showcomment = true; - element.text("Comment changed in "+githubRepo+" / "+branchName); + + /* Utility */ + + // Extract infos from string location "../lib/standard/collection/array.nit:457,1--458,0" + this.parseLocation = function(location) { + var parts = location.split(":"); + var loc = new Object(); + loc.path = parts[0].substr(3, parts[0].length); + loc.lstart = parseInt(parts[1].split("--")[0].split(",")[0]); + loc.tabpos = parseInt(parts[1].split("--")[0].split(",")[1]); + loc.lend = parseInt(parts[1].split("--")[1].split(",")[0]); + return loc; } - var parent = element.parent().next(".description"); - var textarea = parent.children('#fileContent'); - var text = textarea.val(); - var preSave = parent.children('#preSave'); - textarea.val(preSave.text()); - preSave.text(text); - // Resize edit box - textarea.height(textarea.prop("scrollHeight")); - resizeTextarea(textarea); -} -/* GitHub login box management */ + // Meld modified comment into file content + this.mergeComment = function(fileContent, comment, location) { + // replace comment in file content + var res = new String(); + var lines = fileContent.split("\n"); + // copy lines fron 0 to lstart + for(var i = 0; i < location.lstart - 1; i++) { + res += lines[i] + "\n"; + } + // set comment + if(comment && comment != "") { + var commentLines = comment.split("\n"); + for(var i = 0; i < commentLines.length; i++) { + var line = commentLines[i]; + var tab = location.tabpos > 1 ? "\t" : ""; + res += tab + "# " + line + "\n"; + } + } + // copy lines fron lend to end + for(var i = location.lend - 1; i < lines.length; i++) { + res += lines[i]; + if(i < lines.length - 1) { res += "\n"; } + } + return res; + } -function createLoginBox() { - $("nav.main ul").append( - "

  • " + - " " + - " GitHub" + - " " + - " " + - "
  • " + - "" - ); } +var ui; -function toggleLoginBox(){ - if ($('.popover').is(':hidden')) { - if(sessionStarted){ getListBranches(); } - $('.popover').show(); - } else { - $('.popover').hide(); - } - updateDisplaying(); -} diff --git a/share/ni_nitdoc/styles/github.css b/share/ni_nitdoc/styles/github.css index aab4ac5..c66eb18 100644 --- a/share/ni_nitdoc/styles/github.css +++ b/share/ni_nitdoc/styles/github.css @@ -221,7 +221,11 @@ button.github img { /* Comment editing */ -.description textarea { +.commentBox { + text-align: right; +} + +.commentBox textarea { font-family: monospace; font-size: 1em; width: 100%; @@ -231,11 +235,12 @@ button.github img { overflow-y: hidden; } -.commentBox { - text-align: right; +.commentBox .preview { + margin: 0 15px; + cursor: pointer; } -.commentBox .cancel{ +.commentBox .cancel { background-color: #b33630; background-image: -webkit-gradient(linear, left top, left bottom, from(#b33630), to(#9f312c)); /* Saf4+, Chrome */ background-image: -webkit-linear-gradient(top, #b33630, #9f312c); /* Chrome 10+, Saf5.1+ */ @@ -250,11 +255,11 @@ button.github img { span.noComment a { color: #0D8921; } -a.newComment: hover { +span.noComment a:hover { color: #333; } -pre.locked { +div.comment.locked { color: gray; } p.locked { diff --git a/share/ni_nitdoc/styles/main.css b/share/ni_nitdoc/styles/main.css index 05375d7..5be5003 100644 --- a/share/ni_nitdoc/styles/main.css +++ b/share/ni_nitdoc/styles/main.css @@ -18,23 +18,22 @@ ul { list-style-type: square; } -pre, code { +pre, code, .description > div { white-space: pre; font-family: monospace; font-size: 1em; } -pre { +.description div.comment { background: #EEE; - padding: 5px; color: black; overflow: auto; - padding-left: 12px; - margin-bottom:0px; + padding: 0 0 0 12px; + margin: 1em 0 0 0; } -pre.noComment { - background: transparent; +.description textarea.baseComment { + display: none; } hr { @@ -364,7 +363,8 @@ article .info .code { .content section.concerns { padding: 10px; - background: #EEE; + border: 1px solid #ccc; + } .content section.concerns h2 { @@ -395,10 +395,6 @@ article .info .code { color: #999; } -.noComment { - background-color: transparent; -} - .show-code { margin: 0; } @@ -512,22 +508,3 @@ nav h3 a.fold { background-color: #E0E0E0; } -/* New comments style */ - -.content .nitdoc { - background: #F7F7F7; - padding: 5px; - color: black; - overflow: auto; -} - -.content .nitdoc pre { - background: #EEE; - border-radius: 8px; -} - -.content .nitdoc code { - background: #DDD; - padding: 0 1px; - border-radius: 5px; -} diff --git a/share/nitdoc/styles/main.css b/share/nitdoc/styles/main.css index 5b5b24f..d45e1cc 100644 --- a/share/nitdoc/styles/main.css +++ b/share/nitdoc/styles/main.css @@ -314,7 +314,7 @@ article .info .code { .content section.concerns { padding: 10px; - background: #EEE; + border: 1px solid #ccc; } .content section.concerns h2 { diff --git a/src/ni_nitdoc.nit b/src/ni_nitdoc.nit index 017f09f..7fa887c 100644 --- a/src/ni_nitdoc.nit +++ b/src/ni_nitdoc.nit @@ -46,6 +46,9 @@ class NitdocContext private var opt_custom_overview_text: OptionString = new OptionString("Text displayed as introduction of Overview page before the modules list", "--custom-overview-text") private var opt_custom_footer_text: OptionString = new OptionString("Text displayed as footer of all pages", "--custom-footer-text") + private var opt_github_base: OptionString = new OptionString("The branch (or git ref) edited commits will be pulled into (ex: octocat:master)", "--github-base") + private var opt_github_head: OptionString = new OptionString("The reference branch name used to create pull requests (ex: master)", "--github-head") + init do toolcontext.option_context.add_option(opt_dir) toolcontext.option_context.add_option(opt_source) @@ -56,6 +59,8 @@ class NitdocContext toolcontext.option_context.add_option(opt_custom_footer_text) toolcontext.option_context.add_option(opt_custom_overview_text) toolcontext.option_context.add_option(opt_custom_menu_items) + toolcontext.option_context.add_option(opt_github_base) + toolcontext.option_context.add_option(opt_github_head) toolcontext.process_options self.arguments = toolcontext.option_context.rest @@ -205,6 +210,7 @@ abstract class NitdocPage append("") append("") append("") + append("") append("") append("") append("") @@ -283,7 +289,12 @@ abstract class NitdocPage append("") head append("") - append("") + append("") header append("
    ") content @@ -525,7 +536,7 @@ class NitdocModule end redef fun title do - if mbuilder.mmodule2nmodule.has_key(mmodule) then + if mbuilder.mmodule2nmodule.has_key(mmodule) and not mbuilder.mmodule2nmodule[mmodule].short_comment.is_empty then var nmodule = mbuilder.mmodule2nmodule[mmodule] return "{mmodule.name} module | {nmodule.short_comment}" else @@ -616,11 +627,7 @@ class NitdocModule mmodule.html_signature(self) append("
    ") # comment - var nmodule = ctx.mbuilder.mmodule2nmodule[mmodule] - append("
    ") - if not nmodule.full_comment.is_empty then append("
    {nmodule.full_comment}
    ") - process_generate_dot - append("
    ") + mmodule.html_comment(self) # classes var class_sorter = new MClassNameSorter # intro @@ -907,9 +914,7 @@ class NitdocClass mclass.html_namespace(self) append("{mclass.html_short_signature}
    ") # comment - var nclass = ctx.mbuilder.mclassdef2nclassdef[mclass.intro] - append("
    ") - if nclass isa AStdClassdef and not nclass.full_comment.is_empty then append("
    {nclass.full_comment}
    ") + mclass.html_comment(self) process_generate_dot append("
    ") # concerns @@ -964,6 +969,7 @@ class NitdocClass # properties var prop_sorter = new MPropDefNameSorter var lmmodule = new List[MModule] + var nclass = ctx.mbuilder.mclassdef2nclassdef[mclass.intro] # virtual and formal types var local_vtypes = new Array[MVirtualTypeDef] for vt in vtypes do if not inherited.has(vt) then local_vtypes.add(vt) @@ -1197,12 +1203,23 @@ redef class MModule end # Return the full comment of the module decorated with html - fun html_full_comment(page: NitdocPage) do + private fun html_comment(page: NitdocPage) do + page.append("
    ") if page.ctx.mbuilder.mmodule2nmodule.has_key(self) then - page.append("
    ") - page.append("
    {page.ctx.mbuilder.mmodule2nmodule[self].full_comment}
    ") - page.append("
    ") + var nmodule = page.ctx.mbuilder.mmodule2nmodule[self] + page.append("") + if nmodule.full_comment == "" then + page.append("

    ") + page.append("no comment for ") + else + page.append("

    {nmodule.full_markdown}
    ") + page.append("

    ") + end + page.append("definition in ") + self.html_full_namespace(page) + page.append(" {page.show_source(nmodule.location)}

    ") end + page.append("
    ") end private fun has_mclassdef_for(mclass: MClass): Bool do @@ -1369,35 +1386,56 @@ redef class MClass page.append("{html_short_signature}") end - private fun html_comment(page: NitdocModule) do - page.mmodule.linearize_mclassdefs(mclassdefs) + private fun html_comment(page: NitdocPage) do page.append("
    ") - # 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("") + if nclass.full_comment == "" then + page.append("

    ") + page.append("no comment for ") + else + page.append("

    {nclass.full_markdown}
    ") + page.append("

    ") + 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)}

    ") + 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("") if nclass.full_comment == "" then page.append("

    ") page.append("no comment for ") else - page.append("

    {nclass.full_comment}
    ") + page.append("
    {nclass.full_markdown}
    ") page.append("

    ") 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)}

    ") end end end page.append("
    ") end - + private fun html_redefs(page: NitdocModule) do page.mmodule.linearize_mclassdefs(mclassdefs) page.append("
    ") @@ -1536,11 +1574,12 @@ redef class MPropDef if not is_intro then if page.ctx.mbuilder.mpropdef2npropdef.has_key(mproperty.intro) then var intro_nprop = page.ctx.mbuilder.mpropdef2npropdef[mproperty.intro] + page.append("") if intro_nprop.full_comment.is_empty then page.append("

    ") page.append("no comment for ") else - page.append("

    {intro_nprop.full_comment}
    ") + page.append("
    {intro_nprop.full_markdown}
    ") page.append("

    ") end page.append("introduction in ") @@ -1550,16 +1589,17 @@ redef class MPropDef end if page.ctx.mbuilder.mpropdef2npropdef.has_key(self) then var nprop = page.ctx.mbuilder.mpropdef2npropdef[self] + page.append("") if nprop.full_comment == "" then page.append("

    ") page.append("no comment for ") else - page.append("

    {nprop.full_comment}
    ") + page.append("
    {nprop.full_markdown}
    ") page.append("

    ") end if is_intro then page.append("introduction in ") - else + else page.append("redefinition in ") end mclassdef.html_namespace(page) @@ -1596,7 +1636,7 @@ redef class MMethodDef html_comment(page) page.append("") end - + redef fun html_info(page, ctx) do page.append("

    ") if mproperty.visibility < public_visibility then page.append("{mproperty.visibility.to_s} ") @@ -1678,45 +1718,97 @@ end # Nodes redefs # +redef class ADoc + private fun short_comment: String do + return n_comment.first.text.substring_from(2).replace("\n", "").html_escape + end + + private fun full_comment: String do + var res = new Buffer + for t in n_comment do + var text = t.text + text = text.substring_from(1) + if text.first == ' ' then text = text.substring_from(1) + res.append(text.html_escape) + end + return res.to_s + end +end + redef class AModule private fun short_comment: String do if n_moduledecl != null and n_moduledecl.n_doc != null then - return n_moduledecl.n_doc.n_comment.first.text.substring_from(2).replace("\n", "").html_escape + return n_moduledecl.n_doc.short_comment end return "" end private fun full_comment: String do if n_moduledecl != null and n_moduledecl.n_doc != null then + return n_moduledecl.n_doc.full_comment + end + return "" + end + + private fun full_markdown: String do + if n_moduledecl != null and n_moduledecl.n_doc != null then return n_moduledecl.n_doc.full_markdown.html end return "" end + + private fun doc_location: Location do + if n_moduledecl != null and n_moduledecl.n_doc != null then + return n_moduledecl.n_doc.location + end + return location + end end redef class AStdClassdef private fun short_comment: String do - if n_doc != null then return n_doc.n_comment.first.text.substring_from(2).replace("\n", "").html_escape + if n_doc != null then return n_doc.short_comment return "" end private fun full_comment: String do + if n_doc != null then return n_doc.full_comment + return "" + end + + private fun full_markdown: String do if n_doc != null then return n_doc.full_markdown.html return "" end + + private fun doc_location: Location do + if n_doc != null then return n_doc.location + return location + end end redef class APropdef private fun short_comment: String do - if n_doc != null then return n_doc.n_comment.first.text.substring_from(2).replace("\n", "").html_escape + if n_doc != null then return n_doc.short_comment return "" end private fun full_comment: String do + if n_doc != null then return n_doc.full_comment + return "" + end + + private fun full_markdown: String do if n_doc != null then return n_doc.full_markdown.html return "" end + + private fun doc_location: Location do + if n_doc != null then return n_doc.location + return location + end end + var nitdoc = new NitdocContext nitdoc.generate_nitdoc