Merge "Merge branch 'stable-3.5'"
diff --git a/java/com/google/gerrit/extensions/webui/PatchSetWebLink.java b/java/com/google/gerrit/extensions/webui/PatchSetWebLink.java
index 0e8e28e..74bccbd 100644
--- a/java/com/google/gerrit/extensions/webui/PatchSetWebLink.java
+++ b/java/com/google/gerrit/extensions/webui/PatchSetWebLink.java
@@ -37,6 +37,34 @@
* @return WebLinkInfo that links to patch set in external service, null if there should be no
* link.
*/
+ @Deprecated
WebLinkInfo getPatchSetWebLink(
String projectName, String commit, String commitMessage, String branchName);
+
+ /**
+ * {@link com.google.gerrit.extensions.common.WebLinkInfo} describing a link from a patch set to
+ * an external service.
+ *
+ * <p>In order for the web link to be visible {@link
+ * com.google.gerrit.extensions.common.WebLinkInfo#url} and {@link
+ * com.google.gerrit.extensions.common.WebLinkInfo#name} must be set.
+ *
+ * <p>
+ *
+ * @param projectName name of the project
+ * @param commit commit of the patch set
+ * @param commitMessage the commit message of the change
+ * @param branchName target branch of the change
+ * @param changeKey the changeID for this change
+ * @return WebLinkInfo that links to patch set in external service, null if there should be no
+ * link.
+ */
+ default WebLinkInfo getPatchSetWebLink(
+ String projectName,
+ String commit,
+ String commitMessage,
+ String branchName,
+ String changeKey) {
+ return getPatchSetWebLink(projectName, commit, commitMessage, branchName);
+ }
}
diff --git a/java/com/google/gerrit/server/WebLinks.java b/java/com/google/gerrit/server/WebLinks.java
index c92b194..58396f5 100644
--- a/java/com/google/gerrit/server/WebLinks.java
+++ b/java/com/google/gerrit/server/WebLinks.java
@@ -86,12 +86,19 @@
* @param commit SHA1 of commit.
* @param commitMessage the commit message of the commit.
* @param branchName branch of the commit.
+ * @param changeKey change Identifier for this change
*/
public ImmutableList<WebLinkInfo> getPatchSetLinks(
- Project.NameKey project, String commit, String commitMessage, String branchName) {
+ Project.NameKey project,
+ String commit,
+ String commitMessage,
+ String branchName,
+ String changeKey) {
return filterLinks(
patchSetLinks,
- webLink -> webLink.getPatchSetWebLink(project.get(), commit, commitMessage, branchName));
+ webLink ->
+ webLink.getPatchSetWebLink(
+ project.get(), commit, commitMessage, branchName, changeKey));
}
/**
diff --git a/java/com/google/gerrit/server/change/RevisionJson.java b/java/com/google/gerrit/server/change/RevisionJson.java
index 0321fcb..7d40f06 100644
--- a/java/com/google/gerrit/server/change/RevisionJson.java
+++ b/java/com/google/gerrit/server/change/RevisionJson.java
@@ -168,7 +168,8 @@
RevCommit commit,
boolean addLinks,
boolean fillCommit,
- String branchName)
+ String branchName,
+ String changeKey)
throws IOException {
CommitInfo info = new CommitInfo();
if (fillCommit) {
@@ -182,7 +183,8 @@
if (addLinks) {
ImmutableList<WebLinkInfo> patchSetLinks =
- webLinks.getPatchSetLinks(project, commit.name(), commit.getFullMessage(), branchName);
+ webLinks.getPatchSetLinks(
+ project, commit.name(), commit.getFullMessage(), branchName, changeKey);
info.webLinks = patchSetLinks.isEmpty() ? null : patchSetLinks;
ImmutableList<WebLinkInfo> resolveConflictsLinks =
webLinks.getResolveConflictsLinks(
@@ -301,7 +303,9 @@
rw.parseBody(commit);
String branchName = cd.change().getDest().branch();
if (setCommit) {
- out.commit = getCommitInfo(project, rw, commit, has(WEB_LINKS), fillCommit, branchName);
+ out.commit =
+ getCommitInfo(
+ project, rw, commit, has(WEB_LINKS), fillCommit, branchName, c.getKey().get());
}
if (addFooters) {
Ref ref = repo.exactRef(branchName);
diff --git a/java/com/google/gerrit/server/restapi/change/GetCommit.java b/java/com/google/gerrit/server/restapi/change/GetCommit.java
index d76ce04..5193501 100644
--- a/java/com/google/gerrit/server/restapi/change/GetCommit.java
+++ b/java/com/google/gerrit/server/restapi/change/GetCommit.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.restapi.change;
+import static java.util.concurrent.TimeUnit.DAYS;
+
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.common.CommitInfo;
@@ -25,7 +27,6 @@
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
import java.io.IOException;
-import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
@@ -64,10 +65,11 @@
commit,
addLinks,
/* fillCommit= */ true,
- rsrc.getChange().getDest().branch());
+ rsrc.getChange().getDest().branch(),
+ rsrc.getChange().getKey().get());
Response<CommitInfo> r = Response.ok(info);
if (rsrc.isCacheable()) {
- r.caching(CacheControl.PRIVATE(7, TimeUnit.DAYS));
+ r.caching(CacheControl.PRIVATE(7, DAYS));
}
return r;
}
diff --git a/java/com/google/gerrit/server/restapi/change/GetMergeList.java b/java/com/google/gerrit/server/restapi/change/GetMergeList.java
index f0639b5..551b50f 100644
--- a/java/com/google/gerrit/server/restapi/change/GetMergeList.java
+++ b/java/com/google/gerrit/server/restapi/change/GetMergeList.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.restapi.change;
+import static java.util.concurrent.TimeUnit.DAYS;
+
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.entities.Project;
@@ -30,7 +32,6 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
@@ -77,7 +78,7 @@
return createResponse(rsrc, ImmutableList.of());
}
- List<RevCommit> commits = MergeListBuilder.build(rw, commit, uninterestingParent);
+ ImmutableList<RevCommit> commits = MergeListBuilder.build(rw, commit, uninterestingParent);
List<CommitInfo> result = new ArrayList<>(commits.size());
RevisionJson changeJson = json.create(ImmutableSet.of());
for (RevCommit c : commits) {
@@ -88,7 +89,8 @@
c,
addLinks,
/* fillCommit= */ true,
- rsrc.getChange().getDest().branch()));
+ rsrc.getChange().getDest().branch(),
+ rsrc.getChange().getKey().get()));
}
return createResponse(rsrc, result);
}
@@ -98,7 +100,7 @@
RevisionResource rsrc, List<CommitInfo> result) {
Response<List<CommitInfo>> r = Response.ok(result);
if (rsrc.isCacheable()) {
- r.caching(CacheControl.PRIVATE(7, TimeUnit.DAYS));
+ r.caching(CacheControl.PRIVATE(7, DAYS));
}
return r;
}
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
index 2dc401f..e8448b7 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
@@ -2555,8 +2555,9 @@
_resetReplyOverlayFocusStops() {
const dialog = query<GrReplyDialog>(this, '#replyDialog');
- if (!dialog) return;
- this.$.replyOverlay.setFocusStops(dialog.getFocusStops());
+ const focusStops = dialog?.getFocusStops();
+ if (!focusStops) return;
+ this.$.replyOverlay.setFocusStops(focusStops);
}
_handleToggleStar(e: CustomEvent<ChangeStarToggleStarDetail>) {
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.ts
index d7b4ef3..2972d24 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.ts
@@ -101,12 +101,12 @@
test('submit blocked when invalid email is supplied to ccs', () => {
const sendStub = sinon.stub(element, 'send').returns(Promise.resolve());
- element.$.ccs.$.entry.setText('test');
+ element.$.ccs.entry!.setText('test');
MockInteractions.tap(queryAndAssert(element, 'gr-button.send'));
assert.isFalse(sendStub.called);
flush();
- element.$.ccs.$.entry.setText('test@test.test');
+ element.$.ccs.entry!.setText('test@test.test');
MockInteractions.tap(queryAndAssert(element, 'gr-button.send'));
assert.isTrue(sendStub.called);
});
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
index b91538b..c7b1982 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
@@ -116,6 +116,7 @@
import {resolve, DIPolymerElement} from '../../../models/dependency';
import {changeModelToken} from '../../../models/change/change-model';
import {LabelNameToValuesMap} from '../../../api/rest-api';
+import {ValueChangedEvent} from '../../../types/events';
const STORAGE_DEBOUNCE_INTERVAL_MS = 400;
@@ -477,6 +478,7 @@
getFocusStops() {
const end = this._sendDisabled ? this.$.cancelButton : this.$.sendButton;
+ if (!this.$.reviewers.focusStart) return undefined;
return {
start: this.$.reviewers.focusStart,
end,
@@ -666,10 +668,10 @@
setTimeout(() => textarea.getNativeTextarea().focus());
} else if (section === FocusTarget.REVIEWERS) {
const reviewerEntry = this.$.reviewers.focusStart;
- setTimeout(() => reviewerEntry.focus());
+ setTimeout(() => reviewerEntry?.focus());
} else if (section === FocusTarget.CCS) {
const ccEntry = this.$.ccs.focusStart;
- setTimeout(() => ccEntry.focus());
+ setTimeout(() => ccEntry?.focus());
}
}
@@ -1265,6 +1267,33 @@
Object.keys(this.getLabelScores().getLabelValues(false)).length !== 0;
}
+ // To decouple account-list and reply dialog
+ _getAccountListCopy(list: (AccountInfo | GroupInfo)[]) {
+ return list.slice();
+ }
+
+ _handleReviewersChanged(e: ValueChangedEvent<(AccountInfo | GroupInfo)[]>) {
+ this._reviewers = e.detail.value.slice();
+ this._reviewersMutated = true;
+ }
+
+ _handleCcsChanged(e: ValueChangedEvent<(AccountInfo | GroupInfo)[]>) {
+ this._ccs = e.detail.value.slice();
+ this._reviewersMutated = true;
+ }
+
+ _handleReviewersConfirmationChanged(
+ e: ValueChangedEvent<SuggestedReviewerGroupInfo | null>
+ ) {
+ this._reviewerPendingConfirmation = e.detail.value;
+ }
+
+ _handleCcsConfirmationChanged(
+ e: ValueChangedEvent<SuggestedReviewerGroupInfo | null>
+ ) {
+ this._ccPendingConfirmation = e.detail.value;
+ }
+
_isState(knownLatestState?: LatestPatchState, value?: LatestPatchState) {
return knownLatestState === value;
}
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_html.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_html.ts
index c1d7cb1..c4b3578 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_html.ts
@@ -266,11 +266,13 @@
<div class="peopleListLabel">Reviewers</div>
<gr-account-list
id="reviewers"
- accounts="{{_reviewers}}"
+ accounts="[[_getAccountListCopy(_reviewers)]]"
on-account-added="accountAdded"
+ on-accounts-changed="_handleReviewersChanged"
removable-values="[[change.removable_reviewers]]"
filter="[[filterReviewerSuggestion]]"
- pending-confirmation="{{_reviewerPendingConfirmation}}"
+ pending-confirmation="[[_reviewerPendingConfirmation]]"
+ on-pending-confirmation-changed="_handleReviewersConfirmationChanged"
placeholder="Add reviewer..."
on-account-text-changed="_handleAccountTextEntry"
suggestions-provider="[[_getReviewerSuggestionsProvider(change)]]"
@@ -284,10 +286,12 @@
<div class="peopleListLabel">CC</div>
<gr-account-list
id="ccs"
- accounts="{{_ccs}}"
+ accounts="[[_getAccountListCopy(_ccs)]]"
on-account-added="accountAdded"
+ on-accounts-changed="_handleCcsChanged"
filter="[[filterCCSuggestion]]"
- pending-confirmation="{{_ccPendingConfirmation}}"
+ pending-confirmation="[[_ccPendingConfirmation]]"
+ pending-confirmation-changed="_handleCcsConfirmationChanged"
allow-any-input=""
placeholder="Add CC..."
on-account-text-changed="_handleAccountTextEntry"
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts
index e324cf2d..883fbfa 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts
@@ -1184,7 +1184,7 @@
// We should be focused on account entry input.
assert.isTrue(
isFocusInsideElement(
- queryAndAssert<GrAccountList>(element, '#reviewers').$.entry.$.input.$
+ queryAndAssert<GrAccountList>(element, '#reviewers').entry!.$.input.$
.input
)
);
@@ -1245,13 +1245,13 @@
if (cc) {
assert.isTrue(
isFocusInsideElement(
- queryAndAssert<GrAccountList>(element, '#ccs').$.entry.$.input.$.input
+ queryAndAssert<GrAccountList>(element, '#ccs').entry!.$.input.$.input
)
);
} else {
assert.isTrue(
isFocusInsideElement(
- queryAndAssert<GrAccountList>(element, '#reviewers').$.entry.$.input.$
+ queryAndAssert<GrAccountList>(element, '#reviewers').entry!.$.input.$
.input
)
);
@@ -1683,13 +1683,14 @@
const cc1 = makeAccount();
const cc2 = makeAccount();
const cc3 = makeAccount();
- element._reviewers = [reviewer1, reviewer2];
- element._ccs = [cc1, cc2, cc3];
-
element.change!.reviewers = {
[ReviewerState.CC]: [],
[ReviewerState.REVIEWER]: [{_account_id: 33 as AccountId}],
};
+ await flush();
+
+ element._reviewers = [reviewer1, reviewer2];
+ element._ccs = [cc1, cc2, cc3];
const mutations: ReviewerInput[] = [];
@@ -1697,6 +1698,8 @@
mutations.push(...review.reviewers!);
});
+ assert.isFalse(element._reviewersMutated);
+
// Remove and add to other field.
reviewers.dispatchEvent(
new CustomEvent('remove', {
@@ -1705,7 +1708,10 @@
bubbles: true,
})
);
- ccs.$.entry.dispatchEvent(
+
+ await flush();
+ assert.isTrue(element._reviewersMutated);
+ ccs.entry!.dispatchEvent(
new CustomEvent('add', {
detail: {value: {account: reviewer1}},
composed: true,
@@ -1726,7 +1732,7 @@
bubbles: true,
})
);
- reviewers.$.entry.dispatchEvent(
+ reviewers.entry!.dispatchEvent(
new CustomEvent('add', {
detail: {value: {account: cc1}},
composed: true,
@@ -1736,14 +1742,14 @@
// Add to other field without removing from former field.
// (Currently not possible in UI, but this is a good consistency check).
- reviewers.$.entry.dispatchEvent(
+ reviewers.entry!.dispatchEvent(
new CustomEvent('add', {
detail: {value: {account: cc2}},
composed: true,
bubbles: true,
})
);
- ccs.$.entry.dispatchEvent(
+ ccs.entry!.dispatchEvent(
new CustomEvent('add', {
detail: {value: {account: reviewer2}},
composed: true,
@@ -1766,6 +1772,7 @@
// Send and purge and verify moves, delete cc3.
await element.send(false, false);
+ await flush();
expect(mutations).to.have.lengthOf(5);
expect(mutations[0]).to.deep.equal(
mapReviewer(cc1, ReviewerState.REVIEWER)
@@ -1814,7 +1821,7 @@
bubbles: true,
})
);
- ccs.$.entry.dispatchEvent(
+ ccs.entry!.dispatchEvent(
new CustomEvent('add', {
detail: {value: {account: reviewer1}},
composed: true,
@@ -2216,7 +2223,7 @@
await flush();
assert.equal(
- element.getFocusStops().end,
+ element.getFocusStops()!.end,
queryAndAssert(element, '#cancelButton')
);
element.draftCommentThreads = [
@@ -2234,7 +2241,7 @@
await flush();
assert.equal(
- element.getFocusStops().end,
+ element.getFocusStops()!.end,
queryAndAssert(element, '#sendButton')
);
});
diff --git a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.ts b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.ts
index 1553eef..cf24bcd 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.ts
@@ -16,11 +16,7 @@
*/
import '../gr-account-chip/gr-account-chip';
import '../gr-account-entry/gr-account-entry';
-import '../../../styles/shared-styles';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-account-list_html';
import {getAppContext} from '../../../services/app-context';
-import {customElement, property} from '@polymer/decorators';
import {
ChangeInfo,
Suggestion,
@@ -30,20 +26,29 @@
SuggestedReviewerGroupInfo,
SuggestedReviewerAccountInfo,
} from '../../../types/common';
-import {
- ReviewerSuggestionsProvider,
- SuggestionItem,
-} from '../../../scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider';
+import {ReviewerSuggestionsProvider} from '../../../scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider';
import {GrAccountEntry} from '../gr-account-entry/gr-account-entry';
import {GrAccountChip} from '../gr-account-chip/gr-account-chip';
-import {PolymerDeepPropertyChange} from '@polymer/polymer/interfaces';
import {PaperInputElementExt} from '../../../types/types';
-import {fireAlert, fire} from '../../../utils/event-util';
+import {fire, fireAlert} from '../../../utils/event-util';
import {accountOrGroupKey} from '../../../utils/account-util';
+import {LitElement, css, html, PropertyValues} from 'lit';
+import {customElement, property, query, state} from 'lit/decorators';
+import {sharedStyles} from '../../../styles/shared-styles';
+import {classMap} from 'lit/directives/class-map';
+import {
+ AutocompleteQuery,
+ AutocompleteSuggestion,
+} from '../gr-autocomplete/gr-autocomplete';
+import {ValueChangedEvent} from '../../../types/events';
const VALID_EMAIL_ALERT = 'Please input a valid email.';
declare global {
+ interface HTMLElementEventMap {
+ 'accounts-changed': ValueChangedEvent<(AccountInfo | GroupInfo)[]>;
+ 'pending-confirmation-changed': ValueChangedEvent<SuggestedReviewerGroupInfo | null>;
+ }
interface HTMLElementTagNameMap {
'gr-account-list': GrAccountList;
}
@@ -51,13 +56,6 @@
'account-added': CustomEvent<AccountInputDetail>;
}
}
-
-export interface GrAccountList {
- $: {
- entry: GrAccountEntry;
- };
-}
-
export interface AccountInputDetail {
account: AccountInput;
}
@@ -115,18 +113,15 @@
}
@customElement('gr-account-list')
-export class GrAccountList extends PolymerElement {
- static get template() {
- return htmlTemplate;
- }
-
+export class GrAccountList extends LitElement {
/**
* Fired when user inputs an invalid email address.
*
* @event show-alert
*/
+ @query('#entry') entry?: GrAccountEntry;
- @property({type: Array, notify: true})
+ @property({type: Array})
accounts: AccountInput[] = [];
@property({type: Object})
@@ -135,7 +130,7 @@
@property({type: Object})
filter?: (input: Suggestion) => boolean;
- @property({type: String})
+ @property()
placeholder = '';
@property({type: Boolean})
@@ -150,7 +145,7 @@
/**
* Needed for template checking since value is initially set to null.
*/
- @property({type: Object, notify: true})
+ @property({type: Object})
pendingConfirmation: SuggestedReviewerGroupInfo | null = null;
@property({type: Boolean})
@@ -159,7 +154,7 @@
/**
* When true, allows for non-suggested inputs to be added.
*/
- @property({type: Boolean})
+ @property({type: Boolean, attribute: 'allow-any-input'})
allowAnyInput = false;
/**
@@ -175,8 +170,7 @@
/**
* Returns suggestion items
*/
- @property({type: Object})
- _querySuggestions: (input: string) => Promise<SuggestionItem[]>;
+ @state() private querySuggestions: AutocompleteQuery;
private readonly reporting = getAppContext().reportingService;
@@ -184,21 +178,92 @@
constructor() {
super();
- this._querySuggestions = input => this._getSuggestions(input);
+ this.querySuggestions = input => this.getSuggestions(input);
this.addEventListener('remove', e =>
- this._handleRemove(e as CustomEvent<{account: AccountInput}>)
+ this.handleRemove(e as CustomEvent<{account: AccountInput}>)
);
}
- get accountChips() {
- return Array.from(this.root?.querySelectorAll('gr-account-chip') || []);
+ static override styles = [
+ sharedStyles,
+ css`
+ gr-account-chip {
+ display: inline-block;
+ margin: var(--spacing-xs) var(--spacing-xs) var(--spacing-xs) 0;
+ }
+ gr-account-entry {
+ display: flex;
+ flex: 1;
+ min-width: 10em;
+ margin: var(--spacing-xs) var(--spacing-xs) var(--spacing-xs) 0;
+ }
+ .group {
+ --account-label-suffix: ' (group)';
+ }
+ .pending-add {
+ font-style: italic;
+ }
+ .list {
+ align-items: center;
+ display: flex;
+ flex-wrap: wrap;
+ }
+ `,
+ ];
+
+ override render() {
+ return html`<div class="list">
+ ${this.accounts.map(
+ account => html`
+ <gr-account-chip
+ .account=${account}
+ class=${classMap({
+ group: !!account._group,
+ pendingAdd: !!account._pendingAdd,
+ })}
+ ?removable=${this.computeRemovable(account)}
+ @keydown=${this.handleChipKeydown}
+ tabindex="-1"
+ >
+ </gr-account-chip>
+ `
+ )}
+ </div>
+ <gr-account-entry
+ borderless=""
+ ?hidden=${(this.maxCount && this.maxCount <= this.accounts.length) ||
+ this.readonly}
+ id="entry"
+ .placeholder=${this.placeholder}
+ @add=${this.handleAdd}
+ @keydown=${this.handleInputKeydown}
+ .allowAnyInput=${this.allowAnyInput}
+ .querySuggestions=${this.querySuggestions}
+ >
+ </gr-account-entry>
+ <slot></slot>`;
+ }
+
+ override willUpdate(changedProperties: PropertyValues) {
+ if (changedProperties.has('pendingConfirmation')) {
+ fire(this, 'pending-confirmation-changed', {
+ value: this.pendingConfirmation,
+ });
+ }
+ }
+
+ get accountChips(): GrAccountChip[] {
+ return Array.from(
+ this.shadowRoot?.querySelectorAll('gr-account-chip') || []
+ );
}
get focusStart() {
- return this.$.entry.focusStart;
+ // Entry is always defined and we cannot return undefined.
+ return this.entry?.focusStart;
}
- _getSuggestions(input: string) {
+ getSuggestions(input: string): Promise<AutocompleteSuggestion[]> {
const provider = this.suggestionsProvider;
if (!provider) return Promise.resolve([]);
return provider.getSuggestions(input).then(suggestions => {
@@ -212,8 +277,11 @@
});
}
- _handleAdd(e: CustomEvent<{value: RawAccountInput}>) {
- this.addAccountItem(e.detail.value);
+ // private but used in test
+ handleAdd(e: ValueChangedEvent<string>) {
+ // TODO(TS) this is temporary hack to avoid cascade of ts issues
+ const item = e.detail.value as RawAccountInput;
+ this.addAccountItem(item);
}
addAccountItem(item: RawAccountInput) {
@@ -226,7 +294,7 @@
if (isAccountObject(item)) {
account = {...item.account, _pendingAdd: true};
this.removeFromPendingRemoval(account);
- this.push('accounts', account);
+ this.accounts.push(account);
itemTypeAdded = 'account';
} else if (isSuggestedReviewerGroupInfo(item)) {
if (item.confirm) {
@@ -234,41 +302,45 @@
return;
}
group = {...item.group, _pendingAdd: true, _group: true};
- this.push('accounts', group);
+ this.accounts.push(group);
this.removeFromPendingRemoval(group);
itemTypeAdded = 'group';
} else if (this.allowAnyInput) {
if (!item.includes('@')) {
// Repopulate the input with what the user tried to enter and have
// a toast tell them why they can't enter it.
- this.$.entry.setText(item);
+ this.entry?.setText(item);
fireAlert(this, VALID_EMAIL_ALERT);
return false;
} else {
account = {email: item as EmailAddress, _pendingAdd: true};
- this.push('accounts', account);
+ this.accounts.push(account);
this.removeFromPendingRemoval(account);
itemTypeAdded = 'email';
}
}
-
+ fire(this, 'accounts-changed', {value: this.accounts.slice()});
fire(this, 'account-added', {account: (account ?? group)! as AccountInput});
this.reporting.reportInteraction(`Add to ${this.id}`, {itemTypeAdded});
this.pendingConfirmation = null;
+ this.requestUpdate();
return true;
}
confirmGroup(group: GroupInfo) {
- this.push('accounts', {
+ this.accounts.push({
...group,
confirmed: true,
_pendingAdd: true,
_group: true,
});
this.pendingConfirmation = null;
+ fire(this, 'accounts-changed', {value: this.accounts});
+ this.requestUpdate();
}
- _computeChipClass(account: AccountInput) {
+ // private but used in test
+ computeChipClass(account: AccountInput) {
const classes = [];
if (account._group) {
classes.push('group');
@@ -279,8 +351,9 @@
return classes.join(' ');
}
- _computeRemovable(account: AccountInput, readonly: boolean) {
- if (readonly) {
+ // private but used in test
+ computeRemovable(account: AccountInput) {
+ if (this.readonly) {
return false;
}
if (this.removableValues) {
@@ -297,21 +370,23 @@
return true;
}
- _handleRemove(e: CustomEvent<{account: AccountInput}>) {
+ private handleRemove(e: CustomEvent<{account: AccountInput}>) {
const toRemove = e.detail.account;
this.removeAccount(toRemove);
- this.$.entry.focus();
+ this.entry?.focus();
}
removeAccount(toRemove?: AccountInput) {
- if (!toRemove || !this._computeRemovable(toRemove, this.readonly)) {
+ if (!toRemove || !this.computeRemovable(toRemove)) {
return;
}
for (let i = 0; i < this.accounts.length; i++) {
if (accountOrGroupKey(toRemove) === accountOrGroupKey(this.accounts[i])) {
- this.splice('accounts', i, 1);
+ this.accounts.splice(i, 1);
this.pendingRemoval.add(toRemove);
this.reporting.reportInteraction(`Remove from ${this.id}`);
+ this.requestUpdate();
+ fire(this, 'accounts-changed', {value: this.accounts.slice()});
return;
}
}
@@ -320,23 +395,23 @@
);
}
- _getNativeInput(paperInput: PaperInputElementExt) {
+ // private but used in test
+ getOwnNativeInput(paperInput: PaperInputElementExt) {
// In Polymer 2 inputElement isn't nativeInput anymore
return (paperInput.$.nativeInput ||
paperInput.inputElement) as HTMLTextAreaElement;
}
- _handleInputKeydown(
- e: CustomEvent<{input: PaperInputElementExt; keyCode: number}>
- ) {
- const input = this._getNativeInput(e.detail.input);
+ private handleInputKeydown(e: KeyboardEvent) {
+ const target = e.target as GrAccountEntry;
+ const input = this.getOwnNativeInput(target.$.input.$.input);
if (
input.selectionStart !== input.selectionEnd ||
input.selectionStart !== 0
) {
return;
}
- switch (e.detail.keyCode) {
+ switch (e.keyCode) {
case 8: // Backspace
this.removeAccount(this.accounts[this.accounts.length - 1]);
break;
@@ -348,7 +423,7 @@
}
}
- _handleChipKeydown(e: KeyboardEvent) {
+ private handleChipKeydown(e: KeyboardEvent) {
const chip = e.target as GrAccountChip;
const chips = this.accountChips;
const index = chips.indexOf(chip);
@@ -366,7 +441,7 @@
} else if (index > 0) {
chips[index - 1].focus();
} else {
- this.$.entry.focus();
+ this.entry?.focus();
}
break;
case 37: // Left arrow
@@ -380,7 +455,7 @@
if (index < chips.length - 1) {
chips[index + 1].focus();
} else {
- this.$.entry.focus();
+ this.entry?.focus();
}
break;
}
@@ -395,13 +470,13 @@
* return true.
*/
submitEntryText() {
- const text = this.$.entry.getText();
- if (!text.length) {
+ const text = this.entry?.getText();
+ if (!text?.length) {
return true;
}
const wasSubmitted = this.addAccountItem(text);
if (wasSubmitted) {
- this.$.entry.clear();
+ this.entry?.clear();
}
return wasSubmitted;
}
@@ -432,19 +507,11 @@
});
}
- removeFromPendingRemoval(account: AccountInput) {
+ private removeFromPendingRemoval(account: AccountInput) {
this.pendingRemoval.delete(account);
}
clearPendingRemovals() {
this.pendingRemoval.clear();
}
-
- _computeEntryHidden(
- maxCount: number,
- accountsRecord: PolymerDeepPropertyChange<AccountInput[], AccountInput[]>,
- readonly: boolean
- ) {
- return (maxCount && maxCount <= accountsRecord.base.length) || readonly;
- }
}
diff --git a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_html.ts b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_html.ts
deleted file mode 100644
index 7a47e29..0000000
--- a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_html.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {html} from '@polymer/polymer/lib/utils/html-tag';
-
-export const htmlTemplate = html`
- <style include="shared-styles">
- gr-account-chip {
- display: inline-block;
- margin: var(--spacing-xs) var(--spacing-xs) var(--spacing-xs) 0;
- }
- gr-account-entry {
- display: flex;
- flex: 1;
- min-width: 10em;
- margin: var(--spacing-xs) var(--spacing-xs) var(--spacing-xs) 0;
- }
- .group {
- --account-label-suffix: ' (group)';
- }
- .pending-add {
- font-style: italic;
- }
- .list {
- align-items: center;
- display: flex;
- flex-wrap: wrap;
- }
- </style>
- <!--
- NOTE(Issue 6419): Nest the inner dom-repeat template in a div rather than
- as a direct child of the dom-module's template.
- -->
- <div class="list">
- <template id="chips" is="dom-repeat" items="[[accounts]]" as="account">
- <gr-account-chip
- account="[[account]]"
- class$="[[_computeChipClass(account)]]"
- data-account-id$="[[account._account_id]]"
- removable="[[_computeRemovable(account, readonly)]]"
- on-keydown="_handleChipKeydown"
- tabindex="-1"
- >
- </gr-account-chip>
- </template>
- </div>
- <gr-account-entry
- borderless=""
- hidden$="[[_computeEntryHidden(maxCount, accounts.*, readonly)]]"
- id="entry"
- placeholder="[[placeholder]]"
- on-add="_handleAdd"
- on-input-keydown="_handleInputKeydown"
- allow-any-input="[[allowAnyInput]]"
- query-suggestions="[[_querySuggestions]]"
- >
- </gr-account-entry>
- <slot></slot>
-`;
diff --git a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.ts b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.ts
index d77dec3..7509023 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.ts
@@ -28,7 +28,6 @@
GroupBaseInfo,
GroupId,
GroupName,
- SuggestedReviewerAccountInfo,
Suggestion,
} from '../../../types/common';
import {queryAll} from '../../../test/test-utils';
@@ -52,7 +51,7 @@
_account_id: 1 as AccountId,
} as AccountInfo,
count: 1,
- } as SuggestedReviewerAccountInfo,
+ } as unknown as string,
};
}
}
@@ -85,12 +84,14 @@
}
function handleAdd(value: RawAccountInput) {
- element._handleAdd(
- new CustomEvent<{value: RawAccountInput}>('add', {detail: {value}})
+ element.handleAdd(
+ new CustomEvent<{value: string}>('add', {
+ detail: {value: value as unknown as string},
+ })
);
}
- setup(() => {
+ setup(async () => {
existingAccount1 = makeAccount();
existingAccount2 = makeAccount();
@@ -98,18 +99,33 @@
element.accounts = [existingAccount1, existingAccount2];
suggestionsProvider = new MockSuggestionsProvider();
element.suggestionsProvider = suggestionsProvider;
+ await element.updateComplete;
});
- test('account entry only appears when editable', () => {
+ test('renders', () => {
+ expect(element).shadowDom.to.equal(
+ /* HTML */
+ `<div class="list">
+ <gr-account-chip removable="" tabindex="-1"> </gr-account-chip>
+ <gr-account-chip removable="" tabindex="-1"> </gr-account-chip>
+ </div>
+ <gr-account-entry borderless="" id="entry"></gr-account-entry>
+ <slot></slot>`
+ );
+ });
+
+ test('account entry only appears when editable', async () => {
element.readonly = false;
- assert.isFalse(element.$.entry.hasAttribute('hidden'));
+ await element.updateComplete;
+ assert.isFalse(element.entry!.hasAttribute('hidden'));
element.readonly = true;
- assert.isTrue(element.$.entry.hasAttribute('hidden'));
+ await element.updateComplete;
+ assert.isTrue(element.entry!.hasAttribute('hidden'));
});
- test('addition and removal of account/group chips', () => {
- flush();
- sinon.stub(element, '_computeRemovable').returns(true);
+ test('addition and removal of account/group chips', async () => {
+ await element.updateComplete;
+ sinon.stub(element, 'computeRemovable').returns(true);
// Existing accounts are listed.
let chips = getChips();
assert.equal(chips.length, 2);
@@ -119,7 +135,7 @@
// New accounts are added to end with pendingAdd class.
const newAccount = makeAccount();
handleAdd({account: newAccount, count: 1});
- flush();
+ await element.updateComplete;
chips = getChips();
assert.equal(chips.length, 3);
assert.isFalse(chips[0].classList.contains('pendingAdd'));
@@ -134,7 +150,7 @@
bubbles: true,
})
);
- flush();
+ await element.updateComplete;
chips = getChips();
assert.equal(chips.length, 2);
assert.isFalse(chips[0].classList.contains('pendingAdd'));
@@ -155,7 +171,7 @@
bubbles: true,
})
);
- flush();
+ await element.updateComplete;
chips = getChips();
assert.equal(chips.length, 1);
assert.isFalse(chips[0].classList.contains('pendingAdd'));
@@ -163,7 +179,7 @@
// New groups are added to end with pendingAdd and group classes.
const newGroup = makeGroup();
handleAdd({group: newGroup, confirm: false, count: 1});
- flush();
+ await element.updateComplete;
chips = getChips();
assert.equal(chips.length, 2);
assert.isTrue(chips[1].classList.contains('group'));
@@ -177,13 +193,13 @@
bubbles: true,
})
);
- flush();
+ await element.updateComplete;
chips = getChips();
assert.equal(chips.length, 1);
assert.isFalse(chips[0].classList.contains('pendingAdd'));
});
- test('_getSuggestions uses filter correctly', () => {
+ test('getSuggestions uses filter correctly', () => {
const originalSuggestions: Suggestion[] = [
{
email: 'abc@example.com' as EmailAddress,
@@ -212,12 +228,12 @@
value: {
account: suggestion as AccountInfo,
count: 1,
- },
+ } as unknown as string,
};
});
return element
- ._getSuggestions('')
+ .getSuggestions('')
.then(suggestions => {
// Default is no filtering.
assert.equal(suggestions.length, 3);
@@ -228,7 +244,7 @@
return (suggestion as AccountInfo)._account_id === accountId;
};
- return element._getSuggestions('');
+ return element.getSuggestions('');
})
.then(suggestions => {
assert.deepEqual(suggestions, [
@@ -237,52 +253,55 @@
value: {
account: originalSuggestions[0] as AccountInfo,
count: 1,
- },
+ } as unknown as string,
},
]);
});
});
- test('_computeChipClass', () => {
+ test('computeChipClass', () => {
const account = makeAccount() as AccountInfoInput;
- assert.equal(element._computeChipClass(account), '');
+ assert.equal(element.computeChipClass(account), '');
account._pendingAdd = true;
- assert.equal(element._computeChipClass(account), 'pendingAdd');
+ assert.equal(element.computeChipClass(account), 'pendingAdd');
account._group = true;
- assert.equal(element._computeChipClass(account), 'group pendingAdd');
+ assert.equal(element.computeChipClass(account), 'group pendingAdd');
account._pendingAdd = false;
- assert.equal(element._computeChipClass(account), 'group');
+ assert.equal(element.computeChipClass(account), 'group');
});
- test('_computeRemovable', () => {
+ test('computeRemovable', async () => {
const newAccount = makeAccount() as AccountInfoInput;
newAccount._pendingAdd = true;
element.readonly = false;
element.removableValues = [];
- assert.isFalse(element._computeRemovable(existingAccount1, false));
- assert.isTrue(element._computeRemovable(newAccount, false));
+ element.updateComplete;
+ assert.isFalse(element.computeRemovable(existingAccount1));
+ assert.isTrue(element.computeRemovable(newAccount));
element.removableValues = [existingAccount1];
- assert.isTrue(element._computeRemovable(existingAccount1, false));
- assert.isTrue(element._computeRemovable(newAccount, false));
- assert.isFalse(element._computeRemovable(existingAccount2, false));
+ element.updateComplete;
+ assert.isTrue(element.computeRemovable(existingAccount1));
+ assert.isTrue(element.computeRemovable(newAccount));
+ assert.isFalse(element.computeRemovable(existingAccount2));
element.readonly = true;
- assert.isFalse(element._computeRemovable(existingAccount1, true));
- assert.isFalse(element._computeRemovable(newAccount, true));
+ element.updateComplete;
+ assert.isFalse(element.computeRemovable(existingAccount1));
+ assert.isFalse(element.computeRemovable(newAccount));
});
- test('submitEntryText', () => {
+ test('submitEntryText', async () => {
element.allowAnyInput = true;
- flush();
+ await element.updateComplete;
- const getTextStub = sinon.stub(element.$.entry, 'getText');
+ const getTextStub = sinon.stub(element.entry!, 'getText');
getTextStub.onFirstCall().returns('');
getTextStub.onSecondCall().returns('test');
getTextStub.onThirdCall().returns('test@test');
// When entry is empty, return true.
- const clearStub = sinon.stub(element.$.entry, 'clear');
+ const clearStub = sinon.stub(element.entry!, 'clear');
assert.isTrue(element.submitEntryText());
assert.isFalse(clearStub.called);
@@ -363,12 +382,12 @@
assert.equal(element.accounts.length, 1);
});
- test('max-count', () => {
+ test('max-count', async () => {
element.maxCount = 1;
const acct = makeAccount();
handleAdd({account: acct, count: 1});
- flush();
- assert.isTrue(element.$.entry.hasAttribute('hidden'));
+ await element.updateComplete;
+ assert.isTrue(element.entry!.hasAttribute('hidden'));
});
test('enter text calls suggestions provider', async () => {
@@ -391,12 +410,12 @@
'makeSuggestionItem'
);
- const input = element.$.entry.$.input;
+ const input = element.entry!.$.input;
input.text = 'newTest';
MockInteractions.focus(input.$.input);
input.noDebounce = true;
- await flush();
+ await element.updateComplete;
assert.isTrue(getSuggestionsStub.calledOnce);
assert.equal(getSuggestionsStub.lastCall.args[0], 'newTest');
assert.equal(makeSuggestionItemSpy.getCalls().length, 2);
@@ -427,38 +446,38 @@
suite('keyboard interactions', () => {
test('backspace at text input start removes last account', async () => {
- const input = element.$.entry.$.input;
+ const input = element.entry!.$.input;
sinon.stub(input, '_updateSuggestions');
- sinon.stub(element, '_computeRemovable').returns(true);
- await flush();
+ sinon.stub(element, 'computeRemovable').returns(true);
+ await await element.updateComplete;
// Next line is a workaround for Firefox not moving cursor
// on input field update
- assert.equal(element._getNativeInput(input.$.input).selectionStart, 0);
+ assert.equal(element.getOwnNativeInput(input.$.input).selectionStart, 0);
input.text = 'test';
MockInteractions.focus(input.$.input);
- flush();
+ await element.updateComplete;
assert.equal(element.accounts.length, 2);
MockInteractions.pressAndReleaseKeyOn(
- element._getNativeInput(input.$.input),
+ element.getOwnNativeInput(input.$.input),
8
); // Backspace
assert.equal(element.accounts.length, 2);
input.text = '';
MockInteractions.pressAndReleaseKeyOn(
- element._getNativeInput(input.$.input),
+ element.getOwnNativeInput(input.$.input),
8
); // Backspace
- flush();
+ await element.updateComplete;
assert.equal(element.accounts.length, 1);
});
test('arrow key navigation', async () => {
- const input = element.$.entry.$.input;
+ const input = element.entry!.$.input;
input.text = '';
element.accounts = [makeAccount(), makeAccount()];
- flush();
+ await element.updateComplete;
MockInteractions.focus(input.$.input);
- await flush();
+ await await element.updateComplete;
const chips = element.accountChips;
const chipsOneSpy = sinon.spy(chips[1], 'focus');
MockInteractions.pressAndReleaseKeyOn(input.$.input, 37); // Left
@@ -472,9 +491,9 @@
assert.isTrue(chipsOneSpy.calledTwice);
});
- test('delete', () => {
+ test('delete', async () => {
element.accounts = [makeAccount(), makeAccount()];
- flush();
+ await element.updateComplete;
const focusSpy = sinon.spy(element.accountChips[1], 'focus');
const removeSpy = sinon.spy(element, 'removeAccount');
MockInteractions.pressAndReleaseKeyOn(element.accountChips[0], 8); // Backspace
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
index a685f32..36baf87 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
@@ -54,7 +54,7 @@
name?: string;
label?: string;
value?: T;
- text?: T;
+ text?: string;
}
export interface AutocompleteCommitEventDetail {
diff --git a/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls-section.ts b/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls-section.ts
deleted file mode 100644
index 77ba8cd..0000000
--- a/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls-section.ts
+++ /dev/null
@@ -1,113 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import '../../../elements/shared/gr-button/gr-button';
-import {html, LitElement} from 'lit';
-import {customElement, property, state} from 'lit/decorators';
-import {DiffInfo, DiffViewMode, RenderPreferences} from '../../../api/diff';
-import {GrDiffGroup, GrDiffGroupType} from '../gr-diff/gr-diff-group';
-import {diffClasses} from '../gr-diff/gr-diff-utils';
-import {getShowConfig} from './gr-context-controls';
-import {ifDefined} from 'lit/directives/if-defined';
-
-@customElement('gr-context-controls-section')
-export class GrContextControlsSection extends LitElement {
- /** Should context controls be rendered for expanding above the section? */
- @property({type: Boolean}) showAbove = false;
-
- /** Should context controls be rendered for expanding below the section? */
- @property({type: Boolean}) showBelow = false;
-
- @property({type: Object}) viewMode = DiffViewMode.SIDE_BY_SIDE;
-
- /** Must be of type GrDiffGroupType.CONTEXT_CONTROL. */
- @property({type: Object})
- group?: GrDiffGroup;
-
- @property({type: Object})
- diff?: DiffInfo;
-
- @property({type: Object})
- renderPrefs?: RenderPreferences;
-
- /**
- * Semantic DOM diff testing does not work with just table fragments, so when
- * running such tests the render() method has to wrap the DOM in a proper
- * <table> element.
- */
- @state()
- addTableWrapperForTesting = false;
-
- /**
- * The browser API for handling selection does not (yet) work for selection
- * across multiple shadow DOM elements. So we are rendering gr-diff components
- * into the light DOM instead of the shadow DOM by overriding this method,
- * which was the recommended workaround by the lit team.
- * See also https://github.com/WICG/webcomponents/issues/79.
- */
- override createRenderRoot() {
- return this;
- }
-
- private renderPaddingRow(whereClass: 'above' | 'below') {
- if (!this.showAbove && whereClass === 'above') return;
- if (!this.showBelow && whereClass === 'below') return;
- const sideBySide = this.viewMode === DiffViewMode.SIDE_BY_SIDE;
- const modeClass = sideBySide ? 'side-by-side' : 'unified';
- const type = sideBySide ? GrDiffGroupType.CONTEXT_CONTROL : undefined;
- return html`
- <tr
- class=${diffClasses('contextBackground', modeClass, whereClass)}
- left-type=${ifDefined(type)}
- right-type=${ifDefined(type)}
- >
- <td class=${diffClasses('blame')} data-line-number="0"></td>
- <td class=${diffClasses('contextLineNum')}></td>
- ${sideBySide ? html`<td class=${diffClasses()}></td>` : ''}
- <td class=${diffClasses('contextLineNum')}></td>
- <td class=${diffClasses()}></td>
- </tr>
- `;
- }
-
- private createContextControlRow() {
- const sideBySide = this.viewMode === DiffViewMode.SIDE_BY_SIDE;
- const showConfig = getShowConfig(this.showAbove, this.showBelow);
- return html`
- <tr class=${diffClasses('dividerRow', `show-${showConfig}`)}>
- <td class=${diffClasses('blame')} data-line-number="0"></td>
- ${sideBySide ? html`<td class=${diffClasses()}></td>` : ''}
- <td class=${diffClasses('dividerCell')} colspan="3">
- <gr-context-controls
- .diff=${this.diff}
- .renderPreferences=${this.renderPrefs}
- .group=${this.group}
- .showConfig=${showConfig}
- >
- </gr-context-controls>
- </td>
- </tr>
- `;
- }
-
- override render() {
- const rows = html`
- ${this.renderPaddingRow('above')} ${this.createContextControlRow()}
- ${this.renderPaddingRow('below')}
- `;
- if (this.addTableWrapperForTesting) {
- return html`<table>
- ${rows}
- </table>`;
- }
- return rows;
- }
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- 'gr-context-controls-section': GrContextControlsSection;
- }
-}
diff --git a/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls-section_test.ts b/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls-section_test.ts
deleted file mode 100644
index 2c1043d..0000000
--- a/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls-section_test.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import '../../../test/common-test-setup-karma';
-import './gr-context-controls-section';
-import {GrContextControlsSection} from './gr-context-controls-section';
-import {html} from 'lit';
-import {fixture} from '@open-wc/testing-helpers';
-
-suite('gr-context-controls-section test', () => {
- let element: GrContextControlsSection;
-
- setup(async () => {
- element = await fixture<GrContextControlsSection>(
- html`<gr-context-controls-section></gr-context-controls-section>`
- );
- element.addTableWrapperForTesting = true;
- await element.updateComplete;
- });
-
- test('render: normal with showAbove and showBelow', async () => {
- element.showAbove = true;
- element.showBelow = true;
- await element.updateComplete;
- expect(element).lightDom.to.equal(/* HTML */ `
- <table>
- <tbody>
- <tr
- class="above contextBackground gr-diff side-by-side style-scope"
- left-type="contextControl"
- right-type="contextControl"
- >
- <td class="blame gr-diff style-scope" data-line-number="0"></td>
- <td class="contextLineNum gr-diff style-scope"></td>
- <td class="gr-diff style-scope"></td>
- <td class="contextLineNum gr-diff style-scope"></td>
- <td class="gr-diff style-scope"></td>
- </tr>
- <tr class="dividerRow gr-diff show-both style-scope">
- <td class="blame gr-diff style-scope" data-line-number="0"></td>
- <td class="gr-diff style-scope"></td>
- <td class="dividerCell gr-diff style-scope" colspan="3">
- <gr-context-controls showconfig="both"> </gr-context-controls>
- </td>
- </tr>
- <tr
- class="below contextBackground gr-diff side-by-side style-scope"
- left-type="contextControl"
- right-type="contextControl"
- >
- <td class="blame gr-diff style-scope" data-line-number="0"></td>
- <td class="contextLineNum gr-diff style-scope"></td>
- <td class="gr-diff style-scope"></td>
- <td class="contextLineNum gr-diff style-scope"></td>
- <td class="gr-diff style-scope"></td>
- </tr>
- </tbody>
- </table>
- `);
- });
-});
diff --git a/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls.ts b/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls.ts
index a451700..77a5dfb 100644
--- a/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls.ts
+++ b/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls.ts
@@ -80,19 +80,6 @@
export type GrContextControlsShowConfig = 'above' | 'below' | 'both';
-export function getShowConfig(
- showAbove: boolean,
- showBelow: boolean
-): GrContextControlsShowConfig {
- if (showAbove && !showBelow) return 'above';
- if (!showAbove && showBelow) return 'below';
-
- // Note that !showAbove && !showBelow also intentionally returns 'both'.
- // This means the file is completely collapsed, which is unusual, but at least
- // happens in one test.
- return 'both';
-}
-
@customElement('gr-context-controls')
export class GrContextControls extends LitElement {
@property({type: Object}) renderPreferences?: RenderPreferences;
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-element.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-element.ts
index 27ebe4e..aabdf57 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-element.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-element.ts
@@ -25,7 +25,6 @@
import {GrDiffBuilderImage} from './gr-diff-builder-image';
import {GrDiffBuilderUnified} from './gr-diff-builder-unified';
import {GrDiffBuilderBinary} from './gr-diff-builder-binary';
-import {GrDiffBuilderLit} from './gr-diff-builder-lit';
import {CancelablePromise, util} from '../../../scripts/util';
import {customElement, property, observe} from '@polymer/decorators';
import {BlameInfo, ImageInfo} from '../../../types/common';
@@ -464,24 +463,13 @@
// If the diff is binary, but not an image.
return new GrDiffBuilderBinary(this.diff, localPrefs, this.diffElement);
} else if (this.viewMode === DiffViewMode.SIDE_BY_SIDE) {
- const useLit = this.renderPrefs?.use_lit_components;
- if (useLit) {
- builder = new GrDiffBuilderLit(
- this.diff,
- localPrefs,
- this.diffElement,
- this._layers,
- this.renderPrefs
- );
- } else {
- builder = new GrDiffBuilderSideBySide(
- this.diff,
- localPrefs,
- this.diffElement,
- this._layers,
- this.renderPrefs
- );
- }
+ builder = new GrDiffBuilderSideBySide(
+ this.diff,
+ localPrefs,
+ this.diffElement,
+ this._layers,
+ this.renderPrefs
+ );
} else if (this.viewMode === DiffViewMode.UNIFIED) {
builder = new GrDiffBuilderUnified(
this.diff,
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-lit.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-lit.ts
deleted file mode 100644
index 3687747..0000000
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-lit.ts
+++ /dev/null
@@ -1,185 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {RenderPreferences} from '../../../api/diff';
-import {LineNumber} from '../gr-diff/gr-diff-line';
-import {GrDiffGroup} from '../gr-diff/gr-diff-group';
-import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
-import {Side} from '../../../constants/constants';
-import {DiffLayer, notUndefined} from '../../../types/types';
-import {diffClasses} from '../gr-diff/gr-diff-utils';
-import {GrDiffBuilder} from './gr-diff-builder';
-import {BlameInfo} from '../../../types/common';
-import {html, render} from 'lit';
-import {GrDiffSection} from './gr-diff-section';
-import '../gr-context-controls/gr-context-controls';
-import './gr-diff-section';
-import {GrDiffRow} from './gr-diff-row';
-
-/**
- * Base class for builders that are creating the diff using Lit elements.
- */
-export class GrDiffBuilderLit extends GrDiffBuilder {
- constructor(
- diff: DiffInfo,
- prefs: DiffPreferencesInfo,
- outputEl: HTMLElement,
- layers: DiffLayer[] = [],
- renderPrefs?: RenderPreferences
- ) {
- super(diff, prefs, outputEl, layers, renderPrefs);
- }
-
- override getContentTdByLine(
- lineNumber: LineNumber,
- side?: Side,
- _root: Element = this.outputEl
- ): HTMLTableCellElement | null {
- if (!side) return null;
- const row = this.findRow(lineNumber, side);
- return row?.getContentCell(side) ?? null;
- }
-
- override getLineElByNumber(lineNumber: LineNumber, side: Side) {
- const row = this.findRow(lineNumber, side);
- return row?.getLineNumberCell(side) ?? null;
- }
-
- private findRow(lineNumber?: LineNumber, side?: Side): GrDiffRow | undefined {
- if (!side || !lineNumber) return undefined;
- const group = this.findGroup(side, lineNumber);
- if (!group) return undefined;
- const section = this.findSection(group);
- if (!section) return undefined;
- return section.findRow(side, lineNumber);
- }
-
- private getDiffRows() {
- const sections = [
- ...this.outputEl.querySelectorAll<GrDiffSection>('gr-diff-section'),
- ];
- return sections.map(s => s.getDiffRows()).flat();
- }
-
- override getLineNumberRows(): HTMLTableRowElement[] {
- const rows = this.getDiffRows();
- return rows.map(r => r.getTableRow()).filter(notUndefined);
- }
-
- override getLineNumEls(side: Side): HTMLTableCellElement[] {
- const rows = this.getDiffRows();
- return rows.map(r => r.getLineNumberCell(side)).filter(notUndefined);
- }
-
- override getBlameTdByLine(lineNumber: number): Element | undefined {
- return this.findRow(lineNumber, Side.LEFT)?.getBlameCell();
- }
-
- override getContentByLine(
- lineNumber: LineNumber,
- side?: Side,
- _root?: HTMLElement
- ): HTMLElement | null {
- const cell = this.getContentTdByLine(lineNumber, side);
- return (cell?.firstChild ?? null) as HTMLElement | null;
- }
-
- override renderContentByRange(
- start: LineNumber,
- end: LineNumber,
- side: Side
- ) {
- // TODO: Revisit whether there is maybe a more efficient and reliable
- // approach. renderContentByRange() is only used when layers announce
- // updates. We have to look deeper into the design of layers anyway. So
- // let's defer optimizing this code until a refactor of layers in general.
- const groups = this.getGroupsByLineRange(start, end, side);
- for (const group of groups) {
- const section = this.findSection(group);
- for (const row of section?.getDiffRows() ?? []) {
- row.requestUpdate();
- }
- }
- }
-
- private findSection(group?: GrDiffGroup): GrDiffSection | undefined {
- if (!group) return undefined;
- const leftClass = `left-${group.lineRange.left.start_line}`;
- const rightClass = `right-${group.lineRange.right.start_line}`;
- return (
- this.outputEl.querySelector<GrDiffSection>(
- `gr-diff-section.${leftClass}.${rightClass}`
- ) ?? undefined
- );
- }
-
- override renderBlameByRange(
- blameInfo: BlameInfo,
- start: number,
- end: number
- ) {
- for (let lineNumber = start; lineNumber <= end; lineNumber++) {
- const row = this.findRow(lineNumber, Side.LEFT);
- if (!row) continue;
- row.blameInfo = blameInfo;
- }
- }
-
- // TODO: Refactor this such that adding the move controls becomes part of the
- // lit element.
- protected override getMoveControlsConfig() {
- return {
- numberOfCells: 4, // How many cells does the diff table have?
- movedOutIndex: 1, // Index of left content column in diff table.
- movedInIndex: 3, // Index of right content column in diff table.
- lineNumberCols: [0, 2], // Indices of line number columns in diff table.
- };
- }
-
- protected override buildSectionElement(group: GrDiffGroup) {
- const leftCl = `left-${group.lineRange.left.start_line}`;
- const rightCl = `right-${group.lineRange.right.start_line}`;
- const section = html`
- <gr-diff-section
- class="${leftCl} ${rightCl}"
- .group=${group}
- .diff=${this._diff}
- .layers=${this.layers}
- .diffPrefs=${this._prefs}
- .renderPrefs=${this.renderPrefs}
- ></gr-diff-section>
- `;
- // TODO: Refactor GrDiffBuilder.emitGroup() and buildSectionElement()
- // such that we can render directly into the correct container.
- const tempContainer = document.createElement('div');
- render(section, tempContainer);
- return tempContainer.firstElementChild as GrDiffSection;
- }
-
- override addColumns(outputEl: HTMLElement, lineNumberWidth: number): void {
- render(
- html`
- <colgroup>
- <col class=${diffClasses('blame')}></col>
- <col class=${diffClasses(Side.LEFT)} width=${lineNumberWidth}></col>
- <col class=${diffClasses(Side.LEFT)}></col>
- <col class=${diffClasses(Side.RIGHT)} width=${lineNumberWidth}></col>
- <col class=${diffClasses(Side.RIGHT)}></col>
- </colgroup>
- `,
- outputEl
- );
- }
-
- protected override getNextContentOnSide(
- _content: HTMLElement,
- _side: Side
- ): HTMLElement | null {
- // TODO: getNextContentOnSide() is not required by lit based rendering.
- // So let's refactor it to be moved into gr-diff-builder-legacy.
- console.warn('unimplemented method getNextContentOnSide() called');
- return null;
- }
-}
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder.ts
index 4efa238..add7ffa 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder.ts
@@ -194,7 +194,7 @@
group.element = element;
}
- protected getGroupsByLineRange(
+ private getGroupsByLineRange(
startLine: LineNumber,
endLine: LineNumber,
side: Side
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-row.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-row.ts
deleted file mode 100644
index ae18e59..0000000
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-row.ts
+++ /dev/null
@@ -1,368 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {html, LitElement, TemplateResult} from 'lit';
-import {customElement, property, state} from 'lit/decorators';
-import {ifDefined} from 'lit/directives/if-defined';
-import {createRef, Ref, ref} from 'lit/directives/ref';
-import {
- DiffResponsiveMode,
- Side,
- LineNumber,
- DiffLayer,
-} from '../../../api/diff';
-import {BlameInfo} from '../../../types/common';
-import {assertIsDefined} from '../../../utils/common-util';
-import {fire} from '../../../utils/event-util';
-import {getBaseUrl} from '../../../utils/url-util';
-import './gr-diff-text';
-import {GrDiffLine, GrDiffLineType} from '../gr-diff/gr-diff-line';
-import {diffClasses, isResponsive} from '../gr-diff/gr-diff-utils';
-
-@customElement('gr-diff-row')
-export class GrDiffRow extends LitElement {
- contentLeftRef: Ref<HTMLDivElement> = createRef();
-
- contentRightRef: Ref<HTMLDivElement> = createRef();
-
- lineNumberLeftRef: Ref<HTMLTableCellElement> = createRef();
-
- lineNumberRightRef: Ref<HTMLTableCellElement> = createRef();
-
- blameCellRef: Ref<HTMLTableCellElement> = createRef();
-
- tableRowRef: Ref<HTMLTableRowElement> = createRef();
-
- @property({type: Object})
- left?: GrDiffLine;
-
- @property({type: Object})
- right?: GrDiffLine;
-
- @property({type: Object})
- blameInfo?: BlameInfo;
-
- @property({type: Object})
- responsiveMode?: DiffResponsiveMode;
-
- @property({type: Number})
- tabSize = 2;
-
- @property({type: Number})
- lineLength = 80;
-
- @property({type: Boolean})
- hideFileCommentButton = false;
-
- @property({type: Object})
- layers: DiffLayer[] = [];
-
- /**
- * While not visible we are trying to optimize rendering performance by
- * rendering a simpler version of the diff. Once this has become true it
- * cannot be set back to false.
- */
- @state()
- isVisible = false;
-
- /**
- * Semantic DOM diff testing does not work with just table fragments, so when
- * running such tests the render() method has to wrap the DOM in a proper
- * <table> element.
- */
- @state()
- addTableWrapperForTesting = false;
-
- /**
- * The browser API for handling selection does not (yet) work for selection
- * across multiple shadow DOM elements. So we are rendering gr-diff components
- * into the light DOM instead of the shadow DOM by overriding this method,
- * which was the recommended workaround by the lit team.
- * See also https://github.com/WICG/webcomponents/issues/79.
- */
- override createRenderRoot() {
- return this;
- }
-
- override updated() {
- this.updateLayers(Side.LEFT);
- this.updateLayers(Side.RIGHT);
- }
-
- /**
- * TODO: This needs some refinement, because layers do not detect whether they
- * have already applied their information, so at the moment all layers would
- * constantly re-apply their information to the diff in each lit rendering
- * pass.
- */
- private updateLayers(side: Side) {
- if (!this.isVisible) return;
- const line = this.line(side);
- const contentEl = this.contentRef(side).value;
- const lineNumberEl = this.lineNumberRef(side).value;
- if (!line || !contentEl || !lineNumberEl) return;
- for (const layer of this.layers) {
- if (typeof layer.annotate === 'function') {
- layer.annotate(contentEl, lineNumberEl, line, side);
- }
- }
- }
-
- private renderInvisible() {
- return html`
- <tr>
- <td class="style-scope gr-diff blame"></td>
- <td class="style-scope gr-diff left"></td>
- <td class="style-scope gr-diff left content">
- <div>${this.left?.text ?? ''}</div>
- </td>
- <td class="style-scope gr-diff right"></td>
- <td class="style-scope gr-diff right content">
- <div>${this.right?.text ?? ''}</div>
- </td>
- </tr>
- `;
- }
-
- override render() {
- if (!this.left || !this.right) return;
- if (!this.isVisible) return this.renderInvisible();
- const row = html`
- <tr
- ${ref(this.tableRowRef)}
- class=${diffClasses('diff-row', 'side-by-side')}
- left-type=${this.left.type}
- right-type=${this.right.type}
- tabindex="-1"
- >
- ${this.renderBlameCell()} ${this.renderLineNumberCell(Side.LEFT)}
- ${this.renderContentCell(Side.LEFT)}
- ${this.renderLineNumberCell(Side.RIGHT)}
- ${this.renderContentCell(Side.RIGHT)}
- </tr>
- `;
- if (this.addTableWrapperForTesting) {
- return html`<table>
- ${row}
- </table>`;
- }
- return row;
- }
-
- getTableRow(): HTMLTableRowElement | undefined {
- return this.tableRowRef.value;
- }
-
- getLineNumberCell(side: Side): HTMLTableCellElement | undefined {
- return this.lineNumberRef(side).value;
- }
-
- getContentCell(side: Side) {
- const div = this.contentRef(side)?.value;
- if (!div) return undefined;
- return div.parentElement as HTMLTableCellElement;
- }
-
- getBlameCell() {
- return this.blameCellRef.value;
- }
-
- private renderBlameCell() {
- // td.blame has `white-space: pre`, so prettier must not add spaces.
- // prettier-ignore
- return html`
- <td
- ${ref(this.blameCellRef)}
- class=${diffClasses('blame')}
- data-line-number=${this.left?.beforeNumber ?? 0}
- >${this.renderBlameElement()}</td>
- `;
- }
-
- private renderBlameElement() {
- const lineNum = this.left?.beforeNumber;
- const commit = this.blameInfo;
- if (!lineNum || !commit) return;
-
- const isStartOfRange = commit.ranges.some(r => r.start === lineNum);
- const extras: string[] = [];
- if (isStartOfRange) extras.push('startOfRange');
- const date = new Date(commit.time * 1000).toLocaleDateString();
- const shortName = commit.author.split(' ')[0];
- const url = `${getBaseUrl()}/q/${commit.id}`;
-
- // td.blame has `white-space: pre`, so prettier must not add spaces.
- // prettier-ignore
- return html`<span class=${diffClasses(...extras)}
- ><a href=${url} class=${diffClasses('blameDate')}>${date}</a
- ><span class=${diffClasses('blameAuthor')}> ${shortName}</span
- ><gr-hovercard class=${diffClasses()}>
- <span class=${diffClasses('blameHoverCard')}>
- Commit ${commit.id}<br />
- Author: ${commit.author}<br />
- Date: ${date}<br />
- <br />
- ${commit.commit_msg}
- </span>
- </gr-hovercard
- ></span>`;
- }
-
- private renderLineNumberCell(side: Side): TemplateResult {
- const line = this.line(side);
- const lineNumber = this.lineNumber(side);
- if (!line || !lineNumber || line.type === GrDiffLineType.BLANK) {
- return html`<td
- ${ref(this.lineNumberRef(side))}
- class=${diffClasses(side)}
- ></td>`;
- }
-
- return html`<td
- ${ref(this.lineNumberRef(side))}
- class=${diffClasses(side, 'lineNum')}
- data-value=${lineNumber}
- >
- ${this.renderLineNumberButton(line, lineNumber, side)}
- </td>`;
- }
-
- private renderLineNumberButton(
- line: GrDiffLine,
- lineNumber: LineNumber,
- side: Side
- ) {
- if (this.hideFileCommentButton && lineNumber === 'FILE') return;
- if (lineNumber === 'LOST') return;
- // .lineNumButton has `white-space: pre`, so prettier must not add spaces.
- // prettier-ignore
- return html`
- <button
- class=${diffClasses('lineNumButton', side)}
- tabindex="-1"
- data-value=${lineNumber}
- aria-label=${ifDefined(
- this.computeLineNumberAriaLabel(line, lineNumber)
- )}
- @mouseenter=${() =>
- fire(this, 'line-mouse-enter', {lineNum: lineNumber, side})}
- @mouseleave=${() =>
- fire(this, 'line-mouse-leave', {lineNum: lineNumber, side})}
- >${lineNumber === 'FILE' ? 'File' : lineNumber.toString()}</button>
- `;
- }
-
- private computeLineNumberAriaLabel(line: GrDiffLine, lineNumber: LineNumber) {
- if (lineNumber === 'FILE') return 'Add file comment';
-
- // Add aria-labels for valid line numbers.
- // For unified diff, this method will be called with number set to 0 for
- // the empty line number column for added/removed lines. This should not
- // be announced to the screenreader.
- if (lineNumber <= 0) return undefined;
-
- switch (line.type) {
- case GrDiffLineType.REMOVE:
- return `${lineNumber} removed`;
- case GrDiffLineType.ADD:
- return `${lineNumber} added`;
- case GrDiffLineType.BOTH:
- case GrDiffLineType.BLANK:
- return undefined;
- }
- }
-
- private renderContentCell(side: Side): TemplateResult {
- const line = this.line(side);
- const lineNumber = this.lineNumber(side);
- assertIsDefined(line, 'line');
- const extras: string[] = [line.type, side];
- if (line.type !== GrDiffLineType.BLANK) extras.push('content');
- if (!line.hasIntralineInfo) extras.push('no-intraline-info');
- if (line.beforeNumber === 'FILE') extras.push('file');
- if (line.beforeNumber === 'LOST') extras.push('lost');
-
- // .content has `white-space: pre`, so prettier must not add spaces.
- // prettier-ignore
- return html`
- <td
- class=${diffClasses(...extras)}
- @mouseenter=${() => {
- if (lineNumber)
- fire(this, 'line-mouse-enter', {lineNum: lineNumber, side});
- }}
- @mouseleave=${() => {
- if (lineNumber)
- fire(this, 'line-mouse-leave', {lineNum: lineNumber, side});
- }}
- >${this.renderText(side)}${this.renderThreadGroup(side, lineNumber)}</td>
- `;
- }
-
- private renderThreadGroup(side: Side, lineNumber?: LineNumber) {
- if (!lineNumber) return;
- // TODO: For the LOST line number the convention is that a <tr> will always
- // be rendered, but it will not be visible, because of all cells being
- // empty. For this to work with lit-based rendering we may only render a
- // thread-group and a <slot> when there is a thread using that slot. The
- // cleanest solution for that is probably introducing a gr-diff-model, where
- // each diff row can look up or observe comment threads.
- // .content has `white-space: pre`, so prettier must not add spaces.
- // prettier-ignore
- return html`<div class="thread-group" data-side=${side}><slot name="${side}-${lineNumber}"></slot></div>`;
- }
-
- private contentRef(side: Side) {
- return side === Side.LEFT ? this.contentLeftRef : this.contentRightRef;
- }
-
- private lineNumberRef(side: Side) {
- return side === Side.LEFT
- ? this.lineNumberLeftRef
- : this.lineNumberRightRef;
- }
-
- private lineNumber(side: Side) {
- return this.line(side)?.lineNumber(side);
- }
-
- private line(side: Side) {
- return side === Side.LEFT ? this.left : this.right;
- }
-
- /**
- * Returns a 'div' element containing the supplied |text| as its innerText,
- * with '\t' characters expanded to a width determined by |tabSize|, and the
- * text wrapped at column |lineLimit|, which may be Infinity if no wrapping is
- * desired.
- */
- private renderText(side: Side) {
- const line = this.line(side);
- const lineNumber = this.lineNumber(side);
- if (lineNumber === 'FILE' || lineNumber === 'LOST') return;
- // prettier-ignore
- const textElement = line?.text
- ? html`<gr-diff-text
- ${ref(this.contentRef(side))}
- .text=${line?.text}
- .tabSize=${this.tabSize}
- .lineLimit=${this.lineLength}
- .isResponsive=${isResponsive(this.responsiveMode)}
- ></gr-diff-text>` : '';
- // .content has `white-space: pre`, so prettier must not add spaces.
- // prettier-ignore
- return html`<div
- class=${diffClasses('contentText', side)}
- .ariaLabel=${line?.text ?? ''}
- data-side=${ifDefined(side)}
- >${textElement}</div>`;
- }
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- 'gr-diff-row': GrDiffRow;
- }
-}
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-row_test.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-row_test.ts
deleted file mode 100644
index 757d906..0000000
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-row_test.ts
+++ /dev/null
@@ -1,195 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import '../../../test/common-test-setup-karma';
-import './gr-diff-row';
-import {GrDiffRow} from './gr-diff-row';
-import {html} from 'lit';
-import {fixture} from '@open-wc/testing-helpers';
-import {GrDiffLine} from '../gr-diff/gr-diff-line';
-import {GrDiffLineType} from '../../../api/diff';
-
-suite('gr-diff-row test', () => {
- let element: GrDiffRow;
-
- setup(async () => {
- element = await fixture<GrDiffRow>(html`<gr-diff-row></gr-diff-row>`);
- element.isVisible = true;
- element.addTableWrapperForTesting = true;
- await element.updateComplete;
- });
-
- test('both', async () => {
- const line = new GrDiffLine(GrDiffLineType.BOTH, 1, 1);
- line.text = 'lorem ipsum';
- element.left = line;
- element.right = line;
- await element.updateComplete;
- expect(element).lightDom.to.equal(/* HTML */ `
- <table>
- <tbody>
- <tr
- class="diff-row gr-diff side-by-side style-scope"
- left-type="both"
- right-type="both"
- tabindex="-1"
- >
- <td class="blame gr-diff style-scope" data-line-number="1"></td>
- <td class="gr-diff left lineNum style-scope" data-value="1">
- <button
- class="gr-diff left lineNumButton style-scope"
- data-value="1"
- tabindex="-1"
- >
- 1
- </button>
- </td>
- <td class="both content gr-diff left no-intraline-info style-scope">
- <div
- aria-label="lorem ipsum"
- class="contentText gr-diff left style-scope"
- data-side="left"
- >
- <gr-diff-text>lorem ipsum</gr-diff-text>
- </div>
- <div class="thread-group" data-side="left">
- <slot name="left-1"> </slot>
- </div>
- </td>
- <td class="gr-diff lineNum right style-scope" data-value="1">
- <button
- class="gr-diff lineNumButton right style-scope"
- data-value="1"
- tabindex="-1"
- >
- 1
- </button>
- </td>
- <td
- class="both content gr-diff no-intraline-info right style-scope"
- >
- <div
- aria-label="lorem ipsum"
- class="contentText gr-diff right style-scope"
- data-side="right"
- >
- <gr-diff-text>lorem ipsum</gr-diff-text>
- </div>
- <div class="thread-group" data-side="right">
- <slot name="right-1"> </slot>
- </div>
- </td>
- </tr>
- </tbody>
- </table>
- `);
- });
-
- test('add', async () => {
- const line = new GrDiffLine(GrDiffLineType.ADD, 0, 1);
- line.text = 'lorem ipsum';
- element.left = new GrDiffLine(GrDiffLineType.BLANK);
- element.right = line;
- await element.updateComplete;
- expect(element).lightDom.to.equal(/* HTML */ `
- <table>
- <tbody>
- <tr
- class="diff-row gr-diff side-by-side style-scope"
- left-type="blank"
- right-type="add"
- tabindex="-1"
- >
- <td class="blame gr-diff style-scope" data-line-number="0"></td>
- <td class="gr-diff left style-scope"></td>
- <td class="blank gr-diff left no-intraline-info style-scope">
- <div
- aria-label=""
- class="contentText gr-diff left style-scope"
- data-side="left"
- ></div>
- </td>
- <td class="gr-diff lineNum right style-scope" data-value="1">
- <button
- aria-label="1 added"
- class="gr-diff lineNumButton right style-scope"
- data-value="1"
- tabindex="-1"
- >
- 1
- </button>
- </td>
- <td class="add content gr-diff no-intraline-info right style-scope">
- <div
- aria-label="lorem ipsum"
- class="contentText gr-diff right style-scope"
- data-side="right"
- >
- <gr-diff-text>lorem ipsum</gr-diff-text>
- </div>
- <div class="thread-group" data-side="right">
- <slot name="right-1"> </slot>
- </div>
- </td>
- </tr>
- </tbody>
- </table>
- `);
- });
-
- test('remove', async () => {
- const line = new GrDiffLine(GrDiffLineType.REMOVE, 1, 0);
- line.text = 'lorem ipsum';
- element.left = line;
- element.right = new GrDiffLine(GrDiffLineType.BLANK);
- await element.updateComplete;
- expect(element).lightDom.to.equal(/* HTML */ `
- <table>
- <tbody>
- <tr
- class="diff-row gr-diff side-by-side style-scope"
- left-type="remove"
- right-type="blank"
- tabindex="-1"
- >
- <td class="blame gr-diff style-scope" data-line-number="1"></td>
- <td class="gr-diff left lineNum style-scope" data-value="1">
- <button
- aria-label="1 removed"
- class="gr-diff left lineNumButton style-scope"
- data-value="1"
- tabindex="-1"
- >
- 1
- </button>
- </td>
- <td
- class="content gr-diff left no-intraline-info remove style-scope"
- >
- <div
- aria-label="lorem ipsum"
- class="contentText gr-diff left style-scope"
- data-side="left"
- >
- <gr-diff-text>lorem ipsum</gr-diff-text>
- </div>
- <div class="thread-group" data-side="left">
- <slot name="left-1"> </slot>
- </div>
- </td>
- <td class="gr-diff right style-scope"></td>
- <td class="blank gr-diff no-intraline-info right style-scope">
- <div
- aria-label=""
- class="contentText gr-diff right style-scope"
- data-side="right"
- ></div>
- </td>
- </tr>
- </tbody>
- </table>
- `);
- });
-});
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section.ts
deleted file mode 100644
index b11d767..0000000
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section.ts
+++ /dev/null
@@ -1,240 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {html, LitElement} from 'lit';
-import {customElement, property, state} from 'lit/decorators';
-import {
- DiffInfo,
- DiffLayer,
- DiffViewMode,
- MovedLinkClickedEventDetail,
- RenderPreferences,
- Side,
- LineNumber,
- DiffPreferencesInfo,
-} from '../../../api/diff';
-import {GrDiffGroup, GrDiffGroupType} from '../gr-diff/gr-diff-group';
-import {countLines, diffClasses} from '../gr-diff/gr-diff-utils';
-import {GrDiffRow} from './gr-diff-row';
-import '../gr-context-controls/gr-context-controls-section';
-import '../gr-context-controls/gr-context-controls';
-import '../gr-range-header/gr-range-header';
-import './gr-diff-row';
-import {whenVisible} from '../../../utils/dom-util';
-
-@customElement('gr-diff-section')
-export class GrDiffSection extends LitElement {
- @property({type: Object})
- group?: GrDiffGroup;
-
- @property({type: Object})
- diff?: DiffInfo;
-
- @property({type: Object})
- renderPrefs?: RenderPreferences;
-
- @property({type: Object})
- diffPrefs?: DiffPreferencesInfo;
-
- @property({type: Object})
- layers: DiffLayer[] = [];
-
- /**
- * While not visible we are trying to optimize rendering performance by
- * rendering a simpler version of the diff.
- */
- @state()
- isVisible = false;
-
- /**
- * Semantic DOM diff testing does not work with just table fragments, so when
- * running such tests the render() method has to wrap the DOM in a proper
- * <table> element.
- */
- @state()
- addTableWrapperForTesting = false;
-
- /**
- * The browser API for handling selection does not (yet) work for selection
- * across multiple shadow DOM elements. So we are rendering gr-diff components
- * into the light DOM instead of the shadow DOM by overriding this method,
- * which was the recommended workaround by the lit team.
- * See also https://github.com/WICG/webcomponents/issues/79.
- */
- override createRenderRoot() {
- return this;
- }
-
- override connectedCallback() {
- super.connectedCallback();
- // TODO: Refine this obviously simplistic approach to optimized rendering.
- whenVisible(this.parentElement!, () => (this.isVisible = true), 1000);
- }
-
- override render() {
- if (!this.group) return;
- const extras: string[] = [];
- extras.push('section');
- extras.push(this.group.type);
- if (this.group.isTotal()) extras.push('total');
- if (this.group.dueToRebase) extras.push('dueToRebase');
- if (this.group.moveDetails) extras.push('dueToMove');
- if (this.group.ignoredWhitespaceOnly) extras.push('ignoredWhitespaceOnly');
-
- const isControl = this.group.type === GrDiffGroupType.CONTEXT_CONTROL;
- const pairs = isControl ? [] : this.group.getSideBySidePairs();
- const body = html`
- <tbody class=${diffClasses(...extras)}>
- ${this.renderContextControls()} ${this.renderMoveControls()}
- ${pairs.map(pair => {
- const leftCl = `left-${pair.left.lineNumber(Side.LEFT)}`;
- const rightCl = `right-${pair.right.lineNumber(Side.RIGHT)}`;
- return html`
- <gr-diff-row
- class="${leftCl} ${rightCl}"
- .left=${pair.left}
- .right=${pair.right}
- .layers=${this.layers}
- .lineLength=${this.diffPrefs?.line_length ?? 80}
- .tabSize=${this.diffPrefs?.tab_size ?? 2}
- .isVisible=${this.isVisible}
- >
- </gr-diff-row>
- `;
- })}
- </tbody>
- `;
- if (this.addTableWrapperForTesting) {
- return html`<table>
- ${body}
- </table>`;
- }
- return body;
- }
-
- getDiffRows(): GrDiffRow[] {
- return [...this.querySelectorAll<GrDiffRow>('gr-diff-row')];
- }
-
- private renderContextControls() {
- if (this.group?.type !== GrDiffGroupType.CONTEXT_CONTROL) return;
-
- const leftStart = this.group.lineRange.left.start_line;
- const leftEnd = this.group.lineRange.left.end_line;
- const firstGroupIsSkipped = !!this.group.contextGroups[0].skip;
- const lastGroupIsSkipped =
- !!this.group.contextGroups[this.group.contextGroups.length - 1].skip;
- const lineCountLeft = countLines(this.diff, Side.LEFT);
- const containsWholeFile = lineCountLeft === leftEnd - leftStart + 1;
- const showAbove =
- (leftStart > 1 && !firstGroupIsSkipped) || containsWholeFile;
- const showBelow = leftEnd < lineCountLeft && !lastGroupIsSkipped;
-
- return html`
- <gr-context-controls-section
- .showAbove=${showAbove}
- .showBelow=${showBelow}
- .group=${this.group}
- .diff=${this.diff}
- .renderPrefs=${this.renderPrefs}
- .viewMode=${DiffViewMode.SIDE_BY_SIDE}
- >
- </gr-context-controls-section>
- `;
- }
-
- findRow(side: Side, lineNumber: LineNumber): GrDiffRow | undefined {
- return (
- this.querySelector<GrDiffRow>(`gr-diff-row.${side}-${lineNumber}`) ??
- undefined
- );
- }
-
- private renderMoveControls() {
- if (!this.group?.moveDetails) return;
- const movedIn = this.group.adds.length > 0;
- const plainCell = html`<td class=${diffClasses()}></td>`;
- const lineNumberCell = html`
- <td class=${diffClasses('moveControlsLineNumCol')}></td>
- `;
- const moveCell = html`
- <td class=${diffClasses('moveHeader')}>
- <gr-range-header class=${diffClasses()} icon="gr-icons:move-item">
- ${this.renderMoveDescription(movedIn)}
- </gr-range-header>
- </td>
- `;
- return html`
- <tr
- class=${diffClasses('moveControls', movedIn ? 'movedIn' : 'movedOut')}
- >
- ${lineNumberCell} ${movedIn ? plainCell : moveCell} ${lineNumberCell}
- ${movedIn ? moveCell : plainCell}
- </tr>
- `;
- }
-
- private renderMoveDescription(movedIn: boolean) {
- if (this.group?.moveDetails?.range) {
- const {changed, range} = this.group.moveDetails;
- const otherSide = movedIn ? Side.LEFT : Side.RIGHT;
- const andChangedLabel = changed ? 'and changed ' : '';
- const direction = movedIn ? 'from' : 'to';
- const textLabel = `Moved ${andChangedLabel}${direction} lines `;
- return html`
- <div class=${diffClasses()}>
- <span class=${diffClasses()}>${textLabel}</span>
- ${this.renderMovedLineAnchor(range.start, otherSide)}
- <span class=${diffClasses()}> - </span>
- ${this.renderMovedLineAnchor(range.end, otherSide)}
- </div>
- `;
- }
-
- return html`
- <div class=${diffClasses()}>
- <span class=${diffClasses()}
- >${movedIn ? 'Moved in' : 'Moved out'}</span
- >
- </div>
- `;
- }
-
- private renderMovedLineAnchor(line: number, side: Side) {
- const listener = (e: MouseEvent) => {
- e.preventDefault();
- this.handleMovedLineAnchorClick(e.target, side, line);
- };
- // `href` is not actually used but important for Screen Readers
- return html`
- <a class=${diffClasses()} href=${`#${line}`} @click=${listener}
- >${line}</a
- >
- `;
- }
-
- private handleMovedLineAnchorClick(
- anchor: EventTarget | null,
- side: Side,
- line: number
- ) {
- anchor?.dispatchEvent(
- new CustomEvent<MovedLinkClickedEventDetail>('moved-link-clicked', {
- detail: {
- lineNum: line,
- side,
- },
- composed: true,
- bubbles: true,
- })
- );
- }
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- 'gr-diff-section': GrDiffSection;
- }
-}
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section_test.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section_test.ts
deleted file mode 100644
index 88c0e83..0000000
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section_test.ts
+++ /dev/null
@@ -1,213 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import '../../../test/common-test-setup-karma';
-import './gr-diff-section';
-import {GrDiffSection} from './gr-diff-section';
-import {html} from 'lit';
-import {fixture} from '@open-wc/testing-helpers';
-import {GrDiffGroup, GrDiffGroupType} from '../gr-diff/gr-diff-group';
-import {GrDiffLine} from '../gr-diff/gr-diff-line';
-import {GrDiffLineType} from '../../../api/diff';
-
-suite('gr-diff-section test', () => {
- let element: GrDiffSection;
-
- setup(async () => {
- element = await fixture<GrDiffSection>(
- html`<gr-diff-section></gr-diff-section>`
- );
- element.addTableWrapperForTesting = true;
- element.isVisible = true;
- await element.updateComplete;
- });
-
- test('3 normal unchanged rows', async () => {
- const lines = [
- new GrDiffLine(GrDiffLineType.BOTH, 1, 1),
- new GrDiffLine(GrDiffLineType.BOTH, 1, 1),
- new GrDiffLine(GrDiffLineType.BOTH, 1, 1),
- ];
- lines[0].text = 'asdf';
- lines[1].text = 'qwer';
- lines[2].text = 'zxcv';
- const group = new GrDiffGroup({type: GrDiffGroupType.BOTH, lines});
- element.group = group;
- await element.updateComplete;
- expect(element).dom.to.equal(/* HTML */ `
- <gr-diff-section>
- <gr-diff-row class="left-1 right-1"> </gr-diff-row>
- <gr-diff-row class="left-1 right-1"> </gr-diff-row>
- <gr-diff-row class="left-1 right-1"> </gr-diff-row>
- <table>
- <tbody class="both gr-diff section style-scope">
- <tr
- class="diff-row gr-diff side-by-side style-scope"
- left-type="both"
- right-type="both"
- tabindex="-1"
- >
- <td class="blame gr-diff style-scope" data-line-number="1"></td>
- <td class="gr-diff left lineNum style-scope" data-value="1">
- <button
- class="gr-diff left lineNumButton style-scope"
- data-value="1"
- tabindex="-1"
- >
- 1
- </button>
- </td>
- <td
- class="both content gr-diff left no-intraline-info style-scope"
- >
- <div
- aria-label="asdf"
- class="contentText gr-diff left style-scope"
- data-side="left"
- >
- <gr-diff-text></gr-diff-text>
- </div>
- <div class="thread-group" data-side="left">
- <slot name="left-1"> </slot>
- </div>
- </td>
- <td class="gr-diff lineNum right style-scope" data-value="1">
- <button
- class="gr-diff lineNumButton right style-scope"
- data-value="1"
- tabindex="-1"
- >
- 1
- </button>
- </td>
- <td
- class="both content gr-diff no-intraline-info right style-scope"
- >
- <div
- aria-label="asdf"
- class="contentText gr-diff right style-scope"
- data-side="right"
- >
- <gr-diff-text></gr-diff-text>
- </div>
- <div class="thread-group" data-side="right">
- <slot name="right-1"> </slot>
- </div>
- </td>
- </tr>
- <tr
- class="diff-row gr-diff side-by-side style-scope"
- left-type="both"
- right-type="both"
- tabindex="-1"
- >
- <td class="blame gr-diff style-scope" data-line-number="1"></td>
- <td class="gr-diff left lineNum style-scope" data-value="1">
- <button
- class="gr-diff left lineNumButton style-scope"
- data-value="1"
- tabindex="-1"
- >
- 1
- </button>
- </td>
- <td
- class="both content gr-diff left no-intraline-info style-scope"
- >
- <div
- aria-label="qwer"
- class="contentText gr-diff left style-scope"
- data-side="left"
- >
- <gr-diff-text></gr-diff-text>
- </div>
- <div class="thread-group" data-side="left">
- <slot name="left-1"> </slot>
- </div>
- </td>
- <td class="gr-diff lineNum right style-scope" data-value="1">
- <button
- class="gr-diff lineNumButton right style-scope"
- data-value="1"
- tabindex="-1"
- >
- 1
- </button>
- </td>
- <td
- class="both content gr-diff no-intraline-info right style-scope"
- >
- <div
- aria-label="qwer"
- class="contentText gr-diff right style-scope"
- data-side="right"
- >
- <gr-diff-text></gr-diff-text>
- </div>
- <div class="thread-group" data-side="right">
- <slot name="right-1"> </slot>
- </div>
- </td>
- </tr>
- <tr
- class="diff-row gr-diff side-by-side style-scope"
- left-type="both"
- right-type="both"
- tabindex="-1"
- >
- <td class="blame gr-diff style-scope" data-line-number="1"></td>
- <td class="gr-diff left lineNum style-scope" data-value="1">
- <button
- class="gr-diff left lineNumButton style-scope"
- data-value="1"
- tabindex="-1"
- >
- 1
- </button>
- </td>
- <td
- class="both content gr-diff left no-intraline-info style-scope"
- >
- <div
- aria-label="zxcv"
- class="contentText gr-diff left style-scope"
- data-side="left"
- >
- <gr-diff-text></gr-diff-text>
- </div>
- <div class="thread-group" data-side="left">
- <slot name="left-1"> </slot>
- </div>
- </td>
- <td class="gr-diff lineNum right style-scope" data-value="1">
- <button
- class="gr-diff lineNumButton right style-scope"
- data-value="1"
- tabindex="-1"
- >
- 1
- </button>
- </td>
- <td
- class="both content gr-diff no-intraline-info right style-scope"
- >
- <div
- aria-label="zxcv"
- class="contentText gr-diff right style-scope"
- data-side="right"
- >
- <gr-diff-text></gr-diff-text>
- </div>
- <div class="thread-group" data-side="right">
- <slot name="right-1"> </slot>
- </div>
- </td>
- </tr>
- </tbody>
- </table>
- </gr-diff-section>
- `);
- });
-});
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text.ts
deleted file mode 100644
index bb37c43..0000000
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text.ts
+++ /dev/null
@@ -1,138 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {LitElement, html, TemplateResult} from 'lit';
-import {customElement, property} from 'lit/decorators';
-import {diffClasses} from '../gr-diff/gr-diff-utils';
-
-const SURROGATE_PAIR = /[\uD800-\uDBFF][\uDC00-\uDFFF]/;
-
-const TAB = '\t';
-
-@customElement('gr-diff-text')
-export class GrDiffText extends LitElement {
- /**
- * The browser API for handling selection does not (yet) work for selection
- * across multiple shadow DOM elements. So we are rendering gr-diff components
- * into the light DOM instead of the shadow DOM by overriding this method,
- * which was the recommended workaround by the lit team.
- * See also https://github.com/WICG/webcomponents/issues/79.
- */
- override createRenderRoot() {
- return this;
- }
-
- @property({type: String})
- text = '';
-
- @property({type: Boolean})
- isResponsive = false;
-
- @property({type: Number})
- tabSize = 2;
-
- @property({type: Number})
- lineLimit = 80;
-
- /** Temporary state while rendering. */
- private textOffset = 0;
-
- /** Temporary state while rendering. */
- private columnPos = 0;
-
- /** Temporary state while rendering. */
- private pieces: (string | TemplateResult)[] = [];
-
- /** Split up the string into tabs, surrogate pairs and regular segments. */
- override render() {
- this.textOffset = 0;
- this.columnPos = 0;
- this.pieces = [];
- const splitByTab = this.text.split('\t');
- for (let i = 0; i < splitByTab.length; i++) {
- const splitBySurrogate = splitByTab[i].split(SURROGATE_PAIR);
- for (let j = 0; j < splitBySurrogate.length; j++) {
- this.renderSegment(splitBySurrogate[j]);
- if (j < splitBySurrogate.length - 1) {
- this.renderSurrogatePair();
- }
- }
- if (i < splitByTab.length - 1) {
- this.renderTab();
- }
- }
- if (this.textOffset !== this.text.length) throw new Error('unfinished');
- return this.pieces;
- }
-
- /** Render regular characters, but insert line breaks appropriately. */
- private renderSegment(segment: string) {
- let segmentOffset = 0;
- while (segmentOffset < segment.length) {
- const newOffset = Math.min(
- segment.length,
- segmentOffset + this.lineLimit - this.columnPos
- );
- this.renderString(segment.substring(segmentOffset, newOffset));
- segmentOffset = newOffset;
- if (segmentOffset < segment.length && this.columnPos === this.lineLimit) {
- this.renderLineBreak();
- }
- }
- }
-
- /** Render regular characters. */
- private renderString(s: string) {
- if (s.length === 0) return;
- this.pieces.push(s);
- this.textOffset += s.length;
- this.columnPos += s.length;
- if (this.columnPos > this.lineLimit) throw new Error('over line limit');
- }
-
- /** Render a tab character. */
- private renderTab() {
- let tabSize = this.tabSize - (this.columnPos % this.tabSize);
- if (this.columnPos + tabSize > this.lineLimit) {
- this.renderLineBreak();
- tabSize = this.tabSize;
- }
- const piece = html`<span
- class=${diffClasses('tab')}
- style="tab-size: ${tabSize}; -moz-tab-size: ${tabSize};"
- >${TAB}</span
- >`;
- this.pieces.push(piece);
- this.textOffset += 1;
- this.columnPos += tabSize;
- }
-
- /** Render a surrogate pair: string length is 2, but is just 1 char. */
- private renderSurrogatePair() {
- if (this.columnPos === this.lineLimit) {
- this.renderLineBreak();
- }
- this.pieces.push(this.text.substring(this.textOffset, this.textOffset + 2));
- this.textOffset += 2;
- this.columnPos += 1;
- }
-
- /** Render a line break, don't advance text offset, reset col position. */
- private renderLineBreak() {
- if (this.isResponsive) {
- this.pieces.push(html`<wbr class=${diffClasses()}></wbr>`);
- } else {
- this.pieces.push(html`<span class=${diffClasses('br')}></span>`);
- }
- // this.textOffset += 0;
- this.columnPos = 0;
- }
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- 'gr-diff-text': GrDiffText;
- }
-}
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text_test.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text_test.ts
deleted file mode 100644
index 21c0936..0000000
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text_test.ts
+++ /dev/null
@@ -1,154 +0,0 @@
-/**
- * @license
- * Copyright 2022 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import '../../../test/common-test-setup-karma';
-import './gr-diff-text';
-import {GrDiffText} from './gr-diff-text';
-import {html} from 'lit';
-import {fixture} from '@open-wc/testing-helpers';
-
-const LINE_BREAK = '<span class="style-scope gr-diff br"></span>';
-
-const TAB = '<span class="" style=""></span>';
-
-const TAB_IGNORE = ['class', 'style'];
-
-suite('gr-diff-text test', () => {
- let element: GrDiffText;
-
- setup(async () => {
- element = await fixture<GrDiffText>(html`<gr-diff-text></gr-diff-text>`);
- await element.updateComplete;
- });
-
- const check = async (
- text: string,
- html: string,
- ignoreAttributes: string[] = []
- ) => {
- element.text = text;
- element.tabSize = 4;
- element.lineLimit = 10;
- await element.updateComplete;
- expect(element).lightDom.to.equal(html, {ignoreAttributes});
- };
-
- suite('lit rendering', () => {
- test('renderText newlines 1', () => {
- check('abcdef', 'abcdef');
- check('a'.repeat(20), `aaaaaaaaaa${LINE_BREAK}aaaaaaaaaa`);
- });
-
- test('renderText newlines 2', () => {
- check(
- '<span class="thumbsup">👍</span>',
- '<span clas' +
- LINE_BREAK +
- 's="thumbsu' +
- LINE_BREAK +
- 'p">👍</span' +
- LINE_BREAK +
- '>'
- );
- });
-
- test('renderText newlines 3', () => {
- check(
- '01234\t56789',
- '01234' + TAB + '56' + LINE_BREAK + '789',
- TAB_IGNORE
- );
- });
-
- test('renderText newlines 4', async () => {
- element.lineLimit = 20;
- await element.updateComplete;
- check(
- '👍'.repeat(58),
- '👍'.repeat(20) +
- LINE_BREAK +
- '👍'.repeat(20) +
- LINE_BREAK +
- '👍'.repeat(18)
- );
- });
-
- test('tab wrapper style', async () => {
- for (const size of [1, 3, 8, 55]) {
- element.tabSize = size;
- await element.updateComplete;
- check(
- '\t',
- /* HTML */ `
- <span
- class="style-scope gr-diff tab"
- style="tab-size: ${size}; -moz-tab-size: ${size};"
- >
- </span>
- `
- );
- }
- });
-
- test('tab wrapper insertion', () => {
- check('abc\tdef', 'abc' + TAB + 'def', TAB_IGNORE);
- });
-
- test('escaping HTML', async () => {
- element.lineLimit = 100;
- await element.updateComplete;
- check(
- '<script>alert("XSS");<' + '/script>',
- '<script>alert("XSS");</script>'
- );
- check('& < > " \' / `', '& < > " \' / `');
- });
-
- test('text length with tabs and unicode', async () => {
- async function expectTextLength(
- text: string,
- tabSize: number,
- expected: number
- ) {
- element.text = text;
- element.tabSize = tabSize;
- element.lineLimit = expected;
- await element.updateComplete;
- const result = element.innerHTML;
-
- // Must not contain a line break.
- assert.isNotOk(element.querySelector('span.br'));
-
- // Increasing the line limit by 1 should not change anything.
- element.lineLimit = expected + 1;
- await element.updateComplete;
- const resultPlusOne = element.innerHTML;
- assert.equal(resultPlusOne, result);
-
- // Increasing the line limit to infinity should not change anything.
- element.lineLimit = Infinity;
- await element.updateComplete;
- const resultInf = element.innerHTML;
- assert.equal(resultInf, result);
-
- // Decreasing the line limit by 1 should introduce a line break.
- element.lineLimit = expected + 1;
- await element.updateComplete;
- assert.isNotOk(element.querySelector('span.br'));
- }
- expectTextLength('12345', 4, 5);
- expectTextLength('\t\t12', 4, 10);
- expectTextLength('abc💢123', 4, 7);
- expectTextLength('abc\t', 8, 8);
- expectTextLength('abc\t\t', 10, 20);
- expectTextLength('', 10, 0);
- // 17 Thai combining chars.
- expectTextLength('ก้้้้้้้้้้้้้้้้', 4, 17);
- expectTextLength('abc\tde', 10, 12);
- expectTextLength('abc\tde\t', 10, 20);
- expectTextLength('\t\t\t\t\t', 20, 100);
- });
- });
-});
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-cursor/gr-diff-cursor.ts b/polygerrit-ui/app/embed/diff/gr-diff-cursor/gr-diff-cursor.ts
index 4ecdcb0..dfe8a15 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-cursor/gr-diff-cursor.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-cursor/gr-diff-cursor.ts
@@ -228,7 +228,6 @@
path?: string,
intentionalMove?: boolean
) {
- this._updateStops();
const row = this._findRowByNumberAndFile(number, side, path);
if (row) {
this.side = side;
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-selection/gr-diff-selection.ts b/polygerrit-ui/app/embed/diff/gr-diff-selection/gr-diff-selection.ts
index 22af7e3..2665ef0 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-selection/gr-diff-selection.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-selection/gr-diff-selection.ts
@@ -23,11 +23,7 @@
normalize,
NormalizedRange,
} from '../gr-diff-highlight/gr-range-normalizer';
-import {
- descendedFromClass,
- isElementTarget,
- querySelectorAll,
-} from '../../../utils/dom-util';
+import {descendedFromClass, querySelectorAll} from '../../../utils/dom-util';
import {customElement, property, observe} from '@polymer/decorators';
import {DiffInfo} from '../../../types/diff';
import {Side} from '../../../constants/constants';
@@ -113,9 +109,8 @@
}
_handleDown(e: Event) {
- const target = e.composedPath()[0];
- if (!isElementTarget(target)) return;
-
+ const target = e.target;
+ if (!(target instanceof Element)) return;
// Handle the down event on comment thread in Polymer 2
const handled = this._handleDownOnRangeComment(target);
if (handled) return;
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-line.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-line.ts
index 8593e1b..2927101 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-line.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-line.ts
@@ -19,7 +19,6 @@
GrDiffLine as GrDiffLineApi,
GrDiffLineType,
LineNumber,
- Side,
} from '../../../api/diff';
export {GrDiffLineType, LineNumber};
@@ -39,10 +38,6 @@
text = '';
- lineNumber(side: Side) {
- return side === Side.LEFT ? this.beforeNumber : this.afterNumber;
- }
-
// TODO(TS): remove this properties
static readonly Type = GrDiffLineType;
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils.ts
index a12867f..182d48e 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils.ts
@@ -45,20 +45,12 @@
* Graphemes: http://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries
* A proposed JS API: https://github.com/tc39/proposal-intl-segmenter
*/
-export const REGEX_TAB_OR_SURROGATE_PAIR = /\t|[\uD800-\uDBFF][\uDC00-\uDFFF]/;
+const REGEX_TAB_OR_SURROGATE_PAIR = /\t|[\uD800-\uDBFF][\uDC00-\uDFFF]/;
// If any line of the diff is more than the character limit, then disable
// syntax highlighting for the entire file.
export const SYNTAX_MAX_LINE_LENGTH = 500;
-export function countLines(diff?: DiffInfo, side?: Side) {
- if (!diff?.content || !side) return 0;
- return diff.content.reduce((sum, chunk) => {
- const sideChunk = side === Side.LEFT ? chunk.a : chunk.b;
- return sum + (sideChunk?.length ?? chunk.ab?.length ?? chunk.skip ?? 0);
- }, 0);
-}
-
export function getResponsiveMode(
prefs: DiffPreferencesInfo,
renderPrefs?: RenderPreferences
@@ -73,7 +65,7 @@
return 'NONE';
}
-export function isResponsive(responsiveMode?: DiffResponsiveMode) {
+export function isResponsive(responsiveMode: DiffResponsiveMode) {
return (
responsiveMode === 'FULL_RESPONSIVE' || responsiveMode === 'SHRINK_ONLY'
);
@@ -124,12 +116,7 @@
return null;
}
}
- node =
- (node as Element).assignedSlot ??
- (node as ShadowRoot).host ??
- node.previousSibling ??
- node.parentNode ??
- undefined;
+ node = node.previousSibling ?? node.parentElement ?? undefined;
}
return null;
}
@@ -208,19 +195,6 @@
}
/**
- * Simple helper method for creating element classes in the context of
- * gr-diff.
- *
- * We are adding 'style-scope', 'gr-diff' classes for compatibility with
- * Shady DOM. TODO: Is that still required??
- *
- * Otherwise this is just a super simple convenience function.
- */
-export function diffClasses(...additionalClasses: string[]) {
- return ['style-scope', 'gr-diff', ...additionalClasses].join(' ');
-}
-
-/**
* Simple helper method for creating elements in the context of gr-diff.
*
* We are adding 'style-scope', 'gr-diff' classes for compatibility with
@@ -281,8 +255,6 @@
}
/**
- * Deprecated: Lit based rendering uses the textToPieces() function above.
- *
* Returns a 'div' element containing the supplied |text| as its innerText,
* with '\t' characters expanded to a width determined by |tabSize|, and the
* text wrapped at column |lineLimit|, which may be Infinity if no wrapping is
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils_test.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils_test.ts
index 793b0ef..600913e 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils_test.ts
@@ -4,171 +4,150 @@
* SPDX-License-Identifier: Apache-2.0
*/
import '../../../test/common-test-setup-karma';
-import {
- createElementDiff,
- formatText,
- createTabWrapper,
- diffClasses,
-} from './gr-diff-utils';
+import {createElementDiff, formatText, createTabWrapper} from './gr-diff-utils';
const LINE_BREAK_HTML = '<span class="style-scope gr-diff br"></span>';
suite('gr-diff-utils tests', () => {
- suite('legacy rendering', () => {
- test('createElementDiff classStr applies all classes', () => {
- const node = createElementDiff('div', 'test classes');
- assert.isTrue(node.classList.contains('gr-diff'));
- assert.isTrue(node.classList.contains('test'));
- assert.isTrue(node.classList.contains('classes'));
- });
-
- test('formatText newlines 1', () => {
- let text = 'abcdef';
-
- assert.equal(formatText(text, 'NONE', 4, 10).innerHTML, text);
- text = 'a'.repeat(20);
- assert.equal(
- formatText(text, 'NONE', 4, 10).innerHTML,
- 'a'.repeat(10) + LINE_BREAK_HTML + 'a'.repeat(10)
- );
- });
-
- test('formatText newlines 2', () => {
- const text = '<span class="thumbsup">👍</span>';
- assert.equal(
- formatText(text, 'NONE', 4, 10).innerHTML,
- '<span clas' +
- LINE_BREAK_HTML +
- 's="thumbsu' +
- LINE_BREAK_HTML +
- 'p">👍</span' +
- LINE_BREAK_HTML +
- '>'
- );
- });
-
- test('formatText newlines 3', () => {
- const text = '01234\t56789';
- assert.equal(
- formatText(text, 'NONE', 4, 10).innerHTML,
- '01234' + createTabWrapper(3).outerHTML + '56' + LINE_BREAK_HTML + '789'
- );
- });
-
- test('formatText newlines 4', () => {
- const text = '👍'.repeat(58);
- assert.equal(
- formatText(text, 'NONE', 4, 20).innerHTML,
- '👍'.repeat(20) +
- LINE_BREAK_HTML +
- '👍'.repeat(20) +
- LINE_BREAK_HTML +
- '👍'.repeat(18)
- );
- });
-
- test('tab wrapper style', () => {
- const pattern = new RegExp(
- '^<span class="style-scope gr-diff tab" ' +
- 'style="((?:-moz-)?tab-size: (\\d+);.?)+">\\t<\\/span>$'
- );
-
- for (const size of [1, 3, 8, 55]) {
- const html = createTabWrapper(size).outerHTML;
- expect(html).to.match(pattern);
- assert.equal(html.match(pattern)?.[2], size.toString());
- }
- });
-
- test('tab wrapper insertion', () => {
- const html = 'abc\tdef';
- const tabSize = 8;
- const wrapper = createTabWrapper(tabSize - 3);
- assert.ok(wrapper);
- assert.equal(wrapper.innerText, '\t');
- assert.equal(
- formatText(html, 'NONE', tabSize, Infinity).innerHTML,
- 'abc' + wrapper.outerHTML + 'def'
- );
- });
-
- test('escaping HTML', () => {
- let input = '<script>alert("XSS");<' + '/script>';
- let expected = '<script>alert("XSS");</script>';
-
- let result = formatText(
- input,
- 'NONE',
- 1,
- Number.POSITIVE_INFINITY
- ).innerHTML;
- assert.equal(result, expected);
-
- input = '& < > " \' / `';
- expected = '& < > " \' / `';
- result = formatText(input, 'NONE', 1, Number.POSITIVE_INFINITY).innerHTML;
- assert.equal(result, expected);
- });
-
- test('text length with tabs and unicode', () => {
- function expectTextLength(
- text: string,
- tabSize: number,
- expected: number
- ) {
- // Formatting to |expected| columns should not introduce line breaks.
- const result = formatText(text, 'NONE', tabSize, expected);
- assert.isNotOk(
- result.querySelector('.contentText > .br'),
- ' Expected the result of: \n' +
- ` _formatText(${text}', 'NONE', ${tabSize}, ${expected})\n` +
- ' to not contain a br. But the actual result HTML was:\n' +
- ` '${result.innerHTML}'\nwhereupon`
- );
-
- // Increasing the line limit should produce the same markup.
- assert.equal(
- formatText(text, 'NONE', tabSize, Infinity).innerHTML,
- result.innerHTML
- );
- assert.equal(
- formatText(text, 'NONE', tabSize, expected + 1).innerHTML,
- result.innerHTML
- );
-
- // Decreasing the line limit should introduce line breaks.
- if (expected > 0) {
- const tooSmall = formatText(text, 'NONE', tabSize, expected - 1);
- assert.isOk(
- tooSmall.querySelector('.contentText > .br'),
- ' Expected the result of: \n' +
- ` _formatText(${text}', ${tabSize}, ${expected - 1})\n` +
- ' to contain a br. But the actual result HTML was:\n' +
- ` '${tooSmall.innerHTML}'\nwhereupon`
- );
- }
- }
- expectTextLength('12345', 4, 5);
- expectTextLength('\t\t12', 4, 10);
- expectTextLength('abc💢123', 4, 7);
- expectTextLength('abc\t', 8, 8);
- expectTextLength('abc\t\t', 10, 20);
- expectTextLength('', 10, 0);
- // 17 Thai combining chars.
- expectTextLength('ก้้้้้้้้้้้้้้้้', 4, 17);
- expectTextLength('abc\tde', 10, 12);
- expectTextLength('abc\tde\t', 10, 20);
- expectTextLength('\t\t\t\t\t', 20, 100);
- });
+ test('createElementDiff classStr applies all classes', () => {
+ const node = createElementDiff('div', 'test classes');
+ assert.isTrue(node.classList.contains('gr-diff'));
+ assert.isTrue(node.classList.contains('test'));
+ assert.isTrue(node.classList.contains('classes'));
});
- suite('lit rendering', () => {
- test('diffClasses', () => {
- const c = diffClasses('div', 'test classes').split(' ');
- assert.include(c, 'gr-diff');
- assert.include(c, 'style-scope');
- assert.include(c, 'test');
- assert.include(c, 'classes');
- });
+ test('formatText newlines 1', () => {
+ let text = 'abcdef';
+
+ assert.equal(formatText(text, 'NONE', 4, 10).innerHTML, text);
+ text = 'a'.repeat(20);
+ assert.equal(
+ formatText(text, 'NONE', 4, 10).innerHTML,
+ 'a'.repeat(10) + LINE_BREAK_HTML + 'a'.repeat(10)
+ );
+ });
+
+ test('formatText newlines 2', () => {
+ const text = '<span class="thumbsup">👍</span>';
+ assert.equal(
+ formatText(text, 'NONE', 4, 10).innerHTML,
+ '<span clas' +
+ LINE_BREAK_HTML +
+ 's="thumbsu' +
+ LINE_BREAK_HTML +
+ 'p">👍</span' +
+ LINE_BREAK_HTML +
+ '>'
+ );
+ });
+
+ test('formatText newlines 3', () => {
+ const text = '01234\t56789';
+ assert.equal(
+ formatText(text, 'NONE', 4, 10).innerHTML,
+ '01234' + createTabWrapper(3).outerHTML + '56' + LINE_BREAK_HTML + '789'
+ );
+ });
+
+ test('formatText newlines 4', () => {
+ const text = '👍'.repeat(58);
+ assert.equal(
+ formatText(text, 'NONE', 4, 20).innerHTML,
+ '👍'.repeat(20) +
+ LINE_BREAK_HTML +
+ '👍'.repeat(20) +
+ LINE_BREAK_HTML +
+ '👍'.repeat(18)
+ );
+ });
+
+ test('tab wrapper style', () => {
+ const pattern = new RegExp(
+ '^<span class="style-scope gr-diff tab" ' +
+ 'style="((?:-moz-)?tab-size: (\\d+);.?)+">\\t<\\/span>$'
+ );
+
+ for (const size of [1, 3, 8, 55]) {
+ const html = createTabWrapper(size).outerHTML;
+ expect(html).to.match(pattern);
+ assert.equal(html.match(pattern)?.[2], size.toString());
+ }
+ });
+
+ test('tab wrapper insertion', () => {
+ const html = 'abc\tdef';
+ const tabSize = 8;
+ const wrapper = createTabWrapper(tabSize - 3);
+ assert.ok(wrapper);
+ assert.equal(wrapper.innerText, '\t');
+ assert.equal(
+ formatText(html, 'NONE', tabSize, Infinity).innerHTML,
+ 'abc' + wrapper.outerHTML + 'def'
+ );
+ });
+
+ test('escaping HTML', () => {
+ let input = '<script>alert("XSS");<' + '/script>';
+ let expected = '<script>alert("XSS");</script>';
+
+ let result = formatText(
+ input,
+ 'NONE',
+ 1,
+ Number.POSITIVE_INFINITY
+ ).innerHTML;
+ assert.equal(result, expected);
+
+ input = '& < > " \' / `';
+ expected = '& < > " \' / `';
+ result = formatText(input, 'NONE', 1, Number.POSITIVE_INFINITY).innerHTML;
+ assert.equal(result, expected);
+ });
+
+ test('text length with tabs and unicode', () => {
+ function expectTextLength(text: string, tabSize: number, expected: number) {
+ // Formatting to |expected| columns should not introduce line breaks.
+ const result = formatText(text, 'NONE', tabSize, expected);
+ assert.isNotOk(
+ result.querySelector('.contentText > .br'),
+ ' Expected the result of: \n' +
+ ` _formatText(${text}', 'NONE', ${tabSize}, ${expected})\n` +
+ ' to not contain a br. But the actual result HTML was:\n' +
+ ` '${result.innerHTML}'\nwhereupon`
+ );
+
+ // Increasing the line limit should produce the same markup.
+ assert.equal(
+ formatText(text, 'NONE', tabSize, Infinity).innerHTML,
+ result.innerHTML
+ );
+ assert.equal(
+ formatText(text, 'NONE', tabSize, expected + 1).innerHTML,
+ result.innerHTML
+ );
+
+ // Decreasing the line limit should introduce line breaks.
+ if (expected > 0) {
+ const tooSmall = formatText(text, 'NONE', tabSize, expected - 1);
+ assert.isOk(
+ tooSmall.querySelector('.contentText > .br'),
+ ' Expected the result of: \n' +
+ ` _formatText(${text}', ${tabSize}, ${expected - 1})\n` +
+ ' to contain a br. But the actual result HTML was:\n' +
+ ` '${tooSmall.innerHTML}'\nwhereupon`
+ );
+ }
+ }
+ expectTextLength('12345', 4, 5);
+ expectTextLength('\t\t12', 4, 10);
+ expectTextLength('abc💢123', 4, 7);
+ expectTextLength('abc\t', 8, 8);
+ expectTextLength('abc\t\t', 10, 20);
+ expectTextLength('', 10, 0);
+ // 17 Thai combining chars.
+ expectTextLength('ก้้้้้้้้้้้้้้้้', 4, 17);
+ expectTextLength('abc\tde', 10, 12);
+ expectTextLength('abc\tde\t', 10, 20);
+ expectTextLength('\t\t\t\t\t', 20, 100);
});
});
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
index f7e40f9..a38ec91 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
@@ -24,7 +24,7 @@
import '../gr-ranged-comment-themes/gr-ranged-comment-theme';
import '../gr-ranged-comment-hint/gr-ranged-comment-hint';
import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {dom} from '@polymer/polymer/lib/legacy/polymer.dom';
+import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
import {htmlTemplate} from './gr-diff_html';
import {LineNumber} from './gr-diff-line';
import {
@@ -77,7 +77,7 @@
GrDiff as GrDiffApi,
DisplayLine,
} from '../../../api/diff';
-import {isElementTarget, isSafari, toggleClass} from '../../../utils/dom-util';
+import {isSafari, toggleClass} from '../../../utils/dom-util';
import {assertIsDefined} from '../../../utils/common-util';
import {debounce, DelayedTask} from '../../../utils/async-util';
@@ -530,8 +530,7 @@
}
_handleTap(e: CustomEvent) {
- const el = e.composedPath()[0];
- if (!isElementTarget(el)) return;
+ const el = (dom(e) as EventApi).localTarget as Element;
if (
el.getAttribute('data-value') !== 'LOST' &&
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_html.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_html.ts
index c2e5550..e05e85a 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_html.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_html.ts
@@ -670,12 +670,6 @@
.token-highlight {
background-color: var(--token-highlighting-color, #fffd54);
}
-
- gr-diff-section,
- gr-context-controls-section,
- gr-diff-row {
- display: contents;
- }
</style>
<style include="gr-syntax-theme">
/* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
diff --git a/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider.ts b/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider.ts
index a74adf6..78cff25 100644
--- a/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider.ts
+++ b/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider.ts
@@ -25,10 +25,10 @@
isReviewerGroupSuggestion,
NumericChangeId,
ServerInfo,
- SuggestedReviewerInfo,
Suggestion,
} from '../../types/common';
import {assertNever} from '../../utils/common-util';
+import {AutocompleteSuggestion} from '../../elements/shared/gr-autocomplete/gr-autocomplete';
// TODO(TS): enum name doesn't follow typescript style guid rules
// Rename it
@@ -44,15 +44,10 @@
type ApiCallCallback = (input: string) => Promise<Suggestion[] | void>;
-export interface SuggestionItem {
- name: string;
- value: SuggestedReviewerInfo;
-}
-
export interface ReviewerSuggestionsProvider {
init(): void;
getSuggestions(input: string): Promise<Suggestion[]>;
- makeSuggestionItem(suggestion: Suggestion): SuggestionItem;
+ makeSuggestionItem(suggestion: Suggestion): AutocompleteSuggestion;
}
export class GrReviewerSuggestionsProvider
@@ -120,12 +115,15 @@
return this._apiCall(input).then(reviewers => reviewers || []);
}
- makeSuggestionItem(suggestion: Suggestion): SuggestionItem {
+ // this can be retyped to AutocompleteSuggestion<SuggestedReviewerInfo> but
+ // this would need to change generics of gr-autocomplete.
+ makeSuggestionItem(suggestion: Suggestion): AutocompleteSuggestion {
if (isReviewerAccountSuggestion(suggestion)) {
// Reviewer is an account suggestion from getChangeSuggestedReviewers.
return {
name: getAccountDisplayName(this.config, suggestion.account),
- value: suggestion,
+ // TODO(TS) this is temporary hack to avoid cascade of ts issues
+ value: suggestion as unknown as string,
};
}
@@ -133,7 +131,8 @@
// Reviewer is a group suggestion from getChangeSuggestedReviewers.
return {
name: getGroupDisplayName(suggestion.group),
- value: suggestion,
+ // TODO(TS) this is temporary hack to avoid cascade of ts issues
+ value: suggestion as unknown as string,
};
}
@@ -141,7 +140,8 @@
// Reviewer is an account suggestion from getSuggestedAccounts.
return {
name: getAccountDisplayName(this.config, suggestion),
- value: {account: suggestion, count: 1},
+ // TODO(TS) this is temporary hack to avoid cascade of ts issues
+ value: {account: suggestion, count: 1} as unknown as string,
};
}
assertNever(suggestion, 'Received an incorrect suggestion');