diff --git a/application/Espo/Core/Select/Applier/AdditionalAppliers/IsStarred.php b/application/Espo/Core/Select/Applier/AdditionalAppliers/IsStarred.php
new file mode 100644
index 0000000000..39e79051c3
--- /dev/null
+++ b/application/Espo/Core/Select/Applier/AdditionalAppliers/IsStarred.php
@@ -0,0 +1,39 @@
+metadata->get("scopes.$this->entityType.stars")) {
+ return;
+ }
+
+ $queryBuilder
+ ->select(Expr::isNotNull(Expr::column('starSubscription.id')), 'isStarred')
+ ->leftJoin(StarSubscription::ENTITY_TYPE, 'starSubscription', [
+ 'userId' => $this->user->getId(),
+ 'entityType' => $this->entityType,
+ 'entityId:' => 'id',
+ ]);
+ }
+}
diff --git a/application/Espo/Resources/metadata/app/record.json b/application/Espo/Resources/metadata/app/record.json
new file mode 100644
index 0000000000..260f3a1bc4
--- /dev/null
+++ b/application/Espo/Resources/metadata/app/record.json
@@ -0,0 +1,5 @@
+{
+ "selectApplierClassNameList": [
+ "Espo\\Core\\Select\\Applier\\AdditionalAppliers\\IsStarred"
+ ]
+}
diff --git a/client/res/templates/record/list-expanded.tpl b/client/res/templates/record/list-expanded.tpl
index ed839a99c7..db16451dd0 100644
--- a/client/res/templates/record/list-expanded.tpl
+++ b/client/res/templates/record/list-expanded.tpl
@@ -51,10 +51,11 @@
- {{#each rowList}}
- -
- {{{var this ../this}}}
-
+ {{#each rowDataList}}
+ - {{{var id ../this}}}
{{/each}}
diff --git a/client/res/templates/record/list.tpl b/client/res/templates/record/list.tpl
index 7652070c4d..425eaba1d8 100644
--- a/client/res/templates/record/list.tpl
+++ b/client/res/templates/record/list.tpl
@@ -211,10 +211,11 @@
{{/if}}
- {{#each rowList}}
-
- {{{var this ../this}}}
-
+ {{#each rowDataList}}
+ {{{var id ../this}}}
{{/each}}
diff --git a/client/src/views/record/list-tree.js b/client/src/views/record/list-tree.js
index 437100c7b1..7f3007f08d 100644
--- a/client/src/views/record/list-tree.js
+++ b/client/src/views/record/list-tree.js
@@ -84,7 +84,7 @@ class ListTreeRecordView extends ListRecordView {
data.showRootMenu = false;
}
- data.noData = data.createDisabled && !data.rowList.length && !data.showRoot;
+ data.noData = data.createDisabled && !data.rowDataList.length && !data.showRoot;
return data;
}
diff --git a/client/src/views/record/list.js b/client/src/views/record/list.js
index 35e46379dd..ed355da3fc 100644
--- a/client/src/views/record/list.js
+++ b/client/src/views/record/list.js
@@ -1000,6 +1000,16 @@ class ListRecordView extends View {
const noDataDisabled = this.noDataDisabled || this._renderEmpty;
+ const rowDataList = this.rowList ?
+ this.rowList.map(id => {
+ return {
+ id: id,
+ isStarred: this.hasStars && this.collection.get(id) ?
+ this.collection.get(id).attributes.isStarred :
+ false,
+ };
+ }) : [];
+
return {
scope: this.scope,
collectionLength: this.collection.models.length,
@@ -1013,7 +1023,8 @@ class ListRecordView extends View {
moreCount: moreCount,
checkboxes: this.checkboxes,
massActionDataList: this.getMassActionDataList(),
- rowList: this.rowList,
+ rowList: this.rowList, // For bc.
+ rowDataList: rowDataList,
topBar: topBar,
checkAllResultDisabled: checkAllResultDisabled,
buttonList: this.buttonList,
@@ -2019,6 +2030,12 @@ class ListRecordView extends View {
this.checkboxes = false;
}
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.hasStars = this.getMetadata().get(`scopes.${this.entityType}.stars`) || false;
+
if (
this.getUser().isPortal() &&
!this.portalLayoutDisabled &&
@@ -2951,6 +2968,11 @@ class ListRecordView extends View {
buildRows(callback) {
this.checkedList = [];
+ /**
+ * @internal
+ * @type {string[]}
+ * @private
+ */
this.rowList = [];
if (this.collection.length <= 0) {
diff --git a/frontend/less/espo/custom.less b/frontend/less/espo/custom.less
index e652b462b6..9e93e9adc5 100644
--- a/frontend/less/espo/custom.less
+++ b/frontend/less/espo/custom.less
@@ -1247,6 +1247,32 @@ section {
}
}
+.list > {
+ table > tbody {
+ > tr.list-row.starred {
+ td {
+ //background-color: var(--warning-bg);
+ }
+
+ .cell[data-name="name"] {
+ &:has(> a.link) {
+ &::before {
+ font-family: 'Font Awesome 5 Free';
+ font-weight: 900;
+ content: "\f005";
+ font-size: 10px;
+ padding-right: 4px;
+ top: 3px;
+ position: relative;
+ color: var(--state-warning-text);
+ float: left;
+ }
+ }
+ }
+ }
+ }
+}
+
.panel.dashlet > .panel-body > .list-container > .list > .list-group {
> li.list-group-item:last-child {
border-bottom-width: 1px;