mirror of
https://github.com/discourse/discourse.git
synced 2024-11-22 15:16:08 +08:00
REFACTOR: user directories without <table>
, second attempt (#20515)
This commit is contained in:
parent
435761ef58
commit
fac78413c8
|
@ -15,7 +15,7 @@ module.exports = {
|
|||
"directory-item-value",
|
||||
"directory-table-header-title",
|
||||
"loading-spinner",
|
||||
"mobile-directory-item-label",
|
||||
"directory-item-label",
|
||||
],
|
||||
},
|
||||
"no-implicit-this": {
|
||||
|
|
|
@ -31,6 +31,21 @@ 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);
|
||||
|
|
|
@ -26,17 +26,29 @@
|
|||
@title={{this.searchHint}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<LoadMore
|
||||
@class="users-list-container"
|
||||
@selector=".users-list tr"
|
||||
@action={{action "loadMore"}}
|
||||
>
|
||||
{{#if this.model}}
|
||||
<table class="table users-list grid" role="table" aria-label={{this.title}}>
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
<ResponsiveTable
|
||||
@className="users-list"
|
||||
@aria-label={{this.title}}
|
||||
@style={{html-safe
|
||||
(concat
|
||||
"grid-template-columns: minmax(min-content, 2fr) repeat("
|
||||
(html-safe this.columnCount)
|
||||
", minmax(min-content, 1fr))"
|
||||
)
|
||||
}}
|
||||
@updates={{this.model.email}}
|
||||
>
|
||||
|
||||
<:header>
|
||||
<TableHeaderToggle
|
||||
@class="directory-table__column-header--username"
|
||||
@field="username"
|
||||
@labelKey="username"
|
||||
@order={{this.order}}
|
||||
|
@ -44,7 +56,11 @@
|
|||
@automatic={{true}}
|
||||
/>
|
||||
<TableHeaderToggle
|
||||
@class={{if this.showEmails "" "hidden"}}
|
||||
@class={{if
|
||||
this.showEmails
|
||||
"directory-table__column-header--email"
|
||||
"hidden"
|
||||
}}
|
||||
@field="email"
|
||||
@labelKey="email"
|
||||
@order={{this.order}}
|
||||
|
@ -99,67 +115,115 @@
|
|||
/>
|
||||
|
||||
{{#if this.siteSettings.must_approve_users}}
|
||||
<th>{{i18n "admin.users.approved"}}</th>
|
||||
<div class="directory-table__column-header">{{i18n
|
||||
"admin.users.approved"
|
||||
}}</div>
|
||||
{{/if}}
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<div class="directory-table__column-header"> </div>
|
||||
|
||||
</:header>
|
||||
|
||||
<:body>
|
||||
{{#each this.model as |user|}}
|
||||
<tr
|
||||
<div
|
||||
class="user
|
||||
{{user.selected}}
|
||||
{{unless user.active 'not-activated'}}"
|
||||
{{unless user.active 'not-activated'}}
|
||||
directory-table__row"
|
||||
>
|
||||
<div class="directory-table__cell username">
|
||||
<a
|
||||
class="avatar"
|
||||
href={{user.path}}
|
||||
data-user-card={{user.username}}
|
||||
>
|
||||
<td class="username">
|
||||
<a href={{user.path}} data-user-card={{user.username}}>
|
||||
{{avatar user imageSize="small"}}
|
||||
</a>
|
||||
<LinkTo
|
||||
@route="adminUser"
|
||||
@model={{user}}
|
||||
>{{user.username}}</LinkTo>
|
||||
<LinkTo @route="adminUser" @model={{user}}>
|
||||
{{user.username}}
|
||||
</LinkTo>
|
||||
{{#if user.staged}}
|
||||
{{d-icon "far-envelope" title="user.staged"}}
|
||||
{{/if}}
|
||||
</td>
|
||||
<td class="email {{if this.showEmails '' 'hidden'}}">
|
||||
</div>
|
||||
<div
|
||||
class="directory-table__cell email
|
||||
{{if this.showEmails '' 'hidden'}}"
|
||||
>
|
||||
<span class="directory-table__value">
|
||||
{{~user.email~}}
|
||||
</td>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{{#if user.last_emailed_at}}
|
||||
<td class="last-emailed" title={{raw-date user.last_emailed_at}}>
|
||||
<div class="label">{{i18n "admin.users.last_emailed"}}</div>
|
||||
<div>{{format-duration user.last_emailed_age}}</div>
|
||||
</td>
|
||||
<div
|
||||
class="directory-table__cell last-emailed"
|
||||
title={{raw-date user.last_emailed_at}}
|
||||
>
|
||||
<span class="directory-table__label">
|
||||
<span>{{i18n "admin.users.last_emailed"}}</span>
|
||||
</span>
|
||||
<span class="directory-table__value">
|
||||
{{format-duration user.last_emailed_age}}
|
||||
</span>
|
||||
</div>
|
||||
{{else}}
|
||||
<td class="last-emailed">
|
||||
<div class="label">{{i18n "admin.users.last_emailed"}}</div>
|
||||
<div>{{format-duration user.last_emailed_age}}</div>
|
||||
</td>
|
||||
<div class="directory-table__cell last-emailed">
|
||||
<span class="directory-table__label">
|
||||
<span>{{i18n "admin.users.last_emailed"}}</span>
|
||||
</span>
|
||||
<span class="directory-table__value">
|
||||
{{format-duration user.last_emailed_age}}
|
||||
</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<td class="last-seen" title={{raw-date user.last_seen_at}}>
|
||||
<div class="label">{{i18n "last_seen"}}</div>
|
||||
<div>{{format-duration user.last_seen_age}}</div>
|
||||
</td>
|
||||
<td class="topics-entered">
|
||||
<div class="label">{{i18n "admin.user.topics_entered"}}</div>
|
||||
<div>{{number user.topics_entered}}</div>
|
||||
</td>
|
||||
<td class="posts-read">
|
||||
<div class="label">{{i18n "admin.user.posts_read_count"}}</div>
|
||||
<div>{{number user.posts_read_count}}</div>
|
||||
</td>
|
||||
<td class="time-read">
|
||||
<div class="label">{{i18n "admin.user.time_read"}}</div>
|
||||
<div>{{format-duration user.time_read}}</div>
|
||||
</td>
|
||||
|
||||
<td class="created" title={{raw-date user.created_at}}>
|
||||
<div class="label">{{i18n "created"}}</div>
|
||||
<div>{{format-duration user.created_at_age}}</div>
|
||||
</td>
|
||||
<div
|
||||
class="directory-table__cell last-seen"
|
||||
title={{raw-date user.last_seen_at}}
|
||||
>
|
||||
<span class="directory-table__label">
|
||||
<span>{{i18n "last_seen"}}</span>
|
||||
</span>
|
||||
<span class="directory-table__value">
|
||||
{{format-duration user.last_seen_age}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="directory-table__cell topics-entered">
|
||||
<span class="directory-table__label">
|
||||
<span>{{i18n "admin.user.topics_entered"}}</span>
|
||||
</span>
|
||||
<span class="directory-table__value">
|
||||
{{number user.topics_entered}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="directory-table__cell posts-read">
|
||||
<span class="directory-table__label">
|
||||
<span>{{i18n "admin.user.posts_read_count"}}</span>
|
||||
</span>
|
||||
<span class="directory-table__value">
|
||||
{{number user.posts_read_count}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="directory-table__cell time-read">
|
||||
<span class="directory-table__label">
|
||||
<span>{{i18n "admin.user.time_read"}}</span>
|
||||
</span>
|
||||
<span class="directory-table__value">
|
||||
{{format-duration user.time_read}}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="directory-table__cell created"
|
||||
title={{raw-date user.created_at}}
|
||||
>
|
||||
<span class="directory-table__label">
|
||||
<span>{{i18n "created"}}</span>
|
||||
</span>
|
||||
<span class="directory-table__value">
|
||||
{{format-duration user.created_at_age}}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<PluginOutlet
|
||||
@name="admin-users-list-td-after"
|
||||
|
@ -167,10 +231,21 @@
|
|||
/>
|
||||
|
||||
{{#if this.siteSettings.must_approve_users}}
|
||||
<td>{{i18n-yes-no user.approved}}</td>
|
||||
<div class="directory-table__cell">
|
||||
<span class="directory-table__label">
|
||||
<span>{{i18n "admin.users.approved"}}</span>
|
||||
</span>
|
||||
<span class="directory-table__value">
|
||||
{{i18n-yes-no user.approved}}
|
||||
</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<td class="user-status">
|
||||
<div class="directory-table__cell user-status">
|
||||
<span class="directory-table__label">
|
||||
<span>{{i18n "admin.users.status"}}</span>
|
||||
</span>
|
||||
<span class="directory-table__value">
|
||||
{{#if user.admin}}
|
||||
{{d-icon "shield-alt" title="admin.title"}}
|
||||
{{/if}}
|
||||
|
@ -180,16 +255,19 @@
|
|||
{{#if user.second_factor_enabled}}
|
||||
{{d-icon "lock" title="admin.user.second_factor_enabled"}}
|
||||
{{/if}}
|
||||
</span>
|
||||
<PluginOutlet
|
||||
@name="admin-users-list-icon"
|
||||
@connectorTagName="div"
|
||||
@outletArgs={{hash user=user query=this.query}}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</:body>
|
||||
|
||||
</ResponsiveTable>
|
||||
|
||||
<ConditionalLoadingSpinner @condition={{this.refreshing}} />
|
||||
{{else}}
|
||||
<p>{{i18n "search.no_results"}}</p>
|
||||
|
|
|
@ -1,16 +1,38 @@
|
|||
<td><UserInfo @user={{this.item.user}} /></td>
|
||||
<div class="directory-table__cell">
|
||||
<UserInfo @user={{this.item.user}} />
|
||||
</div>
|
||||
|
||||
{{#each this.columns as |column|}}
|
||||
<td>
|
||||
{{#if (directory-column-is-user-field column=column)}}
|
||||
<div class="directory-table__cell--user-field">
|
||||
<span class="directory-table__label">
|
||||
<span>{{column.name}}</span>
|
||||
</span>
|
||||
{{directory-item-user-field-value item=this.item column=column}}
|
||||
</div>
|
||||
{{else}}
|
||||
{{directory-item-value item=this.item column=column}}
|
||||
<div class="directory-table__cell">
|
||||
<span class="directory-table__label">
|
||||
<span>
|
||||
{{#if column.icon}}
|
||||
{{d-icon column.icon}}
|
||||
{{/if}}
|
||||
</td>
|
||||
{{directory-item-label item=this.item column=column}}
|
||||
</span>
|
||||
</span>
|
||||
{{directory-item-value item=this.item column=column}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{/each}}
|
||||
|
||||
{{#if this.showTimeRead}}
|
||||
<td><span class="time-read">{{format-duration
|
||||
this.item.time_read
|
||||
}}</span></td>
|
||||
<div class="directory-table__cell time-read">
|
||||
<span class="directory-table__label">
|
||||
<span>{{i18n "directory.time_read"}}</span>
|
||||
</span>
|
||||
<span class="directory-table__value">
|
||||
{{format-duration this.item.time_read}}
|
||||
</span>
|
||||
</div>
|
||||
{{/if}}
|
|
@ -2,7 +2,8 @@ import Component from "@ember/component";
|
|||
import { propertyEqual } from "discourse/lib/computed";
|
||||
|
||||
export default Component.extend({
|
||||
tagName: "tr",
|
||||
tagName: "div",
|
||||
classNames: ["directory-table__row"],
|
||||
classNameBindings: ["me"],
|
||||
me: propertyEqual("item.user.id", "currentUser.id"),
|
||||
columns: null,
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
<div class="directory-table-top-scroll">
|
||||
<div class="directory-table-top-scroll-fake-content"></div>
|
||||
</div>
|
||||
|
||||
<div class="directory-table-container">
|
||||
<table class="directory-table">
|
||||
<thead>
|
||||
<ResponsiveTable>
|
||||
<:header>
|
||||
<TableHeaderToggle
|
||||
@field="username"
|
||||
@order={{this.order}}
|
||||
|
@ -23,10 +18,14 @@
|
|||
{{/each}}
|
||||
|
||||
{{#if this.showTimeRead}}
|
||||
<th>{{i18n "directory.time_read"}}</th>
|
||||
<div class="directory-table__column-header">
|
||||
<div class="header-contents">
|
||||
{{i18n "directory.time_read"}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</thead>
|
||||
<tbody>
|
||||
</:header>
|
||||
<:body>
|
||||
{{#each this.items as |item|}}
|
||||
<DirectoryItem
|
||||
@item={{item}}
|
||||
|
@ -34,6 +33,5 @@
|
|||
@showTimeRead={{this.showTimeRead}}
|
||||
/>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</:body>
|
||||
</ResponsiveTable>
|
|
@ -2,109 +2,31 @@ 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._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);
|
||||
this._table.style.gridTemplateColumns = `minmax(13em, 3fr) repeat(${this._columnCount}, minmax(max-content, 1fr))`;
|
||||
},
|
||||
|
||||
@action
|
||||
setActiveHeader(header) {
|
||||
// After render, scroll table left to ensure the order by column is visible
|
||||
if (!this._tableContainer) {
|
||||
this.set(
|
||||
"_tableContainer",
|
||||
document.querySelector(".directory-table-container")
|
||||
);
|
||||
if (!this._table) {
|
||||
this.set("_table", document.querySelector(".directory-table"));
|
||||
}
|
||||
const scrollPixels =
|
||||
header.offsetLeft +
|
||||
header.offsetWidth +
|
||||
10 -
|
||||
this._tableContainer.offsetWidth;
|
||||
header.offsetLeft + header.offsetWidth + 10 - this._table.offsetWidth;
|
||||
|
||||
if (scrollPixels > 0) {
|
||||
this._tableContainer.scrollLeft = scrollPixels;
|
||||
this._table.scrollLeft = scrollPixels;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<div class="directory-table-container">
|
||||
<div class="directory-table-top-scroll" {{on "scroll" this.onTopScroll}}>
|
||||
<div class="directory-table-top-scroll-fake-content"></div>
|
||||
</div>
|
||||
<div
|
||||
class={{concat-class "directory-table" @className}}
|
||||
role="table"
|
||||
aria-label={{@ariaLabel}}
|
||||
style={{@style}}
|
||||
{{did-insert this.checkScroll}}
|
||||
{{did-update this.checkScroll @updates}}
|
||||
{{on-resize this.checkScroll}}
|
||||
{{on "scroll" this.onBottomScroll}}
|
||||
>
|
||||
<div class="directory-table__header">
|
||||
{{yield to="header"}}
|
||||
</div>
|
||||
<div class="directory-table__body">
|
||||
{{yield to="body"}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,51 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
<span
|
||||
<div
|
||||
class="header-contents"
|
||||
id={{this.id}}
|
||||
role="button"
|
||||
|
@ -6,6 +6,9 @@
|
|||
aria-label={{this.ariaLabel}}
|
||||
aria-pressed={{this.pressedState}}
|
||||
>
|
||||
|
||||
{{yield}}
|
||||
<span class="text">
|
||||
{{directory-table-header-title
|
||||
field=this.field
|
||||
labelKey=this.labelKey
|
||||
|
@ -13,4 +16,5 @@
|
|||
translated=this.translated
|
||||
}}
|
||||
{{this.chevronIcon}}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
|
@ -6,8 +6,8 @@ import discourseComputed from "discourse-common/utils/decorators";
|
|||
import I18n from "I18n";
|
||||
|
||||
export default Component.extend({
|
||||
tagName: "th",
|
||||
classNames: ["sortable"],
|
||||
tagName: "div",
|
||||
classNames: ["directory-table__column-header", "sortable"],
|
||||
attributeBindings: ["title", "colspan", "ariaSort:aria-sort", "role"],
|
||||
role: "columnheader",
|
||||
labelKey: null,
|
||||
|
|
|
@ -3,7 +3,7 @@ import { number } from "discourse/lib/formatter";
|
|||
import { registerUnbound } from "discourse-common/lib/helpers";
|
||||
import I18n from "I18n";
|
||||
|
||||
registerUnbound("mobile-directory-item-label", function (args) {
|
||||
registerUnbound("directory-item-label", function (args) {
|
||||
// Args should include key/values { item, column }
|
||||
const count = args.item.get(args.column.name);
|
||||
const translationPrefix =
|
||||
|
@ -14,7 +14,9 @@ registerUnbound("mobile-directory-item-label", function (args) {
|
|||
registerUnbound("directory-item-value", function (args) {
|
||||
// Args should include key/values { item, column }
|
||||
return htmlSafe(
|
||||
`<span class='number'>${number(args.item.get(args.column.name))}</span>`
|
||||
`<span class='directory-table__value'>${number(
|
||||
args.item.get(args.column.name)
|
||||
)}</span>`
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -25,7 +27,9 @@ registerUnbound("directory-item-user-field-value", function (args) {
|
|||
? args.item.user.user_fields[args.column.user_field_id]
|
||||
: null;
|
||||
const content = value || "-";
|
||||
return htmlSafe(`<span class='user-field-value'>${content}</span>`);
|
||||
return htmlSafe(
|
||||
`<span class='directory-table__value--user-field'>${content}</span>`
|
||||
);
|
||||
});
|
||||
|
||||
registerUnbound("directory-column-is-automatic", function (args) {
|
||||
|
|
|
@ -1,5 +1,15 @@
|
|||
<section class="user-content">
|
||||
<div class="group-members-actions">
|
||||
|
||||
{{#if this.canManageGroup}}
|
||||
<DButton
|
||||
@class="bulk-select"
|
||||
@icon="list"
|
||||
@action={{action "toggleBulkSelect"}}
|
||||
@title="topics.bulk.toggle"
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.model.can_see_members}}
|
||||
<TextField
|
||||
@value={{this.filterInput}}
|
||||
|
@ -10,6 +20,35 @@
|
|||
{{/if}}
|
||||
|
||||
{{#if this.canManageGroup}}
|
||||
|
||||
{{#if this.isBulk}}
|
||||
<span class="bulk-select-buttons-wrap">
|
||||
{{#if this.bulkSelection}}
|
||||
<BulkGroupMemberDropdown
|
||||
@bulkSelection={{this.bulkSelection}}
|
||||
@canAdminGroup={{this.model.can_admin_group}}
|
||||
@canEditGroup={{this.model.can_edit_group}}
|
||||
@onChange={{action "actOnSelection" this.bulkSelection}}
|
||||
/>
|
||||
|
||||
<DButton
|
||||
@action={{action "bulkClearAll"}}
|
||||
@label="topics.bulk.clear_all"
|
||||
@icon="far-square"
|
||||
@class="bulk-select-clear"
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
<DButton
|
||||
@action={{action "bulkSelectAll"}}
|
||||
@label="topics.bulk.select_all"
|
||||
@icon="check-square"
|
||||
@class="bulk-select-all"
|
||||
/>
|
||||
|
||||
</span>
|
||||
{{/if}}
|
||||
|
||||
<div class="group-members-manage">
|
||||
<DButton
|
||||
@icon="plus"
|
||||
|
@ -31,44 +70,17 @@
|
|||
</div>
|
||||
|
||||
{{#if this.hasMembers}}
|
||||
<LoadMore @selector=".group-members tr" @action={{action "loadMore"}}>
|
||||
<table
|
||||
class={{if this.isBulk "group-members sticky-header" "group-members"}}
|
||||
<LoadMore
|
||||
@selector=".group-members .directory-table-row"
|
||||
@action={{action "loadMore"}}
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="bulk-select">
|
||||
{{#if this.canManageGroup}}
|
||||
<FlatButton
|
||||
@class="bulk-select"
|
||||
@icon="list"
|
||||
@action={{action "toggleBulkSelect"}}
|
||||
@title="topics.bulk.toggle"
|
||||
/>
|
||||
{{/if}}
|
||||
</th>
|
||||
{{#if this.isBulk}}
|
||||
<th class="bulk-select-buttons">
|
||||
<span class="bulk-select-buttons-wrap">
|
||||
{{#if this.bulkSelection}}
|
||||
<BulkGroupMemberDropdown
|
||||
@bulkSelection={{this.bulkSelection}}
|
||||
@canAdminGroup={{this.model.can_admin_group}}
|
||||
@canEditGroup={{this.model.can_edit_group}}
|
||||
@onChange={{action "actOnSelection" this.bulkSelection}}
|
||||
/>
|
||||
{{/if}}
|
||||
<DButton
|
||||
@action={{action "bulkSelectAll"}}
|
||||
@label="topics.bulk.select_all"
|
||||
/>
|
||||
<DButton
|
||||
@action={{action "bulkClearAll"}}
|
||||
@label="topics.bulk.clear_all"
|
||||
/>
|
||||
</span>
|
||||
</th>
|
||||
{{/if}}
|
||||
|
||||
<ResponsiveTable
|
||||
@className="group-members
|
||||
{{if this.isBulk 'sticky-header' ''}}
|
||||
{{if this.canManageGroup 'group-members--can-manage' ''}}"
|
||||
>
|
||||
<:header>
|
||||
<TableHeaderToggle
|
||||
@order={{this.order}}
|
||||
@asc={{this.asc}}
|
||||
|
@ -78,7 +90,13 @@
|
|||
@automatic={{true}}
|
||||
@colspan="2"
|
||||
/>
|
||||
|
||||
{{#if this.canManageGroup}}
|
||||
<div class="directory-table__column-header"></div>
|
||||
{{/if}}
|
||||
|
||||
<TableHeaderToggle
|
||||
@class="directory-table__column-header"
|
||||
@order={{this.order}}
|
||||
@asc={{this.asc}}
|
||||
@field="added_at"
|
||||
|
@ -86,6 +104,7 @@
|
|||
@automatic={{true}}
|
||||
/>
|
||||
<TableHeaderToggle
|
||||
@class="directory-table__column-header"
|
||||
@order={{this.order}}
|
||||
@asc={{this.asc}}
|
||||
@field="last_posted_at"
|
||||
|
@ -93,39 +112,49 @@
|
|||
@automatic={{true}}
|
||||
/>
|
||||
<TableHeaderToggle
|
||||
@class="directory-table__column-header"
|
||||
@order={{this.order}}
|
||||
@asc={{this.asc}}
|
||||
@field="last_seen_at"
|
||||
@labelKey="last_seen"
|
||||
@automatic={{true}}
|
||||
/>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{{#if this.canManageGroup}}
|
||||
<div class="directory-table__column-header"></div>
|
||||
{{/if}}
|
||||
</:header>
|
||||
|
||||
<:body>
|
||||
{{#each this.model.members as |m|}}
|
||||
<tr>
|
||||
<div class="directory-table__row">
|
||||
|
||||
<div class="directory-table__cell group-member" colspan="2">
|
||||
{{#if this.canManageGroup}}
|
||||
{{#if this.isBulk}}
|
||||
<td class="bulk-select">
|
||||
<Input
|
||||
@type="checkbox"
|
||||
class="bulk-select"
|
||||
{{on "click" (action "selectMember" m)}}
|
||||
/>
|
||||
</td>
|
||||
{{/if}}
|
||||
|
||||
<td class="group-member" colspan="2">
|
||||
{{/if}}
|
||||
<UserInfo
|
||||
@user={{m}}
|
||||
@skipName={{this.skipName}}
|
||||
@showStatus={{true}}
|
||||
@showStatusTooltip={{true}}
|
||||
/>
|
||||
</td>
|
||||
</div>
|
||||
|
||||
<td class="group-owner">
|
||||
{{#if this.canManageGroup}}
|
||||
<div class="directory-table__cell group-owner">
|
||||
{{#if (or m.owner m.primary)}}
|
||||
<span class="directory-table__label">
|
||||
<span>{{i18n "groups.members.status"}}</span>
|
||||
</span>
|
||||
{{/if}}
|
||||
<span class="directory-table__value">
|
||||
{{#if m.owner}}
|
||||
{{d-icon "shield-alt"}}
|
||||
{{i18n "groups.members.owner"}}<br />
|
||||
|
@ -133,32 +162,56 @@
|
|||
{{#if m.primary}}
|
||||
{{i18n "groups.members.primary"}}
|
||||
{{/if}}
|
||||
</td>
|
||||
<td>
|
||||
<span class="text">{{bound-date m.added_at}}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="text">{{bound-date m.last_posted_at}}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="text">{{bound-date m.last_seen_at}}</span>
|
||||
</td>
|
||||
</span>
|
||||
|
||||
<td>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="directory-table__cell">
|
||||
<span class="directory-table__label">
|
||||
<span>{{i18n "groups.member_added"}}</span>
|
||||
</span>
|
||||
<span class="directory-table__value">
|
||||
{{bound-date m.added_at}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="directory-table__cell">
|
||||
{{#if m.last_posted_at}}
|
||||
<span class="directory-table__label">
|
||||
<span>{{i18n "last_post"}}</span>
|
||||
</span>
|
||||
{{/if}}
|
||||
<span class="directory-table__value">
|
||||
{{bound-date m.last_posted_at}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="directory-table__cell">
|
||||
{{#if m.last_seen_at}}
|
||||
<span class="directory-table__label">
|
||||
<span>{{i18n "last_seen"}}</span>
|
||||
</span>
|
||||
{{/if}}
|
||||
<span class="directory-table__value">
|
||||
{{bound-date m.last_seen_at}}
|
||||
</span>
|
||||
</div>
|
||||
{{#if this.canManageGroup}}
|
||||
<div class="directory-table__cell member-settings">
|
||||
<GroupMemberDropdown
|
||||
@member={{m}}
|
||||
@canAdminGroup={{this.model.can_admin_group}}
|
||||
@canEditGroup={{this.model.can_edit_group}}
|
||||
@onChange={{action "actOnGroup" m}}
|
||||
@options={{hash placementStrategy="absolute"}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{! group parameter is used by plugins }}
|
||||
</td>
|
||||
</tr>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</:body>
|
||||
|
||||
</ResponsiveTable>
|
||||
|
||||
</LoadMore>
|
||||
|
||||
<ConditionalLoadingSpinner @condition={{this.loading}} />
|
||||
|
|
|
@ -9,10 +9,14 @@
|
|||
</div>
|
||||
|
||||
{{#if this.hasRequesters}}
|
||||
<LoadMore @selector=".group-members tr" @action={{action "loadMore"}}>
|
||||
<table class="group-members">
|
||||
<thead>
|
||||
<LoadMore
|
||||
@selector=".group-members .directory-table__row"
|
||||
@action={{action "loadMore"}}
|
||||
>
|
||||
<ResponsiveTable @className="group-members group-members__requests">
|
||||
<:header>
|
||||
<TableHeaderToggle
|
||||
@class="username"
|
||||
@order={{this.order}}
|
||||
@asc={{this.asc}}
|
||||
@field="username_lower"
|
||||
|
@ -26,22 +30,34 @@
|
|||
@labelKey="groups.member_requested"
|
||||
@automatic={{true}}
|
||||
/>
|
||||
<th>{{i18n "groups.requests.reason"}}</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<div class="directory-table__column-header">{{i18n
|
||||
"groups.requests.reason"
|
||||
}}</div>
|
||||
<div class="directory-table__column-header"></div>
|
||||
</:header>
|
||||
<:body>
|
||||
{{#each this.model.requesters as |m|}}
|
||||
<tr>
|
||||
<td class="group-member">
|
||||
<div class="directory-table__row">
|
||||
<div class="directory-table__cell group-member">
|
||||
<UserInfo @user={{m}} @skipName={{this.skipName}} />
|
||||
</td>
|
||||
<td>
|
||||
<span class="text">{{bound-date m.requested_at}}</span>
|
||||
</td>
|
||||
<td>{{m.reason}}</td>
|
||||
<td>
|
||||
</div>
|
||||
<div class="directory-table__cell">
|
||||
<span class="directory-table__label">
|
||||
<span>{{i18n "groups.member_requested"}}</span>
|
||||
</span>
|
||||
<span class="directory-table__value">
|
||||
<span>{{bound-date m.requested_at}}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="directory-table__cell">
|
||||
<span class="directory-table__label">
|
||||
<span>{{i18n "groups.requests.reason"}}</span>
|
||||
</span>
|
||||
<span class="directory-table__value">
|
||||
{{m.reason}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="directory-table__cell group-accept-deny-buttons">
|
||||
{{#if m.request_undone}}
|
||||
{{i18n "groups.requests.undone"}}
|
||||
{{else if m.request_accepted}}
|
||||
|
@ -67,17 +83,14 @@
|
|||
@class="btn-danger"
|
||||
/>
|
||||
{{/if}}
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</:body>
|
||||
</ResponsiveTable>
|
||||
</LoadMore>
|
||||
|
||||
<ConditionalLoadingSpinner @condition={{this.loading}} />
|
||||
{{else}}
|
||||
<div>{{i18n "groups.empty.requests"}}</div>
|
||||
{{/if}}
|
||||
|
||||
</section>
|
|
@ -1,37 +0,0 @@
|
|||
<UserInfo @user={{this.item.user}} />
|
||||
|
||||
{{#each this.columns as |column|}}
|
||||
{{#if (directory-column-is-user-field column=column)}}
|
||||
{{#if (get this.item.user.user_fields column.user_field_id)}}
|
||||
<div class="user-stat">
|
||||
<span class="value user-field">
|
||||
{{directory-item-user-field-value item=this.item column=column}}
|
||||
</span>
|
||||
<span class="label">
|
||||
{{column.name}}
|
||||
</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{else}}
|
||||
<div class="user-stat">
|
||||
<span class="value">
|
||||
{{directory-item-value item=this.item column=column}}
|
||||
</span>
|
||||
<span class="label">
|
||||
{{#if column.icon}}
|
||||
{{d-icon column.icon}}
|
||||
{{/if}}
|
||||
{{mobile-directory-item-label item=this.item column=column}}
|
||||
</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
|
||||
{{#if this.showTimeRead}}
|
||||
<UserStat
|
||||
@value={{this.item.time_read}}
|
||||
@label="directory.time_read"
|
||||
@type="duration"
|
||||
/>
|
||||
{{/if}}
|
|
@ -1,80 +0,0 @@
|
|||
<LoadMore @selector=".directory .user" @action={{action "loadMore"}}>
|
||||
<div class="container">
|
||||
<div class="users-directory directory">
|
||||
<span>
|
||||
<PluginOutlet
|
||||
@name="users-top"
|
||||
@connectorTagName="div"
|
||||
@outletArgs={{hash model=this.model}}
|
||||
/>
|
||||
</span>
|
||||
|
||||
<div class="directory-controls">
|
||||
<PeriodChooser
|
||||
@period={{this.period}}
|
||||
@onChange={{action (mut this.period)}}
|
||||
@fullDay={{false}}
|
||||
/>
|
||||
{{#if this.lastUpdatedAt}}
|
||||
<div class="directory-last-updated">
|
||||
{{i18n "directory.last_updated"}}
|
||||
{{this.lastUpdatedAt}}
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="inline-form full-width">
|
||||
<Input
|
||||
@value={{readonly this.nameInput}}
|
||||
placeholder={{i18n "directory.filter_name"}}
|
||||
class="filter-name no-blur"
|
||||
{{on
|
||||
"input"
|
||||
(action "onUsernameFilterChanged" value="target.value")
|
||||
}}
|
||||
/>
|
||||
<ComboBox
|
||||
@class="directory-group-selector"
|
||||
@value={{this.group}}
|
||||
@content={{this.groupOptions}}
|
||||
@onChange={{action this.groupChanged}}
|
||||
@options={{hash none="directory.group.all"}}
|
||||
/>
|
||||
{{#if this.currentUser.staff}}
|
||||
<DButton
|
||||
@icon="wrench"
|
||||
@action={{action "showEditColumnsModal"}}
|
||||
@class="btn-default open-edit-columns-btn"
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
<PluginOutlet
|
||||
@name="users-directory-controls"
|
||||
@outletArgs={{hash model=this.model}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ConditionalLoadingSpinner @condition={{this.isLoading}}>
|
||||
{{#if this.model.length}}
|
||||
<div class="total-rows">{{i18n
|
||||
"directory.total_rows"
|
||||
count=this.model.totalRows
|
||||
}}</div>
|
||||
{{#each this.model as |item|}}
|
||||
<DirectoryItem
|
||||
@tagName="div"
|
||||
@class="user"
|
||||
@item={{item}}
|
||||
@columns={{this.columns}}
|
||||
@showTimeRead={{this.showTimeRead}}
|
||||
/>
|
||||
{{/each}}
|
||||
|
||||
<ConditionalLoadingSpinner @condition={{this.model.loadingMore}} />
|
||||
{{else}}
|
||||
<div class="clearfix"></div>
|
||||
<p>{{i18n "directory.no_results"}}</p>
|
||||
{{/if}}
|
||||
</ConditionalLoadingSpinner>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</LoadMore>
|
|
@ -68,7 +68,8 @@ acceptance("Admin - Users List", function (needs) {
|
|||
await click(".hide-emails");
|
||||
|
||||
assert.strictEqual(
|
||||
query(".users-list .user:nth-child(1) .email").innerText,
|
||||
query(".users-list .user:nth-child(1) .email .directory-table__value")
|
||||
.innerText,
|
||||
"",
|
||||
"hides the emails"
|
||||
);
|
||||
|
|
|
@ -20,7 +20,7 @@ acceptance("Group Members - Anonymous", function () {
|
|||
1,
|
||||
"it displays the group's avatar flair"
|
||||
);
|
||||
assert.ok(exists(".group-members tr"), "it lists group members");
|
||||
assert.ok(exists(".group-members .group-member"), "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-buttons button:nth-child(1)");
|
||||
await click(".bulk-select-all");
|
||||
|
||||
assert.ok(
|
||||
exists(".bulk-select-buttons-wrap details"),
|
||||
|
|
|
@ -12,7 +12,7 @@ acceptance("Managing Group Category Notification Defaults", function () {
|
|||
await visit("/g/discourse/manage/categories");
|
||||
|
||||
assert.ok(
|
||||
exists(".group-members tr"),
|
||||
exists(".group-members .group-member"),
|
||||
"it should redirect to members page for an anonymous user"
|
||||
);
|
||||
});
|
||||
|
|
|
@ -12,7 +12,7 @@ acceptance("Managing Group Profile", function () {
|
|||
await visit("/g/discourse/manage/profile");
|
||||
|
||||
assert.ok(
|
||||
exists(".group-members tr"),
|
||||
exists(".group-members .group-member"),
|
||||
"it should redirect to members page for an anonymous user"
|
||||
);
|
||||
});
|
||||
|
|
|
@ -12,7 +12,7 @@ acceptance("Managing Group Tag Notification Defaults", function () {
|
|||
await visit("/g/discourse/manage/tags");
|
||||
|
||||
assert.ok(
|
||||
exists(".group-members tr"),
|
||||
exists(".group-members .group-member"),
|
||||
"it should redirect to members page for an anonymous user"
|
||||
);
|
||||
});
|
||||
|
|
|
@ -89,37 +89,49 @@ acceptance("Group Requests", function (needs) {
|
|||
test("Group Requests", async function (assert) {
|
||||
await visit("/g/Macdonald/requests");
|
||||
|
||||
assert.strictEqual(count(".group-members tr"), 2);
|
||||
assert.strictEqual(count(".group-members .group-member"), 2);
|
||||
assert.strictEqual(
|
||||
query(".group-members tr:first-child td:nth-child(1)")
|
||||
query(".group-members .directory-table__row:first-child .user-detail")
|
||||
.innerText.trim()
|
||||
.replace(/\s+/g, " "),
|
||||
"eviltrout Robin Ward"
|
||||
);
|
||||
assert.strictEqual(
|
||||
query(".group-members tr:first-child td:nth-child(3)").innerText.trim(),
|
||||
query(
|
||||
".group-members .directory-table__row:first-child .directory-table__cell:nth-child(3)"
|
||||
).innerText.trim(),
|
||||
"Please accept my membership request."
|
||||
);
|
||||
assert.strictEqual(
|
||||
query(".group-members tr:first-child .btn-primary").innerText.trim(),
|
||||
query(
|
||||
".group-members .directory-table__row:first-child .btn-primary"
|
||||
).innerText.trim(),
|
||||
"Accept"
|
||||
);
|
||||
assert.strictEqual(
|
||||
query(".group-members tr:first-child .btn-danger").innerText.trim(),
|
||||
query(
|
||||
".group-members .directory-table__row:first-child .btn-danger"
|
||||
).innerText.trim(),
|
||||
"Deny"
|
||||
);
|
||||
|
||||
await click(".group-members tr:first-child .btn-primary");
|
||||
await click(
|
||||
".group-members .directory-table__row:first-child .btn-primary"
|
||||
);
|
||||
assert.ok(
|
||||
query(".group-members tr:first-child td:nth-child(4)")
|
||||
query(
|
||||
".group-members .directory-table__row:first-child .directory-table__cell:nth-child(4)"
|
||||
)
|
||||
.innerText.trim()
|
||||
.startsWith("accepted")
|
||||
);
|
||||
assert.deepEqual(requests, [["19", "true"]]);
|
||||
|
||||
await click(".group-members tr:last-child .btn-danger");
|
||||
await click(".group-members .directory-table__row:last-child .btn-danger");
|
||||
assert.strictEqual(
|
||||
query(".group-members tr:last-child td:nth-child(4)").innerText.trim(),
|
||||
query(
|
||||
".group-members .directory-table__row:last-child .directory-table__cell:nth-child(4)"
|
||||
).innerText.trim(),
|
||||
"denied"
|
||||
);
|
||||
assert.deepEqual(requests, [
|
||||
|
|
|
@ -7,6 +7,9 @@ acceptance("User Directory - Mobile", function (needs) {
|
|||
|
||||
test("Visit Page", async function (assert) {
|
||||
await visit("/u");
|
||||
assert.ok(exists(".directory .user"), "has a list of users");
|
||||
assert.ok(
|
||||
exists(".directory .directory-table__row"),
|
||||
"has a list of users"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,7 +14,10 @@ acceptance("User Directory", function () {
|
|||
document.body.classList.contains("users-page"),
|
||||
"has the body class"
|
||||
);
|
||||
assert.ok(exists(".directory table tr"), "has a list of users");
|
||||
assert.ok(
|
||||
exists(".directory .directory-table .directory-table__row"),
|
||||
"has a list of users"
|
||||
);
|
||||
});
|
||||
|
||||
test("Visit All Time", async function (assert) {
|
||||
|
@ -28,7 +31,10 @@ acceptance("User Directory", function () {
|
|||
document.body.classList.contains("users-page"),
|
||||
"has the body class"
|
||||
);
|
||||
assert.ok(exists(".directory table tr"), "has a list of users");
|
||||
assert.ok(
|
||||
exists(".directory .directory-table .directory-table__row"),
|
||||
"has a list of users"
|
||||
);
|
||||
});
|
||||
|
||||
test("Visit With Group Filter", async function (assert) {
|
||||
|
@ -37,27 +43,27 @@ acceptance("User Directory", function () {
|
|||
document.body.classList.contains("users-page"),
|
||||
"has the body class"
|
||||
);
|
||||
assert.ok(exists(".directory table tr"), "has a list of users");
|
||||
assert.ok(
|
||||
exists(".directory .directory-table .directory-table__row"),
|
||||
"has a list of users"
|
||||
);
|
||||
});
|
||||
|
||||
test("Custom user fields are present", async function (assert) {
|
||||
await visit("/u");
|
||||
|
||||
const firstRow = query(".users-directory table tr");
|
||||
const columnData = firstRow.querySelectorAll("td");
|
||||
const favoriteColorTd = columnData[columnData.length - 1];
|
||||
|
||||
assert.strictEqual(
|
||||
favoriteColorTd.querySelector("span").textContent,
|
||||
"Blue"
|
||||
const firstRowUserField = query(
|
||||
".directory .directory-table__body .directory-table__row:first-child .directory-table__value--user-field"
|
||||
);
|
||||
|
||||
assert.strictEqual(firstRowUserField.textContent, "Blue");
|
||||
});
|
||||
|
||||
test("Can sort table via keyboard", async function (assert) {
|
||||
await visit("/u");
|
||||
|
||||
const secondHeading =
|
||||
".users-directory table th:nth-child(2) .header-contents";
|
||||
".users-directory .directory-table__header div:nth-child(2) .header-contents";
|
||||
|
||||
await triggerKeyEvent(secondHeading, "keypress", "Enter");
|
||||
|
||||
|
|
|
@ -802,19 +802,15 @@ section.details {
|
|||
}
|
||||
}
|
||||
|
||||
tr.not-activated {
|
||||
td,
|
||||
td a,
|
||||
td a:visited {
|
||||
.directory-table {
|
||||
.not-activated {
|
||||
.directory-table__cell {
|
||||
&,
|
||||
a,
|
||||
a:visited {
|
||||
color: #bbb;
|
||||
}
|
||||
}
|
||||
|
||||
.details.not-activated {
|
||||
.username .value,
|
||||
.email .value a,
|
||||
.email .value a:visited {
|
||||
color: #bbb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -99,49 +99,55 @@
|
|||
}
|
||||
|
||||
.admin-users-list {
|
||||
td.username {
|
||||
@include ellipsis;
|
||||
overflow-wrap: break-word;
|
||||
.directory-table__cell {
|
||||
&.username {
|
||||
justify-content: start;
|
||||
}
|
||||
@media screen and (max-width: 970px) and (min-width: 768px) {
|
||||
td.username {
|
||||
max-width: 23vw; // Prevents horizontal scroll down to 768px
|
||||
}
|
||||
td.email {
|
||||
max-width: 28vw; // Prevents horizontal scroll down to 768px
|
||||
overflow-wrap: break-word;
|
||||
&.email {
|
||||
justify-content: start;
|
||||
span {
|
||||
display: flex;
|
||||
min-width: 17em;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 767px) {
|
||||
tr {
|
||||
td.username {
|
||||
grid-column-start: 1;
|
||||
grid-column-end: -2;
|
||||
font-weight: bold;
|
||||
}
|
||||
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;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
margin: 0.5em 0 0 0;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
.directory-table {
|
||||
margin-top: 1em;
|
||||
&__column-header--username,
|
||||
&__column-header--email {
|
||||
.header-contents {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
&__cell.username {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__cell.email {
|
||||
@include breakpoint("tablet") {
|
||||
grid-column-start: 1;
|
||||
grid-column-end: -1;
|
||||
span {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.directory-table__cell {
|
||||
padding: 0.5em 0.25em;
|
||||
}
|
||||
|
||||
.user-status span {
|
||||
gap: 0.15em;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
}
|
||||
|
||||
// mobile styles
|
||||
|
|
|
@ -1,16 +1,11 @@
|
|||
.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;
|
||||
|
@ -39,58 +34,6 @@
|
|||
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 {
|
||||
|
@ -139,3 +82,226 @@
|
|||
.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-container {
|
||||
container-type: inline-size;
|
||||
container-name: directory-table;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,15 +25,27 @@
|
|||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
gap: 0.5em 0;
|
||||
|
||||
input + .group-members-manage {
|
||||
margin-left: auto;
|
||||
.bulk-select + input {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
.group-username-filter {
|
||||
margin: 0 0 5px 0;
|
||||
vertical-align: middle;
|
||||
input {
|
||||
margin: 0 auto 0 0;
|
||||
}
|
||||
|
||||
.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 {
|
||||
|
@ -118,58 +130,45 @@ table.group-manage-logs {
|
|||
}
|
||||
}
|
||||
|
||||
table.group-members {
|
||||
width: 100%;
|
||||
.group-members {
|
||||
grid-template-columns: 3fr repeat(3, minmax(min-content, 1fr));
|
||||
|
||||
th {
|
||||
text-align: center;
|
||||
|
||||
&.bulk-select {
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
&--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;
|
||||
}
|
||||
|
||||
&.bulk-select-buttons {
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
width: 1%;
|
||||
|
||||
.bulk-select-buttons-wrap {
|
||||
display: flex;
|
||||
.member-settings {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
}
|
||||
|
||||
&.username {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
color: var(--primary-medium);
|
||||
padding: 0.8em 0;
|
||||
text-align: center;
|
||||
|
||||
&.group-member {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: block;
|
||||
padding-right: 3.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-flair {
|
||||
&.group-members__requests {
|
||||
grid-template-columns: 3fr repeat(3, minmax(min-content, 1fr));
|
||||
}
|
||||
|
||||
.directory-table__value {
|
||||
font-size: var(--font-0);
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.user-status-message {
|
||||
img.emoji {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
.group-accept-deny-buttons {
|
||||
gap: 0.5em;
|
||||
}
|
||||
|
||||
@container (max-width: 47em) {
|
||||
.directory-table__cell.group-owner {
|
||||
order: 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,35 +21,6 @@
|
|||
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 {
|
||||
|
|
|
@ -923,6 +923,7 @@ 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."
|
||||
|
@ -5629,6 +5630,7 @@ 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:
|
||||
|
|
Loading…
Reference in New Issue
Block a user