Skip to content
This repository was archived by the owner on Aug 31, 2023. It is now read-only.

Commit 391e6ed

Browse files
authored
Merge pull request #1 from EpocDotFr/copy-mr-info
Copy basic Merge Request information
2 parents e52ed23 + a3e6cd3 commit 391e6ed

13 files changed

+256
-54
lines changed

README.md

+6-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ A browser extension that enhance all Merge Requests lists on any instance of Git
1010

1111
- Display source and target branches
1212
- Buttons allowing to easily copy these branches name (can be disabled in the extension preferences)
13+
- Button allowing to copy Merge Request information (useful when sharing the Merge Request on e.g instant messaging softwares)
14+
- Can be disabled in the extension preferences
15+
- Text format is customizable (with support of placeholders)
1316
- Compatible with all GitLab editions (GitLab CE, GitLab EE, GitLab.com) (look at the prerequisites, though)
1417
- No configuration needed
1518

@@ -28,15 +31,15 @@ You can also install this add-on manually by using one of the ZIP files on the [
2831

2932
## Credits
3033

31-
- Logo by [Thanga Vignesh P](https://www.iconfinder.com/icons/5402348/add_list_playlist_icon) (CC BY-NC 3.0)
34+
- Logo: [GitLab](https://about.gitlab.com/press/press-kit/#logos) and [Octicons](https://primer.style/octicons/git-pull-request) (MIT License)
3235

3336
## Roadmap
3437

3538
👉 = current version
3639

3740
- **1.0** - Initial release (display Merge Request source and target branches)
38-
- 👉 **1.1** - Copy source and target branches name
39-
- **1.2** - Copy basic Merge Request information (intended for sharing on e.g instant messaging softwares)
41+
- **1.1** - Copy source and target branches name
42+
- 👉 **1.2** - Copy Merge Request information (intended for sharing on e.g instant messaging softwares)
4043
- **1.3** - Direct Jira ticket link (automatic detection of ticket ID in branch name or Merge Request title)
4144
- **1.4** - WIP / unWIP toggle button
4245

css/options.css

+66-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,76 @@
11
/************************************************************************
22
* Global styles */
33

4+
/*
5+
* Firefox for Mac: use a dark theme if the OS/browser also uses one.
6+
* This doesn't affects Firefox for Windows (as for now), as dark/light theme detection isn't implemented for this OS.
7+
*/
8+
@supports (-moz-appearance:none) {
9+
@media (prefers-color-scheme: dark) {
10+
body {
11+
background-color: #202023;
12+
color: rgb(249, 249, 250);
13+
}
14+
}
15+
}
16+
17+
.is-hidden {
18+
display: none;
19+
}
20+
21+
/************************************************************************
22+
* Text styles */
23+
424
.txt-center {
525
text-align: center;
626
}
727

28+
.monospaced {
29+
font-family: monospace;
30+
}
31+
32+
/************************************************************************
33+
* Sizing styles */
34+
35+
.w100 {
36+
width: 100%;
37+
}
38+
39+
.w40p {
40+
width: 40px;
41+
}
42+
843
/************************************************************************
9-
* Preferences form */
44+
* Layout styles */
45+
46+
.row {
47+
display: table;
48+
width: 100%;
49+
}
50+
51+
.row > * {
52+
display: table-cell;
53+
}
54+
55+
/************************************************************************
56+
* Margin and padding styles */
57+
58+
.pas {
59+
padding: 5px;
60+
}
61+
62+
.pts {
63+
padding-top: 5px;
64+
}
65+
66+
.pbs {
67+
padding-bottom: 5px;
68+
}
69+
70+
.pll {
71+
padding-left: 40px;
72+
}
1073

11-
form > * {
12-
padding-top: 10px;
13-
padding-bottom: 10px;
74+
.man {
75+
margin: 0;
1476
}

html/options.html

+24-3
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,31 @@
66
</head>
77
<body>
88
<form>
9-
<div class="browser-style">
10-
<input type="checkbox" id="enable_buttons_to_copy_source_and_target_branches_name" class="browser-style"> <label for="enable_buttons_to_copy_source_and_target_branches_name">Enable buttons allowing to copy source and target branches name</label>
9+
<div class="pts pbs man row">
10+
<div class="w40p txt-center browser-style">
11+
<input type="checkbox" id="enable_buttons_to_copy_source_and_target_branches_name">
12+
</div>
13+
<div>
14+
<label for="enable_buttons_to_copy_source_and_target_branches_name">Enable buttons allowing to copy source and target branches name</label>
15+
</div>
1116
</div>
12-
<div class="txt-center"><button type="submit" class="browser-style">Save preferences</button></div>
17+
<div class="pts man row">
18+
<div class="w40p txt-center browser-style">
19+
<input type="checkbox" id="enable_button_to_copy_mr_info">
20+
</div>
21+
<div>
22+
<label for="enable_button_to_copy_mr_info">Enable button allowing to copy Merge Request information</label>
23+
</div>
24+
</div>
25+
<div class="pbs pll">
26+
<small>Useful when sharing the Merge Request on e.g instant messaging softwares</small>
27+
</div>
28+
<div class="pll pts pbs man" id="copy-mr-info-options">
29+
<div class="pbs"><label for="copy_mr_info_format">Text format:</label></div>
30+
<div><textarea class="browser-style w100 monospaced man pas" id="copy_mr_info_format" rows="6" required></textarea></div>
31+
<div class="pts"><small>Available placeholders: <code>{MR_TITLE}</code>, <code>{MR_ID}</code>, <code>{MR_URL}</code>, <code>{MR_DIFFS_URL}</code>, <code>{MR_AUTHOR_NAME}</code>, <code>{MR_STATUS}</code>, <code>{MR_SOURCE_BRANCH_NAME}</code>, <code>{MR_TARGET_BRANCH_NAME}</code></small></div>
32+
</div>
33+
<div class="txt-center pts pbs man"><button type="submit" class="browser-style">Save preferences</button></div>
1334
</form>
1435

1536
<script src="../js/preferences.js"></script>

images/logo_128.png

5.46 KB
Loading

images/logo_16.png

356 Bytes
Loading

images/logo_48.png

1.65 KB
Loading

images/logo_96.png

3.42 KB
Loading

js/content.js

+134-39
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@
8787
this.baseApiUrl = this.baseUrl + '/api/v4/';
8888
this.apiClient = new GitLabApiClient(this.baseApiUrl);
8989

90-
let currentMergeRequestIds = this.getCurrentMergeRequestIdsAndSetUuidDataAttributes();
90+
let currentMergeRequestIds = this.getCurrentMergeRequestIds();
9191
let preferencesManager = new globals.Gmrle.PreferencesManager();
9292

9393
let self = this;
@@ -121,18 +121,13 @@
121121
}
122122

123123
/**
124-
* Gets all Merge Requests IDs that are currently displayed AND sets the `iid` data attribute (public Merge
125-
* Request identifier) on all DOM nodes representing a Merge Requests (it's used later in the process).
124+
* Gets all Merge Requests IDs that are currently displayed.
126125
*/
127-
getCurrentMergeRequestIdsAndSetUuidDataAttributes() {
126+
getCurrentMergeRequestIds() {
128127
return Array.from(
129-
document.querySelectorAll('.mr-list .merge-request')
128+
document.querySelectorAll('.mr-list .merge-request .issuable-reference')
130129
).map(function(el) {
131-
let iid = el.querySelector('.issuable-reference').textContent.trim().replace('!', '');
132-
133-
el.dataset.iid = iid;
134-
135-
return iid;
130+
return el.textContent.trim().replace('!', '');
136131
});
137132
}
138133

@@ -148,7 +143,14 @@
148143
if (this.status == 200) {
149144
self.removeExistingTargetBranchNodes();
150145
self.updateMergeRequestsNodes(this.response);
151-
self.attachClickEventToCopyBranchNameButtons();
146+
147+
if (self.preferences.enable_buttons_to_copy_source_and_target_branches_name) {
148+
self.attachClickEventToCopyBranchNameButtons();
149+
}
150+
151+
if (self.preferences.enable_button_to_copy_mr_info) {
152+
self.attachClickEventToCopyMergeRequestInfoButtons();
153+
}
152154
} else {
153155
alert('Got error from GitLab, check console for more information.');
154156

@@ -170,81 +172,174 @@
170172
}
171173

172174
/**
173-
* Append the given HTML string at the end of the given child target node.
175+
* Parses HTML code and applies a callback on all of the parsed root DOM nodes.
174176
*/
175-
parseHtmlAndAppendChild(targetNode, html) {
177+
parseHtml(html, callback) {
176178
new DOMParser()
177179
.parseFromString(html, 'text/html')
178180
.querySelector('body')
179181
.childNodes
180182
.forEach(function(node) {
181-
targetNode.appendChild(node);
183+
callback(node);
182184
}
183-
)
185+
);
186+
}
187+
188+
/**
189+
* Prepends the given HTML string at the beginning of the given child target node.
190+
*/
191+
parseHtmlAndPrepend(targetNode, html) {
192+
this.parseHtml(html, function(node) {
193+
targetNode.prepend(node);
194+
});
195+
}
196+
197+
/**
198+
* Appends the given HTML string at the end of the given child target node.
199+
*/
200+
parseHtmlAndAppend(targetNode, html) {
201+
this.parseHtml(html, function(node) {
202+
targetNode.append(node);
203+
});
184204
}
185205

186206
/**
187207
* Actually updates the UI by altering the DOM by adding our stuff.
188208
*/
189209
updateMergeRequestsNodes(mergeRequestsDetails) {
190-
let self = this;
191-
192210
mergeRequestsDetails.forEach(function(mergeRequest) {
193-
let infoDiv = document
194-
.querySelector('.mr-list .merge-request[data-iid="' + mergeRequest.iid + '"] .issuable-main-info');
211+
let mergeRequestContainer = document.querySelector('.mr-list .merge-request[data-id="' + mergeRequest.id + '"]');
212+
213+
this.setDataAttributesToMergeRequestContainer(mergeRequestContainer, mergeRequest);
214+
215+
// -----------------------------------------------
216+
// Copy MR info button
217+
218+
if (this.preferences.enable_button_to_copy_mr_info) {
219+
let copyMrInfoButton = '<button class="btn btn-secondary btn-md btn-default btn-transparent btn-clipboard has-tooltip gmrle-copy-mr-info" title="Copy Merge Request info">' +
220+
'<i class="fa fa-share-square-o" aria-hidden="true"></i>' +
221+
'</button> ';
222+
223+
this.parseHtmlAndPrepend(
224+
mergeRequestContainer.querySelector('.issuable-info'),
225+
copyMrInfoButton
226+
);
227+
}
228+
229+
// -----------------------------------------------
230+
// Source and target branches info
195231

196-
let html = '<div class="issuable-info">' +
232+
// Source branch name
233+
let newInfoLineToInject = '<div class="issuable-info">' +
197234
'<span class="project-ref-path has-tooltip" title="Source branch">' +
198-
'<a class="ref-name" href="' + self.baseProjectUrl + '/-/commits/' + mergeRequest.source_branch + '">' + mergeRequest.source_branch + '</a>' +
235+
'<a class="ref-name" href="' + this.baseProjectUrl + '/-/commits/' + mergeRequest.source_branch + '">' + mergeRequest.source_branch + '</a>' +
199236
'</span>';
200237

201-
if (self.preferences.enable_buttons_to_copy_source_and_target_branches_name) {
202-
html += ' <button class="btn btn-secondary btn-md btn-default btn-transparent btn-clipboard has-tooltip gmrle-copy-branch-name" title="Copy branch name" data-branch-name="' + mergeRequest.source_branch + '">' +
238+
// Copy source branch name button
239+
if (this.preferences.enable_buttons_to_copy_source_and_target_branches_name) {
240+
newInfoLineToInject += ' <button class="btn btn-secondary btn-md btn-default btn-transparent btn-clipboard has-tooltip gmrle-copy-branch-name" title="Copy branch name" data-branch-name-to-copy="source">' +
203241
'<i class="fa fa-clipboard" aria-hidden="true"></i>' +
204-
'</button>'
242+
'</button>';
205243
}
206244

207-
html += ' <i class="fa fa-long-arrow-right" aria-hidden="true"></i> ' +
245+
// Target branch name
246+
newInfoLineToInject += ' <i class="fa fa-long-arrow-right" aria-hidden="true"></i> ' +
208247
'<span class="project-ref-path has-tooltip" title="Target branch">' +
209-
'<a class="ref-name" href="' + self.baseProjectUrl + '/-/commits/' + mergeRequest.target_branch + '">' + mergeRequest.target_branch + '</a>' +
248+
'<a class="ref-name" href="' + this.baseProjectUrl + '/-/commits/' + mergeRequest.target_branch + '">' + mergeRequest.target_branch + '</a>' +
210249
'</span>';
211250

212-
if (self.preferences.enable_buttons_to_copy_source_and_target_branches_name) {
213-
html += ' <button class="btn btn-secondary btn-md btn-default btn-transparent btn-clipboard has-tooltip gmrle-copy-branch-name" title="Copy branch name" data-branch-name="' + mergeRequest.target_branch + '">' +
251+
// Copy target branch name button
252+
if (this.preferences.enable_buttons_to_copy_source_and_target_branches_name) {
253+
newInfoLineToInject += ' <button class="btn btn-secondary btn-md btn-default btn-transparent btn-clipboard has-tooltip gmrle-copy-branch-name" title="Copy branch name" data-branch-name-to-copy="target">' +
214254
'<i class="fa fa-clipboard" aria-hidden="true"></i>' +
215255
'</button>';
216256
}
217257

218-
html += '</div>';
258+
newInfoLineToInject += '</div>';
219259

220-
self.parseHtmlAndAppendChild(
221-
infoDiv,
222-
html
260+
this.parseHtmlAndAppend(
261+
mergeRequestContainer.querySelector('.issuable-main-info'),
262+
newInfoLineToInject
223263
);
224-
});
264+
}, this);
265+
}
266+
267+
/**
268+
* Sets several data-* attributes on a DOM node representing a Merge Request so these values may be used later.
269+
*/
270+
setDataAttributesToMergeRequestContainer(mergeRequestContainer, mergeRequest) {
271+
mergeRequestContainer.dataset.title = mergeRequest.title;
272+
mergeRequestContainer.dataset.iid = mergeRequest.iid;
273+
mergeRequestContainer.dataset.url = mergeRequest.web_url;
274+
mergeRequestContainer.dataset.diffsUrl = mergeRequest.web_url + '/diffs';
275+
mergeRequestContainer.dataset.authorName = mergeRequest.author.name;
276+
mergeRequestContainer.dataset.status = mergeRequest.state;
277+
mergeRequestContainer.dataset.sourceBranchName = mergeRequest.source_branch;
278+
mergeRequestContainer.dataset.targetBranchName = mergeRequest.target_branch;
225279
}
226280

227281
/**
228282
* Attach a click event to all buttons inserted by the extension allowing to copy the source and target
229-
* branches name (if feature is enabled by the user).
283+
* branches name.
230284
*/
231285
attachClickEventToCopyBranchNameButtons() {
232-
if (!this.preferences.enable_buttons_to_copy_source_and_target_branches_name) {
233-
return
234-
}
235-
236286
document.querySelectorAll('button.gmrle-copy-branch-name').forEach(function(el) {
237287
el.addEventListener('click', function(e) {
238288
e.preventDefault();
239289

240-
navigator.clipboard.writeText(el.dataset.branchName).then(function() {
290+
let branchName = this.closest('.merge-request').dataset[this.dataset.branchNameToCopy + 'BranchName'];
291+
292+
navigator.clipboard.writeText(branchName).then(function() {
241293
// Do nothing if copy was successful.
242294
}, function() {
243295
alert('Unable to copy branch name.');
244296
});
245297
});
246298
});
247299
}
300+
301+
/**
302+
* Attach a click event to all buttons inserted by the extension allowing to copy Merge Request info.
303+
*/
304+
attachClickEventToCopyMergeRequestInfoButtons() {
305+
let self = this;
306+
307+
document.querySelectorAll('button.gmrle-copy-mr-info').forEach(function(el) {
308+
el.addEventListener('click', function(e) {
309+
e.preventDefault();
310+
311+
let text = self.buildMergeRequestInfoText(this.closest('.merge-request'));
312+
313+
navigator.clipboard.writeText(text).then(function() {
314+
// Do nothing if copy was successful.
315+
}, function() {
316+
alert('Unable to copy Merge Request info.');
317+
});
318+
});
319+
});
320+
}
321+
322+
/**
323+
* Creates the Merge Request info text from a Merge Request container DOM node.
324+
*/
325+
buildMergeRequestInfoText(mergeRequestContainer) {
326+
let placeholders = {
327+
'MR_TITLE': mergeRequestContainer.dataset.title,
328+
'MR_ID': mergeRequestContainer.dataset.iid,
329+
'MR_URL': mergeRequestContainer.dataset.url,
330+
'MR_DIFFS_URL': mergeRequestContainer.dataset.diffsUrl,
331+
'MR_AUTHOR_NAME': mergeRequestContainer.dataset.authorName,
332+
'MR_STATUS': mergeRequestContainer.dataset.status,
333+
'MR_SOURCE_BRANCH_NAME': mergeRequestContainer.dataset.sourceBranchName,
334+
'MR_TARGET_BRANCH_NAME': mergeRequestContainer.dataset.targetBranchName
335+
};
336+
337+
let placeholdersReplaceRegex = new RegExp('{(' + Object.keys(placeholders).join('|') + ')}', 'g');
338+
339+
return this.preferences.copy_mr_info_format.replace(placeholdersReplaceRegex, function(_, placeholder) {
340+
return placeholders[placeholder];
341+
});
342+
}
248343
}
249344

250345
let cs = new ContentScript();

0 commit comments

Comments
 (0)