Merge "Add the functionality to include inline attachments to EmailSender."
diff --git a/Documentation/cmd-version.txt b/Documentation/cmd-version.txt
index cdfc779..3e53678 100644
--- a/Documentation/cmd-version.txt
+++ b/Documentation/cmd-version.txt
@@ -7,6 +7,8 @@
[verse]
--
_ssh_ -p <port> <host> _gerrit version_
+ [--verbose | -v]
+ [--json]
--
== DESCRIPTION
@@ -31,6 +33,14 @@
== SCRIPTING
This command is intended to be used in scripts.
+== OPTIONS
+--verbose::
+-v::
+ Verbose output, include also the NoteDb version and the version of each index.
+
+--json::
+ Json output format. Assumes verbose output.
+
== EXAMPLES
----
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index fe9b13c..a347d6c 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -30,6 +30,32 @@
"2.7"
----
+The `verbose` option can be used to provide a verbose version output as
+link:#version-info[VersionInfo].
+
+.Request
+----
+ GET /config/server/version?verbose HTTP/1.0
+----
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ {
+ "gerrit_version": "3.8.0",
+ "note_db_version": 185,
+ "change_index_version": 83,
+ "account_index_version": 13,
+ "project_index_version": 6,
+ "group_index_version": 10
+ }
+----
+
+
+
[[get-info]]
=== Get Server Info
--
@@ -1968,6 +1994,22 @@
details.
|=======================================
+[[version-info]]
+=== VersionInfo
+The `VersionInfo` entity contains information about the version of the
+Gerrit server.
+
+[options="header",cols="1,^1,5"]
+|=======================================
+|Field Name ||Description
+|`gerrit_version` ||Gerrit server version
+|`note_db_version` ||NoteDb version
+|`change_index_version` ||Change index version
+|`account_index_version` ||Account index version
+|`project_index_version` ||Project index version
+|`group_index_version` ||Group index version
+|=======================================
+
[[server-info]]
=== ServerInfo
The `ServerInfo` entity contains information about the configuration of
diff --git a/WORKSPACE b/WORKSPACE
index 29266cd..ad34969 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -112,8 +112,8 @@
http_archive(
name = "build_bazel_rules_nodejs",
- sha256 = "c29944ba9b0b430aadcaf3bf2570fece6fc5ebfb76df145c6cdad40d65c20811",
- urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/5.7.0/rules_nodejs-5.7.0.tar.gz"],
+ sha256 = "94070eff79305be05b7699207fbac5d2608054dd53e6109f7d00d923919ff45a",
+ urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/5.8.2/rules_nodejs-5.8.2.tar.gz"],
)
load("@build_bazel_rules_nodejs//:repositories.bzl", "build_bazel_rules_nodejs_dependencies")
diff --git a/java/com/google/gerrit/extensions/common/VersionInfo.java b/java/com/google/gerrit/extensions/common/VersionInfo.java
new file mode 100644
index 0000000..f18e1cc
--- /dev/null
+++ b/java/com/google/gerrit/extensions/common/VersionInfo.java
@@ -0,0 +1,41 @@
+// Copyright (C) 2023 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.
+
+package com.google.gerrit.extensions.common;
+
+public class VersionInfo {
+ public String gerritVersion;
+ public int noteDbVersion;
+ public int changeIndexVersion;
+ public int accountIndexVersion;
+ public int projectIndexVersion;
+ public int groupIndexVersion;
+
+ public String compact() {
+ return "gerrit version " + gerritVersion + "\n";
+ }
+
+ public String verbose() {
+ StringBuilder s = new StringBuilder();
+ s.append("gerrit version " + gerritVersion).append("\n");
+ s.append("NoteDb version " + noteDbVersion).append("\n");
+ s.append("Index versions\n");
+ String format = " %-8s %3d\n";
+ s.append(String.format(format, "changes", changeIndexVersion));
+ s.append(String.format(format, "accounts", accountIndexVersion));
+ s.append(String.format(format, "projects", projectIndexVersion));
+ s.append(String.format(format, "groups", groupIndexVersion));
+ return s.toString();
+ }
+}
diff --git a/java/com/google/gerrit/lucene/LuceneChangeIndex.java b/java/com/google/gerrit/lucene/LuceneChangeIndex.java
index 728606d..b94e840 100644
--- a/java/com/google/gerrit/lucene/LuceneChangeIndex.java
+++ b/java/com/google/gerrit/lucene/LuceneChangeIndex.java
@@ -113,7 +113,7 @@
private static final String CHANGE_FIELD = ChangeField.CHANGE_SPEC.getName();
static Term idTerm(ChangeData cd) {
- return idTerm(cd.getId());
+ return idTerm(cd.getVirtualId());
}
static Term idTerm(Change.Id id) {
diff --git a/java/com/google/gerrit/pgm/BUILD b/java/com/google/gerrit/pgm/BUILD
index df64bc7..8523e8a 100644
--- a/java/com/google/gerrit/pgm/BUILD
+++ b/java/com/google/gerrit/pgm/BUILD
@@ -20,6 +20,7 @@
"//java/com/google/gerrit/httpd/auth/restapi",
"//java/com/google/gerrit/index",
"//java/com/google/gerrit/index/project",
+ "//java/com/google/gerrit/json",
"//java/com/google/gerrit/launcher",
"//java/com/google/gerrit/lifecycle",
"//java/com/google/gerrit/lucene",
@@ -39,6 +40,7 @@
"//java/com/google/gerrit/server/restapi",
"//java/com/google/gerrit/server/schema",
"//java/com/google/gerrit/server/util/time",
+ "//java/com/google/gerrit/server/version",
"//java/com/google/gerrit/sshd",
"//lib:args4j",
"//lib:guava",
@@ -56,5 +58,6 @@
"//lib/prolog:cafeteria",
"//lib/prolog:compiler",
"//lib/prolog:runtime",
+ "@gson//jar",
],
)
diff --git a/java/com/google/gerrit/pgm/Version.java b/java/com/google/gerrit/pgm/Version.java
index 2392be5..27c52d3 100644
--- a/java/com/google/gerrit/pgm/Version.java
+++ b/java/com/google/gerrit/pgm/Version.java
@@ -14,18 +14,40 @@
package com.google.gerrit.pgm;
+import com.google.gerrit.extensions.common.VersionInfo;
+import com.google.gerrit.json.OutputFormat;
import com.google.gerrit.pgm.util.AbstractProgram;
+import com.google.gerrit.server.version.VersionInfoModule;
+import org.kohsuke.args4j.Option;
/** Display the version of Gerrit. */
public class Version extends AbstractProgram {
+
+ @Option(
+ name = "--verbose",
+ aliases = {"-v"},
+ usage = "verbose version info")
+ private boolean verbose;
+
+ @Option(name = "--json", usage = "json output format, assumes verbose output")
+ private boolean json;
+
@Override
public int run() throws Exception {
- final String v = com.google.gerrit.common.Version.getVersion();
- if (v == null) {
+ VersionInfo versionInfo = new VersionInfoModule().createVersionInfo();
+ if (versionInfo.gerritVersion == null) {
System.err.println("fatal: version unavailable");
return 1;
}
- System.out.println("gerrit version " + v);
+
+ if (json) {
+ System.out.println(OutputFormat.JSON.newGson().toJson(versionInfo));
+ } else if (verbose) {
+ System.out.print(versionInfo.verbose());
+ } else {
+ System.out.print(versionInfo.compact());
+ }
+
return 0;
}
}
diff --git a/java/com/google/gerrit/server/BUILD b/java/com/google/gerrit/server/BUILD
index d68f809..53b6c95 100644
--- a/java/com/google/gerrit/server/BUILD
+++ b/java/com/google/gerrit/server/BUILD
@@ -155,6 +155,7 @@
"//java/com/google/gerrit/server/logging",
"//java/com/google/gerrit/server/restapi",
"//java/com/google/gerrit/server/rules/prolog",
+ "//java/com/google/gerrit/server/version",
"//lib:blame-cache",
"//lib:guava",
"//lib:jgit",
diff --git a/java/com/google/gerrit/server/change/GetRelatedChangesUtil.java b/java/com/google/gerrit/server/change/GetRelatedChangesUtil.java
index 9a75469..834a623 100644
--- a/java/com/google/gerrit/server/change/GetRelatedChangesUtil.java
+++ b/java/com/google/gerrit/server/change/GetRelatedChangesUtil.java
@@ -99,8 +99,8 @@
}
List<ChangeData> cds =
- InternalChangeQuery.byBranchGroups(
- queryProvider, indexConfig, changeData.change().getDest(), groups);
+ InternalChangeQuery.byProjectGroups(
+ queryProvider, indexConfig, changeData.project(), groups);
if (cds.isEmpty()) {
return Collections.emptyList();
}
diff --git a/java/com/google/gerrit/server/config/GerritGlobalModule.java b/java/com/google/gerrit/server/config/GerritGlobalModule.java
index e9912f5..f32ba02 100644
--- a/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -224,6 +224,7 @@
import com.google.gerrit.server.validators.HashtagValidationListener;
import com.google.gerrit.server.validators.OutgoingEmailValidationListener;
import com.google.gerrit.server.validators.ProjectCreationValidationListener;
+import com.google.gerrit.server.version.VersionInfoModule;
import com.google.gitiles.blame.cache.BlameCache;
import com.google.gitiles.blame.cache.BlameCacheImpl;
import com.google.inject.Inject;
@@ -290,6 +291,7 @@
install(ThreadLocalRequestContext.module());
install(new ApprovalModule());
install(new MailSoySauceModule());
+ install(new VersionInfoModule());
factory(CapabilityCollection.Factory.class);
factory(ChangeData.AssistedFactory.class);
diff --git a/java/com/google/gerrit/server/config/GerritImportedServerIdsProvider.java b/java/com/google/gerrit/server/config/GerritImportedServerIdsProvider.java
index f3f7645..2a74833 100644
--- a/java/com/google/gerrit/server/config/GerritImportedServerIdsProvider.java
+++ b/java/com/google/gerrit/server/config/GerritImportedServerIdsProvider.java
@@ -14,24 +14,24 @@
package com.google.gerrit.server.config;
-import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableList;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.eclipse.jgit.lib.Config;
-public class GerritImportedServerIdsProvider implements Provider<ImmutableSet<String>> {
+public class GerritImportedServerIdsProvider implements Provider<ImmutableList<String>> {
public static final String SECTION = "gerrit";
public static final String KEY = "importedServerId";
- private final ImmutableSet<String> importedIds;
+ private final ImmutableList<String> importedIds;
@Inject
public GerritImportedServerIdsProvider(@GerritServerConfig Config cfg) {
- importedIds = ImmutableSet.copyOf(cfg.getStringList(SECTION, null, KEY));
+ importedIds = ImmutableList.copyOf(cfg.getStringList(SECTION, null, KEY));
}
@Override
- public ImmutableSet<String> get() {
+ public ImmutableList<String> get() {
return importedIds;
}
}
diff --git a/java/com/google/gerrit/server/events/EventFactory.java b/java/com/google/gerrit/server/events/EventFactory.java
index 14aadf47..678f4d0 100644
--- a/java/com/google/gerrit/server/events/EventFactory.java
+++ b/java/com/google/gerrit/server/events/EventFactory.java
@@ -294,8 +294,8 @@
// Find changes in the same related group as this patch set, having a patch
// set whose parent matches this patch set's revision.
for (ChangeData cd :
- InternalChangeQuery.byBranchGroups(
- queryProvider, indexConfig, change.getDest(), currentPs.groups())) {
+ InternalChangeQuery.byProjectGroups(
+ queryProvider, indexConfig, change.getProject(), currentPs.groups())) {
PATCH_SETS:
for (PatchSet ps : cd.patchSets()) {
RevCommit commit = rw.parseCommit(ps.commitId());
diff --git a/java/com/google/gerrit/server/index/change/ChangeField.java b/java/com/google/gerrit/server/index/change/ChangeField.java
index 1d5818a..7057ff7 100644
--- a/java/com/google/gerrit/server/index/change/ChangeField.java
+++ b/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -128,7 +128,7 @@
.required()
// The numeric change id is integer in string form
.size(10)
- .build(cd -> String.valueOf(cd.getId().get()));
+ .build(cd -> String.valueOf(cd.getVirtualId().get()));
public static final IndexedField<ChangeData, String>.SearchSpec NUMERIC_ID_STR_SPEC =
NUMERIC_ID_STR_FIELD.exact("legacy_id_str");
diff --git a/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java b/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
index 2edea26..5ffb5fb 100644
--- a/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
+++ b/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
@@ -17,7 +17,7 @@
import static java.util.Objects.requireNonNull;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.UsedAt;
import com.google.gerrit.entities.Change;
@@ -53,7 +53,7 @@
public final AllUsersName allUsers;
public final NoteDbMetrics metrics;
public final String serverId;
- public final ImmutableSet<String> importedServerIds;
+ public final ImmutableList<String> importedServerIds;
// Providers required to avoid dependency cycles.
@@ -68,7 +68,7 @@
NoteDbMetrics metrics,
Provider<ChangeNotesCache> cache,
@GerritServerId String serverId,
- @GerritImportedServerIds ImmutableSet<String> importedServerIds) {
+ @GerritImportedServerIds ImmutableList<String> importedServerIds) {
this.failOnLoadForTest = new AtomicBoolean();
this.repoManager = repoManager;
this.allUsers = allUsers;
diff --git a/java/com/google/gerrit/server/query/change/ChangeData.java b/java/com/google/gerrit/server/query/change/ChangeData.java
index 01c2708..80c8085 100644
--- a/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -74,6 +74,7 @@
import com.google.gerrit.server.change.MergeabilityCache;
import com.google.gerrit.server.change.PureRevert;
import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.GerritServerId;
import com.google.gerrit.server.config.TrackingFooters;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MergeUtilFactory;
@@ -262,15 +263,65 @@
* <p>Attempting to lazy load data will fail with NPEs. Callers may consider manually setting
* fields that can be set.
*
+ * @param project project name
* @param id change ID
+ * @param currentPatchSetId current patchset number
+ * @param commitId commit SHA1 of the current patchset
* @return instance for testing.
*/
public static ChangeData createForTest(
Project.NameKey project, Change.Id id, int currentPatchSetId, ObjectId commitId) {
+ return createForTest(project, id, currentPatchSetId, commitId, null, null, null);
+ }
+
+ /**
+ * Create an instance for testing only.
+ *
+ * <p>Attempting to lazy load data will fail with NPEs. Callers may consider manually setting
+ * fields that can be set.
+ *
+ * @param project project name
+ * @param id change ID
+ * @param currentPatchSetId current patchset number
+ * @param commitId commit SHA1 of the current patchset
+ * @param serverId Gerrit server id
+ * @param virtualIdAlgo algorithm for virtualising the Change number
+ * @param changeNotes notes associated with the Change
+ * @return instance for testing.
+ */
+ public static ChangeData createForTest(
+ Project.NameKey project,
+ Change.Id id,
+ int currentPatchSetId,
+ ObjectId commitId,
+ @Nullable String serverId,
+ @Nullable ChangeNumberVirtualIdAlgorithm virtualIdAlgo,
+ @Nullable ChangeNotes changeNotes) {
ChangeData cd =
new ChangeData(
- null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, project, id, null, null);
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ serverId,
+ virtualIdAlgo,
+ project,
+ id,
+ null,
+ changeNotes);
cd.currentPatchSet =
PatchSet.builder()
.id(PatchSet.id(id, currentPatchSetId))
@@ -363,6 +414,9 @@
private Optional<Instant> mergedOn;
private ImmutableSetMultimap<NameKey, RefState> refStates;
private ImmutableList<byte[]> refStatePatterns;
+ private String gerritServerId;
+ private String changeServerId;
+ private ChangeNumberVirtualIdAlgorithm virtualIdFunc;
@Inject
private ChangeData(
@@ -383,6 +437,8 @@
SubmitRequirementsEvaluator submitRequirementsEvaluator,
SubmitRequirementsUtil submitRequirementsUtil,
SubmitRuleEvaluator.Factory submitRuleEvaluatorFactory,
+ @GerritServerId String gerritServerId,
+ ChangeNumberVirtualIdAlgorithm virtualIdFunc,
@Assisted Project.NameKey project,
@Assisted Change.Id id,
@Assisted @Nullable Change change,
@@ -410,6 +466,10 @@
this.change = change;
this.notes = notes;
+
+ this.changeServerId = notes == null ? null : notes.getServerId();
+ this.gerritServerId = gerritServerId;
+ this.virtualIdFunc = virtualIdFunc;
}
/**
@@ -530,6 +590,14 @@
return legacyId;
}
+ public Change.Id getVirtualId() {
+ if (virtualIdFunc == null || changeServerId == null || changeServerId.equals(gerritServerId)) {
+ return legacyId;
+ }
+
+ return Change.id(virtualIdFunc.apply(changeServerId, legacyId.get()));
+ }
+
public Project.NameKey project() {
return project;
}
@@ -560,6 +628,7 @@
throw new StorageException("Unable to load change " + legacyId, e);
}
change = notes.getChange();
+ changeServerId = notes.getServerId();
setPatchSets(null);
return change;
}
diff --git a/java/com/google/gerrit/server/query/change/ChangeNumberBitmapMaskAlgorithm.java b/java/com/google/gerrit/server/query/change/ChangeNumberBitmapMaskAlgorithm.java
new file mode 100644
index 0000000..726a376
--- /dev/null
+++ b/java/com/google/gerrit/server/query/change/ChangeNumberBitmapMaskAlgorithm.java
@@ -0,0 +1,76 @@
+// Copyright (C) 2022 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.
+
+package com.google.gerrit.server.query.change;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.server.config.GerritImportedServerIds;
+import com.google.inject.Inject;
+import com.google.inject.ProvisionException;
+import com.google.inject.Singleton;
+
+/**
+ * Dictionary-based encoding algorithm for combining a serverId/legacyChangeNum into a virtual
+ * numeric id
+ *
+ * <p>TODO: To be reverted on master and stable-3.8
+ */
+@Singleton
+public class ChangeNumberBitmapMaskAlgorithm implements ChangeNumberVirtualIdAlgorithm {
+ /*
+ * Bit-wise masks for representing the Change's VirtualId as combination of ServerId + ChangeNum:
+ */
+ private static final int CHANGE_NUM_BIT_LEN = 28; // Allows up to 268M changes
+ private static final int LEGACY_ID_BIT_MASK = (1 << CHANGE_NUM_BIT_LEN) - 1;
+ private static final int SERVER_ID_BIT_LEN =
+ Integer.BYTES * 8 - CHANGE_NUM_BIT_LEN; // Allows up to 64 ServerIds
+
+ private final ImmutableMap<String, Integer> serverIdCodes;
+
+ @Inject
+ public ChangeNumberBitmapMaskAlgorithm(
+ @GerritImportedServerIds ImmutableList<String> importedServerIds) {
+ if (importedServerIds.size() >= 1 << SERVER_ID_BIT_LEN) {
+ throw new ProvisionException(
+ String.format(
+ "Too many imported GerritServerIds (%d) to fit into the Change virtual id",
+ importedServerIds.size()));
+ }
+ ImmutableMap.Builder<String, Integer> serverIdCodesBuilder = new ImmutableMap.Builder<>();
+ for (int i = 0; i < importedServerIds.size(); i++) {
+ serverIdCodesBuilder.put(importedServerIds.get(i), i + 1);
+ }
+
+ serverIdCodes = serverIdCodesBuilder.build();
+ }
+
+ @Override
+ public int apply(String changeServerId, int changeNum) {
+ if ((changeNum & LEGACY_ID_BIT_MASK) != changeNum) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Change number %d is too large to be converted into a virtual id", changeNum));
+ }
+
+ Integer encodedServerId = serverIdCodes.get(changeServerId);
+ if (encodedServerId == null) {
+ throw new IllegalArgumentException(
+ String.format("ServerId %s is not part of the GerritImportedServerIds", changeServerId));
+ }
+ int virtualId = (changeNum & LEGACY_ID_BIT_MASK) | (encodedServerId << CHANGE_NUM_BIT_LEN);
+
+ return virtualId;
+ }
+}
diff --git a/java/com/google/gerrit/server/query/change/ChangeNumberVirtualIdAlgorithm.java b/java/com/google/gerrit/server/query/change/ChangeNumberVirtualIdAlgorithm.java
new file mode 100644
index 0000000..ab21705
--- /dev/null
+++ b/java/com/google/gerrit/server/query/change/ChangeNumberVirtualIdAlgorithm.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2022 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.
+
+package com.google.gerrit.server.query.change;
+
+import com.google.inject.ImplementedBy;
+
+/**
+ * Algorithm for encoding a serverId/legacyChangeNum into a virtual numeric id
+ *
+ * <p>TODO: To be reverted on master and stable-3.8
+ */
+@ImplementedBy(ChangeNumberBitmapMaskAlgorithm.class)
+public interface ChangeNumberVirtualIdAlgorithm {
+
+ /**
+ * Convert a serverId/legacyChangeNum tuple into a virtual numeric id
+ *
+ * @param serverId Gerrit serverId
+ * @param legacyChangeNum legacy change number
+ * @return virtual id which combines serverId and legacyChangeNum together
+ */
+ int apply(String serverId, int legacyChangeNum);
+}
diff --git a/java/com/google/gerrit/server/query/change/InternalChangeQuery.java b/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
index 3edad69..62c070c 100644
--- a/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
+++ b/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
@@ -264,21 +264,21 @@
return query(ChangePredicates.submissionId(cs));
}
- private static Predicate<ChangeData> byBranchGroupsPredicate(
- IndexConfig indexConfig, BranchNameKey branchAndProject, Collection<String> groups) {
- int n = indexConfig.maxTerms() - 2;
+ private static Predicate<ChangeData> byProjectGroupsPredicate(
+ IndexConfig indexConfig, Project.NameKey project, Collection<String> groups) {
+ int n = indexConfig.maxTerms() - 1;
checkArgument(groups.size() <= n, "cannot exceed %s groups", n);
List<GroupPredicate> groupPredicates = new ArrayList<>(groups.size());
for (String g : groups) {
groupPredicates.add(new GroupPredicate(g));
}
- return and(project(branchAndProject.project()), ref(branchAndProject), or(groupPredicates));
+ return and(project(project), or(groupPredicates));
}
- public static ImmutableList<ChangeData> byBranchGroups(
+ public static ImmutableList<ChangeData> byProjectGroups(
Provider<InternalChangeQuery> queryProvider,
IndexConfig indexConfig,
- BranchNameKey branchAndProject,
+ Project.NameKey project,
Collection<String> groups) {
// These queries may be complex along multiple dimensions:
// * Many groups per change, if there are very many patch sets. This requires partitioning the
@@ -289,17 +289,16 @@
// InternalChangeQuery is single-use.
Supplier<InternalChangeQuery> querySupplier = () -> queryProvider.get().enforceVisibility(true);
- int batchSize = indexConfig.maxTerms() - 2;
+ int batchSize = indexConfig.maxTerms() - 1;
if (groups.size() <= batchSize) {
return queryExhaustively(
- querySupplier, byBranchGroupsPredicate(indexConfig, branchAndProject, groups));
+ querySupplier, byProjectGroupsPredicate(indexConfig, project, groups));
}
Set<Change.Id> seen = new HashSet<>();
ImmutableList.Builder<ChangeData> result = ImmutableList.builder();
for (List<String> part : Iterables.partition(groups, batchSize)) {
for (ChangeData cd :
- queryExhaustively(
- querySupplier, byBranchGroupsPredicate(indexConfig, branchAndProject, part))) {
+ queryExhaustively(querySupplier, byProjectGroupsPredicate(indexConfig, project, part))) {
if (!seen.add(cd.getId())) {
result.add(cd);
}
diff --git a/java/com/google/gerrit/server/restapi/change/ChangeRestApiModule.java b/java/com/google/gerrit/server/restapi/change/ChangeRestApiModule.java
index 3e985c2..0f2aa3c 100644
--- a/java/com/google/gerrit/server/restapi/change/ChangeRestApiModule.java
+++ b/java/com/google/gerrit/server/restapi/change/ChangeRestApiModule.java
@@ -92,6 +92,7 @@
post(ATTENTION_SET_ENTRY_KIND, "delete").to(RemoveFromAttentionSet.class);
postOnCollection(ATTENTION_SET_ENTRY_KIND).to(AddToAttentionSet.class);
get(CHANGE_KIND, "hashtags").to(GetHashtags.class);
+ get(CHANGE_KIND, "custom_keyed_values").to(GetCustomKeyedValues.class);
get(CHANGE_KIND, "comments").to(ListChangeComments.class);
get(CHANGE_KIND, "robotcomments").to(ListChangeRobotComments.class);
get(CHANGE_KIND, "drafts").to(ListChangeDrafts.class);
@@ -103,6 +104,7 @@
delete(CHANGE_KIND).to(DeleteChange.class);
post(CHANGE_KIND, "abandon").to(Abandon.class);
post(CHANGE_KIND, "hashtags").to(PostHashtags.class);
+ post(CHANGE_KIND, "custom_keyed_values").to(PostCustomKeyedValues.class);
post(CHANGE_KIND, "restore").to(Restore.class);
post(CHANGE_KIND, "revert").to(Revert.class);
post(CHANGE_KIND, "revert_submission").to(RevertSubmission.class);
diff --git a/java/com/google/gerrit/server/restapi/config/GetVersion.java b/java/com/google/gerrit/server/restapi/config/GetVersion.java
index ee206d6..6b205e4 100644
--- a/java/com/google/gerrit/server/restapi/config/GetVersion.java
+++ b/java/com/google/gerrit/server/restapi/config/GetVersion.java
@@ -14,23 +14,40 @@
package com.google.gerrit.server.restapi.config;
-import com.google.gerrit.common.Version;
+import com.google.gerrit.extensions.common.VersionInfo;
import com.google.gerrit.extensions.restapi.CacheControl;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.config.ConfigResource;
-import com.google.inject.Singleton;
+import com.google.inject.Inject;
import java.util.concurrent.TimeUnit;
+import org.kohsuke.args4j.Option;
-@Singleton
public class GetVersion implements RestReadView<ConfigResource> {
+
+ @Option(
+ name = "--verbose",
+ aliases = {"-v"},
+ usage = "verbose version info")
+ boolean verbose;
+
+ private final VersionInfo versionInfo;
+
+ @Inject
+ public GetVersion(VersionInfo versionInfo) {
+ this.versionInfo = versionInfo;
+ }
+
@Override
- public Response<String> apply(ConfigResource resource) throws ResourceNotFoundException {
- String version = Version.getVersion();
- if (version == null) {
+ public Response<?> apply(ConfigResource resource) throws ResourceNotFoundException {
+ if (versionInfo.gerritVersion == null) {
throw new ResourceNotFoundException();
}
- return Response.ok(version).caching(CacheControl.PRIVATE(30, TimeUnit.SECONDS));
+ if (verbose) {
+ return Response.ok(versionInfo).caching(CacheControl.PRIVATE(30, TimeUnit.SECONDS));
+ }
+ return Response.ok(versionInfo.gerritVersion)
+ .caching(CacheControl.PRIVATE(30, TimeUnit.SECONDS));
}
}
diff --git a/java/com/google/gerrit/server/schema/SchemaModule.java b/java/com/google/gerrit/server/schema/SchemaModule.java
index e0e64a3..9593522 100644
--- a/java/com/google/gerrit/server/schema/SchemaModule.java
+++ b/java/com/google/gerrit/server/schema/SchemaModule.java
@@ -16,7 +16,7 @@
import static com.google.inject.Scopes.SINGLETON;
-import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableList;
import com.google.gerrit.extensions.config.FactoryModule;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.GerritPersonIdentProvider;
@@ -55,7 +55,7 @@
.toProvider(GerritServerIdProvider.class)
.in(SINGLETON);
- bind(new TypeLiteral<ImmutableSet<String>>() {})
+ bind(new TypeLiteral<ImmutableList<String>>() {})
.annotatedWith(GerritImportedServerIds.class)
.toProvider(GerritImportedServerIdsProvider.class)
.in(SINGLETON);
diff --git a/java/com/google/gerrit/server/version/BUILD b/java/com/google/gerrit/server/version/BUILD
new file mode 100644
index 0000000..c7f659c
--- /dev/null
+++ b/java/com/google/gerrit/server/version/BUILD
@@ -0,0 +1,17 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
+java_library(
+ name = "version",
+ srcs = glob(
+ ["**/*.java"],
+ ),
+ visibility = ["//visibility:public"],
+ deps = [
+ "//java/com/google/gerrit/common:server",
+ "//java/com/google/gerrit/extensions:api",
+ "//java/com/google/gerrit/index/project",
+ "//java/com/google/gerrit/server",
+ "//java/com/google/gerrit/server/schema",
+ "@guice-library//jar",
+ ],
+)
diff --git a/java/com/google/gerrit/server/version/VersionInfoModule.java b/java/com/google/gerrit/server/version/VersionInfoModule.java
new file mode 100644
index 0000000..e6dea71
--- /dev/null
+++ b/java/com/google/gerrit/server/version/VersionInfoModule.java
@@ -0,0 +1,41 @@
+// Copyright (C) 2023 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.
+
+package com.google.gerrit.server.version;
+
+import com.google.gerrit.common.Version;
+import com.google.gerrit.extensions.common.VersionInfo;
+import com.google.gerrit.index.project.ProjectSchemaDefinitions;
+import com.google.gerrit.server.index.account.AccountSchemaDefinitions;
+import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
+import com.google.gerrit.server.index.group.GroupSchemaDefinitions;
+import com.google.gerrit.server.schema.NoteDbSchemaVersions;
+import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
+import com.google.inject.Singleton;
+
+public class VersionInfoModule extends AbstractModule {
+ @Provides
+ @Singleton
+ public VersionInfo createVersionInfo() {
+ VersionInfo v = new VersionInfo();
+ v.gerritVersion = Version.getVersion();
+ v.noteDbVersion = NoteDbSchemaVersions.LATEST;
+ v.changeIndexVersion = ChangeSchemaDefinitions.INSTANCE.getLatest().getVersion();
+ v.accountIndexVersion = AccountSchemaDefinitions.INSTANCE.getLatest().getVersion();
+ v.projectIndexVersion = ProjectSchemaDefinitions.INSTANCE.getLatest().getVersion();
+ v.groupIndexVersion = GroupSchemaDefinitions.INSTANCE.getLatest().getVersion();
+ return v;
+ }
+}
diff --git a/java/com/google/gerrit/sshd/BUILD b/java/com/google/gerrit/sshd/BUILD
index af7078d..7a11131c 100644
--- a/java/com/google/gerrit/sshd/BUILD
+++ b/java/com/google/gerrit/sshd/BUILD
@@ -21,7 +21,9 @@
"//java/com/google/gerrit/server/ioutil",
"//java/com/google/gerrit/server/logging",
"//java/com/google/gerrit/server/restapi",
+ "//java/com/google/gerrit/server/schema",
"//java/com/google/gerrit/server/util/time",
+ "//java/com/google/gerrit/server/version",
"//java/com/google/gerrit/util/cli",
"//java/com/google/gerrit/util/logging",
"//lib:args4j",
diff --git a/java/com/google/gerrit/sshd/commands/VersionCommand.java b/java/com/google/gerrit/sshd/commands/VersionCommand.java
index f8771fb..c274b3d 100644
--- a/java/com/google/gerrit/sshd/commands/VersionCommand.java
+++ b/java/com/google/gerrit/sshd/commands/VersionCommand.java
@@ -16,21 +16,40 @@
import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
-import com.google.gerrit.common.Version;
+import com.google.gerrit.extensions.common.VersionInfo;
+import com.google.gerrit.json.OutputFormat;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
+import com.google.inject.Inject;
+import org.kohsuke.args4j.Option;
@CommandMetaData(name = "version", description = "Display gerrit version", runsAt = MASTER_OR_SLAVE)
final class VersionCommand extends SshCommand {
+ @Option(
+ name = "--verbose",
+ aliases = {"-v"},
+ usage = "verbose version info")
+ private boolean verbose;
+
+ @Option(name = "--json", usage = "json output format, assumes verbose output")
+ private boolean json;
+
+ @Inject private VersionInfo versionInfo;
+
@Override
protected void run() throws Failure {
enableGracefulStop();
- String v = Version.getVersion();
- if (v == null) {
+ if (versionInfo.gerritVersion == null) {
throw new Failure(1, "fatal: version unavailable");
}
- stdout.println("gerrit version " + v);
+ if (json) {
+ stdout.println(OutputFormat.JSON.newGson().toJson(versionInfo));
+ } else if (verbose) {
+ stdout.print(versionInfo.verbose());
+ } else {
+ stdout.print(versionInfo.compact());
+ }
}
}
diff --git a/java/com/google/gerrit/testing/InMemoryModule.java b/java/com/google/gerrit/testing/InMemoryModule.java
index e86fd09..52ac58a 100644
--- a/java/com/google/gerrit/testing/InMemoryModule.java
+++ b/java/com/google/gerrit/testing/InMemoryModule.java
@@ -19,7 +19,7 @@
import static com.google.inject.Scopes.SINGLETON;
import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.gerrit.acceptance.testsuite.group.GroupOperations;
import com.google.gerrit.acceptance.testsuite.group.GroupOperationsImpl;
@@ -324,9 +324,9 @@
@Provides
@Singleton
@GerritImportedServerIds
- public ImmutableSet<String> createImportedServerIds() {
- ImmutableSet<String> serverIds =
- ImmutableSet.copyOf(
+ public ImmutableList<String> createImportedServerIds() {
+ ImmutableList<String> serverIds =
+ ImmutableList.copyOf(
cfg.getStringList(
GerritServerIdProvider.SECTION, null, GerritImportedServerIdsProvider.KEY));
return serverIds;
diff --git a/javatests/com/google/gerrit/acceptance/rest/binding/ChangesRestApiBindingsIT.java b/javatests/com/google/gerrit/acceptance/rest/binding/ChangesRestApiBindingsIT.java
index 27bd6b9..2df820b 100644
--- a/javatests/com/google/gerrit/acceptance/rest/binding/ChangesRestApiBindingsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/binding/ChangesRestApiBindingsIT.java
@@ -60,6 +60,7 @@
RestCall.delete("/changes/%s/topic"),
RestCall.get("/changes/%s/in"),
RestCall.get("/changes/%s/hashtags"),
+ RestCall.get("/changes/%s/custom_keyed_values"),
RestCall.get("/changes/%s/comments"),
RestCall.get("/changes/%s/robotcomments"),
RestCall.get("/changes/%s/drafts"),
diff --git a/javatests/com/google/gerrit/acceptance/server/change/GetRelatedIT.java b/javatests/com/google/gerrit/acceptance/server/change/GetRelatedIT.java
index 3e03b2a..e15ed08 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/GetRelatedIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/GetRelatedIT.java
@@ -40,13 +40,11 @@
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.AccountGroup;
-import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.api.changes.GetRelatedOption;
import com.google.gerrit.extensions.api.changes.RelatedChangeAndCommitInfo;
import com.google.gerrit.extensions.api.changes.ReviewInput;
-import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.common.CommitInfo;
import com.google.gerrit.extensions.common.EditInfo;
import com.google.gerrit.index.IndexConfig;
@@ -71,8 +69,6 @@
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.transport.PushResult;
-import org.eclipse.jgit.transport.RemoteRefUpdate;
import org.junit.Test;
@NoHttpd
@@ -670,71 +666,6 @@
}
}
- @Test
- public void getRelatedLinearSameCommitPushedTwice() throws Exception {
- RevCommit base = projectOperations.project(project).getHead("master");
-
- // 1,1---2,1 on master
- PushOneCommit.Result r1 =
- createChange(
- testRepo,
- "master",
- "subject: 1",
- "a.txt",
- "1",
- /** topic= */
- null);
- RevCommit c1_1 = r1.getCommit();
- PatchSet.Id ps1_1 = r1.getPatchSetId();
-
- PushOneCommit.Result r2 =
- createChange(
- testRepo,
- "master",
- "subject: 2",
- "b.txt",
- "2",
- /** topic= */
- null);
- RevCommit c2_1 = r2.getCommit();
- PatchSet.Id ps2_1 = r2.getPatchSetId();
-
- // 3,1---4,1 on stable
- gApi.projects().name(project.get()).branch("stable").create(new BranchInput());
- testRepo.reset(c1_1);
- PushResult r3 = pushHead(testRepo, "refs/for/stable%base=" + base.getName());
- assertThat(r3.getRemoteUpdate("refs/for/stable%base=" + base.getName()).getStatus())
- .isEqualTo(RemoteRefUpdate.Status.OK);
- ChangeData change3 =
- Iterables.getOnlyElement(
- queryProvider
- .get()
- .byBranchCommit(BranchNameKey.create(project, "stable"), c1_1.getName()));
- assertThat(change3.currentPatchSet().commitId()).isEqualTo(c1_1);
- RevCommit c3_1 = c1_1;
- PatchSet.Id ps3_1 = change3.currentPatchSet().id();
-
- PushOneCommit.Result r4 =
- createChange(
- testRepo,
- "stable",
- "subject: 4",
- "d.txt",
- "4",
- /** topic= */
- null);
- RevCommit c4_1 = r4.getCommit();
- PatchSet.Id ps4_1 = r4.getPatchSetId();
-
- for (PatchSet.Id ps : ImmutableList.of(ps2_1, ps1_1)) {
- assertRelated(ps, changeAndCommit(ps2_1, c2_1, 1), changeAndCommit(ps1_1, c1_1, 1));
- }
-
- for (PatchSet.Id ps : ImmutableList.of(ps4_1, ps3_1)) {
- assertRelated(ps, changeAndCommit(ps4_1, c4_1, 1), changeAndCommit(ps3_1, c3_1, 1));
- }
- }
-
private static Correspondence<RelatedChangeAndCommitInfo, String>
getRelatedChangeToStatusCorrespondence() {
return Correspondence.transforming(
diff --git a/javatests/com/google/gerrit/server/index/change/ChangeFieldTest.java b/javatests/com/google/gerrit/server/index/change/ChangeFieldTest.java
index 35077db..59b354c 100644
--- a/javatests/com/google/gerrit/server/index/change/ChangeFieldTest.java
+++ b/javatests/com/google/gerrit/server/index/change/ChangeFieldTest.java
@@ -157,14 +157,16 @@
@Test
public void tolerateNullValuesForInsertion() {
Project.NameKey project = Project.nameKey("project");
- ChangeData cd = ChangeData.createForTest(project, Change.id(1), 1, ObjectId.zeroId());
+ ChangeData cd =
+ ChangeData.createForTest(project, Change.id(1), 1, ObjectId.zeroId(), null, null, null);
assertThat(ChangeField.ADDED_LINES_SPEC.setIfPossible(cd, new FakeStoredValue(null))).isTrue();
}
@Test
public void tolerateNullValuesForDeletion() {
Project.NameKey project = Project.nameKey("project");
- ChangeData cd = ChangeData.createForTest(project, Change.id(1), 1, ObjectId.zeroId());
+ ChangeData cd =
+ ChangeData.createForTest(project, Change.id(1), 1, ObjectId.zeroId(), null, null, null);
assertThat(ChangeField.DELETED_LINES_SPEC.setIfPossible(cd, new FakeStoredValue(null)))
.isTrue();
}
diff --git a/javatests/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java b/javatests/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java
index bf6839d..a7ec4c6 100644
--- a/javatests/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java
+++ b/javatests/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java
@@ -19,7 +19,6 @@
import static java.util.concurrent.TimeUnit.SECONDS;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.Comment;
@@ -185,9 +184,9 @@
install(new DefaultRefLogIdentityProvider.Module());
bind(AllUsersName.class).toProvider(AllUsersNameProvider.class);
bind(String.class).annotatedWith(GerritServerId.class).toInstance(serverId);
- bind(new TypeLiteral<ImmutableSet<String>>() {})
+ bind(new TypeLiteral<ImmutableList<String>>() {})
.annotatedWith(GerritImportedServerIds.class)
- .toInstance(new ImmutableSet.Builder<String>().add(importedServerIds).build());
+ .toInstance(new ImmutableList.Builder<String>().add(importedServerIds).build());
bind(GitRepositoryManager.class).toInstance(repoManager);
bind(ProjectCache.class).to(NullProjectCache.class);
bind(Config.class).annotatedWith(GerritServerConfig.class).toInstance(testConfig);
diff --git a/javatests/com/google/gerrit/server/query/change/ChangeDataTest.java b/javatests/com/google/gerrit/server/query/change/ChangeDataTest.java
index f80a96d..0ce00eb 100644
--- a/javatests/com/google/gerrit/server/query/change/ChangeDataTest.java
+++ b/javatests/com/google/gerrit/server/query/change/ChangeDataTest.java
@@ -15,18 +15,29 @@
package com.google.gerrit.server.query.change;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.util.time.TimeUtil;
import com.google.gerrit.testing.TestChanges;
+import java.util.UUID;
import org.eclipse.jgit.lib.ObjectId;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+@RunWith(MockitoJUnitRunner.class)
public class ChangeDataTest {
+ private static final String GERRIT_SERVER_ID = UUID.randomUUID().toString();
+
+ @Mock private ChangeNotes changeNotesMock;
+
@Test
public void setPatchSetsClearsCurrentPatchSet() throws Exception {
Project.NameKey project = Project.nameKey("project");
@@ -41,6 +52,26 @@
assertThat(curr2).isNotSameInstanceAs(curr1);
}
+ @Test
+ public void getChangeVirtualIdUsingAlgorithm() throws Exception {
+ Project.NameKey project = Project.nameKey("project");
+ final int encodedChangeNum = 12345678;
+
+ when(changeNotesMock.getServerId()).thenReturn(UUID.randomUUID().toString());
+
+ ChangeData cd =
+ ChangeData.createForTest(
+ project,
+ Change.id(1),
+ 1,
+ ObjectId.zeroId(),
+ GERRIT_SERVER_ID,
+ (s, c) -> encodedChangeNum,
+ changeNotesMock);
+
+ assertThat(cd.getVirtualId().get()).isEqualTo(encodedChangeNum);
+ }
+
private static PatchSet newPatchSet(Change.Id changeId, int num) {
return PatchSet.builder()
.id(PatchSet.id(changeId, num))
diff --git a/package.json b/package.json
index 362b9dc..7b310aa 100644
--- a/package.json
+++ b/package.json
@@ -1,12 +1,12 @@
{
"name": "gerrit",
- "version": "3.1.0-SNAPSHOT",
+ "version": "3.9.0-SNAPSHOT",
"description": "Gerrit Code Review",
"dependencies": {
- "@bazel/concatjs": "^5.5.0",
- "@bazel/rollup": "^5.5.0",
- "@bazel/terser": "^5.5.0",
- "@bazel/typescript": "^5.5.0"
+ "@bazel/concatjs": "^5.8.0",
+ "@bazel/rollup": "^5.8.0",
+ "@bazel/terser": "^5.8.0",
+ "@bazel/typescript": "^5.8.0"
},
"devDependencies": {
"@koa/cors": "^3.3.0",
@@ -18,7 +18,7 @@
"eslint-config-google": "^0.14.0",
"eslint-plugin-html": "^6.2.0",
"eslint-plugin-import": "^2.26.0",
- "eslint-plugin-jsdoc": "^39.6.4",
+ "eslint-plugin-jsdoc": "^44.2.4",
"eslint-plugin-lit": "^1.6.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^4.0.0",
diff --git a/plugins/.eslintrc.js b/plugins/.eslintrc.js
index 149a31e..920ec2a 100644
--- a/plugins/.eslintrc.js
+++ b/plugins/.eslintrc.js
@@ -186,8 +186,6 @@
'jsdoc/implements-on-classes': 2,
// https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-match-description
'jsdoc/match-description': 0,
- // https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-newline-after-description
- 'jsdoc/newline-after-description': 2,
// https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-no-types
'jsdoc/no-types': 0,
// https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-no-undefined-types
diff --git a/plugins/package.json b/plugins/package.json
index 504fc17..b99a9e8 100644
--- a/plugins/package.json
+++ b/plugins/package.json
@@ -6,7 +6,7 @@
"@gerritcodereview/typescript-api": "3.8.0",
"@polymer/decorators": "^3.0.0",
"@polymer/polymer": "^3.4.1",
- "@open-wc/testing": "^3.1.6",
+ "@open-wc/testing": "^3.1.8",
"@web/dev-server-esbuild": "^0.3.2",
"@web/test-runner": "^0.14.0",
"@codemirror/autocomplete": "^6.5.1",
diff --git a/plugins/yarn.lock b/plugins/yarn.lock
index 3f53453..1a82f27 100644
--- a/plugins/yarn.lock
+++ b/plugins/yarn.lock
@@ -501,26 +501,26 @@
"@types/chai" "^4.3.1"
"@web/test-runner-commands" "^0.6.1"
-"@open-wc/testing-helpers@^2.1.4":
- version "2.1.4"
- resolved "https://registry.yarnpkg.com/@open-wc/testing-helpers/-/testing-helpers-2.1.4.tgz#4b439442ecb1ea3fbcbb1ef76e8717574d78dc97"
- integrity sha512-iZJxxKI9jRgnPczm8p2jpuvBZ3DHYSLrBmhDfzs7ol8vXMNt+HluzM1j1TSU95MFVGnfaspvvt9fMbXKA7cNcA==
+"@open-wc/testing-helpers@^2.2.1":
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/@open-wc/testing-helpers/-/testing-helpers-2.2.1.tgz#10ed75c33faec0ed68c76e027ebe8be262a36921"
+ integrity sha512-8zuJK7tUQYuXRIC/cVcPbAPOhtBJCe3Jfpk7im7WK0DIAXH9Q/ycB+yu3R8g4BQ31f/FdLjIFRbPZzIU75kkRg==
dependencies:
"@open-wc/scoped-elements" "^2.1.3"
lit "^2.0.0"
lit-html "^2.0.0"
-"@open-wc/testing@^3.1.6":
- version "3.1.7"
- resolved "https://registry.yarnpkg.com/@open-wc/testing/-/testing-3.1.7.tgz#65200c759626d510fda103c3cb4ede6202b1b88b"
- integrity sha512-HCS2LuY6hXtEwjqmad+eanId5H7E+3mUi9Z3rjAhH+1DCJ53lUnjzWF1lbCYbREqrdCpmzZvW1t5R3e9gJZSCA==
+"@open-wc/testing@^3.1.8":
+ version "3.1.8"
+ resolved "https://registry.yarnpkg.com/@open-wc/testing/-/testing-3.1.8.tgz#3760a354e421a38bf432010a067ff3d3fdb60b1e"
+ integrity sha512-SpKhlSwCqUkVOOmdb9RanOQgqv4T32wzExkvuaVcUFcBeUdpwQsg1+WYpdv31Z4cRCkAhQ4A8OIpGphzqF8T7w==
dependencies:
"@esm-bundle/chai" "^4.3.4-fix.0"
"@open-wc/chai-dom-equals" "^0.12.36"
"@open-wc/semantic-dom-diff" "^0.19.7"
- "@open-wc/testing-helpers" "^2.1.4"
+ "@open-wc/testing-helpers" "^2.2.1"
"@types/chai" "^4.2.11"
- "@types/chai-dom" "^0.0.12"
+ "@types/chai-dom" "^1.11.0"
"@types/sinon-chai" "^3.2.3"
chai-a11y-axe "^1.3.2"
@@ -621,10 +621,10 @@
"@types/connect" "*"
"@types/node" "*"
-"@types/chai-dom@^0.0.12":
- version "0.0.12"
- resolved "https://registry.yarnpkg.com/@types/chai-dom/-/chai-dom-0.0.12.tgz#fdd7a52bed4dd235ed1c94d3d2d31d4e7db1d03a"
- integrity sha512-4rE7sDw713cV61TYzQbMrPjC4DjNk3x4vk9nAVRNXcSD4p0/5lEEfm0OgoCz5eNuWUXNKA0YiKiH/JDTuKivkA==
+"@types/chai-dom@^1.11.0":
+ version "1.11.0"
+ resolved "https://registry.yarnpkg.com/@types/chai-dom/-/chai-dom-1.11.0.tgz#e9bd01f3408b2ffd27755fe4418ff92ffd8f4e66"
+ integrity sha512-Aja99Mmnny+Sz+T2hBK3oEsrcy18yabplT0pGX/QwIke9jMJHdvHlV2f4Tmq5SqxTMYwt1Zjbisv/4r83EUIHw==
dependencies:
"@types/chai" "*"
diff --git a/polygerrit-ui/app/.eslintrc.js b/polygerrit-ui/app/.eslintrc.js
index e18a3af..7abc658 100644
--- a/polygerrit-ui/app/.eslintrc.js
+++ b/polygerrit-ui/app/.eslintrc.js
@@ -176,8 +176,6 @@
'jsdoc/implements-on-classes': 2,
// https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-match-description
'jsdoc/match-description': 0,
- // https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-newline-after-description
- 'jsdoc/newline-after-description': 2,
// https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-no-types
'jsdoc/no-types': 0,
// https://github.com/gajus/eslint-plugin-jsdoc#eslint-plugin-jsdoc-rules-no-undefined-types
diff --git a/polygerrit-ui/app/api/package.json b/polygerrit-ui/app/api/package.json
index 7df755b..a125b8f 100644
--- a/polygerrit-ui/app/api/package.json
+++ b/polygerrit-ui/app/api/package.json
@@ -1,6 +1,6 @@
{
"name": "@gerritcodereview/typescript-api",
- "version": "3.8.0",
+ "version": "3.9.0-SNAPSHOT",
"description": "Gerrit Code Review - TypeScript API",
"homepage": "https://www.gerritcodereview.com/",
"browser": true,
diff --git a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.ts b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.ts
index ff7a528..0e75b84 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.ts
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.ts
@@ -142,65 +142,67 @@
}
}
- static override styles = [
- sharedStyles,
- paperStyles,
- formStyles,
- menuPageStyles,
- css`
- :host {
- display: block;
- margin-bottom: var(--spacing-m);
- }
- .header {
- align-items: baseline;
- display: flex;
- justify-content: space-between;
- margin: var(--spacing-s) var(--spacing-m);
- }
- .rules {
- background: var(--table-header-background-color);
- border: 1px solid var(--border-color);
- border-bottom: 0;
- }
- .editing .rules {
- border-bottom: 1px solid var(--border-color);
- }
- .title {
- margin-bottom: var(--spacing-s);
- }
- #addRule,
- #removeBtn {
- display: none;
- }
- .right {
- display: flex;
- align-items: center;
- }
- .editing #removeBtn {
- display: block;
- margin-left: var(--spacing-xl);
- }
- .editing #addRule {
- display: block;
- padding: var(--spacing-m);
- }
- #deletedContainer,
- .deleted #mainContainer {
- display: none;
- }
- .deleted #deletedContainer {
- align-items: baseline;
- border: 1px solid var(--border-color);
- display: flex;
- justify-content: space-between;
- padding: var(--spacing-m);
- }
- #mainContainer {
- display: block;
- }
- `,
- ];
+ static override get styles() {
+ return [
+ sharedStyles,
+ paperStyles,
+ formStyles,
+ menuPageStyles,
+ css`
+ :host {
+ display: block;
+ margin-bottom: var(--spacing-m);
+ }
+ .header {
+ align-items: baseline;
+ display: flex;
+ justify-content: space-between;
+ margin: var(--spacing-s) var(--spacing-m);
+ }
+ .rules {
+ background: var(--table-header-background-color);
+ border: 1px solid var(--border-color);
+ border-bottom: 0;
+ }
+ .editing .rules {
+ border-bottom: 1px solid var(--border-color);
+ }
+ .title {
+ margin-bottom: var(--spacing-s);
+ }
+ #addRule,
+ #removeBtn {
+ display: none;
+ }
+ .right {
+ display: flex;
+ align-items: center;
+ }
+ .editing #removeBtn {
+ display: block;
+ margin-left: var(--spacing-xl);
+ }
+ .editing #addRule {
+ display: block;
+ padding: var(--spacing-m);
+ }
+ #deletedContainer,
+ .deleted #mainContainer {
+ display: none;
+ }
+ .deleted #deletedContainer {
+ align-items: baseline;
+ border: 1px solid var(--border-color);
+ display: flex;
+ justify-content: space-between;
+ padding: var(--spacing-m);
+ }
+ #mainContainer {
+ display: block;
+ }
+ `,
+ ];
+ }
override render() {
if (!this.section || !this.permission) {
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
index 1cb25ea..8d72a97 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
@@ -212,104 +212,106 @@
this.queryHashtag = (input: string) => this.getHashtagSuggestions(input);
}
- static override styles = [
- sharedStyles,
- fontStyles,
- changeMetadataStyles,
- css`
- :host {
- display: table;
- }
- gr-submit-requirements {
- --requirements-horizontal-padding: var(--metadata-horizontal-padding);
- }
- gr-editable-label {
- max-width: 9em;
- }
- gr-weblink {
- display: block;
- }
- gr-account-chip[disabled],
- gr-linked-chip[disabled] {
- opacity: 0;
- pointer-events: none;
- }
- .hashtagChip {
- padding-bottom: var(--spacing-s);
- }
- /* consistent with section .title, .value */
- .hashtagChip:not(last-of-type) {
- padding-bottom: var(--spacing-s);
- }
- .hashtagChip:last-of-type {
- display: inline;
- vertical-align: top;
- }
- .parentList.merge {
- list-style-type: decimal;
- padding-left: var(--spacing-l);
- }
- .parentList gr-commit-info {
- display: inline-block;
- }
- .hideDisplay,
- #parentNotCurrentMessage {
- display: none;
- }
- .icon {
- margin: -3px 0;
- }
- .icon.help,
- .icon.notTrusted {
- color: var(--warning-foreground);
- }
- .icon.invalid {
- color: var(--negative-red-text-color);
- }
- .icon.trusted {
- color: var(--positive-green-text-color);
- }
- .parentList.notCurrent.nonMerge #parentNotCurrentMessage {
- --arrow-color: var(--warning-foreground);
- display: inline-block;
- }
- .oldSeparatedSection {
- margin-top: var(--spacing-l);
- padding: var(--spacing-m) 0;
- }
- .separatedSection {
- padding: var(--spacing-m) 0;
- }
- .hashtag gr-linked-chip,
- .topic gr-linked-chip {
- --linked-chip-text-color: var(--link-color);
- }
- gr-reviewer-list {
- --account-max-length: 100px;
- max-width: 285px;
- }
- .metadata-title {
- color: var(--deemphasized-text-color);
- padding-left: var(--metadata-horizontal-padding);
- }
- .metadata-header {
- display: flex;
- justify-content: space-between;
- align-items: flex-end;
- /* The goal is to achieve alignment of the owner account chip and the
+ static override get styles() {
+ return [
+ sharedStyles,
+ fontStyles,
+ changeMetadataStyles,
+ css`
+ :host {
+ display: table;
+ }
+ gr-submit-requirements {
+ --requirements-horizontal-padding: var(--metadata-horizontal-padding);
+ }
+ gr-editable-label {
+ max-width: 9em;
+ }
+ gr-weblink {
+ display: block;
+ }
+ gr-account-chip[disabled],
+ gr-linked-chip[disabled] {
+ opacity: 0;
+ pointer-events: none;
+ }
+ .hashtagChip {
+ padding-bottom: var(--spacing-s);
+ }
+ /* consistent with section .title, .value */
+ .hashtagChip:not(last-of-type) {
+ padding-bottom: var(--spacing-s);
+ }
+ .hashtagChip:last-of-type {
+ display: inline;
+ vertical-align: top;
+ }
+ .parentList.merge {
+ list-style-type: decimal;
+ padding-left: var(--spacing-l);
+ }
+ .parentList gr-commit-info {
+ display: inline-block;
+ }
+ .hideDisplay,
+ #parentNotCurrentMessage {
+ display: none;
+ }
+ .icon {
+ margin: -3px 0;
+ }
+ .icon.help,
+ .icon.notTrusted {
+ color: var(--warning-foreground);
+ }
+ .icon.invalid {
+ color: var(--negative-red-text-color);
+ }
+ .icon.trusted {
+ color: var(--positive-green-text-color);
+ }
+ .parentList.notCurrent.nonMerge #parentNotCurrentMessage {
+ --arrow-color: var(--warning-foreground);
+ display: inline-block;
+ }
+ .oldSeparatedSection {
+ margin-top: var(--spacing-l);
+ padding: var(--spacing-m) 0;
+ }
+ .separatedSection {
+ padding: var(--spacing-m) 0;
+ }
+ .hashtag gr-linked-chip,
+ .topic gr-linked-chip {
+ --linked-chip-text-color: var(--link-color);
+ }
+ gr-reviewer-list {
+ --account-max-length: 100px;
+ max-width: 285px;
+ }
+ .metadata-title {
+ color: var(--deemphasized-text-color);
+ padding-left: var(--metadata-horizontal-padding);
+ }
+ .metadata-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-end;
+ /* The goal is to achieve alignment of the owner account chip and the
commit message box. Their top border should be on the same line. */
- margin-bottom: var(--spacing-s);
- }
- .show-all-button gr-icon {
- color: inherit;
- font-size: 18px;
- }
- gr-vote-chip {
- --gr-vote-chip-width: 14px;
- --gr-vote-chip-height: 14px;
- }
- `,
- ];
+ margin-bottom: var(--spacing-s);
+ }
+ .show-all-button gr-icon {
+ color: inherit;
+ font-size: 18px;
+ }
+ gr-vote-chip {
+ --gr-vote-chip-width: 14px;
+ --gr-vote-chip-height: 14px;
+ }
+ `,
+ ];
+ }
override render() {
if (!this.change) return nothing;
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.ts
index 7723327..77a2cf5 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog.ts
@@ -27,23 +27,25 @@
* @event cancel
*/
- static override styles = [
- sharedStyles,
- css`
- :host {
- display: block;
- }
- :host([disabled]) {
- opacity: 0.5;
- pointer-events: none;
- }
- .main {
- display: flex;
- flex-direction: column;
- width: 100%;
- }
- `,
- ];
+ static override get styles() {
+ return [
+ sharedStyles,
+ css`
+ :host {
+ display: block;
+ }
+ :host([disabled]) {
+ opacity: 0.5;
+ pointer-events: none;
+ }
+ .main {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ }
+ `,
+ ];
+ }
override render() {
return html`
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
index 891209e..080eb0c 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.ts
@@ -154,75 +154,77 @@
}
}
- static override styles = [
- sharedStyles,
- css`
- :host {
- display: block;
- }
- :host([disabled]) {
- opacity: 0.5;
- pointer-events: none;
- }
- label {
- cursor: pointer;
- }
- .main {
- display: flex;
- flex-direction: column;
- width: 100%;
- }
- .main label,
- .main input[type='text'] {
- display: block;
- width: 100%;
- }
- iron-autogrow-textarea {
- font-family: var(--monospace-font-family);
- font-size: var(--font-size-mono);
- line-height: var(--line-height-mono);
- width: 73ch; /* Add a char to account for the border. */
- }
- .cherryPickTopicLayout {
- display: flex;
- align-items: center;
- margin-bottom: var(--spacing-m);
- }
- .cherryPickSingleChange,
- .cherryPickTopic {
- margin-left: var(--spacing-m);
- }
- .cherry-pick-topic-message {
- margin-bottom: var(--spacing-m);
- }
- label[for='messageInput'],
- label[for='baseInput'] {
- margin-top: var(--spacing-m);
- }
- .title {
- font-weight: var(--font-weight-bold);
- }
- tr > td {
- padding: var(--spacing-m);
- }
- th {
- color: var(--deemphasized-text-color);
- }
- table {
- border-collapse: collapse;
- }
- tr {
- border-bottom: 1px solid var(--border-color);
- }
- .error {
- color: var(--error-text-color);
- }
- .error-message {
- color: var(--error-text-color);
- margin: var(--spacing-m) 0 var(--spacing-m) 0;
- }
- `,
- ];
+ static override get styles() {
+ return [
+ sharedStyles,
+ css`
+ :host {
+ display: block;
+ }
+ :host([disabled]) {
+ opacity: 0.5;
+ pointer-events: none;
+ }
+ label {
+ cursor: pointer;
+ }
+ .main {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ }
+ .main label,
+ .main input[type='text'] {
+ display: block;
+ width: 100%;
+ }
+ iron-autogrow-textarea {
+ font-family: var(--monospace-font-family);
+ font-size: var(--font-size-mono);
+ line-height: var(--line-height-mono);
+ width: 73ch; /* Add a char to account for the border. */
+ }
+ .cherryPickTopicLayout {
+ display: flex;
+ align-items: center;
+ margin-bottom: var(--spacing-m);
+ }
+ .cherryPickSingleChange,
+ .cherryPickTopic {
+ margin-left: var(--spacing-m);
+ }
+ .cherry-pick-topic-message {
+ margin-bottom: var(--spacing-m);
+ }
+ label[for='messageInput'],
+ label[for='baseInput'] {
+ margin-top: var(--spacing-m);
+ }
+ .title {
+ font-weight: var(--font-weight-bold);
+ }
+ tr > td {
+ padding: var(--spacing-m);
+ }
+ th {
+ color: var(--deemphasized-text-color);
+ }
+ table {
+ border-collapse: collapse;
+ }
+ tr {
+ border-bottom: 1px solid var(--border-color);
+ }
+ .error {
+ color: var(--error-text-color);
+ }
+ .error-message {
+ color: var(--error-text-color);
+ margin: var(--spacing-m) 0 var(--spacing-m) 0;
+ }
+ `,
+ ];
+ }
override render() {
return html`
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts
index 6ad416e..7bbfd93 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts
@@ -160,39 +160,41 @@
}
}
- static override styles = [
- sharedStyles,
- css`
- :host {
- display: block;
- width: 30em;
- }
- :host([disabled]) {
- opacity: 0.5;
- pointer-events: none;
- }
- label {
- cursor: pointer;
- }
- .message {
- font-style: italic;
- }
- .parentRevisionContainer label,
- .parentRevisionContainer input[type='text'] {
- display: block;
- width: 100%;
- }
- .rebaseCheckbox {
- margin-top: var(--spacing-m);
- }
- .rebaseOption {
- margin: var(--spacing-m) 0;
- }
- .rebaseOnBehalfMsg {
- margin-top: var(--spacing-m);
- }
- `,
- ];
+ static override get styles() {
+ return [
+ sharedStyles,
+ css`
+ :host {
+ display: block;
+ width: 30em;
+ }
+ :host([disabled]) {
+ opacity: 0.5;
+ pointer-events: none;
+ }
+ label {
+ cursor: pointer;
+ }
+ .message {
+ font-style: italic;
+ }
+ .parentRevisionContainer label,
+ .parentRevisionContainer input[type='text'] {
+ display: block;
+ width: 100%;
+ }
+ .rebaseCheckbox {
+ margin-top: var(--spacing-m);
+ }
+ .rebaseOption {
+ margin: var(--spacing-m) 0;
+ }
+ .rebaseOnBehalfMsg {
+ margin-top: var(--spacing-m);
+ }
+ `,
+ ];
+ }
override render() {
return html`
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.ts
index e421c9c..c01d89f 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.ts
@@ -66,43 +66,45 @@
private readonly getPluginLoader = resolve(this, pluginLoaderToken);
- static override styles = [
- sharedStyles,
- css`
- :host {
- display: block;
- }
- :host([disabled]) {
- opacity: 0.5;
- pointer-events: none;
- }
- label {
- cursor: pointer;
- display: block;
- width: 100%;
- }
- .revertSubmissionLayout {
- display: flex;
- align-items: center;
- }
- .label {
- margin-left: var(--spacing-m);
- }
- iron-autogrow-textarea {
- font-family: var(--monospace-font-family);
- font-size: var(--font-size-mono);
- line-height: var(--line-height-mono);
- width: 73ch; /* Add a char to account for the border. */
- }
- .error {
- color: var(--error-text-color);
- margin-bottom: var(--spacing-m);
- }
- label[for='messageInput'] {
- margin-top: var(--spacing-m);
- }
- `,
- ];
+ static override get styles() {
+ return [
+ sharedStyles,
+ css`
+ :host {
+ display: block;
+ }
+ :host([disabled]) {
+ opacity: 0.5;
+ pointer-events: none;
+ }
+ label {
+ cursor: pointer;
+ display: block;
+ width: 100%;
+ }
+ .revertSubmissionLayout {
+ display: flex;
+ align-items: center;
+ }
+ .label {
+ margin-left: var(--spacing-m);
+ }
+ iron-autogrow-textarea {
+ font-family: var(--monospace-font-family);
+ font-size: var(--font-size-mono);
+ line-height: var(--line-height-mono);
+ width: 73ch; /* Add a char to account for the border. */
+ }
+ .error {
+ color: var(--error-text-color);
+ margin-bottom: var(--spacing-m);
+ }
+ label[for='messageInput'] {
+ margin-top: var(--spacing-m);
+ }
+ `,
+ ];
+ }
override render() {
return html`
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
index adea275..f6f3706 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
@@ -139,99 +139,101 @@
);
}
- static override styles = [
- sharedStyles,
- css`
- .prefsButton {
- float: right;
- }
- .patchInfoOldPatchSet.patchInfo-header {
- background-color: var(--emphasis-color);
- }
- .patchInfo-header {
- align-items: center;
- display: flex;
- padding: var(--spacing-s) var(--spacing-l);
- }
- .patchInfo-left {
- align-items: baseline;
- display: flex;
- }
- .patchInfoContent {
- align-items: center;
- display: flex;
- flex-wrap: wrap;
- }
- .latestPatchContainer a {
- text-decoration: none;
- }
- .mobile {
- display: none;
- }
- .patchInfo-header .container {
- align-items: center;
- display: flex;
- }
- .downloadContainer,
- .uploadContainer {
- margin-right: 16px;
- }
- .uploadContainer.hide {
- display: none;
- }
- .rightControls {
- align-self: flex-end;
- margin: auto 0 auto auto;
- align-items: center;
- display: flex;
- flex-wrap: wrap;
- font-weight: var(--font-weight-normal);
- justify-content: flex-end;
- }
- #collapseBtn,
- .allExpanded #expandBtn,
- .fileViewActions {
- display: none;
- }
- .someExpanded #expandBtn {
- margin-right: 8px;
- }
- .someExpanded #collapseBtn,
- .allExpanded #collapseBtn,
- .openFile .fileViewActions {
- align-items: center;
- display: flex;
- }
- .rightControls gr-button,
- gr-patch-range-select {
- margin: 0 -4px;
- }
- .fileViewActions gr-button {
- margin: 0;
- --gr-button-padding: 2px 4px;
- }
- .flexContainer {
- align-items: center;
- display: flex;
- }
- .label {
- font-weight: var(--font-weight-bold);
- margin-right: 24px;
- }
- gr-commit-info,
- gr-edit-controls {
- margin-right: -5px;
- }
- .fileViewActionsLabel {
- margin-right: var(--spacing-xs);
- }
- @media screen and (max-width: 50em) {
- .patchInfo-header .desktop {
+ static override get styles() {
+ return [
+ sharedStyles,
+ css`
+ .prefsButton {
+ float: right;
+ }
+ .patchInfoOldPatchSet.patchInfo-header {
+ background-color: var(--emphasis-color);
+ }
+ .patchInfo-header {
+ align-items: center;
+ display: flex;
+ padding: var(--spacing-s) var(--spacing-l);
+ }
+ .patchInfo-left {
+ align-items: baseline;
+ display: flex;
+ }
+ .patchInfoContent {
+ align-items: center;
+ display: flex;
+ flex-wrap: wrap;
+ }
+ .latestPatchContainer a {
+ text-decoration: none;
+ }
+ .mobile {
display: none;
}
- }
- `,
- ];
+ .patchInfo-header .container {
+ align-items: center;
+ display: flex;
+ }
+ .downloadContainer,
+ .uploadContainer {
+ margin-right: 16px;
+ }
+ .uploadContainer.hide {
+ display: none;
+ }
+ .rightControls {
+ align-self: flex-end;
+ margin: auto 0 auto auto;
+ align-items: center;
+ display: flex;
+ flex-wrap: wrap;
+ font-weight: var(--font-weight-normal);
+ justify-content: flex-end;
+ }
+ #collapseBtn,
+ .allExpanded #expandBtn,
+ .fileViewActions {
+ display: none;
+ }
+ .someExpanded #expandBtn {
+ margin-right: 8px;
+ }
+ .someExpanded #collapseBtn,
+ .allExpanded #collapseBtn,
+ .openFile .fileViewActions {
+ align-items: center;
+ display: flex;
+ }
+ .rightControls gr-button,
+ gr-patch-range-select {
+ margin: 0 -4px;
+ }
+ .fileViewActions gr-button {
+ margin: 0;
+ --gr-button-padding: 2px 4px;
+ }
+ .flexContainer {
+ align-items: center;
+ display: flex;
+ }
+ .label {
+ font-weight: var(--font-weight-bold);
+ margin-right: 24px;
+ }
+ gr-commit-info,
+ gr-edit-controls {
+ margin-right: -5px;
+ }
+ .fileViewActionsLabel {
+ margin-right: var(--spacing-xs);
+ }
+ @media screen and (max-width: 50em) {
+ .patchInfo-header .desktop {
+ display: none;
+ }
+ }
+ `,
+ ];
+ }
override render() {
if (!this.change || !this.diffPrefs) {
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 f41236b..a9c472f 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
@@ -354,223 +354,225 @@
private readonly shortcuts = new ShortcutController(this);
- static override styles = [
- sharedStyles,
- modalStyles,
- css`
- :host {
- background-color: var(--dialog-background-color);
- display: block;
- max-height: 90vh;
- --label-score-padding-left: var(--spacing-xl);
- }
- :host([disabled]) {
- pointer-events: none;
- }
- :host([disabled]) .container {
- opacity: 0.5;
- }
- section {
- border-top: 1px solid var(--border-color);
- flex-shrink: 0;
- padding: var(--spacing-m) var(--spacing-xl);
- width: 100%;
- }
- section.labelsContainer {
- /* We want the :hover highlight to extend to the border of the dialog. */
- padding: var(--spacing-m) 0;
- }
- .stickyBottom {
- background-color: var(--dialog-background-color);
- box-shadow: 0px 0px 8px 0px rgba(60, 64, 67, 0.15);
- margin-top: var(--spacing-s);
- bottom: 0;
- position: sticky;
- /* @see Issue 8602 */
- z-index: 1;
- }
- .stickyBottom.newReplyDialog {
- margin-top: unset;
- }
- .actions {
- display: flex;
- justify-content: space-between;
- }
- .actions .right gr-button {
- margin-left: var(--spacing-l);
- }
- .peopleContainer,
- .labelsContainer {
- flex-shrink: 0;
- }
- .peopleContainer {
- border-top: none;
- display: table;
- }
- .peopleList {
- display: flex;
- }
- .peopleListLabel {
- color: var(--deemphasized-text-color);
- margin-top: var(--spacing-xs);
- min-width: 6em;
- padding-right: var(--spacing-m);
- }
- gr-account-list {
- display: flex;
- flex-wrap: wrap;
- flex: 1;
- }
- #reviewerConfirmationModal {
- padding: var(--spacing-l);
- text-align: center;
- }
- .reviewerConfirmationButtons {
- margin-top: var(--spacing-l);
- }
- .groupName {
- font-weight: var(--font-weight-bold);
- }
- .groupSize {
- font-style: italic;
- }
- .textareaContainer {
- min-height: 12em;
- position: relative;
- }
- .newReplyDialog.textareaContainer {
- min-height: unset;
- }
- textareaContainer,
- gr-endpoint-decorator[name='reply-text'] {
- display: flex;
- width: 100%;
- }
- gr-endpoint-decorator[name='reply-text'] {
- flex-direction: column;
- }
- #checkingStatusLabel,
- #notLatestLabel {
- margin-left: var(--spacing-l);
- }
- #checkingStatusLabel {
- color: var(--deemphasized-text-color);
- font-style: italic;
- }
- #notLatestLabel,
- #savingLabel {
- color: var(--error-text-color);
- }
- #savingLabel {
- display: none;
- }
- #savingLabel.saving {
- display: inline;
- }
- #pluginMessage {
- color: var(--deemphasized-text-color);
- margin-left: var(--spacing-l);
- margin-bottom: var(--spacing-m);
- }
- #pluginMessage:empty {
- display: none;
- }
- .attention .edit-attention-button {
- vertical-align: top;
- --gr-button-padding: 0px 4px;
- }
- .attention .edit-attention-button gr-icon {
- color: inherit;
- /* The line-height:26px hack (see below) requires us to do this.
+ static override get styles() {
+ return [
+ sharedStyles,
+ modalStyles,
+ css`
+ :host {
+ background-color: var(--dialog-background-color);
+ display: block;
+ max-height: 90vh;
+ --label-score-padding-left: var(--spacing-xl);
+ }
+ :host([disabled]) {
+ pointer-events: none;
+ }
+ :host([disabled]) .container {
+ opacity: 0.5;
+ }
+ section {
+ border-top: 1px solid var(--border-color);
+ flex-shrink: 0;
+ padding: var(--spacing-m) var(--spacing-xl);
+ width: 100%;
+ }
+ section.labelsContainer {
+ /* We want the :hover highlight to extend to the border of the dialog. */
+ padding: var(--spacing-m) 0;
+ }
+ .stickyBottom {
+ background-color: var(--dialog-background-color);
+ box-shadow: 0px 0px 8px 0px rgba(60, 64, 67, 0.15);
+ margin-top: var(--spacing-s);
+ bottom: 0;
+ position: sticky;
+ /* @see Issue 8602 */
+ z-index: 1;
+ }
+ .stickyBottom.newReplyDialog {
+ margin-top: unset;
+ }
+ .actions {
+ display: flex;
+ justify-content: space-between;
+ }
+ .actions .right gr-button {
+ margin-left: var(--spacing-l);
+ }
+ .peopleContainer,
+ .labelsContainer {
+ flex-shrink: 0;
+ }
+ .peopleContainer {
+ border-top: none;
+ display: table;
+ }
+ .peopleList {
+ display: flex;
+ }
+ .peopleListLabel {
+ color: var(--deemphasized-text-color);
+ margin-top: var(--spacing-xs);
+ min-width: 6em;
+ padding-right: var(--spacing-m);
+ }
+ gr-account-list {
+ display: flex;
+ flex-wrap: wrap;
+ flex: 1;
+ }
+ #reviewerConfirmationModal {
+ padding: var(--spacing-l);
+ text-align: center;
+ }
+ .reviewerConfirmationButtons {
+ margin-top: var(--spacing-l);
+ }
+ .groupName {
+ font-weight: var(--font-weight-bold);
+ }
+ .groupSize {
+ font-style: italic;
+ }
+ .textareaContainer {
+ min-height: 12em;
+ position: relative;
+ }
+ .newReplyDialog.textareaContainer {
+ min-height: unset;
+ }
+ textareaContainer,
+ gr-endpoint-decorator[name='reply-text'] {
+ display: flex;
+ width: 100%;
+ }
+ gr-endpoint-decorator[name='reply-text'] {
+ flex-direction: column;
+ }
+ #checkingStatusLabel,
+ #notLatestLabel {
+ margin-left: var(--spacing-l);
+ }
+ #checkingStatusLabel {
+ color: var(--deemphasized-text-color);
+ font-style: italic;
+ }
+ #notLatestLabel,
+ #savingLabel {
+ color: var(--error-text-color);
+ }
+ #savingLabel {
+ display: none;
+ }
+ #savingLabel.saving {
+ display: inline;
+ }
+ #pluginMessage {
+ color: var(--deemphasized-text-color);
+ margin-left: var(--spacing-l);
+ margin-bottom: var(--spacing-m);
+ }
+ #pluginMessage:empty {
+ display: none;
+ }
+ .attention .edit-attention-button {
+ vertical-align: top;
+ --gr-button-padding: 0px 4px;
+ }
+ .attention .edit-attention-button gr-icon {
+ color: inherit;
+ /* The line-height:26px hack (see below) requires us to do this.
Normally the gr-icon would account for a proper positioning
within the standard line-height:20px context. */
- top: 5px;
- }
- .attention a,
- .attention-detail a {
- text-decoration: none;
- }
- .attentionSummary {
- display: flex;
- justify-content: space-between;
- }
- .attentionSummary {
- /* The account label for selection is misbehaving currently: It consumes
+ top: 5px;
+ }
+ .attention a,
+ .attention-detail a {
+ text-decoration: none;
+ }
+ .attentionSummary {
+ display: flex;
+ justify-content: space-between;
+ }
+ .attentionSummary {
+ /* The account label for selection is misbehaving currently: It consumes
26px height instead of 20px, which is the default line-height and thus
the max that can be nicely fit into an inline layout flow. We
acknowledge that using a fixed 26px value here is a hack and not a
great solution. */
- line-height: 26px;
- }
- .attentionSummary gr-account-label,
- .attention-detail gr-account-label {
- --account-max-length: 120px;
- display: inline-block;
- padding: var(--spacing-xs) var(--spacing-m);
- user-select: none;
- --label-border-radius: 8px;
- }
- .attentionSummary gr-account-label {
- margin: 0 var(--spacing-xs);
- line-height: var(--line-height-normal);
- vertical-align: top;
- }
- .attention-detail .peopleListValues {
- line-height: calc(var(--line-height-normal) + 10px);
- }
- .attention-detail gr-account-label {
- line-height: var(--line-height-normal);
- }
- .attentionSummary gr-account-label:focus,
- .attention-detail gr-account-label:focus {
- outline: none;
- }
- .attentionSummary gr-account-label:hover,
- .attention-detail gr-account-label:hover {
- box-shadow: var(--elevation-level-1);
- cursor: pointer;
- }
- .attention-detail .attentionDetailsTitle {
- display: flex;
- justify-content: space-between;
- }
- .attention-detail .selectUsers {
- color: var(--deemphasized-text-color);
- margin-bottom: var(--spacing-m);
- }
- .attentionTip {
- padding: var(--spacing-m);
- border: 1px solid var(--border-color);
- border-radius: var(--border-radius);
- margin-top: var(--spacing-m);
- background-color: var(--line-item-highlight-color);
- }
- .attentionTip div gr-icon {
- margin-right: var(--spacing-s);
- }
- .patchsetLevelContainer {
- width: 80ch;
- border-radius: var(--border-radius);
- box-shadow: var(--elevation-level-2);
- }
- .patchsetLevelContainer.resolved {
- background-color: var(--comment-background-color);
- }
- .patchsetLevelContainer.unresolved {
- background-color: var(--unresolved-comment-background-color);
- }
- .privateVisiblityInfo {
- display: flex;
- justify-content: center;
- background-color: var(--info-background);
- padding: var(--spacing-s) 0;
- }
- .privateVisiblityInfo gr-icon {
- margin-right: var(--spacing-m);
- color: var(--info-foreground);
- }
- `,
- ];
+ line-height: 26px;
+ }
+ .attentionSummary gr-account-label,
+ .attention-detail gr-account-label {
+ --account-max-length: 120px;
+ display: inline-block;
+ padding: var(--spacing-xs) var(--spacing-m);
+ user-select: none;
+ --label-border-radius: 8px;
+ }
+ .attentionSummary gr-account-label {
+ margin: 0 var(--spacing-xs);
+ line-height: var(--line-height-normal);
+ vertical-align: top;
+ }
+ .attention-detail .peopleListValues {
+ line-height: calc(var(--line-height-normal) + 10px);
+ }
+ .attention-detail gr-account-label {
+ line-height: var(--line-height-normal);
+ }
+ .attentionSummary gr-account-label:focus,
+ .attention-detail gr-account-label:focus {
+ outline: none;
+ }
+ .attentionSummary gr-account-label:hover,
+ .attention-detail gr-account-label:hover {
+ box-shadow: var(--elevation-level-1);
+ cursor: pointer;
+ }
+ .attention-detail .attentionDetailsTitle {
+ display: flex;
+ justify-content: space-between;
+ }
+ .attention-detail .selectUsers {
+ color: var(--deemphasized-text-color);
+ margin-bottom: var(--spacing-m);
+ }
+ .attentionTip {
+ padding: var(--spacing-m);
+ border: 1px solid var(--border-color);
+ border-radius: var(--border-radius);
+ margin-top: var(--spacing-m);
+ background-color: var(--line-item-highlight-color);
+ }
+ .attentionTip div gr-icon {
+ margin-right: var(--spacing-s);
+ }
+ .patchsetLevelContainer {
+ width: 80ch;
+ border-radius: var(--border-radius);
+ box-shadow: var(--elevation-level-2);
+ }
+ .patchsetLevelContainer.resolved {
+ background-color: var(--comment-background-color);
+ }
+ .patchsetLevelContainer.unresolved {
+ background-color: var(--unresolved-comment-background-color);
+ }
+ .privateVisiblityInfo {
+ display: flex;
+ justify-content: center;
+ background-color: var(--info-background);
+ padding: var(--spacing-s) 0;
+ }
+ .privateVisiblityInfo gr-icon {
+ margin-right: var(--spacing-m);
+ color: var(--info-foreground);
+ }
+ `,
+ ];
+ }
constructor() {
super();
diff --git a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
index 7723c66..02d5124 100644
--- a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
+++ b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
@@ -149,38 +149,40 @@
);
}
- static override styles = [
- sharedStyles,
- modalStyles,
- css`
- .diffContainer {
- padding: var(--spacing-l) 0;
- border-bottom: 1px solid var(--border-color);
- }
- .file-name {
- display: block;
- padding: var(--spacing-s) var(--spacing-l);
- background-color: var(--background-color-secondary);
- border-bottom: 1px solid var(--border-color);
- }
- gr-button {
- margin-left: var(--spacing-m);
- }
- .fix-picker {
- display: flex;
- align-items: center;
- margin-right: var(--spacing-l);
- }
- .info {
- background-color: var(--info-background);
- padding: var(--spacing-l) var(--spacing-xl);
- }
- .info gr-icon {
- color: var(--selected-foreground);
- margin-right: var(--spacing-xl);
- }
- `,
- ];
+ static override get styles() {
+ return [
+ sharedStyles,
+ modalStyles,
+ css`
+ .diffContainer {
+ padding: var(--spacing-l) 0;
+ border-bottom: 1px solid var(--border-color);
+ }
+ .file-name {
+ display: block;
+ padding: var(--spacing-s) var(--spacing-l);
+ background-color: var(--background-color-secondary);
+ border-bottom: 1px solid var(--border-color);
+ }
+ gr-button {
+ margin-left: var(--spacing-m);
+ }
+ .fix-picker {
+ display: flex;
+ align-items: center;
+ margin-right: var(--spacing-l);
+ }
+ .info {
+ background-color: var(--info-background);
+ padding: var(--spacing-l) var(--spacing-xl);
+ }
+ .info gr-icon {
+ color: var(--selected-foreground);
+ margin-right: var(--spacing-xl);
+ }
+ `,
+ ];
+ }
override render() {
return html`
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
index 33c8c22..ff46351 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
@@ -99,6 +99,7 @@
import {subscribe} from '../../lit/subscription-controller';
import {userModelToken} from '../../../models/user/user-model';
import {pluginLoaderToken} from '../../shared/gr-js-api-interface/gr-plugin-loader';
+import {keyed} from 'lit/directives/keyed.js';
const EMPTY_BLAME = 'No blame information for this diff.';
@@ -337,6 +338,12 @@
private checksSubscription?: Subscription;
+ /**
+ * This key is used for the `keyed()` directive when rendering `gr-diff` and
+ * can thus be used to trigger re-construction of `gr-diff`.
+ */
+ private grDiffKey = 0;
+
constructor() {
super();
this.syntaxLayer = new GrSyntaxLayerWorker(
@@ -493,30 +500,33 @@
KnownExperimentId.NEW_IMAGE_DIFF_UI
);
- return html` <gr-diff
- id="diff"
- ?hidden=${this.hidden}
- .noAutoRender=${this.noAutoRender}
- .path=${this.path}
- .prefs=${this.prefs}
- .isImageDiff=${this.isImageDiff}
- .noRenderOnPrefsChange=${this.noRenderOnPrefsChange}
- .renderPrefs=${this.renderPrefs}
- .lineWrapping=${this.lineWrapping}
- .viewMode=${this.viewMode}
- .lineOfInterest=${this.lineOfInterest}
- .loggedIn=${this.loggedIn}
- .errorMessage=${this.errorMessage}
- .baseImage=${this.baseImage}
- .revisionImage=${this.revisionImage}
- .coverageRanges=${this.coverageRanges}
- .blame=${this.blame}
- .layers=${this.layers}
- .diff=${this.diff}
- .showNewlineWarningLeft=${showNewlineWarningLeft}
- .showNewlineWarningRight=${showNewlineWarningRight}
- .useNewImageDiffUi=${useNewImageDiffUi}
- ></gr-diff>`;
+ return keyed(
+ this.grDiffKey,
+ html`<gr-diff
+ id="diff"
+ ?hidden=${this.hidden}
+ .noAutoRender=${this.noAutoRender}
+ .path=${this.path}
+ .prefs=${this.prefs}
+ .isImageDiff=${this.isImageDiff}
+ .noRenderOnPrefsChange=${this.noRenderOnPrefsChange}
+ .renderPrefs=${this.renderPrefs}
+ .lineWrapping=${this.lineWrapping}
+ .viewMode=${this.viewMode}
+ .lineOfInterest=${this.lineOfInterest}
+ .loggedIn=${this.loggedIn}
+ .errorMessage=${this.errorMessage}
+ .baseImage=${this.baseImage}
+ .revisionImage=${this.revisionImage}
+ .coverageRanges=${this.coverageRanges}
+ .blame=${this.blame}
+ .layers=${this.layers}
+ .diff=${this.diff}
+ .showNewlineWarningLeft=${showNewlineWarningLeft}
+ .showNewlineWarningRight=${showNewlineWarningRight}
+ .useNewImageDiffUi=${useNewImageDiffUi}
+ ></gr-diff>`
+ );
}
async initLayers() {
@@ -566,6 +576,7 @@
async reloadInternal(shouldReportMetric?: boolean) {
this.reporting.time(Timing.DIFF_TOTAL);
this.reporting.time(Timing.DIFF_LOAD);
+ this.grDiffKey++;
// TODO: Find better names for these 3 clear/cancel methods. Ideally the
// <gr-diff-host> should not re-used at all for another diff rendering pass.
this.clear();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.ts b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.ts
index e0d5f1d..c4f4d1a 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.ts
@@ -60,6 +60,7 @@
CommentsModel,
commentsModelToken,
} from '../../../models/comments/comments-model';
+import {isNewDiff} from '../../../embed/diff/gr-diff/gr-diff-utils';
suite('gr-diff-host tests', () => {
let element: GrDiffHost;
@@ -153,6 +154,7 @@
});
test('reload() cancels before network resolves', async () => {
+ if (isNewDiff()) return;
assertIsDefined(element.diffElement);
const cancelStub = sinon.stub(element.diffElement, 'cancel');
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts
index 1d46841..d8c3abe 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts
@@ -59,21 +59,23 @@
super.disconnectedCallback();
}
- static override styles = [
- sharedStyles,
- css`
- :host {
- /* Used to remove horizontal whitespace between the icons. */
- display: flex;
- }
- gr-button.selected gr-icon {
- color: var(--link-color);
- }
- gr-icon {
- font-size: 1.3rem;
- }
- `,
- ];
+ static override get styles() {
+ return [
+ sharedStyles,
+ css`
+ :host {
+ /* Used to remove horizontal whitespace between the icons. */
+ display: flex;
+ }
+ gr-button.selected gr-icon {
+ color: var(--link-color);
+ }
+ gr-icon {
+ font-size: 1.3rem;
+ }
+ `,
+ ];
+ }
override render() {
return html`
diff --git a/polygerrit-ui/app/elements/gr-app-global-var-init.ts b/polygerrit-ui/app/elements/gr-app-global-var-init.ts
index 6589ee8..ec46d8d 100644
--- a/polygerrit-ui/app/elements/gr-app-global-var-init.ts
+++ b/polygerrit-ui/app/elements/gr-app-global-var-init.ts
@@ -11,7 +11,6 @@
* expose these variables until plugins switch to direct import from polygerrit.
*/
-import {GrAnnotation} from '../embed/diff/gr-diff-highlight/gr-annotation';
import {GrPluginActionContext} from './shared/gr-js-api-interface/gr-plugin-action-context';
import {AppContext, injectAppContext} from '../services/app-context';
import {PluginLoader} from './shared/gr-js-api-interface/gr-plugin-loader';
@@ -39,7 +38,6 @@
initClickReporter(reportingService);
initInteractionReporter(reportingService);
}
- window.GrAnnotation = GrAnnotation;
window.GrPluginActionContext = GrPluginActionContext;
}
diff --git a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.ts b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.ts
index 89513e3..747f3eb 100644
--- a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.ts
+++ b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.ts
@@ -60,41 +60,43 @@
private readonly restApiService = getAppContext().restApiService;
- static override styles = [
- sharedStyles,
- formStyles,
- css`
- gr-avatar {
- height: 120px;
- width: 120px;
- margin-right: var(--spacing-xs);
- vertical-align: -0.25em;
- }
- div section.hide {
- display: none;
- }
- gr-hovercard-account-contents {
- display: block;
- max-width: 600px;
- margin-top: var(--spacing-m);
- background: var(--dialog-background-color);
- border: 1px solid var(--border-color);
- border-radius: var(--border-radius);
- box-shadow: var(--elevation-level-5);
- }
- iron-autogrow-textarea {
- background-color: var(--view-background-color);
- color: var(--primary-text-color);
- }
- .lengthCounter {
- font-weight: var(--font-weight-normal);
- }
- p {
- max-width: 65ch;
- margin-bottom: var(--spacing-m);
- }
- `,
- ];
+ static override get styles() {
+ return [
+ sharedStyles,
+ formStyles,
+ css`
+ gr-avatar {
+ height: 120px;
+ width: 120px;
+ margin-right: var(--spacing-xs);
+ vertical-align: -0.25em;
+ }
+ div section.hide {
+ display: none;
+ }
+ gr-hovercard-account-contents {
+ display: block;
+ max-width: 600px;
+ margin-top: var(--spacing-m);
+ background: var(--dialog-background-color);
+ border: 1px solid var(--border-color);
+ border-radius: var(--border-radius);
+ box-shadow: var(--elevation-level-5);
+ }
+ iron-autogrow-textarea {
+ background-color: var(--view-background-color);
+ color: var(--primary-text-color);
+ }
+ .lengthCounter {
+ font-weight: var(--font-weight-normal);
+ }
+ p {
+ max-width: 65ch;
+ margin-bottom: var(--spacing-m);
+ }
+ `,
+ ];
+ }
override render() {
if (!this.account || this.loading) return nothing;
diff --git a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.ts b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.ts
index 53548f8..568e7c3 100644
--- a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.ts
+++ b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.ts
@@ -36,28 +36,30 @@
private readonly getConfigModel = resolve(this, configModelToken);
- static override styles = [
- sharedStyles,
- formStyles,
- css`
- #changeCols {
- width: auto;
- }
- #changeCols .visibleHeader {
- text-align: center;
- }
- .checkboxContainer {
- cursor: pointer;
- text-align: center;
- }
- .checkboxContainer input {
- cursor: pointer;
- }
- .checkboxContainer:hover {
- outline: 1px solid var(--border-color);
- }
- `,
- ];
+ static override get styles() {
+ return [
+ sharedStyles,
+ formStyles,
+ css`
+ #changeCols {
+ width: auto;
+ }
+ #changeCols .visibleHeader {
+ text-align: center;
+ }
+ .checkboxContainer {
+ cursor: pointer;
+ text-align: center;
+ }
+ .checkboxContainer input {
+ cursor: pointer;
+ }
+ .checkboxContainer:hover {
+ outline: 1px solid var(--border-color);
+ }
+ `,
+ ];
+ }
constructor() {
super();
diff --git a/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor.ts b/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor.ts
index b9f59bf..1c4fd30 100644
--- a/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor.ts
+++ b/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor.ts
@@ -29,35 +29,37 @@
readonly restApiService = getAppContext().restApiService;
- static override styles = [
- sharedStyles,
- formStyles,
- css`
- th {
- color: var(--deemphasized-text-color);
- text-align: left;
- }
- #emailTable .emailColumn {
- min-width: 32.5em;
- width: auto;
- }
- #emailTable .preferredHeader {
- text-align: center;
- width: 6em;
- }
- #emailTable .preferredControl {
- cursor: pointer;
- height: auto;
- text-align: center;
- }
- #emailTable .preferredControl .preferredRadio {
- height: auto;
- }
- .preferredControl:hover {
- outline: 1px solid var(--border-color);
- }
- `,
- ];
+ static override get styles() {
+ return [
+ sharedStyles,
+ formStyles,
+ css`
+ th {
+ color: var(--deemphasized-text-color);
+ text-align: left;
+ }
+ #emailTable .emailColumn {
+ min-width: 32.5em;
+ width: auto;
+ }
+ #emailTable .preferredHeader {
+ text-align: center;
+ width: 6em;
+ }
+ #emailTable .preferredControl {
+ cursor: pointer;
+ height: auto;
+ text-align: center;
+ }
+ #emailTable .preferredControl .preferredRadio {
+ height: auto;
+ }
+ .preferredControl:hover {
+ outline: 1px solid var(--border-color);
+ }
+ `,
+ ];
+ }
override render() {
return html`<div class="gr-form-styles">
diff --git a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor.ts b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor.ts
index 32b32e2..da1f758 100644
--- a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor.ts
+++ b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor.ts
@@ -49,34 +49,36 @@
private readonly restApiService = getAppContext().restApiService;
- static override styles = [
- formStyles,
- sharedStyles,
- modalStyles,
- css`
- .keyHeader {
- width: 9em;
- }
- .userIdHeader {
- width: 15em;
- }
- #viewKeyModal {
- padding: var(--spacing-xxl);
- width: 50em;
- }
- .closeButton {
- bottom: 2em;
- position: absolute;
- right: 2em;
- }
- #existing {
- margin-bottom: var(--spacing-l);
- }
- iron-autogrow-textarea {
- background-color: var(--view-background-color);
- }
- `,
- ];
+ static override get styles() {
+ return [
+ formStyles,
+ sharedStyles,
+ modalStyles,
+ css`
+ .keyHeader {
+ width: 9em;
+ }
+ .userIdHeader {
+ width: 15em;
+ }
+ #viewKeyModal {
+ padding: var(--spacing-xxl);
+ width: 50em;
+ }
+ .closeButton {
+ bottom: 2em;
+ position: absolute;
+ right: 2em;
+ }
+ #existing {
+ margin-bottom: var(--spacing-l);
+ }
+ iron-autogrow-textarea {
+ background-color: var(--view-background-color);
+ }
+ `,
+ ];
+ }
override render() {
return html`
diff --git a/polygerrit-ui/app/elements/settings/gr-identities/gr-identities.ts b/polygerrit-ui/app/elements/settings/gr-identities/gr-identities.ts
index 7f67ea8..4c8e8da 100644
--- a/polygerrit-ui/app/elements/settings/gr-identities/gr-identities.ts
+++ b/polygerrit-ui/app/elements/settings/gr-identities/gr-identities.ts
@@ -36,37 +36,39 @@
private readonly restApiService = getAppContext().restApiService;
- static override styles = [
- sharedStyles,
- formStyles,
- modalStyles,
- css`
- tr th.emailAddressHeader,
- tr th.identityHeader {
- width: 15em;
- padding: 0 10px;
- }
- tr td.statusColumn,
- tr td.emailAddressColumn,
- tr td.identityColumn {
- word-break: break-word;
- }
- tr td.emailAddressColumn,
- tr td.identityColumn {
- padding: 4px 10px;
- width: 15em;
- }
- .deleteButton {
- float: right;
- }
- .deleteButton:not(.show) {
- display: none;
- }
- .space {
- margin-bottom: var(--spacing-l);
- }
- `,
- ];
+ static override get styles() {
+ return [
+ sharedStyles,
+ formStyles,
+ modalStyles,
+ css`
+ tr th.emailAddressHeader,
+ tr th.identityHeader {
+ width: 15em;
+ padding: 0 10px;
+ }
+ tr td.statusColumn,
+ tr td.emailAddressColumn,
+ tr td.identityColumn {
+ word-break: break-word;
+ }
+ tr td.emailAddressColumn,
+ tr td.identityColumn {
+ padding: 4px 10px;
+ width: 15em;
+ }
+ .deleteButton {
+ float: right;
+ }
+ .deleteButton:not(.show) {
+ display: none;
+ }
+ .space {
+ margin-bottom: var(--spacing-l);
+ }
+ `,
+ ];
+ }
override render() {
return html`<div class="gr-form-styles">
diff --git a/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor.ts b/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor.ts
index 9c23857..b00529a 100644
--- a/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor.ts
+++ b/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor.ts
@@ -48,31 +48,33 @@
);
}
- static override styles = [
- formStyles,
- sharedStyles,
- fontStyles,
- menuPageStyles,
- css`
- .buttonColumn {
- width: 2em;
- }
- .moveUpButton,
- .moveDownButton {
- width: 100%;
- }
- tbody tr:first-of-type td .moveUpButton,
- tbody tr:last-of-type td .moveDownButton {
- display: none;
- }
- td.urlCell {
- word-break: break-word;
- }
- .newUrlInput {
- min-width: 23em;
- }
- `,
- ];
+ static override get styles() {
+ return [
+ formStyles,
+ sharedStyles,
+ fontStyles,
+ menuPageStyles,
+ css`
+ .buttonColumn {
+ width: 2em;
+ }
+ .moveUpButton,
+ .moveDownButton {
+ width: 100%;
+ }
+ tbody tr:first-of-type td .moveUpButton,
+ tbody tr:last-of-type td .moveDownButton {
+ display: none;
+ }
+ td.urlCell {
+ word-break: break-word;
+ }
+ .newUrlInput {
+ min-width: 23em;
+ }
+ `,
+ ];
+ }
override render() {
const unchanged = deepEqual(this.menuItems, this.originalPrefs.my);
diff --git a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.ts b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.ts
index c6c023e..435eb44 100644
--- a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.ts
+++ b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.ts
@@ -68,50 +68,52 @@
}
}
- static override styles = [
- sharedStyles,
- formStyles,
- css`
- :host {
- display: block;
- }
- main {
- max-width: 46em;
- }
- :host(.loading) main {
- display: none;
- }
- .loadingMessage {
- display: none;
- font-style: italic;
- }
- :host(.loading) .loadingMessage {
- display: block;
- }
- hr {
- margin-top: var(--spacing-l);
- margin-bottom: var(--spacing-l);
- }
- header {
- border-bottom: 1px solid var(--border-color);
- font-weight: var(--font-weight-bold);
- margin-bottom: var(--spacing-l);
- }
- .container {
- padding: var(--spacing-m) var(--spacing-xl);
- }
- footer {
- display: flex;
- justify-content: flex-end;
- }
- footer gr-button {
- margin-left: var(--spacing-l);
- }
- input {
- width: 20em;
- }
- `,
- ];
+ static override get styles() {
+ return [
+ sharedStyles,
+ formStyles,
+ css`
+ :host {
+ display: block;
+ }
+ main {
+ max-width: 46em;
+ }
+ :host(.loading) main {
+ display: none;
+ }
+ .loadingMessage {
+ display: none;
+ font-style: italic;
+ }
+ :host(.loading) .loadingMessage {
+ display: block;
+ }
+ hr {
+ margin-top: var(--spacing-l);
+ margin-bottom: var(--spacing-l);
+ }
+ header {
+ border-bottom: 1px solid var(--border-color);
+ font-weight: var(--font-weight-bold);
+ margin-bottom: var(--spacing-l);
+ }
+ .container {
+ padding: var(--spacing-m) var(--spacing-xl);
+ }
+ footer {
+ display: flex;
+ justify-content: flex-end;
+ }
+ footer gr-button {
+ margin-left: var(--spacing-l);
+ }
+ input {
+ width: 20em;
+ }
+ `,
+ ];
+ }
override render() {
return html`<div class="container gr-form-styles">
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
index 24a18c9..29203d0 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
@@ -303,43 +303,45 @@
});
}
- static override styles = [
- sharedStyles,
- paperStyles,
- fontStyles,
- formStyles,
- menuPageStyles,
- pageNavStyles,
- css`
- :host {
- color: var(--primary-text-color);
- }
- h2 {
- font-family: var(--header-font-family);
- font-size: var(--font-size-h2);
- font-weight: var(--font-weight-h2);
- line-height: var(--line-height-h2);
- }
- .newEmailInput {
- width: 20em;
- }
- #email {
- margin-bottom: var(--spacing-l);
- }
- .filters p {
- margin-bottom: var(--spacing-l);
- }
- .queryExample em {
- color: violet;
- }
- .toggle {
- align-items: center;
- display: flex;
- margin-bottom: var(--spacing-l);
- margin-right: var(--spacing-l);
- }
- `,
- ];
+ static override get styles() {
+ return [
+ sharedStyles,
+ paperStyles,
+ fontStyles,
+ formStyles,
+ menuPageStyles,
+ pageNavStyles,
+ css`
+ :host {
+ color: var(--primary-text-color);
+ }
+ h2 {
+ font-family: var(--header-font-family);
+ font-size: var(--font-size-h2);
+ font-weight: var(--font-weight-h2);
+ line-height: var(--line-height-h2);
+ }
+ .newEmailInput {
+ width: 20em;
+ }
+ #email {
+ margin-bottom: var(--spacing-l);
+ }
+ .filters p {
+ margin-bottom: var(--spacing-l);
+ }
+ .queryExample em {
+ color: violet;
+ }
+ .toggle {
+ align-items: center;
+ display: flex;
+ margin-bottom: var(--spacing-l);
+ margin-right: var(--spacing-l);
+ }
+ `,
+ ];
+ }
override render() {
const isLoading = this.loading || this.loading === undefined;
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 07209db..9045dd6 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
@@ -158,32 +158,34 @@
);
}
- 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)';
- }
- .newlyAdded {
- font-style: italic;
- }
- .list {
- align-items: center;
- display: flex;
- flex-wrap: wrap;
- }
- `,
- ];
+ static override get styles() {
+ return [
+ 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)';
+ }
+ .newlyAdded {
+ font-style: italic;
+ }
+ .list {
+ align-items: center;
+ display: flex;
+ flex-wrap: wrap;
+ }
+ `,
+ ];
+ }
override render() {
return html`<div class="list">
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 fd1311c..210b91c 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
@@ -201,59 +201,63 @@
.inputElement as HTMLInputElement;
}
- static override styles = [
- sharedStyles,
- css`
- paper-input.borderless {
- border: none;
- padding: 0;
- }
- paper-input {
- background-color: var(--view-background-color);
- color: var(--primary-text-color);
- border: 1px solid var(--prominent-border-color, var(--border-color));
- border-radius: var(--border-radius);
- padding: var(--spacing-s);
- --paper-input-container_-_padding: 0;
- --paper-input-container-input_-_font-size: var(--font-size-normal);
- --paper-input-container-input_-_line-height: var(--line-height-normal);
- /* This is a hack for not being able to set height:0 on the underline
+ static override get styles() {
+ return [
+ sharedStyles,
+ css`
+ paper-input.borderless {
+ border: none;
+ padding: 0;
+ }
+ paper-input {
+ background-color: var(--view-background-color);
+ color: var(--primary-text-color);
+ border: 1px solid var(--prominent-border-color, var(--border-color));
+ border-radius: var(--border-radius);
+ padding: var(--spacing-s);
+ --paper-input-container_-_padding: 0;
+ --paper-input-container-input_-_font-size: var(--font-size-normal);
+ --paper-input-container-input_-_line-height: var(
+ --line-height-normal
+ );
+ /* This is a hack for not being able to set height:0 on the underline
of a paper-input 2.2.3 element. All the underline fixes below only
actually work in 3.x.x, so the height must be adjusted directly as
a workaround until we are on Polymer 3. */
- height: var(--line-height-normal);
- --paper-input-container-underline-height: 0;
- --paper-input-container-underline-wrapper-height: 0;
- --paper-input-container-underline-focus-height: 0;
- --paper-input-container-underline-legacy-height: 0;
- --paper-input-container-underline_-_height: 0;
- --paper-input-container-underline_-_display: none;
- --paper-input-container-underline-focus_-_height: 0;
- --paper-input-container-underline-focus_-_display: none;
- --paper-input-container-underline-disabled_-_height: 0;
- --paper-input-container-underline-disabled_-_display: none;
- /* Hide label for input. The label is still visible for
+ height: var(--line-height-normal);
+ --paper-input-container-underline-height: 0;
+ --paper-input-container-underline-wrapper-height: 0;
+ --paper-input-container-underline-focus-height: 0;
+ --paper-input-container-underline-legacy-height: 0;
+ --paper-input-container-underline_-_height: 0;
+ --paper-input-container-underline_-_display: none;
+ --paper-input-container-underline-focus_-_height: 0;
+ --paper-input-container-underline-focus_-_display: none;
+ --paper-input-container-underline-disabled_-_height: 0;
+ --paper-input-container-underline-disabled_-_display: none;
+ /* Hide label for input. The label is still visible for
screen readers. Workaround found at:
https://github.com/PolymerElements/paper-input/issues/478 */
- --paper-input-container-label_-_display: none;
- }
- paper-input.showBlueFocusBorder:focus {
- border: 2px solid var(--input-focus-border-color);
- /*
+ --paper-input-container-label_-_display: none;
+ }
+ paper-input.showBlueFocusBorder:focus {
+ border: 2px solid var(--input-focus-border-color);
+ /*
* The goal is to have a thicker blue border when focused and a thinner
* gray border when blurred. To avoid shifting neighboring elements
* around when the border size changes, a negative margin is added to
* compensate. box-sizing: border-box; will not work since there is
* important padding to add around the content.
*/
- margin: -1px;
- }
- paper-input.warnUncommitted {
- --paper-input-container-input_-_color: var(--error-text-color);
- --paper-input-container-input_-_font-size: inherit;
- }
- `,
- ];
+ margin: -1px;
+ }
+ paper-input.warnUncommitted {
+ --paper-input-container-input_-_color: var(--error-text-color);
+ --paper-input-container-input_-_font-size: inherit;
+ }
+ `,
+ ];
+ }
override connectedCallback() {
super.connectedCallback();
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
index e880531..405409d 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
@@ -56,67 +56,70 @@
* Note: Do not use sharedStyles or other styles here that should not affect
* the generated HTML of the markdown.
*/
- static override styles = [
- css`
- a {
- color: var(--link-color);
- }
- p,
- ul,
- code,
- blockquote {
- margin: 0 0 var(--spacing-m) 0;
- max-width: var(--gr-formatted-text-prose-max-width, none);
- }
- p:last-child,
- ul:last-child,
- blockquote:last-child,
- pre:last-child {
- margin: 0;
- }
- blockquote {
- border-left: var(--spacing-xxs) solid var(--comment-quote-marker-color);
- padding: 0 var(--spacing-m);
- }
- code {
- background-color: var(--background-color-secondary);
- border: var(--spacing-xxs) solid var(--border-color);
- display: block;
- font-family: var(--monospace-font-family);
- font-size: var(--font-size-code);
- line-height: var(--line-height-mono);
- margin: var(--spacing-m) 0;
- padding: var(--spacing-xxs) var(--spacing-s);
- overflow-x: auto;
- /* Pre will preserve whitespace and line breaks but not wrap */
- white-space: pre;
- }
- /* Non-multiline code elements need display:inline to shrink and not take
+ static override get styles() {
+ return [
+ css`
+ a {
+ color: var(--link-color);
+ }
+ p,
+ ul,
+ code,
+ blockquote {
+ margin: 0 0 var(--spacing-m) 0;
+ max-width: var(--gr-formatted-text-prose-max-width, none);
+ }
+ p:last-child,
+ ul:last-child,
+ blockquote:last-child,
+ pre:last-child {
+ margin: 0;
+ }
+ blockquote {
+ border-left: var(--spacing-xxs) solid
+ var(--comment-quote-marker-color);
+ padding: 0 var(--spacing-m);
+ }
+ code {
+ background-color: var(--background-color-secondary);
+ border: var(--spacing-xxs) solid var(--border-color);
+ display: block;
+ font-family: var(--monospace-font-family);
+ font-size: var(--font-size-code);
+ line-height: var(--line-height-mono);
+ margin: var(--spacing-m) 0;
+ padding: var(--spacing-xxs) var(--spacing-s);
+ overflow-x: auto;
+ /* Pre will preserve whitespace and line breaks but not wrap */
+ white-space: pre;
+ }
+ /* Non-multiline code elements need display:inline to shrink and not take
a whole row */
- :not(pre) > code {
- display: inline;
- }
- li {
- margin-left: var(--spacing-xl);
- }
- gr-account-chip {
- display: inline;
- }
- .plaintext {
- font: inherit;
- white-space: var(--linked-text-white-space, pre-wrap);
- word-wrap: var(--linked-text-word-wrap, break-word);
- }
- .markdown-html {
- /* code overrides white-space to pre, everything else should wrap as
+ :not(pre) > code {
+ display: inline;
+ }
+ li {
+ margin-left: var(--spacing-xl);
+ }
+ gr-account-chip {
+ display: inline;
+ }
+ .plaintext {
+ font: inherit;
+ white-space: var(--linked-text-white-space, pre-wrap);
+ word-wrap: var(--linked-text-word-wrap, break-word);
+ }
+ .markdown-html {
+ /* code overrides white-space to pre, everything else should wrap as
normal. */
- white-space: normal;
- /* prose will automatically wrap but inline <code> blocks won't and we
+ white-space: normal;
+ /* prose will automatically wrap but inline <code> blocks won't and we
should overflow in that case rather than wrapping or leaking out */
- overflow-x: auto;
- }
- `,
- ];
+ overflow-x: auto;
+ }
+ `,
+ ];
+ }
constructor() {
super();
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts
index 95f1b8a..c29417e 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.ts
@@ -175,53 +175,55 @@
}
}
- static override styles = [
- sharedStyles,
- css`
- :host {
- display: flex;
- position: relative;
- }
- :host(.monospace) {
- font-family: var(--monospace-font-family);
- font-size: var(--font-size-mono);
- line-height: var(--line-height-mono);
- font-weight: var(--font-weight-normal);
- }
- :host(.code) {
- font-family: var(--monospace-font-family);
- font-size: var(--font-size-code);
- /* usually 16px = 12px + 4px */
- line-height: calc(var(--font-size-code) + var(--spacing-s));
- font-weight: var(--font-weight-normal);
- }
- #emojiSuggestions {
- font-family: var(--font-family);
- }
- #textarea {
- background-color: var(--view-background-color);
- width: 100%;
- }
- #hiddenText #emojiSuggestions {
- visibility: visible;
- white-space: normal;
- }
- iron-autogrow-textarea {
- position: relative;
- }
- #textarea.noBorder {
- border: none;
- }
- #hiddenText {
- display: block;
- float: left;
- position: absolute;
- visibility: hidden;
- width: 100%;
- white-space: pre-wrap;
- }
- `,
- ];
+ static override get styles() {
+ return [
+ sharedStyles,
+ css`
+ :host {
+ display: flex;
+ position: relative;
+ }
+ :host(.monospace) {
+ font-family: var(--monospace-font-family);
+ font-size: var(--font-size-mono);
+ line-height: var(--line-height-mono);
+ font-weight: var(--font-weight-normal);
+ }
+ :host(.code) {
+ font-family: var(--monospace-font-family);
+ font-size: var(--font-size-code);
+ /* usually 16px = 12px + 4px */
+ line-height: calc(var(--font-size-code) + var(--spacing-s));
+ font-weight: var(--font-weight-normal);
+ }
+ #emojiSuggestions {
+ font-family: var(--font-family);
+ }
+ #textarea {
+ background-color: var(--view-background-color);
+ width: 100%;
+ }
+ #hiddenText #emojiSuggestions {
+ visibility: visible;
+ white-space: normal;
+ }
+ iron-autogrow-textarea {
+ position: relative;
+ }
+ #textarea.noBorder {
+ border: none;
+ }
+ #hiddenText {
+ display: block;
+ float: left;
+ position: absolute;
+ visibility: hidden;
+ width: 100%;
+ white-space: pre-wrap;
+ }
+ `,
+ ];
+ }
override render() {
return html`
diff --git a/polygerrit-ui/app/elements/shared/gr-user-suggestion-fix/gr-user-suggestion-fix.ts b/polygerrit-ui/app/elements/shared/gr-user-suggestion-fix/gr-user-suggestion-fix.ts
index ff068a7..ea67661 100644
--- a/polygerrit-ui/app/elements/shared/gr-user-suggestion-fix/gr-user-suggestion-fix.ts
+++ b/polygerrit-ui/app/elements/shared/gr-user-suggestion-fix/gr-user-suggestion-fix.ts
@@ -25,42 +25,44 @@
export class GrUserSuggetionFix extends LitElement {
private readonly flagsService = getAppContext().flagsService;
- static override styles = [
- css`
- .header {
- background-color: var(--background-color-primary);
- border: 1px solid var(--border-color);
- padding: var(--spacing-xs) var(--spacing-xl);
- display: flex;
- align-items: center;
- border-top-left-radius: var(--border-radius);
- border-top-right-radius: var(--border-radius);
- }
- .header .title {
- flex: 1;
- }
- .copyButton {
- margin-right: var(--spacing-l);
- }
- code {
- max-width: var(--gr-formatted-text-prose-max-width, none);
- background-color: var(--background-color-secondary);
- border: 1px solid var(--border-color);
- border-top: 0;
- display: block;
- font-family: var(--monospace-font-family);
- font-size: var(--font-size-code);
- line-height: var(--line-height-mono);
- margin-bottom: var(--spacing-m);
- padding: var(--spacing-xxs) var(--spacing-s);
- overflow-x: auto;
- /* Pre will preserve whitespace and line breaks but not wrap */
- white-space: pre;
- border-bottom-left-radius: var(--border-radius);
- border-bottom-right-radius: var(--border-radius);
- }
- `,
- ];
+ static override get styles() {
+ return [
+ css`
+ .header {
+ background-color: var(--background-color-primary);
+ border: 1px solid var(--border-color);
+ padding: var(--spacing-xs) var(--spacing-xl);
+ display: flex;
+ align-items: center;
+ border-top-left-radius: var(--border-radius);
+ border-top-right-radius: var(--border-radius);
+ }
+ .header .title {
+ flex: 1;
+ }
+ .copyButton {
+ margin-right: var(--spacing-l);
+ }
+ code {
+ max-width: var(--gr-formatted-text-prose-max-width, none);
+ background-color: var(--background-color-secondary);
+ border: 1px solid var(--border-color);
+ border-top: 0;
+ display: block;
+ font-family: var(--monospace-font-family);
+ font-size: var(--font-size-code);
+ line-height: var(--line-height-mono);
+ margin-bottom: var(--spacing-m);
+ padding: var(--spacing-xxs) var(--spacing-s);
+ overflow-x: auto;
+ /* Pre will preserve whitespace and line breaks but not wrap */
+ white-space: pre;
+ border-bottom-left-radius: var(--border-radius);
+ border-bottom-right-radius: var(--border-radius);
+ }
+ `,
+ ];
+ }
override render() {
if (!this.flagsService.isEnabled(KnownExperimentId.SUGGEST_EDIT)) {
diff --git a/polygerrit-ui/app/embed/diff-old/gr-context-controls/gr-context-controls.ts b/polygerrit-ui/app/embed/diff-old/gr-context-controls/gr-context-controls.ts
index 5695f4d..e4afd23 100644
--- a/polygerrit-ui/app/embed/diff-old/gr-context-controls/gr-context-controls.ts
+++ b/polygerrit-ui/app/embed/diff-old/gr-context-controls/gr-context-controls.ts
@@ -99,112 +99,123 @@
linesToExpand: number;
}>();
- static override styles = css`
- :host {
- display: flex;
- justify-content: center;
- flex-direction: column;
- position: relative;
- }
+ static override get styles() {
+ return [
+ css`
+ :host {
+ display: flex;
+ justify-content: center;
+ flex-direction: column;
+ position: relative;
+ }
- :host([showConfig='above']) {
- justify-content: flex-end;
- margin-top: calc(-1px - var(--line-height-normal) - var(--spacing-s));
- margin-bottom: var(--gr-context-controls-margin-bottom);
- height: calc(var(--line-height-normal) + var(--spacing-s));
- .horizontalFlex {
- align-items: end;
- }
- }
+ :host([showConfig='above']) {
+ justify-content: flex-end;
+ margin-top: calc(-1px - var(--line-height-normal) - var(--spacing-s));
+ margin-bottom: var(--gr-context-controls-margin-bottom);
+ height: calc(var(--line-height-normal) + var(--spacing-s));
+ .horizontalFlex {
+ align-items: end;
+ }
+ }
- :host([showConfig='below']) {
- justify-content: flex-start;
- margin-top: 1px;
- margin-bottom: calc(0px - var(--line-height-normal) - var(--spacing-s));
- .horizontalFlex {
- align-items: start;
- }
- }
+ :host([showConfig='below']) {
+ justify-content: flex-start;
+ margin-top: 1px;
+ margin-bottom: calc(
+ 0px - var(--line-height-normal) - var(--spacing-s)
+ );
+ .horizontalFlex {
+ align-items: start;
+ }
+ }
- :host([showConfig='both']) {
- margin-top: calc(0px - var(--line-height-normal) - var(--spacing-s));
- margin-bottom: calc(0px - var(--line-height-normal) - var(--spacing-s));
- height: calc(
- 2 * var(--line-height-normal) + 2 * var(--spacing-s) +
- var(--divider-height)
- );
- .horizontalFlex {
- align-items: center;
- }
- }
+ :host([showConfig='both']) {
+ margin-top: calc(0px - var(--line-height-normal) - var(--spacing-s));
+ margin-bottom: calc(
+ 0px - var(--line-height-normal) - var(--spacing-s)
+ );
+ height: calc(
+ 2 * var(--line-height-normal) + 2 * var(--spacing-s) +
+ var(--divider-height)
+ );
+ .horizontalFlex {
+ align-items: center;
+ }
+ }
- .contextControlButton {
- background-color: var(--default-button-background-color);
- font: var(--context-control-button-font, inherit);
- }
+ .contextControlButton {
+ background-color: var(--default-button-background-color);
+ font: var(--context-control-button-font, inherit);
+ }
- paper-button {
- text-transform: none;
- align-items: center;
- background-color: var(--background-color);
- font-family: inherit;
- margin: var(--margin, 0);
- min-width: var(--border, 0);
- color: var(--diff-context-control-color);
- border: solid var(--border-color);
- border-width: 1px;
- border-radius: var(--border-radius);
- padding: var(--spacing-s) var(--spacing-l);
- }
+ paper-button {
+ text-transform: none;
+ align-items: center;
+ background-color: var(--background-color);
+ font-family: inherit;
+ margin: var(--margin, 0);
+ min-width: var(--border, 0);
+ color: var(--diff-context-control-color);
+ border: solid var(--border-color);
+ border-width: 1px;
+ border-radius: var(--border-radius);
+ padding: var(--spacing-s) var(--spacing-l);
+ }
- paper-button:hover {
- /* same as defined in gr-button */
- background: rgba(0, 0, 0, 0.12);
- }
- paper-button:focus-visible {
- /* paper-button sets this to 0, thus preventing focus-based styling. */
- outline-width: 1px;
- }
+ paper-button:hover {
+ /* same as defined in gr-button */
+ background: rgba(0, 0, 0, 0.12);
+ }
+ paper-button:focus-visible {
+ /* paper-button sets this to 0, thus preventing focus-based styling. */
+ outline-width: 1px;
+ }
- .aboveBelowButtons {
- display: flex;
- flex-direction: column;
- justify-content: center;
- margin-left: var(--spacing-m);
- position: relative;
- }
- .aboveBelowButtons:first-child {
- margin-left: 0;
- /* Places a default background layer behind the "all button" that can have opacity */
- background-color: var(--default-button-background-color);
- }
+ .aboveBelowButtons {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ margin-left: var(--spacing-m);
+ position: relative;
+ }
+ .aboveBelowButtons:first-child {
+ margin-left: 0;
+ /* Places a default background layer behind the "all button" that can have opacity */
+ background-color: var(--default-button-background-color);
+ }
- .horizontalFlex {
- display: flex;
- justify-content: center;
- align-items: var(--gr-context-controls-horizontal-align-items, center);
- }
+ .horizontalFlex {
+ display: flex;
+ justify-content: center;
+ align-items: var(
+ --gr-context-controls-horizontal-align-items,
+ center
+ );
+ }
- .aboveButton {
- border-bottom-width: 0;
- border-bottom-right-radius: 0;
- border-bottom-left-radius: 0;
- padding: var(--spacing-xxs) var(--spacing-l);
- }
- .belowButton {
- border-top-width: 0;
- border-top-left-radius: 0;
- border-top-right-radius: 0;
- padding: var(--spacing-xxs) var(--spacing-l);
- margin-top: calc(var(--divider-height) + 2 * var(--spacing-xxs));
- }
- .belowButton:first-child {
- margin-top: 0;
- }
- .breadcrumbTooltip {
- white-space: nowrap;
- }
- `;
+ .aboveButton {
+ border-bottom-width: 0;
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+ padding: var(--spacing-xxs) var(--spacing-l);
+ }
+ .belowButton {
+ border-top-width: 0;
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ padding: var(--spacing-xxs) var(--spacing-l);
+ margin-top: calc(var(--divider-height) + 2 * var(--spacing-xxs));
+ }
+ .belowButton:first-child {
+ margin-top: 0;
+ }
+ .breadcrumbTooltip {
+ white-space: nowrap;
+ }
+ `,
+ ];
+ }
constructor() {
super();
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff-highlight/gr-annotation.ts b/polygerrit-ui/app/embed/diff-old/gr-diff-highlight/gr-annotation.ts
index 38bd707..5669bcf 100644
--- a/polygerrit-ui/app/embed/diff-old/gr-diff-highlight/gr-annotation.ts
+++ b/polygerrit-ui/app/embed/diff-old/gr-diff-highlight/gr-annotation.ts
@@ -19,7 +19,7 @@
*/
getLength(node: Node) {
if (node instanceof Comment) return 0;
- return this.getStringLength(node.textContent || '');
+ return GrAnnotation.getStringLength(node.textContent || '');
},
/**
@@ -65,7 +65,7 @@
const nestedNodes: Node[] = [];
for (let node of childNodes) {
- const initialNodeLength = this.getLength(node);
+ const initialNodeLength = GrAnnotation.getLength(node);
// If the current node is completely before the offset.
if (offset > 0 && initialNodeLength <= offset) {
offset -= initialNodeLength;
@@ -73,15 +73,15 @@
}
if (offset > 0) {
- node = this.splitNode(node, offset);
+ node = GrAnnotation.splitNode(node, offset);
offset = 0;
}
- if (this.getLength(node) > length) {
- this.splitNode(node, length);
+ if (GrAnnotation.getLength(node) > length) {
+ GrAnnotation.splitNode(node, length);
}
nestedNodes.push(node);
- length -= this.getLength(node);
+ length -= GrAnnotation.getLength(node);
if (!length) break;
}
@@ -116,7 +116,7 @@
let subLength;
for (const node of nodes) {
- nodeLength = this.getLength(node);
+ nodeLength = GrAnnotation.getLength(node);
// If the current node is completely before the offset.
if (nodeLength <= offset) {
@@ -128,9 +128,9 @@
subLength = Math.min(length, nodeLength - offset);
if (node instanceof Text) {
- this._annotateText(node, offset, subLength, cssClass);
+ GrAnnotation._annotateText(node, offset, subLength, cssClass);
} else if (node instanceof Element) {
- this.annotateElement(node, offset, subLength, cssClass);
+ GrAnnotation.annotateElement(node, offset, subLength, cssClass);
}
// If there is still more to annotate, then shift the indices, otherwise
@@ -172,19 +172,19 @@
firstPart?: boolean
) {
if (
- (this.getLength(node) === offset && firstPart) ||
+ (GrAnnotation.getLength(node) === offset && firstPart) ||
(offset === 0 && !firstPart)
) {
- return this.wrapInHighlight(node, cssClass);
+ return GrAnnotation.wrapInHighlight(node, cssClass);
}
if (firstPart) {
- this.splitNode(node, offset);
+ GrAnnotation.splitNode(node, offset);
// Node points to first part of the Text, second one is sibling.
} else {
// if node is Text then splitNode will return a Text
- node = this.splitNode(node, offset) as Text;
+ node = GrAnnotation.splitNode(node, offset) as Text;
}
- return this.wrapInHighlight(node, cssClass);
+ return GrAnnotation.wrapInHighlight(node, cssClass);
},
/**
@@ -193,7 +193,7 @@
*/
splitNode(element: Node, offset: number) {
if (element instanceof Text) {
- return this.splitTextNode(element, offset);
+ return GrAnnotation.splitTextNode(element, offset);
}
const tail = element.cloneNode(false);
@@ -203,13 +203,14 @@
let node = element.firstChild;
while (
node &&
- (this.getLength(node) <= offset || this.getLength(node) === 0)
+ (GrAnnotation.getLength(node) <= offset ||
+ GrAnnotation.getLength(node) === 0)
) {
- offset -= this.getLength(node);
+ offset -= GrAnnotation.getLength(node);
node = node.nextSibling;
}
- if (node && this.getLength(node) > offset) {
- tail.appendChild(this.splitNode(node, offset));
+ if (node && GrAnnotation.getLength(node) > offset) {
+ tail.appendChild(GrAnnotation.splitNode(node, offset));
}
while (node && node.nextSibling) {
tail.appendChild(node.nextSibling);
@@ -245,7 +246,7 @@
},
_annotateText(node: Text, offset: number, length: number, cssClass: string) {
- const nodeLength = this.getLength(node);
+ const nodeLength = GrAnnotation.getLength(node);
// There are four cases:
// 1) Entire node is highlighted.
@@ -255,17 +256,17 @@
if (offset === 0 && nodeLength === length) {
// Case 1.
- this.wrapInHighlight(node, cssClass);
+ GrAnnotation.wrapInHighlight(node, cssClass);
} else if (offset === 0) {
// Case 2.
- this.splitAndWrapInHighlight(node, length, cssClass, true);
+ GrAnnotation.splitAndWrapInHighlight(node, length, cssClass, true);
} else if (offset + length === nodeLength) {
// Case 3
- this.splitAndWrapInHighlight(node, offset, cssClass, false);
+ GrAnnotation.splitAndWrapInHighlight(node, offset, cssClass, false);
} else {
// Case 4
- this.splitAndWrapInHighlight(
- this.splitTextNode(node, offset),
+ GrAnnotation.splitAndWrapInHighlight(
+ GrAnnotation.splitTextNode(node, offset),
length,
cssClass,
true
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff/gr-diff.ts b/polygerrit-ui/app/embed/diff-old/gr-diff/gr-diff.ts
index 2aa8096..0da3522 100644
--- a/polygerrit-ui/app/embed/diff-old/gr-diff/gr-diff.ts
+++ b/polygerrit-ui/app/embed/diff-old/gr-diff/gr-diff.ts
@@ -374,6 +374,7 @@
private renderContainer() {
const cssClasses = {
+ oldDiff: true,
diffContainer: true,
unified: this.viewMode === DiffViewMode.UNIFIED,
sideBySide: this.viewMode === DiffViewMode.SIDE_BY_SIDE,
diff --git a/polygerrit-ui/app/embed/diff-old/gr-diff/gr-diff_test.ts b/polygerrit-ui/app/embed/diff-old/gr-diff/gr-diff_test.ts
index 645a64a..af3a492 100644
--- a/polygerrit-ui/app/embed/diff-old/gr-diff/gr-diff_test.ts
+++ b/polygerrit-ui/app/embed/diff-old/gr-diff/gr-diff_test.ts
@@ -61,7 +61,7 @@
assert.shadowDom.equal(
element,
/* HTML */ `
- <div class="diffContainer sideBySide">
+ <div class="diffContainer oldDiff sideBySide">
<table id="diffTable"></table>
</div>
`
@@ -77,7 +77,7 @@
assert.shadowDom.equal(
element,
/* HTML */ `
- <div class="diffContainer unified">
+ <div class="diffContainer oldDiff unified">
<table class="selected-right" id="diffTable">
<colgroup>
<col class="blame gr-diff" />
@@ -1343,7 +1343,7 @@
assert.shadowDom.equal(
element,
/* HTML */ `
- <div class="diffContainer sideBySide">
+ <div class="diffContainer oldDiff sideBySide">
<table class="selected-right" id="diffTable">
<colgroup>
<col class="blame gr-diff" />
@@ -3141,7 +3141,7 @@
assert.shadowDom.equal(
element,
/* HTML */ `
- <div class="diffContainer sideBySide">
+ <div class="diffContainer oldDiff sideBySide">
<gr-diff-section class="left-FILE right-FILE"> </gr-diff-section>
<gr-diff-row class="left-FILE right-FILE"> </gr-diff-row>
<table class="selected-right" id="diffTable">
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
index 36e1f1a..e7ae8a4 100644
--- 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
@@ -12,6 +12,9 @@
import {getShowConfig} from './gr-context-controls';
import {ifDefined} from 'lit/directives/if-defined.js';
import {when} from 'lit/directives/when.js';
+import {subscribe} from '../../../elements/lit/subscription-controller';
+import {resolve} from '../../../models/dependency';
+import {diffModelToken} from '../gr-diff-model/gr-diff-model';
export class GrContextControlsSection extends LitElement {
/** Should context controls be rendered for expanding above the section? */
@@ -38,6 +41,19 @@
@state()
addTableWrapperForTesting = false;
+ @state() viewMode: DiffViewMode = DiffViewMode.SIDE_BY_SIDE;
+
+ private readonly getDiffModel = resolve(this, diffModelToken);
+
+ constructor() {
+ super();
+ subscribe(
+ this,
+ () => this.getDiffModel().viewMode$,
+ viewMode => (this.viewMode = viewMode)
+ );
+ }
+
/**
* The browser API for handling selection does not (yet) work for selection
* across multiple shadow DOM elements. So we are rendering gr-diff components
@@ -82,7 +98,7 @@
}
private isSideBySide() {
- return this.renderPrefs?.view_mode !== DiffViewMode.UNIFIED;
+ return this.viewMode !== DiffViewMode.UNIFIED;
}
private createContextControlRow() {
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
index 6a557fc..d936126 100644
--- 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
@@ -3,7 +3,12 @@
* Copyright 2022 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
+import {
+ DIProviderElement,
+ wrapInProvider,
+} from '../../../models/di-provider-element';
import '../../../test/common-test-setup';
+import {DiffModel, diffModelToken} from '../gr-diff-model/gr-diff-model';
import './gr-context-controls-section';
import {GrContextControlsSection} from './gr-context-controls-section';
import {fixture, html, assert} from '@open-wc/testing';
@@ -12,9 +17,17 @@
let element: GrContextControlsSection;
setup(async () => {
- element = await fixture<GrContextControlsSection>(
- html`<gr-context-controls-section></gr-context-controls-section>`
- );
+ const diffModel = new DiffModel();
+ element = (
+ await fixture<DIProviderElement>(
+ wrapInProvider(
+ html`<gr-context-controls-section></gr-context-controls-section>`,
+ diffModelToken,
+ diffModel
+ )
+ )
+ ).querySelector<GrContextControlsSection>('gr-context-controls-section')!;
+
element.addTableWrapperForTesting = true;
await element.updateComplete;
});
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 43c8113..b2c0fcb 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
@@ -99,112 +99,123 @@
linesToExpand: number;
}>();
- static override styles = css`
- :host {
- display: flex;
- justify-content: center;
- flex-direction: column;
- position: relative;
- }
+ static override get styles() {
+ return [
+ css`
+ :host {
+ display: flex;
+ justify-content: center;
+ flex-direction: column;
+ position: relative;
+ }
- :host([showConfig='above']) {
- justify-content: flex-end;
- margin-top: calc(-1px - var(--line-height-normal) - var(--spacing-s));
- margin-bottom: var(--gr-context-controls-margin-bottom);
- height: calc(var(--line-height-normal) + var(--spacing-s));
- .horizontalFlex {
- align-items: end;
- }
- }
+ :host([showConfig='above']) {
+ justify-content: flex-end;
+ margin-top: calc(-1px - var(--line-height-normal) - var(--spacing-s));
+ margin-bottom: var(--gr-context-controls-margin-bottom);
+ height: calc(var(--line-height-normal) + var(--spacing-s));
+ .horizontalFlex {
+ align-items: end;
+ }
+ }
- :host([showConfig='below']) {
- justify-content: flex-start;
- margin-top: 1px;
- margin-bottom: calc(0px - var(--line-height-normal) - var(--spacing-s));
- .horizontalFlex {
- align-items: start;
- }
- }
+ :host([showConfig='below']) {
+ justify-content: flex-start;
+ margin-top: 1px;
+ margin-bottom: calc(
+ 0px - var(--line-height-normal) - var(--spacing-s)
+ );
+ .horizontalFlex {
+ align-items: start;
+ }
+ }
- :host([showConfig='both']) {
- margin-top: calc(0px - var(--line-height-normal) - var(--spacing-s));
- margin-bottom: calc(0px - var(--line-height-normal) - var(--spacing-s));
- height: calc(
- 2 * var(--line-height-normal) + 2 * var(--spacing-s) +
- var(--divider-height)
- );
- .horizontalFlex {
- align-items: center;
- }
- }
+ :host([showConfig='both']) {
+ margin-top: calc(0px - var(--line-height-normal) - var(--spacing-s));
+ margin-bottom: calc(
+ 0px - var(--line-height-normal) - var(--spacing-s)
+ );
+ height: calc(
+ 2 * var(--line-height-normal) + 2 * var(--spacing-s) +
+ var(--divider-height)
+ );
+ .horizontalFlex {
+ align-items: center;
+ }
+ }
- .contextControlButton {
- background-color: var(--default-button-background-color);
- font: var(--context-control-button-font, inherit);
- }
+ .contextControlButton {
+ background-color: var(--default-button-background-color);
+ font: var(--context-control-button-font, inherit);
+ }
- paper-button {
- text-transform: none;
- align-items: center;
- background-color: var(--background-color);
- font-family: inherit;
- margin: var(--margin, 0);
- min-width: var(--border, 0);
- color: var(--diff-context-control-color);
- border: solid var(--border-color);
- border-width: 1px;
- border-radius: var(--border-radius);
- padding: var(--spacing-s) var(--spacing-l);
- }
+ paper-button {
+ text-transform: none;
+ align-items: center;
+ background-color: var(--background-color);
+ font-family: inherit;
+ margin: var(--margin, 0);
+ min-width: var(--border, 0);
+ color: var(--diff-context-control-color);
+ border: solid var(--border-color);
+ border-width: 1px;
+ border-radius: var(--border-radius);
+ padding: var(--spacing-s) var(--spacing-l);
+ }
- paper-button:hover {
- /* same as defined in gr-button */
- background: rgba(0, 0, 0, 0.12);
- }
- paper-button:focus-visible {
- /* paper-button sets this to 0, thus preventing focus-based styling. */
- outline-width: 1px;
- }
+ paper-button:hover {
+ /* same as defined in gr-button */
+ background: rgba(0, 0, 0, 0.12);
+ }
+ paper-button:focus-visible {
+ /* paper-button sets this to 0, thus preventing focus-based styling. */
+ outline-width: 1px;
+ }
- .aboveBelowButtons {
- display: flex;
- flex-direction: column;
- justify-content: center;
- margin-left: var(--spacing-m);
- position: relative;
- }
- .aboveBelowButtons:first-child {
- margin-left: 0;
- /* Places a default background layer behind the "all button" that can have opacity */
- background-color: var(--default-button-background-color);
- }
+ .aboveBelowButtons {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ margin-left: var(--spacing-m);
+ position: relative;
+ }
+ .aboveBelowButtons:first-child {
+ margin-left: 0;
+ /* Places a default background layer behind the "all button" that can have opacity */
+ background-color: var(--default-button-background-color);
+ }
- .horizontalFlex {
- display: flex;
- justify-content: center;
- align-items: var(--gr-context-controls-horizontal-align-items, center);
- }
+ .horizontalFlex {
+ display: flex;
+ justify-content: center;
+ align-items: var(
+ --gr-context-controls-horizontal-align-items,
+ center
+ );
+ }
- .aboveButton {
- border-bottom-width: 0;
- border-bottom-right-radius: 0;
- border-bottom-left-radius: 0;
- padding: var(--spacing-xxs) var(--spacing-l);
- }
- .belowButton {
- border-top-width: 0;
- border-top-left-radius: 0;
- border-top-right-radius: 0;
- padding: var(--spacing-xxs) var(--spacing-l);
- margin-top: calc(var(--divider-height) + 2 * var(--spacing-xxs));
- }
- .belowButton:first-child {
- margin-top: 0;
- }
- .breadcrumbTooltip {
- white-space: nowrap;
- }
- `;
+ .aboveButton {
+ border-bottom-width: 0;
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+ padding: var(--spacing-xxs) var(--spacing-l);
+ }
+ .belowButton {
+ border-top-width: 0;
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ padding: var(--spacing-xxs) var(--spacing-l);
+ margin-top: calc(var(--divider-height) + 2 * var(--spacing-xxs));
+ }
+ .belowButton:first-child {
+ margin-top: 0;
+ }
+ .breadcrumbTooltip {
+ white-space: nowrap;
+ }
+ `,
+ ];
+ }
constructor() {
super();
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-binary.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-binary.ts
deleted file mode 100644
index 7ace605..0000000
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-binary.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-/**
- * @license
- * Copyright 2017 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import {GrDiffBuilder} from './gr-diff-builder';
-import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
-import {createElementDiff} from '../gr-diff/gr-diff-utils';
-import {GrDiffGroup} from '../gr-diff/gr-diff-group';
-import {html, render} from 'lit';
-import {FILE} from '../../../api/diff';
-
-export class GrDiffBuilderBinary extends GrDiffBuilder {
- constructor(
- diff: DiffInfo,
- prefs: DiffPreferencesInfo,
- outputEl: HTMLElement
- ) {
- super(diff, prefs, outputEl);
- }
-
- override buildSectionElement(group: GrDiffGroup): HTMLElement {
- const section = createElementDiff('tbody', 'binary-diff');
- // Do not create a diff row for LOST.
- if (group.lines[0].beforeNumber !== FILE) return section;
- return super.buildSectionElement(group);
- }
-
- public renderBinaryDiff() {
- render(
- html`
- <tbody class="gr-diff binary-diff">
- <tr class="gr-diff">
- <td colspan="5" class="gr-diff">
- <span>Difference in binary files</span>
- </td>
- </tr>
- </tbody>
- `,
- this.outputEl
- );
- }
-}
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-image.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-image.ts
index eeb07d8..fd3b975 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-image.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-image.ts
@@ -4,83 +4,16 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {ImageInfo} from '../../../types/common';
-import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
-import {FILE, RenderPreferences, Side} from '../../../api/diff';
+import {Side} from '../../../api/diff';
import '../gr-diff-image-viewer/gr-image-viewer';
import {html, LitElement, nothing} from 'lit';
import {property, query, state} from 'lit/decorators.js';
-import {GrDiffBuilder} from './gr-diff-builder';
-import {GrDiffGroup} from '../gr-diff/gr-diff-group';
-import {isNewDiff, createElementDiff} from '../gr-diff/gr-diff-utils';
+import {isNewDiff} from '../gr-diff/gr-diff-utils';
// MIME types for images we allow showing. Do not include SVG, it can contain
// arbitrary JavaScript.
const IMAGE_MIME_PATTERN = /^image\/(bmp|gif|x-icon|jpeg|jpg|png|tiff|webp)$/;
-export class GrDiffBuilderImage extends GrDiffBuilder {
- constructor(
- diff: DiffInfo,
- prefs: DiffPreferencesInfo,
- outputEl: HTMLElement,
- private readonly baseImage: ImageInfo | null,
- private readonly revisionImage: ImageInfo | null,
- renderPrefs?: RenderPreferences,
- private readonly useNewImageDiffUi: boolean = false
- ) {
- super(diff, prefs, outputEl, [], renderPrefs);
- }
-
- override buildSectionElement(group: GrDiffGroup): HTMLElement {
- const section = createElementDiff('tbody');
- // Do not create a diff row for LOST.
- if (group.lines[0].beforeNumber !== FILE) return section;
- return super.buildSectionElement(group);
- }
-
- public renderImageDiff() {
- const imageDiff = this.useNewImageDiffUi
- ? this.createImageDiffNew()
- : this.createImageDiffOld();
- this.outputEl.appendChild(imageDiff);
- }
-
- private createImageDiffNew() {
- // TODO(newdiff-cleanup): Remove cast when newdiff migration is complete.
- const imageDiff = document.createElement(
- 'gr-diff-image-new'
- ) as GrDiffImageNew;
- imageDiff.automaticBlink = this.autoBlink();
- imageDiff.baseImage = this.baseImage ?? undefined;
- imageDiff.revisionImage = this.revisionImage ?? undefined;
- return imageDiff;
- }
-
- private createImageDiffOld() {
- // TODO(newdiff-cleanup): Remove cast when newdiff migration is complete.
- const imageDiff = document.createElement(
- 'gr-diff-image-old'
- ) as GrDiffImageOld;
- imageDiff.baseImage = this.baseImage ?? undefined;
- imageDiff.revisionImage = this.revisionImage ?? undefined;
- return imageDiff;
- }
-
- private autoBlink(): boolean {
- return !!this.renderPrefs?.image_diff_prefs?.automatic_blink;
- }
-
- override updateRenderPrefs(renderPrefs: RenderPreferences) {
- this.renderPrefs = renderPrefs;
-
- // We have to update `imageDiff.automaticBlink` manually, because `this` is
- // not a LitElement.
- const imageDiff = this.outputEl.querySelector(
- 'gr-diff-image-new'
- ) as GrDiffImageNew;
- if (imageDiff) imageDiff.automaticBlink = this.autoBlink();
- }
-}
-
class GrDiffImageNew extends LitElement {
@property() baseImage?: ImageInfo;
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
deleted file mode 100644
index bcc54d4..0000000
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder.ts
+++ /dev/null
@@ -1,352 +0,0 @@
-/**
- * @license
- * Copyright 2016 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-import './gr-diff-section';
-import '../gr-context-controls/gr-context-controls';
-import {
- ContentLoadNeededEventDetail,
- DiffContextExpandedExternalDetail,
- DiffViewMode,
- LineNumber,
- RenderPreferences,
-} from '../../../api/diff';
-import {GrDiffGroup} from '../gr-diff/gr-diff-group';
-import {BlameInfo} from '../../../types/common';
-import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
-import {Side} from '../../../constants/constants';
-import {DiffLayer, isDefined} from '../../../types/types';
-import {GrDiffRow} from './gr-diff-row';
-import {GrDiffSection} from './gr-diff-section';
-import {html, render} from 'lit';
-import {diffClasses} from '../gr-diff/gr-diff-utils';
-import {when} from 'lit/directives/when.js';
-import {GrDiffBuilderImage} from './gr-diff-builder-image';
-import {GrDiffBuilderBinary} from './gr-diff-builder-binary';
-
-export interface DiffContextExpandedEventDetail
- extends DiffContextExpandedExternalDetail {
- /** The context control group that should be replaced by `groups`. */
- contextGroup: GrDiffGroup;
- groups: GrDiffGroup[];
-}
-
-declare global {
- interface HTMLElementEventMap {
- 'diff-context-expanded-internal-new': CustomEvent<DiffContextExpandedEventDetail>;
- 'diff-context-expanded': CustomEvent<DiffContextExpandedExternalDetail>;
- 'content-load-needed': CustomEvent<ContentLoadNeededEventDetail>;
- }
-}
-
-export function isImageDiffBuilder<T extends GrDiffBuilder>(
- x: T | GrDiffBuilderImage | undefined
-): x is GrDiffBuilderImage {
- return !!x && !!(x as GrDiffBuilderImage).renderImageDiff;
-}
-
-export function isBinaryDiffBuilder<T extends GrDiffBuilder>(
- x: T | GrDiffBuilderBinary | undefined
-): x is GrDiffBuilderBinary {
- return !!x && !!(x as GrDiffBuilderBinary).renderBinaryDiff;
-}
-
-/**
- * The builder takes GrDiffGroups, and builds the corresponding DOM elements,
- * called sections. Only the builder should add or remove sections from the
- * DOM. Callers can use the ...group() methods to modify groups and thus cause
- * rendering changes.
- */
-export class GrDiffBuilder {
- private readonly diff: DiffInfo;
-
- readonly prefs: DiffPreferencesInfo;
-
- renderPrefs?: RenderPreferences;
-
- readonly outputEl: HTMLElement;
-
- private groups: GrDiffGroup[];
-
- private readonly layerUpdateListener: (
- start: LineNumber,
- end: LineNumber,
- side: Side
- ) => void;
-
- constructor(
- diff: DiffInfo,
- prefs: DiffPreferencesInfo,
- outputEl: HTMLElement,
- readonly layers: DiffLayer[] = [],
- renderPrefs?: RenderPreferences
- ) {
- this.diff = diff;
- this.prefs = prefs;
- this.renderPrefs = renderPrefs;
- this.outputEl = outputEl;
- this.groups = [];
-
- if (isNaN(prefs.tab_size) || prefs.tab_size <= 0) {
- throw Error('Invalid tab size from preferences.');
- }
-
- if (isNaN(prefs.line_length) || prefs.line_length <= 0) {
- throw Error('Invalid line length from preferences.');
- }
-
- this.layerUpdateListener = (
- start: LineNumber,
- end: LineNumber,
- side: Side
- ) => this.renderContentByRange(start, end, side);
- this.init();
- }
-
- getContentTdByLine(
- lineNumber: LineNumber,
- side?: Side
- ): HTMLTableCellElement | undefined {
- if (!side) return undefined;
- const row = this.findRow(lineNumber, side);
- return row?.getContentCell(side);
- }
-
- getLineElByNumber(
- lineNumber: LineNumber,
- side?: Side
- ): HTMLTableCellElement | undefined {
- if (!side) return undefined;
- const row = this.findRow(lineNumber, side);
- return row?.getLineNumberCell(side);
- }
-
- 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();
- }
-
- getLineNumberRows(): HTMLTableRowElement[] {
- const rows = this.getDiffRows();
- return rows.map(r => r.getTableRow()).filter(isDefined);
- }
-
- getLineNumEls(side: Side): HTMLTableCellElement[] {
- const rows = this.getDiffRows();
- return rows.map(r => r.getLineNumberCell(side)).filter(isDefined);
- }
-
- /** This is used when layers initiate an update. */
- renderContentByRange(start: LineNumber, end: LineNumber, side: Side) {
- 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 {
- const leftClass = `left-${group.startLine(Side.LEFT)}`;
- const rightClass = `right-${group.startLine(Side.RIGHT)}`;
- return (
- this.outputEl.querySelector<GrDiffSection>(
- `gr-diff-section.${leftClass}.${rightClass}`
- ) ?? undefined
- );
- }
-
- buildSectionElement(group: GrDiffGroup): HTMLElement {
- const leftCl = `left-${group.startLine(Side.LEFT)}`;
- const rightCl = `right-${group.startLine(Side.RIGHT)}`;
- 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>
- `;
- // When using Lit's `render()` method it wants to be in full control of the
- // element that it renders into, so we let it render into a temp element.
- // Rendering into the diff table directly would interfere with
- // `clearDiffContent()`for example.
- // TODO: Convert <gr-diff> to be fully lit controlled and incorporate this
- // method into Lit's `render()` cycle.
- const tempEl = document.createElement('div');
- render(section, tempEl);
- const sectionEl = tempEl.firstElementChild as GrDiffSection;
- return sectionEl;
- }
-
- addColumns(outputEl: HTMLElement, lineNumberWidth: number): void {
- const colgroup = html`
- <colgroup>
- <col class=${diffClasses('blame')}></col>
- ${when(
- this.renderPrefs?.view_mode === DiffViewMode.UNIFIED,
- () => html` ${this.renderUnifiedColumns(lineNumberWidth)} `,
- () => html`
- ${this.renderSideBySideColumns(Side.LEFT, lineNumberWidth)}
- ${this.renderSideBySideColumns(Side.RIGHT, lineNumberWidth)}
- `
- )}
- </colgroup>
- `;
- // When using Lit's `render()` method it wants to be in full control of the
- // element that it renders into, so we let it render into a temp element.
- // Rendering into the diff table directly would interfere with
- // `clearDiffContent()`for example.
- // TODO: Convert <gr-diff> to be fully lit controlled and incorporate this
- // method into Lit's `render()` cycle.
- const tempEl = document.createElement('div');
- render(colgroup, tempEl);
- const colgroupEl = tempEl.firstElementChild as HTMLElement;
- outputEl.appendChild(colgroupEl);
- }
-
- private renderUnifiedColumns(lineNumberWidth: number) {
- return html`
- <col class=${diffClasses()} width=${lineNumberWidth}></col>
- <col class=${diffClasses()} width=${lineNumberWidth}></col>
- <col class=${diffClasses()}></col>
- `;
- }
-
- private renderSideBySideColumns(side: Side, lineNumberWidth: number) {
- return html`
- <col class=${diffClasses(side)} width=${lineNumberWidth}></col>
- <col class=${diffClasses(side, 'sign')}></col>
- <col class=${diffClasses(side)}></col>
- `;
- }
-
- /**
- * This is meant to be called when the gr-diff component re-connects, or when
- * the diff is (re-)rendered.
- *
- * Make sure that this method is symmetric with cleanup(), which is called
- * when gr-diff disconnects.
- */
- init() {
- this.cleanup();
- for (const layer of this.layers) {
- if (layer.addListener) {
- layer.addListener(this.layerUpdateListener);
- }
- }
- }
-
- /**
- * This is meant to be called when the gr-diff component disconnects, or when
- * the diff is (re-)rendered.
- *
- * Make sure that this method is symmetric with init(), which is called when
- * gr-diff re-connects.
- */
- cleanup() {
- for (const layer of this.layers) {
- if (layer.removeListener) {
- layer.removeListener(this.layerUpdateListener);
- }
- }
- }
-
- addGroups(groups: readonly GrDiffGroup[]) {
- for (const group of groups) {
- this.groups.push(group);
- this.emitGroup(group);
- }
- }
-
- clearGroups() {
- for (const deletedGroup of this.groups) {
- deletedGroup.element?.remove();
- }
- this.groups = [];
- }
-
- replaceGroup(contextControl: GrDiffGroup, groups: readonly GrDiffGroup[]) {
- const i = this.groups.indexOf(contextControl);
- if (i === -1) throw new Error('cannot find context control group');
-
- const contextControlSection = this.groups[i].element;
- if (!contextControlSection) throw new Error('diff group element not set');
-
- this.groups.splice(i, 1, ...groups);
- for (const group of groups) {
- this.emitGroup(group, contextControlSection);
- }
- if (contextControlSection) contextControlSection.remove();
- }
-
- findGroup(side: Side, line: LineNumber) {
- return this.groups.find(group => group.containsLine(side, line));
- }
-
- private emitGroup(group: GrDiffGroup, beforeSection?: HTMLElement) {
- const element = this.buildSectionElement(group);
- this.outputEl.insertBefore(element, beforeSection ?? null);
- group.element = element;
- }
-
- // visible for testing
- getGroupsByLineRange(
- startLine: LineNumber,
- endLine: LineNumber,
- side: Side
- ): GrDiffGroup[] {
- const startIndex = this.groups.findIndex(group =>
- group.containsLine(side, startLine)
- );
- if (startIndex === -1) return [];
- let endIndex = this.groups.findIndex(group =>
- group.containsLine(side, endLine)
- );
- // Not all groups may have been processed yet (i.e. this.groups is still
- // incomplete). In that case let's just return *all* groups until the end
- // of the array.
- if (endIndex === -1) endIndex = this.groups.length - 1;
- // The filter preserves the legacy behavior to only return non-context
- // groups
- return this.groups
- .slice(startIndex, endIndex + 1)
- .filter(group => group.lines.length > 0);
- }
-
- /**
- * Set the blame information for the diff. For any already-rendered line,
- * re-render its blame cell content.
- */
- setBlame(blame: BlameInfo[]) {
- for (const blameInfo of blame) {
- for (const range of blameInfo.ranges) {
- for (let line = range.start; line <= range.end; line++) {
- const row = this.findRow(line, Side.LEFT);
- if (row) row.blameInfo = blameInfo;
- }
- }
- }
- }
-
- /**
- * Only special builders need to implement this. The default is to
- * just ignore it.
- */
- updateRenderPrefs(_: RenderPreferences) {}
-}
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
index 51024da..bfd6a0d 100644
--- 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
@@ -21,6 +21,7 @@
import {fire} from '../../../utils/event-util';
import {getBaseUrl} from '../../../utils/url-util';
import './gr-diff-text';
+import '../../../elements/shared/gr-hovercard/gr-hovercard';
import {GrDiffLine} from '../gr-diff/gr-diff-line';
import {diffClasses, isNewDiff, isResponsive} from '../gr-diff/gr-diff-utils';
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
index e02d62b..aad7928 100644
--- 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
@@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {html, LitElement} from 'lit';
-import {property, state} from 'lit/decorators.js';
+import {property, queryAll, state} from 'lit/decorators.js';
import {
DiffInfo,
DiffLayer,
@@ -28,8 +28,14 @@
import {when} from 'lit/directives/when.js';
import {fire} from '../../../utils/event-util';
import {countLines} from '../../../utils/diff-util';
+import {resolve} from '../../../models/dependency';
+import {diffModelToken} from '../gr-diff-model/gr-diff-model';
+import {subscribe} from '../../../elements/lit/subscription-controller';
export class GrDiffSection extends LitElement {
+ @queryAll('gr-diff-row')
+ diffRows?: NodeListOf<GrDiffRow>;
+
@property({type: Object})
group?: GrDiffGroup;
@@ -45,6 +51,9 @@
@property({type: Object})
layers: DiffLayer[] = [];
+ @state()
+ lineLength = 100;
+
/**
* 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
@@ -53,6 +62,24 @@
@state()
addTableWrapperForTesting = false;
+ @state() viewMode: DiffViewMode = DiffViewMode.SIDE_BY_SIDE;
+
+ private readonly getDiffModel = resolve(this, diffModelToken);
+
+ constructor() {
+ super();
+ subscribe(
+ this,
+ () => this.getDiffModel().lineLength$,
+ lineLength => (this.lineLength = lineLength)
+ );
+ subscribe(
+ this,
+ () => this.getDiffModel().viewMode$,
+ viewMode => (this.viewMode = viewMode)
+ );
+ }
+
/**
* The browser API for handling selection does not (yet) work for selection
* across multiple shadow DOM elements. So we are rendering gr-diff components
@@ -64,6 +91,13 @@
return this;
}
+ protected override async getUpdateComplete(): Promise<boolean> {
+ const result = await super.getUpdateComplete();
+ const rows = [...(this.diffRows ?? [])];
+ await Promise.all(rows.map(row => row.updateComplete));
+ return result;
+ }
+
override render() {
if (!this.group) return;
const extras: string[] = [];
@@ -84,11 +118,11 @@
<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)}`;
+ const leftClass = `left-${pair.left.lineNumber(Side.LEFT)}`;
+ const rightClass = `right-${pair.right.lineNumber(Side.RIGHT)}`;
return html`
<gr-diff-row
- class="${leftCl} ${rightCl}"
+ class="${leftClass} ${rightClass}"
.left=${pair.left}
.right=${pair.right}
.layers=${this.layers}
@@ -112,7 +146,7 @@
}
private isUnifiedDiff() {
- return this.renderPrefs?.view_mode === DiffViewMode.UNIFIED;
+ return this.viewMode === DiffViewMode.UNIFIED;
}
getLinePairs() {
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
index 381f9b2..d23c9c5 100644
--- 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
@@ -73,10 +73,7 @@
});
test('unified', async () => {
- element.renderPrefs = {
- ...element.renderPrefs,
- view_mode: DiffViewMode.UNIFIED,
- };
+ element.viewMode = DiffViewMode.UNIFIED;
const row = await waitQueryAndAssert(element, 'tr.moveControls');
// Semantic dom diff has a problem with just comparing table rows or
// cells directly. So as a workaround put the row into an empty test
@@ -162,7 +159,7 @@
data-side="left"
id="left-content-1"
>
- <gr-diff-text> </gr-diff-text>
+ <gr-diff-text>asdf</gr-diff-text>
</div>
<div class="thread-group" data-side="left">
<slot name="left-1"> </slot>
@@ -186,7 +183,7 @@
data-side="right"
id="right-content-1"
>
- <gr-diff-text> </gr-diff-text>
+ <gr-diff-text>asdf </gr-diff-text>
</div>
<div class="thread-group" data-side="right">
<slot name="right-1"> </slot>
@@ -219,7 +216,7 @@
data-side="left"
id="left-content-1"
>
- <gr-diff-text> </gr-diff-text>
+ <gr-diff-text> qwer</gr-diff-text>
</div>
<div class="thread-group" data-side="left">
<slot name="left-1"> </slot>
@@ -243,7 +240,7 @@
data-side="right"
id="right-content-1"
>
- <gr-diff-text> </gr-diff-text>
+ <gr-diff-text>qwer </gr-diff-text>
</div>
<div class="thread-group" data-side="right">
<slot name="right-1"> </slot>
@@ -276,7 +273,7 @@
data-side="left"
id="left-content-1"
>
- <gr-diff-text> </gr-diff-text>
+ <gr-diff-text>zxcv </gr-diff-text>
</div>
<div class="thread-group" data-side="left">
<slot name="left-1"> </slot>
@@ -300,7 +297,7 @@
data-side="right"
id="right-content-1"
>
- <gr-diff-text> </gr-diff-text>
+ <gr-diff-text>zxcv </gr-diff-text>
</div>
<div class="thread-group" data-side="right">
<slot name="right-1"> </slot>
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/token-highlight-layer.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/token-highlight-layer.ts
index e9076aa..9478d13 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/token-highlight-layer.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/token-highlight-layer.ts
@@ -6,7 +6,10 @@
import {DiffLayer} from '../../../types/types';
import {GrDiffLine, Side, TokenHighlightListener} from '../../../api/diff';
import {assertIsDefined} from '../../../utils/common-util';
-import {GrAnnotation} from '../gr-diff-highlight/gr-annotation';
+import {
+ GrAnnotationImpl,
+ getStringLength,
+} from '../gr-diff-highlight/gr-annotation';
import {debounce, DelayedTask} from '../../../utils/async-util';
import {getLineElByChild, getSideByLineEl} from '../gr-diff/gr-diff-utils';
@@ -147,8 +150,8 @@
// This is to correctly count surrogate pairs in text and token.
// If the index calculation becomes a hotspot, we could precompute a code
// unit to code point index map for text before iterating over the results
- const index = GrAnnotation.getStringLength(text.slice(0, match.index));
- const length = GrAnnotation.getStringLength(token);
+ const index = getStringLength(text.slice(0, match.index));
+ const length = getStringLength(token);
atLeastOneTokenMatched = true;
const highlightTypeClass =
@@ -158,7 +161,7 @@
// We add the TOKEN_TEXT_PREFIX class so that we can look up the token later easily
// even if the token element was split up into multiple smaller nodes.
// All parts of a single token will share a common TOKEN_INDEX_PREFIX class within the line of code.
- GrAnnotation.annotateElement(
+ GrAnnotationImpl.annotateElement(
el,
index,
length,
@@ -345,7 +348,7 @@
start_line: line,
start_column: index + 1, // 1-based inclusive
end_line: line,
- end_column: index + GrAnnotation.getStringLength(token), // 1-based inclusive
+ end_column: index + getStringLength(token), // 1-based inclusive
};
this.tokenHighlightListener({token, element, side, range});
}
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/token-highlight-layer_test.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/token-highlight-layer_test.ts
index 5651dcf..8d0050f 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/token-highlight-layer_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/token-highlight-layer_test.ts
@@ -11,7 +11,7 @@
} from '../../../api/diff';
import {GrDiffLine} from '../gr-diff/gr-diff-line';
import {HOVER_DELAY_MS, TokenHighlightLayer} from './token-highlight-layer';
-import {GrAnnotation} from '../gr-diff-highlight/gr-annotation';
+import {GrAnnotationImpl} from '../gr-diff-highlight/gr-annotation';
import {html, render} from 'lit';
import {_testOnly_allTasks} from '../../../utils/async-util';
import {queryAndAssert} from '../../../test/test-utils';
@@ -123,10 +123,13 @@
}
test('annotate adds css token', () => {
- const annotateElementStub = sinon.stub(GrAnnotation, 'annotateElement');
+ const annotateElementStub = sinon.stub(
+ GrAnnotationImpl,
+ 'annotateElement'
+ );
const el = createLine('these are words');
annotate(el);
- assert.isTrue(annotateElementStub.calledThrice);
+ assert.equal(annotateElementStub.callCount, 3);
assertAnnotation(annotateElementStub.args[0], {
parent: el,
offset: 0,
@@ -148,7 +151,10 @@
});
test('annotate adds css tokens w/ emojis', () => {
- const annotateElementStub = sinon.stub(GrAnnotation, 'annotateElement');
+ const annotateElementStub = sinon.stub(
+ GrAnnotationImpl,
+ 'annotateElement'
+ );
const el = createLine('these 💩 are 👨👩👧👦 words');
annotate(el);
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-cursor/gr-diff-cursor_test.ts b/polygerrit-ui/app/embed/diff/gr-diff-cursor/gr-diff-cursor_test.ts
index 61f8551..e5aaafd 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-cursor/gr-diff-cursor_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-cursor/gr-diff-cursor_test.ts
@@ -229,7 +229,7 @@
suite('unified diff', () => {
setup(async () => {
diffElement.viewMode = DiffViewMode.UNIFIED;
- await waitForEventOnce(diffElement, 'render');
+ await diffElement.updateComplete;
cursor.reInitCursor();
});
@@ -457,9 +457,15 @@
.callsFake(() => {
scrollBehaviorDuringMove = cursor.cursorManager.scrollMode;
});
- diffElement.diff = createDiff();
- await diffElement.updateComplete;
- await waitForEventOnce(diffElement, 'render');
+ cursor.dispose();
+ const diff = createDiff();
+ diff.content.push({ab: ['one more line']});
+ diffElement.diff = diff;
+ diffElement.prefs = createDefaultDiffPrefs();
+ await Promise.all([
+ diffElement.updateComplete,
+ waitForEventOnce(diffElement, 'render'),
+ ]);
cursor.reInitCursor();
assert.isFalse(moveToNumStub.called);
assert.isTrue(moveToChunkStub.called);
@@ -478,9 +484,10 @@
cursor.initialLineNumber = 10;
cursor.side = Side.RIGHT;
- diffElement.diff = createDiff();
- await diffElement.updateComplete;
- await waitForEventOnce(diffElement, 'render');
+ cursor.dispose();
+ const diff = createDiff();
+ diff.content.push({ab: ['one more line']});
+ diffElement.diff = diff;
cursor.reInitCursor();
assert.isFalse(moveToChunkStub.called);
assert.isTrue(moveToNumStub.called);
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-annotation.ts b/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-annotation.ts
index 38bd707..bddfcac 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-annotation.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-annotation.ts
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {getSanitizeDOMValue} from '@polymer/polymer/lib/utils/settings';
+import {GrAnnotation} from '../../../api/diff';
// TODO(wyatta): refactor this to be <MARK> rather than <HL>.
const ANNOTATION_TAG = 'HL';
@@ -11,267 +12,271 @@
// Astral code point as per https://mathiasbynens.be/notes/javascript-unicode
const REGEX_ASTRAL_SYMBOL = /[\uD800-\uDBFF][\uDC00-\uDFFF]/;
-export const GrAnnotation = {
- /**
- * The DOM API textContent.length calculation is broken when the text
- * contains Unicode. See https://mathiasbynens.be/notes/javascript-unicode .
- *
- */
- getLength(node: Node) {
- if (node instanceof Comment) return 0;
- return this.getStringLength(node.textContent || '');
- },
+/**
+ * The DOM API textContent.length calculation is broken when the text
+ * contains Unicode. See https://mathiasbynens.be/notes/javascript-unicode .
+ */
+export function getLength(node: Node) {
+ if (node instanceof Comment) return 0;
+ return getStringLength(node.textContent || '');
+}
- /**
- * Returns the number of Unicode code points in the given string
- *
- * This is not necessarily the same as the number of visible symbols.
- * See https://mathiasbynens.be/notes/javascript-unicode for more details.
- */
- getStringLength(str: string) {
- return [...str].length;
- },
+/**
+ * Returns the number of Unicode code points in the given string
+ *
+ * This is not necessarily the same as the number of visible symbols.
+ * See https://mathiasbynens.be/notes/javascript-unicode for more details.
+ */
+export function getStringLength(str: string) {
+ return [...str].length;
+}
- /**
- * Annotates the [offset, offset+length) text segment in the parent with the
- * element definition provided as arguments.
- *
- * @param parent the node whose contents will be annotated.
- * If parent is Text then parent.parentNode must not be null
- * @param offset the 0-based offset from which the annotation will
- * start.
- * @param length of the annotated text.
- * @param elementSpec the spec to create the
- * annotating element.
- */
- annotateWithElement(
- parent: Node,
- offset: number,
- length: number,
- elSpec: ElementSpec
+/**
+ * Annotates the [offset, offset+length) text segment in the parent with the
+ * element definition provided as arguments.
+ *
+ * @param parent the node whose contents will be annotated.
+ * If parent is Text then parent.parentNode must not be null
+ * @param offset the 0-based offset from which the annotation will
+ * start.
+ * @param length of the annotated text.
+ * @param elementSpec the spec to create the
+ * annotating element.
+ */
+export function annotateWithElement(
+ parent: Node,
+ offset: number,
+ length: number,
+ elSpec: ElementSpec
+) {
+ const tagName = elSpec.tagName;
+ const attributes = elSpec.attributes || {};
+ let childNodes: Node[];
+
+ if (parent instanceof Element) {
+ childNodes = Array.from(parent.childNodes);
+ } else if (parent instanceof Text) {
+ childNodes = [parent];
+ parent = parent.parentNode!;
+ } else {
+ return;
+ }
+
+ const nestedNodes: Node[] = [];
+ for (let node of childNodes) {
+ const initialNodeLength = getLength(node);
+ // If the current node is completely before the offset.
+ if (offset > 0 && initialNodeLength <= offset) {
+ offset -= initialNodeLength;
+ continue;
+ }
+
+ if (offset > 0) {
+ node = splitNode(node, offset);
+ offset = 0;
+ }
+ if (getLength(node) > length) {
+ splitNode(node, length);
+ }
+ nestedNodes.push(node);
+
+ length -= getLength(node);
+ if (!length) break;
+ }
+
+ const wrapper = document.createElement(tagName);
+ const sanitizer = getSanitizeDOMValue();
+ for (let [name, value] of Object.entries(attributes)) {
+ if (!value) continue;
+ if (sanitizer) {
+ value = sanitizer(value, name, 'attribute', wrapper) as string;
+ }
+ wrapper.setAttribute(name, value);
+ }
+ for (const inner of nestedNodes) {
+ parent.replaceChild(wrapper, inner);
+ wrapper.appendChild(inner);
+ }
+}
+
+/**
+ * Surrounds the element's text at specified range in an ANNOTATION_TAG
+ * element. If the element has child elements, the range is split and
+ * applied as deeply as possible.
+ */
+export function annotateElement(
+ parent: HTMLElement,
+ offset: number,
+ length: number,
+ cssClass: string
+) {
+ const nodes: Array<HTMLElement | Text> = [].slice.apply(parent.childNodes);
+ let nodeLength;
+ let subLength;
+
+ for (const node of nodes) {
+ nodeLength = getLength(node);
+
+ // If the current node is completely before the offset.
+ if (nodeLength <= offset) {
+ offset -= nodeLength;
+ continue;
+ }
+
+ // Sublength is the annotation length for the current node.
+ subLength = Math.min(length, nodeLength - offset);
+
+ if (node instanceof Text) {
+ _annotateText(node, offset, subLength, cssClass);
+ } else if (node instanceof Element) {
+ annotateElement(node, offset, subLength, cssClass);
+ }
+
+ // If there is still more to annotate, then shift the indices, otherwise
+ // work is done, so break the loop.
+ if (subLength < length) {
+ length -= subLength;
+ offset = 0;
+ } else {
+ break;
+ }
+ }
+}
+
+/**
+ * Wraps node in annotation tag with cssClass, replacing the node in DOM.
+ */
+function wrapInHighlight(node: Element | Text, cssClass: string) {
+ let hl;
+ if (!(node instanceof Text) && node.tagName === ANNOTATION_TAG) {
+ hl = node;
+ hl.classList.add(cssClass);
+ } else {
+ hl = document.createElement(ANNOTATION_TAG);
+ hl.className = cssClass;
+ if (node.parentElement) node.parentElement.replaceChild(hl, node);
+ hl.appendChild(node);
+ }
+ return hl;
+}
+
+/**
+ * Splits Text Node and wraps it in hl with cssClass.
+ * Wraps trailing part after split, tailing one if firstPart is true.
+ */
+function splitAndWrapInHighlight(
+ node: Text,
+ offset: number,
+ cssClass: string,
+ firstPart?: boolean
+) {
+ if (
+ (getLength(node) === offset && firstPart) ||
+ (offset === 0 && !firstPart)
) {
- const tagName = elSpec.tagName;
- const attributes = elSpec.attributes || {};
- let childNodes: Node[];
+ return wrapInHighlight(node, cssClass);
+ }
+ if (firstPart) {
+ splitNode(node, offset);
+ // Node points to first part of the Text, second one is sibling.
+ } else {
+ // if node is Text then splitNode will return a Text
+ node = splitNode(node, offset) as Text;
+ }
+ return wrapInHighlight(node, cssClass);
+}
- if (parent instanceof Element) {
- childNodes = Array.from(parent.childNodes);
- } else if (parent instanceof Text) {
- childNodes = [parent];
- parent = parent.parentNode!;
- } else {
- return;
+/**
+ * Splits Node at offset.
+ * If Node is Element, it's cloned and the node at offset is split too.
+ */
+function splitNode(element: Node, offset: number) {
+ if (element instanceof Text) {
+ return splitTextNode(element, offset);
+ }
+ const tail = element.cloneNode(false);
+
+ if (element.parentElement)
+ element.parentElement.insertBefore(tail, element.nextSibling);
+ // Skip nodes before offset.
+ let node = element.firstChild;
+ while (node && (getLength(node) <= offset || getLength(node) === 0)) {
+ offset -= getLength(node);
+ node = node.nextSibling;
+ }
+ if (node && getLength(node) > offset) {
+ tail.appendChild(splitNode(node, offset));
+ }
+ while (node && node.nextSibling) {
+ tail.appendChild(node.nextSibling);
+ }
+ return tail;
+}
+
+/**
+ * Node.prototype.splitText Unicode-valid alternative.
+ *
+ * DOM Api for splitText() is broken for Unicode:
+ * https://mathiasbynens.be/notes/javascript-unicode
+ *
+ * @return Trailing Text Node.
+ */
+function splitTextNode(node: Text, offset: number) {
+ if (node.textContent?.match(REGEX_ASTRAL_SYMBOL)) {
+ const head = Array.from(node.textContent);
+ const tail = head.splice(offset);
+ const parent = node.parentNode;
+
+ // Split the content of the original node.
+ node.textContent = head.join('');
+
+ const tailNode = document.createTextNode(tail.join(''));
+ if (parent) {
+ parent.insertBefore(tailNode, node.nextSibling);
}
+ return tailNode;
+ } else {
+ return node.splitText(offset);
+ }
+}
- const nestedNodes: Node[] = [];
- for (let node of childNodes) {
- const initialNodeLength = this.getLength(node);
- // If the current node is completely before the offset.
- if (offset > 0 && initialNodeLength <= offset) {
- offset -= initialNodeLength;
- continue;
- }
+function _annotateText(
+ node: Text,
+ offset: number,
+ length: number,
+ cssClass: string
+) {
+ const nodeLength = getLength(node);
- if (offset > 0) {
- node = this.splitNode(node, offset);
- offset = 0;
- }
- if (this.getLength(node) > length) {
- this.splitNode(node, length);
- }
- nestedNodes.push(node);
+ // There are four cases:
+ // 1) Entire node is highlighted.
+ // 2) Highlight is at the start.
+ // 3) Highlight is at the end.
+ // 4) Highlight is in the middle.
- length -= this.getLength(node);
- if (!length) break;
- }
+ if (offset === 0 && nodeLength === length) {
+ // Case 1.
+ wrapInHighlight(node, cssClass);
+ } else if (offset === 0) {
+ // Case 2.
+ splitAndWrapInHighlight(node, length, cssClass, true);
+ } else if (offset + length === nodeLength) {
+ // Case 3
+ splitAndWrapInHighlight(node, offset, cssClass, false);
+ } else {
+ // Case 4
+ splitAndWrapInHighlight(
+ splitTextNode(node, offset),
+ length,
+ cssClass,
+ true
+ );
+ }
+}
- const wrapper = document.createElement(tagName);
- const sanitizer = getSanitizeDOMValue();
- for (let [name, value] of Object.entries(attributes)) {
- if (!value) continue;
- if (sanitizer) {
- value = sanitizer(value, name, 'attribute', wrapper) as string;
- }
- wrapper.setAttribute(name, value);
- }
- for (const inner of nestedNodes) {
- parent.replaceChild(wrapper, inner);
- wrapper.appendChild(inner);
- }
- },
-
- /**
- * Surrounds the element's text at specified range in an ANNOTATION_TAG
- * element. If the element has child elements, the range is split and
- * applied as deeply as possible.
- */
- annotateElement(
- parent: HTMLElement,
- offset: number,
- length: number,
- cssClass: string
- ) {
- const nodes: Array<HTMLElement | Text> = [].slice.apply(parent.childNodes);
- let nodeLength;
- let subLength;
-
- for (const node of nodes) {
- nodeLength = this.getLength(node);
-
- // If the current node is completely before the offset.
- if (nodeLength <= offset) {
- offset -= nodeLength;
- continue;
- }
-
- // Sublength is the annotation length for the current node.
- subLength = Math.min(length, nodeLength - offset);
-
- if (node instanceof Text) {
- this._annotateText(node, offset, subLength, cssClass);
- } else if (node instanceof Element) {
- this.annotateElement(node, offset, subLength, cssClass);
- }
-
- // If there is still more to annotate, then shift the indices, otherwise
- // work is done, so break the loop.
- if (subLength < length) {
- length -= subLength;
- offset = 0;
- } else {
- break;
- }
- }
- },
-
- /**
- * Wraps node in annotation tag with cssClass, replacing the node in DOM.
- */
- wrapInHighlight(node: Element | Text, cssClass: string) {
- let hl;
- if (!(node instanceof Text) && node.tagName === ANNOTATION_TAG) {
- hl = node;
- hl.classList.add(cssClass);
- } else {
- hl = document.createElement(ANNOTATION_TAG);
- hl.className = cssClass;
- if (node.parentElement) node.parentElement.replaceChild(hl, node);
- hl.appendChild(node);
- }
- return hl;
- },
-
- /**
- * Splits Text Node and wraps it in hl with cssClass.
- * Wraps trailing part after split, tailing one if firstPart is true.
- */
- splitAndWrapInHighlight(
- node: Text,
- offset: number,
- cssClass: string,
- firstPart?: boolean
- ) {
- if (
- (this.getLength(node) === offset && firstPart) ||
- (offset === 0 && !firstPart)
- ) {
- return this.wrapInHighlight(node, cssClass);
- }
- if (firstPart) {
- this.splitNode(node, offset);
- // Node points to first part of the Text, second one is sibling.
- } else {
- // if node is Text then splitNode will return a Text
- node = this.splitNode(node, offset) as Text;
- }
- return this.wrapInHighlight(node, cssClass);
- },
-
- /**
- * Splits Node at offset.
- * If Node is Element, it's cloned and the node at offset is split too.
- */
- splitNode(element: Node, offset: number) {
- if (element instanceof Text) {
- return this.splitTextNode(element, offset);
- }
- const tail = element.cloneNode(false);
-
- if (element.parentElement)
- element.parentElement.insertBefore(tail, element.nextSibling);
- // Skip nodes before offset.
- let node = element.firstChild;
- while (
- node &&
- (this.getLength(node) <= offset || this.getLength(node) === 0)
- ) {
- offset -= this.getLength(node);
- node = node.nextSibling;
- }
- if (node && this.getLength(node) > offset) {
- tail.appendChild(this.splitNode(node, offset));
- }
- while (node && node.nextSibling) {
- tail.appendChild(node.nextSibling);
- }
- return tail;
- },
-
- /**
- * Node.prototype.splitText Unicode-valid alternative.
- *
- * DOM Api for splitText() is broken for Unicode:
- * https://mathiasbynens.be/notes/javascript-unicode
- *
- * @return Trailing Text Node.
- */
- splitTextNode(node: Text, offset: number) {
- if (node.textContent?.match(REGEX_ASTRAL_SYMBOL)) {
- const head = Array.from(node.textContent);
- const tail = head.splice(offset);
- const parent = node.parentNode;
-
- // Split the content of the original node.
- node.textContent = head.join('');
-
- const tailNode = document.createTextNode(tail.join(''));
- if (parent) {
- parent.insertBefore(tailNode, node.nextSibling);
- }
- return tailNode;
- } else {
- return node.splitText(offset);
- }
- },
-
- _annotateText(node: Text, offset: number, length: number, cssClass: string) {
- const nodeLength = this.getLength(node);
-
- // There are four cases:
- // 1) Entire node is highlighted.
- // 2) Highlight is at the start.
- // 3) Highlight is at the end.
- // 4) Highlight is in the middle.
-
- if (offset === 0 && nodeLength === length) {
- // Case 1.
- this.wrapInHighlight(node, cssClass);
- } else if (offset === 0) {
- // Case 2.
- this.splitAndWrapInHighlight(node, length, cssClass, true);
- } else if (offset + length === nodeLength) {
- // Case 3
- this.splitAndWrapInHighlight(node, offset, cssClass, false);
- } else {
- // Case 4
- this.splitAndWrapInHighlight(
- this.splitTextNode(node, offset),
- length,
- cssClass,
- true
- );
- }
- },
+export const GrAnnotationImpl: GrAnnotation = {
+ annotateElement,
+ annotateWithElement,
};
/**
@@ -282,3 +287,8 @@
tagName: string;
attributes?: {[attributeName: string]: string | undefined};
}
+
+export const TEST_ONLY = {
+ _annotateText,
+ splitTextNode,
+};
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-annotation_test.ts b/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-annotation_test.ts
index 3e1ce66..15a6a15 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-annotation_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-annotation_test.ts
@@ -4,7 +4,12 @@
* SPDX-License-Identifier: Apache-2.0
*/
import '../../../test/common-test-setup';
-import {GrAnnotation} from './gr-annotation';
+import {
+ TEST_ONLY,
+ annotateElement,
+ annotateWithElement,
+ getStringLength,
+} from './gr-annotation';
import {
getSanitizeDOMValue,
setSanitizeDOMValue,
@@ -27,7 +32,7 @@
});
test('_annotateText length:0 offset:0', () => {
- GrAnnotation._annotateText(textNode, 0, 0, 'foobar');
+ TEST_ONLY._annotateText(textNode, 0, 0, 'foobar');
assert.equal(parent.textContent, str);
assert.equal(
@@ -37,7 +42,7 @@
});
test('_annotateText length:0 offset:1', () => {
- GrAnnotation._annotateText(textNode, 1, 0, 'foobar');
+ TEST_ONLY._annotateText(textNode, 1, 0, 'foobar');
assert.equal(parent.textContent, str);
assert.equal(
@@ -47,7 +52,7 @@
});
test('_annotateText length:0 offset:str.length', () => {
- GrAnnotation._annotateText(textNode, str.length, 0, 'foobar');
+ TEST_ONLY._annotateText(textNode, str.length, 0, 'foobar');
assert.equal(parent.textContent, str);
assert.equal(
@@ -57,7 +62,7 @@
});
test('_annotateText Case 1', () => {
- GrAnnotation._annotateText(textNode, 0, str.length, 'foobar');
+ TEST_ONLY._annotateText(textNode, 0, str.length, 'foobar');
assert.equal(parent.textContent, str);
assert.equal(
@@ -67,7 +72,7 @@
});
test('_annotateText Case 2', () => {
- GrAnnotation._annotateText(textNode, 0, 12, 'foobar');
+ TEST_ONLY._annotateText(textNode, 0, 12, 'foobar');
assert.equal(parent.textContent, str);
assert.equal(
@@ -77,7 +82,7 @@
});
test('_annotateText Case 3', () => {
- GrAnnotation._annotateText(textNode, 12, str.length - 12, 'foobar');
+ TEST_ONLY._annotateText(textNode, 12, str.length - 12, 'foobar');
assert.equal(parent.textContent, str);
assert.equal(
@@ -90,7 +95,7 @@
const index = str.indexOf('dolor');
const length = 'dolor '.length;
- GrAnnotation._annotateText(textNode, index, length, 'foobar');
+ TEST_ONLY._annotateText(textNode, index, length, 'foobar');
assert.equal(parent.textContent, str);
assert.equal(
@@ -104,7 +109,7 @@
// Apply the layers successively.
layers.forEach((layer, i) => {
- GrAnnotation.annotateElement(
+ annotateElement(
parent,
str.indexOf(layer),
layer.length,
@@ -129,13 +134,13 @@
// Non-unicode path:
node = document.createTextNode(helloString + asciiString);
- tail = GrAnnotation.splitTextNode(node, helloString.length);
+ tail = TEST_ONLY.splitTextNode(node, helloString.length);
assert(node.textContent, helloString);
assert(tail.textContent, asciiString);
// Unicdoe path:
node = document.createTextNode(helloString + unicodeString);
- tail = GrAnnotation.splitTextNode(node, helloString.length);
+ tail = TEST_ONLY.splitTextNode(node, helloString.length);
assert(node.textContent, helloString);
assert(tail.textContent, unicodeString);
});
@@ -166,7 +171,7 @@
const length = 10;
const container = document.createElement('div');
container.textContent = fullText;
- GrAnnotation.annotateWithElement(container, 1, length, {
+ annotateWithElement(container, 1, length, {
tagName: 'test-wrapper',
});
@@ -180,8 +185,8 @@
const length = 10;
const container = document.createElement('div');
container.textContent = fullText;
- GrAnnotation.annotateElement(container, 5, length, 'testclass');
- GrAnnotation.annotateWithElement(container, 1, length, {
+ annotateElement(container, 5, length, 'testclass');
+ annotateWithElement(container, 1, length, {
tagName: 'test-wrapper',
});
@@ -201,7 +206,7 @@
const length = 10;
const container = document.createElement('div');
container.textContent = fullText;
- GrAnnotation.annotateWithElement(container.childNodes[0], 1, length, {
+ annotateWithElement(container.childNodes[0], 1, length, {
tagName: 'test-wrapper',
});
@@ -216,7 +221,7 @@
container.appendChild(document.createTextNode('0123456789'));
container.appendChild(document.createElement('span'));
container.appendChild(document.createTextNode('0123456789'));
- GrAnnotation.annotateWithElement(container, 1, 10, {
+ annotateWithElement(container, 1, 10, {
tagName: 'test-wrapper',
});
@@ -233,7 +238,7 @@
container.appendChild(document.createComment('comment2'));
container.appendChild(document.createElement('span'));
container.appendChild(document.createTextNode('0123456789'));
- GrAnnotation.annotateWithElement(container, 1, 10, {
+ annotateWithElement(container, 1, 10, {
tagName: 'test-wrapper',
});
@@ -254,7 +259,7 @@
'data-foo': 'bar',
class: 'hello world',
};
- GrAnnotation.annotateWithElement(container, 1, length, {
+ annotateWithElement(container, 1, length, {
tagName: 'test-wrapper',
attributes,
});
@@ -291,17 +296,17 @@
suite('getStringLength', () => {
test('ASCII characters are counted correctly', () => {
- assert.equal(GrAnnotation.getStringLength('ASCII'), 5);
+ assert.equal(getStringLength('ASCII'), 5);
});
test('Unicode surrogate pairs count as one symbol', () => {
- assert.equal(GrAnnotation.getStringLength('Unic💢de'), 7);
- assert.equal(GrAnnotation.getStringLength('💢💢'), 2);
+ assert.equal(getStringLength('Unic💢de'), 7);
+ assert.equal(getStringLength('💢💢'), 2);
});
test('Grapheme clusters count as multiple symbols', () => {
- assert.equal(GrAnnotation.getStringLength('man\u0303ana'), 7); // mañana
- assert.equal(GrAnnotation.getStringLength('q\u0307\u0323'), 3); // q̣̇
+ assert.equal(getStringLength('man\u0303ana'), 7); // mañana
+ assert.equal(getStringLength('q\u0307\u0323'), 3); // q̣̇
});
});
});
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-diff-highlight.ts b/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-diff-highlight.ts
index 0d9250c..1cdfbc3 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-diff-highlight.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-diff-highlight.ts
@@ -5,7 +5,7 @@
*/
import '../../../styles/shared-styles';
import '../gr-selection-action-box/gr-selection-action-box';
-import {GrAnnotation} from './gr-annotation';
+import {getLength} from './gr-annotation';
import {normalize} from './gr-range-normalizer';
import {strToClassName} from '../../../utils/dom-util';
import {Side} from '../../../constants/constants';
@@ -508,7 +508,7 @@
if (node instanceof Element && node.classList.contains('content')) {
return this.getLength(queryAndAssert(node, '.contentText'));
} else {
- return GrAnnotation.getLength(node);
+ return getLength(node);
}
}
}
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-image-viewer.ts b/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-image-viewer.ts
index 645de1b..4f05cca 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-image-viewer.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-image-viewer.ts
@@ -150,211 +150,218 @@
// TODO(hermannloose): Make GrLibLoader a singleton.
private static readonly libLoader = new GrLibLoader();
- static override styles = css`
- :host {
- display: grid;
- grid-template-rows: 1fr auto;
- grid-template-columns: 1fr auto;
- width: 100%;
- height: 100%;
- box-sizing: border-box;
- text-align: initial !important;
- font-size: var(--font-size-normal);
- --image-border-width: 2px;
- }
- .imageArea {
- grid-row-start: 1;
- grid-column-start: 1;
- box-sizing: border-box;
- flex-grow: 1;
- overflow: hidden;
- display: flex;
- flex-direction: column;
- align-items: center;
- margin: var(--spacing-m);
- padding: var(--image-border-width);
- max-height: 100%;
- position: relative;
- }
- #spacer {
- visibility: hidden;
- }
- gr-zoomed-image {
- border: var(--image-border-width) solid;
- margin: calc(-1 * var(--image-border-width));
- box-sizing: content-box;
- position: absolute;
- overflow: hidden;
- cursor: pointer;
- }
- gr-zoomed-image.base {
- border-color: var(--base-image-border-color, rgb(255, 205, 210));
- }
- gr-zoomed-image.revision {
- border-color: var(--revision-image-border-color, rgb(170, 242, 170));
- }
- #automatic-blink-button {
- position: absolute;
- right: var(--spacing-xl);
- bottom: var(--spacing-xl);
- opacity: 0;
- transition: opacity 200ms ease;
- --paper-fab-background: var(--primary-button-background-color);
- --paper-fab-keyboard-focus-background: var(
- --primary-button-background-color
- );
- }
- #automatic-blink-button.show,
- #automatic-blink-button:focus-visible {
- opacity: 1;
- }
- .checkerboard {
- --square-size: var(--checkerboard-square-size, 10px);
- --square-color: var(--checkerboard-square-color, #808080);
- background-color: var(--checkerboard-background-color, #aaaaaa);
- background-image: linear-gradient(
- 45deg,
- var(--square-color) 25%,
- transparent 25%
- ),
- linear-gradient(-45deg, var(--square-color) 25%, transparent 25%),
- linear-gradient(45deg, transparent 75%, var(--square-color) 75%),
- linear-gradient(-45deg, transparent 75%, var(--square-color) 75%);
- background-size: calc(var(--square-size) * 2) calc(var(--square-size) * 2);
- background-position: 0 0, 0 var(--square-size),
- var(--square-size) calc(-1 * var(--square-size)),
- calc(-1 * var(--square-size)) 0;
- }
- .dimensions {
- grid-row-start: 2;
- justify-self: center;
- align-self: center;
- background: var(--primary-button-background-color);
- color: var(--primary-button-text-color);
- font-family: var(--font-family);
- font-size: var(--font-size-small);
- line-height: var(--line-height-small);
- border-radius: var(--border-radius, 4px);
- margin: var(--spacing-s);
- padding: var(--spacing-xxs) var(--spacing-s);
- }
- .controls {
- grid-column-start: 2;
- flex-grow: 0;
- display: flex;
- flex-direction: column;
- align-self: flex-start;
- margin: var(--spacing-m);
- padding-bottom: var(--spacing-xl);
- }
- paper-button {
- padding: var(--spacing-m);
- font: var(--image-diff-button-font);
- text-transform: var(--image-diff-button-text-transform, uppercase);
- outline: 1px solid transparent;
- border: 1px solid var(--primary-button-background-color);
- }
- paper-button.unelevated {
- color: var(--primary-button-text-color);
- background-color: var(--primary-button-background-color);
- }
- paper-button.outlined {
- color: var(--primary-button-background-color);
- }
- #version-switcher {
- display: flex;
- align-items: center;
- margin: var(--spacing-xl) var(--spacing-xl) var(--spacing-m);
- /* Start a stacking context to contain FAB below. */
- z-index: 0;
- }
- #version-switcher paper-button {
- flex-grow: 1;
- margin: 0;
- /*
+ static override get styles() {
+ return [
+ css`
+ :host {
+ display: grid;
+ grid-template-rows: 1fr auto;
+ grid-template-columns: 1fr auto;
+ width: 100%;
+ height: 100%;
+ box-sizing: border-box;
+ text-align: initial !important;
+ font-size: var(--font-size-normal);
+ --image-border-width: 2px;
+ }
+ .imageArea {
+ grid-row-start: 1;
+ grid-column-start: 1;
+ box-sizing: border-box;
+ flex-grow: 1;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin: var(--spacing-m);
+ padding: var(--image-border-width);
+ max-height: 100%;
+ position: relative;
+ }
+ #spacer {
+ visibility: hidden;
+ }
+ gr-zoomed-image {
+ border: var(--image-border-width) solid;
+ margin: calc(-1 * var(--image-border-width));
+ box-sizing: content-box;
+ position: absolute;
+ overflow: hidden;
+ cursor: pointer;
+ }
+ gr-zoomed-image.base {
+ border-color: var(--base-image-border-color, rgb(255, 205, 210));
+ }
+ gr-zoomed-image.revision {
+ border-color: var(--revision-image-border-color, rgb(170, 242, 170));
+ }
+ #automatic-blink-button {
+ position: absolute;
+ right: var(--spacing-xl);
+ bottom: var(--spacing-xl);
+ opacity: 0;
+ transition: opacity 200ms ease;
+ --paper-fab-background: var(--primary-button-background-color);
+ --paper-fab-keyboard-focus-background: var(
+ --primary-button-background-color
+ );
+ }
+ #automatic-blink-button.show,
+ #automatic-blink-button:focus-visible {
+ opacity: 1;
+ }
+ .checkerboard {
+ --square-size: var(--checkerboard-square-size, 10px);
+ --square-color: var(--checkerboard-square-color, #808080);
+ background-color: var(--checkerboard-background-color, #aaaaaa);
+ background-image: linear-gradient(
+ 45deg,
+ var(--square-color) 25%,
+ transparent 25%
+ ),
+ linear-gradient(-45deg, var(--square-color) 25%, transparent 25%),
+ linear-gradient(45deg, transparent 75%, var(--square-color) 75%),
+ linear-gradient(-45deg, transparent 75%, var(--square-color) 75%);
+ background-size: calc(var(--square-size) * 2)
+ calc(var(--square-size) * 2);
+ background-position: 0 0, 0 var(--square-size),
+ var(--square-size) calc(-1 * var(--square-size)),
+ calc(-1 * var(--square-size)) 0;
+ }
+ .dimensions {
+ grid-row-start: 2;
+ justify-self: center;
+ align-self: center;
+ background: var(--primary-button-background-color);
+ color: var(--primary-button-text-color);
+ font-family: var(--font-family);
+ font-size: var(--font-size-small);
+ line-height: var(--line-height-small);
+ border-radius: var(--border-radius, 4px);
+ margin: var(--spacing-s);
+ padding: var(--spacing-xxs) var(--spacing-s);
+ }
+ .controls {
+ grid-column-start: 2;
+ flex-grow: 0;
+ display: flex;
+ flex-direction: column;
+ align-self: flex-start;
+ margin: var(--spacing-m);
+ padding-bottom: var(--spacing-xl);
+ }
+ paper-button {
+ padding: var(--spacing-m);
+ font: var(--image-diff-button-font);
+ text-transform: var(--image-diff-button-text-transform, uppercase);
+ outline: 1px solid transparent;
+ border: 1px solid var(--primary-button-background-color);
+ }
+ paper-button.unelevated {
+ color: var(--primary-button-text-color);
+ background-color: var(--primary-button-background-color);
+ }
+ paper-button.outlined {
+ color: var(--primary-button-background-color);
+ }
+ #version-switcher {
+ display: flex;
+ align-items: center;
+ margin: var(--spacing-xl) var(--spacing-xl) var(--spacing-m);
+ /* Start a stacking context to contain FAB below. */
+ z-index: 0;
+ }
+ #version-switcher paper-button {
+ flex-grow: 1;
+ margin: 0;
+ /*
The floating action button below overlaps part of the version buttons.
This min-width ensures the button text still appears somewhat balanced.
*/
- min-width: 7rem;
- }
- #version-switcher paper-fab {
- /* Round button overlaps Base and Revision buttons. */
- z-index: 1;
- margin: 0 -12px;
- /* Styled as an outlined button. */
- color: var(--primary-button-background-color);
- border: 1px solid var(--primary-button-background-color);
- --paper-fab-background: var(--primary-background-color);
- --paper-fab-keyboard-focus-background: var(--primary-background-color);
- }
- #version-explanation {
- color: var(--deemphasized-text-color);
- text-align: center;
- margin: var(--spacing-xl) var(--spacing-xl) var(--spacing-m);
- }
- #highlight-changes {
- margin: var(--spacing-m) var(--spacing-xl);
- }
- gr-overview-image {
- min-width: 200px;
- min-height: 150px;
- margin-top: var(--spacing-m);
- }
- #zoom-control {
- margin: 0 var(--spacing-xl);
- }
- paper-item {
- cursor: pointer;
- }
- paper-item:hover {
- background-color: var(--hover-background-color);
- }
- #follow-mouse {
- margin: var(--spacing-m) var(--spacing-xl);
- }
- .color-picker {
- margin: var(--spacing-m) var(--spacing-xl) 0;
- }
- .color-picker .label {
- margin-bottom: var(--spacing-s);
- }
- .color-picker .options {
- display: flex;
- /* Ignore selection border for alignment, for visual balance. */
- margin-left: -3px;
- }
- .color-picker-button {
- border-width: 2px;
- border-style: solid;
- border-color: transparent;
- border-radius: 50%;
- width: 24px;
- height: 24px;
- padding: 1px;
- }
- .color-picker-button.selected {
- border-color: var(--primary-button-background-color);
- }
- .color-picker-button:focus-within:not(.selected) {
- /* Not an actual outline, as those do not follow border-radius. */
- border-color: var(--outline-color-focus);
- }
- .color-picker-button .color {
- border: 1px solid var(--border-color);
- border-radius: 50%;
- width: 100%;
- height: 100%;
- box-sizing: border-box;
- }
- #source-plus-highlight-container {
- position: relative;
- }
- #source-plus-highlight-container img {
- position: absolute;
- top: 0;
- left: 0;
- }
- `;
+ min-width: 7rem;
+ }
+ #version-switcher paper-fab {
+ /* Round button overlaps Base and Revision buttons. */
+ z-index: 1;
+ margin: 0 -12px;
+ /* Styled as an outlined button. */
+ color: var(--primary-button-background-color);
+ border: 1px solid var(--primary-button-background-color);
+ --paper-fab-background: var(--primary-background-color);
+ --paper-fab-keyboard-focus-background: var(
+ --primary-background-color
+ );
+ }
+ #version-explanation {
+ color: var(--deemphasized-text-color);
+ text-align: center;
+ margin: var(--spacing-xl) var(--spacing-xl) var(--spacing-m);
+ }
+ #highlight-changes {
+ margin: var(--spacing-m) var(--spacing-xl);
+ }
+ gr-overview-image {
+ min-width: 200px;
+ min-height: 150px;
+ margin-top: var(--spacing-m);
+ }
+ #zoom-control {
+ margin: 0 var(--spacing-xl);
+ }
+ paper-item {
+ cursor: pointer;
+ }
+ paper-item:hover {
+ background-color: var(--hover-background-color);
+ }
+ #follow-mouse {
+ margin: var(--spacing-m) var(--spacing-xl);
+ }
+ .color-picker {
+ margin: var(--spacing-m) var(--spacing-xl) 0;
+ }
+ .color-picker .label {
+ margin-bottom: var(--spacing-s);
+ }
+ .color-picker .options {
+ display: flex;
+ /* Ignore selection border for alignment, for visual balance. */
+ margin-left: -3px;
+ }
+ .color-picker-button {
+ border-width: 2px;
+ border-style: solid;
+ border-color: transparent;
+ border-radius: 50%;
+ width: 24px;
+ height: 24px;
+ padding: 1px;
+ }
+ .color-picker-button.selected {
+ border-color: var(--primary-button-background-color);
+ }
+ .color-picker-button:focus-within:not(.selected) {
+ /* Not an actual outline, as those do not follow border-radius. */
+ border-color: var(--outline-color-focus);
+ }
+ .color-picker-button .color {
+ border: 1px solid var(--border-color);
+ border-radius: 50%;
+ width: 100%;
+ height: 100%;
+ box-sizing: border-box;
+ }
+ #source-plus-highlight-container {
+ position: relative;
+ }
+ #source-plus-highlight-container img {
+ position: absolute;
+ top: 0;
+ left: 0;
+ }
+ `,
+ ];
+ }
private renderColorPickerButton(color: string, colorPicked: () => void) {
const selected =
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-overview-image.ts b/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-overview-image.ts
index 21a7cf8..a05b5e2 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-overview-image.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-overview-image.ts
@@ -75,39 +75,43 @@
}
);
- static override styles = css`
- :host {
- --background-color: var(--overview-image-background-color, #000);
- --frame-color: var(--overview-image-frame-color, #f00);
- display: flex;
- }
- * {
- box-sizing: border-box;
- }
- ::slotted(*) {
- display: block;
- }
- .content-box {
- border: 1px solid var(--background-color);
- background-color: var(--background-color);
- width: 100%;
- position: relative;
- }
- .content {
- position: absolute;
- cursor: pointer;
- }
- .content-transform {
- position: absolute;
- transform-origin: top left;
- will-change: transform;
- }
- .frame {
- border: 1px solid var(--frame-color);
- position: absolute;
- will-change: transform;
- }
- `;
+ static override get styles() {
+ return [
+ css`
+ :host {
+ --background-color: var(--overview-image-background-color, #000);
+ --frame-color: var(--overview-image-frame-color, #f00);
+ display: flex;
+ }
+ * {
+ box-sizing: border-box;
+ }
+ ::slotted(*) {
+ display: block;
+ }
+ .content-box {
+ border: 1px solid var(--background-color);
+ background-color: var(--background-color);
+ width: 100%;
+ position: relative;
+ }
+ .content {
+ position: absolute;
+ cursor: pointer;
+ }
+ .content-transform {
+ position: absolute;
+ transform-origin: top left;
+ will-change: transform;
+ }
+ .frame {
+ border: 1px solid var(--frame-color);
+ position: absolute;
+ will-change: transform;
+ }
+ `,
+ ];
+ }
override render() {
return html`
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-zoomed-image.ts b/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-zoomed-image.ts
index 7b46c51..3b778b1 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-zoomed-image.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-zoomed-image.ts
@@ -25,25 +25,29 @@
@state() protected imageStyles: StyleInfo = {};
- static override styles = css`
- :host {
- display: block;
- }
- ::slotted(*) {
- display: block;
- }
- #clip {
- position: relative;
- width: 100%;
- height: 100%;
- overflow: hidden;
- }
- #transform {
- position: absolute;
- transform-origin: top left;
- will-change: transform;
- }
- `;
+ static override get styles() {
+ return [
+ css`
+ :host {
+ display: block;
+ }
+ ::slotted(*) {
+ display: block;
+ }
+ #clip {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ }
+ #transform {
+ position: absolute;
+ transform-origin: top left;
+ will-change: transform;
+ }
+ `,
+ ];
+ }
override render() {
return html`
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-model/gr-diff-model.ts b/polygerrit-ui/app/embed/diff/gr-diff-model/gr-diff-model.ts
index d2e997c..8dae154 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-model/gr-diff-model.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-model/gr-diff-model.ts
@@ -3,59 +3,158 @@
* Copyright 2023 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
-import {Observable} from 'rxjs';
-import {filter} from 'rxjs/operators';
+import {Observable, combineLatest, from} from 'rxjs';
+import {debounceTime, filter, switchMap, withLatestFrom} from 'rxjs/operators';
import {
DiffInfo,
DiffPreferencesInfo,
+ DiffViewMode,
DisplayLine,
RenderPreferences,
} from '../../../api/diff';
import {define} from '../../../models/dependency';
import {Model} from '../../../models/model';
-import {isDefined} from '../../../types/types';
import {select} from '../../../utils/observable-util';
import {
+ FullContext,
GrDiffCommentThread,
KeyLocations,
+ computeContext,
computeKeyLocations,
+ computeLineLength,
} from '../gr-diff/gr-diff-utils';
+import {createDefaultDiffPrefs} from '../../../constants/constants';
+import {
+ GrDiffProcessor,
+ ProcessingOptions,
+} from '../gr-diff-processor/gr-diff-processor';
+import {GrDiffGroup, GrDiffGroupType} from '../gr-diff/gr-diff-group';
+import {assert} from '../../../utils/common-util';
export interface DiffState {
- diff: DiffInfo;
+ diff?: DiffInfo;
path?: string;
renderPrefs: RenderPreferences;
diffPrefs: DiffPreferencesInfo;
lineOfInterest?: DisplayLine;
comments: GrDiffCommentThread[];
+ groups: GrDiffGroup[];
+ /** how much context to show for large files */
+ showFullContext: FullContext;
+ isImageDiff: boolean;
}
export const diffModelToken = define<DiffModel>('diff-model');
-export class DiffModel extends Model<DiffState | undefined> {
+export class DiffModel extends Model<DiffState> {
readonly diff$: Observable<DiffInfo> = select(
- this.state$.pipe(filter(isDefined)),
- diffState => diffState.diff
+ this.state$.pipe(filter(state => state.diff !== undefined)),
+ diffState => diffState.diff!
);
readonly path$: Observable<string | undefined> = select(
- this.state$.pipe(filter(isDefined)),
+ this.state$,
diffState => diffState.path
);
readonly renderPrefs$: Observable<RenderPreferences> = select(
- this.state$.pipe(filter(isDefined)),
+ this.state$,
diffState => diffState.renderPrefs
);
+ readonly viewMode$: Observable<DiffViewMode> = select(
+ this.renderPrefs$,
+ renderPrefs => renderPrefs.view_mode ?? DiffViewMode.SIDE_BY_SIDE
+ );
+
readonly diffPrefs$: Observable<DiffPreferencesInfo> = select(
- this.state$.pipe(filter(isDefined)),
+ this.state$,
diffState => diffState.diffPrefs
);
+ readonly context$: Observable<number> = select(this.state$, state =>
+ computeContext(
+ state.diffPrefs.context,
+ state.showFullContext,
+ createDefaultDiffPrefs().context
+ )
+ );
+
+ readonly isImageDiff$: Observable<boolean> = select(
+ this.state$,
+ diffState => diffState.isImageDiff
+ );
+
+ readonly groups$: Observable<GrDiffGroup[]> = select(
+ this.state$,
+ diffState => diffState.groups ?? []
+ );
+
+ readonly lineLength$: Observable<number> = select(this.state$, state =>
+ computeLineLength(state.diffPrefs, state.path)
+ );
+
readonly keyLocations$: Observable<KeyLocations> = select(
- this.state$.pipe(filter(isDefined)),
+ this.state$,
diffState =>
computeKeyLocations(diffState.lineOfInterest, diffState.comments ?? [])
);
+
+ constructor() {
+ super({
+ diffPrefs: createDefaultDiffPrefs(),
+ renderPrefs: {},
+ comments: [],
+ groups: [],
+ showFullContext: FullContext.UNDECIDED,
+ isImageDiff: false,
+ });
+ this.subscriptions = [this.processDiff()];
+ }
+
+ processDiff() {
+ return combineLatest([
+ this.diff$,
+ this.context$,
+ this.renderPrefs$,
+ this.isImageDiff$,
+ ])
+ .pipe(
+ withLatestFrom(this.keyLocations$),
+ debounceTime(1),
+ switchMap(
+ ([[diff, context, renderPrefs, isImageDiff], keyLocations]) => {
+ const options: ProcessingOptions = {
+ context,
+ keyLocations,
+ isBinary: !!(isImageDiff || diff.binary),
+ };
+ if (renderPrefs?.num_lines_rendered_at_once) {
+ options.asyncThreshold = renderPrefs.num_lines_rendered_at_once;
+ }
+ const processor = new GrDiffProcessor(options);
+ return from(processor.process(diff.content));
+ }
+ )
+ )
+ .subscribe(groups => {
+ this.updateState({groups});
+ });
+ }
+
+ /**
+ * Replace a context control group with some expanded groups. Happens when the
+ * user clicks "+10" or something similar.
+ */
+ replaceGroup(group: GrDiffGroup, newGroups: readonly GrDiffGroup[]) {
+ assert(
+ group.type === GrDiffGroupType.CONTEXT_CONTROL,
+ 'gr-diff can only replace context control groups'
+ );
+ const groups = [...this.getState().groups];
+ const i = groups.indexOf(group);
+ if (i === -1) throw new Error('cannot find context control group');
+ groups.splice(i, 1, ...newGroups);
+ this.updateState({groups});
+ }
}
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-processor/gr-diff-processor.ts b/polygerrit-ui/app/embed/diff/gr-diff-processor/gr-diff-processor.ts
index 483a4da..5db6db9 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-processor/gr-diff-processor.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-processor/gr-diff-processor.ts
@@ -13,11 +13,9 @@
import {Side} from '../../../constants/constants';
import {debounce, DelayedTask} from '../../../utils/async-util';
import {assert} from '../../../utils/common-util';
-import {GrAnnotation} from '../gr-diff-highlight/gr-annotation';
-import {FILE, GrDiffLineType, LineNumber} from '../../../api/diff';
-import {KeyLocations} from '../gr-diff/gr-diff-utils';
-
-const WHOLE_FILE = -1;
+import {getStringLength} from '../gr-diff-highlight/gr-annotation';
+import {GrDiffLineType, LineNumber} from '../../../api/diff';
+import {FULL_CONTEXT, KeyLocations} from '../gr-diff/gr-diff-utils';
// visible for testing
export interface State {
@@ -107,11 +105,9 @@
private resetIsScrollingTask?: DelayedTask;
- constructor(
- private consumer: GroupConsumer | undefined,
- options: ProcessingOptions
- ) {
- this.consumer = consumer;
+ private readonly groups: GrDiffGroup[] = [];
+
+ constructor(options: ProcessingOptions) {
this.context = options.context;
this.asyncThreshold = options.asyncThreshold ?? 64;
this.keyLocations = options.keyLocations ?? {left: {}, right: {}};
@@ -134,62 +130,24 @@
* @return A promise that resolves with an
* array of GrDiffGroups when the diff is completely processed.
*/
- process(chunks: DiffContent[]) {
+ async process(chunks: DiffContent[]): Promise<GrDiffGroup[]> {
assert(this.isStarted === false, 'diff processor cannot be started twice');
window.addEventListener('scroll', this.handleWindowScroll);
- this.consumer?.clearGroups();
- this.consumer?.addGroup(this.makeGroup('LOST'));
- this.consumer?.addGroup(this.makeGroup(FILE));
+ this.groups.push(this.makeGroup('LOST'));
+ this.groups.push(this.makeGroup('FILE'));
- if (this.isBinary) return Promise.resolve();
-
- return new Promise<void>(resolve => {
- const state = {
- lineNums: {left: 0, right: 0},
- chunkIndex: 0,
- };
-
- chunks = this.splitLargeChunks(chunks);
- chunks = this.splitCommonChunksWithKeyLocations(chunks);
-
- let currentBatch = 0;
- const nextStep = () => {
- if (this.isCancelled || state.chunkIndex >= chunks.length) {
- resolve();
- return;
- }
- if (this.isScrolling) {
- window.setTimeout(nextStep, 100);
- return;
- }
-
- const stateUpdate = this.processNext(state, chunks);
- for (const group of stateUpdate.groups) {
- this.consumer?.addGroup(group);
- currentBatch += group.lines.length;
- }
- state.lineNums.left += stateUpdate.lineDelta.left;
- state.lineNums.right += stateUpdate.lineDelta.right;
-
- state.chunkIndex = stateUpdate.newChunkIndex;
- if (currentBatch >= this.asyncThreshold) {
- currentBatch = 0;
- window.setTimeout(nextStep, 1);
- } else {
- nextStep.call(this);
- }
- };
-
- nextStep.call(this);
- }).finally(() => {
+ if (this.isBinary) return this.groups;
+ try {
+ await this.processChunks(chunks);
+ } finally {
this.finish();
- });
+ }
+ return this.groups;
}
finish() {
- this.consumer = undefined;
window.removeEventListener('scroll', this.handleWindowScroll);
}
@@ -198,6 +156,50 @@
this.finish();
}
+ async processChunks(chunks: DiffContent[]) {
+ let completed = () => {};
+ const promise = new Promise<void>(resolve => (completed = resolve));
+
+ const state = {
+ lineNums: {left: 0, right: 0},
+ chunkIndex: 0,
+ };
+
+ chunks = this.splitLargeChunks(chunks);
+ chunks = this.splitCommonChunksWithKeyLocations(chunks);
+
+ let currentBatch = 0;
+ const nextStep = () => {
+ if (this.isCancelled || state.chunkIndex >= chunks.length) {
+ completed();
+ return;
+ }
+ if (this.isScrolling) {
+ window.setTimeout(nextStep, 100);
+ return;
+ }
+
+ const stateUpdate = this.processNext(state, chunks);
+ for (const group of stateUpdate.groups) {
+ this.groups.push(group);
+ currentBatch += group.lines.length;
+ }
+ state.lineNums.left += stateUpdate.lineDelta.left;
+ state.lineNums.right += stateUpdate.lineDelta.right;
+
+ state.chunkIndex = stateUpdate.newChunkIndex;
+ if (currentBatch >= this.asyncThreshold) {
+ currentBatch = 0;
+ window.setTimeout(nextStep, 1);
+ } else {
+ nextStep.call(this);
+ }
+ };
+
+ nextStep.call(this);
+ await promise;
+ }
+
/**
* Process the next uncollapsible chunk, or the next collapsible chunks.
*/
@@ -286,7 +288,7 @@
);
const hasSkippedGroup = !!groups.find(g => g.skip);
- if (this.context !== WHOLE_FILE || hasSkippedGroup) {
+ if (this.context !== FULL_CONTEXT || hasSkippedGroup) {
const contextNumLines = this.context > 0 ? this.context : 0;
const hiddenStart = state.chunkIndex === 0 ? 0 : contextNumLines;
const hiddenEnd =
@@ -477,7 +479,10 @@
// enabled for any other context preference because manipulating the
// chunks in this way violates assumptions by the context grouper logic.
const MAX_GROUP_SIZE = calcMaxGroupSize(this.asyncThreshold);
- if (this.context === -1 && chunk.ab.length > MAX_GROUP_SIZE * 2) {
+ if (
+ this.context === FULL_CONTEXT &&
+ chunk.ab.length > MAX_GROUP_SIZE * 2
+ ) {
// Split large shared chunks in two, where the first is the maximum
// group size.
newChunks.push({ab: chunk.ab.slice(0, MAX_GROUP_SIZE)});
@@ -628,7 +633,7 @@
intralineInfos: number[][]
): Highlights[] {
// +1 to account for the \n that is not part of the rows passed here
- const lineLengths = rows.map(r => GrAnnotation.getStringLength(r) + 1);
+ const lineLengths = rows.map(r => getStringLength(r) + 1);
let rowIndex = 0;
let idx = 0;
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-processor/gr-diff-processor_test.ts b/polygerrit-ui/app/embed/diff/gr-diff-processor/gr-diff-processor_test.ts
index 706c208..3485fe4 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-processor/gr-diff-processor_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-processor/gr-diff-processor_test.ts
@@ -7,18 +7,13 @@
import './gr-diff-processor';
import {GrDiffLine} from '../gr-diff/gr-diff-line';
import {GrDiffGroup, GrDiffGroupType} from '../gr-diff/gr-diff-group';
-import {
- GrDiffProcessor,
- GroupConsumer,
- ProcessingOptions,
- State,
-} from './gr-diff-processor';
+import {GrDiffProcessor, ProcessingOptions, State} from './gr-diff-processor';
import {DiffContent} from '../../../types/diff';
import {assert} from '@open-wc/testing';
import {FILE, GrDiffLineType} from '../../../api/diff';
+import {FULL_CONTEXT} from '../gr-diff/gr-diff-utils';
suite('gr-diff-processor tests', () => {
- const WHOLE_FILE = -1;
const loremIpsum =
'Lorem ipsum dolor sit amet, ei nonumes vituperata ius. ' +
'Duo animal omnesque fabellas et. Id has phaedrum dignissim ' +
@@ -30,26 +25,16 @@
let options: ProcessingOptions = {
context: 4,
};
- let groups: GrDiffGroup[];
- const consumer: GroupConsumer = {
- addGroup(group: GrDiffGroup) {
- groups.push(group);
- },
- clearGroups() {
- groups = [];
- },
- };
setup(() => {});
suite('not logged in', () => {
setup(() => {
- groups = [];
options = {context: 4};
- processor = new GrDiffProcessor(consumer, options);
+ processor = new GrDiffProcessor(options);
});
- test('process loaded content', () => {
+ test('process loaded content', async () => {
const content: DiffContent[] = [
{
ab: ['<!DOCTYPE html>', '<meta charset="utf-8">'],
@@ -67,82 +52,80 @@
},
];
- return processor.process(content).then(() => {
- groups.shift(); // remove portedThreadsWithoutRangeGroup
- assert.equal(groups.length, 4);
+ const groups = await processor.process(content);
+ groups.shift(); // remove portedThreadsWithoutRangeGroup
+ assert.equal(groups.length, 4);
- let group = groups[0];
- assert.equal(group.type, GrDiffGroupType.BOTH);
- assert.equal(group.lines.length, 1);
- assert.equal(group.lines[0].text, '');
- assert.equal(group.lines[0].beforeNumber, FILE);
- assert.equal(group.lines[0].afterNumber, FILE);
+ let group = groups[0];
+ assert.equal(group.type, GrDiffGroupType.BOTH);
+ assert.equal(group.lines.length, 1);
+ assert.equal(group.lines[0].text, '');
+ assert.equal(group.lines[0].beforeNumber, FILE);
+ assert.equal(group.lines[0].afterNumber, FILE);
- group = groups[1];
- assert.equal(group.type, GrDiffGroupType.BOTH);
- assert.equal(group.lines.length, 2);
+ group = groups[1];
+ assert.equal(group.type, GrDiffGroupType.BOTH);
+ assert.equal(group.lines.length, 2);
- function beforeNumberFn(l: GrDiffLine) {
- return l.beforeNumber;
- }
- function afterNumberFn(l: GrDiffLine) {
- return l.afterNumber;
- }
- function textFn(l: GrDiffLine) {
- return l.text;
- }
+ function beforeNumberFn(l: GrDiffLine) {
+ return l.beforeNumber;
+ }
+ function afterNumberFn(l: GrDiffLine) {
+ return l.afterNumber;
+ }
+ function textFn(l: GrDiffLine) {
+ return l.text;
+ }
- assert.deepEqual(group.lines.map(beforeNumberFn), [1, 2]);
- assert.deepEqual(group.lines.map(afterNumberFn), [1, 2]);
- assert.deepEqual(group.lines.map(textFn), [
- '<!DOCTYPE html>',
- '<meta charset="utf-8">',
- ]);
+ assert.deepEqual(group.lines.map(beforeNumberFn), [1, 2]);
+ assert.deepEqual(group.lines.map(afterNumberFn), [1, 2]);
+ assert.deepEqual(group.lines.map(textFn), [
+ '<!DOCTYPE html>',
+ '<meta charset="utf-8">',
+ ]);
- group = groups[2];
- assert.equal(group.type, GrDiffGroupType.DELTA);
- assert.equal(group.lines.length, 3);
- assert.equal(group.adds.length, 1);
- assert.equal(group.removes.length, 2);
- assert.deepEqual(group.removes.map(beforeNumberFn), [3, 4]);
- assert.deepEqual(group.adds.map(afterNumberFn), [3]);
- assert.deepEqual(group.removes.map(textFn), [
- ' Welcome ',
- ' to the wooorld of tomorrow!',
- ]);
- assert.deepEqual(group.adds.map(textFn), [' Hello, world!']);
+ group = groups[2];
+ assert.equal(group.type, GrDiffGroupType.DELTA);
+ assert.equal(group.lines.length, 3);
+ assert.equal(group.adds.length, 1);
+ assert.equal(group.removes.length, 2);
+ assert.deepEqual(group.removes.map(beforeNumberFn), [3, 4]);
+ assert.deepEqual(group.adds.map(afterNumberFn), [3]);
+ assert.deepEqual(group.removes.map(textFn), [
+ ' Welcome ',
+ ' to the wooorld of tomorrow!',
+ ]);
+ assert.deepEqual(group.adds.map(textFn), [' Hello, world!']);
- group = groups[3];
- assert.equal(group.type, GrDiffGroupType.BOTH);
- assert.equal(group.lines.length, 3);
- assert.deepEqual(group.lines.map(beforeNumberFn), [5, 6, 7]);
- assert.deepEqual(group.lines.map(afterNumberFn), [4, 5, 6]);
- assert.deepEqual(group.lines.map(textFn), [
- 'Leela: This is the only place the ship can’t hear us, so ',
- 'everyone pretend to shower.',
- 'Fry: Same as every day. Got it.',
- ]);
- });
+ group = groups[3];
+ assert.equal(group.type, GrDiffGroupType.BOTH);
+ assert.equal(group.lines.length, 3);
+ assert.deepEqual(group.lines.map(beforeNumberFn), [5, 6, 7]);
+ assert.deepEqual(group.lines.map(afterNumberFn), [4, 5, 6]);
+ assert.deepEqual(group.lines.map(textFn), [
+ 'Leela: This is the only place the ship can’t hear us, so ',
+ 'everyone pretend to shower.',
+ 'Fry: Same as every day. Got it.',
+ ]);
});
- test('first group is for file', () => {
+ test('first group is for file', async () => {
const content = [{b: ['foo']}];
- return processor.process(content).then(() => {
- groups.shift(); // remove portedThreadsWithoutRangeGroup
+ const groups = await processor.process(content);
+ groups.shift(); // remove portedThreadsWithoutRangeGroup
- assert.equal(groups[0].type, GrDiffGroupType.BOTH);
- assert.equal(groups[0].lines.length, 1);
- assert.equal(groups[0].lines[0].text, '');
- assert.equal(groups[0].lines[0].beforeNumber, FILE);
- assert.equal(groups[0].lines[0].afterNumber, FILE);
- });
+ assert.equal(groups[0].type, GrDiffGroupType.BOTH);
+ assert.equal(groups[0].lines.length, 1);
+ assert.equal(groups[0].lines[0].text, '');
+ assert.equal(groups[0].lines[0].beforeNumber, FILE);
+ assert.equal(groups[0].lines[0].afterNumber, FILE);
});
- suite('context groups', () => {
- test('at the beginning, larger than context', () => {
+ suite('context groups', async () => {
+ test('at the beginning, larger than context', async () => {
options.context = 10;
- processor = new GrDiffProcessor(consumer, options);
+ processor = new GrDiffProcessor(options);
const content = [
{
ab: Array.from<string>({length: 100}).fill(
@@ -152,28 +135,27 @@
{a: ['all work and no play make andybons a dull boy']},
];
- return processor.process(content).then(() => {
- // group[0] is the LOST group
- // group[1] is the FILE group
+ const groups = await processor.process(content);
+ // group[0] is the LOST group
+ // group[1] is the FILE group
- assert.equal(groups[2].type, GrDiffGroupType.CONTEXT_CONTROL);
- assert.instanceOf(groups[2].contextGroups[0], GrDiffGroup);
- assert.equal(groups[2].contextGroups[0].lines.length, 90);
- for (const l of groups[2].contextGroups[0].lines) {
- assert.equal(l.text, 'all work and no play make jack a dull boy');
- }
+ assert.equal(groups[2].type, GrDiffGroupType.CONTEXT_CONTROL);
+ assert.instanceOf(groups[2].contextGroups[0], GrDiffGroup);
+ assert.equal(groups[2].contextGroups[0].lines.length, 90);
+ for (const l of groups[2].contextGroups[0].lines) {
+ assert.equal(l.text, 'all work and no play make jack a dull boy');
+ }
- assert.equal(groups[3].type, GrDiffGroupType.BOTH);
- assert.equal(groups[3].lines.length, 10);
- for (const l of groups[3].lines) {
- assert.equal(l.text, 'all work and no play make jack a dull boy');
- }
- });
+ assert.equal(groups[3].type, GrDiffGroupType.BOTH);
+ assert.equal(groups[3].lines.length, 10);
+ for (const l of groups[3].lines) {
+ assert.equal(l.text, 'all work and no play make jack a dull boy');
+ }
});
test('at the beginning with skip chunks', async () => {
options.context = 10;
- processor = new GrDiffProcessor(consumer, options);
+ processor = new GrDiffProcessor(options);
const content = [
{
ab: Array.from<string>({length: 20}).fill(
@@ -185,8 +167,7 @@
{a: ['some other content']},
];
- await processor.process(content);
-
+ const groups = await processor.process(content);
groups.shift(); // remove portedThreadsWithoutRangeGroup
// group[0] is the file group
@@ -224,9 +205,9 @@
}
});
- test('at the beginning, smaller than context', () => {
+ test('at the beginning, smaller than context', async () => {
options.context = 10;
- processor = new GrDiffProcessor(consumer, options);
+ processor = new GrDiffProcessor(options);
const content = [
{
ab: Array.from<string>({length: 5}).fill(
@@ -236,22 +217,21 @@
{a: ['all work and no play make andybons a dull boy']},
];
- return processor.process(content).then(() => {
- groups.shift(); // remove portedThreadsWithoutRangeGroup
+ const groups = await processor.process(content);
+ groups.shift(); // remove portedThreadsWithoutRangeGroup
- // group[0] is the file group
+ // group[0] is the file group
- assert.equal(groups[1].type, GrDiffGroupType.BOTH);
- assert.equal(groups[1].lines.length, 5);
- for (const l of groups[1].lines) {
- assert.equal(l.text, 'all work and no play make jack a dull boy');
- }
- });
+ assert.equal(groups[1].type, GrDiffGroupType.BOTH);
+ assert.equal(groups[1].lines.length, 5);
+ for (const l of groups[1].lines) {
+ assert.equal(l.text, 'all work and no play make jack a dull boy');
+ }
});
- test('at the end, larger than context', () => {
+ test('at the end, larger than context', async () => {
options.context = 10;
- processor = new GrDiffProcessor(consumer, options);
+ processor = new GrDiffProcessor(options);
const content = [
{a: ['all work and no play make andybons a dull boy']},
{
@@ -261,28 +241,27 @@
},
];
- return processor.process(content).then(() => {
- groups.shift(); // remove portedThreadsWithoutRangeGroup
+ const groups = await processor.process(content);
+ groups.shift(); // remove portedThreadsWithoutRangeGroup
- // group[0] is the file group
- // group[1] is the "a" group
+ // group[0] is the file group
+ // group[1] is the "a" group
- assert.equal(groups[2].type, GrDiffGroupType.BOTH);
- assert.equal(groups[2].lines.length, 10);
- for (const l of groups[2].lines) {
- assert.equal(l.text, 'all work and no play make jill a dull girl');
- }
+ assert.equal(groups[2].type, GrDiffGroupType.BOTH);
+ assert.equal(groups[2].lines.length, 10);
+ for (const l of groups[2].lines) {
+ assert.equal(l.text, 'all work and no play make jill a dull girl');
+ }
- assert.equal(groups[3].type, GrDiffGroupType.CONTEXT_CONTROL);
- assert.instanceOf(groups[3].contextGroups[0], GrDiffGroup);
- assert.equal(groups[3].contextGroups[0].lines.length, 90);
- for (const l of groups[3].contextGroups[0].lines) {
- assert.equal(l.text, 'all work and no play make jill a dull girl');
- }
- });
+ assert.equal(groups[3].type, GrDiffGroupType.CONTEXT_CONTROL);
+ assert.instanceOf(groups[3].contextGroups[0], GrDiffGroup);
+ assert.equal(groups[3].contextGroups[0].lines.length, 90);
+ for (const l of groups[3].contextGroups[0].lines) {
+ assert.equal(l.text, 'all work and no play make jill a dull girl');
+ }
});
- test('at the end, smaller than context', () => {
+ test('at the end, smaller than context', async () => {
options.context = 10;
const content = [
{a: ['all work and no play make andybons a dull boy']},
@@ -293,23 +272,22 @@
},
];
- return processor.process(content).then(() => {
- groups.shift(); // remove portedThreadsWithoutRangeGroup
+ const groups = await processor.process(content);
+ groups.shift(); // remove portedThreadsWithoutRangeGroup
- // group[0] is the file group
- // group[1] is the "a" group
+ // group[0] is the file group
+ // group[1] is the "a" group
- assert.equal(groups[2].type, GrDiffGroupType.BOTH);
- assert.equal(groups[2].lines.length, 5);
- for (const l of groups[2].lines) {
- assert.equal(l.text, 'all work and no play make jill a dull girl');
- }
- });
+ assert.equal(groups[2].type, GrDiffGroupType.BOTH);
+ assert.equal(groups[2].lines.length, 5);
+ for (const l of groups[2].lines) {
+ assert.equal(l.text, 'all work and no play make jill a dull girl');
+ }
});
- test('for interleaved ab and common: true chunks', () => {
+ test('for interleaved ab and common: true chunks', async () => {
options.context = 10;
- processor = new GrDiffProcessor(consumer, options);
+ processor = new GrDiffProcessor(options);
const content = [
{a: ['all work and no play make andybons a dull boy']},
{
@@ -347,85 +325,75 @@
},
];
- return processor.process(content).then(() => {
- groups.shift(); // remove portedThreadsWithoutRangeGroup
+ const groups = await processor.process(content);
+ groups.shift(); // remove portedThreadsWithoutRangeGroup
- // group[0] is the file group
- // group[1] is the "a" group
+ // group[0] is the file group
+ // group[1] is the "a" group
- // The first three interleaved chunks are completely shown because
- // they are part of the context (3 * 3 <= 10)
+ // The first three interleaved chunks are completely shown because
+ // they are part of the context (3 * 3 <= 10)
- assert.equal(groups[2].type, GrDiffGroupType.BOTH);
- assert.equal(groups[2].lines.length, 3);
- for (const l of groups[2].lines) {
- assert.equal(l.text, 'all work and no play make jill a dull girl');
- }
+ assert.equal(groups[2].type, GrDiffGroupType.BOTH);
+ assert.equal(groups[2].lines.length, 3);
+ for (const l of groups[2].lines) {
+ assert.equal(l.text, 'all work and no play make jill a dull girl');
+ }
- assert.equal(groups[3].type, GrDiffGroupType.DELTA);
- assert.equal(groups[3].lines.length, 6);
- assert.equal(groups[3].adds.length, 3);
- assert.equal(groups[3].removes.length, 3);
- for (const l of groups[3].removes) {
- assert.equal(l.text, 'all work and no play make jill a dull girl');
- }
- for (const l of groups[3].adds) {
- assert.equal(
- l.text,
- ' all work and no play make jill a dull girl'
- );
- }
+ assert.equal(groups[3].type, GrDiffGroupType.DELTA);
+ assert.equal(groups[3].lines.length, 6);
+ assert.equal(groups[3].adds.length, 3);
+ assert.equal(groups[3].removes.length, 3);
+ for (const l of groups[3].removes) {
+ assert.equal(l.text, 'all work and no play make jill a dull girl');
+ }
+ for (const l of groups[3].adds) {
+ assert.equal(l.text, ' all work and no play make jill a dull girl');
+ }
- assert.equal(groups[4].type, GrDiffGroupType.BOTH);
- assert.equal(groups[4].lines.length, 3);
- for (const l of groups[4].lines) {
- assert.equal(l.text, 'all work and no play make jill a dull girl');
- }
+ assert.equal(groups[4].type, GrDiffGroupType.BOTH);
+ assert.equal(groups[4].lines.length, 3);
+ for (const l of groups[4].lines) {
+ assert.equal(l.text, 'all work and no play make jill a dull girl');
+ }
- // The next chunk is partially shown, so it results in two groups
+ // The next chunk is partially shown, so it results in two groups
- assert.equal(groups[5].type, GrDiffGroupType.DELTA);
- assert.equal(groups[5].lines.length, 2);
- assert.equal(groups[5].adds.length, 1);
- assert.equal(groups[5].removes.length, 1);
- for (const l of groups[5].removes) {
- assert.equal(l.text, 'all work and no play make jill a dull girl');
- }
- for (const l of groups[5].adds) {
- assert.equal(
- l.text,
- ' all work and no play make jill a dull girl'
- );
- }
+ assert.equal(groups[5].type, GrDiffGroupType.DELTA);
+ assert.equal(groups[5].lines.length, 2);
+ assert.equal(groups[5].adds.length, 1);
+ assert.equal(groups[5].removes.length, 1);
+ for (const l of groups[5].removes) {
+ assert.equal(l.text, 'all work and no play make jill a dull girl');
+ }
+ for (const l of groups[5].adds) {
+ assert.equal(l.text, ' all work and no play make jill a dull girl');
+ }
- assert.equal(groups[6].type, GrDiffGroupType.CONTEXT_CONTROL);
- assert.equal(groups[6].contextGroups.length, 2);
+ assert.equal(groups[6].type, GrDiffGroupType.CONTEXT_CONTROL);
+ assert.equal(groups[6].contextGroups.length, 2);
- assert.equal(groups[6].contextGroups[0].lines.length, 4);
- assert.equal(groups[6].contextGroups[0].removes.length, 2);
- assert.equal(groups[6].contextGroups[0].adds.length, 2);
- for (const l of groups[6].contextGroups[0].removes) {
- assert.equal(l.text, 'all work and no play make jill a dull girl');
- }
- for (const l of groups[6].contextGroups[0].adds) {
- assert.equal(
- l.text,
- ' all work and no play make jill a dull girl'
- );
- }
+ assert.equal(groups[6].contextGroups[0].lines.length, 4);
+ assert.equal(groups[6].contextGroups[0].removes.length, 2);
+ assert.equal(groups[6].contextGroups[0].adds.length, 2);
+ for (const l of groups[6].contextGroups[0].removes) {
+ assert.equal(l.text, 'all work and no play make jill a dull girl');
+ }
+ for (const l of groups[6].contextGroups[0].adds) {
+ assert.equal(l.text, ' all work and no play make jill a dull girl');
+ }
- // The final chunk is completely hidden
- assert.equal(groups[6].contextGroups[1].type, GrDiffGroupType.BOTH);
- assert.equal(groups[6].contextGroups[1].lines.length, 3);
- for (const l of groups[6].contextGroups[1].lines) {
- assert.equal(l.text, 'all work and no play make jill a dull girl');
- }
- });
+ // The final chunk is completely hidden
+ assert.equal(groups[6].contextGroups[1].type, GrDiffGroupType.BOTH);
+ assert.equal(groups[6].contextGroups[1].lines.length, 3);
+ for (const l of groups[6].contextGroups[1].lines) {
+ assert.equal(l.text, 'all work and no play make jill a dull girl');
+ }
});
- test('in the middle, larger than context', () => {
+ test('in the middle, larger than context', async () => {
options.context = 10;
- processor = new GrDiffProcessor(consumer, options);
+ processor = new GrDiffProcessor(options);
const content = [
{a: ['all work and no play make andybons a dull boy']},
{
@@ -436,36 +404,35 @@
{a: ['all work and no play make andybons a dull boy']},
];
- return processor.process(content).then(() => {
- groups.shift(); // remove portedThreadsWithoutRangeGroup
+ const groups = await processor.process(content);
+ groups.shift(); // remove portedThreadsWithoutRangeGroup
- // group[0] is the file group
- // group[1] is the "a" group
+ // group[0] is the file group
+ // group[1] is the "a" group
- assert.equal(groups[2].type, GrDiffGroupType.BOTH);
- assert.equal(groups[2].lines.length, 10);
- for (const l of groups[2].lines) {
- assert.equal(l.text, 'all work and no play make jill a dull girl');
- }
+ assert.equal(groups[2].type, GrDiffGroupType.BOTH);
+ assert.equal(groups[2].lines.length, 10);
+ for (const l of groups[2].lines) {
+ assert.equal(l.text, 'all work and no play make jill a dull girl');
+ }
- assert.equal(groups[3].type, GrDiffGroupType.CONTEXT_CONTROL);
- assert.instanceOf(groups[3].contextGroups[0], GrDiffGroup);
- assert.equal(groups[3].contextGroups[0].lines.length, 80);
- for (const l of groups[3].contextGroups[0].lines) {
- assert.equal(l.text, 'all work and no play make jill a dull girl');
- }
+ assert.equal(groups[3].type, GrDiffGroupType.CONTEXT_CONTROL);
+ assert.instanceOf(groups[3].contextGroups[0], GrDiffGroup);
+ assert.equal(groups[3].contextGroups[0].lines.length, 80);
+ for (const l of groups[3].contextGroups[0].lines) {
+ assert.equal(l.text, 'all work and no play make jill a dull girl');
+ }
- assert.equal(groups[4].type, GrDiffGroupType.BOTH);
- assert.equal(groups[4].lines.length, 10);
- for (const l of groups[4].lines) {
- assert.equal(l.text, 'all work and no play make jill a dull girl');
- }
- });
+ assert.equal(groups[4].type, GrDiffGroupType.BOTH);
+ assert.equal(groups[4].lines.length, 10);
+ for (const l of groups[4].lines) {
+ assert.equal(l.text, 'all work and no play make jill a dull girl');
+ }
});
- test('in the middle, smaller than context', () => {
+ test('in the middle, smaller than context', async () => {
options.context = 10;
- processor = new GrDiffProcessor(consumer, options);
+ processor = new GrDiffProcessor(options);
const content = [
{a: ['all work and no play make andybons a dull boy']},
{
@@ -476,24 +443,23 @@
{a: ['all work and no play make andybons a dull boy']},
];
- return processor.process(content).then(() => {
- groups.shift(); // remove portedThreadsWithoutRangeGroup
+ const groups = await processor.process(content);
+ groups.shift(); // remove portedThreadsWithoutRangeGroup
- // group[0] is the file group
- // group[1] is the "a" group
+ // group[0] is the file group
+ // group[1] is the "a" group
- assert.equal(groups[2].type, GrDiffGroupType.BOTH);
- assert.equal(groups[2].lines.length, 5);
- for (const l of groups[2].lines) {
- assert.equal(l.text, 'all work and no play make jill a dull girl');
- }
- });
+ assert.equal(groups[2].type, GrDiffGroupType.BOTH);
+ assert.equal(groups[2].lines.length, 5);
+ for (const l of groups[2].lines) {
+ assert.equal(l.text, 'all work and no play make jill a dull girl');
+ }
});
});
test('in the middle with skip chunks', async () => {
options.context = 10;
- processor = new GrDiffProcessor(consumer, options);
+ processor = new GrDiffProcessor(options);
const content = [
{a: ['all work and no play make andybons a dull boy']},
{
@@ -510,8 +476,7 @@
{a: ['all work and no play make andybons a dull boy']},
];
- await processor.process(content);
-
+ const groups = await processor.process(content);
groups.shift(); // remove portedThreadsWithoutRangeGroup
// group[0] is the file group
@@ -547,7 +512,7 @@
test('works with skip === 0', async () => {
options.context = 3;
- processor = new GrDiffProcessor(consumer, options);
+ processor = new GrDiffProcessor(options);
const content = [
{
skip: 0,
@@ -571,7 +536,7 @@
left: {1: true},
right: {10: true},
};
- processor = new GrDiffProcessor(consumer, options);
+ processor = new GrDiffProcessor(options);
const content = [
{
@@ -620,7 +585,7 @@
.fill(0)
.map(() => `${Math.random()}`);
const content = [{ab}];
- processor.context = -1;
+ processor.context = FULL_CONTEXT;
const result = processor.splitLargeChunks(content);
assert.equal(result.length, 2);
assert.deepEqual(result[0].ab, content[0].ab.slice(0, maxGroupSize));
@@ -802,27 +767,20 @@
]);
});
- test('isScrolling paused', () => {
+ test('isScrolling paused', async () => {
const content = Array(200).fill({ab: ['', '']});
processor.isScrolling = true;
- processor.process(content);
- // Just the FILE and LOST groups.
- assert.equal(groups.length, 2);
- });
-
- test('isScrolling unpaused', () => {
- const content = Array(200).fill({ab: ['', '']});
+ const promise = processor.process(content);
processor.isScrolling = false;
- processor.process(content);
- // More groups have been processed. How many does not matter here.
+ const groups = await promise;
assert.isAtLeast(groups.length, 3);
});
- test('image diffs', () => {
+ test('image diffs', async () => {
const content = Array(200).fill({ab: ['', '']});
options.isBinary = true;
- processor = new GrDiffProcessor(consumer, options);
- processor.process(content);
+ processor = new GrDiffProcessor(options);
+ const groups = await processor.process(content);
assert.equal(groups.length, 2);
// Image diffs don't process content, just the 'FILE' line.
@@ -836,8 +794,8 @@
rows = loremIpsum.split(' ');
});
- test('WHOLE_FILE', () => {
- processor.context = WHOLE_FILE;
+ test('FULL_CONTEXT', () => {
+ processor.context = FULL_CONTEXT;
const state: State = {
lineNums: {left: 10, right: 100},
chunkIndex: 1,
@@ -870,8 +828,8 @@
);
});
- test('WHOLE_FILE with skip chunks still get collapsed', () => {
- processor.context = WHOLE_FILE;
+ test('FULL_CONTEXT with skip chunks still get collapsed', () => {
+ processor.context = FULL_CONTEXT;
const lineNums = {left: 10, right: 100};
const state = {
lineNums,
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-selection/gr-diff-selection_test.ts b/polygerrit-ui/app/embed/diff/gr-diff-selection/gr-diff-selection_test.ts
index f216e04..9cc6a90 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-selection/gr-diff-selection_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-selection/gr-diff-selection_test.ts
@@ -123,6 +123,7 @@
test('asks for text for left side Elements', () => {
const getSelectedTextStub = sinon.stub(element, 'getSelectedText');
emulateCopyOn(diffTable.querySelector('div.contentText'));
+ assert.isTrue(getSelectedTextStub.called);
assert.deepEqual([Side.LEFT], getSelectedTextStub.lastCall.args);
});
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-group.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-group.ts
index 771e298..f406215 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-group.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-group.ts
@@ -6,9 +6,7 @@
import {BLANK_LINE, GrDiffLine} from './gr-diff-line';
import {GrDiffLineType, LineNumber, LineRange, Side} from '../../../api/diff';
import {assertIsDefined, assert} from '../../../utils/common-util';
-import {untilRendered} from '../../../utils/dom-util';
import {isDefined} from '../../../types/types';
-import {LitElement} from 'lit';
export enum GrDiffGroupType {
/** Unchanged context. */
@@ -318,11 +316,6 @@
*/
readonly keyLocation: boolean = false;
- /**
- * Once rendered the diff builder sets this to the diff section element.
- */
- element?: HTMLElement;
-
readonly lines: GrDiffLine[] = [];
readonly adds: GrDiffLine[] = [];
@@ -490,22 +483,6 @@
}
}
- async waitUntilRendered() {
- const lineNumber = this.lines[0]?.beforeNumber;
- // The LOST or FILE lines may be hidden and thus never resolve an
- // untilRendered() promise.
- if (
- this.skip !== undefined ||
- typeof lineNumber !== 'number' ||
- this.type === GrDiffGroupType.CONTEXT_CONTROL
- ) {
- return Promise.resolve();
- }
- assertIsDefined(this.element);
- await (this.element as LitElement).updateComplete;
- await untilRendered(this.element.firstElementChild as HTMLElement);
- }
-
/**
* Determines whether the group is either totally an addition or totally
* a removal.
@@ -517,4 +494,10 @@
!(!this.adds.length && !this.removes.length)
);
}
+
+ id() {
+ return `${this.type} ${this.startLine(Side.LEFT)} ${this.startLine(
+ Side.RIGHT
+ )}`;
+ }
}
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 d309556..cfb64b9 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
@@ -4,8 +4,9 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {BlameInfo, CommentRange} from '../../../types/common';
-import {Side} from '../../../constants/constants';
+import {Side, SpecialFilePath} from '../../../constants/constants';
import {
+ DiffContextExpandedExternalDetail,
DiffPreferencesInfo,
DiffResponsiveMode,
DisplayLine,
@@ -15,6 +16,7 @@
RenderPreferences,
} from '../../../api/diff';
import {getBaseUrl} from '../../../utils/url-util';
+import {GrDiffGroup} from './gr-diff-group';
/**
* In JS, unicode code points above 0xFFFF occupy two elements of a string.
@@ -188,6 +190,59 @@
right: {[key: string]: boolean};
}
+/**
+ * "Context" is the number of lines that we are showing around diff chunks and
+ * commented lines. This typically comes from a user preference and is set to
+ * something like 3 or 10.
+ *
+ * `FULL_CONTEXT` means that the user wants to see the entire file. We could
+ * also call this "infinite context".
+ */
+export const FULL_CONTEXT = -1;
+
+export enum FullContext {
+ /** User has opted into showing the full context. */
+ YES = 'YES',
+ /** User has opted into showing only limited context. */
+ NO = 'NO',
+ /**
+ * User has not decided yet. Will see a warning message with two options then,
+ * if the file is too large.
+ */
+ UNDECIDED = 'UNDECIDED',
+}
+
+export function computeContext(
+ prefsContext: number | undefined,
+ showFullContext: FullContext,
+ defaultContext: number
+) {
+ if (showFullContext === FullContext.YES) {
+ return FULL_CONTEXT;
+ }
+ if (
+ prefsContext &&
+ !(showFullContext === FullContext.NO && prefsContext === FULL_CONTEXT)
+ ) {
+ return prefsContext;
+ }
+ return defaultContext;
+}
+
+export function computeLineLength(
+ prefs: DiffPreferencesInfo,
+ path: string | undefined
+): number {
+ if (path === SpecialFilePath.COMMIT_MESSAGE) {
+ return 72;
+ }
+ const lineLength = prefs.line_length;
+ if (Number.isInteger(lineLength) && lineLength > 0) {
+ return lineLength;
+ }
+ return 100;
+}
+
export function computeKeyLocations(
lineOfInterest: DisplayLine | undefined,
comments: GrDiffCommentThread[]
@@ -435,3 +490,11 @@
return blameNode;
}
+
+export interface DiffContextExpandedEventDetail
+ extends DiffContextExpandedExternalDetail {
+ /** The context control group that should be replaced by `groups`. */
+ contextGroup: GrDiffGroup;
+ groups: GrDiffGroup[];
+ numLines: number;
+}
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 7e6e7fc..639b1ac 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
@@ -15,8 +15,13 @@
toCommentThreadModel,
compareComments,
GrDiffThreadElement,
+ computeContext,
+ FULL_CONTEXT,
+ FullContext,
+ computeLineLength,
} from './gr-diff-utils';
import {FILE, LOST, Side} from '../../../api/diff';
+import {createDefaultDiffPrefs} from '../../../constants/constants';
const LINE_BREAK_HTML = '<span class="gr-diff br"></span>';
@@ -184,6 +189,55 @@
assert.isUndefined(getRange(threadEl));
});
+ suite('computeContext', () => {
+ test('computeContext 1', () => {
+ assert.equal(computeContext(1, FullContext.YES, 2), FULL_CONTEXT);
+ assert.equal(computeContext(1, FullContext.NO, 2), 1);
+ assert.equal(computeContext(1, FullContext.UNDECIDED, 2), 1);
+ });
+
+ test('computeContext FULL_CONTEXT', () => {
+ assert.equal(
+ computeContext(FULL_CONTEXT, FullContext.YES, 2),
+ FULL_CONTEXT
+ );
+ assert.equal(computeContext(FULL_CONTEXT, FullContext.NO, 2), 2);
+ assert.equal(
+ computeContext(FULL_CONTEXT, FullContext.UNDECIDED, 2),
+ FULL_CONTEXT
+ );
+ });
+ });
+
+ suite('computeLineLength', () => {
+ test('computeLineLength(1, ...)', () => {
+ assert.equal(
+ computeLineLength(
+ {...createDefaultDiffPrefs(), line_length: 1},
+ 'a.txt'
+ ),
+ 1
+ );
+ assert.equal(
+ computeLineLength(
+ {...createDefaultDiffPrefs(), line_length: 1},
+ undefined
+ ),
+ 1
+ );
+ });
+
+ test('computeLineLength(1, "/COMMIT_MSG")', () => {
+ assert.equal(
+ computeLineLength(
+ {...createDefaultDiffPrefs(), line_length: 1},
+ '/COMMIT_MSG'
+ ),
+ 72
+ );
+ });
+ });
+
suite('key locations', () => {
test('lineOfInterest is a key location', () => {
const lineOfInterest = {lineNum: 789, side: Side.LEFT};
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 1f212b0..2834f08 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
@@ -11,6 +11,9 @@
import '../gr-syntax-themes/gr-syntax-theme';
import '../gr-ranged-comment-themes/gr-ranged-comment-theme';
import '../gr-ranged-comment-hint/gr-ranged-comment-hint';
+import '../gr-diff-builder/gr-diff-builder-image';
+import '../gr-diff-builder/gr-diff-section';
+import '../gr-diff-builder/gr-diff-row';
import {
getLine,
getLineElByChild,
@@ -27,7 +30,9 @@
getSideByLineEl,
compareComments,
toCommentThreadModel,
- KeyLocations,
+ FullContext,
+ diffClasses,
+ DiffContextExpandedEventDetail,
} from '../gr-diff/gr-diff-utils';
import {BlameInfo, CommentRange, ImageInfo} from '../../../types/common';
import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
@@ -41,14 +46,10 @@
GrRangedCommentLayer,
} from '../gr-ranged-comment-layer/gr-ranged-comment-layer';
import {
- createDefaultDiffPrefs,
DiffViewMode,
Side,
+ createDefaultDiffPrefs,
} from '../../../constants/constants';
-import {
- GrDiffProcessor,
- ProcessingOptions,
-} from '../gr-diff-processor/gr-diff-processor';
import {fire, fireAlert} from '../../../utils/event-util';
import {MovedLinkClickedEvent, ValueChangedEvent} from '../../../types/events';
import {getContentEditableRange} from '../../../utils/safari-selection-util';
@@ -59,16 +60,12 @@
DisplayLine,
LineNumber,
LOST,
+ ContentLoadNeededEventDetail,
} from '../../../api/diff';
import {isHtmlElement, isSafari, toggleClass} from '../../../utils/dom-util';
import {assertIsDefined} from '../../../utils/common-util';
-import {
- debounceP,
- DelayedPromise,
- DELAYED_CANCELLATION,
-} from '../../../utils/async-util';
import {GrDiffSelection} from '../gr-diff-selection/gr-diff-selection';
-import {property, query, state} from 'lit/decorators.js';
+import {property, query, queryAll, state} from 'lit/decorators.js';
import {sharedStyles} from '../../../styles/shared-styles';
import {html, LitElement, nothing, PropertyValues} from 'lit';
import {when} from 'lit/directives/when.js';
@@ -83,14 +80,9 @@
import {getDiffLength} from '../../../utils/diff-util';
import {GrCoverageLayer} from '../gr-coverage-layer/gr-coverage-layer';
import {
- GrDiffBuilder,
- isImageDiffBuilder,
- isBinaryDiffBuilder,
- DiffContextExpandedEventDetail,
-} from '../gr-diff-builder/gr-diff-builder';
-import {GrDiffBuilderBinary} from '../gr-diff-builder/gr-diff-builder-binary';
-import {GrDiffBuilderImage} from '../gr-diff-builder/gr-diff-builder-image';
-import {GrAnnotation} from '../gr-diff-highlight/gr-annotation';
+ GrAnnotationImpl,
+ getStringLength,
+} from '../gr-diff-highlight/gr-annotation';
import {
GrDiffGroup,
GrDiffGroupType,
@@ -98,6 +90,9 @@
} from './gr-diff-group';
import {GrDiffLine} from './gr-diff-line';
import {subscribe} from '../../../elements/lit/subscription-controller';
+import {GrDiffSection} from '../gr-diff-builder/gr-diff-section';
+import {GrDiffRow} from '../gr-diff-builder/gr-diff-row';
+import {repeat} from 'lit/directives/repeat.js';
const TRAILING_WHITESPACE_PATTERN = /\s+$/;
@@ -154,6 +149,9 @@
@query('#diffTable')
diffTable?: HTMLTableElement;
+ @queryAll('gr-diff-section')
+ diffSections?: NodeListOf<GrDiffSection>;
+
@property({type: Boolean})
noAutoRender = false;
@@ -236,21 +234,6 @@
@property({type: Boolean})
override isContentEditable = isSafari();
- /**
- * Whether the safety check for large diffs when whole-file is set has
- * been bypassed. If the value is null, then the safety has not been
- * bypassed. If the value is a number, then that number represents the
- * context preference to use when rendering the bypassed diff.
- *
- * Private but used in tests.
- */
- @state()
- safetyBypass: number | null = null;
-
- // Private but used in tests.
- @state()
- showWarning?: boolean;
-
@property({type: String})
errorMessage: string | null = null;
@@ -270,59 +253,50 @@
@state()
diffLength?: number;
- /**
- * Observes comment nodes added or removed at any point.
- * Can be used to unregister upon detachment.
- */
+ /** Observes comment nodes added or removed at any point. */
private nodeObserver?: MutationObserver;
- @property({type: Array})
- layers?: DiffLayer[];
-
- // Private but used in tests.
- renderDiffTableTask?: DelayedPromise<void>;
-
// Private but used in tests.
diffSelection = new GrDiffSelection();
// Private but used in tests.
highlights = new GrDiffHighlight();
- private diffModel = new DiffModel(undefined);
-
- // visible for testing
- builder?: GrDiffBuilder;
+ private diffModel = new DiffModel();
/**
- * All layers, both from the outside and the default ones. See `layers` for
- * the property that can be set from the outside.
+ * Just the layers that are passed in from the outside. See `layersAll`
+ * for an array of all layers.
*/
- // visible for testing
- layersInternal: DiffLayer[] = [];
+ @property({type: Array})
+ layers: DiffLayer[] = [];
- // visible for testing
- showTabs?: boolean;
+ /**
+ * Just the internal default layers. See `layers` for the property that can
+ * be set from the outside.
+ */
+ @state() layersInternal: DiffLayer[] = [];
- // visible for testing
- showTrailingWhitespace?: boolean;
+ /**
+ * All layers, just combines `layers` and `layersInternal`.
+ */
+ @state() layersAll: DiffLayer[] = [];
private coverageLayerLeft = new GrCoverageLayer(Side.LEFT);
private coverageLayerRight = new GrCoverageLayer(Side.RIGHT);
- private rangeLayer?: GrRangedCommentLayer;
+ private rangeLayer = new GrRangedCommentLayer();
- // visible for testing
- processor?: GrDiffProcessor;
+ @state() groups: GrDiffGroup[] = [];
- /**
- * Groups are mostly just passed on to the diff builder (this.builder). But
- * we also keep track of them here for being able to fire a `render-content`
- * event when .element of each group has rendered.
- */
- private groups: GrDiffGroup[] = [];
+ @state() private context = 3;
- private keyLocations: KeyLocations = {left: {}, right: {}};
+ private readonly layerUpdateListener: (
+ start: LineNumber,
+ end: LineNumber,
+ side: Side
+ ) => void;
static override get styles() {
return [
@@ -339,18 +313,32 @@
provide(this, diffModelToken, () => this.diffModel);
subscribe(
this,
- () => this.diffModel.keyLocations$,
- keyLocations => (this.keyLocations = keyLocations)
+ () => this.diffModel.context$,
+ context => (this.context = context)
+ );
+ subscribe(
+ this,
+ () => this.diffModel.groups$,
+ groups => (this.groups = groups)
);
this.addEventListener(
'create-range-comment',
(e: CustomEvent<CreateRangeCommentEventDetail>) =>
this.handleCreateRangeComment(e)
);
- this.addEventListener('render-content', () => this.handleRenderContent());
this.addEventListener('moved-link-clicked', (e: MovedLinkClickedEvent) => {
this.dispatchSelectedLine(e.detail.lineNum, e.detail.side);
});
+ this.addEventListener(
+ 'diff-context-expanded-internal-new',
+ this.onDiffContextExpanded
+ );
+ this.layerUpdateListener = (
+ start: LineNumber,
+ end: LineNumber,
+ side: Side
+ ) => this.requestRowUpdates(start, end, side);
+ this.layersInternalInit();
}
override connectedCallback() {
@@ -364,15 +352,12 @@
if (this.diffTable) {
this.highlights.init(this.diffTable, this);
}
- this.diffBuilderInit();
}
override disconnectedCallback() {
this.removeSelectionListeners();
- this.renderDiffTableTask?.cancel();
this.diffSelection.cleanup();
this.highlights.cleanup();
- this.diffBuilderCleanup();
super.disconnectedCallback();
}
@@ -381,16 +366,24 @@
changedProperties.has('diff') ||
changedProperties.has('path') ||
changedProperties.has('renderPrefs') ||
+ changedProperties.has('viewMode') ||
changedProperties.has('prefs') ||
changedProperties.has('lineOfInterest')
) {
- this.diffModel.updateState({
- diff: this.diff,
- path: this.path,
- renderPrefs: this.renderPrefs,
- diffPrefs: this.prefs,
- lineOfInterest: this.lineOfInterest,
- });
+ if (this.diff && this.prefs) {
+ const renderPrefs = {...(this.renderPrefs ?? {})};
+ if (renderPrefs.view_mode === undefined) {
+ renderPrefs.view_mode = this.viewMode;
+ }
+ this.diffModel.updateState({
+ diff: this.diff,
+ path: this.path,
+ renderPrefs,
+ diffPrefs: this.prefs,
+ lineOfInterest: this.lineOfInterest,
+ isImageDiff: this.isImageDiff,
+ });
+ }
}
if (
changedProperties.has('path') ||
@@ -401,6 +394,9 @@
) {
this.prefsChanged();
}
+ if (changedProperties.has('layers')) {
+ this.layersChanged();
+ }
if (changedProperties.has('blame')) {
this.blameChanged();
}
@@ -422,18 +418,37 @@
}
}
- protected override updated(changedProperties: PropertyValues<this>): void {
+ private async fireRenderContent() {
+ await this.updateComplete;
+ this.loading = false;
+ this.observeNodes();
+ // TODO: Retire one of these two events.
+ fire(this, 'render-content', {});
+ fire(this, 'render', {});
+ }
+
+ protected override async getUpdateComplete(): Promise<boolean> {
+ const result = await super.getUpdateComplete();
+ const sections = [...(this.diffSections ?? [])];
+ await Promise.all(sections.map(section => section.updateComplete));
+ return result;
+ }
+
+ protected override updated(changedProperties: PropertyValues<this>) {
if (changedProperties.has('diff')) {
- // diffChanged relies on diffTable ahving been rendered.
+ // diffChanged relies on diffTable having been rendered.
this.diffChanged();
}
+ if (changedProperties.has('groups')) {
+ if (this.groups?.length > 0) this.fireRenderContent();
+ }
}
override render() {
+ fire(this.diffTable, 'render-start', {});
return html`
${this.renderHeader()} ${this.renderContainer()}
${this.renderNewlineWarning()} ${this.renderLoadingError()}
- ${this.renderSizeWarning()}
`;
}
@@ -449,6 +464,7 @@
private renderContainer() {
const cssClasses = {
+ newDiff: true,
diffContainer: true,
unified: this.viewMode === DiffViewMode.UNIFIED,
sideBySide: this.viewMode === DiffViewMode.SIDE_BY_SIDE,
@@ -460,7 +476,19 @@
id="diffTable"
class=${this.diffTableClass}
?contenteditable=${this.isContentEditable}
- ></table>
+ >
+ ${this.renderColumns()}
+ ${when(!this.showWarning(), () =>
+ repeat(
+ this.groups,
+ group => group.id(),
+ group => this.renderSectionElement(group)
+ )
+ )}
+ ${when(this.diff?.binary, () =>
+ this.isImageDiff ? this.renderImageDiff() : this.renderBinaryDiff()
+ )}
+ </table>
${when(
this.showNoChangeMessage(),
() => html`
@@ -470,6 +498,7 @@
</div>
`
)}
+ ${when(this.showWarning(), () => this.renderSizeWarning())}
</div>
`;
}
@@ -486,7 +515,7 @@
}
private renderSizeWarning() {
- if (!this.showWarning) return nothing;
+ if (!this.showWarning()) return nothing;
// TODO: Update comment about 'Whole file' as it's not in settings.
return html`
<div id="sizeWarning">
@@ -597,7 +626,7 @@
});
}
- this.updateCommentRanges(this.commentRanges);
+ this.rangeLayer?.updateRanges(this.commentRanges);
}
// Dispatch events that are handled by the gr-diff-highlight.
@@ -613,11 +642,8 @@
});
}
- /** Cancel any remaining diff builder rendering work. */
- cancel() {
- this.diffBuilderCleanup();
- this.renderDiffTableTask?.cancel();
- }
+ /** TODO: Can be removed when diff-old is gone. */
+ cancel() {}
getCursorStops(): Array<HTMLElement | AbortStop> {
if (this.hidden && this.noAutoRender) return [];
@@ -642,7 +668,7 @@
}
private blameChanged() {
- this.setBlame(this.blame);
+ this.setBlame(this.blame ?? []);
if (this.blame) {
this.classList.add('showBlame');
} else {
@@ -760,22 +786,20 @@
this.unhideLine(lineNum, this.lineOfInterest.side);
}
- private cleanup() {
- this.cancel();
- this.blame = null;
- this.safetyBypass = null;
- this.showWarning = false;
- this.clearDiffContent();
- }
-
private prefsChanged() {
if (!this.prefs) return;
this.blame = null;
this.updatePreferenceStyles();
- if (this.diff && !this.noRenderOnPrefsChange) {
- this.debounceRenderDiffTable();
+ if (!Number.isInteger(this.prefs.tab_size) || this.prefs.tab_size <= 0) {
+ this.handlePreferenceError('tab size');
+ }
+ if (
+ !Number.isInteger(this.prefs.line_length) ||
+ this.prefs.line_length <= 0
+ ) {
+ this.handlePreferenceError('diff width');
}
}
@@ -855,15 +879,12 @@
if (this.prefs) {
this.updatePreferenceStyles();
}
- this.updateRenderPrefs(this.renderPrefs);
}
private diffChanged() {
this.loading = true;
- this.cleanup();
if (this.diff) {
this.diffLength = this.getDiffLength(this.diff);
- this.debounceRenderDiffTable();
assertIsDefined(this.diffTable, 'diffTable');
this.diffSelection.init(this.diff, this.diffTable);
this.highlights.init(this.diffTable, this);
@@ -875,73 +896,23 @@
return getDiffLength(diff);
}
- /**
- * When called multiple times from the same task, will call
- * _renderDiffTable only once, in the next task (scheduled via `setTimeout`).
- *
- * This should be used instead of calling _renderDiffTable directly to
- * render the diff in response to an input change, because there may be
- * multiple inputs changing in the same microtask, but we only want to
- * render once.
- */
- private debounceRenderDiffTable() {
- // at this point gr-diff might be considered as rendered from the outside
- // (client), although it was not actually rendered. Clients need to know
- // when it is safe to perform operations like cursor moves, for example,
- // and if changing an input actually requires a reload of the diff table.
- // Since `fire` is synchronous it allows clients to be aware when an
- // async render is needed and that they can wait for a further `render`
- // event to actually take further action.
- fire(this, 'render-required', {});
- this.renderDiffTableTask = debounceP(
- this.renderDiffTableTask,
- async () => await this.renderDiffTable()
- );
- this.renderDiffTableTask.catch((e: unknown) => {
- if (e === DELAYED_CANCELLATION) return;
- throw e;
- });
- }
-
- // Private but used in tests.
- async renderDiffTable() {
- this.unobserveNodes();
- if (!this.diff || !this.prefs) {
- fire(this, 'render', {});
- return;
- }
- if (
- this.getBypassPrefs().context === -1 &&
+ private showWarning() {
+ return (
+ this.prefs?.context === FULL_CONTEXT &&
+ this.diffModel.getState().showFullContext === FullContext.UNDECIDED &&
this.diffLength &&
- this.diffLength >= LARGE_DIFF_THRESHOLD_LINES &&
- this.safetyBypass === null
- ) {
- this.showWarning = true;
- fire(this, 'render', {});
- return;
- }
-
- this.showWarning = false;
-
- this.updateCommentRanges(this.commentRanges);
- this.updateCoverageRanges(this.coverageRanges);
- await this.legacyRender();
- }
-
- private handleRenderContent() {
- this.querySelectorAll('gr-ranged-comment-hint').forEach(element =>
- element.remove()
+ this.diffLength >= LARGE_DIFF_THRESHOLD_LINES
);
- this.loading = false;
- this.observeNodes();
- // We are just converting 'render-content' into 'render' here. Maybe we
- // should retire the 'render' event in favor of 'render-content'?
- fire(this, 'render', {});
}
+ /**
+ * This must be called once, but only after diff lines are rendered. Otherwise
+ * `processNodes()` will fail to lookup the HTML elements that it wants to
+ * manipulate.
+ */
private observeNodes() {
+ if (this.nodeObserver) return;
// First stop observing old nodes.
- this.unobserveNodes();
// Then introduce a Mutation observer that watches for children being added
// to gr-diff. If those children are `isThreadEl`, namely then they are
// processed.
@@ -1020,19 +991,6 @@
}
}
- private unobserveNodes() {
- if (this.nodeObserver) {
- this.nodeObserver.disconnect();
- this.nodeObserver = undefined;
- }
- // You only stop observing for comment thread elements when the diff is
- // completely rendered from scratch. And then comment thread elements
- // will be (re-)added *after* rendering is done. That is also when we
- // re-start observing. So it is appropriate to thoroughly clean up
- // everything that the observer is managing.
- this.commentRanges = [];
- }
-
private insertPortedCommentsWithoutRangeMessage(lostCell: Element) {
const existingMessage = lostCell.querySelector('div.lost-message');
if (existingMessage) return;
@@ -1048,23 +1006,8 @@
lostCell.insertBefore(div, lostCell.firstChild);
}
- /**
- * Get the preferences object including the safety bypass context (if any).
- */
- // visible for testing
- getBypassPrefs() {
- assertIsDefined(this.prefs, 'prefs');
- if (this.safetyBypass !== null) {
- return {...this.prefs, context: this.safetyBypass};
- }
- return this.prefs;
- }
-
- clearDiffContent() {
- this.unobserveNodes();
- if (!this.diffTable) return;
- this.diffTable.innerHTML = '';
- }
+ /** TODO: Can be removed when diff-old is gone. */
+ clearDiffContent() {}
// Private but used in tests.
computeDiffHeaderItems() {
@@ -1083,28 +1026,20 @@
}
private handleFullBypass() {
- this.safetyBypass = FULL_CONTEXT;
- this.debounceRenderDiffTable();
+ this.diffModel.updateState({showFullContext: FullContext.YES});
}
private collapseContext() {
- // Uses the default context amount if the preference is for the entire file.
- this.safetyBypass =
- this.prefs?.context && this.prefs.context >= 0
- ? null
- : createDefaultDiffPrefs().context;
- this.debounceRenderDiffTable();
+ this.diffModel.updateState({showFullContext: FullContext.NO});
}
+ // TODO: Migrate callers to just update prefs.context.
toggleAllContext() {
- if (!this.prefs) {
- return;
- }
- if (this.getBypassPrefs().context < 0) {
- this.collapseContext();
- } else {
- this.handleFullBypass();
- }
+ const current = this.diffModel.getState().showFullContext;
+ this.diffModel.updateState({
+ showFullContext:
+ current === FullContext.YES ? FullContext.NO : FullContext.YES,
+ });
}
private computeNewlineWarning(): string | undefined {
@@ -1121,72 +1056,49 @@
return messages.join(' \u2014 '); // \u2014 - '—'
}
- private updateCommentRanges(ranges: CommentRangeLayer[]) {
- this.rangeLayer?.updateRanges(ranges);
- }
-
private updateCoverageRanges(rs: CoverageRange[]) {
this.coverageLayerLeft.setRanges(rs.filter(r => r?.side === Side.LEFT));
this.coverageLayerRight.setRanges(rs.filter(r => r?.side === Side.RIGHT));
}
- legacyRender(): Promise<void> {
- assertIsDefined(this.diff, 'diff');
- assertIsDefined(this.diffTable, 'diff table');
- assertIsDefined(this.prefs, 'prefs');
-
- // Setting up annotation layers must happen after plugins are
- // installed, and |render| satisfies the requirement, however,
- // |attached| doesn't because in the diff view page, the element is
- // attached before plugins are installed.
- this.setupAnnotationLayers();
-
- this.showTabs = this.prefs.show_tabs;
- this.showTrailingWhitespace = this.prefs.show_whitespace_errors;
-
- this.diffBuilderCleanup();
- this.builder = this.getDiffBuilder();
- this.diffBuilderInit();
-
- this.diffTable.innerHTML = '';
- this.builder.addColumns(this.diffTable, getLineNumberCellWidth(this.prefs));
-
- const options: ProcessingOptions = {
- context: this.getBypassPrefs().context,
- keyLocations: this.keyLocations,
- isBinary: !!(this.isImageDiff || this.diff.binary),
- };
- if (this.renderPrefs?.num_lines_rendered_at_once) {
- options.asyncThreshold = this.renderPrefs.num_lines_rendered_at_once;
- }
- this.processor = new GrDiffProcessor(this, options);
-
- fire(this.diffTable, 'render-start', {});
- return (
- this.processor
- .process(this.diff.content)
- .then(async () => {
- if (isImageDiffBuilder(this.builder)) {
- this.builder.renderImageDiff();
- } else if (isBinaryDiffBuilder(this.builder)) {
- this.builder.renderBinaryDiff();
- }
- await this.untilGroupsRendered();
- fire(this.diffTable, 'render-content', {});
- })
- // Mocha testing does not like uncaught rejections, so we catch
- // the cancels which are expected and should not throw errors in
- // tests.
- .catch(e => {
- if (!e.isCanceled) return Promise.reject(e);
- return;
- })
+ public renderImageDiff() {
+ return when(
+ this.useNewImageDiffUi,
+ () => this.renderImageDiffNew(),
+ () => this.renderImageDiffOld()
);
}
- // visible for testing
- async untilGroupsRendered(groups: readonly GrDiffGroup[] = this.groups) {
- return Promise.all(groups.map(g => g.waitUntilRendered()));
+ private renderImageDiffNew() {
+ const autoBlink = !!this.renderPrefs?.image_diff_prefs?.automatic_blink;
+ return html`
+ <gr-diff-image-new
+ .automaticBlink=${autoBlink}
+ .baseImage=${this.baseImage ?? undefined}
+ .revisionImage=${this.revisionImage ?? undefined}
+ ></gr-diff-image-new>
+ `;
+ }
+
+ private renderImageDiffOld() {
+ return html`
+ <gr-diff-image-old
+ .baseImage=${this.baseImage ?? undefined}
+ .revisionImage=${this.revisionImage ?? undefined}
+ ></gr-diff-image-old>
+ `;
+ }
+
+ public renderBinaryDiff() {
+ return html`
+ <tbody class="gr-diff binary-diff">
+ <tr class="gr-diff">
+ <td colspan="5" class="gr-diff">
+ <span>Difference in binary files</span>
+ </td>
+ </tr>
+ </tbody>
+ `;
}
private onDiffContextExpanded = (
@@ -1194,14 +1106,19 @@
) => {
// Don't stop propagation. The host may listen for reporting or
// resizing.
- this.replaceGroup(e.detail.contextGroup, e.detail.groups);
+ this.diffModel.replaceGroup(e.detail.contextGroup, e.detail.groups);
};
- // visible for testing
- setupAnnotationLayers() {
- this.rangeLayer = new GrRangedCommentLayer();
+ private layersChanged() {
+ this.layersAll = [...this.layersInternal, ...this.layers];
+ for (const layer of this.layersAll) {
+ layer.removeListener?.(this.layerUpdateListener);
+ layer.addListener?.(this.layerUpdateListener);
+ }
+ }
- const layers: DiffLayer[] = [
+ private layersInternalInit() {
+ this.layersInternal = [
this.createTrailingWhitespaceLayer(),
this.createIntralineLayer(),
this.createTabIndicatorLayer(),
@@ -1210,16 +1127,7 @@
this.coverageLayerLeft,
this.coverageLayerRight,
];
-
- if (this.layers) {
- layers.push(...this.layers);
- }
- this.layersInternal = layers;
- }
-
- getContentTdByLine(lineNumber: LineNumber, side?: Side) {
- if (!this.builder) return undefined;
- return this.builder.getContentTdByLine(lineNumber, side);
+ this.layersChanged();
}
getContentTdByLineEl(lineEl?: Element): Element | undefined {
@@ -1230,21 +1138,6 @@
return this.getContentTdByLine(line, side);
}
- getLineElByNumber(lineNumber: LineNumber, side?: Side) {
- if (!this.builder) return undefined;
- return this.builder.getLineElByNumber(lineNumber, side);
- }
-
- getLineNumberRows() {
- if (!this.builder) return [];
- return this.builder.getLineNumberRows();
- }
-
- getLineNumEls(side: Side) {
- if (!this.builder) return [];
- return this.builder.getLineNumEls(side);
- }
-
/**
* When the line is hidden behind a context expander, expand it.
*
@@ -1255,8 +1148,7 @@
*/
unhideLine(lineNum: number, side: Side) {
assertIsDefined(this.prefs, 'prefs');
- if (!this.builder) return;
- const group = this.builder.findGroup(side, lineNum);
+ const group = this.findGroup(side, lineNum);
// Cannot unhide a line that is not part of the diff.
if (!group) return;
// If it's already visible, great!
@@ -1267,7 +1159,7 @@
const groups = hideInContextControl(
group.contextGroups,
0,
- lineOffset - 1 - this.prefs.context
+ lineOffset - 1 - this.context
);
// If there is a context group, it will be the first group because we
// start hiding from 0 offset
@@ -1277,68 +1169,14 @@
newGroups.push(
...hideInContextControl(
groups,
- lineOffset + 1 + this.prefs.context,
+ lineOffset + 1 + this.context,
// Both ends inclusive, so difference is the offset of the last line.
// But we need to pass the first line not to hide, which is the element
// after.
lineRange.end_line - lineRange.start_line + 1
)
);
- this.replaceGroup(group, newGroups);
- }
-
- /**
- * Replace the group of a context control section by rendering the provided
- * groups instead. This happens in response to expanding a context control
- * group.
- *
- * @param contextGroup The context control group to replace
- * @param newGroups The groups that are replacing the context control group
- */
- private replaceGroup(
- contextGroup: GrDiffGroup,
- newGroups: readonly GrDiffGroup[]
- ) {
- if (!this.builder) return;
- fire(this.diffTable, 'render-start', {});
- this.builder.replaceGroup(contextGroup, newGroups);
- this.groups = this.groups.filter(g => g !== contextGroup);
- this.groups.push(...newGroups);
- this.untilGroupsRendered(newGroups).then(() => {
- fire(this.diffTable, 'render-content', {});
- });
- }
-
- /**
- * This is meant to be called when the gr-diff component re-connects, or when
- * the diff is (re-)rendered.
- *
- * Make sure that this method is symmetric with cleanup(), which is called
- * when gr-diff disconnects.
- */
- private diffBuilderInit() {
- this.cleanup();
- this.diffTable?.addEventListener(
- 'diff-context-expanded-internal-new',
- this.onDiffContextExpanded
- );
- this.builder?.init();
- }
-
- /**
- * This is meant to be called when the gr-diff component disconnects, or when
- * the diff is (re-)rendered.
- *
- * Make sure that this method is symmetric with init(), which is called when
- * gr-diff re-connects.
- */
- private diffBuilderCleanup() {
- this.processor?.cancel();
- this.builder?.cleanup();
- this.diffTable?.removeEventListener(
- 'diff-context-expanded-internal-new',
- this.onDiffContextExpanded
- );
+ this.diffModel.replaceGroup(group, newGroups);
}
// visible for testing
@@ -1346,95 +1184,11 @@
const message =
`The value of the '${pref}' user preference is ` +
'invalid. Fix in diff preferences';
- assertIsDefined(this.diffTable, 'diff table');
- fireAlert(this.diffTable, message);
+ fireAlert(this, message);
throw Error(`Invalid preference value: ${pref}`);
}
// visible for testing
- getDiffBuilder(): GrDiffBuilder {
- assertIsDefined(this.diff, 'diff');
- assertIsDefined(this.diffTable, 'diff table');
- assertIsDefined(this.prefs, 'prefs');
- if (isNaN(this.prefs.tab_size) || this.prefs.tab_size <= 0) {
- this.handlePreferenceError('tab size');
- }
-
- if (isNaN(this.prefs.line_length) || this.prefs.line_length <= 0) {
- this.handlePreferenceError('diff width');
- }
-
- const localPrefs = {...this.prefs};
- if (this.path === COMMIT_MSG_PATH) {
- // override line_length for commit msg the same way as
- // in gr-diff
- localPrefs.line_length = COMMIT_MSG_LINE_LENGTH;
- }
-
- let builder = null;
- if (this.isImageDiff) {
- builder = new GrDiffBuilderImage(
- this.diff,
- localPrefs,
- this.diffTable,
- this.baseImage ?? null,
- this.revisionImage ?? null,
- this.renderPrefs,
- this.useNewImageDiffUi
- );
- } else if (this.diff.binary) {
- return new GrDiffBuilderBinary(this.diff, localPrefs, this.diffTable);
- } else if (this.viewMode === DiffViewMode.SIDE_BY_SIDE) {
- this.renderPrefs = {
- ...this.renderPrefs,
- view_mode: DiffViewMode.SIDE_BY_SIDE,
- };
- builder = new GrDiffBuilder(
- this.diff,
- localPrefs,
- this.diffTable,
- this.layersInternal,
- this.renderPrefs
- );
- } else if (this.viewMode === DiffViewMode.UNIFIED) {
- this.renderPrefs = {
- ...this.renderPrefs,
- view_mode: DiffViewMode.UNIFIED,
- };
- builder = new GrDiffBuilder(
- this.diff,
- localPrefs,
- this.diffTable,
- this.layersInternal,
- this.renderPrefs
- );
- }
- if (!builder) {
- throw Error(`Unsupported diff view mode: ${this.viewMode}`);
- }
- return builder;
- }
-
- /**
- * Called when the processor starts converting the diff information from the
- * server into chunks.
- */
- clearGroups() {
- if (!this.builder) return;
- this.groups = [];
- this.builder.clearGroups();
- }
-
- /**
- * Called when the processor is done converting a chunk of the diff.
- */
- addGroup(group: GrDiffGroup) {
- if (!this.builder) return;
- this.builder.addGroups([group]);
- this.groups.push(group);
- }
-
- // visible for testing
createIntralineLayer(): DiffLayer {
return {
// Take a DIV.contentText element and a line object with intraline
@@ -1453,10 +1207,10 @@
// If endIndex isn't present, continue to the end of the line.
const endIndex =
highlight.endIndex === undefined
- ? GrAnnotation.getStringLength(line.text)
+ ? getStringLength(line.text)
: highlight.endIndex;
- GrAnnotation.annotateElement(
+ GrAnnotationImpl.annotateElement(
contentEl,
highlight.startIndex,
endIndex - highlight.startIndex,
@@ -1469,15 +1223,10 @@
// visible for testing
createTabIndicatorLayer(): DiffLayer {
- const show = () => this.showTabs;
+ const show = () => this.prefs?.show_tabs;
return {
annotate(contentEl: HTMLElement, _: HTMLElement, line: GrDiffLine) {
- // If visible tabs are disabled, do nothing.
- if (!show()) {
- return;
- }
-
- // Find and annotate the locations of tabs.
+ if (!show()) return;
annotateSymbols(contentEl, line, '\t', 'tab-indicator');
},
};
@@ -1501,23 +1250,17 @@
// visible for testing
createTrailingWhitespaceLayer(): DiffLayer {
- const show = () => this.showTrailingWhitespace;
-
+ const show = () => this.prefs?.show_whitespace_errors;
return {
annotate(contentEl: HTMLElement, _: HTMLElement, line: GrDiffLine) {
- if (!show()) {
- return;
- }
-
+ if (!show()) return;
const match = line.text.match(TRAILING_WHITESPACE_PATTERN);
if (match) {
// Normalize string positions in case there is unicode before or
// within the match.
- const index = GrAnnotation.getStringLength(
- line.text.substr(0, match.index)
- );
- const length = GrAnnotation.getStringLength(match[0]);
- GrAnnotation.annotateElement(
+ const index = getStringLength(line.text.substr(0, match.index));
+ const length = getStringLength(match[0]);
+ GrAnnotationImpl.annotateElement(
contentEl,
index,
length,
@@ -1528,13 +1271,167 @@
};
}
- setBlame(blame: BlameInfo[] | null) {
- if (!this.builder) return;
- this.builder.setBlame(blame ?? []);
+ getContentTdByLine(
+ lineNumber: LineNumber,
+ side?: Side
+ ): HTMLTableCellElement | undefined {
+ if (!side) return undefined;
+ const row = this.findRow(side, lineNumber);
+ return row?.getContentCell(side);
}
- updateRenderPrefs(renderPrefs: RenderPreferences) {
- this.builder?.updateRenderPrefs(renderPrefs);
+ getLineElByNumber(
+ lineNumber: LineNumber,
+ side?: Side
+ ): HTMLTableCellElement | undefined {
+ if (!side) return undefined;
+ const row = this.findRow(side, lineNumber);
+ return row?.getLineNumberCell(side);
+ }
+
+ private findRow(side: Side, lineNumber: LineNumber): GrDiffRow | 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() {
+ assertIsDefined(this.diffTable, 'diffTable');
+ const sections = [
+ ...this.diffTable.querySelectorAll<GrDiffSection>('gr-diff-section'),
+ ];
+ return sections.map(s => s.getDiffRows()).flat();
+ }
+
+ getLineNumberRows(): HTMLTableRowElement[] {
+ const rows = this.getDiffRows();
+ return rows.map(r => r.getTableRow()).filter(isDefined);
+ }
+
+ getLineNumEls(side: Side): HTMLTableCellElement[] {
+ const rows = this.getDiffRows();
+ return rows.map(r => r.getLineNumberCell(side)).filter(isDefined);
+ }
+
+ /** This is used when layers initiate an update. */
+ private requestRowUpdates(start: LineNumber, end: LineNumber, side: Side) {
+ 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 {
+ assertIsDefined(this.diffTable, 'diffTable');
+ const leftClass = `left-${group.startLine(Side.LEFT)}`;
+ const rightClass = `right-${group.startLine(Side.RIGHT)}`;
+ return (
+ this.diffTable.querySelector<GrDiffSection>(
+ `gr-diff-section.${leftClass}.${rightClass}`
+ ) ?? undefined
+ );
+ }
+
+ renderSectionElement(group: GrDiffGroup) {
+ const leftClass = `left-${group.startLine(Side.LEFT)}`;
+ const rightClass = `right-${group.startLine(Side.RIGHT)}`;
+ if (this.diff?.binary && group.startLine(Side.LEFT) === LOST) {
+ return nothing;
+ }
+ return html`
+ <gr-diff-section
+ class="${leftClass} ${rightClass}"
+ .group=${group}
+ .diff=${this.diff}
+ .layers=${this.layersAll}
+ .diffPrefs=${this.prefs}
+ .renderPrefs=${this.renderPrefs}
+ ></gr-diff-section>
+ `;
+ }
+
+ renderColumns() {
+ const lineNumberWidth = getLineNumberCellWidth(
+ this.prefs ?? createDefaultDiffPrefs()
+ );
+ return html`
+ <colgroup>
+ <col class=${diffClasses('blame')}></col>
+ ${when(
+ (this.renderPrefs?.view_mode ?? this.viewMode) ===
+ DiffViewMode.UNIFIED,
+ () => html` ${this.renderUnifiedColumns(lineNumberWidth)} `,
+ () => html`
+ ${this.renderSideBySideColumns(Side.LEFT, lineNumberWidth)}
+ ${this.renderSideBySideColumns(Side.RIGHT, lineNumberWidth)}
+ `
+ )}
+ </colgroup>
+ `;
+ }
+
+ private renderUnifiedColumns(lineNumberWidth: number) {
+ return html`
+ <col class=${diffClasses()} width=${lineNumberWidth}></col>
+ <col class=${diffClasses()} width=${lineNumberWidth}></col>
+ <col class=${diffClasses()}></col>
+ `;
+ }
+
+ private renderSideBySideColumns(side: Side, lineNumberWidth: number) {
+ return html`
+ <col class=${diffClasses(side)} width=${lineNumberWidth}></col>
+ <col class=${diffClasses(side, 'sign')}></col>
+ <col class=${diffClasses(side)}></col>
+ `;
+ }
+
+ findGroup(side: Side, line: LineNumber) {
+ return this.groups.find(group => group.containsLine(side, line));
+ }
+
+ // visible for testing
+ getGroupsByLineRange(
+ startLine: LineNumber,
+ endLine: LineNumber,
+ side: Side
+ ): GrDiffGroup[] {
+ const startIndex = this.groups.findIndex(group =>
+ group.containsLine(side, startLine)
+ );
+ if (startIndex === -1) return [];
+ let endIndex = this.groups.findIndex(group =>
+ group.containsLine(side, endLine)
+ );
+ // Not all groups may have been processed yet (i.e. this.groups is still
+ // incomplete). In that case let's just return *all* groups until the end
+ // of the array.
+ if (endIndex === -1) endIndex = this.groups.length - 1;
+ // The filter preserves the legacy behavior to only return non-context
+ // groups
+ return this.groups
+ .slice(startIndex, endIndex + 1)
+ .filter(group => group.lines.length > 0);
+ }
+
+ /**
+ * Set the blame information for the diff. For any already-rendered line,
+ * re-render its blame cell content.
+ */
+ setBlame(blame: BlameInfo[]) {
+ for (const blameInfo of blame) {
+ for (const range of blameInfo.ranges) {
+ for (let line = range.start; line <= range.end; line++) {
+ const row = this.findRow(Side.LEFT, line);
+ if (row) row.blameInfo = blameInfo;
+ }
+ }
+ }
}
}
@@ -1564,7 +1461,7 @@
// Skip forward by the length of the content
pos += split[i].length;
- GrAnnotation.annotateElement(contentEl, pos, 1, `gr-diff ${className}`);
+ GrAnnotationImpl.annotateElement(contentEl, pos, 1, `gr-diff ${className}`);
pos++;
}
@@ -1595,5 +1492,7 @@
* renders and for partial rerenders.
*/
'render-content': CustomEvent<{}>;
+ 'diff-context-expanded-internal-new': CustomEvent<DiffContextExpandedEventDetail>;
+ 'content-load-needed': CustomEvent<ContentLoadNeededEventDetail>;
}
}
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_test.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_test.ts
index 99db4e9..30f85cc 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_test.ts
@@ -30,7 +30,6 @@
queryAndAssert,
stubBaseUrl,
stubRestApi,
- waitEventLoop,
waitQueryAndAssert,
waitUntil,
} from '../../../test/test-utils';
@@ -39,12 +38,13 @@
import {GrDiff} from './gr-diff';
import {ImageInfo} from '../../../types/common';
import {GrRangedCommentHint} from '../gr-ranged-comment-hint/gr-ranged-comment-hint';
-import {assertIsDefined} from '../../../utils/common-util';
import {fixture, html, assert} from '@open-wc/testing';
import {createDefaultDiffPrefs} from '../../../constants/constants';
-import {GrDiffBuilder} from '../gr-diff-builder/gr-diff-builder';
import {GrDiffRow} from '../gr-diff-builder/gr-diff-row';
-import {GrAnnotation} from '../gr-diff-highlight/gr-annotation';
+import {
+ GrAnnotationImpl,
+ getStringLength,
+} from '../gr-diff-highlight/gr-annotation';
import {GrDiffLine} from './gr-diff-line';
const DEFAULT_PREFS = createDefaultDiffPrefs();
@@ -76,8 +76,18 @@
assert.shadowDom.equal(
element,
/* HTML */ `
- <div class="diffContainer sideBySide">
- <table id="diffTable"></table>
+ <div class="diffContainer newDiff sideBySide">
+ <table id="diffTable">
+ <colgroup>
+ <col class="blame gr-diff" />
+ <col class="gr-diff left" width="48" />
+ <col class="gr-diff left sign" />
+ <col class="gr-diff left" />
+ <col class="gr-diff right" width="48" />
+ <col class="gr-diff right sign" />
+ <col class="gr-diff right" />
+ </colgroup>
+ </table>
</div>
`
);
@@ -92,7 +102,7 @@
assert.shadowDom.equal(
element,
/* HTML */ `
- <div class="diffContainer unified">
+ <div class="diffContainer newDiff unified">
<table class="selected-right" id="diffTable">
<colgroup>
<col class="blame gr-diff" />
@@ -1358,7 +1368,7 @@
assert.shadowDom.equal(
element,
/* HTML */ `
- <div class="diffContainer sideBySide">
+ <div class="diffContainer newDiff sideBySide">
<table class="selected-right" id="diffTable">
<colgroup>
<col class="blame gr-diff" />
@@ -3150,7 +3160,7 @@
assert.shadowDom.equal(
element,
/* HTML */ `
- <div class="diffContainer sideBySide">
+ <div class="diffContainer newDiff sideBySide">
<gr-diff-section class="left-FILE right-FILE"> </gr-diff-section>
<gr-diff-row class="left-FILE right-FILE"> </gr-diff-row>
<table class="selected-right" id="diffTable">
@@ -3163,7 +3173,6 @@
<col class="gr-diff right sign" />
<col class="gr-diff right" />
</colgroup>
- <tbody class="binary-diff gr-diff"></tbody>
<tbody class="both gr-diff section">
<tr
aria-labelledby="left-button-FILE left-content-FILE right-button-FILE right-content-FILE"
@@ -3305,13 +3314,13 @@
<td class="blank gr-diff left lineNum"></td>
<td class="gr-diff left">
<label class="gr-diff">
- <span class="gr-diff label"> image/bmp </span>
+ <span class="gr-diff label"> 1×1 image/bmp </span>
</label>
</td>
<td class="blank gr-diff lineNum right"></td>
<td class="gr-diff right">
<label class="gr-diff">
- <span class="gr-diff label"> image/bmp </span>
+ <span class="gr-diff label"> 1×1 image/bmp </span>
</label>
</td>
</tr>
@@ -3369,7 +3378,7 @@
<label class="gr-diff">
<span class="gr-diff name"> carrot.jpg </span>
<br class="gr-diff" />
- <span class="gr-diff label"> image/bmp </span>
+ <span class="gr-diff label"> 1×1 image/bmp </span>
</label>
`
);
@@ -3379,7 +3388,7 @@
<label class="gr-diff">
<span class="gr-diff name"> carrot2.jpg </span>
<br class="gr-diff" />
- <span class="gr-diff label"> image/bmp </span>
+ <span class="gr-diff label"> 1×1 image/bmp </span>
</label>
`
);
@@ -3538,7 +3547,6 @@
ignore_whitespace: 'IGNORE_NONE',
};
await element.updateComplete;
- element.renderDiffTable();
}
test('returns [] when hidden and noAutoRender', async () => {
@@ -3554,6 +3562,7 @@
test('returns one stop per line and one for the file row', async () => {
await setupDiff();
element.loading = false;
+ await waitUntil(() => element.groups.length > 2);
await element.updateComplete;
const ROWS = 48;
const FILE_ROW = 1;
@@ -3567,10 +3576,12 @@
test('returns an additional AbortStop when still loading', async () => {
await setupDiff();
element.loading = true;
+ await waitUntil(() => element.groups.length > 2);
await element.updateComplete;
const ROWS = 48;
const FILE_ROW = 1;
const LOST_ROW = 1;
+ element.loading = true;
const actual = element.getCursorStops();
assert.equal(actual.length, ROWS + FILE_ROW + LOST_ROW + 1);
assert.isTrue(actual[actual.length - 1] instanceof AbortStop);
@@ -3694,67 +3705,6 @@
assert.isEmpty(element.querySelectorAll('gr-ranged-comment-hint'));
});
-
- suite('change in preferences', () => {
- setup(async () => {
- element.diff = {
- meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg', lines: 66},
- meta_b: {name: 'carrot.jpg', content_type: 'image/jpeg', lines: 560},
- diff_header: [],
- intraline_status: 'OK',
- change_type: 'MODIFIED',
- content: [{skip: 66}],
- };
- await element.updateComplete;
- await element.renderDiffTableTask?.flush();
- });
-
- test('change in preferences re-renders diff', async () => {
- const stub = sinon.stub(element, 'renderDiffTable');
- element.prefs = {
- ...MINIMAL_PREFS,
- };
- await element.updateComplete;
- await element.renderDiffTableTask?.flush();
- assert.isTrue(stub.called);
- });
-
- test('adding/removing property in preferences re-renders diff', async () => {
- const stub = sinon.stub(element, 'renderDiffTable');
- const newPrefs1: DiffPreferencesInfo = {
- ...MINIMAL_PREFS,
- line_wrapping: true,
- };
- element.prefs = newPrefs1;
- await element.updateComplete;
- await element.renderDiffTableTask?.flush();
- assert.isTrue(stub.called);
- stub.reset();
-
- const newPrefs2 = {...newPrefs1};
- delete newPrefs2.line_wrapping;
- element.prefs = newPrefs2;
- await element.updateComplete;
- await element.renderDiffTableTask?.flush();
- assert.isTrue(stub.called);
- });
-
- test(
- 'change in preferences does not re-renders diff with ' +
- 'noRenderOnPrefsChange',
- async () => {
- const stub = sinon.stub(element, 'renderDiffTable');
- element.noRenderOnPrefsChange = true;
- element.prefs = {
- ...MINIMAL_PREFS,
- context: 12,
- };
- await element.updateComplete;
- await element.renderDiffTableTask?.flush();
- assert.isFalse(stub.called);
- }
- );
- });
});
suite('diff header', () => {
@@ -3801,89 +3751,6 @@
});
});
- suite('safety and bypass', () => {
- let renderStub: sinon.SinonStub;
-
- setup(async () => {
- renderStub = sinon.stub(element, 'legacyRender').callsFake(() => {
- assertIsDefined(element.diffTable);
- element.diffTable.dispatchEvent(
- new CustomEvent('render', {bubbles: true, composed: true})
- );
- return Promise.resolve();
- });
- sinon.stub(element, 'getDiffLength').returns(10000);
- element.diff = createDiff();
- element.noRenderOnPrefsChange = true;
- await element.updateComplete;
- });
-
- test('large render w/ context = 10', async () => {
- element.prefs = {...MINIMAL_PREFS, context: 10};
- element.renderDiffTable();
- await waitForEventOnce(element, 'render');
-
- assert.isTrue(renderStub.called);
- assert.isFalse(element.showWarning);
- });
-
- test('large render w/ whole file and bypass', async () => {
- element.prefs = {...MINIMAL_PREFS, context: -1};
- element.safetyBypass = 10;
- element.renderDiffTable();
- await waitForEventOnce(element, 'render');
-
- assert.isTrue(renderStub.called);
- assert.isFalse(element.showWarning);
- });
-
- test('large render w/ whole file and no bypass', async () => {
- element.prefs = {...MINIMAL_PREFS, context: -1};
- element.renderDiffTable();
- await waitForEventOnce(element, 'render');
-
- assert.isFalse(renderStub.called);
- assert.isTrue(element.showWarning);
- });
-
- test('toggles expand context using bypass', async () => {
- element.prefs = {...MINIMAL_PREFS, context: 3};
-
- element.toggleAllContext();
- element.renderDiffTable();
- await element.updateComplete;
-
- assert.equal(element.prefs.context, 3);
- assert.equal(element.safetyBypass, -1);
- assert.equal(element.getBypassPrefs().context, -1);
- });
-
- test('toggles collapse context from bypass', async () => {
- element.prefs = {...MINIMAL_PREFS, context: 3};
- element.safetyBypass = -1;
-
- element.toggleAllContext();
- element.renderDiffTable();
- await element.updateComplete;
-
- assert.equal(element.prefs.context, 3);
- assert.isNull(element.safetyBypass);
- assert.equal(element.getBypassPrefs().context, 3);
- });
-
- test('toggles collapse context from pref using default', async () => {
- element.prefs = {...MINIMAL_PREFS, context: -1};
-
- element.toggleAllContext();
- element.renderDiffTable();
- await element.updateComplete;
-
- assert.equal(element.prefs.context, -1);
- assert.equal(element.safetyBypass, 10);
- assert.equal(element.getBypassPrefs().context, 10);
- });
- });
-
suite('blame', () => {
test('unsetting', async () => {
element.blame = [];
@@ -3891,7 +3758,7 @@
element.classList.add('showBlame');
element.blame = null;
await element.updateComplete;
- assert.isTrue(setBlameSpy.calledWithExactly(null));
+ assert.isTrue(setBlameSpy.calledWithExactly([]));
assert.isFalse(element.classList.contains('showBlame'));
});
@@ -3999,36 +3866,10 @@
content,
binary,
};
+ await waitUntil(() => element.groups.length > 1);
await element.updateComplete;
- await element.renderDiffTableTask;
};
- test('clear diff table content as soon as diff changes', async () => {
- const content = [
- {
- a: ['all work and no play make andybons a dull boy'],
- },
- {
- b: ['Non eram nescius, Brute, cum, quae summis ingeniis '],
- },
- ];
- function diffTableHasContent() {
- assertIsDefined(element.diffTable);
- return element.diffTable.innerText.includes(content[0].a?.[0] ?? '');
- }
- await setupSampleDiff({content});
- await waitUntil(diffTableHasContent);
- element.diff = {...element.diff!};
- await element.updateComplete;
- // immediately cleaned up
- assertIsDefined(element.diffTable);
- assert.equal(element.diffTable.innerHTML, '');
- element.renderDiffTable();
- await element.updateComplete;
- // rendered again
- await waitUntil(diffTableHasContent);
- });
-
suite('selection test', () => {
test('user-select set correctly on side-by-side view', async () => {
const content = [
@@ -4044,8 +3885,9 @@
},
];
await setupSampleDiff({content});
- await waitEventLoop();
+ // We are selecting "Non eram nescius..." on the left side.
+ // The default is `selected-right`, so we will have to click.
const diffLine = queryAll<HTMLElement>(element, '.contentText')[2];
assert.equal(getComputedStyle(diffLine).userSelect, 'none');
mouseDown(diffLine);
@@ -4065,10 +3907,12 @@
],
},
];
- await setupSampleDiff({content});
element.viewMode = DiffViewMode.UNIFIED;
- await element.updateComplete;
- const diffLine = queryAll<HTMLElement>(element, '.contentText')[2];
+ await setupSampleDiff({content});
+
+ // We are selecting "all work and no play..." on the left side.
+ // The default is `selected-right`, so we will have to click.
+ const diffLine = queryAll<HTMLElement>(element, '.contentText')[0];
assert.equal(getComputedStyle(diffLine).userSelect, 'none');
mouseDown(diffLine);
assert.equal(getComputedStyle(diffLine).userSelect, 'text');
@@ -4140,16 +3984,6 @@
suite('former gr-diff-builder tests', () => {
let element: GrDiff;
- let builder: GrDiffBuilder;
- let diffTable: HTMLTableElement;
-
- const setBuilderPrefs = (prefs: Partial<DiffPreferencesInfo>) => {
- builder = new GrDiffBuilder(
- createEmptyDiff(),
- {...createDefaultDiffPrefs(), ...prefs},
- diffTable
- );
- };
const line = (text: string) => {
const line = new GrDiffLine(GrDiffLineType.BOTH);
@@ -4164,51 +3998,6 @@
stubRestApi('getLoggedIn').returns(Promise.resolve(false));
stubRestApi('getProjectConfig').returns(Promise.resolve(createConfig()));
stubBaseUrl('/r');
- setBuilderPrefs({});
- });
-
- [DiffViewMode.UNIFIED, DiffViewMode.SIDE_BY_SIDE].forEach(mode => {
- test(`line_length used for regular files under ${mode}`, () => {
- element.path = '/a.txt';
- element.viewMode = mode;
- element.diff = createEmptyDiff();
- element.prefs = {
- ...createDefaultDiffPrefs(),
- tab_size: 4,
- line_length: 50,
- };
- builder = element.getDiffBuilder();
- assert.equal(builder.prefs.line_length, 50);
- });
-
- test(`line_length ignored for commit msg under ${mode}`, () => {
- element.path = '/COMMIT_MSG';
- element.viewMode = mode;
- element.diff = createEmptyDiff();
- element.prefs = {
- ...createDefaultDiffPrefs(),
- tab_size: 4,
- line_length: 50,
- };
- builder = element.getDiffBuilder();
- assert.equal(builder.prefs.line_length, 72);
- });
- });
-
- test('_handlePreferenceError throws with invalid preference', () => {
- element.prefs = {...createDefaultDiffPrefs(), tab_size: 0};
- assert.throws(() => element.getDiffBuilder());
- });
-
- test('_handlePreferenceError triggers alert and javascript error', () => {
- const errorStub = sinon.stub();
- element.diffTable!.addEventListener('show-alert', errorStub);
- assert.throws(() => element.handlePreferenceError('tab size'));
- assert.equal(
- errorStub.lastCall.args[0].detail.message,
- "The value of the 'tab size' user preference is invalid. " +
- 'Fix in diff preferences'
- );
});
suite('intraline differences', () => {
@@ -4227,7 +4016,7 @@
<div>Lorem ipsum dolor sit amet, suspendisse inceptos vehicula</div>
`);
str = el.textContent ?? '';
- annotateElementSpy = sinon.spy(GrAnnotation, 'annotateElement');
+ annotateElementSpy = sinon.spy(GrAnnotationImpl, 'annotateElement');
layer = element.createIntralineLayer();
});
@@ -4339,7 +4128,7 @@
const str0 = slice(str, 0, 6);
const str1 = slice(str, 6);
- const numHighlightedChars = GrAnnotation.getStringLength(str1);
+ const numHighlightedChars = getStringLength(str1);
layer.annotate(el, lineNumberEl, l, Side.LEFT);
@@ -4359,14 +4148,17 @@
const lineNumberEl = document.createElement('td');
setup(() => {
- element.showTabs = true;
+ element.prefs = {...DEFAULT_PREFS, show_tabs: true};
layer = element.createTabIndicatorLayer();
});
test('does nothing with empty line', () => {
const l = line('');
const el = document.createElement('div');
- const annotateElementStub = sinon.stub(GrAnnotation, 'annotateElement');
+ const annotateElementStub = sinon.stub(
+ GrAnnotationImpl,
+ 'annotateElement'
+ );
layer.annotate(el, lineNumberEl, l, Side.LEFT);
@@ -4378,7 +4170,10 @@
const l = line(str);
const el = document.createElement('div');
el.textContent = str;
- const annotateElementStub = sinon.stub(GrAnnotation, 'annotateElement');
+ const annotateElementStub = sinon.stub(
+ GrAnnotationImpl,
+ 'annotateElement'
+ );
layer.annotate(el, lineNumberEl, l, Side.LEFT);
@@ -4390,7 +4185,10 @@
const l = line(str);
const el = document.createElement('div');
el.textContent = str;
- const annotateElementStub = sinon.stub(GrAnnotation, 'annotateElement');
+ const annotateElementStub = sinon.stub(
+ GrAnnotationImpl,
+ 'annotateElement'
+ );
layer.annotate(el, lineNumberEl, l, Side.LEFT);
@@ -4403,13 +4201,16 @@
});
test('does not annotate when disabled', () => {
- element.showTabs = false;
+ element.prefs = {...DEFAULT_PREFS, show_tabs: false};
const str = '\tlorem upsum';
const l = line(str);
const el = document.createElement('div');
el.textContent = str;
- const annotateElementStub = sinon.stub(GrAnnotation, 'annotateElement');
+ const annotateElementStub = sinon.stub(
+ GrAnnotationImpl,
+ 'annotateElement'
+ );
layer.annotate(el, lineNumberEl, l, Side.LEFT);
@@ -4421,7 +4222,10 @@
const l = line(str);
const el = document.createElement('div');
el.textContent = str;
- const annotateElementStub = sinon.stub(GrAnnotation, 'annotateElement');
+ const annotateElementStub = sinon.stub(
+ GrAnnotationImpl,
+ 'annotateElement'
+ );
layer.annotate(el, lineNumberEl, l, Side.LEFT);
@@ -4445,7 +4249,10 @@
const l = line(str);
const el = document.createElement('div');
el.textContent = str;
- const annotateElementStub = sinon.stub(GrAnnotation, 'annotateElement');
+ const annotateElementStub = sinon.stub(
+ GrAnnotationImpl,
+ 'annotateElement'
+ );
layer.annotate(el, lineNumberEl, l, Side.LEFT);
@@ -4458,51 +4265,25 @@
});
});
- suite('layers', () => {
- let initialLayersCount = 0;
- let withLayerCount = 0;
- setup(() => {
- const layers: DiffLayer[] = [];
- element.layers = layers;
- element.showTrailingWhitespace = true;
- element.setupAnnotationLayers();
- initialLayersCount = element.layersInternal.length;
- });
-
- test('no layers', () => {
- element.setupAnnotationLayers();
- assert.equal(element.layersInternal.length, initialLayersCount);
- });
-
- suite('with layers', () => {
- const layers: DiffLayer[] = [{annotate: () => {}}, {annotate: () => {}}];
- setup(() => {
- element.layers = layers;
- element.showTrailingWhitespace = true;
- element.setupAnnotationLayers();
- withLayerCount = element.layersInternal.length;
- });
- test('with layers', () => {
- element.setupAnnotationLayers();
- assert.equal(element.layersInternal.length, withLayerCount);
- assert.equal(initialLayersCount + layers.length, withLayerCount);
- });
- });
- });
-
suite('trailing whitespace', () => {
let layer: DiffLayer;
const lineNumberEl = document.createElement('td');
setup(() => {
- element.showTrailingWhitespace = true;
+ element.prefs = {
+ ...createDefaultDiffPrefs(),
+ show_whitespace_errors: true,
+ };
layer = element.createTrailingWhitespaceLayer();
});
test('does nothing with empty line', () => {
const l = line('');
const el = document.createElement('div');
- const annotateElementStub = sinon.stub(GrAnnotation, 'annotateElement');
+ const annotateElementStub = sinon.stub(
+ GrAnnotationImpl,
+ 'annotateElement'
+ );
layer.annotate(el, lineNumberEl, l, Side.LEFT);
assert.isFalse(annotateElementStub.called);
});
@@ -4512,7 +4293,10 @@
const l = line(str);
const el = document.createElement('div');
el.textContent = str;
- const annotateElementStub = sinon.stub(GrAnnotation, 'annotateElement');
+ const annotateElementStub = sinon.stub(
+ GrAnnotationImpl,
+ 'annotateElement'
+ );
layer.annotate(el, lineNumberEl, l, Side.LEFT);
assert.isFalse(annotateElementStub.called);
});
@@ -4522,7 +4306,10 @@
const l = line(str);
const el = document.createElement('div');
el.textContent = str;
- const annotateElementStub = sinon.stub(GrAnnotation, 'annotateElement');
+ const annotateElementStub = sinon.stub(
+ GrAnnotationImpl,
+ 'annotateElement'
+ );
layer.annotate(el, lineNumberEl, l, Side.LEFT);
assert.isTrue(annotateElementStub.called);
assert.equal(annotateElementStub.lastCall.args[1], 11);
@@ -4534,7 +4321,10 @@
const l = line(str);
const el = document.createElement('div');
el.textContent = str;
- const annotateElementStub = sinon.stub(GrAnnotation, 'annotateElement');
+ const annotateElementStub = sinon.stub(
+ GrAnnotationImpl,
+ 'annotateElement'
+ );
layer.annotate(el, lineNumberEl, l, Side.LEFT);
assert.isTrue(annotateElementStub.called);
assert.equal(annotateElementStub.lastCall.args[1], 11);
@@ -4546,7 +4336,10 @@
const l = line(str);
const el = document.createElement('div');
el.textContent = str;
- const annotateElementStub = sinon.stub(GrAnnotation, 'annotateElement');
+ const annotateElementStub = sinon.stub(
+ GrAnnotationImpl,
+ 'annotateElement'
+ );
layer.annotate(el, lineNumberEl, l, Side.LEFT);
assert.isTrue(annotateElementStub.called);
assert.equal(annotateElementStub.lastCall.args[1], 11);
@@ -4558,7 +4351,10 @@
const l = line(str);
const el = document.createElement('div');
el.textContent = str;
- const annotateElementStub = sinon.stub(GrAnnotation, 'annotateElement');
+ const annotateElementStub = sinon.stub(
+ GrAnnotationImpl,
+ 'annotateElement'
+ );
layer.annotate(el, lineNumberEl, l, Side.LEFT);
assert.isTrue(annotateElementStub.called);
assert.equal(annotateElementStub.lastCall.args[1], 1);
@@ -4566,12 +4362,18 @@
});
test('does not annotate when disabled', () => {
- element.showTrailingWhitespace = false;
+ element.prefs = {
+ ...createDefaultDiffPrefs(),
+ show_whitespace_errors: false,
+ };
const str = 'lorem upsum\t \t ';
const l = line(str);
const el = document.createElement('div');
el.textContent = str;
- const annotateElementStub = sinon.stub(GrAnnotation, 'annotateElement');
+ const annotateElementStub = sinon.stub(
+ GrAnnotationImpl,
+ 'annotateElement'
+ );
layer.annotate(el, lineNumberEl, l, Side.LEFT);
assert.isFalse(annotateElementStub.called);
});
@@ -4603,29 +4405,47 @@
test('text', async () => {
element.diff = {...createEmptyDiff(), content};
- await waitForEventOnce(element.diffTable!, 'render-content');
- assert.equal(querySelectorAll(element.diffTable!, 'tbody')?.length, 4);
+ await waitUntil(() => element.groups.length > 2);
+ await element.updateComplete;
+ const bodies = [...(querySelectorAll(element.diffTable!, 'tbody') ?? [])];
+ assert.equal(bodies.length, 4);
+ assert.isTrue(bodies[0].innerHTML.includes('LOST'));
+ assert.isTrue(bodies[1].innerHTML.includes('FILE'));
+ assert.isTrue(bodies[2].innerHTML.includes('andybons a dull boy'));
+ assert.isTrue(bodies[3].innerHTML.includes('Non eram nescius'));
});
test('image', async () => {
element.diff = {...createEmptyDiff(), content, binary: true};
element.isImageDiff = true;
- await waitForEventOnce(element.diffTable!, 'render-content');
- assert.equal(querySelectorAll(element.diffTable!, 'tbody')?.length, 4);
+ await element.updateComplete;
+ const body = queryAndAssert(element, 'tbody.image-diff');
+ assert.lightDom.equal(
+ body,
+ /* HTML */ `
+ <label class="gr-diff">
+ <span class="gr-diff label"> No image </span>
+ </label>
+ <label class="gr-diff">
+ <span class="gr-diff label"> No image </span>
+ </label>
+ `
+ );
});
test('binary', async () => {
element.diff = {...createEmptyDiff(), content, binary: true};
- await waitForEventOnce(element.diffTable!, 'render-content');
- assert.equal(querySelectorAll(element.diffTable!, 'tbody')?.length, 3);
+ await element.updateComplete;
+ const body = queryAndAssert(element, 'tbody.binary-diff');
+ assert.lightDom.equal(
+ body,
+ /* HTML */ '<span>Difference in binary files</span>'
+ );
});
});
suite('context hiding and expanding', () => {
- let dispatchStub: sinon.SinonStub;
-
setup(async () => {
- dispatchStub = sinon.stub(element.diffTable!, 'dispatchEvent');
element.diff = {
...createEmptyDiff(),
content: [
@@ -4635,15 +4455,12 @@
],
};
element.viewMode = DiffViewMode.SIDE_BY_SIDE;
-
element.prefs = {
...DEFAULT_PREFS,
context: 1,
};
+ await waitUntil(() => element.groups.length > 2);
await element.updateComplete;
- element.legacyRender();
- // Make sure all listeners are installed.
- await element.untilGroupsRendered();
});
test('hides lines behind two context controls', () => {
@@ -4700,7 +4517,6 @@
});
test('unhideLine shows the line with context', async () => {
- dispatchStub.reset();
element.unhideLine(4, Side.LEFT);
await waitUntil(() => {
@@ -4725,10 +4541,6 @@
assert.include(diffRows[8].textContent, 'before');
assert.include(diffRows[8].textContent, 'after');
assert.include(diffRows[9].textContent, 'unchanged 11');
-
- await element.untilGroupsRendered();
- const firedEventTypes = dispatchStub.getCalls().map(c => c.args[0].type);
- assert.include(firedEventTypes, 'render-content');
});
});
});
diff --git a/polygerrit-ui/app/embed/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.ts b/polygerrit-ui/app/embed/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.ts
index e2837ab..24729ff 100644
--- a/polygerrit-ui/app/embed/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.ts
+++ b/polygerrit-ui/app/embed/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.ts
@@ -3,7 +3,7 @@
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
-import {GrAnnotation} from '../gr-diff-highlight/gr-annotation';
+import {GrAnnotationImpl} from '../gr-diff-highlight/gr-annotation';
import {GrDiffLine} from '../gr-diff/gr-diff-line';
import {strToClassName} from '../../../utils/dom-util';
import {Side} from '../../../constants/constants';
@@ -94,7 +94,7 @@
}
for (const range of ranges) {
- GrAnnotation.annotateElement(
+ GrAnnotationImpl.annotateElement(
el,
range.start,
range.end - range.start,
diff --git a/polygerrit-ui/app/embed/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.ts b/polygerrit-ui/app/embed/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.ts
index b90d6f7..5bfd94d 100644
--- a/polygerrit-ui/app/embed/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.ts
@@ -10,11 +10,11 @@
CommentRangeLayer,
GrRangedCommentLayer,
} from './gr-ranged-comment-layer';
-import {GrAnnotation} from '../gr-diff-highlight/gr-annotation';
import {GrDiffLine} from '../gr-diff/gr-diff-line';
import {GrDiffLineType, Side} from '../../../api/diff';
import {SinonStub} from 'sinon';
import {assert} from '@open-wc/testing';
+import {GrAnnotationImpl} from '../gr-diff-highlight/gr-annotation';
const rangeA: CommentRangeLayer = {
side: Side.LEFT,
@@ -130,7 +130,7 @@
}
setup(() => {
- annotateElementStub = sinon.stub(GrAnnotation, 'annotateElement');
+ annotateElementStub = sinon.stub(GrAnnotationImpl, 'annotateElement');
el = document.createElement('div');
el.setAttribute('data-side', Side.LEFT);
line = new GrDiffLine(GrDiffLineType.BOTH);
diff --git a/polygerrit-ui/app/embed/diff/gr-selection-action-box/gr-selection-action-box.ts b/polygerrit-ui/app/embed/diff/gr-selection-action-box/gr-selection-action-box.ts
index 68aa3b4..756f6fa 100644
--- a/polygerrit-ui/app/embed/diff/gr-selection-action-box/gr-selection-action-box.ts
+++ b/polygerrit-ui/app/embed/diff/gr-selection-action-box/gr-selection-action-box.ts
@@ -40,20 +40,22 @@
this.addEventListener('mousedown', e => this.handleMouseDown(e));
}
- static override styles = [
- sharedStyles,
- css`
- :host {
- cursor: pointer;
- font-family: var(--font-family);
- position: absolute;
- white-space: nowrap;
- }
- gr-tooltip[invisible] {
- visibility: hidden;
- }
- `,
- ];
+ static override get styles() {
+ return [
+ sharedStyles,
+ css`
+ :host {
+ cursor: pointer;
+ font-family: var(--font-family);
+ position: absolute;
+ white-space: nowrap;
+ }
+ gr-tooltip[invisible] {
+ visibility: hidden;
+ }
+ `,
+ ];
+ }
override render() {
return html`
diff --git a/polygerrit-ui/app/embed/diff/gr-syntax-layer/gr-syntax-layer-worker.ts b/polygerrit-ui/app/embed/diff/gr-syntax-layer/gr-syntax-layer-worker.ts
index baa2ab4..4e166ba 100644
--- a/polygerrit-ui/app/embed/diff/gr-syntax-layer/gr-syntax-layer-worker.ts
+++ b/polygerrit-ui/app/embed/diff/gr-syntax-layer/gr-syntax-layer-worker.ts
@@ -3,7 +3,7 @@
* Copyright 2022 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
-import {GrAnnotation} from '../gr-diff-highlight/gr-annotation';
+import {annotateElement} from '../gr-diff-highlight/gr-annotation';
import {GrDiffLine} from '../gr-diff/gr-diff-line';
import {DiffFileMetaInfo, DiffInfo} from '../../../types/diff';
import {DiffLayer, DiffLayerListener} from '../../../types/types';
@@ -212,7 +212,7 @@
for (const range of ranges) {
if (!CLASS_SAFELIST.has(range.className)) continue;
if (range.length === 0) continue;
- GrAnnotation.annotateElement(
+ annotateElement(
el,
range.start,
range.length,
diff --git a/polygerrit-ui/app/embed/gr-diff.ts b/polygerrit-ui/app/embed/gr-diff.ts
index 977f8a9..1524a78 100644
--- a/polygerrit-ui/app/embed/gr-diff.ts
+++ b/polygerrit-ui/app/embed/gr-diff.ts
@@ -12,11 +12,11 @@
// exposed by shared gr-diff component.
import '../api/embed';
import '../scripts/bundled-polymer';
-import './diff/gr-diff/gr-diff';
-import './diff/gr-diff-cursor/gr-diff-cursor';
+import './diff-old/gr-diff/gr-diff';
+import './diff-old/gr-diff-cursor/gr-diff-cursor';
import {TokenHighlightLayer} from './diff/gr-diff-builder/token-highlight-layer';
-import {GrDiffCursor} from './diff/gr-diff-cursor/gr-diff-cursor';
-import {GrAnnotation} from './diff/gr-diff-highlight/gr-annotation';
+import {GrDiffCursor} from './diff-old/gr-diff-cursor/gr-diff-cursor';
+import {GrAnnotation} from './diff-old/gr-diff-highlight/gr-annotation';
import {createDiffAppContext} from './gr-diff-app-context-init';
import {injectAppContext} from '../services/app-context';
@@ -29,6 +29,3 @@
GrDiffCursor,
TokenHighlightLayer,
};
-
-// TODO(oler): Remove when clients have adjusted to namespaced globals above
-window.GrAnnotation = GrAnnotation;
diff --git a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl.ts b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl.ts
index 68ec76a..ba6e384 100644
--- a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl.ts
+++ b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl.ts
@@ -3084,37 +3084,48 @@
});
}
- async setInProjectLookup(changeNum: NumericChangeId, project: RepoName) {
- const lookupProject = await this._projectLookup[changeNum];
- if (lookupProject && lookupProject !== project) {
- console.warn(
- 'Change set with multiple project nums.' +
- 'One of them must be invalid.'
- );
- }
+ /**
+ * This can be called by the router, if the project can be determined from
+ * the URL. Or when handling a dashabord or a search response.
+ *
+ * Then we don't need to make a dedicated REST API call or have a fallback,
+ * if that fails.
+ */
+ setInProjectLookup(changeNum: NumericChangeId, project: RepoName) {
this._projectLookup[changeNum] = Promise.resolve(project);
}
getFromProjectLookup(
changeNum: NumericChangeId
): Promise<RepoName | undefined> {
- const project = this._projectLookup[`${changeNum}`];
- if (project) {
- return project;
- }
+ // Hopefully setInProjectLookup() has already been called. Then we don't
+ // have to make a dedicated REST API call to look up the project.
+ let projectPromise = this._projectLookup[changeNum];
+ if (projectPromise) return projectPromise;
- const onError = (response?: Response | null) => firePageError(response);
+ // Ignore errors, because we have some dedicated fallback logic, see below.
+ const onError = () => {};
+ projectPromise = this.getChange(changeNum, onError).then(change => {
+ if (change?.project) return change.project;
- const projectPromise = this.getChange(changeNum, onError).then(change => {
- if (!change || !change.project) {
- return;
+ // In the very rare case that the change index cannot provide an answer
+ // (e.g. stale index) we should check, if the router has called
+ // setInProjectLookup() in the meantime. Then we can fall back to that.
+ const currentProjectPromise = this._projectLookup[changeNum];
+ if (currentProjectPromise !== projectPromise) {
+ return currentProjectPromise;
}
- this.setInProjectLookup(changeNum, change.project);
- return change.project;
+
+ // No luck. Without knowing the project we cannot proceed at all.
+ firePageError(
+ new Response(
+ `Failed to lookup the repo for change number ${changeNum}`,
+ {status: 404}
+ )
+ );
+ return undefined;
});
-
this._projectLookup[changeNum] = projectPromise;
-
return projectPromise;
}
@@ -3304,10 +3315,6 @@
return this.getDocsBaseUrlCachedPromise;
}
- testOnly_clearDocsBaseUrlCache() {
- this.getDocsBaseUrlCachedPromise = undefined;
- }
-
getDocumentationSearches(filter: string): Promise<DocResult[] | undefined> {
filter = filter.trim();
const encodedFilter = encodeURIComponent(filter);
diff --git a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl_test.ts b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl_test.ts
index d570163..764aa98 100644
--- a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl_test.ts
+++ b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl_test.ts
@@ -38,6 +38,7 @@
} from '../../constants/constants';
import {
BasePatchSetNum,
+ ChangeInfo,
ChangeMessageId,
CommentInfo,
DashboardId,
@@ -62,6 +63,7 @@
import {assert} from '@open-wc/testing';
import {AuthService} from '../gr-auth/gr-auth';
import {GrAuthMock} from '../gr-auth/gr-auth_mock';
+import {getBaseUrl} from '../../utils/url-util';
const EXPECTED_QUERY_OPTIONS = listChangesOptionsToHex(
ListChangesOption.CHANGE_ACTIONS,
@@ -356,7 +358,7 @@
assert.isTrue(fetchStub.calledOnce);
assert.equal(
fetchStub.firstCall.args[0].url,
- 'test52/accounts/?o=DETAILS&q=%22bro%22'
+ `${getBaseUrl()}/accounts/?o=DETAILS&q=%22bro%22`
);
});
@@ -365,7 +367,7 @@
assert.isTrue(fetchStub.calledOnce);
assert.equal(
fetchStub.firstCall.args[0].url,
- 'test53/accounts/?o=DETAILS&q=%22bro%22%20and%20cansee%3A341682'
+ `${getBaseUrl()}/accounts/?o=DETAILS&q=%22bro%22%20and%20cansee%3A341682`
);
});
@@ -379,8 +381,7 @@
assert.isTrue(fetchStub.calledOnce);
assert.equal(
fetchStub.firstCall.args[0].url,
- 'test54/accounts/?o=DETAILS&q=%22bro%22%20and%20' +
- 'cansee%3A341682%20and%20is%3Aactive'
+ `${getBaseUrl()}/accounts/?o=DETAILS&q=%22bro%22%20and%20cansee%3A341682%20and%20is%3Aactive`
);
});
});
@@ -1154,32 +1155,46 @@
});
test('setInProjectLookup', async () => {
- await element.setInProjectLookup(
- 555 as NumericChangeId,
- 'project' as RepoName
- );
+ element.setInProjectLookup(555 as NumericChangeId, 'project' as RepoName);
const project = await element.getFromProjectLookup(555 as NumericChangeId);
assert.deepEqual(project, 'project' as RepoName);
});
suite('getFromProjectLookup', () => {
- test('getChange succeeds, no project', async () => {
- sinon.stub(element, 'getChange').resolves(null);
- const val = await element.getFromProjectLookup(555 as NumericChangeId);
- assert.strictEqual(val, undefined);
+ const changeNum = 555 as NumericChangeId;
+ const repo = 'test-repo' as RepoName;
+
+ test('getChange fails to yield a project', async () => {
+ const promise = mockPromise<null>();
+ sinon.stub(element, 'getChange').returns(promise);
+
+ const projectLookup = element.getFromProjectLookup(changeNum);
+ promise.resolve(null);
+
+ assert.isUndefined(await projectLookup);
});
test('getChange succeeds with project', async () => {
- sinon
- .stub(element, 'getChange')
- .resolves({...createChange(), project: 'project' as RepoName});
- const projectLookup = element.getFromProjectLookup(
- 555 as NumericChangeId
- );
- const val = await projectLookup;
- assert.equal(val, 'project' as RepoName);
+ const promise = mockPromise<null | ChangeInfo>();
+ sinon.stub(element, 'getChange').returns(promise);
+
+ const projectLookup = element.getFromProjectLookup(changeNum);
+ promise.resolve({...createChange(), project: repo});
+
+ assert.equal(await projectLookup, repo);
assert.deepEqual(element._projectLookup, {'555': projectLookup});
});
+
+ test('getChange fails, but a setInProjectLookup() call is used as fallback', async () => {
+ const promise = mockPromise<null>();
+ sinon.stub(element, 'getChange').returns(promise);
+
+ const projectLookup = element.getFromProjectLookup(changeNum);
+ element.setInProjectLookup(changeNum, repo);
+ promise.resolve(null);
+
+ assert.equal(await projectLookup, repo);
+ });
});
suite('getChanges populates _projectLookup', () => {
@@ -1592,10 +1607,11 @@
test('null config', async () => {
const probePathMock = sinon.stub(element, 'probePath').resolves(true);
const docsBaseUrl = await element.getDocsBaseUrl(undefined);
- assert.isTrue(
- probePathMock.calledWith('test91/Documentation/index.html')
+ assert.equal(
+ probePathMock.lastCall.args[0],
+ `${getBaseUrl()}/Documentation/index.html`
);
- assert.equal(docsBaseUrl, 'test91/Documentation');
+ assert.equal(docsBaseUrl, `${getBaseUrl()}/Documentation`);
});
test('no doc config', async () => {
@@ -1605,10 +1621,11 @@
gerrit: createGerritInfo(),
};
const docsBaseUrl = await element.getDocsBaseUrl(config);
- assert.isTrue(
- probePathMock.calledWith('test92/Documentation/index.html')
+ assert.equal(
+ probePathMock.lastCall.args[0],
+ `${getBaseUrl()}/Documentation/index.html`
);
- assert.equal(docsBaseUrl, 'test92/Documentation');
+ assert.equal(docsBaseUrl, `${getBaseUrl()}/Documentation`);
});
test('has doc config', async () => {
@@ -1625,8 +1642,9 @@
test('no probe', async () => {
const probePathMock = sinon.stub(element, 'probePath').resolves(false);
const docsBaseUrl = await element.getDocsBaseUrl(undefined);
- assert.isTrue(
- probePathMock.calledWith('test94/Documentation/index.html')
+ assert.equal(
+ probePathMock.lastCall.args[0],
+ `${getBaseUrl()}/Documentation/index.html`
);
assert.isNotOk(docsBaseUrl);
});
diff --git a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api.ts b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api.ts
index 24ba0e5..f1fae3b 100644
--- a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api.ts
+++ b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api.ts
@@ -375,7 +375,6 @@
): Promise<string>;
getDocsBaseUrl(config?: ServerInfo): Promise<string | null>;
- testOnly_clearDocsBaseUrlCache(): void;
createChange(
repo: RepoName,
diff --git a/polygerrit-ui/app/services/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider.ts b/polygerrit-ui/app/services/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider.ts
index 842dace..34187fe 100644
--- a/polygerrit-ui/app/services/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider.ts
+++ b/polygerrit-ui/app/services/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider.ts
@@ -3,25 +3,24 @@
* Copyright 2019 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
-import {
- getAccountDisplayName,
- getGroupDisplayName,
-} from '../../utils/display-name-util';
import {RestApiService} from '../gr-rest-api/gr-rest-api';
import {
- AccountInfo,
isReviewerAccountSuggestion,
isReviewerGroupSuggestion,
NumericChangeId,
ServerInfo,
+ SuggestedReviewerAccountInfo,
SuggestedReviewerInfo,
Suggestion,
} from '../../types/common';
-import {assertNever} from '../../utils/common-util';
import {AutocompleteSuggestion} from '../../elements/shared/gr-autocomplete/gr-autocomplete';
import {allSettled, isFulfilled} from '../../utils/async-util';
import {isDefined, ParsedChangeInfo} from '../../types/types';
-import {accountKey} from '../../utils/account-util';
+import {
+ accountKey,
+ getSuggestedReviewerName,
+ isAccountSuggestion,
+} from '../../utils/account-util';
import {
AccountId,
ChangeInfo,
@@ -96,30 +95,11 @@
makeSuggestionItem(
suggestion: Suggestion
): AutocompleteSuggestion<SuggestedReviewerInfo> {
- if (isReviewerAccountSuggestion(suggestion)) {
- // Reviewer is an account suggestion from getChangeSuggestedReviewers.
- return {
- name: getAccountDisplayName(this.config, suggestion.account),
- value: suggestion,
- };
- }
-
- if (isReviewerGroupSuggestion(suggestion)) {
- // Reviewer is a group suggestion from getChangeSuggestedReviewers.
- return {
- name: getGroupDisplayName(suggestion.group),
- value: suggestion,
- };
- }
-
- if (isAccountSuggestion(suggestion)) {
- // Reviewer is an account suggestion from getSuggestedAccounts.
- return {
- name: getAccountDisplayName(this.config, suggestion),
- value: {account: suggestion, count: 1},
- };
- }
- assertNever(suggestion, 'Received an incorrect suggestion');
+ const name = getSuggestedReviewerName(suggestion, this.config);
+ const value = isAccountSuggestion(suggestion)
+ ? ({account: suggestion, count: 1} as SuggestedReviewerAccountInfo)
+ : suggestion;
+ return {name, value};
}
private getSuggestionsForChange(
@@ -160,7 +140,3 @@
}
return undefined;
}
-
-function isAccountSuggestion(s: Suggestion): s is AccountInfo {
- return (s as AccountInfo)._account_id !== undefined;
-}
diff --git a/polygerrit-ui/app/test/mocks/gr-rest-api_mock.ts b/polygerrit-ui/app/test/mocks/gr-rest-api_mock.ts
index f8e4160..bfa881e 100644
--- a/polygerrit-ui/app/test/mocks/gr-rest-api_mock.ts
+++ b/polygerrit-ui/app/test/mocks/gr-rest-api_mock.ts
@@ -321,9 +321,6 @@
}
return Promise.resolve('');
},
- testOnly_clearDocsBaseUrlCache() {
- return;
- },
getDocumentationSearches(): Promise<DocResult[] | undefined> {
return Promise.resolve([]);
},
diff --git a/polygerrit-ui/app/test/test-app-context-init.ts b/polygerrit-ui/app/test/test-app-context-init.ts
index 026e3b5..73ee6aa 100644
--- a/polygerrit-ui/app/test/test-app-context-init.ts
+++ b/polygerrit-ui/app/test/test-app-context-init.ts
@@ -18,6 +18,10 @@
import {DependencyToken} from '../models/dependency';
import {storageServiceToken} from '../services/storage/gr-storage_impl';
import {highlightServiceToken} from '../services/highlight/highlight-service';
+import {
+ diffModelToken,
+ DiffModel,
+} from '../embed/diff/gr-diff-model/gr-diff-model';
export function createTestAppContext(): AppContext & Finalizable {
const appRegistry: Registry<AppContext> = {
@@ -49,5 +53,6 @@
highlightServiceToken,
() => new MockHighlightService(appContext.reportingService)
);
+ dependencies.set(diffModelToken, () => new DiffModel());
return dependencies;
}
diff --git a/polygerrit-ui/app/types/globals.ts b/polygerrit-ui/app/types/globals.ts
index 554fa23..4a709b0 100644
--- a/polygerrit-ui/app/types/globals.ts
+++ b/polygerrit-ui/app/types/globals.ts
@@ -36,10 +36,6 @@
};
/** Enhancements on Gr elements or utils */
- // TODO(TS): should clean up those and removing them may break certain plugin behaviors
- // TODO(TS): as @brohlfs suggested, to avoid importing anything from elements/ to types/
- // use any for them for now
- GrAnnotation: unknown;
// Heads up! There is a known plugin dependency on GrPluginActionContext.
GrPluginActionContext: unknown;
}
diff --git a/polygerrit-ui/app/utils/account-util.ts b/polygerrit-ui/app/utils/account-util.ts
index 7dc9a62..68f6b42 100644
--- a/polygerrit-ui/app/utils/account-util.ts
+++ b/polygerrit-ui/app/utils/account-util.ts
@@ -16,10 +16,17 @@
ReviewerInput,
ServerInfo,
UserId,
+ Suggestion,
+ isReviewerAccountSuggestion,
+ isReviewerGroupSuggestion,
} from '../types/common';
import {AccountTag, ReviewerState} from '../constants/constants';
import {assertNever, hasOwnProperty} from './common-util';
-import {getDisplayName} from './display-name-util';
+import {
+ getAccountDisplayName,
+ getDisplayName,
+ getGroupDisplayName,
+} from './display-name-util';
import {getApprovalInfo} from './label-util';
import {ParsedChangeInfo} from '../types/types';
@@ -220,3 +227,29 @@
}
throw new Error('Must be either an account or a group.');
}
+
+export function isAccountSuggestion(s: Suggestion): s is AccountInfo {
+ return (s as AccountInfo)._account_id !== undefined;
+}
+
+export function getSuggestedReviewerName(
+ suggestion: Suggestion,
+ config?: ServerInfo
+) {
+ if (isAccountSuggestion(suggestion)) {
+ // Reviewer is an account suggestion from getSuggestedAccounts.
+ return getAccountDisplayName(config, suggestion);
+ }
+
+ if (isReviewerAccountSuggestion(suggestion)) {
+ // Reviewer is an account suggestion from getChangeSuggestedReviewers.
+ return getAccountDisplayName(config, suggestion.account);
+ }
+
+ if (isReviewerGroupSuggestion(suggestion)) {
+ // Reviewer is a group suggestion from getChangeSuggestedReviewers.
+ return getGroupDisplayName(suggestion.group);
+ }
+
+ assertNever(suggestion, 'Received an incorrect suggestion');
+}
diff --git a/polygerrit-ui/package.json b/polygerrit-ui/package.json
index e8bd3d8..f3bea34 100644
--- a/polygerrit-ui/package.json
+++ b/polygerrit-ui/package.json
@@ -8,7 +8,7 @@
"devDependencies": {
"@open-wc/karma-esm": "^3.0.9",
"@open-wc/semantic-dom-diff": "^0.19.5",
- "@open-wc/testing": "^3.1.6",
+ "@open-wc/testing": "^3.1.8",
"@web/dev-server-esbuild": "^0.3.2",
"@web/test-runner": "^0.14.0",
"@web/test-runner-playwright": "^0.9.0",
diff --git a/polygerrit-ui/yarn.lock b/polygerrit-ui/yarn.lock
index 35409a8..c1a2bfb 100644
--- a/polygerrit-ui/yarn.lock
+++ b/polygerrit-ui/yarn.lock
@@ -1124,26 +1124,26 @@
"@types/chai" "^4.3.1"
"@web/test-runner-commands" "^0.6.1"
-"@open-wc/testing-helpers@^2.1.2":
- version "2.1.3"
- resolved "https://registry.yarnpkg.com/@open-wc/testing-helpers/-/testing-helpers-2.1.3.tgz#85a133ac8637ed1d880d523b07650788eab4a128"
- integrity sha512-hQujGaWncmWLx/974jq5yf2jydBNNTwnkISw2wLGiYgX34+3R6/ns301Oi9S3Il96Kzd8B7avdExp/gDgqcF5w==
+"@open-wc/testing-helpers@^2.2.1":
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/@open-wc/testing-helpers/-/testing-helpers-2.2.1.tgz#10ed75c33faec0ed68c76e027ebe8be262a36921"
+ integrity sha512-8zuJK7tUQYuXRIC/cVcPbAPOhtBJCe3Jfpk7im7WK0DIAXH9Q/ycB+yu3R8g4BQ31f/FdLjIFRbPZzIU75kkRg==
dependencies:
"@open-wc/scoped-elements" "^2.1.3"
lit "^2.0.0"
lit-html "^2.0.0"
-"@open-wc/testing@^3.1.6":
- version "3.1.6"
- resolved "https://registry.yarnpkg.com/@open-wc/testing/-/testing-3.1.6.tgz#89f71710e5530d74f0c478b0a9239d68dcdb9f5e"
- integrity sha512-MIf9cBtac4/UBE5a+R5cXiRhOGfzetsV+ZPFc188AfkPDPbmffHqjrRoCyk4B/qS6fLEulSBMLSaQ+6ze971gQ==
+"@open-wc/testing@^3.1.8":
+ version "3.1.8"
+ resolved "https://registry.yarnpkg.com/@open-wc/testing/-/testing-3.1.8.tgz#3760a354e421a38bf432010a067ff3d3fdb60b1e"
+ integrity sha512-SpKhlSwCqUkVOOmdb9RanOQgqv4T32wzExkvuaVcUFcBeUdpwQsg1+WYpdv31Z4cRCkAhQ4A8OIpGphzqF8T7w==
dependencies:
"@esm-bundle/chai" "^4.3.4-fix.0"
"@open-wc/chai-dom-equals" "^0.12.36"
"@open-wc/semantic-dom-diff" "^0.19.7"
- "@open-wc/testing-helpers" "^2.1.2"
+ "@open-wc/testing-helpers" "^2.2.1"
"@types/chai" "^4.2.11"
- "@types/chai-dom" "^0.0.12"
+ "@types/chai-dom" "^1.11.0"
"@types/sinon-chai" "^3.2.3"
chai-a11y-axe "^1.3.2"
@@ -1289,10 +1289,10 @@
resolved "https://registry.yarnpkg.com/@types/caniuse-api/-/caniuse-api-3.0.2.tgz#684ba0c284b2a58346abf0000bd0a735ad072d75"
integrity sha512-YfCDMn7R59n7GFFfwjPAM0zLJQy4UvveC32rOJBmTqJJY8uSRqM4Dc7IJj8V9unA48Qy4nj5Bj3jD6Q8VZ1Seg==
-"@types/chai-dom@^0.0.12":
- version "0.0.12"
- resolved "https://registry.yarnpkg.com/@types/chai-dom/-/chai-dom-0.0.12.tgz#fdd7a52bed4dd235ed1c94d3d2d31d4e7db1d03a"
- integrity sha512-4rE7sDw713cV61TYzQbMrPjC4DjNk3x4vk9nAVRNXcSD4p0/5lEEfm0OgoCz5eNuWUXNKA0YiKiH/JDTuKivkA==
+"@types/chai-dom@^1.11.0":
+ version "1.11.0"
+ resolved "https://registry.yarnpkg.com/@types/chai-dom/-/chai-dom-1.11.0.tgz#e9bd01f3408b2ffd27755fe4418ff92ffd8f4e66"
+ integrity sha512-Aja99Mmnny+Sz+T2hBK3oEsrcy18yabplT0pGX/QwIke9jMJHdvHlV2f4Tmq5SqxTMYwt1Zjbisv/4r83EUIHw==
dependencies:
"@types/chai" "*"
diff --git a/yarn.lock b/yarn.lock
index d66270b..df4e166 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -51,53 +51,52 @@
dependencies:
regenerator-runtime "^0.13.4"
-"@bazel/concatjs@^5.5.0":
- version "5.5.0"
- resolved "https://registry.yarnpkg.com/@bazel/concatjs/-/concatjs-5.5.0.tgz#e6104ed70595cae59463ae6b0b5389252566221e"
- integrity sha512-hwG+ahivR20Z3iTOlkUz3OdwnW/PUaZyyz8BIX+GNUTg6U3rPHK51CavUirMupOU/LRJ5GyCwBNAAtjCyquo2g==
+"@bazel/concatjs@^5.8.0":
+ version "5.8.1"
+ resolved "https://registry.yarnpkg.com/@bazel/concatjs/-/concatjs-5.8.1.tgz#dd20882429e382cae79c08cbd3238dfc680d2d67"
+ integrity sha512-TkARsNUxgi3bjFeGwIGlffmQglNhuR9qK9uE7uKhdBZvQE5caAWVCjYiMTzo3viKDhwKn5QNRcHY5huuJMVFfA==
dependencies:
protobufjs "6.8.8"
source-map-support "0.5.9"
tsutils "3.21.0"
-"@bazel/rollup@^5.5.0":
- version "5.5.0"
- resolved "https://registry.yarnpkg.com/@bazel/rollup/-/rollup-5.5.0.tgz#1e152d6147ef5583ec9fd872756c9d0635db73c7"
- integrity sha512-8SRbgVfaYdNb6PyIypj8jzzJHhlIRyMH3s5KpXODsjD+mXECH4jQxJ8VcRkt0f0exsgB12gK5dmoUK/F2PDKCw==
+"@bazel/rollup@^5.8.0":
+ version "5.8.1"
+ resolved "https://registry.yarnpkg.com/@bazel/rollup/-/rollup-5.8.1.tgz#ee876c35595b456f700d258385412e4f0dd57c15"
+ integrity sha512-Ys+UWbRp1TY2j+z15N+SZgID/nuqAtJTgJDsz0NZVjm8F8KzmgXxLDnBb/cUKFVk83pNOAi84G/bq1tINjMSNA==
dependencies:
- "@bazel/worker" "5.5.0"
+ "@bazel/worker" "5.8.1"
-"@bazel/terser@^5.5.0":
- version "5.5.0"
- resolved "https://registry.yarnpkg.com/@bazel/terser/-/terser-5.5.0.tgz#3b2b582a417d99d59ae99b50d74576ca0719c03a"
- integrity sha512-aBjNmJ7TbcD7cKAdFErYQYXn4OqTvrmqrtN6Z6Wnv82d+23kbEsF427ixgdCO3GTQJDw7+x7K9TP2CGogaGtcg==
+"@bazel/terser@^5.8.0":
+ version "5.8.1"
+ resolved "https://registry.yarnpkg.com/@bazel/terser/-/terser-5.8.1.tgz#729a0ec6dcc83e99c4f6d3f2bebb0ff254c10c48"
+ integrity sha512-TPjSDhw1pSZt9P2hd/22IJwl8KCZiJL+u2gB5mghBTCFDVdC5Dgsx135pFtvlqc6LjjOvd3s6dzcQr0YJo2HSg==
-"@bazel/typescript@^5.5.0":
- version "5.5.0"
- resolved "https://registry.yarnpkg.com/@bazel/typescript/-/typescript-5.5.0.tgz#053c255acb1b3cac23d24984cd8d5d5542fe1f7c"
- integrity sha512-Ord0+nCj+B1M4NDbe0uqZf2FyOCzaDAlc4DAsr5UKJrArCipIbMTEAxlsEk+WAYBNAFGO/FS9/zlDtLceqpHqw==
+"@bazel/typescript@^5.8.0":
+ version "5.8.1"
+ resolved "https://registry.yarnpkg.com/@bazel/typescript/-/typescript-5.8.1.tgz#74a76af434fad7930893cf3e98b4cc201e52dc65"
+ integrity sha512-NAJ8WQHZL1WE1YmRoCrq/1hhG15Mvy/viWh6TkvFnBeEhNUiQUsA5GYyhU1ztnBIYW03nATO3vwhAEfO7Q0U5g==
dependencies:
- "@bazel/worker" "5.5.0"
- protobufjs "6.8.8"
+ "@bazel/worker" "5.8.1"
semver "5.6.0"
source-map-support "0.5.9"
tsutils "3.21.0"
-"@bazel/worker@5.5.0":
- version "5.5.0"
- resolved "https://registry.yarnpkg.com/@bazel/worker/-/worker-5.5.0.tgz#d30b75e46f2052d33bcda251b328d36655a5636f"
- integrity sha512-pYfjJKg4D1CQ/AJ1UGC5ySyH09gDqNiBrQJ0uMYVewIBW24uOAkKsJfTE2y4ES0UL1Ik758WO0la0mJeFOKScg==
+"@bazel/worker@5.8.1":
+ version "5.8.1"
+ resolved "https://registry.yarnpkg.com/@bazel/worker/-/worker-5.8.1.tgz#65af7a70dd2f1aaedd6c19330abd9a198f96e7b2"
+ integrity sha512-GMyZSNW3F34f9GjbJqvs1aHyed5BNrNeiDzNJhC1fIizo/UeBM21oBBONIYLBDoBtq936U85VyPZ76JaP/83hw==
dependencies:
google-protobuf "^3.6.1"
-"@es-joy/jsdoccomment@~0.36.1":
- version "0.36.1"
- resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.36.1.tgz#c37db40da36e4b848da5fd427a74bae3b004a30f"
- integrity sha512-922xqFsTpHs6D0BUiG4toiyPOMc8/jafnWKxz1KWgS4XzKPy2qXf1Pe6UFuNSCQqt6tOuhAWXBNuuyUhJmw9Vg==
+"@es-joy/jsdoccomment@~0.39.3":
+ version "0.39.4"
+ resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.39.4.tgz#6b8a62e9b3077027837728818d3c4389a898b392"
+ integrity sha512-Jvw915fjqQct445+yron7Dufix9A+m9j1fCJYlCo1FWlRvTxa3pjJelxdSTdaLWcTwRU6vbL+NYjO4YuNIS5Qg==
dependencies:
comment-parser "1.3.1"
- esquery "^1.4.0"
- jsdoc-type-pratt-parser "~3.1.0"
+ esquery "^1.5.0"
+ jsdoc-type-pratt-parser "~4.0.0"
"@esbuild/linux-loong64@0.14.54":
version "0.14.54"
@@ -690,6 +689,11 @@
normalize-path "^3.0.0"
picomatch "^2.0.4"
+are-docs-informative@^0.0.2:
+ version "0.0.2"
+ resolved "https://registry.yarnpkg.com/are-docs-informative/-/are-docs-informative-0.0.2.tgz#387f0e93f5d45280373d387a59d34c96db321963"
+ integrity sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==
+
argparse@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
@@ -1704,17 +1708,18 @@
resolve "^1.22.0"
tsconfig-paths "^3.14.1"
-eslint-plugin-jsdoc@^39.6.4:
- version "39.6.4"
- resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-39.6.4.tgz#b940aebd3eea26884a0d341785d2dc3aba6a38a7"
- integrity sha512-fskvdLCfwmPjHb6e+xNGDtGgbF8X7cDwMtVLAP2WwSf9Htrx68OAx31BESBM1FAwsN2HTQyYQq7m4aW4Q4Nlag==
+eslint-plugin-jsdoc@^44.2.4:
+ version "44.2.4"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-44.2.4.tgz#0bdc163771504ec7330414eda6a7dbae67156ddb"
+ integrity sha512-/EMMxCyRh1SywhCb66gAqoGX4Yv6Xzc4bsSkF1AiY2o2+bQmGMQ05QZ5+JjHbdFTPDZY9pfn+DsSNP0a5yQpIg==
dependencies:
- "@es-joy/jsdoccomment" "~0.36.1"
+ "@es-joy/jsdoccomment" "~0.39.3"
+ are-docs-informative "^0.0.2"
comment-parser "1.3.1"
debug "^4.3.4"
escape-string-regexp "^4.0.0"
- esquery "^1.4.0"
- semver "^7.3.8"
+ esquery "^1.5.0"
+ semver "^7.5.1"
spdx-expression-parse "^3.0.1"
eslint-plugin-lit@^1.6.1:
@@ -1859,6 +1864,13 @@
dependencies:
estraverse "^5.1.0"
+esquery@^1.5.0:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b"
+ integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==
+ dependencies:
+ estraverse "^5.1.0"
+
esrecurse@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
@@ -2918,10 +2930,10 @@
dependencies:
argparse "^2.0.1"
-jsdoc-type-pratt-parser@~3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-3.1.0.tgz#a4a56bdc6e82e5865ffd9febc5b1a227ff28e67e"
- integrity sha512-MgtD0ZiCDk9B+eI73BextfRrVQl0oyzRG8B2BjORts6jbunj4ScKPcyXGTbB6eXL4y9TzxCm6hyeLq/2ASzNdw==
+jsdoc-type-pratt-parser@~4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz#136f0571a99c184d84ec84662c45c29ceff71114"
+ integrity sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==
json-buffer@3.0.0:
version "3.0.0"
@@ -4107,10 +4119,10 @@
dependencies:
lru-cache "^6.0.0"
-semver@^7.3.8:
- version "7.3.8"
- resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798"
- integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==
+semver@^7.5.1:
+ version "7.5.1"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.1.tgz#c90c4d631cf74720e46b21c1d37ea07edfab91ec"
+ integrity sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==
dependencies:
lru-cache "^6.0.0"