-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathpivotal-linked-github.js
147 lines (132 loc) · 5.2 KB
/
pivotal-linked-github.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
;(function(){
// http://stackoverflow.com/questions/10730309/find-all-text-nodes-in-html-page
function getTextNodesUnder(el){
var n;
var a = [];
var walk = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null, false);
while (n = walk.nextNode()){
a.push(n);
}
return a;
}
// http://stackoverflow.com/questions/4793604/how-to-do-insert-after-in-javascript-without-using-a-library
function insertAfter(newNode, referenceNode) {
referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
}
// http://stackoverflow.com/questions/5540555/how-to-check-if-dom-textnode-is-a-link
// Get the anchor element containing the textNode that is within the rootNode
function getAnchorParentNode(textNode, rootNode) {
var curNode = textNode;
while (curNode) {
if (curNode.tagName == 'A'){
return curNode;
} else if (curNode === rootNode) {
return null;
} else {
curNode = curNode.parentNode;
}
}
}
// matches a hash followed by digits (ex. #12345)
var storyIdRegExp = /#([0-9]+)/g;
// Find all pivotal stories within the string
// Returns an array of matches which include the story id and the position it was found in
function getPivotalStoryIds(string) {
// a pair of square brackets containing one or more sets of a hash followed by id
// https://www.pivotaltracker.com/help/api?version=v5#Tracker_Updates_in_SCM_Post_Commit_Hooks
var pivotalSyntaxRegExp = /\[([^\]]*#[0-9][^\]]*)\]/g;
var matches = [];
var result;
while ((result = pivotalSyntaxRegExp.exec(string)) !== null){
var pivotalSyntaxString = result[1];
var baseIndex = result.index;
var storyIds = pivotalSyntaxString.match(storyIdRegExp);
for (var i = 0; i < storyIds.length; i++){
var innerIndex = pivotalSyntaxString.indexOf(storyIds[i]);
matches.push({storyId: storyIds[i], index: baseIndex + innerIndex + 1});
}
}
return matches;
}
// Clone the right subtree of upToNode that includes textNode as its leftmost node
function cloneRightTree(textNode, upToNode){
var curNode = textNode.parentNode;
var prevNode = textNode;
var clone = textNode;
while (curNode) {
var temp = curNode.cloneNode(false);
temp.appendChild(clone);
clone = temp;
if (curNode === upToNode){
var sibling;
while ((sibling = prevNode.nextSibling) !== null){
clone.appendChild(sibling.cloneNode(true));
}
return clone;
}
prevNode = curNode;
curNode = curNode.parentNode;
}
}
function createPivotalLink(storyId){
var link = document.createElement('a');
link.setAttribute('href', 'https://www.pivotaltracker.com/story/show/' + storyId.slice(1));
link.setAttribute('target', '_blank');
link.innerHTML = storyId;
return link;
}
function addPivotalLinksTo(textNode, matches, baseIndex, anchorParent){
if (matches.length === 0){
return;
}
var curNode = textNode;
var index = matches[0].index - baseIndex;
var pivotalTextNode = curNode.splitText(index);
var pivotalLink = createPivotalLink(matches[0].storyId);
if (anchorParent){
var tempPivotalLink = cloneRightTree(pivotalTextNode, anchorParent);
insertAfter(tempPivotalLink, anchorParent);
curNode = pivotalTextNode.splitText(matches[0].storyId.length);
anchorParent = cloneRightTree(curNode, tempPivotalLink);
insertAfter(anchorParent, tempPivotalLink);
pivotalLink.className = 'issue-link';
tempPivotalLink.parentNode.replaceChild(pivotalLink, tempPivotalLink);
insertAfter(document.createTextNode(' '), pivotalLink);
pivotalLink.parentNode.insertBefore(document.createTextNode(' '), pivotalLink);
} else {
curNode = pivotalTextNode.splitText(matches[0].storyId.length);
pivotalTextNode.parentNode.replaceChild(pivotalLink, pivotalTextNode);
}
addPivotalLinksTo(curNode, matches.slice(1), baseIndex + index + matches[0].storyId.length, anchorParent);
}
function findAllStoriesAndLinkToPivotal(){
var nodes = document.querySelectorAll('.comment-body, .commit-title, .commit-message');
for (var i = 0; i < nodes.length; i++){
var textNodes = getTextNodesUnder(nodes[i]);
for (var j = 0; j< textNodes.length; j++){
var text = textNodes[j].nodeValue;
var matches = getPivotalStoryIds(text);
var anchorNode = getAnchorParentNode(textNodes[j], nodes[i]);
addPivotalLinksTo(textNodes[j], matches, 0, anchorNode);
}
}
}
function isATargetOfInterest(target){
return target.classList.contains('comment-body') ||
target.classList.contains('js-discussion') ||
target.id === 'js-repo-pjax-container'
}
findAllStoriesAndLinkToPivotal();
var pjaxContainer = document.querySelector('#js-repo-pjax-container');
if (pjaxContainer){
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (isATargetOfInterest(mutation.target) && mutation.type === 'childList'){
findAllStoriesAndLinkToPivotal();
return;
}
});
});
observer.observe(pjaxContainer, {childList: true, subtree: true});
}
}());