您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds "unfold all changesets" buttons (hotkey: f) above/below Commit History pages at github, letting you browse the source changes without leaving the page. (Click a commit header again to re-fold it.) You can also fold or unfold individual commits by clicking on non-link parts of the commit. As a bonus, all named commits get their tag/branch names annotated in little bubbles on the right.
// ==UserScript== // @name Github: unfold commit history // @namespace http://github.com/johan/ // @description Adds "unfold all changesets" buttons (hotkey: f) above/below Commit History pages at github, letting you browse the source changes without leaving the page. (Click a commit header again to re-fold it.) You can also fold or unfold individual commits by clicking on non-link parts of the commit. As a bonus, all named commits get their tag/branch names annotated in little bubbles on the right. // @include https://github.com/*/commits* // @include http://github.com/*/commits* // @match https://github.com/*/commits* // @match http://github.com/*/commits* // @version 1.7 // ==/UserScript== (function exit_sandbox() { // see end of file for unsandboxing code var toggle_options = // flip switches you configure by clicking in the UI here: { compact_committers: '#commit .human .actor .name span:contains("committer")' , chain_adjacent_connected_commits: '#commit > .separator > h2' , iso_times: '#commit .human .actor .date > abbr' , author_filter: '.commit .human .actor:nth-child(2) .gravatar > img' }, toggle = { author_filter: function(on) { $('#filtered_authors').attr('disabled',!on); } }, options = // other options you have to edit this file for: { changed: true // Shows files changed, lines added / removed in folded mode }, at = '.commit.loading .machine a[hotkey="c"]', url = '/images/modules/browser/loading.gif', plain = ':not(.magic):not([href*="#"])', all = '.envelope.commit .message a:not(.loaded)'+ plain, css = // used for .toggleClass('folded'), for, optionally, hiding: '.file.folded > .data,\n' + // individual .commit .changeset .file:s '.file.folded > .image,\n' + // (...or their corresponding .image:s) '.commit.folded .changeset,\n' + // whole .commit:s' diffs, '.commit.folded .message .full' + // + full checkin message ' { display: none; }\n' + '.chain_adjacent_connected_commits #commit .adjacent.commit:not(.selected)' + ':not(:last-child) { border-bottom-color: transparent; }\n' + at +':before\n { content: url("'+ url +'"); }\n'+ // show "loading" throbber at +'\n { position: absolute; margin: 1px 0 0 -70px; height: 14px; }\n' + '#commit .selected.loading .machine > span:nth-child(1) { border: none; }\n' + '#commit .machine { padding-left: 14px; padding-bottom: 0; }\n' + // The site has a .site { width: 920px } but #commit .human { width: 50em; }, // which looks bad in Opera, where it becomes about 650px only. Address this: '#commit .human { width: 667px; }\n' + '.fold_unfold, .download_all { float: right; }\n' + '.all_folded .fold_unfold:before { content: "\xAB un"; }\n' + '.all_folded .fold_unfold:after { content: " \xBB"; }\n' + '.all_unfolded .fold_unfold:before { content: "\xBB "; }\n' + '.all_unfolded .fold_unfold:after { content: " \xAB"; }\n' + '#commit .human .message pre { width: auto; }\n' + // don't wrap before EOL! '.folded .message .truncated:after { content: " (\u2026)"; }\n' + '#commit .human .actor { width: 50%; float:left; }\n' + '.compact_committers #commit .human .actor:nth-of-type(odd) {' + ' text-align: right; clear: none; }\n' + '.compact_committers #commit .human .actor:nth-of-type(odd) .gravatar {' + ' float: right; margin: 0 0 0 0.7em; }\n' + 'body:not(.iso_times) .date > .iso { display: none; }\n' + '.iso_times .date > .relatize.relatized:before { content: "("; }\n' + '.iso_times .date > .relatize.relatized:after { content: ")"; }\n' + '.iso_times .date > .relatize.relatized { display: inline; }\n' + '.iso_times .date > .relatize { display: none; }\n' + 'body:not(.author_filter) #author_filter { display: none; }\n' + '#author_filter img.filtered { opacity: 0.5; }' + '#author_filter img { margin: 0 .3em 0 0; background-color: white; '+ ' padding: 2px; border: 1px solid #D0D0D0; }' + '.iso_times .date > .relatize.relatized:after { content: ")"; }\n' + '.iso_times .date > .relatize.relatized { display: inline; }\n' + '.iso_times .date > .relatize { display: none; }\n' + '.magic.tag, .magic.branch { opacity: 0.75; }' + '.message .tag { background: #FE7; text-align: right; padding: 0 2px; ' + ' margin: 0 -5px .1em 0; border-radius: 4px; float: right; clear: both; }\n' + '.message .branch { background: #7EF; text-align: right; padding: 0 2px; ' + ' margin: 0 -5px .1em 0; border-radius: 4px; float: right; clear: both; }\n' + '.magic.tag.diff { clear: left; margin-right: 0.25em; }\n' + // Δ marker (!options.changed ? '' : '#commit .folded .machine { padding-bottom: 0; }\n' + '#commit .machine #toc .diffstat { border: 0; padding: 1px 0 0; }\n' + '#commit .machine #toc .diffstat-bar { opacity: 0.75; }\n' + '#commit .machine #toc .diffstat-summary { font-weight: normal; }\n'+ '#commit .envelope.selected .machine #toc span { border-bottom: 0; }\n' + '#commit .machine #toc { float: right; width: 1px; margin: 0; border: 0; }'); var on_page_change = [ prep_parent_links , inject_commit_names ], keys = Object.keys || function _keys(o) { var r = [], k; for (k in o) if (o.hasOwnProperty(k)) r.push(k); return r; }; // Run first at init, and then once per (settled) page change, for later updates // caused by stuff like AutoPagerize. function onChange() { on_page_change.forEach(function(cb, x) { cb(); }); } function init() { $('body').addClass('all_folded') // preload the loading throbber, so it shows .append('<img src="'+ url +'" style="visibility:hidden;">'); // up promptly $('head').append($('<style type="text/css"></style>').html(css)); $('.commit').live('click', toggle_commit_folding); onChange(); $(document).bind('DOMNodeInserted', when_settled(onChange), false); $('a[href][hotkey=p]') .live('mouseover', null, hilight_related) .live('mouseout', null, unlight_related) .live('click', null, scroll_to_related); // if triggered by mouse click, // scroll to the commit if it's in view, otherwise load that page instead -- // and ditto but for trigger by keyboard hotkey instead (falls back to link): GitHub.Commits.link = AOP_wrap_around(try_scroll_first, GitHub.Commits.link); init_config(); $('<div class="pagination" style="margin: 0; padding: 0;"></div>') .prependTo('#commit .separator:first'); $('<a class="download_all" hotkey="d"><u>d</u>ecorate all</a>') .appendTo('.pagination').click(download_all); $('<a class="fold_unfold" hotkey="f"><u>f</u>old all</a>') .appendTo('.pagination'); $('.fold_unfold').toggle(unfold_all, fold_all); // export to public identifiers for the hotkeys window.toggle_selected_folding = toggle_selected_folding; window.toggle_all_folding = toggle_all_folding; window.download_selected = download_selected; window.download_all = download_all; location.href = 'javascript:$.hotkeys(' + '{ f: toggle_selected_folding' + //', F: toggle_all_folding' + ', d: download_selected' + //', D: download_all' + '});' + // adds our own hotkeys 'delete GitHub.Commits.elements;' + // makes j / k span demand-loaded pages 'GitHub.Commits.__defineGetter__("elements",' + 'function() { return $(".commit"); });void 0'; setTimeout(function() { AOP_also_call('$.facebox.reveal', show_docs); }, 1e3); render_author_filter(); } // makes all authors in the view show up on top; on page load, or page update function render_author_filter(e) { if (e) { // postpone reruns until 100ms passed without activity if (render_author_filter.scheduled) clearTimeout(render_author_filter.scheduled); render_author_filter.scheduled = setTimeout(render_author_filter, 100, 0); return; } delete render_author_filter.scheduled; if (!$('#author_filter').length) { $('#path').after('<div id="author_filter"></div>'); $('#commit').bind('DOMSubtreeModified', render_author_filter); } $('.commit:not(.by) .human .actor:nth-child(2) .gravatar > img') .each(update_author_filter); // find not-yet-catered commits } // makes a particular commit in the view get counted and added to author filter function update_author_filter(e) { var mail_hash = /avatar\/([a-f\d]{32})/.exec(this.src) , author_id = 'author_'+ mail_hash[1] , $gravatar = $('#'+ author_id) , commit_no = parseInt($gravatar.attr('title') || '0', 10) , $envelope = !commit_no && $(this).parents('.actor'); if ($envelope) { var img = this.cloneNode(true); img.alt = $envelope.find('.name').text().replace(/\s*\(author\)\s*$/, ''); img.id = author_id; $('#author_filter').append(img); $(img).click(toggle_author_commits); img.title = ++commit_no +' commit by '+ img.alt; $('head').append('<style id="filtered_authors"></style>'); } else $gravatar.attr('title', ++commit_no +' commits by '+ $gravatar.attr('alt')); $(this).parents('.commit').addClass('by '+author_id); $('#'+ author_id).css('padding-right', (1 + commit_no) +'px'); } function toggle_author_commits(e) { $(this).toggleClass('filtered'); var hide = $('#author_filter .filtered').map(function() { return this.id; }); if (hide.length) hide = '.'+ (array(hide).join(',.')) +' { display: none; }'; else hide = ''; $('#filtered_authors').html(hide); } // fetch some API resource by api function github_api(path, cb) { function get() { if (1 === enqueue().length) $.ajax(request); } function enqueue() { var queue = github_api[path] = github_api[path] || []; queue.push(cb); // always modify in place for dispatch return queue; } function dispatch(queue, args) { for (var i = 0, cb; cb = queue[i]; i++) cb.apply(this, args || []); } var logged_in = github_api.token || $('#header a[href="/logout"]').length , request = { url: path , success: function done() { dispatch(github_api[path], arguments); delete github_api[path]; } , dataType: 'json' , beforeSend: logged_in && function(xhr) { var name = $('#header .avatarname .name').text() , auth = btoa(name+'/token:'+ github_api.token); xhr.setRequestHeader('Authorization', 'Basic '+ auth); } }; if (!logged_in || github_api.token) get(); else if (github_api.pending_token) github_api.pending_token.push(get); else { github_api.pending_token = [get]; $.ajax({ url: '/account/admin' , beforeSend: function(xhr) { xhr.withCredentials = true; } , success: function(html) { var got = html.match(/API token is <code>([^<]*)/); if (got) { github_api.token = got[1]; dispatch(github_api.pending_token); delete github_api.pending_token; } } }); } } // calls cb({ tag1: hash1, ... }, '/repo/name') after fetching the repo's tags, // of if none, no_tags('/repo/name') function get_tags(cb, no_tags, refresh) { return get_named('tags', cb, no_tags, refresh); } // calls cb({ branch: hash1, ... }, '/repo/name') or, no_branches('/repo/name') // (just like get_tags) function get_branches(cb, no_branches, refresh) { return get_named('branches', cb, no_branches, refresh); } function get_named(what, cb, no_cb, refresh) { function got_names(names) { // cache the repository's tags/branches for later var json = window.localStorage[path] = JSON.stringify(names = names[what]); if (json.length > 2) cb(names, repo); else no_cb && no_cb(repo); } function get_name() { return this.textContent.replace(/ \u2713$/, ''); } var repo = window.location.pathname.match(/^(?:\/[^\/]+){2}/); if (repo) repo = repo[0]; else return false; var path = what + repo , xxxs = window.localStorage[path] && JSON.parse(window.localStorage[path]) , _css = '.subnav-bar '+ (what === 'tags' ? 'li + li' : 'li:first-child') , page = $(_css + ' a.dropdown + ul > li').map(get_name).get().sort() || [] , have = xxxs && keys(xxxs).sort() || [] , at_b = 'branches' === what && get_current_branch(); // invalidate the branch cache if we're at the head of a branch, and its hash // contradicts what we have saved if (!xxxs || at_b && xxxs[at_b] !== get_first_commit_hash()) refresh = true; // optimization - if there are no tags in the page, don't go fetch any if ('tags' === what && !page.length) { have = page; xxxs = {}; refresh = false; } // assume the repo still has no names if it didn't at the time the page loaded if (page.length === 0) no_cb && no_cb(repo); // assume the cache is still good if it's got the same tag number and names else if (!refresh && have.length === page.length && have.join() === page.join()) cb(xxxs, repo); else { // refresh the cache github_api('/api/v2/json/repos/show'+ repo +'/'+ what, got_names); return true; } return false; } function get_current_branch() { return $('.subnav-bar li:first-child ul li strong').text().slice(0, -2); } function get_first_commit_hash() { return $('#commit .commit .machine a[hotkey="c"]')[0].pathname.slice(-40); } // annotates commits with tag/branch names in little bubbles on the right side function inject_commit_names() { function draw_names(type, names, repo) { var all_names = keys(names) , kin_cache = {}; // kin_re => [all names matching kin_re] all_names.sort().forEach(function(name) { var hash = names[name] , url = repo +'/commits/'+ name , sel = 'a.'+ type +'[href="'+ url +'"]' , $a = $('.commit pre > a[href$="'+ repo +'/commit/'+ hash +'"]'); if (!$a.parent().find(sel).length) { // does the commit exist in the page? $(sel).remove(); // remove tag / branch from prior location (if any) $a.before('<a class="magic '+type+'" href="'+ url +'">'+ name +'</a>'); // if we just linked a tag, also link a tag changeset, if applicable: if (type !== 'tag') return; var kin_re = quote_re(name).replace(/\d+/g, '\\d+') , similar = new RegExp(kin_re) , kin_tags = kin_cache[similar] = kin_cache[similar] || ( all_names .filter(function(tag) { return similar.test(tag); }) .sort(dwim_sort_func) ) , this_idx = kin_tags.indexOf(name) , last_tag = this_idx && kin_tags[this_idx - 1]; if (last_tag) $a.before( '<a class="magic tag diff" title="Changes since '+ last_tag + '" href="'+ repo +'/compare/'+ last_tag +'...'+ name +'">' + 'Δ</a>' ); } }); } function draw_tags(tags, repo) { draw_names('tag', tags, repo); } function draw_branches(branches, repo) { draw_names('branch', branches, repo); } var refresh = get_branches(draw_branches); // assume it's best to refresh tags too if any branches were moved get_tags(draw_tags, null, refresh); } function quote_re( re ) { return re.replace( /([.*+^$?(){}|\x5B-\x5D])/g, "\\$1" ); // 5B-5D == [\] } // example usage: ['0.10', '0.9'].sort(dwim_sort_func) comes out ['0.9', '0.10'] function dwim_sort_func(a, b) { if (a === b) return 0; var int_str_rest_re = /^(\d*)(\D*)(.*)/ , A = int_str_rest_re.exec(a), a_int, a_str, a_int_len = A[1].length , B = int_str_rest_re.exec(b), b_int, b_str, b_int_len = B[1].length ; if (!a_int_len ^ !b_int_len) return a_int_len ? -1 : 1; do { if ((a_int = A[1]) !== (b_int = B[1])) { if ((a_int = parseInt(a_int, 10)) !== (b_int = parseInt(b_int, 10))) return a_int < b_int ? -1 : 1; } if ((a_str = A[2]) !== (b_str = B[2])) return a_str < b_str ? -1 : 1; a = A[3]; b = B[3]; if (!a.length) return b.length ? -1 : 0; if (!b.length) return a.length ? 1 : 0; A = int_str_rest_re.exec(a); B = int_str_rest_re.exec(b); } while (true); } // make all commits get @id:s c_<hash>, and all parent links get @rel="<hash>" function prep_parent_links() { function hash(a) { return a.pathname.slice(a.pathname.lastIndexOf('/') + 1); } $('.commit:not([id]) a[href][hotkey=p]').each(function reroute() { $(this).attr('rel', hash(this)); }); $('.commit:not([id]) a[href][hotkey=c]').each(function set_id() { var id = hash(this), ci = $(this).closest('.commit'), pr = ci.prev(); if (pr.find('a[hotkey=p][href$='+ id +']').length) pr.addClass('adjacent'); ci.attr('id', 'c_' + id); }); $('.date > abbr.relatize:first-child').each(unrelatize_dates); } function unrelatize_dates() { var ts = this.title, at = new Date(ts.replace(/-/g,'/')), t = ts.split(' ')[1] , wd = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][at.getDay()]; $(this).before('<abbr class="iso" title="'+ ts +'">'+ wd +' '+ t +' </abbr>'); } function try_scroll_first(wrappee, link_type) { function normal() { return wrappee.apply(self, args); } var args = _slice.call(arguments, 1), self = this; if (link_type !== 'p') return normal(); var link = GitHub.Commits.selected().find('[hotkey="'+ link_type +'"]')[0]; // scroll_to_related returns true if link is not in the current view if (link && scroll_to_related.call(link) && confirm('Parent commit not in view -- load parent page instead?')) return normal(); return false; } function scroll_to_related(e) { var to = $('#c_'+ this.rel); if (!to.length) return true; select(this.rel, true); return false; } // hilight the related commit changeset, when a commit link is hovered function hilight_related(e) { $('#c_'+ this.rel).addClass('selected'); } function unlight_related(e) { $('#c_'+ this.rel).removeClass('selected'); if (null != GitHub.Commits.current) GitHub.Commits.select(GitHub.Commits.current); } function show_docs(x) { var docs = { f: '(un)Fold selected (or all, if none)' , d: 'Describe selected (or all, if none)' }; for (var key in docs) $('#facebox .shortcuts .columns:first .column.middle dl:last') .before('<dl class="keyboard-mappings"><dt>'+ key +'</dt>' + '<dd>'+ docs[key] +'</dd></dl>'); return x; } function init_config() { for (var o in toggle_options) { if ((options[o] = !!window.localStorage.getItem(o))) $('body').addClass(o); $(toggle_options[o]) .live('click', { option: o }, toggle_option) .live('hover', { option: o }, show_docs_for); } } function toggle_option(e) { var o = e.data.option, cb, toggle_fn = toggle[o]; if ((options[o] = !window.localStorage.getItem(o))) { window.localStorage.setItem(o, '1'); if (toggle_fn) toggle_fn(true); } else { window.localStorage.removeItem(o); if (toggle_fn) toggle_fn(false); } $('body').toggleClass(o); show_docs_for.apply(this, arguments); return false; // do not fold / unfold } function show_docs_for(e) { var o = e.data.option; var is = !!window.localStorage.getItem(o); $(this).css('cursor', 'pointer') .attr('title', 'Click to toggle option "' + o.replace(/_/g, ' ') +'" '+ (is ? 'off' : 'on')); } function toggle_selected_folding() { var selected = $('.selected'); if (selected.length) selected.click(); else toggle_all_folding(); } function download_selected() { var selected = $('.selected' + all); if (selected.length) selected.each(inline_changeset); else download_all(); } function toggle_all_folding() { if ($('body').hasClass('all_folded')) unfold_all(); else fold_all(); } function download_all() { $(all).each(inline_changeset); } function unfold_all() { $('body').addClass('all_unfolded').removeClass('all_folded'); $('.commit.folded').removeClass('folded'); $(all).each(inline_and_unfold); } function fold_all() { $('body').addClass('all_folded').removeClass('all_unfolded'); $('.commit').addClass('folded'); } // click to fold / unfold, and select: function toggle_commit_folding(e) { if (isNotLeftButton(e) || $(e.target).closest('a[href], .changeset, .gravatar').length) return; // clicked a link, or in the changeset; don't do fold action // .magic and *# links aren't github commit links (but stuff we added) var $link = $('.message a[href*="/commit/"]'+ plain, this); if ($link.hasClass('loaded')) $(this).toggleClass('folded'); else $link.each(inline_and_unfold); select($($(this).closest('.commit')), !'scroll'); } // pass a changeset node, id or hash and have github select it for us function select(changeset, scroll) { var node = changeset, nth; if ('string' === typeof changeset) node = $('#'+ (/^c_/.test(changeset) ? '' : 'c_') + changeset); nth = $('.commit').index(node); pageCall('GitHub.Commits.select', nth); if (scroll) setTimeout(function() { var focused = $('.commit.selected'); //if (focused.offset().top - $(window).scrollTop() + 50 > $(window).height()) focused.scrollTo(200); }, 50); } function pageCall(fn/*, arg, ... */) { var args = JSON.stringify(_slice.call(arguments, 1)).slice(1, -1); location.href = 'javascript:void '+ fn +'('+ args +')'; } // every mouse click is not interesting; return true only on left mouse clicks function isNotLeftButton(e) { // IE has e.which === null for left click && mouseover, FF has e.which === 1 return (e.which > 1) || e.shiftKey || e.ctrlKey || e.altKey || e.metaKey; } function pluralize(noun, n) { return n +' '+ noun + (n == 1 ? '' : 's'); } function inline_and_unfold() { var $c = $(this).closest('.commit'); inline_changeset.call(this, function() { $c.removeClass('folded'); }); } var _slice = Array.prototype.slice; function array(ish) { return _slice.call(ish, 0); } function n(x) { if (x > (1e9 - 5e7 - 1)) return Math.round(x / 1e9) +'G'; if (x > (1e6 - 5e4 - 1)) return Math.round(x / 1e6) +'M'; if (x > (1e3 - 5e1 - 1)) return Math.round(x / 1e3) +'k'; return x + ''; } // loads the changeset link's full commit message, toc and the files changed and // inlines them in the corresponding changeset (in the current page) function inline_changeset(doneCallback) { // make file header click toggle showing file contents (except links @ right) function toggle_file(e) { if (isNotLeftButton(e) || $(e.target).closest('.actions').length) return; // wrong kind of mouse click, or a right-side action link click $(this).parent().toggleClass('folded'); } // diff links for this commit should refer to this commit only function fix_link() { var old = this.id; this.id += '-' + sha1; changeset.find('a[href="#'+ old +'"]') .attr('href', '#'+ this.id); $('div.meta', this).click(toggle_file) .css('cursor', 'pointer') .attr('title', 'Toggle showing of file') .find('.actions').attr('title', ' '); // but don't over-report that title } function show_changed() { var $m = $('.machine', commit), alreadyChanged = $m.find('#toc').length; if (alreadyChanged) return; var F = 0, A = 0, D = 0, $a = $m.append('diff' + '<table id="toc"><tbody><tr><td class="diffstat">' + '<a class="tooltipped leftwards"></a>' + '</td></tr></tbody></table>').find('#toc a'); // count added / removed lines and number of files changed $('.changeset #toc .diffstat a[title]', commit).each(function count() { ++F; // files touched var lines = /(\d+) additions? & (\d+) deletion/.exec(this.title || ''); if (lines) { A += Number(lines[1]); // lines added D += Number(lines[2]); // lines deleted } }); var text = '<b>+'+ n(A) +'</b> / <b>-'+ n(D) +'</b> in <b>'+ n(F) +'</b>', stat = '<span class="diffstat-summary">'+ text +'</span>\n', i, N = 5, plus = Math.round(A / (A + D) * N), bar = '<span class="diffstat-bar">'; // don't show more blobs than total lines, and show ties as even # of blobs if (A + D < N) { plus = A; N = A + D; } else if (A === D) { --plus; --N; } for (i = 0; i < N; i++) bar += '<span class="'+ (i < plus ? 'plus' : 'minus') +'">\u2022</span>'; bar += '</span>'; $a.html(stat + bar).attr('title', A +' additions & '+ D +' deletions in '+ pluralize('file', F)); } // find all diff links and fix them, annotate how many files were changed, and // insert line 2.. of the commit message in the unfolded view of the changeset function post_process() { github_inlined_comments(this); var files = changeset.find('[id^="diff-"]').each(fix_link), line2; if (options.changed) show_changed(); // now, add lines 2.. of the commit message to the unfolded changeset view var whole = $('#commit', changeset); // contains the whole commit message try { if ((line2 = $('.message pre', whole).html().replace(line1, ''))) { $('.human .message pre', commit).append( $('<span class="full"></span>').html(line2)); // commit message $('.human .message pre a.loaded' + plain, commit).after( '<span title="Message continues..." class="truncated"></span>'); } } catch(e) {} // if this fails, fail silent -- no biggie whole.remove(); // and remove the remaining duplicate parts of that commit commit.removeClass('loading'); // remove throbber if ('function' === typeof doneCallback) doneCallback(); } var line1 = /^[^\n]*/, sha1 = this.pathname.slice(this.pathname.lastIndexOf('/') + 1), commit = $(this).closest('.commit').addClass('loading folded'); $(this).addClass('loaded'); // mark that we already did load it on this page commit.find('.human, .machine') .css('cursor', 'pointer'); var changeset = commit .append('<div class="changeset" style="float: left; width: 100%;"/>') .find('.changeset') // ,#all_commit_comments removed from next line .load(this.href + '.html #commit,#toc,#files', post_process); } // Makes a function that can replace wrappee that instead calls wrapper(wrappee) // plus all the args wrappee should have received. (If wrapper does not want the // original function to run, it does not have to.) function AOP_wrap_around(wrapper, wrappee) { return function() { return wrapper.apply(this, [wrappee].concat(array(arguments))); }; } // replace <name> with a function that returns fn(name(...)) function AOP_also_call(name, fn) { location.href = 'javascript:try {'+ name +' = (function(orig) {\n' + 'return function() {\n' + 'var res = orig.apply(this, arguments);\n' + 'return ('+ (fn.toString()) +')(res);' + '};' + '})('+ name +')} finally {void 0}'; } // drop calls until at least <ms> (or 100) ms apart, then pass the last on to cb function when_settled(cb, ms) { function is_settled() { waiter = last = null; cb.apply(self, args); }; ms = ms || 100; var last, waiter, self, args; return function () { self = this; args = arguments; if (waiter) clearTimeout(waiter); waiter = setTimeout(is_settled, 100); }; } // Github handlers (from http://assets1.github.com/javascripts/bundle_github.js) // - this is all probably prone to die horribly as the site grows features, over // time, unless this functionality gets absorbed and maintained by github later. // In other words, everything below is really just the minimum copy-paste needed // from the site javascript for inline comments to work -- minimal testing done. // 5:th $(function) in http://assets1.github.com/javascripts/bundle_github.js, // but with $() selectors scoped to a "self" node passed from the caller above. // On unfolding changeset pages with inline comments, we need to make them live, // as github itself is loading them dynamically after DOMContentLoaded. function github_inlined_comments(self) { $(".inline-comment-placeholder", self).each(function () { var c = $(this); $.get(c.attr("remote"), function got_comment_form(page) { page = $(page); c.closest("tr").replaceWith(page); github_comment_form(page); github_comment(page.find(".comment")); }); }); $("#files .show-inline-comments-toggle", self).change(function () { this.checked ? $(this).closest(".file").find("tr.inline-comments").show() : $(this).closest(".file").find("tr.inline-comments").hide(); }).change(); $("#inline_comments_toggle input", self).change(function () { this.checked ? $("#comments").removeClass("only-commit-comments") : $("#comments").addClass("only-commit-comments"); }).change(); } // http://assets1.github.com/javascripts/bundle_github.js::e(c) function github_comment_form(c) { c.find("ul.inline-tabs").tabs(); c.find(".show-inline-comment-form a").click(function () { c.find(".inline-comment-form").show(); $(this).hide(); return false; }); var b = c.find(".previewable-comment-form") .previewableCommentForm().closest("form"); b.submit(function () { b.find(".ajaxindicator").show(); b.find("button").attr("disabled", "disabled"); b.ajaxSubmit({ success: function (f) { var h = b.closest(".clipper"), d = h.find(".comment-holder"); if (d.length == 0) d = h.prepend($('<div class="inset comment-holder"></div>')) .find(".comment-holder"); f = $(f); d.append(f); github_comment(f); b.find("textarea").val(""); b.find(".ajaxindicator").hide(); b.find("button").attr("disabled", ""); } }); return false; }); } // http://assets1.github.com/javascripts/bundle_github.js::a(c) function github_comment(c) { c.find(".relatize").relatizeDate(); c.editableComment(); } // This block of code injects our source in the content scope and then calls the // passed callback there. The whole script runs in both GM and page content, but // since we have no other code that does anything, the Greasemonkey sandbox does // nothing at all when it has spawned the page script, which gets to use jQuery. // (jQuery unfortunately degrades much when run in Mozilla's javascript sandbox) if ('object' === typeof opera && opera.extension) { this.__proto__ = window; // bleed the web page's js into our execution scope document.addEventListener('DOMContentLoaded', init, false); // GM-style init } else { // for Chrome or Firefox+Greasemonkey if ('undefined' == typeof __UNFOLD_IN_PAGE_SCOPE__) { // unsandbox, please! var src = exit_sandbox + '', script = document.createElement('script'); script.setAttribute('type', 'application/javascript'); script.innerHTML = 'const __UNFOLD_IN_PAGE_SCOPE__ = true;\n('+ src +')();'; document.documentElement.appendChild(script); document.documentElement.removeChild(script); } else { // unsandboxed -- here we go! init(); } } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址