Fix rendering issue after switching to textContent
Replaced all raw html with dynamically generated elements
Change-Id: I863e32d37528cdc79a6b515440c340ed678635b1
diff --git a/src/main/resources/static/find-owners.js b/src/main/resources/static/find-owners.js
index c6f2826..720a424 100644
--- a/src/main/resources/static/find-owners.js
+++ b/src/main/resources/static/find-owners.js
@@ -33,8 +33,7 @@
'position: fixed;' +
'z-index: 100;' +
'overflow: auto;' +
- 'padding: ' + PADDING + 'px;'
- );
+ 'padding: ' + PADDING + 'px;');
const BUTTON_STYLE = Gerrit.css(
'background-color: #4d90fe;' +
'border: 2px solid;' +
@@ -45,26 +44,55 @@
'font-weight: bold;' +
'color: #fff;' +
'-webkit-border-radius: 2px;' +
- 'cursor: pointer;'
- );
- const HTML_BULLET = '<small>★</small>'; // a Black Star
- const HTML_IS_EXEMPTED =
- '<b>This change is Exempt-From-Owner-Approval.</b><br>';
+ 'cursor: pointer;');
+ const createElWithText = (tagName, text) => {
+ const el = document.createElement(tagName);
+ if (text) el.textContent = text;
+ return el;
+ };
- const HTML_NO_OWNER =
- '<b>No owner was found for changed files.</b>';
- const HTML_ONSUBMIT_HEADER =
- '<b>WARNING: Need owner Code-Review vote before submit.</b><hr>';
- const HTML_OWNERS_HEADER = '<hr><b>Owners in alphabetical order:</b><br>';
- const HTML_SELECT_REVIEWERS =
- '<b>Check the box before owner names to select reviewers, ' +
- 'then click the "Apply" button.' +
- '</b><br><small>If owner-approval requirement is enabled, ' +
- 'each file needs at least one Code-Review +1 vote from an owner. ' +
- 'Owners listed after a file are ordered by their importance. ' +
- '(Or declare "<b><span style="font-size:80%;">' +
- 'Exempt-From-Owner-Approval:</span></b> ' +
- '<i>reasons...</i>" in the Commit Message.)</small><br>';
+ const createBulletEl = () => {
+ return createElWithText('small', '\u2605'); // a Black Star
+ };
+ const createIsExemptedEl = () => {
+ return createElWithText('b', 'This change is Exempt-From-Owner-Approval.');
+ };
+
+ const createNoOwnerEl = () => {
+ return createElWithText('b', 'No owner was found for changed files.');
+ };
+
+ const createOnSubmitHeader = () => {
+ return createElWithText(
+ 'b', 'WARNING: Need owner Code-Review vote before submit.');
+ };
+
+ const createOwnersHeaderEl = () => {
+ return createElWithText('b', 'Owners in alphabetical order:');
+ };
+
+ const createSelectReviewersEl = () => {
+ const el = document.createElement('p');
+ el.appendChild(createElWithText(
+ 'b',
+ 'Check the box before owner names to select reviewers, ' +
+ 'then click the "Apply" button.'));
+ el.appendChild(createElWithText('br'));
+ const smallEl = createElWithText(
+ 'small',
+ 'If owner-approval requirement is enabled, ' +
+ 'each file needs at least one Code-Review +1 vote from an owner. ' +
+ 'Owners listed after a file are ordered by their importance. ' +
+ '(Or declare "');
+ const smallerEl = createElWithText('b', 'Exempt-From-Owner-Approval:');
+ smallerEl.style.fontSize = '80%';
+ smallEl.appendChild(smallerEl);
+ smallEl.appendChild(createElWithText('i', 'reasons...'));
+ smallEl.appendChild(document.createTextNode('" in the commit message.)'));
+ el.appendChild(smallEl);
+ el.appendChild(createElWithText('br'));
+ return el;
+ };
// Changed files are put into groups.
// Each group has a unique list of owners and
@@ -72,28 +100,38 @@
// Groups of a type are displayed in one HTML section.
// Group type names are mapped to ordered numbers starting from 0.
const GROUP_TYPE = {
- 'NEED_REVIEWER': 0, // no owner in Reviewers list yet
- 'NEED_APPROVAL': 1, // no owner Code-Review +1 yet
- 'STAR_APPROVED': 2, // has '*', no need of owner vote
- 'OWNER_APPROVED': 3, // has owner approval
- 'HAS_NO_OWNER': 4, // no owner at all, only shown with other types
+ 'NEED_REVIEWER': 0, // no owner in Reviewers list yet
+ 'NEED_APPROVAL': 1, // no owner Code-Review +1 yet
+ 'STAR_APPROVED': 2, // has '*', no need of owner vote
+ 'OWNER_APPROVED': 3, // has owner approval
+ 'HAS_NO_OWNER': 4, // no owner at all, only shown with other types
};
const NUM_GROUP_TYPES = 5;
+ const createGroupTypeHeaderEl = (text) => {
+ const el = createElWithText('span');
+ el.appendChild(createElWithText('hr'));
+ el.appendChild(createElWithText('b', text));
+ el.appendChild(createElWithText('br'));
+ return el;
+ };
const HTML_GROUP_TYPE_HEADER = [
- '<hr><b>Files with owners but no owner is in the Reviewers list:</b><br>',
- '<hr><b>Files with owners but no Code-Review vote from an owner:</b><br>',
- '<hr><b>Files with owners but can be approved by anyone (*):</b><br>',
- '<hr><b>Files with +1 or +2 Code-Review vote from owners:</b><br>',
- '<hr><b>Files without any named owner:</b><br>',
+ createGroupTypeHeaderEl(
+ 'Files with owners but no owner is in the Reviewers list:'),
+ createGroupTypeHeaderEl(
+ 'Files with owners but no Code-Review vote from an owner:'),
+ createGroupTypeHeaderEl(
+ 'Files with owners but can be approved by anyone (*):'),
+ createGroupTypeHeaderEl('Files with +1 or +2 Code-Review vote from owners:'),
+ createGroupTypeHeaderEl('Files without any named owner:'),
];
const GROUP_TYPE_DIV_ID = [
- 'FindOwners:NeedReviewer',
- 'FindOwners:NeedApproval',
- 'FindOwners:StarApproved',
- 'FindOwners:OwnerApproved',
- 'FindOwners:HasNoOwner',
+ 'FindOwners:NeedReviewer',
+ 'FindOwners:NeedApproval',
+ 'FindOwners:StarApproved',
+ 'FindOwners:OwnerApproved',
+ 'FindOwners:HasNoOwner',
];
const APPLY_BUTTON_ID = 'FindOwners:Apply';
@@ -108,14 +146,14 @@
const message = revision.commit.message;
const project = change.project;
- var minVoteLevel = 1; // could be changed by server returned results.
- var reviewerId = {}; // map from a reviewer's email to account id.
- var reviewerVote = {}; // map from a reviewer's email to Code-Review vote.
+ var minVoteLevel = 1; // could be changed by server returned results.
+ var reviewerId = {}; // map from a reviewer's email to account id.
+ var reviewerVote = {}; // map from a reviewer's email to Code-Review vote.
// addList and removeList are used only under applySelections.
- var addList = []; // remain emails to add to reviewers
- var removeList = []; // remain emails to remove from reviewers
- var needRefresh = false; // true if to refresh after checkAddRemoveLists
+ var addList = []; // remain emails to add to reviewers
+ var removeList = []; // remain emails to remove from reviewers
+ var needRefresh = false; // true if to refresh after checkAddRemoveLists
function getElement(id) {
return document.getElementById(id);
@@ -124,11 +162,11 @@
self.restApi().get('/../..' + url).then(callback);
}
function restApiPost(url, data, callback) {
- self.restApi().post('/../..' + url, data).then(callback) ;
+ self.restApi().post('/../..' + url, data).then(callback);
}
function restApiDelete(url, callback, errMessage) {
self.restApi().delete('/../..' + url).then(callback).catch((e) => {
- alert(errMessage);
+ alert(errMessage);
});
}
function getReviewers(change, callBack) {
@@ -149,8 +187,8 @@
}
});
// Give CL author a default minVoteLevel vote.
- if (changeOwner != null &&
- 'email' in changeOwner && '_account_id' in changeOwner &&
+ if (changeOwner != null && 'email' in changeOwner &&
+ '_account_id' in changeOwner &&
(!(changeOwner.email in reviewerId) ||
reviewerVote[changeOwner.email] == 0)) {
reviewerId[changeOwner.email] = changeOwner._account_id;
@@ -168,9 +206,9 @@
// Gerrit core UI shows the error dialog and does not provide
// a way for plugins to handle the error yet.
needRefresh = true;
- restApiPost('/changes/' + changeId + '/reviewers',
- {'reviewer': email},
- checkAddRemoveLists);
+ restApiPost(
+ '/changes/' + changeId + '/reviewers', {'reviewer': email},
+ checkAddRemoveLists);
return;
}
}
@@ -179,10 +217,9 @@
if (email in reviewerId) {
removeList = removeList.slice(i + 1, removeList.length);
needRefresh = true;
- restApiDelete('/changes/' + changeId +
- '/reviewers/' + reviewerId[email],
- checkAddRemoveLists,
- 'Cannot delete reviewer: ' + email);
+ restApiDelete(
+ '/changes/' + changeId + '/reviewers/' + reviewerId[email],
+ checkAddRemoveLists, 'Cannot delete reviewer: ' + email);
return;
}
}
@@ -195,7 +232,7 @@
}
function applyGetReviewers(reviewerList) {
setupReviewersMap(reviewerList);
- checkAddRemoveLists(); // update and pop up window at the end
+ checkAddRemoveLists(); // update and pop up window at the end
}
function hasStar(owners) {
return owners.some(function(owner) {
@@ -218,7 +255,7 @@
if (owners[j] in votes) {
var v = votes[owners[j]];
if (v < 0) {
- return false; // cannot have any negative vote
+ return false; // cannot have any negative vote
}
foundApproval |= v >= minVoteLevel;
}
@@ -233,12 +270,6 @@
e.innerHTML = s;
return e;
}
- function br() {
- return document.createElement('br');
- }
- function hr() {
- return document.createElement('hr');
- }
function newButton(name, action) {
var b = document.createElement('button');
b.appendChild(document.createTextNode(name));
@@ -252,16 +283,18 @@
showBoldKeyValueLines(args, key, JSON.stringify(obj, null, 2));
}
function showBoldKeyValueLines(args, key, value) {
- args.push(hr(), strElement('<b>' + key + '</b>:'), br());
+ args.push(
+ createElWithText('hr'), strElement('<b>' + key + '</b>:'),
+ createElWithText('br'));
value.split('\n').forEach(function(line) {
- args.push(strElement(line), br());
+ args.push(strElement(line), createElWithText('br'));
});
}
function showDebugMessages(result, args) {
function addKeyValue(key, value) {
args.push(strElement('<b>' + key + '</b>: ' + value + '<br>'));
}
- args.push(hr());
+ args.push(createElWithText('hr'));
addKeyValue('changeId', changeId);
addKeyValue('project', project);
addKeyValue('branch', branch);
@@ -277,12 +310,13 @@
});
}
function showFilesAndOwners(result, args) {
- var sortedOwners = result.owners.map(
- function(ownerInfo) { return ownerInfo.email; });
- var groups = {}; // a map from group_name to
- // {'type': 0..(NUM_GROUP_TYPES-1),
- // 'size': num_of_files_in_this_group,
- // 'owners': space_separated_owner_emails}
+ var sortedOwners = result.owners.map(function(ownerInfo) {
+ return ownerInfo.email;
+ });
+ var groups = {}; // a map from group_name to
+ // {'type': 0..(NUM_GROUP_TYPES-1),
+ // 'size': num_of_files_in_this_group,
+ // 'owners': space_separated_owner_emails}
var header = emptyDiv(HEADER_DIV_ID);
var groupTypeDiv = Array(NUM_GROUP_TYPES);
for (var i = 0; i < NUM_GROUP_TYPES; i++) {
@@ -295,8 +329,8 @@
var ownersDiv = emptyDiv(OWNERS_DIV_ID);
var numCheckBoxes = 0;
- var owner2boxes = {}; // owner name ==> array of checkbox id
- var owner2email = {}; // owner name ==> email address
+ var owner2boxes = {}; // owner name ==> array of checkbox id
+ var owner2email = {}; // owner name ==> email address
minVoteLevel =
('minOwnerVoteLevel' in result ? result.minOwnerVoteLevel : 1);
@@ -314,15 +348,17 @@
return e;
}
function colorSpan(str, color) {
- return `<span style="color: ${color};">${str}</span>`;
+ const el = createElWithText('span', str)
+ el.style.color = color;
+ return el;
}
function doApplyButton() {
addList = [];
removeList = [];
// add each owner's email address to addList or removeList
Object.keys(owner2boxes).forEach(function(owner) {
- (getElement(owner2boxes[owner][0]).checked ?
- addList : removeList).push(owner2email[owner]);
+ (getElement(owner2boxes[owner][0]).checked ? addList : removeList)
+ .push(owner2email[owner]);
});
getReviewers(changeId, applyGetReviewers);
}
@@ -330,20 +366,22 @@
var name = event.target.value;
var checked = event.target.checked;
var others = owner2boxes[name];
- others.forEach(function(id) { getElement(id).checked = checked; });
+ others.forEach(function(id) {
+ getElement(id).checked = checked;
+ });
getElement(APPLY_BUTTON_ID).style.display = 'inline';
}
- function addGroupsToDiv(div, keys, title) {
+ function addGroupsToDiv(div, keys, titleEl) {
if (keys.length <= 0) {
div.style.display = 'none';
return;
}
div.innerHTML = '';
div.style.display = 'inline';
- div.appendChild(strElement(title));
+ div.appendChild(titleEl);
function addOwner(itemDiv, ownerEmail) {
if (ownerEmail == '*') {
- return; // no need to list/select '*'
+ return; // no need to list/select '*'
}
numCheckBoxes++;
var name = ownerEmail.replace(/@[^ ]*/g, '');
@@ -360,17 +398,22 @@
box.id = id;
box.value = name;
box.onclick = clickBox;
- itemDiv.appendChild(strElement(' '));
+ itemDiv.appendChild(createElWithText('span', '\u00a0\u00a0'));
var nobr = document.createElement('nobr');
nobr.appendChild(box);
nobr.appendChild(strElement(name));
itemDiv.appendChild(nobr);
}
keys.forEach(function(key) {
- var owners = groups[key].owners; // string of owner emails
+ var owners = groups[key].owners; // string of owner emails
var numFiles = groups[key].size;
- var item = HTML_BULLET + ' <b>' + key + '</b>' +
- ((numFiles > 1) ? (' (' + numFiles + ' files)') : '');
+
+ var itemEl = createElWithText('span');
+ itemEl.appendChild(createBulletEl());
+ itemEl.appendChild(document.createTextNode('\u00a0'));
+ itemEl.appendChild(createElWithText('b', key));
+ itemEl.appendChild(document.createTextNode(
+ ((numFiles > 1) ? (' (' + numFiles + ' files)') : '')));
var setOfOwners = new Set(owners.split(' '));
function add2list(list, email) {
if (setOfOwners.has(email)) {
@@ -380,37 +423,44 @@
}
var reducedList = sortedOwners.reduce(add2list, []);
if (hasNamedOwner(reducedList)) {
- item += ':';
+ itemEl.appendChild(document.createTextNode(':'));
}
let itemDiv = document.createElement('div');
itemDiv.style.paddingTop = '0.5em';
- itemDiv.appendChild(strElement(item));
- itemDiv.appendChild(br());
+ itemDiv.appendChild(itemEl);
+ itemDiv.appendChild(createElWithText('br'));
reducedList.forEach(addOwner.bind(this, itemDiv));
div.appendChild(itemDiv);
});
div.lastElementChild.style.paddingBottom = '0.5em';
}
- function addOwnersDiv(div, title) {
- div.innerHTML = '';
+ function addOwnersDiv(div, titleEl) {
+ div.textContent = '';
div.style.display = 'inline';
- div.appendChild(strElement(title));
+ div.appendChild(titleEl);
+ div.appendChild(createElWithText('br'));
function compareOwnerInfo(o1, o2) {
return o1.email.localeCompare(o2.email);
}
result.owners.sort(compareOwnerInfo).forEach(function(ownerInfo) {
var email = ownerInfo.email;
- if (email != '*') { // do not list special email *
+ var emailEl = createElWithText('span', email);
+ if (email != '*') { // do not list special email *
var vote = reviewerVote[email];
if ((email in reviewerVote) && vote != 0) {
if (vote > 0) {
- email += colorSpan(' (+' + vote + ')', 'green');
+ emailEl.appendChild(
+ colorSpan('\u00a0(+' + vote + ')', 'green'));
} else {
- email += colorSpan(' (' + vote + ')', 'red');
+ emailEl.appendChild(colorSpan('\u00a0(' + vote + ')', 'red'));
}
}
- div.appendChild(strElement(' ' + email + '<br>'));
+ const spanEl = createElWithText('span');
+ spanEl.appendChild(document.createTextNode('\u00a0\u00a0'));
+ spanEl.appendChild(emailEl);
+ spanEl.appendChild(createElWithText('br'));
+ div.appendChild(spanEl);
}
});
}
@@ -425,20 +475,27 @@
// Add message to header div and make visible.
let headerMessageDiv = document.createElement('div');
- headerMessageDiv.innerHTML = isExemptedFromOwnerApproval() ? HTML_IS_EXEMPTED :
- ((onSubmit ? HTML_ONSUBMIT_HEADER : '') + HTML_SELECT_REVIEWERS);
+ if (isExemptedFromOwnerApproval()) {
+ headerMessageDiv.appendChild(createIsExemptedEl());
+ } else {
+ if (onSubmit) {
+ headerMessageDiv.appendChild(createOnSubmitHeader());
+ }
+ headerMessageDiv.appendChild(createSelectReviewersEl());
+ }
header.appendChild(headerMessageDiv);
header.style.display = 'inline';
numCheckBoxes = 0;
owner2boxes = {};
for (var i = 0; i < NUM_GROUP_TYPES; i++) {
- addGroupsToDiv(groupTypeDiv[i], listOfGroup[i], HTML_GROUP_TYPE_HEADER[i]);
+ addGroupsToDiv(
+ groupTypeDiv[i], listOfGroup[i], HTML_GROUP_TYPE_HEADER[i]);
}
- addOwnersDiv(ownersDiv, HTML_OWNERS_HEADER);
+ addOwnersDiv(ownersDiv, createOwnersHeaderEl());
}
function createGroups() {
- var owners2group = {}; // owner list to group name
+ var owners2group = {}; // owner list to group name
var firstNoOwnerFile = null;
var keysOfFile2Owners = Object.keys(result.file2owners);
keysOfFile2Owners.sort().forEach(function(name) {
@@ -461,7 +518,7 @@
} else {
type = GROUP_TYPE.NEED_APPROVAL;
}
- groups[name] = {'type':type, 'size':1, 'owners':owners};
+ groups[name] = {'type': type, 'size': 1, 'owners': owners};
}
});
var numNoOwnerFiles = result.files.length - keysOfFile2Owners.length;
@@ -492,15 +549,19 @@
function showFindOwnersResults(result) {
function prepareElements() {
var elems = [];
- var text = Object.keys(result.file2owners).length <= 0 ? HTML_NO_OWNER : null;
- useContextPopup = !!context && !!text && !!context.popup;
- if (!!text) {
+ var textEl =
+ Object.keys(result.file2owners).length <= 0 ? createNoOwnerEl() : null;
+ useContextPopup = !!context && !!textEl && !!context.popup;
+ if (!!textEl) {
if (useContextPopup) {
- elems.push(hr(), strElement(text), hr());
- var onClick = function() { context.hide(); };
- elems.push(context.button('OK', {onclick: onClick}), hr());
+ elems.push(createElWithText('hr'), textEl, createElWithText('hr'));
+ var onClick = function() {
+ context.hide();
+ };
+ elems.push(
+ context.button('OK', {onclick: onClick}), createElWithText('hr'));
} else {
- elems.push(strElement(text), newButton('OK', hideFindOwnersPage));
+ elems.push(textEl, newButton('OK', hideFindOwnersPage));
}
} else {
showFilesAndOwners(result, elems);
@@ -519,7 +580,9 @@
while (pageDiv.firstChild) {
pageDiv.removeChild(pageDiv.firstChild);
}
- elems.forEach(function(e) { pageDiv.appendChild(e); });
+ elems.forEach(function(e) {
+ pageDiv.appendChild(e);
+ });
pageDiv.className = LARGE_PAGE_STYLE;
// Calculate required height, limited to 85% of window height,
// and required width, limited to 75% of window width.
@@ -534,12 +597,14 @@
pageDiv.style.width = maxWidth + 'px';
rect = pageDiv.getBoundingClientRect();
}
- pageDiv.style.left = Math.round((window.innerWidth - rect.width) / 2) + 'px';
+ pageDiv.style.left =
+ Math.round((window.innerWidth - rect.width) / 2) + 'px';
if (rect.height > maxHeight) {
pageDiv.style.height = maxHeight + 'px';
rect = pageDiv.getBoundingClientRect();
}
- pageDiv.style.top = Math.round((window.innerHeight - rect.height) / 2) + 'px';
+ pageDiv.style.top =
+ Math.round((window.innerHeight - rect.height) / 2) + 'px';
pageDiv.style.visibility = 'visible';
}
}
@@ -560,7 +625,7 @@
popupFindOwnersPage(null, change, revision, true);
return false;
}
- return true; // Okay to submit.
+ return true; // Okay to submit.
}
var actionKey = null;
function onShowChangePolyGerrit(change, revision) {
@@ -574,20 +639,19 @@
actionKey = changeActions.add('revision', '[FIND OWNERS]');
changeActions.setIcon(actionKey, 'robot');
changeActions.setTitle(actionKey, 'Find owners of changed files');
- changeActions.addTapListener(actionKey,
- (e) => {
- if (e) e.stopPropagation();
+ changeActions.addTapListener(actionKey, (e) => {
+ if (e) e.stopPropagation();
- popupFindOwnersPage(null, change, revision, false);
- });
+ popupFindOwnersPage(null, change, revision, false);
+ });
}
function onClick(event) {
if (pageDiv.style.visibility != 'hidden' && !useContextPopup) {
var x = event.clientX;
var y = event.clientY;
var rect = pageDiv.getBoundingClientRect();
- if (x < rect.left || x >= rect.left + rect.width ||
- y < rect.top || y >= rect.top + rect.height) {
+ if (x < rect.left || x >= rect.left + rect.width || y < rect.top ||
+ y >= rect.top + rect.height) {
hideFindOwnersPage();
}
}