discourse/app/assets/javascripts/admin/addon/templates/email-logs-bounced.gjs
Kelv 7b062e24de
DEV: refactor load-more component to glimmer and use intersection observer (#32285)
This PR makes the following key changes to the load-more component:

* Updating to a Glimmer component
* Changing from an eyeline/scrolling-based mechanism to an
IntersectionObserver to determine when to load
* Keeping track of a single invisible sentinel element to help trigger
the loadMore action instead of having to find and track the last item of
the collection upon every load
* The component can now be used without wrapping around some content to
be yielded - the intent is to use this for cases like
[DiscoveryTopicsList](f0057c7353/app/assets/javascripts/discourse/app/components/discovery/topics.gjs (L222))
where we might want more precise placement of the sentinel element.
* Added utility toggle functions to control observer behaviour in this
class for testing

We will replace the load-more mixin in DiscoveryTopicsList in another
PR.



https://github.com/user-attachments/assets/50d9763f-b5f8-40f6-8630-41bdf107baf7


### Technical Considerations
1. Keeping track of a single sentinel element simplifies the logic
greatly and is also more robust to changes in the collection that's
being loaded. (ref: a [previous
commit](https://github.com/discourse/discourse/pull/32285/commits/2279519081eef9649864453c90d72dbb2bd8970c)
that was following the previous approach of tracking specifically the
last item of the loaded collection); this also sidesteps odd edge cases
like if the tracked element is larger than the entire viewport.
2. Using
[isIntersecting](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry/isIntersecting)
instead of calculating manually whether the element is in the viewport
is also less flaky - I ran into issues with the boundingClientRect
inconsistently being calculated as outside the viewport on different
sized screens.
3. We need to properly bind `loadMore` functions with the action
decorator, otherwise the way we pass the loadMore callbacks through to
the observe-intersection modifier results in attempting to call it on
the loadMore component context instead. I've done this for all such
functions except for the one in
[`category-list`](0ed4b09527/app/assets/javascripts/discourse/app/models/category-list.js (L117))
which uses `@bind` that should be equivalent in terms of binding to the
correct `this`.
2025-04-28 10:22:35 +08:00

101 lines
3.4 KiB
Text
Vendored

import { fn } from "@ember/helper";
import { on } from "@ember/modifier";
import { LinkTo } from "@ember/routing";
import RouteTemplate from "ember-route-template";
import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner";
import LoadMore from "discourse/components/load-more";
import TextField from "discourse/components/text-field";
import avatar from "discourse/helpers/avatar";
import icon from "discourse/helpers/d-icon";
import formatDate from "discourse/helpers/format-date";
import { i18n } from "discourse-i18n";
export default RouteTemplate(
<template>
<LoadMore @action={{@controller.loadMore}}>
<table class="table email-list">
<thead>
<tr>
<th>{{i18n "admin.email.time"}}</th>
<th>{{i18n "admin.email.user"}}</th>
<th>{{i18n "admin.email.to_address"}}</th>
<th colspan="2">{{i18n "admin.email.email_type"}}</th>
</tr>
</thead>
<tbody>
<tr class="filters">
<td>{{i18n "admin.email.logs.filters.title"}}</td>
<td>
<TextField
@value={{@controller.filter.user}}
@placeholderKey="admin.email.logs.filters.user_placeholder"
/></td>
<td>
<TextField
@value={{@controller.filter.address}}
@placeholderKey="admin.email.logs.filters.address_placeholder"
/></td>
<td colspan="2">
<TextField
@value={{@controller.filter.type}}
@placeholderKey="admin.email.logs.filters.type_placeholder"
/></td>
</tr>
{{#each @controller.model as |l|}}
<tr>
<td>{{formatDate l.created_at}}</td>
<td>
{{#if l.user}}
<LinkTo @route="adminUser" @model={{l.user}}>{{avatar
l.user
imageSize="tiny"
}}</LinkTo>
<LinkTo
@route="adminUser"
@model={{l.user}}
>{{l.user.username}}</LinkTo>
{{else}}
&mdash;
{{/if}}
</td>
<td class="email-address"><a
href="mailto:{{l.to_address}}"
>{{l.to_address}}</a></td>
<td>
{{#if l.has_bounce_key}}
<a
href
{{on "click" (fn @controller.handleShowIncomingEmail l.id)}}
>
{{l.email_type}}
</a>
{{else}}
{{l.email_type}}
{{/if}}
</td>
<td class="email-details">
{{#if l.has_bounce_key}}
<a
href
{{on "click" (fn @controller.handleShowIncomingEmail l.id)}}
title={{i18n "admin.email.details_title"}}
>
{{icon "circle-info"}}
</a>
{{/if}}
</td>
</tr>
{{else}}
{{#unless @controller.loading}}
<tr>
<td colspan="5">{{i18n "admin.email.logs.none"}}</td></tr>
{{/unless}}
{{/each}}
</tbody>
</table>
</LoadMore>
<ConditionalLoadingSpinner @condition={{@controller.loading}} />
</template>
);