diff --git a/.template-lintrc.js b/.template-lintrc.js index 77a11b8f2cb..a5c4998a0ce 100644 --- a/.template-lintrc.js +++ b/.template-lintrc.js @@ -15,7 +15,7 @@ module.exports = { "directory-item-value", "directory-table-header-title", "loading-spinner", - "directory-item-label", + "mobile-directory-item-label", ], }, "no-implicit-this": { diff --git a/app/assets/javascripts/admin/addon/controllers/admin-users-list-show.js b/app/assets/javascripts/admin/addon/controllers/admin-users-list-show.js index 0976adec96a..e51e7c3a5cb 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-users-list-show.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-users-list-show.js @@ -31,21 +31,6 @@ export default Controller.extend(CanCheckEmails, { return I18n.t("admin.users.titles." + query); }, - @discourseComputed("showEmails") - columnCount(showEmails) { - let colCount = 7; // note that the first column is hardcoded in the template - - if (showEmails) { - colCount += 1; - } - - if (this.siteSettings.must_approve_users) { - colCount += 1; - } - - return colCount; - }, - @observes("listFilter") _filterUsers() { discourseDebounce(this, this.resetFilters, INPUT_DELAY); diff --git a/app/assets/javascripts/admin/addon/templates/users-list-show.hbs b/app/assets/javascripts/admin/addon/templates/users-list-show.hbs index 20ff75eb77c..95f9c8fbbbc 100644 --- a/app/assets/javascripts/admin/addon/templates/users-list-show.hbs +++ b/app/assets/javascripts/admin/addon/templates/users-list-show.hbs @@ -26,204 +26,140 @@ @title={{this.searchHint}} /> + {{#if this.model}} + + + + + + + + + + + + - - - <:header> - - - - - - - - - - - {{#if this.siteSettings.must_approve_users}} -
{{i18n - "admin.users.approved" - }}
- {{/if}} -
 
- - - - <:body> + {{#if this.siteSettings.must_approve_users}} +
+ {{/if}} + + + + {{#each this.model as |user|}} -
-
- +
+ {{#if user.last_emailed_at}} -
- - {{i18n "admin.users.last_emailed"}} - - - {{format-duration user.last_emailed_age}} - -
+
{{else}} -
- - {{i18n "admin.users.last_emailed"}} - - - {{format-duration user.last_emailed_age}} - -
+
{{/if}} -
- - {{i18n "last_seen"}} - - - {{format-duration user.last_seen_age}} - -
-
- - {{i18n "admin.user.topics_entered"}} - - - {{number user.topics_entered}} - -
-
- - {{i18n "admin.user.posts_read_count"}} - - - {{number user.posts_read_count}} - -
-
- - {{i18n "admin.user.time_read"}} - - - {{format-duration user.time_read}} - -
-
- - {{i18n "created"}} - - - {{format-duration user.created_at_age}} - -
+
+ + + + + {{#if this.siteSettings.must_approve_users}} -
- - {{i18n "admin.users.approved"}} - - - {{i18n-yes-no user.approved}} - -
+
{{/if}} -
- - {{i18n "admin.users.status"}} - - - {{#if user.admin}} - {{d-icon "shield-alt" title="admin.title"}} - {{/if}} - {{#if user.moderator}} - {{d-icon "shield-alt" title="admin.moderator"}} - {{/if}} - {{#if user.second_factor_enabled}} - {{d-icon "lock" title="admin.user.second_factor_enabled"}} - {{/if}} - +
+ {{/each}} - - - - + +
{{i18n "admin.users.approved"}} 
+ {{avatar user imageSize="small"}} - - {{user.username}} - + {{user.username}} {{#if user.staged}} {{d-icon "far-envelope" title="user.staged"}} {{/if}} - - + +
{{i18n "admin.users.last_emailed"}}
+
{{format-duration user.last_emailed_age}}
+
+
{{i18n "admin.users.last_emailed"}}
+
{{format-duration user.last_emailed_age}}
+
+
{{i18n "last_seen"}}
+
{{format-duration user.last_seen_age}}
+
+
{{i18n "admin.user.topics_entered"}}
+
{{number user.topics_entered}}
+
+
{{i18n "admin.user.posts_read_count"}}
+
{{number user.posts_read_count}}
+
+
{{i18n "admin.user.time_read"}}
+
{{format-duration user.time_read}}
+
+
{{i18n "created"}}
+
{{format-duration user.created_at_age}}
+
{{i18n-yes-no user.approved}} + {{#if user.admin}} + {{d-icon "shield-alt" title="admin.title"}} + {{/if}} + {{#if user.moderator}} + {{d-icon "shield-alt" title="admin.moderator"}} + {{/if}} + {{#if user.second_factor_enabled}} + {{d-icon "lock" title="admin.user.second_factor_enabled"}} + {{/if}} - - +
{{else}}

{{i18n "search.no_results"}}

diff --git a/app/assets/javascripts/discourse/app/components/directory-item.hbs b/app/assets/javascripts/discourse/app/components/directory-item.hbs index e05a5367c79..4561d5ecb68 100644 --- a/app/assets/javascripts/discourse/app/components/directory-item.hbs +++ b/app/assets/javascripts/discourse/app/components/directory-item.hbs @@ -1,38 +1,16 @@ -
- -
- + {{#each this.columns as |column|}} - {{#if (directory-column-is-user-field column=column)}} -
- - {{column.name}} - + + {{#if (directory-column-is-user-field column=column)}} {{directory-item-user-field-value item=this.item column=column}} -
- {{else}} -
- - - {{#if column.icon}} - {{d-icon column.icon}} - {{/if}} - {{directory-item-label item=this.item column=column}} - - + {{else}} {{directory-item-value item=this.item column=column}} -
- {{/if}} - + {{/if}} + {{/each}} {{#if this.showTimeRead}} -
- - {{i18n "directory.time_read"}} - - - {{format-duration this.item.time_read}} - -
+ {{format-duration + this.item.time_read + }} {{/if}} \ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/components/directory-item.js b/app/assets/javascripts/discourse/app/components/directory-item.js index f1b4e15a829..0b557d6672a 100644 --- a/app/assets/javascripts/discourse/app/components/directory-item.js +++ b/app/assets/javascripts/discourse/app/components/directory-item.js @@ -2,8 +2,7 @@ import Component from "@ember/component"; import { propertyEqual } from "discourse/lib/computed"; export default Component.extend({ - tagName: "div", - classNames: ["directory-table__row"], + tagName: "tr", classNameBindings: ["me"], me: propertyEqual("item.user.id", "currentUser.id"), columns: null, diff --git a/app/assets/javascripts/discourse/app/components/directory-table.hbs b/app/assets/javascripts/discourse/app/components/directory-table.hbs index f9aa74ba30a..d8adc30add1 100644 --- a/app/assets/javascripts/discourse/app/components/directory-table.hbs +++ b/app/assets/javascripts/discourse/app/components/directory-table.hbs @@ -1,37 +1,39 @@ - - <:header> - - {{#each this.columns as |column|}} +
+
+
+ +
+ + - {{/each}} + {{#each this.columns as |column|}} + + {{/each}} - {{#if this.showTimeRead}} -
-
- {{i18n "directory.time_read"}} -
-
- {{/if}} - - <:body> - {{#each this.items as |item|}} - - {{/each}} - - \ No newline at end of file + {{#if this.showTimeRead}} +
+ {{/if}} + + + {{#each this.items as |item|}} + + {{/each}} + +
{{i18n "directory.time_read"}}
+
\ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/components/directory-table.js b/app/assets/javascripts/discourse/app/components/directory-table.js index cf031759516..08cb163562c 100644 --- a/app/assets/javascripts/discourse/app/components/directory-table.js +++ b/app/assets/javascripts/discourse/app/components/directory-table.js @@ -2,31 +2,109 @@ import Component from "@ember/component"; import { action } from "@ember/object"; export default Component.extend({ + lastScrollPosition: 0, + ticking: false, + _topHorizontalScrollBar: null, + _tableContainer: null, _table: null, + _fakeScrollContent: null, didInsertElement() { this._super(...arguments); this.setProperties({ + _tableContainer: this.element.querySelector(".directory-table-container"), + _topHorizontalScrollBar: this.element.querySelector( + ".directory-table-top-scroll" + ), + _fakeScrollContent: this.element.querySelector( + ".directory-table-top-scroll-fake-content" + ), _table: this.element.querySelector(".directory-table"), - _columnCount: this.showTimeRead - ? this.attrs.columns.value.length + 1 - : this.attrs.columns.value.length, }); - this._table.style.gridTemplateColumns = `minmax(13em, 3fr) repeat(${this._columnCount}, minmax(max-content, 1fr))`; + this._tableContainer.addEventListener("scroll", this.onBottomScroll); + this._topHorizontalScrollBar.addEventListener("scroll", this.onTopScroll); + + // Set active header might have already scrolled the _tableContainer. + // Call onHorizontalScroll manually to scroll the _topHorizontalScrollBar + this.onResize(); + this.onHorizontalScroll(this._tableContainer, this._topHorizontalScrollBar); + window.addEventListener("resize", this.onResize); + }, + + @action + onResize() { + if ( + this._tableContainer.getBoundingClientRect().bottom < window.innerHeight + ) { + // Bottom of the table is visible. Hide the scrollbar + this._fakeScrollContent.style.height = 0; + } else { + this._fakeScrollContent.style.width = `${this._table.offsetWidth}px`; + this._fakeScrollContent.style.height = "1px"; + } + }, + + @action + onTopScroll() { + this.onHorizontalScroll(this._topHorizontalScrollBar, this._tableContainer); + }, + + @action + onBottomScroll() { + this.onHorizontalScroll(this._tableContainer, this._topHorizontalScrollBar); + }, + + @action + onHorizontalScroll(primary, replica) { + if ( + this.isDestroying || + this.isDestroyed || + this.lastScrollPosition === primary.scrollLeft + ) { + return; + } + + this.set("lastScrollPosition", primary.scrollLeft); + + if (!this.ticking) { + window.requestAnimationFrame(() => { + if (!this.isDestroying && !this.isDestroyed) { + replica.scrollLeft = this.lastScrollPosition; + this.set("ticking", false); + } + }); + + this.set("ticking", true); + } + }, + + willDestroyElement() { + this._tableContainer.removeEventListener("scroll", this.onBottomScroll); + this._topHorizontalScrollBar.removeEventListener( + "scroll", + this.onTopScroll + ); + window.removeEventListener("resize", this.onResize); }, @action setActiveHeader(header) { // After render, scroll table left to ensure the order by column is visible - if (!this._table) { - this.set("_table", document.querySelector(".directory-table")); + if (!this._tableContainer) { + this.set( + "_tableContainer", + document.querySelector(".directory-table-container") + ); } const scrollPixels = - header.offsetLeft + header.offsetWidth + 10 - this._table.offsetWidth; + header.offsetLeft + + header.offsetWidth + + 10 - + this._tableContainer.offsetWidth; if (scrollPixels > 0) { - this._table.scrollLeft = scrollPixels; + this._tableContainer.scrollLeft = scrollPixels; } }, }); diff --git a/app/assets/javascripts/discourse/app/components/responsive-table.hbs b/app/assets/javascripts/discourse/app/components/responsive-table.hbs deleted file mode 100644 index 26c610e184c..00000000000 --- a/app/assets/javascripts/discourse/app/components/responsive-table.hbs +++ /dev/null @@ -1,20 +0,0 @@ -
-
-
-
-
- {{yield to="header"}} -
-
- {{yield to="body"}} -
-
\ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/components/responsive-table.js b/app/assets/javascripts/discourse/app/components/responsive-table.js deleted file mode 100644 index aecf9c67c34..00000000000 --- a/app/assets/javascripts/discourse/app/components/responsive-table.js +++ /dev/null @@ -1,51 +0,0 @@ -import Component from "@ember/component"; -import { bind } from "discourse-common/utils/decorators"; -import { tracked } from "@glimmer/tracking"; - -export default class ResponsiveTable extends Component { - @tracked lastScrollPosition = 0; - @tracked ticking = false; - @tracked _table = document.querySelector(".directory-table"); - @tracked _topHorizontalScrollBar = document.querySelector( - ".directory-table-top-scroll" - ); - - @bind - checkScroll() { - const _fakeScrollContent = document.querySelector( - ".directory-table-top-scroll-fake-content" - ); - - if (this._table.getBoundingClientRect().bottom < window.innerHeight) { - // Bottom of the table is visible. Hide the scrollbar - _fakeScrollContent.style.height = 0; - } else { - _fakeScrollContent.style.width = `${this._table.scrollWidth}px`; - _fakeScrollContent.style.height = "1px"; - } - } - - @bind - onTopScroll() { - this.onHorizontalScroll(this._topHorizontalScrollBar, this._table); - } - - @bind - onBottomScroll() { - this.onHorizontalScroll(this._table, this._topHorizontalScrollBar); - } - - @bind - onHorizontalScroll(primary, replica) { - this.set("lastScrollPosition", primary?.scrollLeft); - - if (!this.ticking) { - window.requestAnimationFrame(() => { - replica.scrollLeft = this.lastScrollPosition; - this.set("ticking", false); - }); - - this.set("ticking", true); - } - } -} diff --git a/app/assets/javascripts/discourse/app/components/table-header-toggle.hbs b/app/assets/javascripts/discourse/app/components/table-header-toggle.hbs index dc32c5c91fb..4a8aacf6721 100644 --- a/app/assets/javascripts/discourse/app/components/table-header-toggle.hbs +++ b/app/assets/javascripts/discourse/app/components/table-header-toggle.hbs @@ -1,4 +1,4 @@ -
- - {{yield}} - - {{directory-table-header-title - field=this.field - labelKey=this.labelKey - icon=this.icon - translated=this.translated - }} - {{this.chevronIcon}} - -
\ No newline at end of file + {{directory-table-header-title + field=this.field + labelKey=this.labelKey + icon=this.icon + translated=this.translated + }} + {{this.chevronIcon}} + \ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/components/table-header-toggle.js b/app/assets/javascripts/discourse/app/components/table-header-toggle.js index 1b1bb07ce1a..78dc01dffee 100644 --- a/app/assets/javascripts/discourse/app/components/table-header-toggle.js +++ b/app/assets/javascripts/discourse/app/components/table-header-toggle.js @@ -6,8 +6,8 @@ import discourseComputed from "discourse-common/utils/decorators"; import I18n from "I18n"; export default Component.extend({ - tagName: "div", - classNames: ["directory-table__column-header", "sortable"], + tagName: "th", + classNames: ["sortable"], attributeBindings: ["title", "colspan", "ariaSort:aria-sort", "role"], role: "columnheader", labelKey: null, diff --git a/app/assets/javascripts/discourse/app/helpers/directory-item-helpers.js b/app/assets/javascripts/discourse/app/helpers/directory-item-helpers.js index 41ab101dcc2..29339de185a 100644 --- a/app/assets/javascripts/discourse/app/helpers/directory-item-helpers.js +++ b/app/assets/javascripts/discourse/app/helpers/directory-item-helpers.js @@ -3,7 +3,7 @@ import { number } from "discourse/lib/formatter"; import { registerUnbound } from "discourse-common/lib/helpers"; import I18n from "I18n"; -registerUnbound("directory-item-label", function (args) { +registerUnbound("mobile-directory-item-label", function (args) { // Args should include key/values { item, column } const count = args.item.get(args.column.name); const translationPrefix = @@ -14,9 +14,7 @@ registerUnbound("directory-item-label", function (args) { registerUnbound("directory-item-value", function (args) { // Args should include key/values { item, column } return htmlSafe( - `${number( - args.item.get(args.column.name) - )}` + `${number(args.item.get(args.column.name))}` ); }); @@ -27,9 +25,7 @@ registerUnbound("directory-item-user-field-value", function (args) { ? args.item.user.user_fields[args.column.user_field_id] : null; const content = value || "-"; - return htmlSafe( - `${content}` - ); + return htmlSafe(`${content}`); }); registerUnbound("directory-column-is-automatic", function (args) { diff --git a/app/assets/javascripts/discourse/app/templates/group-index.hbs b/app/assets/javascripts/discourse/app/templates/group-index.hbs index ec769263a0c..dc0dfa4809b 100644 --- a/app/assets/javascripts/discourse/app/templates/group-index.hbs +++ b/app/assets/javascripts/discourse/app/templates/group-index.hbs @@ -1,15 +1,5 @@
- - {{#if this.canManageGroup}} - - {{/if}} - {{#if this.model.can_see_members}} - {{#if this.bulkSelection}} - - - - {{/if}} - - - - - {{/if}} -
{{#if this.hasMembers}} - - - + - <:header> - - - {{#if this.canManageGroup}} -
- {{/if}} - - - - - - {{#if this.canManageGroup}} -
- {{/if}} - - - <:body> - {{#each this.model.members as |m|}} -
- -
- {{#if this.canManageGroup}} - {{#if this.isBulk}} - +
+ + {{#if this.isBulk}} + + {{/if}} + + + + + + + + + + {{#each this.model.members as |m|}} + + {{#if this.isBulk}} + + {{/if}} + + - {{#if this.canManageGroup}} -
- {{#if (or m.owner m.primary)}} - - {{i18n "groups.members.status"}} - - {{/if}} - - {{#if m.owner}} - {{d-icon "shield-alt"}} - {{i18n "groups.members.owner"}}
- {{/if}} - {{#if m.primary}} - {{i18n "groups.members.primary"}} - {{/if}} -
+
+ + + - - {{/if}} -
- - {{i18n "groups.member_added"}} - - - {{bound-date m.added_at}} - -
-
- {{#if m.last_posted_at}} - - {{i18n "last_post"}} - - {{/if}} - - {{bound-date m.last_posted_at}} - -
-
- {{#if m.last_seen_at}} - - {{i18n "last_seen"}} - - {{/if}} - - {{bound-date m.last_seen_at}} - -
- {{#if this.canManageGroup}} -
+
+ {{/each}} - - - - + +
+ {{#if this.canManageGroup}} + + {{/if}} + + + {{#if this.bulkSelection}} + {{/if}} - {{/if}} + + + +
+ + - + + {{#if m.owner}} + {{d-icon "shield-alt"}} + {{i18n "groups.members.owner"}}
+ {{/if}} + {{#if m.primary}} + {{i18n "groups.members.primary"}} + {{/if}} +
+ {{bound-date m.added_at}} + + {{bound-date m.last_posted_at}} + + {{bound-date m.last_seen_at}} + + {{#if this.canManageGroup}} - {{! group parameter is used by plugins }} - - {{/if}} - + {{/if}} + {{! group parameter is used by plugins }} +
diff --git a/app/assets/javascripts/discourse/app/templates/group-requests.hbs b/app/assets/javascripts/discourse/app/templates/group-requests.hbs index 2f03497cb6c..6ea7de3ea1a 100644 --- a/app/assets/javascripts/discourse/app/templates/group-requests.hbs +++ b/app/assets/javascripts/discourse/app/templates/group-requests.hbs @@ -9,14 +9,10 @@
{{#if this.hasRequesters}} - - - <:header> + + + -
{{i18n - "groups.requests.reason" - }}
-
- - <:body> +
+ + + + + {{#each this.model.requesters as |m|}} -
-
+
+ + + + + + {{/each}} - - + +
{{i18n "groups.requests.reason"}}
- -
- - {{i18n "groups.member_requested"}} - - - {{bound-date m.requested_at}} - -
-
- - {{i18n "groups.requests.reason"}} - - - {{m.reason}} - -
-
+
+ {{bound-date m.requested_at}} + {{m.reason}} {{#if m.request_undone}} {{i18n "groups.requests.undone"}} {{else if m.request_accepted}} @@ -83,14 +67,17 @@ @class="btn-danger" /> {{/if}} - - +
+ {{else}}
{{i18n "groups.empty.requests"}}
{{/if}} +
\ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/templates/mobile/components/directory-item.hbs b/app/assets/javascripts/discourse/app/templates/mobile/components/directory-item.hbs new file mode 100644 index 00000000000..6f8d45048ec --- /dev/null +++ b/app/assets/javascripts/discourse/app/templates/mobile/components/directory-item.hbs @@ -0,0 +1,37 @@ + + +{{#each this.columns as |column|}} + {{#if (directory-column-is-user-field column=column)}} + {{#if (get this.item.user.user_fields column.user_field_id)}} +
+ + {{directory-item-user-field-value item=this.item column=column}} + + + {{column.name}} + +
+ {{/if}} + + {{else}} +
+ + {{directory-item-value item=this.item column=column}} + + + {{#if column.icon}} + {{d-icon column.icon}} + {{/if}} + {{mobile-directory-item-label item=this.item column=column}} + +
+ {{/if}} +{{/each}} + +{{#if this.showTimeRead}} + +{{/if}} \ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/templates/mobile/users.hbs b/app/assets/javascripts/discourse/app/templates/mobile/users.hbs new file mode 100644 index 00000000000..8f023d3f8fb --- /dev/null +++ b/app/assets/javascripts/discourse/app/templates/mobile/users.hbs @@ -0,0 +1,80 @@ + +
+
+ + + + +
+ + {{#if this.lastUpdatedAt}} +
+ {{i18n "directory.last_updated"}} + {{this.lastUpdatedAt}} +
+ {{/if}} +
+ + + {{#if this.currentUser.staff}} + + {{/if}} +
+ +
+ + + {{#if this.model.length}} +
{{i18n + "directory.total_rows" + count=this.model.totalRows + }}
+ {{#each this.model as |item|}} + + {{/each}} + + + {{else}} +
+

{{i18n "directory.no_results"}}

+ {{/if}} +
+ +
+
+
\ No newline at end of file diff --git a/app/assets/javascripts/discourse/tests/acceptance/admin-users-list-test.js b/app/assets/javascripts/discourse/tests/acceptance/admin-users-list-test.js index 83badf83d05..6e051abe6be 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/admin-users-list-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/admin-users-list-test.js @@ -68,8 +68,7 @@ acceptance("Admin - Users List", function (needs) { await click(".hide-emails"); assert.strictEqual( - query(".users-list .user:nth-child(1) .email .directory-table__value") - .innerText, + query(".users-list .user:nth-child(1) .email").innerText, "", "hides the emails" ); diff --git a/app/assets/javascripts/discourse/tests/acceptance/group-index-test.js b/app/assets/javascripts/discourse/tests/acceptance/group-index-test.js index 475b6baf7f8..2707adf91d9 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/group-index-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/group-index-test.js @@ -20,7 +20,7 @@ acceptance("Group Members - Anonymous", function () { 1, "it displays the group's avatar flair" ); - assert.ok(exists(".group-members .group-member"), "it lists group members"); + assert.ok(exists(".group-members tr"), "it lists group members"); assert.ok( !exists(".group-member-dropdown"), @@ -137,7 +137,7 @@ acceptance("Group Members", function (needs) { ); await click("button.bulk-select"); - await click(".bulk-select-all"); + await click(".bulk-select-buttons button:nth-child(1)"); assert.ok( exists(".bulk-select-buttons-wrap details"), diff --git a/app/assets/javascripts/discourse/tests/acceptance/group-manage-categories-test.js b/app/assets/javascripts/discourse/tests/acceptance/group-manage-categories-test.js index 38dcd87512d..1e36361608c 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/group-manage-categories-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/group-manage-categories-test.js @@ -12,7 +12,7 @@ acceptance("Managing Group Category Notification Defaults", function () { await visit("/g/discourse/manage/categories"); assert.ok( - exists(".group-members .group-member"), + exists(".group-members tr"), "it should redirect to members page for an anonymous user" ); }); diff --git a/app/assets/javascripts/discourse/tests/acceptance/group-manage-profile-test.js b/app/assets/javascripts/discourse/tests/acceptance/group-manage-profile-test.js index c3de67d7f55..aa436364a53 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/group-manage-profile-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/group-manage-profile-test.js @@ -12,7 +12,7 @@ acceptance("Managing Group Profile", function () { await visit("/g/discourse/manage/profile"); assert.ok( - exists(".group-members .group-member"), + exists(".group-members tr"), "it should redirect to members page for an anonymous user" ); }); diff --git a/app/assets/javascripts/discourse/tests/acceptance/group-manage-tags-test.js b/app/assets/javascripts/discourse/tests/acceptance/group-manage-tags-test.js index e3d500831c9..2d75056e83d 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/group-manage-tags-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/group-manage-tags-test.js @@ -12,7 +12,7 @@ acceptance("Managing Group Tag Notification Defaults", function () { await visit("/g/discourse/manage/tags"); assert.ok( - exists(".group-members .group-member"), + exists(".group-members tr"), "it should redirect to members page for an anonymous user" ); }); diff --git a/app/assets/javascripts/discourse/tests/acceptance/group-requests-test.js b/app/assets/javascripts/discourse/tests/acceptance/group-requests-test.js index 2a9441dfb11..abbde3f6038 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/group-requests-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/group-requests-test.js @@ -89,49 +89,37 @@ acceptance("Group Requests", function (needs) { test("Group Requests", async function (assert) { await visit("/g/Macdonald/requests"); - assert.strictEqual(count(".group-members .group-member"), 2); + assert.strictEqual(count(".group-members tr"), 2); assert.strictEqual( - query(".group-members .directory-table__row:first-child .user-detail") + query(".group-members tr:first-child td:nth-child(1)") .innerText.trim() .replace(/\s+/g, " "), "eviltrout Robin Ward" ); assert.strictEqual( - query( - ".group-members .directory-table__row:first-child .directory-table__cell:nth-child(3)" - ).innerText.trim(), + query(".group-members tr:first-child td:nth-child(3)").innerText.trim(), "Please accept my membership request." ); assert.strictEqual( - query( - ".group-members .directory-table__row:first-child .btn-primary" - ).innerText.trim(), + query(".group-members tr:first-child .btn-primary").innerText.trim(), "Accept" ); assert.strictEqual( - query( - ".group-members .directory-table__row:first-child .btn-danger" - ).innerText.trim(), + query(".group-members tr:first-child .btn-danger").innerText.trim(), "Deny" ); - await click( - ".group-members .directory-table__row:first-child .btn-primary" - ); + await click(".group-members tr:first-child .btn-primary"); assert.ok( - query( - ".group-members .directory-table__row:first-child .directory-table__cell:nth-child(4)" - ) + query(".group-members tr:first-child td:nth-child(4)") .innerText.trim() .startsWith("accepted") ); assert.deepEqual(requests, [["19", "true"]]); - await click(".group-members .directory-table__row:last-child .btn-danger"); + await click(".group-members tr:last-child .btn-danger"); assert.strictEqual( - query( - ".group-members .directory-table__row:last-child .directory-table__cell:nth-child(4)" - ).innerText.trim(), + query(".group-members tr:last-child td:nth-child(4)").innerText.trim(), "denied" ); assert.deepEqual(requests, [ diff --git a/app/assets/javascripts/discourse/tests/acceptance/mobile-users-test.js b/app/assets/javascripts/discourse/tests/acceptance/mobile-users-test.js index 4f46e025547..9913b12f4e2 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/mobile-users-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/mobile-users-test.js @@ -7,9 +7,6 @@ acceptance("User Directory - Mobile", function (needs) { test("Visit Page", async function (assert) { await visit("/u"); - assert.ok( - exists(".directory .directory-table__row"), - "has a list of users" - ); + assert.ok(exists(".directory .user"), "has a list of users"); }); }); diff --git a/app/assets/javascripts/discourse/tests/acceptance/users-test.js b/app/assets/javascripts/discourse/tests/acceptance/users-test.js index 941bc4e0d1c..d6ffec8bc65 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/users-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/users-test.js @@ -14,10 +14,7 @@ acceptance("User Directory", function () { document.body.classList.contains("users-page"), "has the body class" ); - assert.ok( - exists(".directory .directory-table .directory-table__row"), - "has a list of users" - ); + assert.ok(exists(".directory table tr"), "has a list of users"); }); test("Visit All Time", async function (assert) { @@ -31,10 +28,7 @@ acceptance("User Directory", function () { document.body.classList.contains("users-page"), "has the body class" ); - assert.ok( - exists(".directory .directory-table .directory-table__row"), - "has a list of users" - ); + assert.ok(exists(".directory table tr"), "has a list of users"); }); test("Visit With Group Filter", async function (assert) { @@ -43,27 +37,27 @@ acceptance("User Directory", function () { document.body.classList.contains("users-page"), "has the body class" ); - assert.ok( - exists(".directory .directory-table .directory-table__row"), - "has a list of users" - ); + assert.ok(exists(".directory table tr"), "has a list of users"); }); test("Custom user fields are present", async function (assert) { await visit("/u"); - const firstRowUserField = query( - ".directory .directory-table__body .directory-table__row:first-child .directory-table__value--user-field" - ); + const firstRow = query(".users-directory table tr"); + const columnData = firstRow.querySelectorAll("td"); + const favoriteColorTd = columnData[columnData.length - 1]; - assert.strictEqual(firstRowUserField.textContent, "Blue"); + assert.strictEqual( + favoriteColorTd.querySelector("span").textContent, + "Blue" + ); }); test("Can sort table via keyboard", async function (assert) { await visit("/u"); const secondHeading = - ".users-directory .directory-table__header div:nth-child(2) .header-contents"; + ".users-directory table th:nth-child(2) .header-contents"; await triggerKeyEvent(secondHeading, "keypress", "Enter"); diff --git a/app/assets/stylesheets/common/admin/admin_base.scss b/app/assets/stylesheets/common/admin/admin_base.scss index 28044352472..3a2514f6cec 100644 --- a/app/assets/stylesheets/common/admin/admin_base.scss +++ b/app/assets/stylesheets/common/admin/admin_base.scss @@ -802,15 +802,19 @@ section.details { } } -.directory-table { - .not-activated { - .directory-table__cell { - &, - a, - a:visited { - color: #bbb; - } - } +tr.not-activated { + td, + td a, + td a:visited { + color: #bbb; + } +} + +.details.not-activated { + .username .value, + .email .value a, + .email .value a:visited { + color: #bbb; } } diff --git a/app/assets/stylesheets/common/admin/users.scss b/app/assets/stylesheets/common/admin/users.scss index e824dbf9a9f..9dff0bd83b3 100644 --- a/app/assets/stylesheets/common/admin/users.scss +++ b/app/assets/stylesheets/common/admin/users.scss @@ -99,55 +99,49 @@ } .admin-users-list { - .directory-table__cell { - &.username { - justify-content: start; + td.username { + @include ellipsis; + overflow-wrap: break-word; + } + @media screen and (max-width: 970px) and (min-width: 768px) { + td.username { + max-width: 23vw; // Prevents horizontal scroll down to 768px } - &.email { - justify-content: start; - span { - display: flex; - min-width: 17em; - word-break: break-all; - } + td.email { + max-width: 28vw; // Prevents horizontal scroll down to 768px + overflow-wrap: break-word; } } - - .directory-table { - margin-top: 1em; - &__column-header--username, - &__column-header--email { - .header-contents { - text-align: left; + @media screen and (max-width: 767px) { + tr { + td.username { + grid-column-start: 1; + grid-column-end: -2; + font-weight: bold; } - } - - &__cell.username { - align-items: center; - } - - &__cell.email { - @include breakpoint("tablet") { + td.user-status { + text-align: right; + grid-row: 1; + grid-column-end: -1; + .d-icon { + margin-left: 0.25em; + } + } + td.email { grid-column-start: 1; grid-column-end: -1; - span { - max-width: 100%; + word-wrap: break-word; + overflow-wrap: break-word; + overflow: hidden; + min-width: 0; + margin: 0.5em 0 0 0; + + &:empty { + display: none; } } } } - - .directory-table__cell { - padding: 0.5em 0.25em; - } - - .user-status span { - gap: 0.15em; - } - - .avatar { - margin-right: 0.25em; - } } // mobile styles diff --git a/app/assets/stylesheets/common/base/directory.scss b/app/assets/stylesheets/common/base/directory.scss index c8a120c1ff8..f64f1b1cec4 100644 --- a/app/assets/stylesheets/common/base/directory.scss +++ b/app/assets/stylesheets/common/base/directory.scss @@ -1,11 +1,16 @@ -.directory-table-top-scroll { - width: 100%; - overflow-x: auto; -} - .directory { margin-bottom: 100px; + .directory-table-container { + width: 100%; + overflow-x: auto; + } + + .directory-table-top-scroll { + width: 100%; + overflow-x: auto; + } + &.users-directory { .directory-group-selector { vertical-align: top; @@ -34,6 +39,58 @@ color: var(--primary-medium); font-size: var(--font-down-1); } + + table { + width: 100%; + margin-bottom: 1em; + + td, + th { + padding: 0.5em; + text-align: left; + border-bottom: 1px solid var(--primary-low); + @media screen and (max-width: $small-width) { + padding: 0.5em 0.25em; + } + + .number, + .time-read { + font-size: var(--font-up-3); + color: var(--primary-medium); + @media screen and (max-width: $small-width) { + font-size: var(--font-up-1); + } + } + .time-read { + white-space: nowrap; + } + .user-field-value { + font-size: var(--font-up-1); + color: var(--primary-medium); + @media screen and (max-width: $small-width) { + font-size: var(--font-0); + } + } + } + + th.sortable { + width: 13%; + .d-icon-heart { + color: var(--love); + margin: 0 0.25em 0 0; + } + } + } + .me { + background-color: var(--highlight-bg); + .username a, + .name a, + .title, + .number, + .time-read { + color: var(--primary-medium); + } + } } .edit-user-directory-columns-modal { @@ -82,221 +139,3 @@ .edit-user-directory-columns-modal .modal-inner-container { min-width: 450px; } - -@container (min-width: 47em) { - .users-directory { - .directory-table { - &__value { - white-space: nowrap; - font-size: var(--font-up-2); - &, - &--user-field { - color: var(--primary-medium); - } - } - } - } -} - -.directory-table { - display: grid; - gap: 0; - width: 100%; - margin-top: 1em; - overflow-x: auto; - - .me { - .directory-table__cell { - &, - &--user-field { - background-color: var(--highlight-low-or-medium); - } - } - } - - &__header, - &__body, - &__row { - display: contents; // we'll be able to remove this with subgrid support - } - - &__column-header, - &__cell, - &__cell--user-field { - display: flex; - border-bottom: 1px solid var(--primary-low); - justify-content: center; - align-items: center; - } - - &__column-header { - display: flex; - align-items: center; - justify-content: center; - white-space: nowrap; - color: var(--primary-medium); - padding: 0.5em; - .d-icon { - margin-right: 0.25em; - } - - &:first-child { - .header-contents { - text-align: left; - } - } - } - - &__cell { - &, - &--user-field { - padding: 0.75em 0.5em; - } - } - - &__value { - white-space: nowrap; - &--user-field { - max-width: 30em; - } - } - - &__label { - display: none; - } - - .d-icon-heart { - font-size: var(--font-down-1); - color: var(--love); - } - - .user-detail { - display: flex; - flex-direction: column; - min-width: 0; // allow content to shrink and hide overflow - } - - .user-info { - display: flex; - min-width: 0; - margin: 0; - width: 100%; - .user-image { - padding-right: 0.5em; - margin-right: 0.5em; - } - .user-detail { - padding: 0; - width: 100%; - @media screen and (max-width: 600px) { - // overrides existing media query - font-size: var(--font-0); - } - @include breakpoint("mobile-medium") { - font-size: var(--font-down-1); - } - } - .title { - margin: 0; - } - } - - .header-contents { - width: 100%; - text-align: center; - } -} - -// using a container query to switch to a flex-based layout -// browsers without support for container queries -// fallback to big horizontal scrolling table - -@container (max-width: 47em) { - .directory-table { - display: flex; - flex-direction: column; - - .me { - background-color: var(--highlight-low-or-medium); - } - - &__label { - display: inline-flex; - color: var(--primary-medium); - padding-right: 0.5em; - align-items: baseline; - align-self: start; - white-space: nowrap; - overflow: hidden; - - span { - // caution: display flex here can interfere with overflow hiding - flex: 0 1 auto; // can shrink if needed - margin-right: 0.25em; - @include ellipsis; - } - - // flexible divider between the label and value - &:after { - flex: 1 1 0; // can grow or shrink, but should be 0 width if needed - color: var(--primary-300); - min-width: 0; - overflow: hidden; - // this needs to be long to account for all possible widths - content: "..................................................................................................................................."; - } - - .d-icon { - font-size: 0.8em; - vertical-align: baseline; - } - } - - &__value { - font-size: var(--font-0); - color: var(--primary); - } - - &__row { - &:first-child { - border-top: 1px solid var(--primary-low); - } - display: grid; - grid-template-columns: repeat(auto-fill, minmax(11em, 1fr)); - border-bottom: 1px solid var(--primary-low); - padding: 0.85em 0.75em 1em; - gap: 0 15%; - } - - &__header { - display: none; - } - - &__cell { - &, - &--user-field { - padding: 0.25em; - border: none; - &:first-child { - width: 100%; - padding: 0.5em 0.25em 1em; - justify-content: start; - // force full width of the cell - grid-column-start: 1; - grid-column-end: -1; - } - } - - &--user-field { - order: 2; - // force full width of the cell - // because we don't know how much content there is - grid-column-start: 1; - grid-column-end: -1; - .directory-table__label { - margin-right: 0.25em; - } - } - } - } -} diff --git a/app/assets/stylesheets/common/base/discourse.scss b/app/assets/stylesheets/common/base/discourse.scss index 25d767cb552..0d1e38e3e9c 100644 --- a/app/assets/stylesheets/common/base/discourse.scss +++ b/app/assets/stylesheets/common/base/discourse.scss @@ -596,8 +596,6 @@ table { #main-outlet { grid-area: content; - container-type: inline-size; - container-name: main-outlet; } } diff --git a/app/assets/stylesheets/common/base/group.scss b/app/assets/stylesheets/common/base/group.scss index d961bedb60e..5ef4362f9bf 100644 --- a/app/assets/stylesheets/common/base/group.scss +++ b/app/assets/stylesheets/common/base/group.scss @@ -25,27 +25,15 @@ display: flex; flex-wrap: wrap; width: 100%; - gap: 0.5em 0; - .bulk-select + input { - margin-left: 0.5em; + input + .group-members-manage { + margin-left: auto; } - input { - margin: 0 auto 0 0; + .group-username-filter { + margin: 0 0 5px 0; + vertical-align: middle; } - - .bulk-select-buttons-wrap { - margin-right: 0.5em; - display: flex; - flex-wrap: wrap; - gap: 0.5em; - } -} - -.group-members-manage { - display: flex; - gap: 0.5em; } .group-info { @@ -130,45 +118,58 @@ table.group-manage-logs { } } -.group-members { - grid-template-columns: 3fr repeat(3, minmax(min-content, 1fr)); +table.group-members { + width: 100%; - &--can-manage { - grid-template-columns: 3fr repeat(4, minmax(min-content, 1fr)) 3em; - @container (max-width: 47em) { - // positioning the member settings button within the same cell - // and avoiding overlap with padding-right on user-info - .group-member, - .member-settings { - grid-row-start: 1; - grid-column-start: 1; - grid-column-end: -1; + th { + text-align: center; + + &.bulk-select { + height: 30px; + width: 30px; + } + + &.bulk-select-buttons { + text-align: left; + white-space: nowrap; + width: 1%; + + .bulk-select-buttons-wrap { + display: flex; } - .member-settings { - margin-left: auto; - } - .user-info { - padding-right: 3.5em; + + .btn { + margin-right: 0.25em; } } + + &.username { + text-align: left; + } } - &.group-members__requests { - grid-template-columns: 3fr repeat(3, minmax(min-content, 1fr)); + td { + color: var(--primary-medium); + padding: 0.8em 0; + text-align: center; + + &.group-member { + text-align: left; + } } - .directory-table__value { - font-size: var(--font-0); - color: var(--primary); - } + .user-info { + display: block; - .group-accept-deny-buttons { - gap: 0.5em; - } + .avatar-flair { + color: var(--primary); + } - @container (max-width: 47em) { - .directory-table__cell.group-owner { - order: 2; + .user-status-message { + img.emoji { + width: 1em; + height: 1em; + } } } } diff --git a/app/assets/stylesheets/mobile/directory.scss b/app/assets/stylesheets/mobile/directory.scss index b1de306686f..8965198565c 100644 --- a/app/assets/stylesheets/mobile/directory.scss +++ b/app/assets/stylesheets/mobile/directory.scss @@ -21,6 +21,35 @@ color: var(--primary-medium); padding: 5px; } + + .user { + border-top: 1px solid var(--primary-low); + padding: 1em; + display: flex; + flex-wrap: wrap; + + .user-info { + width: 100%; + margin-bottom: 1em; + } + + .user-stat { + flex: 1 1 50%; + .value { + font-weight: bold; + &.user-field { + font-size: var(--font-down-1); + } + } + .label { + margin-left: 0.2em; + color: var(--primary-medium); + } + .d-icon-heart { + color: var(--love); + } + } + } } .edit-user-directory-columns-modal .modal-inner-container { diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 515abec9467..4c88a11eee5 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -923,7 +923,6 @@ en: make_all_primary_description: "Make this the primary group for all selected users" remove_all_primary: "Remove as Primary" remove_all_primary_description: "Remove this group as primary" - status: "Status" owner: "Owner" primary: "Primary" forbidden: "You're not allowed to view the members." @@ -5630,7 +5629,6 @@ en: not_found: "Sorry, that username doesn't exist in our system." id_not_found: "Sorry, that user id doesn't exist in our system." active: "Activated" - status: "Status" show_emails: "Show Emails" hide_emails: "Hide Emails" nav: