您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
turn commit references into clickable links
// ==UserScript== // @name Linkify bug comments (rt.perl.org) // @namespace [mauke]/rt.perl.org // @description turn commit references into clickable links // @match http://rt.perl.org/* // @match https://rt.perl.org/* // @grant GM_xmlhttpRequest // @version 1.0.2 // ==/UserScript== 'use strict'; const RT_TICKET = 'http://rt.perl.org/rt3/Public/Bug/Display.html?id='; const GIT_BASE = 'http://perl5.git.perl.org'; const GIT_REPO = GIT_BASE + '/perl.git'; const GIT_COMMITDIFF = GIT_REPO + '/commitdiff/'; function search_git_for(s) { return GIT_REPO + '?a=search&h=HEAD&st=commit&s=' + encodeURIComponent(s); } function process_ranges_under(root, predicate, body, kont) { if (predicate(root)) { let range = document.createRange(); range.selectNode(root); return body(range, kont); } let queue = [root]; let loop_tree = function loop_tree() { while (queue.length) { let node = queue.shift(); if (node.nodeType !== node.ELEMENT_NODE) { continue; } let loop_children = function loop_children(p) { while (p) { if (!predicate(p)) { queue.push(p); p = p.nextSibling; continue; } let range = document.createRange(); range.setStartBefore(p); while (p.nextSibling && predicate(p.nextSibling)) { p = p.nextSibling; } range.setEndAfter(p); p = p.nextSibling; return body(range, () => loop_children(p)); } return loop_tree(); }; return loop_children(node.firstChild); } return kont(); }; return loop_tree(); } function is_kinda_text(node) { return ( node.nodeType === node.TEXT_NODE || node.nodeType === node.ELEMENT_NODE && node.nodeName === 'BR' ); } function replace_text_under(root, body, kont) { return process_ranges_under( root, is_kinda_text, function (range, kont_inner) { let synth = ''; let frag = range.extractContents(); for (let p = frag.firstChild; p; p = p.nextSibling) { synth += p.nodeType === p.TEXT_NODE ? p.nodeValue : '\0'; } return body(synth, (x) => { range.insertNode(x); return kont_inner(); }); }, kont ); } function xpath(expr, doc) { doc = doc || document; return doc.evaluate(expr, doc, () => 'http://www.w3.org/1999/xhtml', XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); } function autolink(text, kont_outer) { let re = function () { let bug_re = ( '(?:' + '\\b' + '(?:' + 'bug' + '|' + 'fix\\w*' + '|' + 'perl' + ')' + ')?' + '[\\0\\s]+' + '#' + // $1 '(' + '\\d{2,}' + ')' + '\\b' ); let commit_re = ( // $2 '(' + '\\b' + '(?:' + 'as' + '|' + 'by' + '|' + 'commit' + '|' + 'in' + '|' + 'of' + '|' + 'with' + ')' + '[\\0\\s]+' + '|' + '[(:\\0]' + '[\\0\\s]*' + ')' + // $3 '(' + '[\\da-f]{4,}' + '\\b' + '(?:' + '[\\0\\s]*' + '(?:' + ',' + '|' + '(?:' + ',' + '[\\0\\s]*' + ')?' + '(?:' + 'and' + '|' + 'or' + ')' + '[\\0\\s]' + ')' + '[\\0\\s]*' + '[\\da-f]{4,}' + '\\b' + ')*' + ')' ); let p4id_re = ( // $4 '(' + 'applied' + '[\\0\\s]+' + 'as' + '[\\0\\s]+' + '|' + 'change' + '[\\0\\s]*' + ')' + // $5 '(' + '#' + '\\d{2,}' + '\\b' + ')' ); return new RegExp([bug_re, commit_re, p4id_re].join('|'), 'ig'); }(); let prev = 0; let frag = document.createDocumentFragment(); function autotext_from(t, a, z) { let chunk = t.slice(a, z); let pieces = chunk.match(/[^\0]+|\0/g) || []; for (let p of pieces) { let x = p === '\0' ? document.createElement('br') : document.createTextNode(p) ; frag.appendChild(x); } } function autotext(to) { autotext_from(text, prev, to); } function step(kont) { let m = re.exec(text); if (!m) { return kont(); } autotext(m.index + (m[2] || m[4] || '').length); let link_url, link_text; let kont_local = function () { let a = document.createElement('a'); a.href = link_url; a.appendChild(document.createTextNode(link_text)); frag.appendChild(a); prev = re.lastIndex; return step(kont); }; if (m[1]) { link_url = RT_TICKET + m[1]; link_text = m[0]; } else if (m[3]) { if (/^[\da-fA-F]+$/.test(m[3])) { link_url = GIT_COMMITDIFF + m[3]; link_text = m[3]; } else { let t = m[3]; let p = 0; let re2 = /\b[\da-fA-F]{4,}\b/g; let m2; while ((m2 = re2.exec(t))) { autotext_from(t, p, m2.index); let a = document.createElement('a'); a.href = GIT_COMMITDIFF + m2[0]; a.appendChild(document.createTextNode(m2[0])); frag.appendChild(a); p = re2.lastIndex; } autotext_from(t, p, t.length); prev = re.lastIndex; return step(kont); } } else { let srch = search_git_for('@' + m[5].substr(1)); return GM_xmlhttpRequest({ method: 'GET', synchronous: false, url: srch, responseType: 'document', onreadystatechange: function (r) { if (r.readyState !== 4) return; link_text = m[5]; link_url = srch; if (r.status === 200 && typeof r.response === 'object') { let results = xpath( '//h:table[@class="commit_search"]' + '//h:tr' + '[h:td/h:span[@class="match"][not(following-sibling::text())]]' + '/h:td[@class="link"]' + '/h:a[text()="commitdiff"][last()]', r.response ); if (results && results.snapshotLength === 1) { let base = (/^\w+:\/\/[^\/]+/.exec(r.finalUrl) || [GIT_BASE])[0]; link_url = results.snapshotItem(0).href.replace(/^(?=\/)/, () => base); } } return kont_local(); }, }); } return kont_local(); } step(function () { autotext(text.length); return kont_outer(frag); }); } let roots = document.querySelectorAll('div.messagebody'); for (let root of roots) { replace_text_under(root, autolink, () => {}); }