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

Commit e52ed23

Browse files
authored
Merge pull request #2 from EpocDotFr/copy-source-target-branch-name
Copy source and target branches name
2 parents b48f97c + c801515 commit e52ed23

File tree

9 files changed

+252
-24
lines changed

9 files changed

+252
-24
lines changed

README.md

+7-4
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,23 @@ A browser extension that enhance all Merge Requests lists on any instance of Git
99
## Features
1010

1111
- Display source and target branches
12+
- Buttons allowing to easily copy these branches name (can be disabled in the extension preferences)
1213
- Compatible with all GitLab editions (GitLab CE, GitLab EE, GitLab.com) (look at the prerequisites, though)
1314
- No configuration needed
1415

1516
## Prerequisites
1617

1718
- **GitLab**: 9.0 or above or GitLab.com (this addon requires GitLab API v4)
18-
- **Firefox**: any recent version of Firefox (>= 57)
19-
- **Chrome**: any version of Chrome
19+
- **Firefox**: >= 63 (because this extension uses the `clipboard.writeText` API)
20+
- **Chrome**: >= 66 (because this extension uses the `clipboard.writeText` API)
2021

2122
## Installation
2223

2324
- **Firefox**: from the [Firefox Add-ons](https://addons.mozilla.org/en-US/firefox/addon/gitlab-mrs-lists-enhancer/) website
2425
- **Chrome**: from the [Chrome Web Store](https://chrome.google.com/webstore/detail/gitlab-merge-requests-lis/emiefdjcbfjkaofipmdcflcddcchmdkf) website
2526

27+
You can also install this add-on manually by using one of the ZIP files on the [Releases](https://github.com/EpocDotFr/gitlab-merge-requests-lists-enhancer/releases) page.
28+
2629
## Credits
2730

2831
- Logo by [Thanga Vignesh P](https://www.iconfinder.com/icons/5402348/add_list_playlist_icon) (CC BY-NC 3.0)
@@ -31,8 +34,8 @@ A browser extension that enhance all Merge Requests lists on any instance of Git
3134

3235
👉 = current version
3336

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

css/options.css

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/************************************************************************
2+
* Global styles */
3+
4+
.txt-center {
5+
text-align: center;
6+
}
7+
8+
/************************************************************************
9+
* Preferences form */
10+
11+
form > * {
12+
padding-top: 10px;
13+
padding-bottom: 10px;
14+
}

html/options.html

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="UTF-8">
5+
<link rel="stylesheet" href="../css/options.css">
6+
</head>
7+
<body>
8+
<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>
11+
</div>
12+
<div class="txt-center"><button type="submit" class="browser-style">Save preferences</button></div>
13+
</form>
14+
15+
<script src="../js/preferences.js"></script>
16+
<script src="../js/options.js"></script>
17+
</body>
18+
</html>

js/content.js

+56-16
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
xhr.onload = callback;
3737

3838
xhr.onerror = function() {
39-
console.error('Error while communicating with GitLab');
39+
alert('Error while communicating with GitLab.');
4040
};
4141

4242
xhr.open(method, this.createEndpointUrl(endpoint, queryStringParameters));
@@ -62,7 +62,7 @@
6262

6363
class ContentScript {
6464
/**
65-
* Initialize the content script of the extension which is executed in the context of the page.
65+
* The content script of the extension which is executed in the context of the page.
6666
*/
6767
constructor() {
6868
this.currentProjectId = this.getCurrentProjectId();
@@ -87,16 +87,15 @@
8787
this.baseApiUrl = this.baseUrl + '/api/v4/';
8888
this.apiClient = new GitLabApiClient(this.baseApiUrl);
8989

90-
console.debug('Current project ID:', this.currentProjectId);
91-
console.debug('Base project URL:', this.baseProjectUrl);
92-
console.debug('GitLab base URL:', this.baseUrl);
93-
console.debug('GitLab API base URL:', this.baseApiUrl);
94-
9590
let currentMergeRequestIds = this.getCurrentMergeRequestIdsAndSetUuidDataAttributes();
91+
let preferencesManager = new globals.Gmrle.PreferencesManager();
9692

97-
console.debug('Current merge requests IDs:', currentMergeRequestIds);
93+
let self = this;
9894

99-
this.fetchMergeRequestsDetailsThenUpdateUI(currentMergeRequestIds);
95+
preferencesManager.getAll(function(preferences) {
96+
self.preferences = preferences;
97+
self.fetchMergeRequestsDetailsThenUpdateUI(currentMergeRequestIds);
98+
});
10099
}
101100

102101
/**
@@ -149,7 +148,10 @@
149148
if (this.status == 200) {
150149
self.removeExistingTargetBranchNodes();
151150
self.updateMergeRequestsNodes(this.response);
151+
self.attachClickEventToCopyBranchNameButtons();
152152
} else {
153+
alert('Got error from GitLab, check console for more information.');
154+
153155
console.error('Got error from GitLab:', this.status, this.response);
154156
}
155157
},
@@ -191,20 +193,58 @@
191193
let infoDiv = document
192194
.querySelector('.mr-list .merge-request[data-iid="' + mergeRequest.iid + '"] .issuable-main-info');
193195

194-
let html = '<div class="issuable-info"><span class="project-ref-path has-tooltip" title="Source branch">' +
195-
'<a class="ref-name" href="' + self.baseProjectUrl + '/-/commits/' + mergeRequest.source_branch + '">' + mergeRequest.source_branch + '</a>' +
196-
'</span>' +
197-
' <i class="fa fa-long-arrow-right" aria-hidden="true"></i> ' +
198-
'<span class="project-ref-path has-tooltip" title="Target branch">' +
199-
'<a class="ref-name" href="' + self.baseProjectUrl + '/-/commits/' + mergeRequest.target_branch + '">' + mergeRequest.target_branch + '</a>' +
200-
'</span></div>';
196+
let html = '<div class="issuable-info">' +
197+
'<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>' +
199+
'</span>';
200+
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 + '">' +
203+
'<i class="fa fa-clipboard" aria-hidden="true"></i>' +
204+
'</button>'
205+
}
206+
207+
html += ' <i class="fa fa-long-arrow-right" aria-hidden="true"></i> ' +
208+
'<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>' +
210+
'</span>';
211+
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 + '">' +
214+
'<i class="fa fa-clipboard" aria-hidden="true"></i>' +
215+
'</button>';
216+
}
217+
218+
html += '</div>';
201219

202220
self.parseHtmlAndAppendChild(
203221
infoDiv,
204222
html
205223
);
206224
});
207225
}
226+
227+
/**
228+
* 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).
230+
*/
231+
attachClickEventToCopyBranchNameButtons() {
232+
if (!this.preferences.enable_buttons_to_copy_source_and_target_branches_name) {
233+
return
234+
}
235+
236+
document.querySelectorAll('button.gmrle-copy-branch-name').forEach(function(el) {
237+
el.addEventListener('click', function(e) {
238+
e.preventDefault();
239+
240+
navigator.clipboard.writeText(el.dataset.branchName).then(function() {
241+
// Do nothing if copy was successful.
242+
}, function() {
243+
alert('Unable to copy branch name.');
244+
});
245+
});
246+
});
247+
}
208248
}
209249

210250
let cs = new ContentScript();

js/options.js

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
(function(globals) {
2+
'use strict';
3+
4+
class OptionsPage {
5+
/**
6+
* Class which handles everything related to the options page of the extension. Preferences are persisted in
7+
* the browser's local storage.
8+
*/
9+
constructor() {
10+
this.preferencesManager = new globals.Gmrle.PreferencesManager();
11+
12+
this.getDomNodes();
13+
this.restoreOptionsFromStorage();
14+
this.attachEventListenersToDomNodes();
15+
}
16+
17+
/**
18+
* Queries and caches every relevant DOM nodes for further manipulations.
19+
*/
20+
getDomNodes() {
21+
this.optionsForm = document.querySelector('form');
22+
this.enableButtonsToCopySourceAndTargetBranchesNameCheckbox = document.querySelector('input#enable_buttons_to_copy_source_and_target_branches_name');
23+
}
24+
25+
/**
26+
* Retrieve preferences from local storage and update the UI accordingly.
27+
*/
28+
restoreOptionsFromStorage() {
29+
let self = this;
30+
31+
this.preferencesManager.getAll(function(preferences) {
32+
self.enableButtonsToCopySourceAndTargetBranchesNameCheckbox.checked = preferences.enable_buttons_to_copy_source_and_target_branches_name;
33+
});
34+
}
35+
36+
/**
37+
* Attach some events to DOM nodes that were queried early.
38+
*/
39+
attachEventListenersToDomNodes() {
40+
let self = this;
41+
42+
this.optionsForm.addEventListener('submit', function(e) {
43+
e.preventDefault();
44+
45+
self.saveOptionsToStorage();
46+
});
47+
}
48+
49+
/**
50+
* Take all DOM nodes values and perist them in the local storage.
51+
*/
52+
saveOptionsToStorage() {
53+
this.preferencesManager.setAll({
54+
enable_buttons_to_copy_source_and_target_branches_name: this.enableButtonsToCopySourceAndTargetBranchesNameCheckbox.checked
55+
});
56+
}
57+
}
58+
59+
document.addEventListener('DOMContentLoaded', function() {
60+
let op = new OptionsPage();
61+
});
62+
}(this));

js/preferences.js

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
(function(globals) {
2+
'use strict';
3+
4+
globals.Gmrle = globals.Gmrle || {};
5+
6+
globals.Gmrle.PreferencesManager = class {
7+
get defaults() {
8+
return {
9+
enable_buttons_to_copy_source_and_target_branches_name: true
10+
};
11+
}
12+
13+
/**
14+
* This class holds all the logic related to user preferences persistance.
15+
*/
16+
constructor() {
17+
if (globals.browser) { // Firefox uses `browser`, Chrome uses `chrome`
18+
this.getAll = this.getAllFirefox;
19+
this.setAll = this.setAllFirefox;
20+
} else if (globals.chrome) {
21+
this.getAll = this.getAllChrome;
22+
this.setAll = this.setAllChrome;
23+
} else {
24+
console.error('Unsupported browser');
25+
}
26+
}
27+
28+
/**
29+
* Get all the user's preferences.
30+
*
31+
* Used as `getAll` if the current browser is Firefox.
32+
*/
33+
getAllFirefox(callback) {
34+
browser.storage.local.get(this.defaults).then(callback, function() {
35+
alert('Error retrieving extension preferences.');
36+
});
37+
}
38+
39+
/**
40+
* Save all the user's preferences.
41+
*
42+
* Used as `setAll` if the current browser is Firefox.
43+
*/
44+
setAllFirefox(preferences) {
45+
browser.storage.local.set(preferences).then(function() {
46+
// Do nothing if save was successful.
47+
}, function() {
48+
alert('Error saving extension preferences.');
49+
});
50+
}
51+
52+
/**
53+
* Get all the user's preferences.
54+
*
55+
* Used as `getAll` if the current browser is Chrome.
56+
*/
57+
getAllChrome(callback) {
58+
chrome.storage.local.get(this.defaults, function(preferences) {
59+
if (chrome.runtime.lastError) {
60+
alert('Error retrieving extension preferences, check console for more information.');
61+
62+
console.error('Error retrieving extension preferences:', chrome.runtime.lastError);
63+
} else {
64+
callback(preferences);
65+
}
66+
});
67+
}
68+
69+
/**
70+
* Save all the user's preferences.
71+
*
72+
* Used as `setAll` if the current browser is Chrome.
73+
*/
74+
setAllChrome(preferences) {
75+
chrome.storage.local.set(preferences, function() {
76+
if (chrome.runtime.lastError) {
77+
alert('Error saving extension preferences, check console for more information.');
78+
79+
console.error('Error saving extension preferences:', chrome.runtime.lastError);
80+
}
81+
});
82+
}
83+
}
84+
}(this));

screenshot.png

-8.86 KB
Loading

scripts/manage.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@ def create_manifest_file(target):
88
data = settings.MANIFEST_FILE
99

1010
if target == 'firefox':
11+
data['options_ui']['browser_style'] = True
1112
data['browser_specific_settings'] = {
1213
'gecko': {
1314
14-
'strict_min_version': '57.0'
15+
'strict_min_version': '63.0'
1516
}
1617
}
18+
elif target == 'chrome':
19+
data['options_ui']['chrome_style'] = True
1720

1821
with open('manifest.json', 'w', encoding='utf-8') as f:
1922
json.dump(data, f, indent=2)

scripts/settings.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
MANIFEST_FILE = {
22
'manifest_version': 2,
33
'name': 'GitLab Merge Requests lists enhancer',
4-
'version': '1.0.0',
4+
'version': '1.1.0',
55
'description': 'An extension that enhance all Merge Requests lists on any instance of Gitlab and GitLab.com.',
66
'homepage_url': 'https://github.com/EpocDotFr/gitlab-merge-requests-lists-enhancer',
77
'author': 'Maxime \'Epoc\' G.',
@@ -14,11 +14,15 @@
1414
'content_scripts': [
1515
{
1616
'matches': ['*://*/*/*/-/merge_requests', '*://*/*/*/-/merge_requests?*'],
17-
'js': ['js/content.js']
17+
'js': ['js/preferences.js', 'js/content.js']
1818
}
1919
],
20+
'options_ui': {
21+
'page': 'html/options.html'
22+
},
2023
'permissions': [
21-
'*://*/*/*/-/merge_requests', '*://*/*/*/-/merge_requests?*'
24+
'*://*/*/*/-/merge_requests', '*://*/*/*/-/merge_requests?*',
25+
'storage'
2226
]
2327
}
2428

0 commit comments

Comments
 (0)