@flyos/design-system 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, signal, computed, Injectable, inject, TemplateRef, ViewContainerRef, effect, Input, Directive, ErrorHandler, Pipe, ChangeDetectionStrategy, Component, makeEnvironmentProviders, provideAppInitializer, PLATFORM_ID, DestroyRef, ElementRef, input, output, afterNextRender, HostListener, ViewChild, NgZone, viewChild, DOCUMENT as DOCUMENT$1, EventEmitter, Output, forwardRef, model, viewChildren, untracked, Injector, ViewEncapsulation, Renderer2,
|
|
3
|
-
import { catchError, throwError, Subject, from, firstValueFrom, of, ReplaySubject, retry, timer
|
|
2
|
+
import { InjectionToken, signal, computed, Injectable, inject, TemplateRef, ViewContainerRef, effect, Input, Directive, ErrorHandler, isDevMode, Pipe, ChangeDetectionStrategy, Component, makeEnvironmentProviders, provideAppInitializer, PLATFORM_ID, DestroyRef, ElementRef, input, output, afterNextRender, HostListener, ViewChild, NgZone, viewChild, DOCUMENT as DOCUMENT$1, EventEmitter, Output, forwardRef, model, viewChildren, untracked, Injector, ViewEncapsulation, Renderer2, contentChildren, contentChild } from '@angular/core';
|
|
3
|
+
import { catchError, throwError, Subject, from, firstValueFrom, of, Observable, map, ReplaySubject, retry, timer } from 'rxjs';
|
|
4
4
|
import { HttpClient, HttpParams, HttpHeaders, HttpErrorResponse, HttpEventType } from '@angular/common/http';
|
|
5
5
|
import { switchMap, debounceTime, distinctUntilChanged, filter } from 'rxjs/operators';
|
|
6
6
|
import { HubConnectionBuilder, HttpTransportType, LogLevel, HubConnectionState } from '@microsoft/signalr';
|
|
@@ -48,7 +48,7 @@ import { CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
|
|
|
48
48
|
// tools/publish-library.ps1 at bump time, and asserted by the spec beside this file.
|
|
49
49
|
// Used only for the diagnostic message; the duplicate-instance detection itself is
|
|
50
50
|
// version-agnostic, so a stale literal misnames a fork rather than hiding one.
|
|
51
|
-
const FLY_DS_VERSION = '1.
|
|
51
|
+
const FLY_DS_VERSION = '1.4.0';
|
|
52
52
|
const FLY_DS_REGISTRY_KEY = '__FLY_DS_INSTANCES__';
|
|
53
53
|
/**
|
|
54
54
|
* Records this design-system instance on the shared `scope` and returns the
|
|
@@ -2697,14 +2697,18 @@ function normalizeLocaleJson(raw) {
|
|
|
2697
2697
|
/**
|
|
2698
2698
|
* Shared I18nService for the shell and Business Apps.
|
|
2699
2699
|
*
|
|
2700
|
-
* **Merge order** (later keys win): DS baseline →
|
|
2701
|
-
*
|
|
2700
|
+
* **Merge order** (later keys win): DS baseline → remote bundles in registration
|
|
2701
|
+
* order → shell layer.
|
|
2702
2702
|
*
|
|
2703
2703
|
* - Baseline: {@link DS_BASELINE_LOCALES} — built-in strings for DS components
|
|
2704
2704
|
* (markdown editor, entity lookup) so they render localized labels even in a
|
|
2705
2705
|
* standalone consumer that never populated the shell layer. Always overridable.
|
|
2706
|
-
* -
|
|
2707
|
-
*
|
|
2706
|
+
* - Remotes: `loadBundle()` per manifest `localeBaseUrl`. May ADD any key and may
|
|
2707
|
+
* override the baseline, but cannot redefine a key the shell owns — the dictionary
|
|
2708
|
+
* is global, so allowing that let one remote restyle the whole desktop's wording.
|
|
2709
|
+
* - Shell: `setShellTranslations()` after loading `locale/{lang}.json` and API
|
|
2710
|
+
* overrides. Highest priority, which also makes the per-tenant
|
|
2711
|
+
* `/api/i18n/desktop-shell/{lang}` overrides authoritative over every app.
|
|
2708
2712
|
*/
|
|
2709
2713
|
class I18nService {
|
|
2710
2714
|
// NOTE (NG0200 / federation): the ErrorHandler ↔ I18nService cycle is broken on the
|
|
@@ -2719,20 +2723,43 @@ class I18nService {
|
|
|
2719
2723
|
_bundleOrder = signal([], ...(ngDevMode ? [{ debugName: "_bundleOrder" }] : /* istanbul ignore next */ []));
|
|
2720
2724
|
_locale = signal('en', ...(ngDevMode ? [{ debugName: "_locale" }] : /* istanbul ignore next */ []));
|
|
2721
2725
|
_version = signal(0, ...(ngDevMode ? [{ debugName: "_version" }] : /* istanbul ignore next */ []));
|
|
2726
|
+
/** `${bundleId}|${locale}` pairs already reported by {@link warnOnSuppressedShellKeys}. */
|
|
2727
|
+
_warnedCollisions = new Set();
|
|
2722
2728
|
/** Built-in DS strings for the active locale (falls back to `en` for any
|
|
2723
2729
|
* locale we don't ship). Lowest-priority layer — always overridable. */
|
|
2724
2730
|
_baseline = computed(() => DS_BASELINE_LOCALES[this._locale()] ?? DS_BASELINE_LOCALES['en'] ?? {}, ...(ngDevMode ? [{ debugName: "_baseline" }] : /* istanbul ignore next */ []));
|
|
2731
|
+
/**
|
|
2732
|
+
* Merge order, lowest priority first: DS baseline → remote bundles (registration
|
|
2733
|
+
* order) → shell.
|
|
2734
|
+
*
|
|
2735
|
+
* The shell layer is LAST, and that is the whole point. Translations live in one
|
|
2736
|
+
* flat, global dictionary with no per-app scoping, so when remote bundles were
|
|
2737
|
+
* merged last, any remote that happened to define a key the shell also defines
|
|
2738
|
+
* silently rewrote that string for the ENTIRE desktop — every other app included.
|
|
2739
|
+
*
|
|
2740
|
+
* That was not hypothetical: the Circles remote's bundle shares 63 keys with the
|
|
2741
|
+
* shell and differed on 27 of them, one being `common.label.dashboard`, which is
|
|
2742
|
+
* how it came to own the Arabic display name of a Core App it has nothing to do
|
|
2743
|
+
* with. The symptom is near-impossible to chase from the outside — editing the
|
|
2744
|
+
* shell's own `locale/{lang}.json` simply has no effect at runtime, with nothing
|
|
2745
|
+
* logged.
|
|
2746
|
+
*
|
|
2747
|
+
* A remote may still override the DS baseline (bundles are merged after it), which
|
|
2748
|
+
* is the layer explicitly documented as always-overridable, and it may still add
|
|
2749
|
+
* any key the shell does not define. What it can no longer do is redefine a string
|
|
2750
|
+
* the shell owns. {@link loadBundle} warns in dev when a bundle tries.
|
|
2751
|
+
*/
|
|
2725
2752
|
_merged = computed(() => {
|
|
2726
2753
|
const shell = this._shell();
|
|
2727
2754
|
const order = this._bundleOrder();
|
|
2728
2755
|
const bundles = this._bundles();
|
|
2729
|
-
let out = { ...this._baseline()
|
|
2756
|
+
let out = { ...this._baseline() };
|
|
2730
2757
|
for (const id of order) {
|
|
2731
2758
|
const b = bundles[id];
|
|
2732
2759
|
if (b)
|
|
2733
2760
|
out = { ...out, ...b };
|
|
2734
2761
|
}
|
|
2735
|
-
return out;
|
|
2762
|
+
return { ...out, ...shell };
|
|
2736
2763
|
}, ...(ngDevMode ? [{ debugName: "_merged" }] : /* istanbul ignore next */ []));
|
|
2737
2764
|
locale = this._locale.asReadonly();
|
|
2738
2765
|
version = this._version.asReadonly();
|
|
@@ -2746,6 +2773,17 @@ class I18nService {
|
|
|
2746
2773
|
this._shell.set(data);
|
|
2747
2774
|
this._locale.set(lang);
|
|
2748
2775
|
this.bump();
|
|
2776
|
+
// Bundles routinely land BEFORE this — the shell kicks off remote locale loads
|
|
2777
|
+
// and its own fetch together, and the remotes often win the race. Re-checking
|
|
2778
|
+
// here is what makes the diagnostic order-independent; without it the warning
|
|
2779
|
+
// fired only when the shell happened to arrive first, which in practice was
|
|
2780
|
+
// never.
|
|
2781
|
+
const bundles = this._bundles();
|
|
2782
|
+
for (const id of this._bundleOrder()) {
|
|
2783
|
+
const b = bundles[id];
|
|
2784
|
+
if (b)
|
|
2785
|
+
this.warnOnSuppressedShellKeys(id, b);
|
|
2786
|
+
}
|
|
2749
2787
|
}
|
|
2750
2788
|
setLanguage(lang) {
|
|
2751
2789
|
this._locale.set(lang);
|
|
@@ -2771,6 +2809,7 @@ class I18nService {
|
|
|
2771
2809
|
const data = normalizeLocaleJson(await resp.json());
|
|
2772
2810
|
if (abortSignal?.aborted)
|
|
2773
2811
|
return false;
|
|
2812
|
+
this.warnOnSuppressedShellKeys(id, data);
|
|
2774
2813
|
this._bundles.update((b) => ({ ...b, [id]: data }));
|
|
2775
2814
|
this._bundleOrder.update((order) => (order.includes(id) ? order : [...order, id]));
|
|
2776
2815
|
this._locale.set(lang);
|
|
@@ -2785,6 +2824,45 @@ class I18nService {
|
|
|
2785
2824
|
return false;
|
|
2786
2825
|
}
|
|
2787
2826
|
}
|
|
2827
|
+
/**
|
|
2828
|
+
* Dev-only: report keys a bundle defines that the shell already owns.
|
|
2829
|
+
*
|
|
2830
|
+
* These are now silently ignored (the shell wins), so without this the app would
|
|
2831
|
+
* simply not use strings the remote shipped and nobody would know which ones. The
|
|
2832
|
+
* previous precedence was worse — it applied them, changing wording across the
|
|
2833
|
+
* whole desktop, also silently. Either way the failure is invisible, which is the
|
|
2834
|
+
* argument for saying it out loud exactly once, when the bundle loads.
|
|
2835
|
+
*
|
|
2836
|
+
* A hit is not automatically a bug: an app legitimately reuses shared keys it does
|
|
2837
|
+
* not intend to redefine, so only keys whose VALUE differs are worth reporting.
|
|
2838
|
+
* The fix on the app's side is to namespace the key under its own app id.
|
|
2839
|
+
*/
|
|
2840
|
+
warnOnSuppressedShellKeys(id, data) {
|
|
2841
|
+
if (!isDevMode())
|
|
2842
|
+
return;
|
|
2843
|
+
const shell = this._shell();
|
|
2844
|
+
// Nothing to conflict with yet — a standalone consumer, or a bundle that raced
|
|
2845
|
+
// ahead of setShellTranslations. Not a collision, so stay quiet; the shell
|
|
2846
|
+
// re-runs this check for every stored bundle once it lands.
|
|
2847
|
+
if (Object.keys(shell).length === 0)
|
|
2848
|
+
return;
|
|
2849
|
+
const suppressed = Object.keys(data).filter((k) => k in shell && shell[k] !== data[k]);
|
|
2850
|
+
if (suppressed.length === 0)
|
|
2851
|
+
return;
|
|
2852
|
+
// Both entry points can reach the same (bundle, locale) pair — a bundle stored
|
|
2853
|
+
// before the shell is checked again when the shell lands, and every locale
|
|
2854
|
+
// switch replays both. Report each pair once so a real signal doesn't drown in
|
|
2855
|
+
// repeats.
|
|
2856
|
+
const seen = `${id}|${this._locale()}`;
|
|
2857
|
+
if (this._warnedCollisions.has(seen))
|
|
2858
|
+
return;
|
|
2859
|
+
this._warnedCollisions.add(seen);
|
|
2860
|
+
const sample = suppressed.slice(0, 10).join(', ');
|
|
2861
|
+
const more = suppressed.length > 10 ? `, …and ${suppressed.length - 10} more` : '';
|
|
2862
|
+
console.warn(`[I18n] Locale bundle "${id}" redefines ${suppressed.length} key(s) the shell owns; ` +
|
|
2863
|
+
`the shell's values win and the bundle's are ignored. Namespace these under "${id}." ` +
|
|
2864
|
+
`if they are meant to be app-specific: ${sample}${more}`);
|
|
2865
|
+
}
|
|
2788
2866
|
removeBundle(id) {
|
|
2789
2867
|
this._bundles.update((b) => {
|
|
2790
2868
|
const next = { ...b };
|
|
@@ -3994,6 +4072,290 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
|
|
|
3994
4072
|
args: [{ providedIn: 'root' }]
|
|
3995
4073
|
}] });
|
|
3996
4074
|
|
|
4075
|
+
/** `POST /api/users/resolve` accepts at most this many ids per call (platform contract). */
|
|
4076
|
+
const RESOLVE_BATCH_SIZE = 200;
|
|
4077
|
+
/**
|
|
4078
|
+
* **`FlyUserDirectoryService`** — the one id→person adapter for the platform user
|
|
4079
|
+
* directory (`GET /api/users/lookup?q=`, `POST /api/users/resolve`).
|
|
4080
|
+
*
|
|
4081
|
+
* Every app that renders a *list* of people — comment authors, contributors,
|
|
4082
|
+
* approvers, credits, audit rows — needs the same thing: turn a `userId` into a
|
|
4083
|
+
* name without issuing one request per row. Before this service each app wrote
|
|
4084
|
+
* its own copy (Circles `UserResolverService`, Thoughts `UserLookupService`,
|
|
4085
|
+
* plus hand-rolled `/api/users/resolve` calls in the shell and admin portal),
|
|
4086
|
+
* and every copy re-derived the same read-through cache. This is that cache,
|
|
4087
|
+
* once, in the design system.
|
|
4088
|
+
*
|
|
4089
|
+
* The HTTP call rides the **host's** `HttpClient`, so the host's gateway routing,
|
|
4090
|
+
* auth interceptor, and correlation headers all apply — the same arrangement
|
|
4091
|
+
* `fly-entity-lookup` uses. The DS never configures a base URL of its own.
|
|
4092
|
+
*
|
|
4093
|
+
* ## `label()` is read-through — hosts do not prime
|
|
4094
|
+
*
|
|
4095
|
+
* {@link label} is a *signal-backed* accessor: on a cache miss it queues the id
|
|
4096
|
+
* and returns `null`, then the queued ids are resolved in ONE batched request a
|
|
4097
|
+
* microtask later. Because the read happens inside Angular's reactive context,
|
|
4098
|
+
* a template calling `label(id)` re-renders by itself when the answer lands —
|
|
4099
|
+
* no `computed` shadow state, no `prime()` call at each fetch site.
|
|
4100
|
+
*
|
|
4101
|
+
* That last point is the whole reason this exists. The prime-then-read shape it
|
|
4102
|
+
* replaces required every host to (a) keep the raw page separate from the mapped
|
|
4103
|
+
* page, (b) derive the mapped page through a `computed`, and (c) remember to
|
|
4104
|
+
* prime on *every* fetch path — root load, load-more, load-replies, refetch. Miss
|
|
4105
|
+
* one path and that path silently renders GUIDs, with nothing failing. Making the
|
|
4106
|
+
* cache read-through deletes the whole bug class.
|
|
4107
|
+
*
|
|
4108
|
+
* ## Failure is graceful, and never a retry storm
|
|
4109
|
+
*
|
|
4110
|
+
* A failed lookup caches nothing, so callers fall back to whatever they render
|
|
4111
|
+
* for an unresolved id (typically the raw id). Deliberate asymmetry in retry
|
|
4112
|
+
* behaviour, because a render-driven read must not hammer a broken endpoint:
|
|
4113
|
+
*
|
|
4114
|
+
* - **Render-driven** ({@link label}) attempts an id **once**. After a failure it
|
|
4115
|
+
* stays unresolved for the session rather than re-requesting on every
|
|
4116
|
+
* change-detection pass.
|
|
4117
|
+
* - **Explicit** ({@link prime}, {@link resolve}, {@link invalidate}) always
|
|
4118
|
+
* re-attempts. A host re-fetching a page therefore retries naturally, and a
|
|
4119
|
+
* retry button can call {@link invalidate}.
|
|
4120
|
+
*
|
|
4121
|
+
* Unknown / cross-tenant ids are omitted by the endpoint rather than erroring, so
|
|
4122
|
+
* they simply never resolve — which is also the correct behaviour for a
|
|
4123
|
+
* standalone External App where `/api/users/*` isn't reachable at all.
|
|
4124
|
+
*
|
|
4125
|
+
* @example Comment thread — one binding, no plumbing
|
|
4126
|
+
* ```html
|
|
4127
|
+
* <fly-comment-thread
|
|
4128
|
+
* [rootPage]="rootPage()"
|
|
4129
|
+
* [resolveDisplayName]="directory.label" />
|
|
4130
|
+
* ```
|
|
4131
|
+
* ```ts
|
|
4132
|
+
* protected readonly directory = inject(FlyUserDirectoryService);
|
|
4133
|
+
* ```
|
|
4134
|
+
*
|
|
4135
|
+
* @example People picker — search + prefill
|
|
4136
|
+
* ```html
|
|
4137
|
+
* <fly-people-picker
|
|
4138
|
+
* [searchFn]="directory.search()"
|
|
4139
|
+
* [initialSelection]="selected()" />
|
|
4140
|
+
* ```
|
|
4141
|
+
*/
|
|
4142
|
+
class FlyUserDirectoryService {
|
|
4143
|
+
http = inject(HttpClient);
|
|
4144
|
+
/** id → person, for everything resolved so far. Signal-backed so {@link label} is reactive. */
|
|
4145
|
+
entries = signal({}, ...(ngDevMode ? [{ debugName: "entries" }] : /* istanbul ignore next */ []));
|
|
4146
|
+
/** Ids with a request in the air — so a re-render cannot duplicate an in-flight lookup. */
|
|
4147
|
+
inFlight = new Set();
|
|
4148
|
+
/**
|
|
4149
|
+
* Ids {@link label} has already attempted at least once. Checked ONLY on the
|
|
4150
|
+
* render-driven path: it is what stops a persistently failing endpoint from
|
|
4151
|
+
* being re-requested on every change-detection pass. Explicit callers ignore it.
|
|
4152
|
+
*/
|
|
4153
|
+
attempted = new Set();
|
|
4154
|
+
/** Ids queued by {@link label}, awaiting the batched microtask flush. */
|
|
4155
|
+
queued = new Set();
|
|
4156
|
+
flushScheduled = false;
|
|
4157
|
+
// ── Read ─────────────────────────────────────────────────────────────────────
|
|
4158
|
+
/**
|
|
4159
|
+
* Display name for `id`, or `null` when it is not (yet) resolved — on a miss the
|
|
4160
|
+
* id is queued for the next batched resolve. Null rather than the raw id on
|
|
4161
|
+
* purpose: a GUID is not a name, and the caller should render its own fallback
|
|
4162
|
+
* (or localized placeholder) instead of the DS guessing one.
|
|
4163
|
+
*
|
|
4164
|
+
* Bound as a value (`[resolveDisplayName]="directory.label"`), so it is an arrow
|
|
4165
|
+
* property rather than a method — `this` stays correct without the host wrapping
|
|
4166
|
+
* it in a closure.
|
|
4167
|
+
*/
|
|
4168
|
+
label = (id) => {
|
|
4169
|
+
if (!id)
|
|
4170
|
+
return null;
|
|
4171
|
+
const hit = this.entries()[id];
|
|
4172
|
+
if (hit)
|
|
4173
|
+
return hit.displayName;
|
|
4174
|
+
this.queueForRender(id);
|
|
4175
|
+
return null;
|
|
4176
|
+
};
|
|
4177
|
+
/**
|
|
4178
|
+
* The full resolved person for `id` (name, email, avatar), or `null` when not yet
|
|
4179
|
+
* known. Same read-through queueing as {@link label} — use it when a surface can
|
|
4180
|
+
* render an avatar rather than just a name.
|
|
4181
|
+
*/
|
|
4182
|
+
option = (id) => {
|
|
4183
|
+
if (!id)
|
|
4184
|
+
return null;
|
|
4185
|
+
const hit = this.entries()[id];
|
|
4186
|
+
if (hit)
|
|
4187
|
+
return hit;
|
|
4188
|
+
this.queueForRender(id);
|
|
4189
|
+
return null;
|
|
4190
|
+
};
|
|
4191
|
+
// ── Explicit population ──────────────────────────────────────────────────────
|
|
4192
|
+
/**
|
|
4193
|
+
* Eagerly resolves any ids not already known, merging them into the cache.
|
|
4194
|
+
* Optional — {@link label} resolves on demand — but useful to warm the cache
|
|
4195
|
+
* before a render, or to force a retry after a failed auto-attempt (explicit
|
|
4196
|
+
* calls are not subject to the once-only rule; see the class doc).
|
|
4197
|
+
*/
|
|
4198
|
+
prime(ids) {
|
|
4199
|
+
const fresh = this.distinct(ids).filter(id => !this.entries()[id] && !this.inFlight.has(id));
|
|
4200
|
+
if (fresh.length === 0)
|
|
4201
|
+
return;
|
|
4202
|
+
this.fetch(fresh);
|
|
4203
|
+
}
|
|
4204
|
+
/**
|
|
4205
|
+
* Bulk-resolves ids to picker options — for prefilling `fly-people-picker`'s
|
|
4206
|
+
* `[initialSelection]` from stored ids. Requests are chunked to the endpoint's
|
|
4207
|
+
* {@link RESOLVE_BATCH_SIZE} cap rather than truncated, so a large selection
|
|
4208
|
+
* resolves fully instead of silently losing its tail. Resolved rows also land in
|
|
4209
|
+
* the cache, so a later {@link label} for the same id is free. Empty in → empty
|
|
4210
|
+
* out with no request.
|
|
4211
|
+
*/
|
|
4212
|
+
resolve(ids) {
|
|
4213
|
+
const distinct = this.distinct(ids);
|
|
4214
|
+
if (distinct.length === 0)
|
|
4215
|
+
return of([]);
|
|
4216
|
+
return new Observable(subscriber => {
|
|
4217
|
+
const chunks = this.chunk(distinct);
|
|
4218
|
+
const collected = [];
|
|
4219
|
+
let remaining = chunks.length;
|
|
4220
|
+
let failed = false;
|
|
4221
|
+
for (const batch of chunks) {
|
|
4222
|
+
this.post(batch).subscribe({
|
|
4223
|
+
next: rows => {
|
|
4224
|
+
this.merge(rows);
|
|
4225
|
+
collected.push(...rows);
|
|
4226
|
+
},
|
|
4227
|
+
error: (err) => {
|
|
4228
|
+
if (failed)
|
|
4229
|
+
return;
|
|
4230
|
+
failed = true;
|
|
4231
|
+
subscriber.error(err);
|
|
4232
|
+
},
|
|
4233
|
+
complete: () => {
|
|
4234
|
+
if (failed)
|
|
4235
|
+
return;
|
|
4236
|
+
if (--remaining === 0) {
|
|
4237
|
+
subscriber.next(collected);
|
|
4238
|
+
subscriber.complete();
|
|
4239
|
+
}
|
|
4240
|
+
},
|
|
4241
|
+
});
|
|
4242
|
+
}
|
|
4243
|
+
});
|
|
4244
|
+
}
|
|
4245
|
+
/**
|
|
4246
|
+
* Drops cached entries so the next read re-resolves them — the retry hook for a
|
|
4247
|
+
* host that surfaces a "couldn't load names" affordance. Omit `ids` to clear the
|
|
4248
|
+
* whole cache.
|
|
4249
|
+
*/
|
|
4250
|
+
invalidate(ids) {
|
|
4251
|
+
if (!ids) {
|
|
4252
|
+
this.entries.set({});
|
|
4253
|
+
this.inFlight.clear();
|
|
4254
|
+
this.attempted.clear();
|
|
4255
|
+
this.queued.clear();
|
|
4256
|
+
return;
|
|
4257
|
+
}
|
|
4258
|
+
const targets = this.distinct(ids);
|
|
4259
|
+
if (targets.length === 0)
|
|
4260
|
+
return;
|
|
4261
|
+
targets.forEach(id => {
|
|
4262
|
+
this.inFlight.delete(id);
|
|
4263
|
+
this.attempted.delete(id);
|
|
4264
|
+
});
|
|
4265
|
+
this.entries.update(prev => {
|
|
4266
|
+
const next = { ...prev };
|
|
4267
|
+
targets.forEach(id => delete next[id]);
|
|
4268
|
+
return next;
|
|
4269
|
+
});
|
|
4270
|
+
}
|
|
4271
|
+
// ── Search ───────────────────────────────────────────────────────────────────
|
|
4272
|
+
/**
|
|
4273
|
+
* A `searchFn` over the platform users directory — bind straight to
|
|
4274
|
+
* `<fly-people-picker [searchFn]>`. Results are cached too, so picking someone
|
|
4275
|
+
* makes their name available to {@link label} without another round-trip.
|
|
4276
|
+
*/
|
|
4277
|
+
search() {
|
|
4278
|
+
return (query) => this.http
|
|
4279
|
+
.get('/api/users/lookup', { params: { q: query } })
|
|
4280
|
+
.pipe(map(res => (res.data ?? []).map(toOption)), map(rows => {
|
|
4281
|
+
this.merge(rows);
|
|
4282
|
+
return rows;
|
|
4283
|
+
}));
|
|
4284
|
+
}
|
|
4285
|
+
// ── Internals ────────────────────────────────────────────────────────────────
|
|
4286
|
+
/** Queues a render-driven miss, honouring the once-only rule. */
|
|
4287
|
+
queueForRender(id) {
|
|
4288
|
+
if (this.entries()[id] || this.inFlight.has(id) || this.attempted.has(id) || this.queued.has(id))
|
|
4289
|
+
return;
|
|
4290
|
+
this.queued.add(id);
|
|
4291
|
+
if (this.flushScheduled)
|
|
4292
|
+
return;
|
|
4293
|
+
this.flushScheduled = true;
|
|
4294
|
+
// A microtask, not a synchronous call: `label()` runs during template
|
|
4295
|
+
// evaluation, and firing the request (and its signal write) inline would
|
|
4296
|
+
// mutate state mid-change-detection. Deferring also batches every miss in
|
|
4297
|
+
// the pass into ONE request — a 20-comment page costs one round-trip.
|
|
4298
|
+
queueMicrotask(() => this.flush());
|
|
4299
|
+
}
|
|
4300
|
+
flush() {
|
|
4301
|
+
this.flushScheduled = false;
|
|
4302
|
+
const batch = [...this.queued];
|
|
4303
|
+
this.queued.clear();
|
|
4304
|
+
if (batch.length === 0)
|
|
4305
|
+
return;
|
|
4306
|
+
batch.forEach(id => this.attempted.add(id));
|
|
4307
|
+
this.fetch(batch);
|
|
4308
|
+
}
|
|
4309
|
+
/** Issues the (chunked) resolve calls for `ids` and merges whatever comes back. */
|
|
4310
|
+
fetch(ids) {
|
|
4311
|
+
for (const batch of this.chunk(ids)) {
|
|
4312
|
+
batch.forEach(id => this.inFlight.add(id));
|
|
4313
|
+
this.post(batch).subscribe({
|
|
4314
|
+
next: rows => this.merge(rows),
|
|
4315
|
+
// Unknown / cross-tenant ids are omitted by the endpoint rather than
|
|
4316
|
+
// erroring, so reaching here means a transport-level failure. Cache
|
|
4317
|
+
// nothing — a wrong answer is worse than an unresolved one.
|
|
4318
|
+
error: () => batch.forEach(id => this.inFlight.delete(id)),
|
|
4319
|
+
complete: () => batch.forEach(id => this.inFlight.delete(id)),
|
|
4320
|
+
});
|
|
4321
|
+
}
|
|
4322
|
+
}
|
|
4323
|
+
post(ids) {
|
|
4324
|
+
return this.http
|
|
4325
|
+
.post('/api/users/resolve', { ids })
|
|
4326
|
+
.pipe(map(res => (res.data ?? []).map(toOption)));
|
|
4327
|
+
}
|
|
4328
|
+
merge(rows) {
|
|
4329
|
+
if (rows.length === 0)
|
|
4330
|
+
return;
|
|
4331
|
+
this.entries.update(prev => {
|
|
4332
|
+
const next = { ...prev };
|
|
4333
|
+
rows.forEach(r => (next[r.id] = r));
|
|
4334
|
+
return next;
|
|
4335
|
+
});
|
|
4336
|
+
}
|
|
4337
|
+
distinct(ids) {
|
|
4338
|
+
return [...new Set(ids.filter((id) => !!id))];
|
|
4339
|
+
}
|
|
4340
|
+
chunk(ids) {
|
|
4341
|
+
const out = [];
|
|
4342
|
+
for (let i = 0; i < ids.length; i += RESOLVE_BATCH_SIZE) {
|
|
4343
|
+
out.push(ids.slice(i, i + RESOLVE_BATCH_SIZE));
|
|
4344
|
+
}
|
|
4345
|
+
return out;
|
|
4346
|
+
}
|
|
4347
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyUserDirectoryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
4348
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyUserDirectoryService, providedIn: 'root' });
|
|
4349
|
+
}
|
|
4350
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyUserDirectoryService, decorators: [{
|
|
4351
|
+
type: Injectable,
|
|
4352
|
+
args: [{ providedIn: 'root' }]
|
|
4353
|
+
}] });
|
|
4354
|
+
/** `UserLookupDto`/`UserResolvedDto` → the picker option shape, 1:1. */
|
|
4355
|
+
function toOption(u) {
|
|
4356
|
+
return { id: u.id, displayName: u.displayName, email: u.email, avatarUrl: u.avatarUrl };
|
|
4357
|
+
}
|
|
4358
|
+
|
|
3997
4359
|
/**
|
|
3998
4360
|
* The page's CSP nonce, read once at module init from `<meta name="csp-nonce">`.
|
|
3999
4361
|
*
|
|
@@ -9562,11 +9924,11 @@ class FlyDrawerComponent {
|
|
|
9562
9924
|
}
|
|
9563
9925
|
}
|
|
9564
9926
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyDrawerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9565
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyDrawerComponent, isStandalone: true, selector: "fly-drawer", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null }, side: { classPropertyName: "side", publicName: "side", isSignal: true, isRequired: false, transformFunction: null }, dismissOnScrim: { classPropertyName: "dismissOnScrim", publicName: "dismissOnScrim", isSignal: true, isRequired: false, transformFunction: null }, dismissOnEscape: { classPropertyName: "dismissOnEscape", publicName: "dismissOnEscape", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, hideCloseButton: { classPropertyName: "hideCloseButton", publicName: "hideCloseButton", isSignal: true, isRequired: false, transformFunction: null }, bodyPadding: { classPropertyName: "bodyPadding", publicName: "bodyPadding", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", openChange: "openChange" }, host: { listeners: { "document:keydown.escape": "onEscape()" }, properties: { "class.fly-drawer--fixed": "position() === 'fixed'" } }, ngImport: i0, template: "<!--\r\n Windowed overlay drawer. Renders only while mounted (open or sliding out).\r\n Scrim + panel are absolutely positioned within the nearest positioned\r\n ancestor (the consuming app root must be position:relative).\r\n-->\r\n@if (rendered()) {\r\n <div\r\n class=\"fly-drawer__scrim\"\r\n [class.fly-drawer__scrim--leaving]=\"leaving()\"\r\n (click)=\"onScrimClick()\"\r\n aria-hidden=\"true\"\r\n ></div>\r\n\r\n <div\r\n class=\"fly-drawer__panel\"\r\n [ngClass]=\"panelClass()\"\r\n [class.fly-drawer__panel--leaving]=\"leaving()\"\r\n [style.--fly-drawer-width]=\"width()\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n [attr.aria-labelledby]=\"labelledBy()\"\r\n [attr.aria-label]=\"!labelledBy() && ariaLabel() ? (ariaLabel()! | translate) : null\"\r\n cdkTrapFocus\r\n (animationend)=\"onPanelAnimationEnd($event.animationName)\"\r\n >\r\n <!-- Header: custom slot, else heading + close \u2715. -->\r\n <ng-content select=\"[flyDrawerHeader]\">\r\n @if (heading()) {\r\n <header class=\"fly-drawer__header\">\r\n <h2 class=\"fly-drawer__title\" [id]=\"headingId\">{{ heading()! | translate }}</h2>\r\n @if (!hideCloseButton()) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-drawer__close\"\r\n (click)=\"requestClose()\"\r\n [attr.aria-label]=\"'common.action.close' | translate\"\r\n >\r\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n </header>\r\n }\r\n </ng-content>\r\n\r\n <div class=\"fly-drawer__body\" [ngClass]=\"'fly-drawer__body--pad-' + bodyPadding()\">\r\n <ng-content></ng-content>\r\n </div>\r\n\r\n <ng-content select=\"[flyDrawerFooter]\"></ng-content>\r\n </div>\r\n}\r\n", styles: [":host{position:absolute;inset:0;z-index:60;pointer-events:none}:host(.fly-drawer--fixed){position:fixed;block-size:100dvh;z-index:var(--z-overlay, 100)}.fly-drawer__scrim{position:absolute;inset:0;pointer-events:auto;background:#00000061;opacity:1;animation:fly-drawer-scrim-in .24s ease both}.fly-drawer__scrim--leaving{animation-name:fly-drawer-scrim-out;animation-duration:.2s}.fly-drawer__panel{--fly-drawer-offset: 100%;position:absolute;inset-block:0;inset-inline-end:0;pointer-events:auto;display:flex;flex-direction:column;min-block-size:0;inline-size:var(--fly-drawer-width, min(480px, 100%));max-inline-size:100%;background:var(--surface-overlay, #fff);-webkit-backdrop-filter:blur(30px) saturate(150%);backdrop-filter:blur(30px) saturate(150%);border-inline-start:1px solid var(--surface-border, rgba(0, 0, 0, .08));box-shadow:-16px 0 48px #00000038;animation:fly-drawer-in .28s cubic-bezier(.32,.72,0,1) both;will-change:transform}.fly-drawer__panel--sm{inline-size:var(--fly-drawer-width, min(360px, 100%))}.fly-drawer__panel--md{inline-size:var(--fly-drawer-width, min(480px, 100%))}.fly-drawer__panel--lg{inline-size:var(--fly-drawer-width, min(640px, 100%))}.fly-drawer__panel--xl{inline-size:var(--fly-drawer-width, min(960px, 94%))}.fly-drawer__panel--side-start{--fly-drawer-offset: -100%;inset-inline:0 auto;border-inline-start:none;border-inline-end:1px solid var(--surface-border, rgba(0, 0, 0, .08));box-shadow:16px 0 48px #00000038}.fly-drawer__panel--leaving{animation-name:fly-drawer-out;animation-duration:.24s;animation-timing-function:cubic-bezier(.4,0,1,1)}@keyframes fly-drawer-in{0%{transform:translate(var(--fly-drawer-offset))}to{transform:translate(0)}}@keyframes fly-drawer-out{0%{transform:translate(0)}to{transform:translate(var(--fly-drawer-offset))}}@keyframes fly-drawer-scrim-in{0%{opacity:0}to{opacity:1}}@keyframes fly-drawer-scrim-out{0%{opacity:1}to{opacity:0}}:host-context([dir=rtl]) .fly-drawer__panel{--fly-drawer-offset: -100%;box-shadow:16px 0 48px #00000038;border-inline-start:1px solid var(--surface-border, rgba(0, 0, 0, .08))}:host-context([dir=rtl]) .fly-drawer__panel--side-start{--fly-drawer-offset: 100%}.fly-drawer__header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 18px;border-block-end:1px solid var(--surface-border, rgba(0, 0, 0, .08));flex:0 0 auto}.fly-drawer__title{margin:0;font-size:16px;font-weight:600;color:var(--label-primary, #1d1d1f)}.fly-drawer__close{display:inline-flex;align-items:center;justify-content:center;inline-size:30px;block-size:30px;border:none;border-radius:8px;background:transparent;color:var(--label-secondary, #6e6e73);cursor:pointer}.fly-drawer__close:hover{background:#0000000f}.fly-drawer__body{flex:1 1 auto;min-block-size:0;overflow-y:auto;padding:var(--fly-drawer-body-padding, 16px 18px)}.fly-drawer__body--pad-none{--fly-drawer-body-padding: 0}.fly-drawer__body--pad-sm{--fly-drawer-body-padding: 12px 14px}.fly-drawer__body--pad-md{--fly-drawer-body-padding: 16px 18px}.fly-drawer__body--pad-lg{--fly-drawer-body-padding: 20px 22px}:host ::ng-deep [flyDrawerFooter]{flex:0 0 auto;display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:12px 18px;border-block-start:1px solid var(--surface-border, rgba(0, 0, 0, .08))}:host-context(html.dark-theme) .fly-drawer__close:hover{background:#ffffff14}@media(prefers-reduced-motion:reduce){.fly-drawer__scrim,.fly-drawer__panel,.fly-drawer__scrim--leaving,.fly-drawer__panel--leaving{animation:none}}@media(forced-colors:active){.fly-drawer__panel{border:1px solid CanvasText}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i2.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
9927
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyDrawerComponent, isStandalone: true, selector: "fly-drawer", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null }, side: { classPropertyName: "side", publicName: "side", isSignal: true, isRequired: false, transformFunction: null }, dismissOnScrim: { classPropertyName: "dismissOnScrim", publicName: "dismissOnScrim", isSignal: true, isRequired: false, transformFunction: null }, dismissOnEscape: { classPropertyName: "dismissOnEscape", publicName: "dismissOnEscape", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, hideCloseButton: { classPropertyName: "hideCloseButton", publicName: "hideCloseButton", isSignal: true, isRequired: false, transformFunction: null }, bodyPadding: { classPropertyName: "bodyPadding", publicName: "bodyPadding", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", openChange: "openChange" }, host: { listeners: { "document:keydown.escape": "onEscape()" }, properties: { "class.fly-drawer--fixed": "position() === 'fixed'" } }, ngImport: i0, template: "<!--\r\n Windowed overlay drawer. Renders only while mounted (open or sliding out).\r\n Scrim + panel are absolutely positioned within the nearest positioned\r\n ancestor (the consuming app root must be position:relative).\r\n-->\r\n@if (rendered()) {\r\n <div\r\n class=\"fly-drawer__scrim\"\r\n [class.fly-drawer__scrim--leaving]=\"leaving()\"\r\n (click)=\"onScrimClick()\"\r\n aria-hidden=\"true\"\r\n ></div>\r\n\r\n <div\r\n class=\"fly-drawer__panel\"\r\n [ngClass]=\"panelClass()\"\r\n [class.fly-drawer__panel--leaving]=\"leaving()\"\r\n [style.--fly-drawer-width]=\"width()\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n [attr.aria-labelledby]=\"labelledBy()\"\r\n [attr.aria-label]=\"!labelledBy() && ariaLabel() ? (ariaLabel()! | translate) : null\"\r\n cdkTrapFocus\r\n (animationend)=\"onPanelAnimationEnd($event.animationName)\"\r\n >\r\n <!-- Header: custom slot, else heading + close \u2715. -->\r\n <ng-content select=\"[flyDrawerHeader]\">\r\n @if (heading()) {\r\n <header class=\"fly-drawer__header\">\r\n <h2 class=\"fly-drawer__title\" [id]=\"headingId\">{{ heading()! | translate }}</h2>\r\n @if (!hideCloseButton()) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-drawer__close\"\r\n (click)=\"requestClose()\"\r\n [attr.aria-label]=\"'common.action.close' | translate\"\r\n >\r\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n </header>\r\n }\r\n </ng-content>\r\n\r\n <div class=\"fly-drawer__body\" [ngClass]=\"'fly-drawer__body--pad-' + bodyPadding()\">\r\n <ng-content></ng-content>\r\n </div>\r\n\r\n <ng-content select=\"[flyDrawerFooter]\"></ng-content>\r\n </div>\r\n}\r\n", styles: [":host{position:absolute;inset:0;z-index:60;pointer-events:none}:host(.fly-drawer--fixed){position:fixed;block-size:100dvh;z-index:var(--z-overlay, 100)}.fly-drawer__scrim{position:absolute;inset:0;pointer-events:auto;background:#00000061;opacity:1;animation:fly-drawer-scrim-in .24s ease both}.fly-drawer__scrim--leaving{animation-name:fly-drawer-scrim-out;animation-duration:.2s}.fly-drawer__panel{--fly-drawer-offset: 100%;position:absolute;inset-block:0;inset-inline-end:0;pointer-events:auto;display:flex;flex-direction:column;min-block-size:0;inline-size:var(--fly-drawer-width, min(480px, 100%));max-inline-size:100%;background:var(--surface-overlay, #fff);-webkit-backdrop-filter:blur(30px) saturate(150%);backdrop-filter:blur(30px) saturate(150%);border-inline-start:1px solid var(--surface-border, rgba(0, 0, 0, .08));box-shadow:-16px 0 48px #00000038;animation:fly-drawer-in .28s cubic-bezier(.32,.72,0,1) both;will-change:transform}.fly-drawer__panel--sm{inline-size:var(--fly-drawer-width, min(360px, 100%))}.fly-drawer__panel--md{inline-size:var(--fly-drawer-width, min(480px, 100%))}.fly-drawer__panel--lg{inline-size:var(--fly-drawer-width, min(640px, 100%))}.fly-drawer__panel--xl{inline-size:var(--fly-drawer-width, min(960px, 94%))}.fly-drawer__panel--side-start{--fly-drawer-offset: -100%;inset-inline:0 auto;border-inline-start:none;border-inline-end:1px solid var(--surface-border, rgba(0, 0, 0, .08));box-shadow:16px 0 48px #00000038}.fly-drawer__panel--leaving{animation-name:fly-drawer-out;animation-duration:.24s;animation-timing-function:cubic-bezier(.4,0,1,1)}@keyframes fly-drawer-in{0%{transform:translate(var(--fly-drawer-offset))}to{transform:translate(0)}}@keyframes fly-drawer-out{0%{transform:translate(0)}to{transform:translate(var(--fly-drawer-offset))}}@keyframes fly-drawer-scrim-in{0%{opacity:0}to{opacity:1}}@keyframes fly-drawer-scrim-out{0%{opacity:1}to{opacity:0}}:host-context([dir=rtl]) .fly-drawer__panel{--fly-drawer-offset: -100%;box-shadow:16px 0 48px #00000038;border-inline-start:1px solid var(--surface-border, rgba(0, 0, 0, .08))}:host-context([dir=rtl]) .fly-drawer__panel--side-start{--fly-drawer-offset: 100%}.fly-drawer__header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 18px;border-block-end:1px solid var(--surface-border, rgba(0, 0, 0, .08));flex:0 0 auto}.fly-drawer__title{margin:0;font-size:16px;font-weight:600;color:var(--label-primary, #1d1d1f)}.fly-drawer__close{display:inline-flex;align-items:center;justify-content:center;inline-size:30px;block-size:30px;border:none;border-radius:8px;background:transparent;color:var(--label-secondary, #6e6e73);cursor:pointer}.fly-drawer__close:hover{background:#0000000f}.fly-drawer__body{flex:1 1 auto;min-block-size:0;overflow-y:auto;padding:var(--fly-drawer-body-padding, 16px 18px);container-type:inline-size}.fly-drawer__body--pad-none{--fly-drawer-body-padding: 0}.fly-drawer__body--pad-sm{--fly-drawer-body-padding: 12px 14px}.fly-drawer__body--pad-md{--fly-drawer-body-padding: 16px 18px}.fly-drawer__body--pad-lg{--fly-drawer-body-padding: 20px 22px}:host ::ng-deep [flyDrawerFooter]{flex:0 0 auto;display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:12px 18px;border-block-start:1px solid var(--surface-border, rgba(0, 0, 0, .08))}:host-context(html.dark-theme) .fly-drawer__close:hover{background:#ffffff14}@media(prefers-reduced-motion:reduce){.fly-drawer__scrim,.fly-drawer__panel,.fly-drawer__scrim--leaving,.fly-drawer__panel--leaving{animation:none}}@media(forced-colors:active){.fly-drawer__panel{border:1px solid CanvasText}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i2.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
9566
9928
|
}
|
|
9567
9929
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyDrawerComponent, decorators: [{
|
|
9568
9930
|
type: Component,
|
|
9569
|
-
args: [{ selector: 'fly-drawer', standalone: true, imports: [CommonModule, A11yModule, TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, host: { '[class.fly-drawer--fixed]': "position() === 'fixed'" }, template: "<!--\r\n Windowed overlay drawer. Renders only while mounted (open or sliding out).\r\n Scrim + panel are absolutely positioned within the nearest positioned\r\n ancestor (the consuming app root must be position:relative).\r\n-->\r\n@if (rendered()) {\r\n <div\r\n class=\"fly-drawer__scrim\"\r\n [class.fly-drawer__scrim--leaving]=\"leaving()\"\r\n (click)=\"onScrimClick()\"\r\n aria-hidden=\"true\"\r\n ></div>\r\n\r\n <div\r\n class=\"fly-drawer__panel\"\r\n [ngClass]=\"panelClass()\"\r\n [class.fly-drawer__panel--leaving]=\"leaving()\"\r\n [style.--fly-drawer-width]=\"width()\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n [attr.aria-labelledby]=\"labelledBy()\"\r\n [attr.aria-label]=\"!labelledBy() && ariaLabel() ? (ariaLabel()! | translate) : null\"\r\n cdkTrapFocus\r\n (animationend)=\"onPanelAnimationEnd($event.animationName)\"\r\n >\r\n <!-- Header: custom slot, else heading + close \u2715. -->\r\n <ng-content select=\"[flyDrawerHeader]\">\r\n @if (heading()) {\r\n <header class=\"fly-drawer__header\">\r\n <h2 class=\"fly-drawer__title\" [id]=\"headingId\">{{ heading()! | translate }}</h2>\r\n @if (!hideCloseButton()) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-drawer__close\"\r\n (click)=\"requestClose()\"\r\n [attr.aria-label]=\"'common.action.close' | translate\"\r\n >\r\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n </header>\r\n }\r\n </ng-content>\r\n\r\n <div class=\"fly-drawer__body\" [ngClass]=\"'fly-drawer__body--pad-' + bodyPadding()\">\r\n <ng-content></ng-content>\r\n </div>\r\n\r\n <ng-content select=\"[flyDrawerFooter]\"></ng-content>\r\n </div>\r\n}\r\n", styles: [":host{position:absolute;inset:0;z-index:60;pointer-events:none}:host(.fly-drawer--fixed){position:fixed;block-size:100dvh;z-index:var(--z-overlay, 100)}.fly-drawer__scrim{position:absolute;inset:0;pointer-events:auto;background:#00000061;opacity:1;animation:fly-drawer-scrim-in .24s ease both}.fly-drawer__scrim--leaving{animation-name:fly-drawer-scrim-out;animation-duration:.2s}.fly-drawer__panel{--fly-drawer-offset: 100%;position:absolute;inset-block:0;inset-inline-end:0;pointer-events:auto;display:flex;flex-direction:column;min-block-size:0;inline-size:var(--fly-drawer-width, min(480px, 100%));max-inline-size:100%;background:var(--surface-overlay, #fff);-webkit-backdrop-filter:blur(30px) saturate(150%);backdrop-filter:blur(30px) saturate(150%);border-inline-start:1px solid var(--surface-border, rgba(0, 0, 0, .08));box-shadow:-16px 0 48px #00000038;animation:fly-drawer-in .28s cubic-bezier(.32,.72,0,1) both;will-change:transform}.fly-drawer__panel--sm{inline-size:var(--fly-drawer-width, min(360px, 100%))}.fly-drawer__panel--md{inline-size:var(--fly-drawer-width, min(480px, 100%))}.fly-drawer__panel--lg{inline-size:var(--fly-drawer-width, min(640px, 100%))}.fly-drawer__panel--xl{inline-size:var(--fly-drawer-width, min(960px, 94%))}.fly-drawer__panel--side-start{--fly-drawer-offset: -100%;inset-inline:0 auto;border-inline-start:none;border-inline-end:1px solid var(--surface-border, rgba(0, 0, 0, .08));box-shadow:16px 0 48px #00000038}.fly-drawer__panel--leaving{animation-name:fly-drawer-out;animation-duration:.24s;animation-timing-function:cubic-bezier(.4,0,1,1)}@keyframes fly-drawer-in{0%{transform:translate(var(--fly-drawer-offset))}to{transform:translate(0)}}@keyframes fly-drawer-out{0%{transform:translate(0)}to{transform:translate(var(--fly-drawer-offset))}}@keyframes fly-drawer-scrim-in{0%{opacity:0}to{opacity:1}}@keyframes fly-drawer-scrim-out{0%{opacity:1}to{opacity:0}}:host-context([dir=rtl]) .fly-drawer__panel{--fly-drawer-offset: -100%;box-shadow:16px 0 48px #00000038;border-inline-start:1px solid var(--surface-border, rgba(0, 0, 0, .08))}:host-context([dir=rtl]) .fly-drawer__panel--side-start{--fly-drawer-offset: 100%}.fly-drawer__header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 18px;border-block-end:1px solid var(--surface-border, rgba(0, 0, 0, .08));flex:0 0 auto}.fly-drawer__title{margin:0;font-size:16px;font-weight:600;color:var(--label-primary, #1d1d1f)}.fly-drawer__close{display:inline-flex;align-items:center;justify-content:center;inline-size:30px;block-size:30px;border:none;border-radius:8px;background:transparent;color:var(--label-secondary, #6e6e73);cursor:pointer}.fly-drawer__close:hover{background:#0000000f}.fly-drawer__body{flex:1 1 auto;min-block-size:0;overflow-y:auto;padding:var(--fly-drawer-body-padding, 16px 18px)}.fly-drawer__body--pad-none{--fly-drawer-body-padding: 0}.fly-drawer__body--pad-sm{--fly-drawer-body-padding: 12px 14px}.fly-drawer__body--pad-md{--fly-drawer-body-padding: 16px 18px}.fly-drawer__body--pad-lg{--fly-drawer-body-padding: 20px 22px}:host ::ng-deep [flyDrawerFooter]{flex:0 0 auto;display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:12px 18px;border-block-start:1px solid var(--surface-border, rgba(0, 0, 0, .08))}:host-context(html.dark-theme) .fly-drawer__close:hover{background:#ffffff14}@media(prefers-reduced-motion:reduce){.fly-drawer__scrim,.fly-drawer__panel,.fly-drawer__scrim--leaving,.fly-drawer__panel--leaving{animation:none}}@media(forced-colors:active){.fly-drawer__panel{border:1px solid CanvasText}}\n"] }]
|
|
9931
|
+
args: [{ selector: 'fly-drawer', standalone: true, imports: [CommonModule, A11yModule, TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, host: { '[class.fly-drawer--fixed]': "position() === 'fixed'" }, template: "<!--\r\n Windowed overlay drawer. Renders only while mounted (open or sliding out).\r\n Scrim + panel are absolutely positioned within the nearest positioned\r\n ancestor (the consuming app root must be position:relative).\r\n-->\r\n@if (rendered()) {\r\n <div\r\n class=\"fly-drawer__scrim\"\r\n [class.fly-drawer__scrim--leaving]=\"leaving()\"\r\n (click)=\"onScrimClick()\"\r\n aria-hidden=\"true\"\r\n ></div>\r\n\r\n <div\r\n class=\"fly-drawer__panel\"\r\n [ngClass]=\"panelClass()\"\r\n [class.fly-drawer__panel--leaving]=\"leaving()\"\r\n [style.--fly-drawer-width]=\"width()\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n [attr.aria-labelledby]=\"labelledBy()\"\r\n [attr.aria-label]=\"!labelledBy() && ariaLabel() ? (ariaLabel()! | translate) : null\"\r\n cdkTrapFocus\r\n (animationend)=\"onPanelAnimationEnd($event.animationName)\"\r\n >\r\n <!-- Header: custom slot, else heading + close \u2715. -->\r\n <ng-content select=\"[flyDrawerHeader]\">\r\n @if (heading()) {\r\n <header class=\"fly-drawer__header\">\r\n <h2 class=\"fly-drawer__title\" [id]=\"headingId\">{{ heading()! | translate }}</h2>\r\n @if (!hideCloseButton()) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-drawer__close\"\r\n (click)=\"requestClose()\"\r\n [attr.aria-label]=\"'common.action.close' | translate\"\r\n >\r\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n </header>\r\n }\r\n </ng-content>\r\n\r\n <div class=\"fly-drawer__body\" [ngClass]=\"'fly-drawer__body--pad-' + bodyPadding()\">\r\n <ng-content></ng-content>\r\n </div>\r\n\r\n <ng-content select=\"[flyDrawerFooter]\"></ng-content>\r\n </div>\r\n}\r\n", styles: [":host{position:absolute;inset:0;z-index:60;pointer-events:none}:host(.fly-drawer--fixed){position:fixed;block-size:100dvh;z-index:var(--z-overlay, 100)}.fly-drawer__scrim{position:absolute;inset:0;pointer-events:auto;background:#00000061;opacity:1;animation:fly-drawer-scrim-in .24s ease both}.fly-drawer__scrim--leaving{animation-name:fly-drawer-scrim-out;animation-duration:.2s}.fly-drawer__panel{--fly-drawer-offset: 100%;position:absolute;inset-block:0;inset-inline-end:0;pointer-events:auto;display:flex;flex-direction:column;min-block-size:0;inline-size:var(--fly-drawer-width, min(480px, 100%));max-inline-size:100%;background:var(--surface-overlay, #fff);-webkit-backdrop-filter:blur(30px) saturate(150%);backdrop-filter:blur(30px) saturate(150%);border-inline-start:1px solid var(--surface-border, rgba(0, 0, 0, .08));box-shadow:-16px 0 48px #00000038;animation:fly-drawer-in .28s cubic-bezier(.32,.72,0,1) both;will-change:transform}.fly-drawer__panel--sm{inline-size:var(--fly-drawer-width, min(360px, 100%))}.fly-drawer__panel--md{inline-size:var(--fly-drawer-width, min(480px, 100%))}.fly-drawer__panel--lg{inline-size:var(--fly-drawer-width, min(640px, 100%))}.fly-drawer__panel--xl{inline-size:var(--fly-drawer-width, min(960px, 94%))}.fly-drawer__panel--side-start{--fly-drawer-offset: -100%;inset-inline:0 auto;border-inline-start:none;border-inline-end:1px solid var(--surface-border, rgba(0, 0, 0, .08));box-shadow:16px 0 48px #00000038}.fly-drawer__panel--leaving{animation-name:fly-drawer-out;animation-duration:.24s;animation-timing-function:cubic-bezier(.4,0,1,1)}@keyframes fly-drawer-in{0%{transform:translate(var(--fly-drawer-offset))}to{transform:translate(0)}}@keyframes fly-drawer-out{0%{transform:translate(0)}to{transform:translate(var(--fly-drawer-offset))}}@keyframes fly-drawer-scrim-in{0%{opacity:0}to{opacity:1}}@keyframes fly-drawer-scrim-out{0%{opacity:1}to{opacity:0}}:host-context([dir=rtl]) .fly-drawer__panel{--fly-drawer-offset: -100%;box-shadow:16px 0 48px #00000038;border-inline-start:1px solid var(--surface-border, rgba(0, 0, 0, .08))}:host-context([dir=rtl]) .fly-drawer__panel--side-start{--fly-drawer-offset: 100%}.fly-drawer__header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 18px;border-block-end:1px solid var(--surface-border, rgba(0, 0, 0, .08));flex:0 0 auto}.fly-drawer__title{margin:0;font-size:16px;font-weight:600;color:var(--label-primary, #1d1d1f)}.fly-drawer__close{display:inline-flex;align-items:center;justify-content:center;inline-size:30px;block-size:30px;border:none;border-radius:8px;background:transparent;color:var(--label-secondary, #6e6e73);cursor:pointer}.fly-drawer__close:hover{background:#0000000f}.fly-drawer__body{flex:1 1 auto;min-block-size:0;overflow-y:auto;padding:var(--fly-drawer-body-padding, 16px 18px);container-type:inline-size}.fly-drawer__body--pad-none{--fly-drawer-body-padding: 0}.fly-drawer__body--pad-sm{--fly-drawer-body-padding: 12px 14px}.fly-drawer__body--pad-md{--fly-drawer-body-padding: 16px 18px}.fly-drawer__body--pad-lg{--fly-drawer-body-padding: 20px 22px}:host ::ng-deep [flyDrawerFooter]{flex:0 0 auto;display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:12px 18px;border-block-start:1px solid var(--surface-border, rgba(0, 0, 0, .08))}:host-context(html.dark-theme) .fly-drawer__close:hover{background:#ffffff14}@media(prefers-reduced-motion:reduce){.fly-drawer__scrim,.fly-drawer__panel,.fly-drawer__scrim--leaving,.fly-drawer__panel--leaving{animation:none}}@media(forced-colors:active){.fly-drawer__panel{border:1px solid CanvasText}}\n"] }]
|
|
9570
9932
|
}], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }], side: [{ type: i0.Input, args: [{ isSignal: true, alias: "side", required: false }] }], dismissOnScrim: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissOnScrim", required: false }] }], dismissOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissOnEscape", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], hideCloseButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideCloseButton", required: false }] }], bodyPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "bodyPadding", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], openChange: [{ type: i0.Output, args: ["openChange"] }], onEscape: [{
|
|
9571
9933
|
type: HostListener,
|
|
9572
9934
|
args: ['document:keydown.escape']
|
|
@@ -12928,6 +13290,13 @@ const ROOT_DRAFT_KEY = '__root__';
|
|
|
12928
13290
|
* eager load-all) — the explicit fix for the legacy unpaged-children scale bug
|
|
12929
13291
|
* — and each level pages independently via `loadMore`.
|
|
12930
13292
|
*
|
|
13293
|
+
* The `comments` core app owns no user profile data, so `FlyComment` carries
|
|
13294
|
+
* `authorUserId` and an OPTIONAL `authorDisplayName`. Rather than make every host
|
|
13295
|
+
* map names in itself (and render GUIDs when it forgets), bind
|
|
13296
|
+
* `[resolveDisplayName]` to `FlyUserDirectoryService.label` — see
|
|
13297
|
+
* {@link FlyCommentThreadComponent.resolveDisplayName}. Still transport-agnostic:
|
|
13298
|
+
* the resolver is a function the host hands over, not a call this component makes.
|
|
13299
|
+
*
|
|
12931
13300
|
* i18n is self-sufficient through the `comment.*` keys in `DS_BASELINE_LOCALES`
|
|
12932
13301
|
* (en/ar/fr/ur), overridable by any consumer key of the same name. RTL works via
|
|
12933
13302
|
* logical CSS. Styling uses DS theme tokens (`--surface-*`, `--label-*`,
|
|
@@ -12940,6 +13309,7 @@ const ROOT_DRAFT_KEY = '__root__';
|
|
|
12940
13309
|
* [rootPage]="rootPage()"
|
|
12941
13310
|
* [repliesByParent]="repliesByParent()"
|
|
12942
13311
|
* [currentUserId]="me.id"
|
|
13312
|
+
* [resolveDisplayName]="directory.label"
|
|
12943
13313
|
* [canModerateThread]="canModerate()"
|
|
12944
13314
|
* [isLocked]="thread().isLocked"
|
|
12945
13315
|
* (loadReplies)="onLoadReplies($event)"
|
|
@@ -12976,6 +13346,23 @@ class FlyCommentThreadComponent {
|
|
|
12976
13346
|
/** Thread-scoped moderator permission (lock/unlock). Additive — distinct from the
|
|
12977
13347
|
* per-comment `canModerate` flag, which gates delete-any on that specific comment. */
|
|
12978
13348
|
canModerateThread = input(false, ...(ngDevMode ? [{ debugName: "canModerateThread" }] : /* istanbul ignore next */ []));
|
|
13349
|
+
/**
|
|
13350
|
+
* Optional id→name lookup, for the common case where the backend DTO carries only
|
|
13351
|
+
* `authorUserId`. The `comments` core app owns no user profile data, so without
|
|
13352
|
+
* this every host had to map display names into `authorDisplayName` itself — and
|
|
13353
|
+
* a host that forgot rendered raw GUIDs at the reader.
|
|
13354
|
+
*
|
|
13355
|
+
* Bind `FlyUserDirectoryService.label` (a signal-backed read-through cache that
|
|
13356
|
+
* resolves on demand and batches). Because the component reads it during render,
|
|
13357
|
+
* the name appears by itself once the lookup lands — the host keeps a plain
|
|
13358
|
+
* page signal, with no `computed` derivation and no priming.
|
|
13359
|
+
*
|
|
13360
|
+
* The component still never calls an API: this is a function the HOST supplies,
|
|
13361
|
+
* exactly like `fly-entity-lookup`'s `LOOKUP_APP_NAME_RESOLVER`. Absent (or
|
|
13362
|
+
* returning `null`), rendering falls back as before. See {@link authorLabel} for
|
|
13363
|
+
* the resolution order.
|
|
13364
|
+
*/
|
|
13365
|
+
resolveDisplayName = input(null, ...(ngDevMode ? [{ debugName: "resolveDisplayName" }] : /* istanbul ignore next */ []));
|
|
12979
13366
|
// ── Outputs ──────────────────────────────────────────────────────────────────
|
|
12980
13367
|
/** Expand a collapsed reply subtree — always page 1 of that parent. */
|
|
12981
13368
|
loadReplies = output();
|
|
@@ -13036,6 +13423,25 @@ class FlyCommentThreadComponent {
|
|
|
13036
13423
|
const me = this.currentUserId();
|
|
13037
13424
|
return !!me && me === comment.authorUserId;
|
|
13038
13425
|
}
|
|
13426
|
+
/**
|
|
13427
|
+
* The name to render for a comment's author. Resolution order:
|
|
13428
|
+
* explicit `authorDisplayName` (a host that already knows the name, or a
|
|
13429
|
+
* backend that returns one, always wins) → {@link resolveDisplayName} →
|
|
13430
|
+
* the raw `authorUserId`.
|
|
13431
|
+
*
|
|
13432
|
+
* The raw id remains the last resort rather than a placeholder: it is stable,
|
|
13433
|
+
* unique, and lets a reader correlate two comments by the same author even
|
|
13434
|
+
* when the directory is unreachable.
|
|
13435
|
+
*/
|
|
13436
|
+
authorLabel(comment) {
|
|
13437
|
+
if (comment.authorDisplayName)
|
|
13438
|
+
return comment.authorDisplayName;
|
|
13439
|
+
return this.resolveDisplayName()?.(comment.authorUserId) || comment.authorUserId;
|
|
13440
|
+
}
|
|
13441
|
+
/** First character of {@link authorLabel}, for the avatar-initials fallback. */
|
|
13442
|
+
authorInitial(comment) {
|
|
13443
|
+
return this.authorLabel(comment).charAt(0);
|
|
13444
|
+
}
|
|
13039
13445
|
canReport(comment) {
|
|
13040
13446
|
return !this.isReadonly() && !comment.isDeleted && !this.isOwn(comment) && !!this.currentUserId();
|
|
13041
13447
|
}
|
|
@@ -13206,16 +13612,16 @@ class FlyCommentThreadComponent {
|
|
|
13206
13612
|
this._drafts.set(map);
|
|
13207
13613
|
}
|
|
13208
13614
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyCommentThreadComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13209
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyCommentThreadComponent, isStandalone: true, selector: "fly-comment-thread", inputs: { rootPage: { classPropertyName: "rootPage", publicName: "rootPage", isSignal: true, isRequired: true, transformFunction: null }, repliesByParent: { classPropertyName: "repliesByParent", publicName: "repliesByParent", isSignal: true, isRequired: false, transformFunction: null }, sort: { classPropertyName: "sort", publicName: "sort", isSignal: true, isRequired: false, transformFunction: null }, isLocked: { classPropertyName: "isLocked", publicName: "isLocked", isSignal: true, isRequired: false, transformFunction: null }, currentUserId: { classPropertyName: "currentUserId", publicName: "currentUserId", isSignal: true, isRequired: false, transformFunction: null }, isReadonly: { classPropertyName: "isReadonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, canModerateThread: { classPropertyName: "canModerateThread", publicName: "canModerateThread", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { loadReplies: "loadReplies", loadMore: "loadMore", submit: "submit", edit: "edit", delete: "delete", report: "report", lockToggle: "lockToggle", sortChange: "sortChange" }, host: { properties: { "class.fly-comment-thread--locked": "isLocked()", "class.fly-comment-thread--readonly": "isReadonly()" }, classAttribute: "fly-comment-thread" }, ngImport: i0, template: "<div class=\"fly-comment-thread__body\">\r\n <!-- \u2500\u2500 Header: count + sort + lock toggle \u2500\u2500 -->\r\n <header class=\"fly-comment-thread__header\">\r\n <span class=\"fly-comment-thread__count\">\r\n {{ 'comment.count' | translate: { n: rootPage().total } }}\r\n </span>\r\n\r\n <div class=\"fly-comment-thread__header-actions\">\r\n <div class=\"fly-comment-thread__sort\" role=\"group\" [attr.aria-label]=\"'comment.sort_label' | translate\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__sort-btn\"\r\n [class.fly-comment-thread__sort-btn--active]=\"sort() === 'newest'\"\r\n (click)=\"onSortPick('newest')\">\r\n {{ 'comment.sort_newest' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__sort-btn\"\r\n [class.fly-comment-thread__sort-btn--active]=\"sort() === 'oldest'\"\r\n (click)=\"onSortPick('oldest')\">\r\n {{ 'comment.sort_oldest' | translate }}\r\n </button>\r\n </div>\r\n\r\n @if (canModerateThread() && !isReadonly()) {\r\n <button type=\"button\" class=\"fly-comment-thread__lock-btn\" (click)=\"toggleLock()\">\r\n {{ (isLocked() ? 'comment.unlock' : 'comment.lock') | translate }}\r\n </button>\r\n }\r\n </div>\r\n </header>\r\n\r\n @if (isLocked()) {\r\n <p class=\"fly-comment-thread__locked-notice\">{{ 'comment.locked_notice' | translate }}</p>\r\n }\r\n\r\n <!-- \u2500\u2500 Root composer \u2500\u2500 -->\r\n @if (canReply()) {\r\n <div class=\"fly-comment-thread__composer\">\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"3\"\r\n [id]=\"fieldId('root-composer')\"\r\n [attr.aria-label]=\"'comment.composer_placeholder' | translate\"\r\n [placeholder]=\"'comment.composer_placeholder' | translate\"\r\n [ngModel]=\"rootDraft()\"\r\n (ngModelChange)=\"onRootDraftChange($event)\"></textarea>\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__submit-btn\"\r\n [disabled]=\"!rootDraft().trim()\"\r\n (click)=\"submitRoot()\">\r\n {{ 'comment.post' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- \u2500\u2500 Root comments \u2500\u2500 -->\r\n @if (rootPage().items.length === 0) {\r\n <p class=\"fly-comment-thread__empty\">{{ 'comment.empty' | translate }}</p>\r\n } @else {\r\n <ul class=\"fly-comment-thread__list\" role=\"list\">\r\n @for (c of rootPage().items; track c.id) {\r\n <ng-container [ngTemplateOutlet]=\"commentNode\" [ngTemplateOutletContext]=\"{ $implicit: c }\"></ng-container>\r\n }\r\n </ul>\r\n\r\n @if (rootPage().hasMore) {\r\n <button type=\"button\" class=\"fly-comment-thread__load-more\" (click)=\"loadMoreRoot()\">\r\n {{ 'comment.load_more' | translate }}\r\n </button>\r\n }\r\n }\r\n</div>\r\n\r\n<!-- \u2500\u2500 Recursive comment node template \u2500\u2500 -->\r\n<ng-template #commentNode let-comment>\r\n <li class=\"fly-comment-thread__item\" [attr.data-depth]=\"comment.depth\">\r\n <div class=\"fly-comment-thread__row\">\r\n <div class=\"fly-comment-thread__avatar\" aria-hidden=\"true\">\r\n @if (comment.authorAvatarUrl) {\r\n <img [src]=\"comment.authorAvatarUrl\" alt=\"\" />\r\n } @else {\r\n <span class=\"fly-comment-thread__avatar-fallback\">{{ (comment.authorDisplayName || comment.authorUserId)[0] }}</span>\r\n }\r\n </div>\r\n\r\n <div class=\"fly-comment-thread__content\">\r\n <div class=\"fly-comment-thread__meta\">\r\n <span class=\"fly-comment-thread__author\">{{ comment.authorDisplayName || comment.authorUserId }}</span>\r\n <span class=\"fly-comment-thread__timestamp\">{{ formatDate(comment.createdAt) }}</span>\r\n @if (comment.isEdited && !comment.isDeleted) {\r\n <span class=\"fly-comment-thread__edited\">{{ 'comment.edited' | translate }}</span>\r\n }\r\n </div>\r\n\r\n @if (comment.isDeleted) {\r\n <p class=\"fly-comment-thread__tombstone\">{{ 'comment.deleted' | translate }}</p>\r\n } @else if (editingId() === comment.id) {\r\n <div class=\"fly-comment-thread__edit-box\">\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"3\"\r\n [ngModel]=\"comment.body\"\r\n (ngModelChange)=\"onEditDraftChange($event)\"></textarea>\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"cancelEdit()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-comment-thread__submit-btn\" (click)=\"saveEdit(comment)\">\r\n {{ 'comment.save' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n } @else {\r\n <p class=\"fly-comment-thread__text\">{{ comment.body }}</p>\r\n }\r\n\r\n @if (!isReadonly() && !comment.isDeleted && editingId() !== comment.id) {\r\n <div class=\"fly-comment-thread__actions\">\r\n @if (canReply()) {\r\n <button type=\"button\" class=\"fly-comment-thread__action-btn\" (click)=\"openReplyBox(comment.id)\">\r\n {{ 'comment.reply' | translate }}\r\n </button>\r\n }\r\n @if (canEditComment(comment)) {\r\n <button type=\"button\" class=\"fly-comment-thread__action-btn\" (click)=\"startEdit(comment)\">\r\n {{ 'comment.edit' | translate }}\r\n </button>\r\n }\r\n @if (canDeleteComment(comment)) {\r\n <button type=\"button\" class=\"fly-comment-thread__action-btn fly-comment-thread__action-btn--danger\" (click)=\"requestDelete(comment)\">\r\n {{ 'comment.delete' | translate }}\r\n </button>\r\n }\r\n @if (canReport(comment)) {\r\n <button type=\"button\" class=\"fly-comment-thread__action-btn\" (click)=\"openReport(comment)\">\r\n {{ 'comment.report' | translate }}\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n @if (confirmingDeleteId() === comment.id) {\r\n <div class=\"fly-comment-thread__confirm\">\r\n <span>{{ 'comment.delete_confirm' | translate }}</span>\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"cancelDelete()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-comment-thread__danger-btn\" (click)=\"confirmDelete(comment)\">\r\n {{ 'comment.delete' | translate }}\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (isReplyBoxOpen(comment.id)) {\r\n <div class=\"fly-comment-thread__composer fly-comment-thread__composer--reply\">\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"2\"\r\n [attr.aria-label]=\"'comment.reply_placeholder' | translate\"\r\n [placeholder]=\"'comment.reply_placeholder' | translate\"\r\n [ngModel]=\"replyDraft(comment.id)\"\r\n (ngModelChange)=\"onReplyDraftChange(comment.id, $event)\"></textarea>\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"closeReplyBox(comment.id)\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__submit-btn\"\r\n [disabled]=\"!replyDraft(comment.id).trim()\"\r\n (click)=\"submitReply(comment.id)\">\r\n {{ 'comment.post' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n\r\n @if (comment.replyCount > 0) {\r\n <button type=\"button\" class=\"fly-comment-thread__toggle-replies\" (click)=\"toggleReplies(comment)\">\r\n {{ (isExpanded(comment.id) ? 'comment.hide_replies' : 'comment.show_replies') | translate: { n: comment.replyCount } }}\r\n </button>\r\n }\r\n\r\n @if (isExpanded(comment.id)) {\r\n <ul class=\"fly-comment-thread__list fly-comment-thread__list--nested\" role=\"list\">\r\n @for (child of repliesFor(comment.id)?.items ?? []; track child.id) {\r\n <ng-container [ngTemplateOutlet]=\"commentNode\" [ngTemplateOutletContext]=\"{ $implicit: child }\"></ng-container>\r\n }\r\n </ul>\r\n @if (repliesFor(comment.id)?.hasMore) {\r\n <button type=\"button\" class=\"fly-comment-thread__load-more fly-comment-thread__load-more--nested\" (click)=\"loadMoreReplies(comment.id)\">\r\n {{ 'comment.load_more' | translate }}\r\n </button>\r\n }\r\n }\r\n </div>\r\n </div>\r\n </li>\r\n</ng-template>\r\n\r\n<!-- \u2500\u2500 Report dialog \u2500\u2500 -->\r\n@if (reportingId()) {\r\n <div class=\"fly-comment-thread__overlay\">\r\n <div class=\"fly-comment-thread__overlay-backdrop\" role=\"button\" tabindex=\"0\"\r\n [attr.aria-label]=\"'common.action.cancel' | translate\"\r\n (click)=\"closeReport()\"\r\n (keydown.enter)=\"closeReport()\"\r\n (keydown.space)=\"onOverlayBackdropSpace($event)\"></div>\r\n <div class=\"fly-comment-thread__dialog\" role=\"dialog\" [attr.aria-label]=\"'comment.report_title' | translate\">\r\n <h3 class=\"fly-comment-thread__dialog-title\">{{ 'comment.report_title' | translate }}</h3>\r\n\r\n <fieldset class=\"fly-comment-thread__reason-group\">\r\n <legend>{{ 'comment.report_reason_label' | translate }}</legend>\r\n @for (reason of reportReasons; track reason) {\r\n <label class=\"fly-comment-thread__reason-option\">\r\n <input\r\n type=\"radio\"\r\n name=\"fly-comment-report-reason\"\r\n [value]=\"reason\"\r\n [checked]=\"reportReason() === reason\"\r\n (change)=\"onReportReasonChange(reason)\" />\r\n <span>{{ 'comment.reason.' + reason | translate }}</span>\r\n </label>\r\n }\r\n </fieldset>\r\n\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"3\"\r\n [attr.aria-label]=\"'comment.report_detail_label' | translate\"\r\n [placeholder]=\"'comment.report_detail_label' | translate\"\r\n [ngModel]=\"reportDetail()\"\r\n (ngModelChange)=\"onReportDetailChange($event)\"></textarea>\r\n\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"closeReport()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-comment-thread__submit-btn\" [disabled]=\"!reportReason()\" (click)=\"submitReport()\">\r\n {{ 'comment.report_submit' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--surface-card, #fff);--_surface-hover: var(--surface-hover, #f1f5f9);--_border: var(--separator, var(--surface-border, #e2e8f0));--_text: var(--label-primary, var(--text-color, #0f172a));--_text-subtle: var(--label-secondary, var(--text-color-secondary, #64748b));--_text-tertiary: var(--label-tertiary, #94a3b8);--_fill: var(--fill-tertiary, #f3f4f6);--_accent: var(--accent);--_danger: var(--system-red, #ef4444);--_radius: 8px;display:block;inline-size:100%;color:var(--_text);font-size:13px}.fly-comment-thread__body{display:flex;flex-direction:column;gap:16px}.fly-comment-thread__header{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.fly-comment-thread__count{font-weight:700;font-size:14px}.fly-comment-thread__header-actions{display:flex;align-items:center;gap:10px}.fly-comment-thread__sort{display:inline-flex;border:1px solid var(--_border);border-radius:var(--_radius);overflow:hidden}.fly-comment-thread__sort-btn{padding:5px 12px;border:none;border-inline-end:1px solid var(--_border);background:transparent;color:var(--_text-subtle);font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__sort-btn:last-child{border-inline-end:none}.fly-comment-thread__sort-btn--active{background:var(--_accent);color:#fff}.fly-comment-thread__lock-btn{padding:5px 12px;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);color:var(--_text);font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__lock-btn:hover{background:var(--_surface-hover)}.fly-comment-thread__locked-notice{margin:0;padding:8px 12px;border-radius:var(--_radius);background:var(--_fill);color:var(--_text-subtle);font-size:12px}.fly-comment-thread__empty{margin:0;color:var(--_text-subtle);text-align:center;padding:16px 0}.fly-comment-thread__composer{display:flex;flex-direction:column;gap:8px}.fly-comment-thread__composer--reply{margin-block-start:8px}.fly-comment-thread__textarea{inline-size:100%;padding:8px 12px;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);color:var(--_text);font:inherit;line-height:1.5;resize:vertical}.fly-comment-thread__textarea:focus-visible{outline:2px solid var(--_accent);outline-offset:1px}.fly-comment-thread__composer-actions{display:flex;justify-content:flex-end;gap:8px}.fly-comment-thread__submit-btn{padding:6px 16px;border:none;border-radius:var(--_radius);background:var(--_accent);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__submit-btn:hover:not(:disabled){opacity:.9}.fly-comment-thread__submit-btn:disabled{opacity:.5;cursor:not-allowed}.fly-comment-thread__cancel-btn{padding:6px 14px;border:1px solid var(--_border);border-radius:var(--_radius);background:transparent;color:var(--_text-subtle);font:inherit;cursor:pointer}.fly-comment-thread__cancel-btn:hover{background:var(--_surface-hover)}.fly-comment-thread__danger-btn{padding:6px 14px;border:none;border-radius:var(--_radius);background:var(--_danger);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__danger-btn:hover{opacity:.9}.fly-comment-thread__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:14px}.fly-comment-thread__list--nested{margin-block-start:10px;margin-inline-start:20px;padding-inline-start:12px;border-inline-start:2px solid var(--_border);gap:12px}.fly-comment-thread__item{display:block}.fly-comment-thread__row{display:flex;gap:10px;align-items:flex-start}.fly-comment-thread__avatar{inline-size:32px;block-size:32px;border-radius:50%;overflow:hidden;flex:none;background:var(--_fill);display:flex;align-items:center;justify-content:center}.fly-comment-thread__avatar img{inline-size:100%;block-size:100%;object-fit:cover}.fly-comment-thread__avatar-fallback{font-weight:700;color:var(--_text-subtle);text-transform:uppercase}.fly-comment-thread__content{flex:1 1 auto;min-inline-size:0;display:flex;flex-direction:column;gap:4px}.fly-comment-thread__meta{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap}.fly-comment-thread__author{font-weight:700;color:var(--_text)}.fly-comment-thread__timestamp{font-size:11px;color:var(--_text-tertiary)}.fly-comment-thread__edited{font-size:11px;color:var(--_text-tertiary);font-style:italic}.fly-comment-thread__text{margin:0;color:var(--_text);line-height:1.5;white-space:pre-wrap;overflow-wrap:anywhere}.fly-comment-thread__tombstone{margin:0;color:var(--_text-tertiary);font-style:italic}.fly-comment-thread__edit-box{display:flex;flex-direction:column;gap:6px}.fly-comment-thread__actions{display:flex;gap:12px;margin-block-start:2px}.fly-comment-thread__action-btn{border:none;background:transparent;padding:0;color:var(--_text-subtle);font:inherit;font-size:12px;font-weight:600;cursor:pointer}.fly-comment-thread__action-btn:hover{color:var(--_accent);text-decoration:underline}.fly-comment-thread__action-btn--danger:hover{color:var(--_danger)}.fly-comment-thread__confirm{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:var(--_radius);background:var(--_fill);font-size:12px;color:var(--_text-subtle)}.fly-comment-thread__toggle-replies{align-self:flex-start;border:none;background:transparent;padding:0;margin-block-start:4px;color:var(--_accent);font:inherit;font-size:12px;font-weight:700;cursor:pointer}.fly-comment-thread__toggle-replies:hover{text-decoration:underline}.fly-comment-thread__load-more{align-self:flex-start;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);padding:6px 14px;color:var(--_text);font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__load-more:hover{background:var(--_surface-hover)}.fly-comment-thread__load-more--nested{margin-inline-start:20px}.fly-comment-thread__overlay{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;z-index:1000}.fly-comment-thread__overlay-backdrop{position:absolute;inset:0;background:#00000073;border:none;padding:0;margin:0;cursor:pointer}.fly-comment-thread__dialog{position:relative;z-index:1;inline-size:min(420px,90vw);max-block-size:85vh;overflow-y:auto;background:var(--_surface);border-radius:12px;padding:20px;display:flex;flex-direction:column;gap:14px;box-shadow:0 12px 32px #00000040}.fly-comment-thread__dialog-title{margin:0;font-size:16px;font-weight:700}.fly-comment-thread__reason-group{display:flex;flex-direction:column;gap:8px;margin:0;padding:0;border:none}.fly-comment-thread__reason-group legend{font-weight:600;padding:0;margin-block-end:4px}.fly-comment-thread__reason-option{display:flex;align-items:center;gap:8px;cursor:pointer}.fly-comment-thread__reason-option input{accent-color:var(--_accent);cursor:pointer}:host(.fly-comment-thread--readonly) .fly-comment-thread__composer,:host(.fly-comment-thread--readonly) .fly-comment-thread__actions{display:none}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
13615
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyCommentThreadComponent, isStandalone: true, selector: "fly-comment-thread", inputs: { rootPage: { classPropertyName: "rootPage", publicName: "rootPage", isSignal: true, isRequired: true, transformFunction: null }, repliesByParent: { classPropertyName: "repliesByParent", publicName: "repliesByParent", isSignal: true, isRequired: false, transformFunction: null }, sort: { classPropertyName: "sort", publicName: "sort", isSignal: true, isRequired: false, transformFunction: null }, isLocked: { classPropertyName: "isLocked", publicName: "isLocked", isSignal: true, isRequired: false, transformFunction: null }, currentUserId: { classPropertyName: "currentUserId", publicName: "currentUserId", isSignal: true, isRequired: false, transformFunction: null }, isReadonly: { classPropertyName: "isReadonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, canModerateThread: { classPropertyName: "canModerateThread", publicName: "canModerateThread", isSignal: true, isRequired: false, transformFunction: null }, resolveDisplayName: { classPropertyName: "resolveDisplayName", publicName: "resolveDisplayName", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { loadReplies: "loadReplies", loadMore: "loadMore", submit: "submit", edit: "edit", delete: "delete", report: "report", lockToggle: "lockToggle", sortChange: "sortChange" }, host: { properties: { "class.fly-comment-thread--locked": "isLocked()", "class.fly-comment-thread--readonly": "isReadonly()" }, classAttribute: "fly-comment-thread" }, ngImport: i0, template: "<div class=\"fly-comment-thread__body\">\r\n <!-- \u2500\u2500 Header: count + one combined sort/lock icon-button toolbar \u2500\u2500 -->\r\n <header class=\"fly-comment-thread__header\">\r\n <span class=\"fly-comment-thread__count\">\r\n {{ 'comment.count' | translate: { n: rootPage().total } }}\r\n </span>\r\n\r\n <div class=\"fly-comment-thread__toolbar\" role=\"group\" [attr.aria-label]=\"'comment.sort_label' | translate\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__icon-btn\"\r\n [class.fly-comment-thread__icon-btn--active]=\"sort() === 'newest'\"\r\n [attr.aria-pressed]=\"sort() === 'newest'\"\r\n [flyTooltip]=\"'comment.sort_newest' | translate\"\r\n [attr.aria-label]=\"'comment.sort_newest' | translate\"\r\n (click)=\"onSortPick('newest')\">\r\n <i class=\"pi pi-sort-amount-down\" aria-hidden=\"true\"></i>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__icon-btn\"\r\n [class.fly-comment-thread__icon-btn--active]=\"sort() === 'oldest'\"\r\n [attr.aria-pressed]=\"sort() === 'oldest'\"\r\n [flyTooltip]=\"'comment.sort_oldest' | translate\"\r\n [attr.aria-label]=\"'comment.sort_oldest' | translate\"\r\n (click)=\"onSortPick('oldest')\">\r\n <i class=\"pi pi-sort-amount-up\" aria-hidden=\"true\"></i>\r\n </button>\r\n\r\n @if (canModerateThread() && !isReadonly()) {\r\n <span class=\"fly-comment-thread__toolbar-sep\" aria-hidden=\"true\"></span>\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__icon-btn\"\r\n [class.fly-comment-thread__icon-btn--active]=\"isLocked()\"\r\n [attr.aria-pressed]=\"isLocked()\"\r\n [flyTooltip]=\"(isLocked() ? 'comment.unlock' : 'comment.lock') | translate\"\r\n [attr.aria-label]=\"(isLocked() ? 'comment.unlock' : 'comment.lock') | translate\"\r\n (click)=\"toggleLock()\">\r\n <i class=\"pi\" [class.pi-lock]=\"!isLocked()\" [class.pi-lock-open]=\"isLocked()\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n </div>\r\n </header>\r\n\r\n @if (isLocked()) {\r\n <p class=\"fly-comment-thread__locked-notice\">{{ 'comment.locked_notice' | translate }}</p>\r\n }\r\n\r\n <!-- \u2500\u2500 Root composer \u2500\u2500 -->\r\n @if (canReply()) {\r\n <div class=\"fly-comment-thread__composer\">\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"3\"\r\n [id]=\"fieldId('root-composer')\"\r\n [attr.aria-label]=\"'comment.composer_placeholder' | translate\"\r\n [placeholder]=\"'comment.composer_placeholder' | translate\"\r\n [ngModel]=\"rootDraft()\"\r\n (ngModelChange)=\"onRootDraftChange($event)\"></textarea>\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__submit-btn\"\r\n [disabled]=\"!rootDraft().trim()\"\r\n (click)=\"submitRoot()\">\r\n {{ 'comment.post' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- \u2500\u2500 Root comments \u2500\u2500 -->\r\n @if (rootPage().items.length === 0) {\r\n <p class=\"fly-comment-thread__empty\">{{ 'comment.empty' | translate }}</p>\r\n } @else {\r\n <ul class=\"fly-comment-thread__list\" role=\"list\">\r\n @for (c of rootPage().items; track c.id) {\r\n <ng-container [ngTemplateOutlet]=\"commentNode\" [ngTemplateOutletContext]=\"{ $implicit: c }\"></ng-container>\r\n }\r\n </ul>\r\n\r\n @if (rootPage().hasMore) {\r\n <button type=\"button\" class=\"fly-comment-thread__load-more\" (click)=\"loadMoreRoot()\">\r\n {{ 'comment.load_more' | translate }}\r\n </button>\r\n }\r\n }\r\n</div>\r\n\r\n<!-- \u2500\u2500 Recursive comment node template \u2500\u2500 -->\r\n<ng-template #commentNode let-comment>\r\n <li class=\"fly-comment-thread__item\" [attr.data-depth]=\"comment.depth\">\r\n <div class=\"fly-comment-thread__row\">\r\n <div class=\"fly-comment-thread__avatar\" aria-hidden=\"true\">\r\n @if (comment.authorAvatarUrl) {\r\n <img [src]=\"comment.authorAvatarUrl\" alt=\"\" />\r\n } @else {\r\n <span class=\"fly-comment-thread__avatar-fallback\">{{ authorInitial(comment) }}</span>\r\n }\r\n </div>\r\n\r\n <div class=\"fly-comment-thread__content\">\r\n <div class=\"fly-comment-thread__meta\">\r\n <span class=\"fly-comment-thread__author\">{{ authorLabel(comment) }}</span>\r\n <span class=\"fly-comment-thread__timestamp\">{{ formatDate(comment.createdAt) }}</span>\r\n @if (comment.isEdited && !comment.isDeleted) {\r\n <span class=\"fly-comment-thread__edited\">{{ 'comment.edited' | translate }}</span>\r\n }\r\n </div>\r\n\r\n @if (comment.isDeleted) {\r\n <p class=\"fly-comment-thread__tombstone\">{{ 'comment.deleted' | translate }}</p>\r\n } @else if (editingId() === comment.id) {\r\n <div class=\"fly-comment-thread__edit-box\">\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"3\"\r\n [ngModel]=\"comment.body\"\r\n (ngModelChange)=\"onEditDraftChange($event)\"></textarea>\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"cancelEdit()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-comment-thread__submit-btn\" (click)=\"saveEdit(comment)\">\r\n {{ 'comment.save' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"fly-comment-thread__text-row\">\r\n <p class=\"fly-comment-thread__text\">{{ comment.body }}</p>\r\n @if (!isReadonly()) {\r\n <div class=\"fly-comment-thread__actions\">\r\n @if (canReply()) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__icon-btn\"\r\n [flyTooltip]=\"'comment.reply' | translate\"\r\n [attr.aria-label]=\"'comment.reply' | translate\"\r\n (click)=\"openReplyBox(comment.id)\">\r\n <i class=\"pi pi-reply\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n @if (canEditComment(comment)) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__icon-btn\"\r\n [flyTooltip]=\"'comment.edit' | translate\"\r\n [attr.aria-label]=\"'comment.edit' | translate\"\r\n (click)=\"startEdit(comment)\">\r\n <i class=\"pi pi-pencil\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n @if (canDeleteComment(comment)) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__icon-btn fly-comment-thread__icon-btn--danger\"\r\n [flyTooltip]=\"'comment.delete' | translate\"\r\n [attr.aria-label]=\"'comment.delete' | translate\"\r\n (click)=\"requestDelete(comment)\">\r\n <i class=\"pi pi-trash\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n @if (canReport(comment)) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__icon-btn\"\r\n [flyTooltip]=\"'comment.report' | translate\"\r\n [attr.aria-label]=\"'comment.report' | translate\"\r\n (click)=\"openReport(comment)\">\r\n <i class=\"pi pi-flag\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n @if (confirmingDeleteId() === comment.id) {\r\n <div class=\"fly-comment-thread__confirm\">\r\n <span>{{ 'comment.delete_confirm' | translate }}</span>\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"cancelDelete()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-comment-thread__danger-btn\" (click)=\"confirmDelete(comment)\">\r\n {{ 'comment.delete' | translate }}\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (isReplyBoxOpen(comment.id)) {\r\n <div class=\"fly-comment-thread__composer fly-comment-thread__composer--reply\">\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"2\"\r\n [attr.aria-label]=\"'comment.reply_placeholder' | translate\"\r\n [placeholder]=\"'comment.reply_placeholder' | translate\"\r\n [ngModel]=\"replyDraft(comment.id)\"\r\n (ngModelChange)=\"onReplyDraftChange(comment.id, $event)\"></textarea>\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"closeReplyBox(comment.id)\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__submit-btn\"\r\n [disabled]=\"!replyDraft(comment.id).trim()\"\r\n (click)=\"submitReply(comment.id)\">\r\n {{ 'comment.post' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n\r\n @if (comment.replyCount > 0) {\r\n <button type=\"button\" class=\"fly-comment-thread__toggle-replies\" (click)=\"toggleReplies(comment)\">\r\n {{ (isExpanded(comment.id) ? 'comment.hide_replies' : 'comment.show_replies') | translate: { n: comment.replyCount } }}\r\n </button>\r\n }\r\n\r\n @if (isExpanded(comment.id)) {\r\n <ul class=\"fly-comment-thread__list fly-comment-thread__list--nested\" role=\"list\">\r\n @for (child of repliesFor(comment.id)?.items ?? []; track child.id) {\r\n <ng-container [ngTemplateOutlet]=\"commentNode\" [ngTemplateOutletContext]=\"{ $implicit: child }\"></ng-container>\r\n }\r\n </ul>\r\n @if (repliesFor(comment.id)?.hasMore) {\r\n <button type=\"button\" class=\"fly-comment-thread__load-more fly-comment-thread__load-more--nested\" (click)=\"loadMoreReplies(comment.id)\">\r\n {{ 'comment.load_more' | translate }}\r\n </button>\r\n }\r\n }\r\n </div>\r\n </div>\r\n </li>\r\n</ng-template>\r\n\r\n<!-- \u2500\u2500 Report dialog \u2500\u2500 -->\r\n@if (reportingId()) {\r\n <div class=\"fly-comment-thread__overlay\">\r\n <div class=\"fly-comment-thread__overlay-backdrop\" role=\"button\" tabindex=\"0\"\r\n [attr.aria-label]=\"'common.action.cancel' | translate\"\r\n (click)=\"closeReport()\"\r\n (keydown.enter)=\"closeReport()\"\r\n (keydown.space)=\"onOverlayBackdropSpace($event)\"></div>\r\n <div class=\"fly-comment-thread__dialog\" role=\"dialog\" [attr.aria-label]=\"'comment.report_title' | translate\">\r\n <h3 class=\"fly-comment-thread__dialog-title\">{{ 'comment.report_title' | translate }}</h3>\r\n\r\n <fieldset class=\"fly-comment-thread__reason-group\">\r\n <legend>{{ 'comment.report_reason_label' | translate }}</legend>\r\n @for (reason of reportReasons; track reason) {\r\n <label class=\"fly-comment-thread__reason-option\">\r\n <input\r\n type=\"radio\"\r\n name=\"fly-comment-report-reason\"\r\n [value]=\"reason\"\r\n [checked]=\"reportReason() === reason\"\r\n (change)=\"onReportReasonChange(reason)\" />\r\n <span>{{ 'comment.reason.' + reason | translate }}</span>\r\n </label>\r\n }\r\n </fieldset>\r\n\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"3\"\r\n [attr.aria-label]=\"'comment.report_detail_label' | translate\"\r\n [placeholder]=\"'comment.report_detail_label' | translate\"\r\n [ngModel]=\"reportDetail()\"\r\n (ngModelChange)=\"onReportDetailChange($event)\"></textarea>\r\n\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"closeReport()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-comment-thread__submit-btn\" [disabled]=\"!reportReason()\" (click)=\"submitReport()\">\r\n {{ 'comment.report_submit' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--surface-card, #fff);--_surface-hover: var(--surface-hover, #f1f5f9);--_border: var(--separator, var(--surface-border, #e2e8f0));--_text: var(--label-primary, var(--text-color, #0f172a));--_text-subtle: var(--label-secondary, var(--text-color-secondary, #64748b));--_text-tertiary: var(--label-tertiary, #94a3b8);--_fill: var(--fill-tertiary, #f3f4f6);--_accent: var(--accent);--_danger: var(--system-red, #ef4444);--_radius: 8px;display:block;inline-size:100%;color:var(--_text);font-size:13px}.fly-comment-thread__body{display:flex;flex-direction:column;gap:16px}.fly-comment-thread__header{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.fly-comment-thread__count{font-weight:700;font-size:14px}.fly-comment-thread__toolbar{display:inline-flex;align-items:center;gap:2px;padding:2px;border:1px solid var(--_border);border-radius:calc(var(--_radius) + 2px);background:var(--_surface)}.fly-comment-thread__toolbar-sep{inline-size:1px;block-size:18px;margin-inline:2px;background:var(--_border)}.fly-comment-thread__icon-btn{display:inline-flex;align-items:center;justify-content:center;inline-size:26px;block-size:26px;flex:none;padding:0;border:none;border-radius:50%;background:transparent;color:var(--_text-subtle);cursor:pointer;transition:background var(--t-state, .15s),color var(--t-state, .15s)}.fly-comment-thread__icon-btn .pi{font-size:13px}.fly-comment-thread__icon-btn:hover{background:var(--_surface-hover);color:var(--_text)}.fly-comment-thread__icon-btn:focus-visible{outline:2px solid var(--_accent);outline-offset:1px}.fly-comment-thread__icon-btn--active{background:var(--_accent);color:#fff}.fly-comment-thread__icon-btn--active:hover{background:var(--_accent);opacity:.9}.fly-comment-thread__icon-btn--danger:hover{background:var(--_danger);color:#fff}.fly-comment-thread__locked-notice{margin:0;padding:8px 12px;border-radius:var(--_radius);background:var(--_fill);color:var(--_text-subtle);font-size:12px}.fly-comment-thread__empty{margin:0;color:var(--_text-subtle);text-align:center;padding:16px 0}.fly-comment-thread__composer{display:flex;flex-direction:column;gap:8px}.fly-comment-thread__composer--reply{margin-block-start:8px}.fly-comment-thread__textarea{inline-size:100%;padding:8px 12px;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);color:var(--_text);font:inherit;line-height:1.5;resize:vertical}.fly-comment-thread__textarea:focus-visible{outline:2px solid var(--_accent);outline-offset:1px}.fly-comment-thread__composer-actions{display:flex;justify-content:flex-end;gap:8px}.fly-comment-thread__submit-btn{padding:6px 16px;border:none;border-radius:var(--_radius);background:var(--_accent);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__submit-btn:hover:not(:disabled){opacity:.9}.fly-comment-thread__submit-btn:disabled{opacity:.5;cursor:not-allowed}.fly-comment-thread__cancel-btn{padding:6px 14px;border:1px solid var(--_border);border-radius:var(--_radius);background:transparent;color:var(--_text-subtle);font:inherit;cursor:pointer}.fly-comment-thread__cancel-btn:hover{background:var(--_surface-hover)}.fly-comment-thread__danger-btn{padding:6px 14px;border:none;border-radius:var(--_radius);background:var(--_danger);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__danger-btn:hover{opacity:.9}.fly-comment-thread__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:14px}.fly-comment-thread__list--nested{margin-block-start:10px;margin-inline-start:20px;padding-inline-start:12px;border-inline-start:2px solid var(--_border);gap:12px}.fly-comment-thread__item{display:block}.fly-comment-thread__row{display:flex;gap:10px;align-items:flex-start}.fly-comment-thread__avatar{inline-size:32px;block-size:32px;border-radius:50%;overflow:hidden;flex:none;background:var(--_fill);display:flex;align-items:center;justify-content:center}.fly-comment-thread__avatar img{inline-size:100%;block-size:100%;object-fit:cover}.fly-comment-thread__avatar-fallback{font-weight:700;color:var(--_text-subtle);text-transform:uppercase}.fly-comment-thread__content{flex:1 1 auto;min-inline-size:0;display:flex;flex-direction:column;gap:4px}.fly-comment-thread__meta{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap}.fly-comment-thread__author{font-weight:700;color:var(--_text)}.fly-comment-thread__timestamp{font-size:11px;color:var(--_text-tertiary)}.fly-comment-thread__edited{font-size:11px;color:var(--_text-tertiary);font-style:italic}.fly-comment-thread__text-row{display:flex;align-items:flex-start;gap:4px}.fly-comment-thread__text{flex:1 1 auto;min-inline-size:0;margin:0;color:var(--_text);line-height:1.5;white-space:pre-wrap;overflow-wrap:anywhere}.fly-comment-thread__tombstone{margin:0;color:var(--_text-tertiary);font-style:italic}.fly-comment-thread__edit-box{display:flex;flex-direction:column;gap:6px}.fly-comment-thread__actions{display:flex;align-items:center;gap:2px;flex:none;margin-inline-start:auto}.fly-comment-thread__confirm{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:var(--_radius);background:var(--_fill);font-size:12px;color:var(--_text-subtle)}.fly-comment-thread__toggle-replies{align-self:flex-start;border:none;background:transparent;padding:0;margin-block-start:4px;color:var(--_accent);font:inherit;font-size:12px;font-weight:700;cursor:pointer}.fly-comment-thread__toggle-replies:hover{text-decoration:underline}.fly-comment-thread__load-more{align-self:flex-start;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);padding:6px 14px;color:var(--_text);font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__load-more:hover{background:var(--_surface-hover)}.fly-comment-thread__load-more--nested{margin-inline-start:20px}.fly-comment-thread__overlay{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;z-index:1000}.fly-comment-thread__overlay-backdrop{position:absolute;inset:0;background:#00000073;border:none;padding:0;margin:0;cursor:pointer}.fly-comment-thread__dialog{position:relative;z-index:1;inline-size:min(420px,90vw);max-block-size:85vh;overflow-y:auto;background:var(--_surface);border-radius:12px;padding:20px;display:flex;flex-direction:column;gap:14px;box-shadow:0 12px 32px #00000040}.fly-comment-thread__dialog-title{margin:0;font-size:16px;font-weight:700}.fly-comment-thread__reason-group{display:flex;flex-direction:column;gap:8px;margin:0;padding:0;border:none}.fly-comment-thread__reason-group legend{font-weight:600;padding:0;margin-block-end:4px}.fly-comment-thread__reason-option{display:flex;align-items:center;gap:8px;cursor:pointer}.fly-comment-thread__reason-option input{accent-color:var(--_accent);cursor:pointer}:host(.fly-comment-thread--readonly) .fly-comment-thread__composer,:host(.fly-comment-thread--readonly) .fly-comment-thread__actions{display:none}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: FlyTooltipDirective, selector: "[flyTooltip], [data-tooltip]", inputs: ["flyTooltip", "flyTooltipPlacement", "flyTooltipDisabled", "flyTooltipDelay"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
13210
13616
|
}
|
|
13211
13617
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyCommentThreadComponent, decorators: [{
|
|
13212
13618
|
type: Component,
|
|
13213
|
-
args: [{ selector: 'fly-comment-thread', standalone: true, imports: [CommonModule, FormsModule, TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
13619
|
+
args: [{ selector: 'fly-comment-thread', standalone: true, imports: [CommonModule, FormsModule, TranslatePipe, FlyTooltipDirective], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
13214
13620
|
class: 'fly-comment-thread',
|
|
13215
13621
|
'[class.fly-comment-thread--locked]': 'isLocked()',
|
|
13216
13622
|
'[class.fly-comment-thread--readonly]': 'isReadonly()',
|
|
13217
|
-
}, template: "<div class=\"fly-comment-thread__body\">\r\n <!-- \u2500\u2500 Header: count + sort + lock toggle \u2500\u2500 -->\r\n <header class=\"fly-comment-thread__header\">\r\n <span class=\"fly-comment-thread__count\">\r\n {{ 'comment.count' | translate: { n: rootPage().total } }}\r\n </span>\r\n\r\n <div class=\"fly-comment-thread__header-actions\">\r\n <div class=\"fly-comment-thread__sort\" role=\"group\" [attr.aria-label]=\"'comment.sort_label' | translate\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__sort-btn\"\r\n [class.fly-comment-thread__sort-btn--active]=\"sort() === 'newest'\"\r\n (click)=\"onSortPick('newest')\">\r\n {{ 'comment.sort_newest' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__sort-btn\"\r\n [class.fly-comment-thread__sort-btn--active]=\"sort() === 'oldest'\"\r\n (click)=\"onSortPick('oldest')\">\r\n {{ 'comment.sort_oldest' | translate }}\r\n </button>\r\n </div>\r\n\r\n @if (canModerateThread() && !isReadonly()) {\r\n <button type=\"button\" class=\"fly-comment-thread__lock-btn\" (click)=\"toggleLock()\">\r\n {{ (isLocked() ? 'comment.unlock' : 'comment.lock') | translate }}\r\n </button>\r\n }\r\n </div>\r\n </header>\r\n\r\n @if (isLocked()) {\r\n <p class=\"fly-comment-thread__locked-notice\">{{ 'comment.locked_notice' | translate }}</p>\r\n }\r\n\r\n <!-- \u2500\u2500 Root composer \u2500\u2500 -->\r\n @if (canReply()) {\r\n <div class=\"fly-comment-thread__composer\">\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"3\"\r\n [id]=\"fieldId('root-composer')\"\r\n [attr.aria-label]=\"'comment.composer_placeholder' | translate\"\r\n [placeholder]=\"'comment.composer_placeholder' | translate\"\r\n [ngModel]=\"rootDraft()\"\r\n (ngModelChange)=\"onRootDraftChange($event)\"></textarea>\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__submit-btn\"\r\n [disabled]=\"!rootDraft().trim()\"\r\n (click)=\"submitRoot()\">\r\n {{ 'comment.post' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- \u2500\u2500 Root comments \u2500\u2500 -->\r\n @if (rootPage().items.length === 0) {\r\n <p class=\"fly-comment-thread__empty\">{{ 'comment.empty' | translate }}</p>\r\n } @else {\r\n <ul class=\"fly-comment-thread__list\" role=\"list\">\r\n @for (c of rootPage().items; track c.id) {\r\n <ng-container [ngTemplateOutlet]=\"commentNode\" [ngTemplateOutletContext]=\"{ $implicit: c }\"></ng-container>\r\n }\r\n </ul>\r\n\r\n @if (rootPage().hasMore) {\r\n <button type=\"button\" class=\"fly-comment-thread__load-more\" (click)=\"loadMoreRoot()\">\r\n {{ 'comment.load_more' | translate }}\r\n </button>\r\n }\r\n }\r\n</div>\r\n\r\n<!-- \u2500\u2500 Recursive comment node template \u2500\u2500 -->\r\n<ng-template #commentNode let-comment>\r\n <li class=\"fly-comment-thread__item\" [attr.data-depth]=\"comment.depth\">\r\n <div class=\"fly-comment-thread__row\">\r\n <div class=\"fly-comment-thread__avatar\" aria-hidden=\"true\">\r\n @if (comment.authorAvatarUrl) {\r\n <img [src]=\"comment.authorAvatarUrl\" alt=\"\" />\r\n } @else {\r\n <span class=\"fly-comment-thread__avatar-fallback\">{{ (comment.authorDisplayName || comment.authorUserId)[0] }}</span>\r\n }\r\n </div>\r\n\r\n <div class=\"fly-comment-thread__content\">\r\n <div class=\"fly-comment-thread__meta\">\r\n <span class=\"fly-comment-thread__author\">{{ comment.authorDisplayName || comment.authorUserId }}</span>\r\n <span class=\"fly-comment-thread__timestamp\">{{ formatDate(comment.createdAt) }}</span>\r\n @if (comment.isEdited && !comment.isDeleted) {\r\n <span class=\"fly-comment-thread__edited\">{{ 'comment.edited' | translate }}</span>\r\n }\r\n </div>\r\n\r\n @if (comment.isDeleted) {\r\n <p class=\"fly-comment-thread__tombstone\">{{ 'comment.deleted' | translate }}</p>\r\n } @else if (editingId() === comment.id) {\r\n <div class=\"fly-comment-thread__edit-box\">\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"3\"\r\n [ngModel]=\"comment.body\"\r\n (ngModelChange)=\"onEditDraftChange($event)\"></textarea>\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"cancelEdit()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-comment-thread__submit-btn\" (click)=\"saveEdit(comment)\">\r\n {{ 'comment.save' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n } @else {\r\n <p class=\"fly-comment-thread__text\">{{ comment.body }}</p>\r\n }\r\n\r\n @if (!isReadonly() && !comment.isDeleted && editingId() !== comment.id) {\r\n <div class=\"fly-comment-thread__actions\">\r\n @if (canReply()) {\r\n <button type=\"button\" class=\"fly-comment-thread__action-btn\" (click)=\"openReplyBox(comment.id)\">\r\n {{ 'comment.reply' | translate }}\r\n </button>\r\n }\r\n @if (canEditComment(comment)) {\r\n <button type=\"button\" class=\"fly-comment-thread__action-btn\" (click)=\"startEdit(comment)\">\r\n {{ 'comment.edit' | translate }}\r\n </button>\r\n }\r\n @if (canDeleteComment(comment)) {\r\n <button type=\"button\" class=\"fly-comment-thread__action-btn fly-comment-thread__action-btn--danger\" (click)=\"requestDelete(comment)\">\r\n {{ 'comment.delete' | translate }}\r\n </button>\r\n }\r\n @if (canReport(comment)) {\r\n <button type=\"button\" class=\"fly-comment-thread__action-btn\" (click)=\"openReport(comment)\">\r\n {{ 'comment.report' | translate }}\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n @if (confirmingDeleteId() === comment.id) {\r\n <div class=\"fly-comment-thread__confirm\">\r\n <span>{{ 'comment.delete_confirm' | translate }}</span>\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"cancelDelete()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-comment-thread__danger-btn\" (click)=\"confirmDelete(comment)\">\r\n {{ 'comment.delete' | translate }}\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (isReplyBoxOpen(comment.id)) {\r\n <div class=\"fly-comment-thread__composer fly-comment-thread__composer--reply\">\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"2\"\r\n [attr.aria-label]=\"'comment.reply_placeholder' | translate\"\r\n [placeholder]=\"'comment.reply_placeholder' | translate\"\r\n [ngModel]=\"replyDraft(comment.id)\"\r\n (ngModelChange)=\"onReplyDraftChange(comment.id, $event)\"></textarea>\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"closeReplyBox(comment.id)\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__submit-btn\"\r\n [disabled]=\"!replyDraft(comment.id).trim()\"\r\n (click)=\"submitReply(comment.id)\">\r\n {{ 'comment.post' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n\r\n @if (comment.replyCount > 0) {\r\n <button type=\"button\" class=\"fly-comment-thread__toggle-replies\" (click)=\"toggleReplies(comment)\">\r\n {{ (isExpanded(comment.id) ? 'comment.hide_replies' : 'comment.show_replies') | translate: { n: comment.replyCount } }}\r\n </button>\r\n }\r\n\r\n @if (isExpanded(comment.id)) {\r\n <ul class=\"fly-comment-thread__list fly-comment-thread__list--nested\" role=\"list\">\r\n @for (child of repliesFor(comment.id)?.items ?? []; track child.id) {\r\n <ng-container [ngTemplateOutlet]=\"commentNode\" [ngTemplateOutletContext]=\"{ $implicit: child }\"></ng-container>\r\n }\r\n </ul>\r\n @if (repliesFor(comment.id)?.hasMore) {\r\n <button type=\"button\" class=\"fly-comment-thread__load-more fly-comment-thread__load-more--nested\" (click)=\"loadMoreReplies(comment.id)\">\r\n {{ 'comment.load_more' | translate }}\r\n </button>\r\n }\r\n }\r\n </div>\r\n </div>\r\n </li>\r\n</ng-template>\r\n\r\n<!-- \u2500\u2500 Report dialog \u2500\u2500 -->\r\n@if (reportingId()) {\r\n <div class=\"fly-comment-thread__overlay\">\r\n <div class=\"fly-comment-thread__overlay-backdrop\" role=\"button\" tabindex=\"0\"\r\n [attr.aria-label]=\"'common.action.cancel' | translate\"\r\n (click)=\"closeReport()\"\r\n (keydown.enter)=\"closeReport()\"\r\n (keydown.space)=\"onOverlayBackdropSpace($event)\"></div>\r\n <div class=\"fly-comment-thread__dialog\" role=\"dialog\" [attr.aria-label]=\"'comment.report_title' | translate\">\r\n <h3 class=\"fly-comment-thread__dialog-title\">{{ 'comment.report_title' | translate }}</h3>\r\n\r\n <fieldset class=\"fly-comment-thread__reason-group\">\r\n <legend>{{ 'comment.report_reason_label' | translate }}</legend>\r\n @for (reason of reportReasons; track reason) {\r\n <label class=\"fly-comment-thread__reason-option\">\r\n <input\r\n type=\"radio\"\r\n name=\"fly-comment-report-reason\"\r\n [value]=\"reason\"\r\n [checked]=\"reportReason() === reason\"\r\n (change)=\"onReportReasonChange(reason)\" />\r\n <span>{{ 'comment.reason.' + reason | translate }}</span>\r\n </label>\r\n }\r\n </fieldset>\r\n\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"3\"\r\n [attr.aria-label]=\"'comment.report_detail_label' | translate\"\r\n [placeholder]=\"'comment.report_detail_label' | translate\"\r\n [ngModel]=\"reportDetail()\"\r\n (ngModelChange)=\"onReportDetailChange($event)\"></textarea>\r\n\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"closeReport()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-comment-thread__submit-btn\" [disabled]=\"!reportReason()\" (click)=\"submitReport()\">\r\n {{ 'comment.report_submit' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--surface-card, #fff);--_surface-hover: var(--surface-hover, #f1f5f9);--_border: var(--separator, var(--surface-border, #e2e8f0));--_text: var(--label-primary, var(--text-color, #0f172a));--_text-subtle: var(--label-secondary, var(--text-color-secondary, #64748b));--_text-tertiary: var(--label-tertiary, #94a3b8);--_fill: var(--fill-tertiary, #f3f4f6);--_accent: var(--accent);--_danger: var(--system-red, #ef4444);--_radius: 8px;display:block;inline-size:100%;color:var(--_text);font-size:13px}.fly-comment-thread__body{display:flex;flex-direction:column;gap:16px}.fly-comment-thread__header{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.fly-comment-thread__count{font-weight:700;font-size:14px}.fly-comment-thread__header-actions{display:flex;align-items:center;gap:10px}.fly-comment-thread__sort{display:inline-flex;border:1px solid var(--_border);border-radius:var(--_radius);overflow:hidden}.fly-comment-thread__sort-btn{padding:5px 12px;border:none;border-inline-end:1px solid var(--_border);background:transparent;color:var(--_text-subtle);font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__sort-btn:last-child{border-inline-end:none}.fly-comment-thread__sort-btn--active{background:var(--_accent);color:#fff}.fly-comment-thread__lock-btn{padding:5px 12px;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);color:var(--_text);font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__lock-btn:hover{background:var(--_surface-hover)}.fly-comment-thread__locked-notice{margin:0;padding:8px 12px;border-radius:var(--_radius);background:var(--_fill);color:var(--_text-subtle);font-size:12px}.fly-comment-thread__empty{margin:0;color:var(--_text-subtle);text-align:center;padding:16px 0}.fly-comment-thread__composer{display:flex;flex-direction:column;gap:8px}.fly-comment-thread__composer--reply{margin-block-start:8px}.fly-comment-thread__textarea{inline-size:100%;padding:8px 12px;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);color:var(--_text);font:inherit;line-height:1.5;resize:vertical}.fly-comment-thread__textarea:focus-visible{outline:2px solid var(--_accent);outline-offset:1px}.fly-comment-thread__composer-actions{display:flex;justify-content:flex-end;gap:8px}.fly-comment-thread__submit-btn{padding:6px 16px;border:none;border-radius:var(--_radius);background:var(--_accent);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__submit-btn:hover:not(:disabled){opacity:.9}.fly-comment-thread__submit-btn:disabled{opacity:.5;cursor:not-allowed}.fly-comment-thread__cancel-btn{padding:6px 14px;border:1px solid var(--_border);border-radius:var(--_radius);background:transparent;color:var(--_text-subtle);font:inherit;cursor:pointer}.fly-comment-thread__cancel-btn:hover{background:var(--_surface-hover)}.fly-comment-thread__danger-btn{padding:6px 14px;border:none;border-radius:var(--_radius);background:var(--_danger);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__danger-btn:hover{opacity:.9}.fly-comment-thread__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:14px}.fly-comment-thread__list--nested{margin-block-start:10px;margin-inline-start:20px;padding-inline-start:12px;border-inline-start:2px solid var(--_border);gap:12px}.fly-comment-thread__item{display:block}.fly-comment-thread__row{display:flex;gap:10px;align-items:flex-start}.fly-comment-thread__avatar{inline-size:32px;block-size:32px;border-radius:50%;overflow:hidden;flex:none;background:var(--_fill);display:flex;align-items:center;justify-content:center}.fly-comment-thread__avatar img{inline-size:100%;block-size:100%;object-fit:cover}.fly-comment-thread__avatar-fallback{font-weight:700;color:var(--_text-subtle);text-transform:uppercase}.fly-comment-thread__content{flex:1 1 auto;min-inline-size:0;display:flex;flex-direction:column;gap:4px}.fly-comment-thread__meta{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap}.fly-comment-thread__author{font-weight:700;color:var(--_text)}.fly-comment-thread__timestamp{font-size:11px;color:var(--_text-tertiary)}.fly-comment-thread__edited{font-size:11px;color:var(--_text-tertiary);font-style:italic}.fly-comment-thread__text{margin:0;color:var(--_text);line-height:1.5;white-space:pre-wrap;overflow-wrap:anywhere}.fly-comment-thread__tombstone{margin:0;color:var(--_text-tertiary);font-style:italic}.fly-comment-thread__edit-box{display:flex;flex-direction:column;gap:6px}.fly-comment-thread__actions{display:flex;gap:12px;margin-block-start:2px}.fly-comment-thread__action-btn{border:none;background:transparent;padding:0;color:var(--_text-subtle);font:inherit;font-size:12px;font-weight:600;cursor:pointer}.fly-comment-thread__action-btn:hover{color:var(--_accent);text-decoration:underline}.fly-comment-thread__action-btn--danger:hover{color:var(--_danger)}.fly-comment-thread__confirm{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:var(--_radius);background:var(--_fill);font-size:12px;color:var(--_text-subtle)}.fly-comment-thread__toggle-replies{align-self:flex-start;border:none;background:transparent;padding:0;margin-block-start:4px;color:var(--_accent);font:inherit;font-size:12px;font-weight:700;cursor:pointer}.fly-comment-thread__toggle-replies:hover{text-decoration:underline}.fly-comment-thread__load-more{align-self:flex-start;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);padding:6px 14px;color:var(--_text);font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__load-more:hover{background:var(--_surface-hover)}.fly-comment-thread__load-more--nested{margin-inline-start:20px}.fly-comment-thread__overlay{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;z-index:1000}.fly-comment-thread__overlay-backdrop{position:absolute;inset:0;background:#00000073;border:none;padding:0;margin:0;cursor:pointer}.fly-comment-thread__dialog{position:relative;z-index:1;inline-size:min(420px,90vw);max-block-size:85vh;overflow-y:auto;background:var(--_surface);border-radius:12px;padding:20px;display:flex;flex-direction:column;gap:14px;box-shadow:0 12px 32px #00000040}.fly-comment-thread__dialog-title{margin:0;font-size:16px;font-weight:700}.fly-comment-thread__reason-group{display:flex;flex-direction:column;gap:8px;margin:0;padding:0;border:none}.fly-comment-thread__reason-group legend{font-weight:600;padding:0;margin-block-end:4px}.fly-comment-thread__reason-option{display:flex;align-items:center;gap:8px;cursor:pointer}.fly-comment-thread__reason-option input{accent-color:var(--_accent);cursor:pointer}:host(.fly-comment-thread--readonly) .fly-comment-thread__composer,:host(.fly-comment-thread--readonly) .fly-comment-thread__actions{display:none}\n"] }]
|
|
13218
|
-
}], propDecorators: { rootPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootPage", required: true }] }], repliesByParent: [{ type: i0.Input, args: [{ isSignal: true, alias: "repliesByParent", required: false }] }], sort: [{ type: i0.Input, args: [{ isSignal: true, alias: "sort", required: false }] }], isLocked: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLocked", required: false }] }], currentUserId: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentUserId", required: false }] }], isReadonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], canModerateThread: [{ type: i0.Input, args: [{ isSignal: true, alias: "canModerateThread", required: false }] }], loadReplies: [{ type: i0.Output, args: ["loadReplies"] }], loadMore: [{ type: i0.Output, args: ["loadMore"] }], submit: [{ type: i0.Output, args: ["submit"] }], edit: [{ type: i0.Output, args: ["edit"] }], delete: [{ type: i0.Output, args: ["delete"] }], report: [{ type: i0.Output, args: ["report"] }], lockToggle: [{ type: i0.Output, args: ["lockToggle"] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }] } });
|
|
13623
|
+
}, template: "<div class=\"fly-comment-thread__body\">\r\n <!-- \u2500\u2500 Header: count + one combined sort/lock icon-button toolbar \u2500\u2500 -->\r\n <header class=\"fly-comment-thread__header\">\r\n <span class=\"fly-comment-thread__count\">\r\n {{ 'comment.count' | translate: { n: rootPage().total } }}\r\n </span>\r\n\r\n <div class=\"fly-comment-thread__toolbar\" role=\"group\" [attr.aria-label]=\"'comment.sort_label' | translate\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__icon-btn\"\r\n [class.fly-comment-thread__icon-btn--active]=\"sort() === 'newest'\"\r\n [attr.aria-pressed]=\"sort() === 'newest'\"\r\n [flyTooltip]=\"'comment.sort_newest' | translate\"\r\n [attr.aria-label]=\"'comment.sort_newest' | translate\"\r\n (click)=\"onSortPick('newest')\">\r\n <i class=\"pi pi-sort-amount-down\" aria-hidden=\"true\"></i>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__icon-btn\"\r\n [class.fly-comment-thread__icon-btn--active]=\"sort() === 'oldest'\"\r\n [attr.aria-pressed]=\"sort() === 'oldest'\"\r\n [flyTooltip]=\"'comment.sort_oldest' | translate\"\r\n [attr.aria-label]=\"'comment.sort_oldest' | translate\"\r\n (click)=\"onSortPick('oldest')\">\r\n <i class=\"pi pi-sort-amount-up\" aria-hidden=\"true\"></i>\r\n </button>\r\n\r\n @if (canModerateThread() && !isReadonly()) {\r\n <span class=\"fly-comment-thread__toolbar-sep\" aria-hidden=\"true\"></span>\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__icon-btn\"\r\n [class.fly-comment-thread__icon-btn--active]=\"isLocked()\"\r\n [attr.aria-pressed]=\"isLocked()\"\r\n [flyTooltip]=\"(isLocked() ? 'comment.unlock' : 'comment.lock') | translate\"\r\n [attr.aria-label]=\"(isLocked() ? 'comment.unlock' : 'comment.lock') | translate\"\r\n (click)=\"toggleLock()\">\r\n <i class=\"pi\" [class.pi-lock]=\"!isLocked()\" [class.pi-lock-open]=\"isLocked()\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n </div>\r\n </header>\r\n\r\n @if (isLocked()) {\r\n <p class=\"fly-comment-thread__locked-notice\">{{ 'comment.locked_notice' | translate }}</p>\r\n }\r\n\r\n <!-- \u2500\u2500 Root composer \u2500\u2500 -->\r\n @if (canReply()) {\r\n <div class=\"fly-comment-thread__composer\">\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"3\"\r\n [id]=\"fieldId('root-composer')\"\r\n [attr.aria-label]=\"'comment.composer_placeholder' | translate\"\r\n [placeholder]=\"'comment.composer_placeholder' | translate\"\r\n [ngModel]=\"rootDraft()\"\r\n (ngModelChange)=\"onRootDraftChange($event)\"></textarea>\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__submit-btn\"\r\n [disabled]=\"!rootDraft().trim()\"\r\n (click)=\"submitRoot()\">\r\n {{ 'comment.post' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- \u2500\u2500 Root comments \u2500\u2500 -->\r\n @if (rootPage().items.length === 0) {\r\n <p class=\"fly-comment-thread__empty\">{{ 'comment.empty' | translate }}</p>\r\n } @else {\r\n <ul class=\"fly-comment-thread__list\" role=\"list\">\r\n @for (c of rootPage().items; track c.id) {\r\n <ng-container [ngTemplateOutlet]=\"commentNode\" [ngTemplateOutletContext]=\"{ $implicit: c }\"></ng-container>\r\n }\r\n </ul>\r\n\r\n @if (rootPage().hasMore) {\r\n <button type=\"button\" class=\"fly-comment-thread__load-more\" (click)=\"loadMoreRoot()\">\r\n {{ 'comment.load_more' | translate }}\r\n </button>\r\n }\r\n }\r\n</div>\r\n\r\n<!-- \u2500\u2500 Recursive comment node template \u2500\u2500 -->\r\n<ng-template #commentNode let-comment>\r\n <li class=\"fly-comment-thread__item\" [attr.data-depth]=\"comment.depth\">\r\n <div class=\"fly-comment-thread__row\">\r\n <div class=\"fly-comment-thread__avatar\" aria-hidden=\"true\">\r\n @if (comment.authorAvatarUrl) {\r\n <img [src]=\"comment.authorAvatarUrl\" alt=\"\" />\r\n } @else {\r\n <span class=\"fly-comment-thread__avatar-fallback\">{{ authorInitial(comment) }}</span>\r\n }\r\n </div>\r\n\r\n <div class=\"fly-comment-thread__content\">\r\n <div class=\"fly-comment-thread__meta\">\r\n <span class=\"fly-comment-thread__author\">{{ authorLabel(comment) }}</span>\r\n <span class=\"fly-comment-thread__timestamp\">{{ formatDate(comment.createdAt) }}</span>\r\n @if (comment.isEdited && !comment.isDeleted) {\r\n <span class=\"fly-comment-thread__edited\">{{ 'comment.edited' | translate }}</span>\r\n }\r\n </div>\r\n\r\n @if (comment.isDeleted) {\r\n <p class=\"fly-comment-thread__tombstone\">{{ 'comment.deleted' | translate }}</p>\r\n } @else if (editingId() === comment.id) {\r\n <div class=\"fly-comment-thread__edit-box\">\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"3\"\r\n [ngModel]=\"comment.body\"\r\n (ngModelChange)=\"onEditDraftChange($event)\"></textarea>\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"cancelEdit()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-comment-thread__submit-btn\" (click)=\"saveEdit(comment)\">\r\n {{ 'comment.save' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"fly-comment-thread__text-row\">\r\n <p class=\"fly-comment-thread__text\">{{ comment.body }}</p>\r\n @if (!isReadonly()) {\r\n <div class=\"fly-comment-thread__actions\">\r\n @if (canReply()) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__icon-btn\"\r\n [flyTooltip]=\"'comment.reply' | translate\"\r\n [attr.aria-label]=\"'comment.reply' | translate\"\r\n (click)=\"openReplyBox(comment.id)\">\r\n <i class=\"pi pi-reply\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n @if (canEditComment(comment)) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__icon-btn\"\r\n [flyTooltip]=\"'comment.edit' | translate\"\r\n [attr.aria-label]=\"'comment.edit' | translate\"\r\n (click)=\"startEdit(comment)\">\r\n <i class=\"pi pi-pencil\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n @if (canDeleteComment(comment)) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__icon-btn fly-comment-thread__icon-btn--danger\"\r\n [flyTooltip]=\"'comment.delete' | translate\"\r\n [attr.aria-label]=\"'comment.delete' | translate\"\r\n (click)=\"requestDelete(comment)\">\r\n <i class=\"pi pi-trash\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n @if (canReport(comment)) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__icon-btn\"\r\n [flyTooltip]=\"'comment.report' | translate\"\r\n [attr.aria-label]=\"'comment.report' | translate\"\r\n (click)=\"openReport(comment)\">\r\n <i class=\"pi pi-flag\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n @if (confirmingDeleteId() === comment.id) {\r\n <div class=\"fly-comment-thread__confirm\">\r\n <span>{{ 'comment.delete_confirm' | translate }}</span>\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"cancelDelete()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-comment-thread__danger-btn\" (click)=\"confirmDelete(comment)\">\r\n {{ 'comment.delete' | translate }}\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (isReplyBoxOpen(comment.id)) {\r\n <div class=\"fly-comment-thread__composer fly-comment-thread__composer--reply\">\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"2\"\r\n [attr.aria-label]=\"'comment.reply_placeholder' | translate\"\r\n [placeholder]=\"'comment.reply_placeholder' | translate\"\r\n [ngModel]=\"replyDraft(comment.id)\"\r\n (ngModelChange)=\"onReplyDraftChange(comment.id, $event)\"></textarea>\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"closeReplyBox(comment.id)\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__submit-btn\"\r\n [disabled]=\"!replyDraft(comment.id).trim()\"\r\n (click)=\"submitReply(comment.id)\">\r\n {{ 'comment.post' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n\r\n @if (comment.replyCount > 0) {\r\n <button type=\"button\" class=\"fly-comment-thread__toggle-replies\" (click)=\"toggleReplies(comment)\">\r\n {{ (isExpanded(comment.id) ? 'comment.hide_replies' : 'comment.show_replies') | translate: { n: comment.replyCount } }}\r\n </button>\r\n }\r\n\r\n @if (isExpanded(comment.id)) {\r\n <ul class=\"fly-comment-thread__list fly-comment-thread__list--nested\" role=\"list\">\r\n @for (child of repliesFor(comment.id)?.items ?? []; track child.id) {\r\n <ng-container [ngTemplateOutlet]=\"commentNode\" [ngTemplateOutletContext]=\"{ $implicit: child }\"></ng-container>\r\n }\r\n </ul>\r\n @if (repliesFor(comment.id)?.hasMore) {\r\n <button type=\"button\" class=\"fly-comment-thread__load-more fly-comment-thread__load-more--nested\" (click)=\"loadMoreReplies(comment.id)\">\r\n {{ 'comment.load_more' | translate }}\r\n </button>\r\n }\r\n }\r\n </div>\r\n </div>\r\n </li>\r\n</ng-template>\r\n\r\n<!-- \u2500\u2500 Report dialog \u2500\u2500 -->\r\n@if (reportingId()) {\r\n <div class=\"fly-comment-thread__overlay\">\r\n <div class=\"fly-comment-thread__overlay-backdrop\" role=\"button\" tabindex=\"0\"\r\n [attr.aria-label]=\"'common.action.cancel' | translate\"\r\n (click)=\"closeReport()\"\r\n (keydown.enter)=\"closeReport()\"\r\n (keydown.space)=\"onOverlayBackdropSpace($event)\"></div>\r\n <div class=\"fly-comment-thread__dialog\" role=\"dialog\" [attr.aria-label]=\"'comment.report_title' | translate\">\r\n <h3 class=\"fly-comment-thread__dialog-title\">{{ 'comment.report_title' | translate }}</h3>\r\n\r\n <fieldset class=\"fly-comment-thread__reason-group\">\r\n <legend>{{ 'comment.report_reason_label' | translate }}</legend>\r\n @for (reason of reportReasons; track reason) {\r\n <label class=\"fly-comment-thread__reason-option\">\r\n <input\r\n type=\"radio\"\r\n name=\"fly-comment-report-reason\"\r\n [value]=\"reason\"\r\n [checked]=\"reportReason() === reason\"\r\n (change)=\"onReportReasonChange(reason)\" />\r\n <span>{{ 'comment.reason.' + reason | translate }}</span>\r\n </label>\r\n }\r\n </fieldset>\r\n\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"3\"\r\n [attr.aria-label]=\"'comment.report_detail_label' | translate\"\r\n [placeholder]=\"'comment.report_detail_label' | translate\"\r\n [ngModel]=\"reportDetail()\"\r\n (ngModelChange)=\"onReportDetailChange($event)\"></textarea>\r\n\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"closeReport()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-comment-thread__submit-btn\" [disabled]=\"!reportReason()\" (click)=\"submitReport()\">\r\n {{ 'comment.report_submit' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--surface-card, #fff);--_surface-hover: var(--surface-hover, #f1f5f9);--_border: var(--separator, var(--surface-border, #e2e8f0));--_text: var(--label-primary, var(--text-color, #0f172a));--_text-subtle: var(--label-secondary, var(--text-color-secondary, #64748b));--_text-tertiary: var(--label-tertiary, #94a3b8);--_fill: var(--fill-tertiary, #f3f4f6);--_accent: var(--accent);--_danger: var(--system-red, #ef4444);--_radius: 8px;display:block;inline-size:100%;color:var(--_text);font-size:13px}.fly-comment-thread__body{display:flex;flex-direction:column;gap:16px}.fly-comment-thread__header{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.fly-comment-thread__count{font-weight:700;font-size:14px}.fly-comment-thread__toolbar{display:inline-flex;align-items:center;gap:2px;padding:2px;border:1px solid var(--_border);border-radius:calc(var(--_radius) + 2px);background:var(--_surface)}.fly-comment-thread__toolbar-sep{inline-size:1px;block-size:18px;margin-inline:2px;background:var(--_border)}.fly-comment-thread__icon-btn{display:inline-flex;align-items:center;justify-content:center;inline-size:26px;block-size:26px;flex:none;padding:0;border:none;border-radius:50%;background:transparent;color:var(--_text-subtle);cursor:pointer;transition:background var(--t-state, .15s),color var(--t-state, .15s)}.fly-comment-thread__icon-btn .pi{font-size:13px}.fly-comment-thread__icon-btn:hover{background:var(--_surface-hover);color:var(--_text)}.fly-comment-thread__icon-btn:focus-visible{outline:2px solid var(--_accent);outline-offset:1px}.fly-comment-thread__icon-btn--active{background:var(--_accent);color:#fff}.fly-comment-thread__icon-btn--active:hover{background:var(--_accent);opacity:.9}.fly-comment-thread__icon-btn--danger:hover{background:var(--_danger);color:#fff}.fly-comment-thread__locked-notice{margin:0;padding:8px 12px;border-radius:var(--_radius);background:var(--_fill);color:var(--_text-subtle);font-size:12px}.fly-comment-thread__empty{margin:0;color:var(--_text-subtle);text-align:center;padding:16px 0}.fly-comment-thread__composer{display:flex;flex-direction:column;gap:8px}.fly-comment-thread__composer--reply{margin-block-start:8px}.fly-comment-thread__textarea{inline-size:100%;padding:8px 12px;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);color:var(--_text);font:inherit;line-height:1.5;resize:vertical}.fly-comment-thread__textarea:focus-visible{outline:2px solid var(--_accent);outline-offset:1px}.fly-comment-thread__composer-actions{display:flex;justify-content:flex-end;gap:8px}.fly-comment-thread__submit-btn{padding:6px 16px;border:none;border-radius:var(--_radius);background:var(--_accent);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__submit-btn:hover:not(:disabled){opacity:.9}.fly-comment-thread__submit-btn:disabled{opacity:.5;cursor:not-allowed}.fly-comment-thread__cancel-btn{padding:6px 14px;border:1px solid var(--_border);border-radius:var(--_radius);background:transparent;color:var(--_text-subtle);font:inherit;cursor:pointer}.fly-comment-thread__cancel-btn:hover{background:var(--_surface-hover)}.fly-comment-thread__danger-btn{padding:6px 14px;border:none;border-radius:var(--_radius);background:var(--_danger);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__danger-btn:hover{opacity:.9}.fly-comment-thread__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:14px}.fly-comment-thread__list--nested{margin-block-start:10px;margin-inline-start:20px;padding-inline-start:12px;border-inline-start:2px solid var(--_border);gap:12px}.fly-comment-thread__item{display:block}.fly-comment-thread__row{display:flex;gap:10px;align-items:flex-start}.fly-comment-thread__avatar{inline-size:32px;block-size:32px;border-radius:50%;overflow:hidden;flex:none;background:var(--_fill);display:flex;align-items:center;justify-content:center}.fly-comment-thread__avatar img{inline-size:100%;block-size:100%;object-fit:cover}.fly-comment-thread__avatar-fallback{font-weight:700;color:var(--_text-subtle);text-transform:uppercase}.fly-comment-thread__content{flex:1 1 auto;min-inline-size:0;display:flex;flex-direction:column;gap:4px}.fly-comment-thread__meta{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap}.fly-comment-thread__author{font-weight:700;color:var(--_text)}.fly-comment-thread__timestamp{font-size:11px;color:var(--_text-tertiary)}.fly-comment-thread__edited{font-size:11px;color:var(--_text-tertiary);font-style:italic}.fly-comment-thread__text-row{display:flex;align-items:flex-start;gap:4px}.fly-comment-thread__text{flex:1 1 auto;min-inline-size:0;margin:0;color:var(--_text);line-height:1.5;white-space:pre-wrap;overflow-wrap:anywhere}.fly-comment-thread__tombstone{margin:0;color:var(--_text-tertiary);font-style:italic}.fly-comment-thread__edit-box{display:flex;flex-direction:column;gap:6px}.fly-comment-thread__actions{display:flex;align-items:center;gap:2px;flex:none;margin-inline-start:auto}.fly-comment-thread__confirm{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:var(--_radius);background:var(--_fill);font-size:12px;color:var(--_text-subtle)}.fly-comment-thread__toggle-replies{align-self:flex-start;border:none;background:transparent;padding:0;margin-block-start:4px;color:var(--_accent);font:inherit;font-size:12px;font-weight:700;cursor:pointer}.fly-comment-thread__toggle-replies:hover{text-decoration:underline}.fly-comment-thread__load-more{align-self:flex-start;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);padding:6px 14px;color:var(--_text);font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__load-more:hover{background:var(--_surface-hover)}.fly-comment-thread__load-more--nested{margin-inline-start:20px}.fly-comment-thread__overlay{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;z-index:1000}.fly-comment-thread__overlay-backdrop{position:absolute;inset:0;background:#00000073;border:none;padding:0;margin:0;cursor:pointer}.fly-comment-thread__dialog{position:relative;z-index:1;inline-size:min(420px,90vw);max-block-size:85vh;overflow-y:auto;background:var(--_surface);border-radius:12px;padding:20px;display:flex;flex-direction:column;gap:14px;box-shadow:0 12px 32px #00000040}.fly-comment-thread__dialog-title{margin:0;font-size:16px;font-weight:700}.fly-comment-thread__reason-group{display:flex;flex-direction:column;gap:8px;margin:0;padding:0;border:none}.fly-comment-thread__reason-group legend{font-weight:600;padding:0;margin-block-end:4px}.fly-comment-thread__reason-option{display:flex;align-items:center;gap:8px;cursor:pointer}.fly-comment-thread__reason-option input{accent-color:var(--_accent);cursor:pointer}:host(.fly-comment-thread--readonly) .fly-comment-thread__composer,:host(.fly-comment-thread--readonly) .fly-comment-thread__actions{display:none}\n"] }]
|
|
13624
|
+
}], propDecorators: { rootPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootPage", required: true }] }], repliesByParent: [{ type: i0.Input, args: [{ isSignal: true, alias: "repliesByParent", required: false }] }], sort: [{ type: i0.Input, args: [{ isSignal: true, alias: "sort", required: false }] }], isLocked: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLocked", required: false }] }], currentUserId: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentUserId", required: false }] }], isReadonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], canModerateThread: [{ type: i0.Input, args: [{ isSignal: true, alias: "canModerateThread", required: false }] }], resolveDisplayName: [{ type: i0.Input, args: [{ isSignal: true, alias: "resolveDisplayName", required: false }] }], loadReplies: [{ type: i0.Output, args: ["loadReplies"] }], loadMore: [{ type: i0.Output, args: ["loadMore"] }], submit: [{ type: i0.Output, args: ["submit"] }], edit: [{ type: i0.Output, args: ["edit"] }], delete: [{ type: i0.Output, args: ["delete"] }], report: [{ type: i0.Output, args: ["report"] }], lockToggle: [{ type: i0.Output, args: ["lockToggle"] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }] } });
|
|
13219
13625
|
|
|
13220
13626
|
// ── Vendored synchronous SHA-256 (FIPS 180-4) ────────────────────────────────
|
|
13221
13627
|
// Why this exists: `fly-captcha`'s PoW solver must brute-force up to
|
|
@@ -15456,32 +15862,56 @@ function isChunkLoadError(err) {
|
|
|
15456
15862
|
return looksLikeModuleUrl && looksLikeFetchStatus;
|
|
15457
15863
|
}
|
|
15458
15864
|
/**
|
|
15459
|
-
* sessionStorage key recording
|
|
15460
|
-
* reload
|
|
15461
|
-
*
|
|
15865
|
+
* sessionStorage key recording WHEN this tab session last spent its chunk-error
|
|
15866
|
+
* reload (epoch ms). It persists across the reload it triggers (an in-memory
|
|
15867
|
+
* flag resets on reload and cannot guard a loop).
|
|
15462
15868
|
*/
|
|
15463
15869
|
const FLY_CHUNK_RELOAD_FLAG = 'fly:chunk-reload-attempted';
|
|
15464
15870
|
/**
|
|
15465
|
-
*
|
|
15466
|
-
*
|
|
15467
|
-
*
|
|
15871
|
+
* How long a spent self-heal attempt stays spent. Within the window a repeat
|
|
15872
|
+
* failure falls through to the caller's error UI (loop guard); after it the
|
|
15873
|
+
* heal re-arms. A permanent one-shot stranded long-lived tabs on environments
|
|
15874
|
+
* that redeploy repeatedly (e.g. a Helm upgrade→rollback→retry cycle flips the
|
|
15875
|
+
* served build several times in an afternoon — each flip needs its own reload,
|
|
15876
|
+
* but only the first ever got one). 10 min is far above any error→reload→error
|
|
15877
|
+
* loop period (seconds) while well below the gap between distinct redeploys.
|
|
15878
|
+
*/
|
|
15879
|
+
const FLY_RELOAD_REARM_MS = 10 * 60 * 1000;
|
|
15880
|
+
/**
|
|
15881
|
+
* Self-heal for a stale client after a redeploy. Permits ONE reload per
|
|
15882
|
+
* {@link FLY_RELOAD_REARM_MS} window per tab session, so a reload that keeps
|
|
15883
|
+
* failing (a chunk truly missing from the current build, or a genuinely-down
|
|
15884
|
+
* host) does NOT loop — but a LATER redeploy re-arms it.
|
|
15468
15885
|
*
|
|
15469
15886
|
* @returns `true` when a reload was initiated (the page will navigate away);
|
|
15470
|
-
* `false` when the
|
|
15887
|
+
* `false` when the window's reload was already spent or sessionStorage is
|
|
15471
15888
|
* unavailable — callers should surface a fallback / stop rather than spin.
|
|
15472
15889
|
*/
|
|
15473
15890
|
function reloadOnceForChunkError() {
|
|
15891
|
+
if (!trySpendHealAttempt(FLY_CHUNK_RELOAD_FLAG))
|
|
15892
|
+
return false;
|
|
15893
|
+
window.location.reload();
|
|
15894
|
+
return true;
|
|
15895
|
+
}
|
|
15896
|
+
/**
|
|
15897
|
+
* Records a heal attempt under `key` unless one was already recorded within the
|
|
15898
|
+
* last {@link FLY_RELOAD_REARM_MS}. A legacy `'1'` value (pre-timestamp builds)
|
|
15899
|
+
* parses to epoch 1 and therefore counts as expired — one extra reload on
|
|
15900
|
+
* upgrade, never a loop. Returns `false` on sessionStorage failure (private
|
|
15901
|
+
* mode / quota / non-browser): cannot record the attempt, so cannot prove a
|
|
15902
|
+
* reload wouldn't loop — prefer the caller's fallback over a spin.
|
|
15903
|
+
*/
|
|
15904
|
+
function trySpendHealAttempt(key) {
|
|
15474
15905
|
try {
|
|
15475
|
-
|
|
15906
|
+
const last = Number(sessionStorage.getItem(key));
|
|
15907
|
+
if (Number.isFinite(last) && last > 0 && Date.now() - last < FLY_RELOAD_REARM_MS) {
|
|
15476
15908
|
return false;
|
|
15477
|
-
|
|
15909
|
+
}
|
|
15910
|
+
sessionStorage.setItem(key, String(Date.now()));
|
|
15478
15911
|
}
|
|
15479
15912
|
catch {
|
|
15480
|
-
// Private mode / quota / non-browser: cannot record the attempt, so cannot
|
|
15481
|
-
// prove a reload wouldn't loop. Prefer the fallback over a spin.
|
|
15482
15913
|
return false;
|
|
15483
15914
|
}
|
|
15484
|
-
window.location.reload();
|
|
15485
15915
|
return true;
|
|
15486
15916
|
}
|
|
15487
15917
|
/**
|
|
@@ -15508,10 +15938,11 @@ function isNativeFederationCacheError(err) {
|
|
|
15508
15938
|
return /Unable to resolve specifier ['"]?@nf-internal\/chunk-/i.test(msg);
|
|
15509
15939
|
}
|
|
15510
15940
|
/**
|
|
15511
|
-
* sessionStorage key recording
|
|
15512
|
-
* cache-heal (revalidation sweep + reload
|
|
15513
|
-
* {@link FLY_CHUNK_RELOAD_FLAG} so a plain chunk-404 reload and a
|
|
15514
|
-
* revalidation sweep each get one independent attempt
|
|
15941
|
+
* sessionStorage key recording WHEN this tab session last spent its
|
|
15942
|
+
* Native-Federation cache-heal (revalidation sweep + reload; epoch ms) —
|
|
15943
|
+
* separate from {@link FLY_CHUNK_RELOAD_FLAG} so a plain chunk-404 reload and a
|
|
15944
|
+
* shared-bundle revalidation sweep each get one independent attempt per
|
|
15945
|
+
* {@link FLY_RELOAD_REARM_MS} window.
|
|
15515
15946
|
*/
|
|
15516
15947
|
const FLY_NF_CACHE_HEAL_FLAG = 'nf-cache-heal-attempted';
|
|
15517
15948
|
/**
|
|
@@ -15530,14 +15961,8 @@ const FLY_NF_CACHE_HEAL_FLAG = 'nf-cache-heal-attempted';
|
|
|
15530
15961
|
* sessionStorage is unavailable (caller should surface a fallback).
|
|
15531
15962
|
*/
|
|
15532
15963
|
function healNativeFederationCacheOnce() {
|
|
15533
|
-
|
|
15534
|
-
if (sessionStorage.getItem(FLY_NF_CACHE_HEAL_FLAG))
|
|
15535
|
-
return false;
|
|
15536
|
-
sessionStorage.setItem(FLY_NF_CACHE_HEAL_FLAG, '1');
|
|
15537
|
-
}
|
|
15538
|
-
catch {
|
|
15964
|
+
if (!trySpendHealAttempt(FLY_NF_CACHE_HEAL_FLAG))
|
|
15539
15965
|
return false;
|
|
15540
|
-
}
|
|
15541
15966
|
const reload = () => {
|
|
15542
15967
|
try {
|
|
15543
15968
|
window.location.reload();
|
|
@@ -15964,6 +16389,13 @@ class FlyModerationQueueComponent {
|
|
|
15964
16389
|
error = input(null, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
15965
16390
|
/** The currently applied status filter; `null` = "All". Host-controlled, like `[sort]` on `fly-comment-thread`. */
|
|
15966
16391
|
status = input(null, ...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
|
|
16392
|
+
/**
|
|
16393
|
+
* Optional id→name lookup for the reporter column, mirroring
|
|
16394
|
+
* `fly-comment-thread`'s input of the same name. Bind
|
|
16395
|
+
* `FlyUserDirectoryService.label`; absent, rows fall back to the raw id.
|
|
16396
|
+
* See {@link reporterLabel} for the resolution order.
|
|
16397
|
+
*/
|
|
16398
|
+
resolveDisplayName = input(null, ...(ngDevMode ? [{ debugName: "resolveDisplayName" }] : /* istanbul ignore next */ []));
|
|
15967
16399
|
// ── Outputs ──────────────────────────────────────────────────────────────────
|
|
15968
16400
|
/** A status-filter pick (page resets to 1) or a `loadMore` (next page at the current filter). */
|
|
15969
16401
|
loadPage = output();
|
|
@@ -16008,6 +16440,16 @@ class FlyModerationQueueComponent {
|
|
|
16008
16440
|
subjectDisplay(r) {
|
|
16009
16441
|
return r.subjectLabel || `${r.subjectType} #${this.shortId(r.subjectId)}`;
|
|
16010
16442
|
}
|
|
16443
|
+
/**
|
|
16444
|
+
* Who filed the report. Resolution order: explicit `reporterDisplayName` →
|
|
16445
|
+
* {@link resolveDisplayName} → the raw `reporterUserId`. Same contract as
|
|
16446
|
+
* `fly-comment-thread`'s `authorLabel`.
|
|
16447
|
+
*/
|
|
16448
|
+
reporterLabel(r) {
|
|
16449
|
+
if (r.reporterDisplayName)
|
|
16450
|
+
return r.reporterDisplayName;
|
|
16451
|
+
return this.resolveDisplayName()?.(r.reporterUserId) || r.reporterUserId;
|
|
16452
|
+
}
|
|
16011
16453
|
formatDate(iso) {
|
|
16012
16454
|
const d = new Date(iso);
|
|
16013
16455
|
return Number.isNaN(d.getTime()) ? '' : d.toLocaleString();
|
|
@@ -16124,12 +16566,12 @@ class FlyModerationQueueComponent {
|
|
|
16124
16566
|
btn?.focus({ preventScroll: true });
|
|
16125
16567
|
}
|
|
16126
16568
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyModerationQueueComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
16127
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyModerationQueueComponent, isStandalone: true, selector: "fly-moderation-queue", inputs: { page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: true, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { loadPage: "loadPage", resolve: "resolve", lockThread: "lockThread", openSubject: "openSubject" }, host: { listeners: { "document:keydown.escape": "onEscape()" }, classAttribute: "fly-moderation-queue" }, ngImport: i0, template: "<div class=\"fly-moderation-queue__body\">\r\n <!-- \u2500\u2500 Header: count + status filter \u2500\u2500 -->\r\n <header class=\"fly-moderation-queue__header\">\r\n <span class=\"fly-moderation-queue__count\">\r\n {{ 'moderation.count' | translate: { n: page().total } }}\r\n </span>\r\n\r\n <div class=\"fly-moderation-queue__status-filter\" role=\"group\" [attr.aria-label]=\"'moderation.status_label' | translate\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-moderation-queue__status-btn\"\r\n [class.fly-moderation-queue__status-btn--active]=\"status() === null\"\r\n [attr.aria-pressed]=\"status() === null\"\r\n [disabled]=\"loading()\"\r\n (click)=\"onStatusPick(null)\">\r\n {{ 'moderation.status_all' | translate }}\r\n </button>\r\n @for (s of statuses; track s) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-moderation-queue__status-btn\"\r\n [class.fly-moderation-queue__status-btn--active]=\"status() === s\"\r\n [attr.aria-pressed]=\"status() === s\"\r\n [disabled]=\"loading()\"\r\n (click)=\"onStatusPick(s)\">\r\n {{ 'moderation.status.' + s | translate }}\r\n </button>\r\n }\r\n </div>\r\n </header>\r\n\r\n <!-- \u2500\u2500 States \u2500\u2500 -->\r\n @if (error()) {\r\n <p class=\"fly-moderation-queue__error\" role=\"alert\">{{ error() }}</p>\r\n } @else if (loading() && page().items.length === 0) {\r\n <p class=\"fly-moderation-queue__status-msg\">{{ 'moderation.loading' | translate }}</p>\r\n } @else if (page().items.length === 0) {\r\n <p class=\"fly-moderation-queue__empty\">{{ 'moderation.empty' | translate }}</p>\r\n } @else {\r\n <!-- \u2500\u2500 Report table \u2500\u2500 -->\r\n <div class=\"fly-moderation-queue__table-wrap\">\r\n <table class=\"fly-moderation-queue__table\">\r\n <caption class=\"fly-moderation-queue__sr-only\">{{ 'moderation.table_caption' | translate }}</caption>\r\n <thead>\r\n <tr>\r\n <th scope=\"col\">{{ 'moderation.col_reason' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_reporter' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_reported' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_excerpt' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_subject' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_status' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_actions' | translate }}</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (r of page().items; track r.id) {\r\n <tr>\r\n <td class=\"fly-moderation-queue__reason-cell\">\r\n <span class=\"fly-moderation-queue__reason\">{{ 'comment.reason.' + r.reason | translate }}</span>\r\n @if (r.detail) {\r\n <span class=\"fly-moderation-queue__detail\" [title]=\"r.detail\">{{ r.detail }}</span>\r\n }\r\n </td>\r\n <td>{{ r.reporterDisplayName || r.reporterUserId }}</td>\r\n <td>{{ formatDate(r.createdAt) }}</td>\r\n <td class=\"fly-moderation-queue__excerpt\">{{ r.commentExcerpt || '\u2014' }}</td>\r\n <td>\r\n <button type=\"button\" class=\"fly-moderation-queue__subject-link\" [title]=\"subjectHref(r)\" (click)=\"onOpenSubject(r)\">\r\n {{ subjectDisplay(r) }}\r\n </button>\r\n </td>\r\n <td>\r\n <span class=\"fly-moderation-queue__badge\" [class]=\"'fly-moderation-queue__badge--' + r.status\">\r\n {{ 'moderation.status.' + r.status | translate }}\r\n </span>\r\n @if (r.resolutionNote) {\r\n <div class=\"fly-moderation-queue__resolution-note\">{{ r.resolutionNote }}</div>\r\n }\r\n </td>\r\n <td class=\"fly-moderation-queue__actions\">\r\n @if (isResolvable(r.status)) {\r\n <button type=\"button\" class=\"fly-moderation-queue__action-btn\" [disabled]=\"loading()\" (click)=\"openResolve(r)\">\r\n {{ 'moderation.resolve' | translate }}\r\n </button>\r\n }\r\n @if (confirmingLockId() === r.id) {\r\n <span class=\"fly-moderation-queue__lock-confirm\">\r\n <button type=\"button\" class=\"fly-moderation-queue__cancel-btn\" (click)=\"cancelLock()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-moderation-queue__danger-btn\" (click)=\"confirmLock(r)\">\r\n {{ 'moderation.lock_thread' | translate }}\r\n </button>\r\n </span>\r\n } @else {\r\n <button type=\"button\" class=\"fly-moderation-queue__action-btn\" [disabled]=\"loading()\" (click)=\"requestLock(r)\">\r\n {{ 'moderation.lock_thread' | translate }}\r\n </button>\r\n }\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n\r\n @if (page().hasMore) {\r\n <button type=\"button\" class=\"fly-moderation-queue__load-more\" [disabled]=\"loading()\" (click)=\"loadMore()\">\r\n {{ 'comment.load_more' | translate }}\r\n </button>\r\n }\r\n }\r\n</div>\r\n\r\n<!-- \u2500\u2500 Resolve dialog \u2500\u2500 -->\r\n@if (resolvingReport(); as r) {\r\n <div class=\"fly-moderation-queue__overlay\">\r\n <div class=\"fly-moderation-queue__overlay-backdrop\" role=\"button\" tabindex=\"0\"\r\n [attr.aria-label]=\"'common.action.cancel' | translate\"\r\n (click)=\"closeResolve()\"\r\n (keydown.enter)=\"closeResolve()\"\r\n (keydown.space)=\"onOverlayBackdropSpace($event)\"></div>\r\n <div\r\n class=\"fly-moderation-queue__dialog\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n tabindex=\"-1\"\r\n [attr.aria-label]=\"'moderation.resolve_title' | translate\">\r\n <h3 class=\"fly-moderation-queue__dialog-title\">{{ 'moderation.resolve_title' | translate }}</h3>\r\n\r\n <fieldset class=\"fly-moderation-queue__resolution-group\">\r\n <legend>{{ 'moderation.resolution_label' | translate }}</legend>\r\n <label class=\"fly-moderation-queue__resolution-option\">\r\n <input\r\n type=\"radio\"\r\n [name]=\"resolutionGroupName\"\r\n [checked]=\"resolutionStatus() === 'ActionTaken'\"\r\n (change)=\"onResolutionStatusChange('ActionTaken')\" />\r\n <span>{{ 'moderation.resolution.ActionTaken' | translate }}</span>\r\n </label>\r\n <label class=\"fly-moderation-queue__resolution-option\">\r\n <input\r\n type=\"radio\"\r\n [name]=\"resolutionGroupName\"\r\n [checked]=\"resolutionStatus() === 'Dismissed'\"\r\n (change)=\"onResolutionStatusChange('Dismissed')\" />\r\n <span>{{ 'moderation.resolution.Dismissed' | translate }}</span>\r\n </label>\r\n </fieldset>\r\n\r\n @if (resolutionStatus() === 'ActionTaken') {\r\n <label class=\"fly-moderation-queue__checkbox-row\">\r\n <input type=\"checkbox\" [checked]=\"deleteComment()\" (change)=\"onDeleteCommentToggle($event)\" />\r\n <span>{{ 'moderation.delete_comment_label' | translate }}</span>\r\n </label>\r\n }\r\n\r\n <textarea\r\n class=\"fly-moderation-queue__textarea\"\r\n rows=\"3\"\r\n [attr.aria-label]=\"'moderation.resolution_note_label' | translate\"\r\n [placeholder]=\"'moderation.resolution_note_label' | translate\"\r\n [ngModel]=\"resolutionNote()\"\r\n (ngModelChange)=\"onResolutionNoteChange($event)\"></textarea>\r\n\r\n <div class=\"fly-moderation-queue__dialog-actions\">\r\n <button type=\"button\" class=\"fly-moderation-queue__cancel-btn\" (click)=\"closeResolve()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fly-moderation-queue__submit-btn\"\r\n [disabled]=\"!resolutionStatus()\"\r\n (click)=\"submitResolve(r)\">\r\n {{ 'moderation.resolve_submit' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--surface-card, #fff);--_surface-hover: var(--surface-hover, #f1f5f9);--_border: var(--separator, var(--surface-border, #e2e8f0));--_text: var(--label-primary, var(--text-color, #0f172a));--_text-subtle: var(--label-secondary, var(--text-color-secondary, #64748b));--_text-tertiary: var(--label-tertiary, #94a3b8);--_fill: var(--fill-tertiary, #f3f4f6);--_accent: var(--accent);--_danger: var(--system-red, #ef4444);--_blue: var(--system-blue, #0a84ff);--_orange: var(--system-orange, #ff9f0a);--_green: var(--system-green, #32d74b);--_radius: 8px;--_blue-text: #1d4ed8;--_orange-text: #9a3412;--_green-text: #166534;--_danger-text: #b91c1c;display:block;inline-size:100%;color:var(--_text);font-size:13px}.fly-moderation-queue__body{display:flex;flex-direction:column;gap:16px}.fly-moderation-queue__header{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.fly-moderation-queue__count{font-weight:700;font-size:14px}.fly-moderation-queue__status-filter{display:inline-flex;flex-wrap:wrap;border:1px solid var(--_border);border-radius:var(--_radius);overflow:hidden}.fly-moderation-queue__status-btn{padding:5px 12px;border:none;border-inline-end:1px solid var(--_border);background:transparent;color:var(--_text-subtle);font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__status-btn:last-child{border-inline-end:none}.fly-moderation-queue__status-btn:disabled{opacity:.6;cursor:not-allowed}.fly-moderation-queue__status-btn--active{background:var(--_accent);color:#fff}.fly-moderation-queue__empty,.fly-moderation-queue__status-msg{margin:0;color:var(--_text-subtle);text-align:center;padding:16px 0}.fly-moderation-queue__error{margin:0;padding:8px 12px;border-radius:var(--_radius);background:color-mix(in srgb,var(--_danger) 12%,transparent);color:var(--_danger);font-size:12px}.fly-moderation-queue__sr-only{position:absolute;inline-size:1px;block-size:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.fly-moderation-queue__table-wrap{overflow-x:auto}.fly-moderation-queue__table{inline-size:100%;border-collapse:collapse;font-size:12px}.fly-moderation-queue__table th{text-align:start;padding:6px 10px;color:var(--_text-subtle);font-weight:600;border-block-end:1px solid var(--_border);white-space:nowrap}.fly-moderation-queue__table td{padding:8px 10px;border-block-end:1px solid var(--_border);vertical-align:top}.fly-moderation-queue__reason-cell{display:table-cell}.fly-moderation-queue__reason{display:block;font-weight:600}.fly-moderation-queue__detail{display:block;margin-block-start:2px;color:var(--_text-tertiary);font-size:11px;max-inline-size:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-moderation-queue__excerpt{max-inline-size:260px;color:var(--_text-subtle);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-moderation-queue__subject-link{border:none;background:transparent;padding:0;color:var(--_accent);font:inherit;font-weight:600;cursor:pointer;text-align:start}.fly-moderation-queue__subject-link:hover{text-decoration:underline}.fly-moderation-queue__badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:700;white-space:nowrap}.fly-moderation-queue__badge--Open{background:color-mix(in srgb,var(--_blue) 12%,#fff);color:var(--_blue-text)}.fly-moderation-queue__badge--UnderReview{background:color-mix(in srgb,var(--_orange) 12%,#fff);color:var(--_orange-text)}.fly-moderation-queue__badge--ActionTaken{background:color-mix(in srgb,var(--_danger) 12%,#fff);color:var(--_danger-text)}.fly-moderation-queue__badge--Dismissed{background:color-mix(in srgb,var(--_green) 12%,#fff);color:var(--_green-text)}.fly-moderation-queue__resolution-note{margin-block-start:4px;color:var(--_text-tertiary);font-size:11px;max-inline-size:200px}.fly-moderation-queue__actions{display:flex;flex-direction:column;align-items:flex-start;gap:6px;white-space:nowrap}.fly-moderation-queue__action-btn{border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);padding:4px 10px;color:var(--_text);font:inherit;font-size:11px;font-weight:600;cursor:pointer}.fly-moderation-queue__action-btn:hover:not(:disabled){background:var(--_surface-hover)}.fly-moderation-queue__action-btn:disabled{opacity:.6;cursor:not-allowed}.fly-moderation-queue__lock-confirm{display:inline-flex;align-items:center;gap:6px}.fly-moderation-queue__load-more{align-self:flex-start;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);padding:6px 14px;color:var(--_text);font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__load-more:hover:not(:disabled){background:var(--_surface-hover)}.fly-moderation-queue__load-more:disabled{opacity:.6;cursor:not-allowed}.fly-moderation-queue__overlay{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;z-index:1000}.fly-moderation-queue__overlay-backdrop{position:absolute;inset:0;background:#00000073;border:none;padding:0;margin:0;cursor:pointer}.fly-moderation-queue__dialog{position:relative;z-index:1;inline-size:min(440px,90vw);max-block-size:85vh;overflow-y:auto;background:var(--_surface);border-radius:12px;padding:20px;display:flex;flex-direction:column;gap:14px;box-shadow:0 12px 32px #00000040}.fly-moderation-queue__dialog:focus-visible{outline:2px solid var(--_accent);outline-offset:2px}.fly-moderation-queue__dialog-title{margin:0;font-size:16px;font-weight:700}.fly-moderation-queue__resolution-group{display:flex;flex-direction:column;gap:8px;margin:0;padding:0;border:none}.fly-moderation-queue__resolution-group legend{font-weight:600;padding:0;margin-block-end:4px}.fly-moderation-queue__resolution-option,.fly-moderation-queue__checkbox-row{display:flex;align-items:center;gap:8px;cursor:pointer}.fly-moderation-queue__resolution-option input,.fly-moderation-queue__checkbox-row input{accent-color:var(--_accent);cursor:pointer}.fly-moderation-queue__textarea{inline-size:100%;padding:8px 12px;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);color:var(--_text);font:inherit;line-height:1.5;resize:vertical}.fly-moderation-queue__textarea:focus-visible{outline:2px solid var(--_accent);outline-offset:1px}.fly-moderation-queue__dialog-actions{display:flex;justify-content:flex-end;gap:8px}.fly-moderation-queue__submit-btn{padding:6px 16px;border:none;border-radius:var(--_radius);background:var(--_accent);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__submit-btn:hover:not(:disabled){opacity:.9}.fly-moderation-queue__submit-btn:disabled{opacity:.5;cursor:not-allowed}.fly-moderation-queue__cancel-btn{padding:6px 14px;border:1px solid var(--_border);border-radius:var(--_radius);background:transparent;color:var(--_text-subtle);font:inherit;cursor:pointer}.fly-moderation-queue__cancel-btn:hover{background:var(--_surface-hover)}.fly-moderation-queue__danger-btn{padding:6px 14px;border:none;border-radius:var(--_radius);background:var(--_danger);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__danger-btn:hover{opacity:.9}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
16569
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyModerationQueueComponent, isStandalone: true, selector: "fly-moderation-queue", inputs: { page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: true, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null }, resolveDisplayName: { classPropertyName: "resolveDisplayName", publicName: "resolveDisplayName", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { loadPage: "loadPage", resolve: "resolve", lockThread: "lockThread", openSubject: "openSubject" }, host: { listeners: { "document:keydown.escape": "onEscape()" }, classAttribute: "fly-moderation-queue" }, ngImport: i0, template: "<div class=\"fly-moderation-queue__body\">\r\n <!-- \u2500\u2500 Header: count + status filter \u2500\u2500 -->\r\n <header class=\"fly-moderation-queue__header\">\r\n <span class=\"fly-moderation-queue__count\">\r\n {{ 'moderation.count' | translate: { n: page().total } }}\r\n </span>\r\n\r\n <div class=\"fly-moderation-queue__status-filter\" role=\"group\" [attr.aria-label]=\"'moderation.status_label' | translate\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-moderation-queue__status-btn\"\r\n [class.fly-moderation-queue__status-btn--active]=\"status() === null\"\r\n [attr.aria-pressed]=\"status() === null\"\r\n [disabled]=\"loading()\"\r\n (click)=\"onStatusPick(null)\">\r\n {{ 'moderation.status_all' | translate }}\r\n </button>\r\n @for (s of statuses; track s) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-moderation-queue__status-btn\"\r\n [class.fly-moderation-queue__status-btn--active]=\"status() === s\"\r\n [attr.aria-pressed]=\"status() === s\"\r\n [disabled]=\"loading()\"\r\n (click)=\"onStatusPick(s)\">\r\n {{ 'moderation.status.' + s | translate }}\r\n </button>\r\n }\r\n </div>\r\n </header>\r\n\r\n <!-- \u2500\u2500 States \u2500\u2500 -->\r\n @if (error()) {\r\n <p class=\"fly-moderation-queue__error\" role=\"alert\">{{ error() }}</p>\r\n } @else if (loading() && page().items.length === 0) {\r\n <p class=\"fly-moderation-queue__status-msg\">{{ 'moderation.loading' | translate }}</p>\r\n } @else if (page().items.length === 0) {\r\n <p class=\"fly-moderation-queue__empty\">{{ 'moderation.empty' | translate }}</p>\r\n } @else {\r\n <!-- \u2500\u2500 Report table \u2500\u2500 -->\r\n <div class=\"fly-moderation-queue__table-wrap\">\r\n <table class=\"fly-moderation-queue__table\">\r\n <caption class=\"fly-moderation-queue__sr-only\">{{ 'moderation.table_caption' | translate }}</caption>\r\n <thead>\r\n <tr>\r\n <th scope=\"col\">{{ 'moderation.col_reason' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_reporter' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_reported' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_excerpt' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_subject' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_status' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_actions' | translate }}</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (r of page().items; track r.id) {\r\n <tr>\r\n <td class=\"fly-moderation-queue__reason-cell\">\r\n <span class=\"fly-moderation-queue__reason\">{{ 'comment.reason.' + r.reason | translate }}</span>\r\n @if (r.detail) {\r\n <span class=\"fly-moderation-queue__detail\" [title]=\"r.detail\">{{ r.detail }}</span>\r\n }\r\n </td>\r\n <td>{{ reporterLabel(r) }}</td>\r\n <td>{{ formatDate(r.createdAt) }}</td>\r\n <td class=\"fly-moderation-queue__excerpt\">{{ r.commentExcerpt || '\u2014' }}</td>\r\n <td>\r\n <button type=\"button\" class=\"fly-moderation-queue__subject-link\" [title]=\"subjectHref(r)\" (click)=\"onOpenSubject(r)\">\r\n {{ subjectDisplay(r) }}\r\n </button>\r\n </td>\r\n <td>\r\n <span class=\"fly-moderation-queue__badge\" [class]=\"'fly-moderation-queue__badge--' + r.status\">\r\n {{ 'moderation.status.' + r.status | translate }}\r\n </span>\r\n @if (r.resolutionNote) {\r\n <div class=\"fly-moderation-queue__resolution-note\">{{ r.resolutionNote }}</div>\r\n }\r\n </td>\r\n <td class=\"fly-moderation-queue__actions\">\r\n @if (isResolvable(r.status)) {\r\n <button type=\"button\" class=\"fly-moderation-queue__action-btn\" [disabled]=\"loading()\" (click)=\"openResolve(r)\">\r\n {{ 'moderation.resolve' | translate }}\r\n </button>\r\n }\r\n @if (confirmingLockId() === r.id) {\r\n <span class=\"fly-moderation-queue__lock-confirm\">\r\n <button type=\"button\" class=\"fly-moderation-queue__cancel-btn\" (click)=\"cancelLock()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-moderation-queue__danger-btn\" (click)=\"confirmLock(r)\">\r\n {{ 'moderation.lock_thread' | translate }}\r\n </button>\r\n </span>\r\n } @else {\r\n <button type=\"button\" class=\"fly-moderation-queue__action-btn\" [disabled]=\"loading()\" (click)=\"requestLock(r)\">\r\n {{ 'moderation.lock_thread' | translate }}\r\n </button>\r\n }\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n\r\n @if (page().hasMore) {\r\n <button type=\"button\" class=\"fly-moderation-queue__load-more\" [disabled]=\"loading()\" (click)=\"loadMore()\">\r\n {{ 'comment.load_more' | translate }}\r\n </button>\r\n }\r\n }\r\n</div>\r\n\r\n<!-- \u2500\u2500 Resolve dialog \u2500\u2500 -->\r\n@if (resolvingReport(); as r) {\r\n <div class=\"fly-moderation-queue__overlay\">\r\n <div class=\"fly-moderation-queue__overlay-backdrop\" role=\"button\" tabindex=\"0\"\r\n [attr.aria-label]=\"'common.action.cancel' | translate\"\r\n (click)=\"closeResolve()\"\r\n (keydown.enter)=\"closeResolve()\"\r\n (keydown.space)=\"onOverlayBackdropSpace($event)\"></div>\r\n <div\r\n class=\"fly-moderation-queue__dialog\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n tabindex=\"-1\"\r\n [attr.aria-label]=\"'moderation.resolve_title' | translate\">\r\n <h3 class=\"fly-moderation-queue__dialog-title\">{{ 'moderation.resolve_title' | translate }}</h3>\r\n\r\n <fieldset class=\"fly-moderation-queue__resolution-group\">\r\n <legend>{{ 'moderation.resolution_label' | translate }}</legend>\r\n <label class=\"fly-moderation-queue__resolution-option\">\r\n <input\r\n type=\"radio\"\r\n [name]=\"resolutionGroupName\"\r\n [checked]=\"resolutionStatus() === 'ActionTaken'\"\r\n (change)=\"onResolutionStatusChange('ActionTaken')\" />\r\n <span>{{ 'moderation.resolution.ActionTaken' | translate }}</span>\r\n </label>\r\n <label class=\"fly-moderation-queue__resolution-option\">\r\n <input\r\n type=\"radio\"\r\n [name]=\"resolutionGroupName\"\r\n [checked]=\"resolutionStatus() === 'Dismissed'\"\r\n (change)=\"onResolutionStatusChange('Dismissed')\" />\r\n <span>{{ 'moderation.resolution.Dismissed' | translate }}</span>\r\n </label>\r\n </fieldset>\r\n\r\n @if (resolutionStatus() === 'ActionTaken') {\r\n <label class=\"fly-moderation-queue__checkbox-row\">\r\n <input type=\"checkbox\" [checked]=\"deleteComment()\" (change)=\"onDeleteCommentToggle($event)\" />\r\n <span>{{ 'moderation.delete_comment_label' | translate }}</span>\r\n </label>\r\n }\r\n\r\n <textarea\r\n class=\"fly-moderation-queue__textarea\"\r\n rows=\"3\"\r\n [attr.aria-label]=\"'moderation.resolution_note_label' | translate\"\r\n [placeholder]=\"'moderation.resolution_note_label' | translate\"\r\n [ngModel]=\"resolutionNote()\"\r\n (ngModelChange)=\"onResolutionNoteChange($event)\"></textarea>\r\n\r\n <div class=\"fly-moderation-queue__dialog-actions\">\r\n <button type=\"button\" class=\"fly-moderation-queue__cancel-btn\" (click)=\"closeResolve()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fly-moderation-queue__submit-btn\"\r\n [disabled]=\"!resolutionStatus()\"\r\n (click)=\"submitResolve(r)\">\r\n {{ 'moderation.resolve_submit' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--surface-card, #fff);--_surface-hover: var(--surface-hover, #f1f5f9);--_border: var(--separator, var(--surface-border, #e2e8f0));--_text: var(--label-primary, var(--text-color, #0f172a));--_text-subtle: var(--label-secondary, var(--text-color-secondary, #64748b));--_text-tertiary: var(--label-tertiary, #94a3b8);--_fill: var(--fill-tertiary, #f3f4f6);--_accent: var(--accent);--_danger: var(--system-red, #ef4444);--_blue: var(--system-blue, #0a84ff);--_orange: var(--system-orange, #ff9f0a);--_green: var(--system-green, #32d74b);--_radius: 8px;--_blue-text: #1d4ed8;--_orange-text: #9a3412;--_green-text: #166534;--_danger-text: #b91c1c;display:block;inline-size:100%;color:var(--_text);font-size:13px}.fly-moderation-queue__body{display:flex;flex-direction:column;gap:16px}.fly-moderation-queue__header{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.fly-moderation-queue__count{font-weight:700;font-size:14px}.fly-moderation-queue__status-filter{display:inline-flex;flex-wrap:wrap;border:1px solid var(--_border);border-radius:var(--_radius);overflow:hidden}.fly-moderation-queue__status-btn{padding:5px 12px;border:none;border-inline-end:1px solid var(--_border);background:transparent;color:var(--_text-subtle);font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__status-btn:last-child{border-inline-end:none}.fly-moderation-queue__status-btn:disabled{opacity:.6;cursor:not-allowed}.fly-moderation-queue__status-btn--active{background:var(--_accent);color:#fff}.fly-moderation-queue__empty,.fly-moderation-queue__status-msg{margin:0;color:var(--_text-subtle);text-align:center;padding:16px 0}.fly-moderation-queue__error{margin:0;padding:8px 12px;border-radius:var(--_radius);background:color-mix(in srgb,var(--_danger) 12%,transparent);color:var(--_danger);font-size:12px}.fly-moderation-queue__sr-only{position:absolute;inline-size:1px;block-size:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.fly-moderation-queue__table-wrap{overflow-x:auto}.fly-moderation-queue__table{inline-size:100%;border-collapse:collapse;font-size:12px}.fly-moderation-queue__table th{text-align:start;padding:6px 10px;color:var(--_text-subtle);font-weight:600;border-block-end:1px solid var(--_border);white-space:nowrap}.fly-moderation-queue__table td{padding:8px 10px;border-block-end:1px solid var(--_border);vertical-align:top}.fly-moderation-queue__reason-cell{display:table-cell}.fly-moderation-queue__reason{display:block;font-weight:600}.fly-moderation-queue__detail{display:block;margin-block-start:2px;color:var(--_text-tertiary);font-size:11px;max-inline-size:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-moderation-queue__excerpt{max-inline-size:260px;color:var(--_text-subtle);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-moderation-queue__subject-link{border:none;background:transparent;padding:0;color:var(--_accent);font:inherit;font-weight:600;cursor:pointer;text-align:start}.fly-moderation-queue__subject-link:hover{text-decoration:underline}.fly-moderation-queue__badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:700;white-space:nowrap}.fly-moderation-queue__badge--Open{background:color-mix(in srgb,var(--_blue) 12%,#fff);color:var(--_blue-text)}.fly-moderation-queue__badge--UnderReview{background:color-mix(in srgb,var(--_orange) 12%,#fff);color:var(--_orange-text)}.fly-moderation-queue__badge--ActionTaken{background:color-mix(in srgb,var(--_danger) 12%,#fff);color:var(--_danger-text)}.fly-moderation-queue__badge--Dismissed{background:color-mix(in srgb,var(--_green) 12%,#fff);color:var(--_green-text)}.fly-moderation-queue__resolution-note{margin-block-start:4px;color:var(--_text-tertiary);font-size:11px;max-inline-size:200px}.fly-moderation-queue__actions{display:flex;flex-direction:column;align-items:flex-start;gap:6px;white-space:nowrap}.fly-moderation-queue__action-btn{border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);padding:4px 10px;color:var(--_text);font:inherit;font-size:11px;font-weight:600;cursor:pointer}.fly-moderation-queue__action-btn:hover:not(:disabled){background:var(--_surface-hover)}.fly-moderation-queue__action-btn:disabled{opacity:.6;cursor:not-allowed}.fly-moderation-queue__lock-confirm{display:inline-flex;align-items:center;gap:6px}.fly-moderation-queue__load-more{align-self:flex-start;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);padding:6px 14px;color:var(--_text);font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__load-more:hover:not(:disabled){background:var(--_surface-hover)}.fly-moderation-queue__load-more:disabled{opacity:.6;cursor:not-allowed}.fly-moderation-queue__overlay{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;z-index:1000}.fly-moderation-queue__overlay-backdrop{position:absolute;inset:0;background:#00000073;border:none;padding:0;margin:0;cursor:pointer}.fly-moderation-queue__dialog{position:relative;z-index:1;inline-size:min(440px,90vw);max-block-size:85vh;overflow-y:auto;background:var(--_surface);border-radius:12px;padding:20px;display:flex;flex-direction:column;gap:14px;box-shadow:0 12px 32px #00000040}.fly-moderation-queue__dialog:focus-visible{outline:2px solid var(--_accent);outline-offset:2px}.fly-moderation-queue__dialog-title{margin:0;font-size:16px;font-weight:700}.fly-moderation-queue__resolution-group{display:flex;flex-direction:column;gap:8px;margin:0;padding:0;border:none}.fly-moderation-queue__resolution-group legend{font-weight:600;padding:0;margin-block-end:4px}.fly-moderation-queue__resolution-option,.fly-moderation-queue__checkbox-row{display:flex;align-items:center;gap:8px;cursor:pointer}.fly-moderation-queue__resolution-option input,.fly-moderation-queue__checkbox-row input{accent-color:var(--_accent);cursor:pointer}.fly-moderation-queue__textarea{inline-size:100%;padding:8px 12px;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);color:var(--_text);font:inherit;line-height:1.5;resize:vertical}.fly-moderation-queue__textarea:focus-visible{outline:2px solid var(--_accent);outline-offset:1px}.fly-moderation-queue__dialog-actions{display:flex;justify-content:flex-end;gap:8px}.fly-moderation-queue__submit-btn{padding:6px 16px;border:none;border-radius:var(--_radius);background:var(--_accent);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__submit-btn:hover:not(:disabled){opacity:.9}.fly-moderation-queue__submit-btn:disabled{opacity:.5;cursor:not-allowed}.fly-moderation-queue__cancel-btn{padding:6px 14px;border:1px solid var(--_border);border-radius:var(--_radius);background:transparent;color:var(--_text-subtle);font:inherit;cursor:pointer}.fly-moderation-queue__cancel-btn:hover{background:var(--_surface-hover)}.fly-moderation-queue__danger-btn{padding:6px 14px;border:none;border-radius:var(--_radius);background:var(--_danger);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__danger-btn:hover{opacity:.9}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
16128
16570
|
}
|
|
16129
16571
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyModerationQueueComponent, decorators: [{
|
|
16130
16572
|
type: Component,
|
|
16131
|
-
args: [{ selector: 'fly-moderation-queue', standalone: true, imports: [CommonModule, FormsModule, TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'fly-moderation-queue' }, template: "<div class=\"fly-moderation-queue__body\">\r\n <!-- \u2500\u2500 Header: count + status filter \u2500\u2500 -->\r\n <header class=\"fly-moderation-queue__header\">\r\n <span class=\"fly-moderation-queue__count\">\r\n {{ 'moderation.count' | translate: { n: page().total } }}\r\n </span>\r\n\r\n <div class=\"fly-moderation-queue__status-filter\" role=\"group\" [attr.aria-label]=\"'moderation.status_label' | translate\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-moderation-queue__status-btn\"\r\n [class.fly-moderation-queue__status-btn--active]=\"status() === null\"\r\n [attr.aria-pressed]=\"status() === null\"\r\n [disabled]=\"loading()\"\r\n (click)=\"onStatusPick(null)\">\r\n {{ 'moderation.status_all' | translate }}\r\n </button>\r\n @for (s of statuses; track s) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-moderation-queue__status-btn\"\r\n [class.fly-moderation-queue__status-btn--active]=\"status() === s\"\r\n [attr.aria-pressed]=\"status() === s\"\r\n [disabled]=\"loading()\"\r\n (click)=\"onStatusPick(s)\">\r\n {{ 'moderation.status.' + s | translate }}\r\n </button>\r\n }\r\n </div>\r\n </header>\r\n\r\n <!-- \u2500\u2500 States \u2500\u2500 -->\r\n @if (error()) {\r\n <p class=\"fly-moderation-queue__error\" role=\"alert\">{{ error() }}</p>\r\n } @else if (loading() && page().items.length === 0) {\r\n <p class=\"fly-moderation-queue__status-msg\">{{ 'moderation.loading' | translate }}</p>\r\n } @else if (page().items.length === 0) {\r\n <p class=\"fly-moderation-queue__empty\">{{ 'moderation.empty' | translate }}</p>\r\n } @else {\r\n <!-- \u2500\u2500 Report table \u2500\u2500 -->\r\n <div class=\"fly-moderation-queue__table-wrap\">\r\n <table class=\"fly-moderation-queue__table\">\r\n <caption class=\"fly-moderation-queue__sr-only\">{{ 'moderation.table_caption' | translate }}</caption>\r\n <thead>\r\n <tr>\r\n <th scope=\"col\">{{ 'moderation.col_reason' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_reporter' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_reported' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_excerpt' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_subject' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_status' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_actions' | translate }}</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (r of page().items; track r.id) {\r\n <tr>\r\n <td class=\"fly-moderation-queue__reason-cell\">\r\n <span class=\"fly-moderation-queue__reason\">{{ 'comment.reason.' + r.reason | translate }}</span>\r\n @if (r.detail) {\r\n <span class=\"fly-moderation-queue__detail\" [title]=\"r.detail\">{{ r.detail }}</span>\r\n }\r\n </td>\r\n <td>{{ r.reporterDisplayName || r.reporterUserId }}</td>\r\n <td>{{ formatDate(r.createdAt) }}</td>\r\n <td class=\"fly-moderation-queue__excerpt\">{{ r.commentExcerpt || '\u2014' }}</td>\r\n <td>\r\n <button type=\"button\" class=\"fly-moderation-queue__subject-link\" [title]=\"subjectHref(r)\" (click)=\"onOpenSubject(r)\">\r\n {{ subjectDisplay(r) }}\r\n </button>\r\n </td>\r\n <td>\r\n <span class=\"fly-moderation-queue__badge\" [class]=\"'fly-moderation-queue__badge--' + r.status\">\r\n {{ 'moderation.status.' + r.status | translate }}\r\n </span>\r\n @if (r.resolutionNote) {\r\n <div class=\"fly-moderation-queue__resolution-note\">{{ r.resolutionNote }}</div>\r\n }\r\n </td>\r\n <td class=\"fly-moderation-queue__actions\">\r\n @if (isResolvable(r.status)) {\r\n <button type=\"button\" class=\"fly-moderation-queue__action-btn\" [disabled]=\"loading()\" (click)=\"openResolve(r)\">\r\n {{ 'moderation.resolve' | translate }}\r\n </button>\r\n }\r\n @if (confirmingLockId() === r.id) {\r\n <span class=\"fly-moderation-queue__lock-confirm\">\r\n <button type=\"button\" class=\"fly-moderation-queue__cancel-btn\" (click)=\"cancelLock()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-moderation-queue__danger-btn\" (click)=\"confirmLock(r)\">\r\n {{ 'moderation.lock_thread' | translate }}\r\n </button>\r\n </span>\r\n } @else {\r\n <button type=\"button\" class=\"fly-moderation-queue__action-btn\" [disabled]=\"loading()\" (click)=\"requestLock(r)\">\r\n {{ 'moderation.lock_thread' | translate }}\r\n </button>\r\n }\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n\r\n @if (page().hasMore) {\r\n <button type=\"button\" class=\"fly-moderation-queue__load-more\" [disabled]=\"loading()\" (click)=\"loadMore()\">\r\n {{ 'comment.load_more' | translate }}\r\n </button>\r\n }\r\n }\r\n</div>\r\n\r\n<!-- \u2500\u2500 Resolve dialog \u2500\u2500 -->\r\n@if (resolvingReport(); as r) {\r\n <div class=\"fly-moderation-queue__overlay\">\r\n <div class=\"fly-moderation-queue__overlay-backdrop\" role=\"button\" tabindex=\"0\"\r\n [attr.aria-label]=\"'common.action.cancel' | translate\"\r\n (click)=\"closeResolve()\"\r\n (keydown.enter)=\"closeResolve()\"\r\n (keydown.space)=\"onOverlayBackdropSpace($event)\"></div>\r\n <div\r\n class=\"fly-moderation-queue__dialog\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n tabindex=\"-1\"\r\n [attr.aria-label]=\"'moderation.resolve_title' | translate\">\r\n <h3 class=\"fly-moderation-queue__dialog-title\">{{ 'moderation.resolve_title' | translate }}</h3>\r\n\r\n <fieldset class=\"fly-moderation-queue__resolution-group\">\r\n <legend>{{ 'moderation.resolution_label' | translate }}</legend>\r\n <label class=\"fly-moderation-queue__resolution-option\">\r\n <input\r\n type=\"radio\"\r\n [name]=\"resolutionGroupName\"\r\n [checked]=\"resolutionStatus() === 'ActionTaken'\"\r\n (change)=\"onResolutionStatusChange('ActionTaken')\" />\r\n <span>{{ 'moderation.resolution.ActionTaken' | translate }}</span>\r\n </label>\r\n <label class=\"fly-moderation-queue__resolution-option\">\r\n <input\r\n type=\"radio\"\r\n [name]=\"resolutionGroupName\"\r\n [checked]=\"resolutionStatus() === 'Dismissed'\"\r\n (change)=\"onResolutionStatusChange('Dismissed')\" />\r\n <span>{{ 'moderation.resolution.Dismissed' | translate }}</span>\r\n </label>\r\n </fieldset>\r\n\r\n @if (resolutionStatus() === 'ActionTaken') {\r\n <label class=\"fly-moderation-queue__checkbox-row\">\r\n <input type=\"checkbox\" [checked]=\"deleteComment()\" (change)=\"onDeleteCommentToggle($event)\" />\r\n <span>{{ 'moderation.delete_comment_label' | translate }}</span>\r\n </label>\r\n }\r\n\r\n <textarea\r\n class=\"fly-moderation-queue__textarea\"\r\n rows=\"3\"\r\n [attr.aria-label]=\"'moderation.resolution_note_label' | translate\"\r\n [placeholder]=\"'moderation.resolution_note_label' | translate\"\r\n [ngModel]=\"resolutionNote()\"\r\n (ngModelChange)=\"onResolutionNoteChange($event)\"></textarea>\r\n\r\n <div class=\"fly-moderation-queue__dialog-actions\">\r\n <button type=\"button\" class=\"fly-moderation-queue__cancel-btn\" (click)=\"closeResolve()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fly-moderation-queue__submit-btn\"\r\n [disabled]=\"!resolutionStatus()\"\r\n (click)=\"submitResolve(r)\">\r\n {{ 'moderation.resolve_submit' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--surface-card, #fff);--_surface-hover: var(--surface-hover, #f1f5f9);--_border: var(--separator, var(--surface-border, #e2e8f0));--_text: var(--label-primary, var(--text-color, #0f172a));--_text-subtle: var(--label-secondary, var(--text-color-secondary, #64748b));--_text-tertiary: var(--label-tertiary, #94a3b8);--_fill: var(--fill-tertiary, #f3f4f6);--_accent: var(--accent);--_danger: var(--system-red, #ef4444);--_blue: var(--system-blue, #0a84ff);--_orange: var(--system-orange, #ff9f0a);--_green: var(--system-green, #32d74b);--_radius: 8px;--_blue-text: #1d4ed8;--_orange-text: #9a3412;--_green-text: #166534;--_danger-text: #b91c1c;display:block;inline-size:100%;color:var(--_text);font-size:13px}.fly-moderation-queue__body{display:flex;flex-direction:column;gap:16px}.fly-moderation-queue__header{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.fly-moderation-queue__count{font-weight:700;font-size:14px}.fly-moderation-queue__status-filter{display:inline-flex;flex-wrap:wrap;border:1px solid var(--_border);border-radius:var(--_radius);overflow:hidden}.fly-moderation-queue__status-btn{padding:5px 12px;border:none;border-inline-end:1px solid var(--_border);background:transparent;color:var(--_text-subtle);font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__status-btn:last-child{border-inline-end:none}.fly-moderation-queue__status-btn:disabled{opacity:.6;cursor:not-allowed}.fly-moderation-queue__status-btn--active{background:var(--_accent);color:#fff}.fly-moderation-queue__empty,.fly-moderation-queue__status-msg{margin:0;color:var(--_text-subtle);text-align:center;padding:16px 0}.fly-moderation-queue__error{margin:0;padding:8px 12px;border-radius:var(--_radius);background:color-mix(in srgb,var(--_danger) 12%,transparent);color:var(--_danger);font-size:12px}.fly-moderation-queue__sr-only{position:absolute;inline-size:1px;block-size:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.fly-moderation-queue__table-wrap{overflow-x:auto}.fly-moderation-queue__table{inline-size:100%;border-collapse:collapse;font-size:12px}.fly-moderation-queue__table th{text-align:start;padding:6px 10px;color:var(--_text-subtle);font-weight:600;border-block-end:1px solid var(--_border);white-space:nowrap}.fly-moderation-queue__table td{padding:8px 10px;border-block-end:1px solid var(--_border);vertical-align:top}.fly-moderation-queue__reason-cell{display:table-cell}.fly-moderation-queue__reason{display:block;font-weight:600}.fly-moderation-queue__detail{display:block;margin-block-start:2px;color:var(--_text-tertiary);font-size:11px;max-inline-size:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-moderation-queue__excerpt{max-inline-size:260px;color:var(--_text-subtle);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-moderation-queue__subject-link{border:none;background:transparent;padding:0;color:var(--_accent);font:inherit;font-weight:600;cursor:pointer;text-align:start}.fly-moderation-queue__subject-link:hover{text-decoration:underline}.fly-moderation-queue__badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:700;white-space:nowrap}.fly-moderation-queue__badge--Open{background:color-mix(in srgb,var(--_blue) 12%,#fff);color:var(--_blue-text)}.fly-moderation-queue__badge--UnderReview{background:color-mix(in srgb,var(--_orange) 12%,#fff);color:var(--_orange-text)}.fly-moderation-queue__badge--ActionTaken{background:color-mix(in srgb,var(--_danger) 12%,#fff);color:var(--_danger-text)}.fly-moderation-queue__badge--Dismissed{background:color-mix(in srgb,var(--_green) 12%,#fff);color:var(--_green-text)}.fly-moderation-queue__resolution-note{margin-block-start:4px;color:var(--_text-tertiary);font-size:11px;max-inline-size:200px}.fly-moderation-queue__actions{display:flex;flex-direction:column;align-items:flex-start;gap:6px;white-space:nowrap}.fly-moderation-queue__action-btn{border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);padding:4px 10px;color:var(--_text);font:inherit;font-size:11px;font-weight:600;cursor:pointer}.fly-moderation-queue__action-btn:hover:not(:disabled){background:var(--_surface-hover)}.fly-moderation-queue__action-btn:disabled{opacity:.6;cursor:not-allowed}.fly-moderation-queue__lock-confirm{display:inline-flex;align-items:center;gap:6px}.fly-moderation-queue__load-more{align-self:flex-start;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);padding:6px 14px;color:var(--_text);font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__load-more:hover:not(:disabled){background:var(--_surface-hover)}.fly-moderation-queue__load-more:disabled{opacity:.6;cursor:not-allowed}.fly-moderation-queue__overlay{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;z-index:1000}.fly-moderation-queue__overlay-backdrop{position:absolute;inset:0;background:#00000073;border:none;padding:0;margin:0;cursor:pointer}.fly-moderation-queue__dialog{position:relative;z-index:1;inline-size:min(440px,90vw);max-block-size:85vh;overflow-y:auto;background:var(--_surface);border-radius:12px;padding:20px;display:flex;flex-direction:column;gap:14px;box-shadow:0 12px 32px #00000040}.fly-moderation-queue__dialog:focus-visible{outline:2px solid var(--_accent);outline-offset:2px}.fly-moderation-queue__dialog-title{margin:0;font-size:16px;font-weight:700}.fly-moderation-queue__resolution-group{display:flex;flex-direction:column;gap:8px;margin:0;padding:0;border:none}.fly-moderation-queue__resolution-group legend{font-weight:600;padding:0;margin-block-end:4px}.fly-moderation-queue__resolution-option,.fly-moderation-queue__checkbox-row{display:flex;align-items:center;gap:8px;cursor:pointer}.fly-moderation-queue__resolution-option input,.fly-moderation-queue__checkbox-row input{accent-color:var(--_accent);cursor:pointer}.fly-moderation-queue__textarea{inline-size:100%;padding:8px 12px;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);color:var(--_text);font:inherit;line-height:1.5;resize:vertical}.fly-moderation-queue__textarea:focus-visible{outline:2px solid var(--_accent);outline-offset:1px}.fly-moderation-queue__dialog-actions{display:flex;justify-content:flex-end;gap:8px}.fly-moderation-queue__submit-btn{padding:6px 16px;border:none;border-radius:var(--_radius);background:var(--_accent);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__submit-btn:hover:not(:disabled){opacity:.9}.fly-moderation-queue__submit-btn:disabled{opacity:.5;cursor:not-allowed}.fly-moderation-queue__cancel-btn{padding:6px 14px;border:1px solid var(--_border);border-radius:var(--_radius);background:transparent;color:var(--_text-subtle);font:inherit;cursor:pointer}.fly-moderation-queue__cancel-btn:hover{background:var(--_surface-hover)}.fly-moderation-queue__danger-btn{padding:6px 14px;border:none;border-radius:var(--_radius);background:var(--_danger);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__danger-btn:hover{opacity:.9}\n"] }]
|
|
16132
|
-
}], propDecorators: { page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: true }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: false }] }], loadPage: [{ type: i0.Output, args: ["loadPage"] }], resolve: [{ type: i0.Output, args: ["resolve"] }], lockThread: [{ type: i0.Output, args: ["lockThread"] }], openSubject: [{ type: i0.Output, args: ["openSubject"] }], onEscape: [{
|
|
16573
|
+
args: [{ selector: 'fly-moderation-queue', standalone: true, imports: [CommonModule, FormsModule, TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'fly-moderation-queue' }, template: "<div class=\"fly-moderation-queue__body\">\r\n <!-- \u2500\u2500 Header: count + status filter \u2500\u2500 -->\r\n <header class=\"fly-moderation-queue__header\">\r\n <span class=\"fly-moderation-queue__count\">\r\n {{ 'moderation.count' | translate: { n: page().total } }}\r\n </span>\r\n\r\n <div class=\"fly-moderation-queue__status-filter\" role=\"group\" [attr.aria-label]=\"'moderation.status_label' | translate\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-moderation-queue__status-btn\"\r\n [class.fly-moderation-queue__status-btn--active]=\"status() === null\"\r\n [attr.aria-pressed]=\"status() === null\"\r\n [disabled]=\"loading()\"\r\n (click)=\"onStatusPick(null)\">\r\n {{ 'moderation.status_all' | translate }}\r\n </button>\r\n @for (s of statuses; track s) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-moderation-queue__status-btn\"\r\n [class.fly-moderation-queue__status-btn--active]=\"status() === s\"\r\n [attr.aria-pressed]=\"status() === s\"\r\n [disabled]=\"loading()\"\r\n (click)=\"onStatusPick(s)\">\r\n {{ 'moderation.status.' + s | translate }}\r\n </button>\r\n }\r\n </div>\r\n </header>\r\n\r\n <!-- \u2500\u2500 States \u2500\u2500 -->\r\n @if (error()) {\r\n <p class=\"fly-moderation-queue__error\" role=\"alert\">{{ error() }}</p>\r\n } @else if (loading() && page().items.length === 0) {\r\n <p class=\"fly-moderation-queue__status-msg\">{{ 'moderation.loading' | translate }}</p>\r\n } @else if (page().items.length === 0) {\r\n <p class=\"fly-moderation-queue__empty\">{{ 'moderation.empty' | translate }}</p>\r\n } @else {\r\n <!-- \u2500\u2500 Report table \u2500\u2500 -->\r\n <div class=\"fly-moderation-queue__table-wrap\">\r\n <table class=\"fly-moderation-queue__table\">\r\n <caption class=\"fly-moderation-queue__sr-only\">{{ 'moderation.table_caption' | translate }}</caption>\r\n <thead>\r\n <tr>\r\n <th scope=\"col\">{{ 'moderation.col_reason' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_reporter' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_reported' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_excerpt' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_subject' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_status' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_actions' | translate }}</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (r of page().items; track r.id) {\r\n <tr>\r\n <td class=\"fly-moderation-queue__reason-cell\">\r\n <span class=\"fly-moderation-queue__reason\">{{ 'comment.reason.' + r.reason | translate }}</span>\r\n @if (r.detail) {\r\n <span class=\"fly-moderation-queue__detail\" [title]=\"r.detail\">{{ r.detail }}</span>\r\n }\r\n </td>\r\n <td>{{ reporterLabel(r) }}</td>\r\n <td>{{ formatDate(r.createdAt) }}</td>\r\n <td class=\"fly-moderation-queue__excerpt\">{{ r.commentExcerpt || '\u2014' }}</td>\r\n <td>\r\n <button type=\"button\" class=\"fly-moderation-queue__subject-link\" [title]=\"subjectHref(r)\" (click)=\"onOpenSubject(r)\">\r\n {{ subjectDisplay(r) }}\r\n </button>\r\n </td>\r\n <td>\r\n <span class=\"fly-moderation-queue__badge\" [class]=\"'fly-moderation-queue__badge--' + r.status\">\r\n {{ 'moderation.status.' + r.status | translate }}\r\n </span>\r\n @if (r.resolutionNote) {\r\n <div class=\"fly-moderation-queue__resolution-note\">{{ r.resolutionNote }}</div>\r\n }\r\n </td>\r\n <td class=\"fly-moderation-queue__actions\">\r\n @if (isResolvable(r.status)) {\r\n <button type=\"button\" class=\"fly-moderation-queue__action-btn\" [disabled]=\"loading()\" (click)=\"openResolve(r)\">\r\n {{ 'moderation.resolve' | translate }}\r\n </button>\r\n }\r\n @if (confirmingLockId() === r.id) {\r\n <span class=\"fly-moderation-queue__lock-confirm\">\r\n <button type=\"button\" class=\"fly-moderation-queue__cancel-btn\" (click)=\"cancelLock()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-moderation-queue__danger-btn\" (click)=\"confirmLock(r)\">\r\n {{ 'moderation.lock_thread' | translate }}\r\n </button>\r\n </span>\r\n } @else {\r\n <button type=\"button\" class=\"fly-moderation-queue__action-btn\" [disabled]=\"loading()\" (click)=\"requestLock(r)\">\r\n {{ 'moderation.lock_thread' | translate }}\r\n </button>\r\n }\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n\r\n @if (page().hasMore) {\r\n <button type=\"button\" class=\"fly-moderation-queue__load-more\" [disabled]=\"loading()\" (click)=\"loadMore()\">\r\n {{ 'comment.load_more' | translate }}\r\n </button>\r\n }\r\n }\r\n</div>\r\n\r\n<!-- \u2500\u2500 Resolve dialog \u2500\u2500 -->\r\n@if (resolvingReport(); as r) {\r\n <div class=\"fly-moderation-queue__overlay\">\r\n <div class=\"fly-moderation-queue__overlay-backdrop\" role=\"button\" tabindex=\"0\"\r\n [attr.aria-label]=\"'common.action.cancel' | translate\"\r\n (click)=\"closeResolve()\"\r\n (keydown.enter)=\"closeResolve()\"\r\n (keydown.space)=\"onOverlayBackdropSpace($event)\"></div>\r\n <div\r\n class=\"fly-moderation-queue__dialog\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n tabindex=\"-1\"\r\n [attr.aria-label]=\"'moderation.resolve_title' | translate\">\r\n <h3 class=\"fly-moderation-queue__dialog-title\">{{ 'moderation.resolve_title' | translate }}</h3>\r\n\r\n <fieldset class=\"fly-moderation-queue__resolution-group\">\r\n <legend>{{ 'moderation.resolution_label' | translate }}</legend>\r\n <label class=\"fly-moderation-queue__resolution-option\">\r\n <input\r\n type=\"radio\"\r\n [name]=\"resolutionGroupName\"\r\n [checked]=\"resolutionStatus() === 'ActionTaken'\"\r\n (change)=\"onResolutionStatusChange('ActionTaken')\" />\r\n <span>{{ 'moderation.resolution.ActionTaken' | translate }}</span>\r\n </label>\r\n <label class=\"fly-moderation-queue__resolution-option\">\r\n <input\r\n type=\"radio\"\r\n [name]=\"resolutionGroupName\"\r\n [checked]=\"resolutionStatus() === 'Dismissed'\"\r\n (change)=\"onResolutionStatusChange('Dismissed')\" />\r\n <span>{{ 'moderation.resolution.Dismissed' | translate }}</span>\r\n </label>\r\n </fieldset>\r\n\r\n @if (resolutionStatus() === 'ActionTaken') {\r\n <label class=\"fly-moderation-queue__checkbox-row\">\r\n <input type=\"checkbox\" [checked]=\"deleteComment()\" (change)=\"onDeleteCommentToggle($event)\" />\r\n <span>{{ 'moderation.delete_comment_label' | translate }}</span>\r\n </label>\r\n }\r\n\r\n <textarea\r\n class=\"fly-moderation-queue__textarea\"\r\n rows=\"3\"\r\n [attr.aria-label]=\"'moderation.resolution_note_label' | translate\"\r\n [placeholder]=\"'moderation.resolution_note_label' | translate\"\r\n [ngModel]=\"resolutionNote()\"\r\n (ngModelChange)=\"onResolutionNoteChange($event)\"></textarea>\r\n\r\n <div class=\"fly-moderation-queue__dialog-actions\">\r\n <button type=\"button\" class=\"fly-moderation-queue__cancel-btn\" (click)=\"closeResolve()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fly-moderation-queue__submit-btn\"\r\n [disabled]=\"!resolutionStatus()\"\r\n (click)=\"submitResolve(r)\">\r\n {{ 'moderation.resolve_submit' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--surface-card, #fff);--_surface-hover: var(--surface-hover, #f1f5f9);--_border: var(--separator, var(--surface-border, #e2e8f0));--_text: var(--label-primary, var(--text-color, #0f172a));--_text-subtle: var(--label-secondary, var(--text-color-secondary, #64748b));--_text-tertiary: var(--label-tertiary, #94a3b8);--_fill: var(--fill-tertiary, #f3f4f6);--_accent: var(--accent);--_danger: var(--system-red, #ef4444);--_blue: var(--system-blue, #0a84ff);--_orange: var(--system-orange, #ff9f0a);--_green: var(--system-green, #32d74b);--_radius: 8px;--_blue-text: #1d4ed8;--_orange-text: #9a3412;--_green-text: #166534;--_danger-text: #b91c1c;display:block;inline-size:100%;color:var(--_text);font-size:13px}.fly-moderation-queue__body{display:flex;flex-direction:column;gap:16px}.fly-moderation-queue__header{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.fly-moderation-queue__count{font-weight:700;font-size:14px}.fly-moderation-queue__status-filter{display:inline-flex;flex-wrap:wrap;border:1px solid var(--_border);border-radius:var(--_radius);overflow:hidden}.fly-moderation-queue__status-btn{padding:5px 12px;border:none;border-inline-end:1px solid var(--_border);background:transparent;color:var(--_text-subtle);font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__status-btn:last-child{border-inline-end:none}.fly-moderation-queue__status-btn:disabled{opacity:.6;cursor:not-allowed}.fly-moderation-queue__status-btn--active{background:var(--_accent);color:#fff}.fly-moderation-queue__empty,.fly-moderation-queue__status-msg{margin:0;color:var(--_text-subtle);text-align:center;padding:16px 0}.fly-moderation-queue__error{margin:0;padding:8px 12px;border-radius:var(--_radius);background:color-mix(in srgb,var(--_danger) 12%,transparent);color:var(--_danger);font-size:12px}.fly-moderation-queue__sr-only{position:absolute;inline-size:1px;block-size:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.fly-moderation-queue__table-wrap{overflow-x:auto}.fly-moderation-queue__table{inline-size:100%;border-collapse:collapse;font-size:12px}.fly-moderation-queue__table th{text-align:start;padding:6px 10px;color:var(--_text-subtle);font-weight:600;border-block-end:1px solid var(--_border);white-space:nowrap}.fly-moderation-queue__table td{padding:8px 10px;border-block-end:1px solid var(--_border);vertical-align:top}.fly-moderation-queue__reason-cell{display:table-cell}.fly-moderation-queue__reason{display:block;font-weight:600}.fly-moderation-queue__detail{display:block;margin-block-start:2px;color:var(--_text-tertiary);font-size:11px;max-inline-size:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-moderation-queue__excerpt{max-inline-size:260px;color:var(--_text-subtle);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-moderation-queue__subject-link{border:none;background:transparent;padding:0;color:var(--_accent);font:inherit;font-weight:600;cursor:pointer;text-align:start}.fly-moderation-queue__subject-link:hover{text-decoration:underline}.fly-moderation-queue__badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:700;white-space:nowrap}.fly-moderation-queue__badge--Open{background:color-mix(in srgb,var(--_blue) 12%,#fff);color:var(--_blue-text)}.fly-moderation-queue__badge--UnderReview{background:color-mix(in srgb,var(--_orange) 12%,#fff);color:var(--_orange-text)}.fly-moderation-queue__badge--ActionTaken{background:color-mix(in srgb,var(--_danger) 12%,#fff);color:var(--_danger-text)}.fly-moderation-queue__badge--Dismissed{background:color-mix(in srgb,var(--_green) 12%,#fff);color:var(--_green-text)}.fly-moderation-queue__resolution-note{margin-block-start:4px;color:var(--_text-tertiary);font-size:11px;max-inline-size:200px}.fly-moderation-queue__actions{display:flex;flex-direction:column;align-items:flex-start;gap:6px;white-space:nowrap}.fly-moderation-queue__action-btn{border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);padding:4px 10px;color:var(--_text);font:inherit;font-size:11px;font-weight:600;cursor:pointer}.fly-moderation-queue__action-btn:hover:not(:disabled){background:var(--_surface-hover)}.fly-moderation-queue__action-btn:disabled{opacity:.6;cursor:not-allowed}.fly-moderation-queue__lock-confirm{display:inline-flex;align-items:center;gap:6px}.fly-moderation-queue__load-more{align-self:flex-start;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);padding:6px 14px;color:var(--_text);font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__load-more:hover:not(:disabled){background:var(--_surface-hover)}.fly-moderation-queue__load-more:disabled{opacity:.6;cursor:not-allowed}.fly-moderation-queue__overlay{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;z-index:1000}.fly-moderation-queue__overlay-backdrop{position:absolute;inset:0;background:#00000073;border:none;padding:0;margin:0;cursor:pointer}.fly-moderation-queue__dialog{position:relative;z-index:1;inline-size:min(440px,90vw);max-block-size:85vh;overflow-y:auto;background:var(--_surface);border-radius:12px;padding:20px;display:flex;flex-direction:column;gap:14px;box-shadow:0 12px 32px #00000040}.fly-moderation-queue__dialog:focus-visible{outline:2px solid var(--_accent);outline-offset:2px}.fly-moderation-queue__dialog-title{margin:0;font-size:16px;font-weight:700}.fly-moderation-queue__resolution-group{display:flex;flex-direction:column;gap:8px;margin:0;padding:0;border:none}.fly-moderation-queue__resolution-group legend{font-weight:600;padding:0;margin-block-end:4px}.fly-moderation-queue__resolution-option,.fly-moderation-queue__checkbox-row{display:flex;align-items:center;gap:8px;cursor:pointer}.fly-moderation-queue__resolution-option input,.fly-moderation-queue__checkbox-row input{accent-color:var(--_accent);cursor:pointer}.fly-moderation-queue__textarea{inline-size:100%;padding:8px 12px;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);color:var(--_text);font:inherit;line-height:1.5;resize:vertical}.fly-moderation-queue__textarea:focus-visible{outline:2px solid var(--_accent);outline-offset:1px}.fly-moderation-queue__dialog-actions{display:flex;justify-content:flex-end;gap:8px}.fly-moderation-queue__submit-btn{padding:6px 16px;border:none;border-radius:var(--_radius);background:var(--_accent);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__submit-btn:hover:not(:disabled){opacity:.9}.fly-moderation-queue__submit-btn:disabled{opacity:.5;cursor:not-allowed}.fly-moderation-queue__cancel-btn{padding:6px 14px;border:1px solid var(--_border);border-radius:var(--_radius);background:transparent;color:var(--_text-subtle);font:inherit;cursor:pointer}.fly-moderation-queue__cancel-btn:hover{background:var(--_surface-hover)}.fly-moderation-queue__danger-btn{padding:6px 14px;border:none;border-radius:var(--_radius);background:var(--_danger);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__danger-btn:hover{opacity:.9}\n"] }]
|
|
16574
|
+
}], propDecorators: { page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: true }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: false }] }], resolveDisplayName: [{ type: i0.Input, args: [{ isSignal: true, alias: "resolveDisplayName", required: false }] }], loadPage: [{ type: i0.Output, args: ["loadPage"] }], resolve: [{ type: i0.Output, args: ["resolve"] }], lockThread: [{ type: i0.Output, args: ["lockThread"] }], openSubject: [{ type: i0.Output, args: ["openSubject"] }], onEscape: [{
|
|
16133
16575
|
type: HostListener,
|
|
16134
16576
|
args: ['document:keydown.escape']
|
|
16135
16577
|
}] } });
|
|
@@ -19164,11 +19606,11 @@ class FlyAppHomeComponent {
|
|
|
19164
19606
|
return `fly-app-home-sec-${index}`;
|
|
19165
19607
|
}
|
|
19166
19608
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyAppHomeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
19167
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyAppHomeComponent, isStandalone: true, selector: "fly-app-home", inputs: { brandLabelKey: { classPropertyName: "brandLabelKey", publicName: "brandLabelKey", isSignal: true, isRequired: false, transformFunction: null }, titleKey: { classPropertyName: "titleKey", publicName: "titleKey", isSignal: true, isRequired: false, transformFunction: null }, subtitleKey: { classPropertyName: "subtitleKey", publicName: "subtitleKey", isSignal: true, isRequired: false, transformFunction: null }, sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: false, transformFunction: null }, modulesLabelKey: { classPropertyName: "modulesLabelKey", publicName: "modulesLabelKey", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { moduleSelected: "moduleSelected", brandSelected: "brandSelected" }, queries: [{ propertyName: "icons", predicate: FlyModuleIconDirective, isSignal: true }], ngImport: i0, template: "<div class=\"ah\">\n <header class=\"ah__util\">\n <button type=\"button\" class=\"ah__brand\" (click)=\"brandSelected.emit()\">\n <span class=\"ah__brand-mark\"><ng-content select=\"[app-home-brand]\" /></span>\n @if (brandLabelKey(); as key) {\n <span class=\"ah__brand-name\">{{ key | translate }}</span>\n }\n </button>\n <ng-content select=\"[app-home-actions]\" />\n </header>\n\n <main class=\"ah__main\">\n @if (titleKey() || subtitleKey()) {\n <section class=\"ah__hero\">\n @if (titleKey(); as key) {\n <h1 class=\"ah__title\">{{ key | translate }}</h1>\n }\n @if (subtitleKey(); as key) {\n <p class=\"ah__sub\">{{ key | translate }}</p>\n }\n </section>\n }\n\n @for (row of rows(); track row.index) {\n <section\n class=\"ah__sec\"\n [class.ah__sec--secondary]=\"row.index > 0\"\n [attr.aria-label]=\"row.section.titleKey ? null : (modulesLabelKey() | translate)\"\n >\n @if (row.section.titleKey; as titleKey) {\n @if (row.section.collapsible) {\n <button\n type=\"button\"\n class=\"ah__sec-hd\"\n (click)=\"toggle(row.index)\"\n [attr.aria-expanded]=\"row.expanded\"\n [attr.aria-controls]=\"sectionId(row.index)\"\n >\n <!-- Points end-ward when shut, down when open. `--open` wins over the RTL\n flip below because a downward chevron has no direction to mirror. -->\n <svg\n class=\"ah__sec-chev\"\n [class.ah__sec-chev--open]=\"row.expanded\"\n width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"\n >\n <polyline points=\"9 6 15 12 9 18\" />\n </svg>\n <span class=\"ah__sec-title\">{{ titleKey | translate }}</span>\n <span class=\"ah__sec-count\">{{ row.section.modules.length }}</span>\n @if (row.hintKey; as hint) {\n <span class=\"ah__sec-hint\">{{ hint | translate }}</span>\n }\n </button>\n } @else {\n <h2 class=\"ah__sec-title ah__sec-title--static\">{{ titleKey | translate }}</h2>\n }\n }\n\n @if (row.expanded) {\n <div class=\"ah__grid\" [class.ah__grid--compact]=\"row.layout === 'compact'\" [id]=\"sectionId(row.index)\">\n @for (mod of row.section.modules; track mod.key) {\n <button\n type=\"button\"\n class=\"ah__card\"\n [class.ah__card--compact]=\"row.layout === 'compact'\"\n [disabled]=\"mod.disabled\"\n (click)=\"select(mod)\"\n >\n <span class=\"ah__card-ico\">\n @if (iconFor(mod.key); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl\" />\n } @else if (mod.iconClass) {\n <i [class]=\"mod.iconClass\" aria-hidden=\"true\"></i>\n }\n </span>\n\n @if (row.layout === 'compact') {\n <span class=\"ah__card-main\">\n <span class=\"ah__card-title\">{{ mod.labelKey | translate }}</span>\n @if (mod.descKey; as descKey) {\n <span class=\"ah__card-desc\">{{ descKey | translate }}</span>\n }\n </span>\n <svg\n class=\"ah__card-arrow\" width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2.6\" stroke-linecap=\"round\"\n stroke-linejoin=\"round\" aria-hidden=\"true\"\n >\n <polyline points=\"9 6 15 12 9 18\" />\n </svg>\n } @else {\n <span class=\"ah__card-title ah__card-title--feature\">{{ mod.labelKey | translate }}</span>\n @if (mod.descKey; as descKey) {\n <span class=\"ah__card-desc ah__card-desc--feature\">{{ descKey | translate }}</span>\n }\n <span class=\"ah__card-open\">\n {{ 'ui.action.open' | translate }}\n <svg\n class=\"ah__card-arrow\" width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2.6\" stroke-linecap=\"round\"\n stroke-linejoin=\"round\" aria-hidden=\"true\"\n >\n <polyline points=\"9 6 15 12 9 18\" />\n </svg>\n </span>\n }\n </button>\n }\n </div>\n }\n </section>\n }\n </main>\n</div>\n", styles: [":host{display:flex;flex-direction:column;min-height:100%;container-type:inline-size}.ah{display:flex;flex-direction:column;flex:1;min-height:100%}.ah__util{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-4);padding:var(--sp-4) var(--sp-6)}.ah__brand{display:inline-flex;align-items:center;gap:var(--sp-2);padding:0;border:0;background:none;color:var(--ink);font-family:inherit;cursor:pointer}.ah__brand:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.ah__brand-name{font-size:var(--text-md);font-weight:var(--fw-semibold);letter-spacing:-.01em}.ah__main{flex:1;inline-size:100%;max-inline-size:1120px;margin-inline:auto;padding:56px 40px;display:flex;flex-direction:column;justify-content:center;gap:44px}.ah__hero{max-inline-size:640px}.ah__title{margin:0 0 var(--sp-3);font-size:44px;font-weight:var(--fw-semibold);letter-spacing:-.03em;text-wrap:balance;background:linear-gradient(180deg,var(--ink),var(--ink-2));-webkit-background-clip:text;background-clip:text;color:transparent}.ah__sub{margin:0;max-inline-size:560px;font-size:var(--text-lg);line-height:1.55;color:var(--ink-3);text-wrap:pretty}.ah__sec{display:flex;flex-direction:column;gap:var(--sp-4)}.ah__sec--secondary{padding-block-start:28px;border-block-start:1px solid var(--line)}.ah__sec-hd{display:flex;align-items:center;gap:var(--sp-3);inline-size:100%;padding:0;background:none;border:0;cursor:pointer;font-family:inherit;color:var(--ink);text-align:start}.ah__sec-hd:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.ah__sec-chev{flex-shrink:0;color:var(--ink-3);transition:transform var(--t-overlay)}.ah__sec-title{font-size:var(--text-md);font-weight:var(--fw-semibold);letter-spacing:-.01em}.ah__sec-title--static{margin:0;color:var(--ink-3);font-size:var(--text-xs);letter-spacing:var(--tracking-wide);text-transform:uppercase}.ah__sec-count{display:inline-grid;place-items:center;min-inline-size:22px;block-size:20px;padding-inline:6px;border-radius:999px;background:var(--bg-3);border:1px solid var(--line);font-size:var(--text-2xs);font-weight:var(--fw-medium);color:var(--ink-3)}.ah__sec-hint{margin-inline-start:auto;font-size:var(--text-xs);color:var(--ink-3)}.ah__grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--sp-3)}.ah__card{display:flex;flex-direction:column;align-items:stretch;position:relative;overflow:hidden;text-align:start;padding:22px 22px 18px;background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-lg);color:inherit;font-family:inherit;cursor:pointer;transition:transform var(--t-overlay),box-shadow var(--t-overlay),border-color var(--t-overlay)}.ah__card:before{content:\"\";position:absolute;inset:0;pointer-events:none;border-radius:inherit;padding:1px;background:var(--grad);-webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;mask-composite:exclude;opacity:0;transition:opacity var(--t-overlay)}.ah__card:hover:not(:disabled){transform:translateY(-2px);border-color:transparent;box-shadow:var(--shadow-filter)}.ah__card:hover:not(:disabled):before{opacity:1}.ah__card:focus-visible{outline:0;border-color:transparent;box-shadow:0 0 0 3px var(--accent-soft),var(--shadow-filter)}.ah__card:focus-visible:before{opacity:1}.ah__card:disabled{cursor:default;opacity:.5}.ah__card-ico{display:grid;place-items:center;inline-size:40px;block-size:40px;margin-block-end:18px;border-radius:11px;background:var(--bg-3);border:0;color:var(--ink);font-size:var(--icon-lg);transition:background var(--t-overlay),color var(--t-overlay)}.ah__card:hover:not(:disabled) .ah__card-ico,.ah__card:focus-visible .ah__card-ico{background:var(--grad);color:var(--ink-inverse)}.ah__card-title{font-size:var(--text-sm);font-weight:var(--fw-semibold);letter-spacing:-.01em;color:var(--ink)}.ah__card-title--feature{margin-block-end:4px;font-size:18px;letter-spacing:-.015em}.ah__card-desc{font-size:var(--text-xs);line-height:1.45;color:var(--ink-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ah__card-desc--feature{margin-block-end:18px;font-size:var(--text-sm);line-height:1.5;text-wrap:pretty;overflow:visible;white-space:normal}.ah__card-open{display:inline-flex;align-items:center;gap:4px;margin-block-start:auto;font-size:var(--text-2xs);color:var(--ink-3);transition:color var(--t-overlay),gap var(--t-overlay)}.ah__card:hover:not(:disabled) .ah__card-open,.ah__card:focus-visible .ah__card-open{color:var(--accent);gap:8px}.ah__card-arrow{flex-shrink:0;transition:color var(--t-overlay),transform var(--t-overlay)}.ah__card--compact{flex-direction:row;align-items:center;gap:var(--sp-3);padding:var(--sp-3) var(--sp-4)}.ah__card--compact .ah__card-ico{inline-size:34px;block-size:34px;margin-block-end:0;flex-shrink:0;border-radius:9px;font-size:var(--icon-md)}.ah__card-main{display:flex;flex-direction:column;gap:2px;flex:1;min-inline-size:0}.ah__card--compact .ah__card-arrow{color:var(--ink-4)}.ah__card--compact:hover:not(:disabled) .ah__card-arrow{color:var(--accent);transform:translate(2px)}:host-context([dir=rtl]) .ah__card-arrow{transform:scaleX(-1)}:host-context([dir=rtl]) .ah__sec-chev:not(.ah__sec-chev--open){transform:scaleX(-1)}:host-context([dir=rtl]) .ah__card--compact:hover:not(:disabled) .ah__card-arrow{transform:scaleX(-1) translate(2px)}.ah__sec-chev--open{transform:rotate(90deg)}@container (width <= 900px){.ah__grid{grid-template-columns:repeat(2,minmax(0,1fr))}.ah__main{padding:40px 24px;gap:36px}.ah__title{font-size:36px}.ah__util{padding:var(--sp-3) var(--sp-5)}}@media(width<=900px){.ah__grid{grid-template-columns:repeat(2,minmax(0,1fr))}.ah__main{padding:40px 24px;gap:36px}.ah__title{font-size:36px}.ah__util{padding:var(--sp-3) var(--sp-5)}}@container (width <= 560px){.ah__grid{grid-template-columns:1fr}.ah__title{font-size:30px}.ah__sub{font-size:var(--text-sm)}.ah__sec-hint{display:none}}@media(width<=560px){.ah__grid{grid-template-columns:1fr}.ah__title{font-size:30px}.ah__sub{font-size:var(--text-sm)}.ah__sec-hint{display:none}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
19609
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyAppHomeComponent, isStandalone: true, selector: "fly-app-home", inputs: { brandLabelKey: { classPropertyName: "brandLabelKey", publicName: "brandLabelKey", isSignal: true, isRequired: false, transformFunction: null }, titleKey: { classPropertyName: "titleKey", publicName: "titleKey", isSignal: true, isRequired: false, transformFunction: null }, subtitleKey: { classPropertyName: "subtitleKey", publicName: "subtitleKey", isSignal: true, isRequired: false, transformFunction: null }, sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: false, transformFunction: null }, modulesLabelKey: { classPropertyName: "modulesLabelKey", publicName: "modulesLabelKey", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { moduleSelected: "moduleSelected", brandSelected: "brandSelected" }, queries: [{ propertyName: "icons", predicate: FlyModuleIconDirective, isSignal: true }], ngImport: i0, template: "<div class=\"ah\">\r\n <header class=\"ah__util\">\r\n <button type=\"button\" class=\"ah__brand\" (click)=\"brandSelected.emit()\">\r\n <span class=\"ah__brand-mark\"><ng-content select=\"[app-home-brand]\" /></span>\r\n @if (brandLabelKey(); as key) {\r\n <span class=\"ah__brand-name\">{{ key | translate }}</span>\r\n }\r\n </button>\r\n <ng-content select=\"[app-home-actions]\" />\r\n </header>\r\n\r\n <main class=\"ah__main\">\r\n @if (titleKey() || subtitleKey()) {\r\n <section class=\"ah__hero\">\r\n @if (titleKey(); as key) {\r\n <h1 class=\"ah__title\">{{ key | translate }}</h1>\r\n }\r\n @if (subtitleKey(); as key) {\r\n <p class=\"ah__sub\">{{ key | translate }}</p>\r\n }\r\n </section>\r\n }\r\n\r\n @for (row of rows(); track row.index) {\r\n <section\r\n class=\"ah__sec\"\r\n [class.ah__sec--secondary]=\"row.index > 0\"\r\n [attr.aria-label]=\"row.section.titleKey ? null : (modulesLabelKey() | translate)\"\r\n >\r\n @if (row.section.titleKey; as titleKey) {\r\n @if (row.section.collapsible) {\r\n <button\r\n type=\"button\"\r\n class=\"ah__sec-hd\"\r\n (click)=\"toggle(row.index)\"\r\n [attr.aria-expanded]=\"row.expanded\"\r\n [attr.aria-controls]=\"sectionId(row.index)\"\r\n >\r\n <!-- Points end-ward when shut, down when open. `--open` wins over the RTL\r\n flip below because a downward chevron has no direction to mirror. -->\r\n <svg\r\n class=\"ah__sec-chev\"\r\n [class.ah__sec-chev--open]=\"row.expanded\"\r\n width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\r\n stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"\r\n >\r\n <polyline points=\"9 6 15 12 9 18\" />\r\n </svg>\r\n <span class=\"ah__sec-title\">{{ titleKey | translate }}</span>\r\n <span class=\"ah__sec-count\">{{ row.section.modules.length }}</span>\r\n @if (row.hintKey; as hint) {\r\n <span class=\"ah__sec-hint\">{{ hint | translate }}</span>\r\n }\r\n </button>\r\n } @else {\r\n <h2 class=\"ah__sec-title ah__sec-title--static\">{{ titleKey | translate }}</h2>\r\n }\r\n }\r\n\r\n @if (row.expanded) {\r\n <div class=\"ah__grid\" [class.ah__grid--compact]=\"row.layout === 'compact'\" [id]=\"sectionId(row.index)\">\r\n @for (mod of row.section.modules; track mod.key) {\r\n <button\r\n type=\"button\"\r\n class=\"ah__card\"\r\n [class.ah__card--compact]=\"row.layout === 'compact'\"\r\n [disabled]=\"mod.disabled\"\r\n (click)=\"select(mod)\"\r\n >\r\n <span class=\"ah__card-ico\">\r\n @if (iconFor(mod.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (mod.iconClass) {\r\n <i [class]=\"mod.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n\r\n @if (row.layout === 'compact') {\r\n <span class=\"ah__card-main\">\r\n <span class=\"ah__card-title\">{{ mod.labelKey | translate }}</span>\r\n @if (mod.descKey; as descKey) {\r\n <span class=\"ah__card-desc\">{{ descKey | translate }}</span>\r\n }\r\n </span>\r\n <svg\r\n class=\"ah__card-arrow\" width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2.6\" stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\" aria-hidden=\"true\"\r\n >\r\n <polyline points=\"9 6 15 12 9 18\" />\r\n </svg>\r\n } @else {\r\n <span class=\"ah__card-title ah__card-title--feature\">{{ mod.labelKey | translate }}</span>\r\n @if (mod.descKey; as descKey) {\r\n <span class=\"ah__card-desc ah__card-desc--feature\">{{ descKey | translate }}</span>\r\n }\r\n <span class=\"ah__card-open\">\r\n {{ 'ui.action.open' | translate }}\r\n <svg\r\n class=\"ah__card-arrow\" width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2.6\" stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\" aria-hidden=\"true\"\r\n >\r\n <polyline points=\"9 6 15 12 9 18\" />\r\n </svg>\r\n </span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n }\r\n </section>\r\n }\r\n </main>\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;min-height:100%;container-type:inline-size}.ah{display:flex;flex-direction:column;flex:1;min-height:100%}.ah__util{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-4);padding:var(--sp-4) var(--sp-6)}.ah__brand{display:inline-flex;align-items:center;gap:var(--sp-2);padding:0;border:0;background:none;color:var(--ink);font-family:inherit;cursor:pointer}.ah__brand:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.ah__brand-name{font-size:var(--text-md);font-weight:var(--fw-semibold);letter-spacing:-.01em}.ah__main{flex:1;inline-size:100%;max-inline-size:1120px;margin-inline:auto;padding:56px 40px;display:flex;flex-direction:column;justify-content:center;gap:44px}.ah__hero{max-inline-size:640px}.ah__title{margin:0 0 var(--sp-3);font-size:44px;font-weight:var(--fw-semibold);letter-spacing:-.03em;text-wrap:balance;background:linear-gradient(180deg,var(--ink),var(--ink-2));-webkit-background-clip:text;background-clip:text;color:transparent}.ah__sub{margin:0;max-inline-size:560px;font-size:var(--text-lg);line-height:1.55;color:var(--ink-3);text-wrap:pretty}.ah__sec{display:flex;flex-direction:column;gap:var(--sp-4)}.ah__sec--secondary{padding-block-start:28px;border-block-start:1px solid var(--line)}.ah__sec-hd{display:flex;align-items:center;gap:var(--sp-3);inline-size:100%;padding:0;background:none;border:0;cursor:pointer;font-family:inherit;color:var(--ink);text-align:start}.ah__sec-hd:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.ah__sec-chev{flex-shrink:0;color:var(--ink-3);transition:transform var(--t-overlay)}.ah__sec-title{font-size:var(--text-md);font-weight:var(--fw-semibold);letter-spacing:-.01em}.ah__sec-title--static{margin:0;color:var(--ink-3);font-size:var(--text-xs);letter-spacing:var(--tracking-wide);text-transform:uppercase}.ah__sec-count{display:inline-grid;place-items:center;min-inline-size:22px;block-size:20px;padding-inline:6px;border-radius:999px;background:var(--bg-3);border:1px solid var(--line);font-size:var(--text-2xs);font-weight:var(--fw-medium);color:var(--ink-3)}.ah__sec-hint{margin-inline-start:auto;font-size:var(--text-xs);color:var(--ink-3)}.ah__grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--sp-3)}.ah__card{display:flex;flex-direction:column;align-items:stretch;position:relative;overflow:hidden;text-align:start;padding:22px 22px 18px;background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-lg);color:inherit;font-family:inherit;cursor:pointer;transition:transform var(--t-overlay),box-shadow var(--t-overlay),border-color var(--t-overlay)}.ah__card:before{content:\"\";position:absolute;inset:0;pointer-events:none;border-radius:inherit;padding:1px;background:var(--grad);-webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;mask-composite:exclude;opacity:0;transition:opacity var(--t-overlay)}.ah__card:hover:not(:disabled){transform:translateY(-2px);border-color:transparent;box-shadow:var(--shadow-filter)}.ah__card:hover:not(:disabled):before{opacity:1}.ah__card:focus-visible{outline:0;border-color:transparent;box-shadow:0 0 0 3px var(--accent-soft),var(--shadow-filter)}.ah__card:focus-visible:before{opacity:1}.ah__card:disabled{cursor:default;opacity:.5}.ah__card-ico{display:grid;place-items:center;inline-size:40px;block-size:40px;margin-block-end:18px;border-radius:11px;background:var(--bg-3);border:0;color:var(--ink);font-size:var(--icon-lg);transition:background var(--t-overlay),color var(--t-overlay)}.ah__card:hover:not(:disabled) .ah__card-ico,.ah__card:focus-visible .ah__card-ico{background:var(--grad);color:var(--ink-inverse)}.ah__card-title{font-size:var(--text-sm);font-weight:var(--fw-semibold);letter-spacing:-.01em;color:var(--ink)}.ah__card-title--feature{margin-block-end:4px;font-size:18px;letter-spacing:-.015em}.ah__card-desc{font-size:var(--text-xs);line-height:1.45;color:var(--ink-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ah__card-desc--feature{margin-block-end:18px;font-size:var(--text-sm);line-height:1.5;text-wrap:pretty;overflow:visible;white-space:normal}.ah__card-open{display:inline-flex;align-items:center;gap:4px;margin-block-start:auto;font-size:var(--text-2xs);color:var(--ink-3);transition:color var(--t-overlay),gap var(--t-overlay)}.ah__card:hover:not(:disabled) .ah__card-open,.ah__card:focus-visible .ah__card-open{color:var(--accent);gap:8px}.ah__card-arrow{flex-shrink:0;transition:color var(--t-overlay),transform var(--t-overlay)}.ah__card--compact{flex-direction:row;align-items:center;gap:var(--sp-3);padding:var(--sp-3) var(--sp-4)}.ah__card--compact .ah__card-ico{inline-size:34px;block-size:34px;margin-block-end:0;flex-shrink:0;border-radius:9px;font-size:var(--icon-md)}.ah__card-main{display:flex;flex-direction:column;gap:2px;flex:1;min-inline-size:0}.ah__card--compact .ah__card-arrow{color:var(--ink-4)}.ah__card--compact:hover:not(:disabled) .ah__card-arrow{color:var(--accent);transform:translate(2px)}:host-context([dir=rtl]) .ah__card-arrow{transform:scaleX(-1)}:host-context([dir=rtl]) .ah__sec-chev:not(.ah__sec-chev--open){transform:scaleX(-1)}:host-context([dir=rtl]) .ah__card--compact:hover:not(:disabled) .ah__card-arrow{transform:scaleX(-1) translate(2px)}.ah__sec-chev--open{transform:rotate(90deg)}@container (width <= 900px){.ah__grid{grid-template-columns:repeat(2,minmax(0,1fr))}.ah__main{padding:40px 24px;gap:36px}.ah__title{font-size:36px}.ah__util{padding:var(--sp-3) var(--sp-5)}}@media(width<=900px){.ah__grid{grid-template-columns:repeat(2,minmax(0,1fr))}.ah__main{padding:40px 24px;gap:36px}.ah__title{font-size:36px}.ah__util{padding:var(--sp-3) var(--sp-5)}}@container (width <= 560px){.ah__grid{grid-template-columns:1fr}.ah__title{font-size:30px}.ah__sub{font-size:var(--text-sm)}.ah__sec-hint{display:none}}@media(width<=560px){.ah__grid{grid-template-columns:1fr}.ah__title{font-size:30px}.ah__sub{font-size:var(--text-sm)}.ah__sec-hint{display:none}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
19168
19610
|
}
|
|
19169
19611
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyAppHomeComponent, decorators: [{
|
|
19170
19612
|
type: Component,
|
|
19171
|
-
args: [{ selector: 'fly-app-home', standalone: true, imports: [NgTemplateOutlet, TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"ah\">\n <header class=\"ah__util\">\n <button type=\"button\" class=\"ah__brand\" (click)=\"brandSelected.emit()\">\n <span class=\"ah__brand-mark\"><ng-content select=\"[app-home-brand]\" /></span>\n @if (brandLabelKey(); as key) {\n <span class=\"ah__brand-name\">{{ key | translate }}</span>\n }\n </button>\n <ng-content select=\"[app-home-actions]\" />\n </header>\n\n <main class=\"ah__main\">\n @if (titleKey() || subtitleKey()) {\n <section class=\"ah__hero\">\n @if (titleKey(); as key) {\n <h1 class=\"ah__title\">{{ key | translate }}</h1>\n }\n @if (subtitleKey(); as key) {\n <p class=\"ah__sub\">{{ key | translate }}</p>\n }\n </section>\n }\n\n @for (row of rows(); track row.index) {\n <section\n class=\"ah__sec\"\n [class.ah__sec--secondary]=\"row.index > 0\"\n [attr.aria-label]=\"row.section.titleKey ? null : (modulesLabelKey() | translate)\"\n >\n @if (row.section.titleKey; as titleKey) {\n @if (row.section.collapsible) {\n <button\n type=\"button\"\n class=\"ah__sec-hd\"\n (click)=\"toggle(row.index)\"\n [attr.aria-expanded]=\"row.expanded\"\n [attr.aria-controls]=\"sectionId(row.index)\"\n >\n <!-- Points end-ward when shut, down when open. `--open` wins over the RTL\n flip below because a downward chevron has no direction to mirror. -->\n <svg\n class=\"ah__sec-chev\"\n [class.ah__sec-chev--open]=\"row.expanded\"\n width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"\n >\n <polyline points=\"9 6 15 12 9 18\" />\n </svg>\n <span class=\"ah__sec-title\">{{ titleKey | translate }}</span>\n <span class=\"ah__sec-count\">{{ row.section.modules.length }}</span>\n @if (row.hintKey; as hint) {\n <span class=\"ah__sec-hint\">{{ hint | translate }}</span>\n }\n </button>\n } @else {\n <h2 class=\"ah__sec-title ah__sec-title--static\">{{ titleKey | translate }}</h2>\n }\n }\n\n @if (row.expanded) {\n <div class=\"ah__grid\" [class.ah__grid--compact]=\"row.layout === 'compact'\" [id]=\"sectionId(row.index)\">\n @for (mod of row.section.modules; track mod.key) {\n <button\n type=\"button\"\n class=\"ah__card\"\n [class.ah__card--compact]=\"row.layout === 'compact'\"\n [disabled]=\"mod.disabled\"\n (click)=\"select(mod)\"\n >\n <span class=\"ah__card-ico\">\n @if (iconFor(mod.key); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl\" />\n } @else if (mod.iconClass) {\n <i [class]=\"mod.iconClass\" aria-hidden=\"true\"></i>\n }\n </span>\n\n @if (row.layout === 'compact') {\n <span class=\"ah__card-main\">\n <span class=\"ah__card-title\">{{ mod.labelKey | translate }}</span>\n @if (mod.descKey; as descKey) {\n <span class=\"ah__card-desc\">{{ descKey | translate }}</span>\n }\n </span>\n <svg\n class=\"ah__card-arrow\" width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2.6\" stroke-linecap=\"round\"\n stroke-linejoin=\"round\" aria-hidden=\"true\"\n >\n <polyline points=\"9 6 15 12 9 18\" />\n </svg>\n } @else {\n <span class=\"ah__card-title ah__card-title--feature\">{{ mod.labelKey | translate }}</span>\n @if (mod.descKey; as descKey) {\n <span class=\"ah__card-desc ah__card-desc--feature\">{{ descKey | translate }}</span>\n }\n <span class=\"ah__card-open\">\n {{ 'ui.action.open' | translate }}\n <svg\n class=\"ah__card-arrow\" width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2.6\" stroke-linecap=\"round\"\n stroke-linejoin=\"round\" aria-hidden=\"true\"\n >\n <polyline points=\"9 6 15 12 9 18\" />\n </svg>\n </span>\n }\n </button>\n }\n </div>\n }\n </section>\n }\n </main>\n</div>\n", styles: [":host{display:flex;flex-direction:column;min-height:100%;container-type:inline-size}.ah{display:flex;flex-direction:column;flex:1;min-height:100%}.ah__util{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-4);padding:var(--sp-4) var(--sp-6)}.ah__brand{display:inline-flex;align-items:center;gap:var(--sp-2);padding:0;border:0;background:none;color:var(--ink);font-family:inherit;cursor:pointer}.ah__brand:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.ah__brand-name{font-size:var(--text-md);font-weight:var(--fw-semibold);letter-spacing:-.01em}.ah__main{flex:1;inline-size:100%;max-inline-size:1120px;margin-inline:auto;padding:56px 40px;display:flex;flex-direction:column;justify-content:center;gap:44px}.ah__hero{max-inline-size:640px}.ah__title{margin:0 0 var(--sp-3);font-size:44px;font-weight:var(--fw-semibold);letter-spacing:-.03em;text-wrap:balance;background:linear-gradient(180deg,var(--ink),var(--ink-2));-webkit-background-clip:text;background-clip:text;color:transparent}.ah__sub{margin:0;max-inline-size:560px;font-size:var(--text-lg);line-height:1.55;color:var(--ink-3);text-wrap:pretty}.ah__sec{display:flex;flex-direction:column;gap:var(--sp-4)}.ah__sec--secondary{padding-block-start:28px;border-block-start:1px solid var(--line)}.ah__sec-hd{display:flex;align-items:center;gap:var(--sp-3);inline-size:100%;padding:0;background:none;border:0;cursor:pointer;font-family:inherit;color:var(--ink);text-align:start}.ah__sec-hd:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.ah__sec-chev{flex-shrink:0;color:var(--ink-3);transition:transform var(--t-overlay)}.ah__sec-title{font-size:var(--text-md);font-weight:var(--fw-semibold);letter-spacing:-.01em}.ah__sec-title--static{margin:0;color:var(--ink-3);font-size:var(--text-xs);letter-spacing:var(--tracking-wide);text-transform:uppercase}.ah__sec-count{display:inline-grid;place-items:center;min-inline-size:22px;block-size:20px;padding-inline:6px;border-radius:999px;background:var(--bg-3);border:1px solid var(--line);font-size:var(--text-2xs);font-weight:var(--fw-medium);color:var(--ink-3)}.ah__sec-hint{margin-inline-start:auto;font-size:var(--text-xs);color:var(--ink-3)}.ah__grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--sp-3)}.ah__card{display:flex;flex-direction:column;align-items:stretch;position:relative;overflow:hidden;text-align:start;padding:22px 22px 18px;background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-lg);color:inherit;font-family:inherit;cursor:pointer;transition:transform var(--t-overlay),box-shadow var(--t-overlay),border-color var(--t-overlay)}.ah__card:before{content:\"\";position:absolute;inset:0;pointer-events:none;border-radius:inherit;padding:1px;background:var(--grad);-webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;mask-composite:exclude;opacity:0;transition:opacity var(--t-overlay)}.ah__card:hover:not(:disabled){transform:translateY(-2px);border-color:transparent;box-shadow:var(--shadow-filter)}.ah__card:hover:not(:disabled):before{opacity:1}.ah__card:focus-visible{outline:0;border-color:transparent;box-shadow:0 0 0 3px var(--accent-soft),var(--shadow-filter)}.ah__card:focus-visible:before{opacity:1}.ah__card:disabled{cursor:default;opacity:.5}.ah__card-ico{display:grid;place-items:center;inline-size:40px;block-size:40px;margin-block-end:18px;border-radius:11px;background:var(--bg-3);border:0;color:var(--ink);font-size:var(--icon-lg);transition:background var(--t-overlay),color var(--t-overlay)}.ah__card:hover:not(:disabled) .ah__card-ico,.ah__card:focus-visible .ah__card-ico{background:var(--grad);color:var(--ink-inverse)}.ah__card-title{font-size:var(--text-sm);font-weight:var(--fw-semibold);letter-spacing:-.01em;color:var(--ink)}.ah__card-title--feature{margin-block-end:4px;font-size:18px;letter-spacing:-.015em}.ah__card-desc{font-size:var(--text-xs);line-height:1.45;color:var(--ink-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ah__card-desc--feature{margin-block-end:18px;font-size:var(--text-sm);line-height:1.5;text-wrap:pretty;overflow:visible;white-space:normal}.ah__card-open{display:inline-flex;align-items:center;gap:4px;margin-block-start:auto;font-size:var(--text-2xs);color:var(--ink-3);transition:color var(--t-overlay),gap var(--t-overlay)}.ah__card:hover:not(:disabled) .ah__card-open,.ah__card:focus-visible .ah__card-open{color:var(--accent);gap:8px}.ah__card-arrow{flex-shrink:0;transition:color var(--t-overlay),transform var(--t-overlay)}.ah__card--compact{flex-direction:row;align-items:center;gap:var(--sp-3);padding:var(--sp-3) var(--sp-4)}.ah__card--compact .ah__card-ico{inline-size:34px;block-size:34px;margin-block-end:0;flex-shrink:0;border-radius:9px;font-size:var(--icon-md)}.ah__card-main{display:flex;flex-direction:column;gap:2px;flex:1;min-inline-size:0}.ah__card--compact .ah__card-arrow{color:var(--ink-4)}.ah__card--compact:hover:not(:disabled) .ah__card-arrow{color:var(--accent);transform:translate(2px)}:host-context([dir=rtl]) .ah__card-arrow{transform:scaleX(-1)}:host-context([dir=rtl]) .ah__sec-chev:not(.ah__sec-chev--open){transform:scaleX(-1)}:host-context([dir=rtl]) .ah__card--compact:hover:not(:disabled) .ah__card-arrow{transform:scaleX(-1) translate(2px)}.ah__sec-chev--open{transform:rotate(90deg)}@container (width <= 900px){.ah__grid{grid-template-columns:repeat(2,minmax(0,1fr))}.ah__main{padding:40px 24px;gap:36px}.ah__title{font-size:36px}.ah__util{padding:var(--sp-3) var(--sp-5)}}@media(width<=900px){.ah__grid{grid-template-columns:repeat(2,minmax(0,1fr))}.ah__main{padding:40px 24px;gap:36px}.ah__title{font-size:36px}.ah__util{padding:var(--sp-3) var(--sp-5)}}@container (width <= 560px){.ah__grid{grid-template-columns:1fr}.ah__title{font-size:30px}.ah__sub{font-size:var(--text-sm)}.ah__sec-hint{display:none}}@media(width<=560px){.ah__grid{grid-template-columns:1fr}.ah__title{font-size:30px}.ah__sub{font-size:var(--text-sm)}.ah__sec-hint{display:none}}\n"] }]
|
|
19613
|
+
args: [{ selector: 'fly-app-home', standalone: true, imports: [NgTemplateOutlet, TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"ah\">\r\n <header class=\"ah__util\">\r\n <button type=\"button\" class=\"ah__brand\" (click)=\"brandSelected.emit()\">\r\n <span class=\"ah__brand-mark\"><ng-content select=\"[app-home-brand]\" /></span>\r\n @if (brandLabelKey(); as key) {\r\n <span class=\"ah__brand-name\">{{ key | translate }}</span>\r\n }\r\n </button>\r\n <ng-content select=\"[app-home-actions]\" />\r\n </header>\r\n\r\n <main class=\"ah__main\">\r\n @if (titleKey() || subtitleKey()) {\r\n <section class=\"ah__hero\">\r\n @if (titleKey(); as key) {\r\n <h1 class=\"ah__title\">{{ key | translate }}</h1>\r\n }\r\n @if (subtitleKey(); as key) {\r\n <p class=\"ah__sub\">{{ key | translate }}</p>\r\n }\r\n </section>\r\n }\r\n\r\n @for (row of rows(); track row.index) {\r\n <section\r\n class=\"ah__sec\"\r\n [class.ah__sec--secondary]=\"row.index > 0\"\r\n [attr.aria-label]=\"row.section.titleKey ? null : (modulesLabelKey() | translate)\"\r\n >\r\n @if (row.section.titleKey; as titleKey) {\r\n @if (row.section.collapsible) {\r\n <button\r\n type=\"button\"\r\n class=\"ah__sec-hd\"\r\n (click)=\"toggle(row.index)\"\r\n [attr.aria-expanded]=\"row.expanded\"\r\n [attr.aria-controls]=\"sectionId(row.index)\"\r\n >\r\n <!-- Points end-ward when shut, down when open. `--open` wins over the RTL\r\n flip below because a downward chevron has no direction to mirror. -->\r\n <svg\r\n class=\"ah__sec-chev\"\r\n [class.ah__sec-chev--open]=\"row.expanded\"\r\n width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\r\n stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"\r\n >\r\n <polyline points=\"9 6 15 12 9 18\" />\r\n </svg>\r\n <span class=\"ah__sec-title\">{{ titleKey | translate }}</span>\r\n <span class=\"ah__sec-count\">{{ row.section.modules.length }}</span>\r\n @if (row.hintKey; as hint) {\r\n <span class=\"ah__sec-hint\">{{ hint | translate }}</span>\r\n }\r\n </button>\r\n } @else {\r\n <h2 class=\"ah__sec-title ah__sec-title--static\">{{ titleKey | translate }}</h2>\r\n }\r\n }\r\n\r\n @if (row.expanded) {\r\n <div class=\"ah__grid\" [class.ah__grid--compact]=\"row.layout === 'compact'\" [id]=\"sectionId(row.index)\">\r\n @for (mod of row.section.modules; track mod.key) {\r\n <button\r\n type=\"button\"\r\n class=\"ah__card\"\r\n [class.ah__card--compact]=\"row.layout === 'compact'\"\r\n [disabled]=\"mod.disabled\"\r\n (click)=\"select(mod)\"\r\n >\r\n <span class=\"ah__card-ico\">\r\n @if (iconFor(mod.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (mod.iconClass) {\r\n <i [class]=\"mod.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n\r\n @if (row.layout === 'compact') {\r\n <span class=\"ah__card-main\">\r\n <span class=\"ah__card-title\">{{ mod.labelKey | translate }}</span>\r\n @if (mod.descKey; as descKey) {\r\n <span class=\"ah__card-desc\">{{ descKey | translate }}</span>\r\n }\r\n </span>\r\n <svg\r\n class=\"ah__card-arrow\" width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2.6\" stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\" aria-hidden=\"true\"\r\n >\r\n <polyline points=\"9 6 15 12 9 18\" />\r\n </svg>\r\n } @else {\r\n <span class=\"ah__card-title ah__card-title--feature\">{{ mod.labelKey | translate }}</span>\r\n @if (mod.descKey; as descKey) {\r\n <span class=\"ah__card-desc ah__card-desc--feature\">{{ descKey | translate }}</span>\r\n }\r\n <span class=\"ah__card-open\">\r\n {{ 'ui.action.open' | translate }}\r\n <svg\r\n class=\"ah__card-arrow\" width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2.6\" stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\" aria-hidden=\"true\"\r\n >\r\n <polyline points=\"9 6 15 12 9 18\" />\r\n </svg>\r\n </span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n }\r\n </section>\r\n }\r\n </main>\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;min-height:100%;container-type:inline-size}.ah{display:flex;flex-direction:column;flex:1;min-height:100%}.ah__util{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-4);padding:var(--sp-4) var(--sp-6)}.ah__brand{display:inline-flex;align-items:center;gap:var(--sp-2);padding:0;border:0;background:none;color:var(--ink);font-family:inherit;cursor:pointer}.ah__brand:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.ah__brand-name{font-size:var(--text-md);font-weight:var(--fw-semibold);letter-spacing:-.01em}.ah__main{flex:1;inline-size:100%;max-inline-size:1120px;margin-inline:auto;padding:56px 40px;display:flex;flex-direction:column;justify-content:center;gap:44px}.ah__hero{max-inline-size:640px}.ah__title{margin:0 0 var(--sp-3);font-size:44px;font-weight:var(--fw-semibold);letter-spacing:-.03em;text-wrap:balance;background:linear-gradient(180deg,var(--ink),var(--ink-2));-webkit-background-clip:text;background-clip:text;color:transparent}.ah__sub{margin:0;max-inline-size:560px;font-size:var(--text-lg);line-height:1.55;color:var(--ink-3);text-wrap:pretty}.ah__sec{display:flex;flex-direction:column;gap:var(--sp-4)}.ah__sec--secondary{padding-block-start:28px;border-block-start:1px solid var(--line)}.ah__sec-hd{display:flex;align-items:center;gap:var(--sp-3);inline-size:100%;padding:0;background:none;border:0;cursor:pointer;font-family:inherit;color:var(--ink);text-align:start}.ah__sec-hd:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.ah__sec-chev{flex-shrink:0;color:var(--ink-3);transition:transform var(--t-overlay)}.ah__sec-title{font-size:var(--text-md);font-weight:var(--fw-semibold);letter-spacing:-.01em}.ah__sec-title--static{margin:0;color:var(--ink-3);font-size:var(--text-xs);letter-spacing:var(--tracking-wide);text-transform:uppercase}.ah__sec-count{display:inline-grid;place-items:center;min-inline-size:22px;block-size:20px;padding-inline:6px;border-radius:999px;background:var(--bg-3);border:1px solid var(--line);font-size:var(--text-2xs);font-weight:var(--fw-medium);color:var(--ink-3)}.ah__sec-hint{margin-inline-start:auto;font-size:var(--text-xs);color:var(--ink-3)}.ah__grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--sp-3)}.ah__card{display:flex;flex-direction:column;align-items:stretch;position:relative;overflow:hidden;text-align:start;padding:22px 22px 18px;background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-lg);color:inherit;font-family:inherit;cursor:pointer;transition:transform var(--t-overlay),box-shadow var(--t-overlay),border-color var(--t-overlay)}.ah__card:before{content:\"\";position:absolute;inset:0;pointer-events:none;border-radius:inherit;padding:1px;background:var(--grad);-webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;mask-composite:exclude;opacity:0;transition:opacity var(--t-overlay)}.ah__card:hover:not(:disabled){transform:translateY(-2px);border-color:transparent;box-shadow:var(--shadow-filter)}.ah__card:hover:not(:disabled):before{opacity:1}.ah__card:focus-visible{outline:0;border-color:transparent;box-shadow:0 0 0 3px var(--accent-soft),var(--shadow-filter)}.ah__card:focus-visible:before{opacity:1}.ah__card:disabled{cursor:default;opacity:.5}.ah__card-ico{display:grid;place-items:center;inline-size:40px;block-size:40px;margin-block-end:18px;border-radius:11px;background:var(--bg-3);border:0;color:var(--ink);font-size:var(--icon-lg);transition:background var(--t-overlay),color var(--t-overlay)}.ah__card:hover:not(:disabled) .ah__card-ico,.ah__card:focus-visible .ah__card-ico{background:var(--grad);color:var(--ink-inverse)}.ah__card-title{font-size:var(--text-sm);font-weight:var(--fw-semibold);letter-spacing:-.01em;color:var(--ink)}.ah__card-title--feature{margin-block-end:4px;font-size:18px;letter-spacing:-.015em}.ah__card-desc{font-size:var(--text-xs);line-height:1.45;color:var(--ink-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ah__card-desc--feature{margin-block-end:18px;font-size:var(--text-sm);line-height:1.5;text-wrap:pretty;overflow:visible;white-space:normal}.ah__card-open{display:inline-flex;align-items:center;gap:4px;margin-block-start:auto;font-size:var(--text-2xs);color:var(--ink-3);transition:color var(--t-overlay),gap var(--t-overlay)}.ah__card:hover:not(:disabled) .ah__card-open,.ah__card:focus-visible .ah__card-open{color:var(--accent);gap:8px}.ah__card-arrow{flex-shrink:0;transition:color var(--t-overlay),transform var(--t-overlay)}.ah__card--compact{flex-direction:row;align-items:center;gap:var(--sp-3);padding:var(--sp-3) var(--sp-4)}.ah__card--compact .ah__card-ico{inline-size:34px;block-size:34px;margin-block-end:0;flex-shrink:0;border-radius:9px;font-size:var(--icon-md)}.ah__card-main{display:flex;flex-direction:column;gap:2px;flex:1;min-inline-size:0}.ah__card--compact .ah__card-arrow{color:var(--ink-4)}.ah__card--compact:hover:not(:disabled) .ah__card-arrow{color:var(--accent);transform:translate(2px)}:host-context([dir=rtl]) .ah__card-arrow{transform:scaleX(-1)}:host-context([dir=rtl]) .ah__sec-chev:not(.ah__sec-chev--open){transform:scaleX(-1)}:host-context([dir=rtl]) .ah__card--compact:hover:not(:disabled) .ah__card-arrow{transform:scaleX(-1) translate(2px)}.ah__sec-chev--open{transform:rotate(90deg)}@container (width <= 900px){.ah__grid{grid-template-columns:repeat(2,minmax(0,1fr))}.ah__main{padding:40px 24px;gap:36px}.ah__title{font-size:36px}.ah__util{padding:var(--sp-3) var(--sp-5)}}@media(width<=900px){.ah__grid{grid-template-columns:repeat(2,minmax(0,1fr))}.ah__main{padding:40px 24px;gap:36px}.ah__title{font-size:36px}.ah__util{padding:var(--sp-3) var(--sp-5)}}@container (width <= 560px){.ah__grid{grid-template-columns:1fr}.ah__title{font-size:30px}.ah__sub{font-size:var(--text-sm)}.ah__sec-hint{display:none}}@media(width<=560px){.ah__grid{grid-template-columns:1fr}.ah__title{font-size:30px}.ah__sub{font-size:var(--text-sm)}.ah__sec-hint{display:none}}\n"] }]
|
|
19172
19614
|
}], propDecorators: { brandLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "brandLabelKey", required: false }] }], titleKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleKey", required: false }] }], subtitleKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitleKey", required: false }] }], sections: [{ type: i0.Input, args: [{ isSignal: true, alias: "sections", required: false }] }], modulesLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "modulesLabelKey", required: false }] }], moduleSelected: [{ type: i0.Output, args: ["moduleSelected"] }], brandSelected: [{ type: i0.Output, args: ["brandSelected"] }], icons: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => FlyModuleIconDirective), { isSignal: true }] }] } });
|
|
19173
19615
|
|
|
19174
19616
|
/**
|
|
@@ -20134,7 +20576,7 @@ class FlyFormSectionComponent {
|
|
|
20134
20576
|
}
|
|
20135
20577
|
</div>
|
|
20136
20578
|
<div class="sec__c"><ng-content /></div>
|
|
20137
|
-
`, isInline: true, styles: [":host{display:grid;grid-template-columns:140px 1fr;gap:24px;padding:22px 0;border-bottom:1px solid var(--line-3)}:host(:last-of-type){border-bottom:0}.sec__l{font-size:var(--text-xs);font-weight:var(--fw-medium);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-3);padding-top:8px;position:sticky;top:8px;align-self:start}.sec__c{min-width:0}@media(width<=720px){:host{grid-template-columns:1fr;gap:8px}.sec__l{padding-top:0;position:static}}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
20579
|
+
`, isInline: true, styles: [":host{display:grid;grid-template-columns:140px 1fr;gap:24px;padding:22px 0;border-bottom:1px solid var(--line-3)}:host(:last-of-type){border-bottom:0}.sec__l{font-size:var(--text-xs);font-weight:var(--fw-medium);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-3);padding-top:8px;position:sticky;top:8px;align-self:start}.sec__c{min-width:0}@container (width <= 720px){:host{grid-template-columns:1fr;gap:8px}.sec__l{padding-top:0;position:static}}@media(width<=720px){:host{grid-template-columns:1fr;gap:8px}.sec__l{padding-top:0;position:static}}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
20138
20580
|
}
|
|
20139
20581
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyFormSectionComponent, decorators: [{
|
|
20140
20582
|
type: Component,
|
|
@@ -20145,7 +20587,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
|
|
|
20145
20587
|
}
|
|
20146
20588
|
</div>
|
|
20147
20589
|
<div class="sec__c"><ng-content /></div>
|
|
20148
|
-
`, styles: [":host{display:grid;grid-template-columns:140px 1fr;gap:24px;padding:22px 0;border-bottom:1px solid var(--line-3)}:host(:last-of-type){border-bottom:0}.sec__l{font-size:var(--text-xs);font-weight:var(--fw-medium);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-3);padding-top:8px;position:sticky;top:8px;align-self:start}.sec__c{min-width:0}@media(width<=720px){:host{grid-template-columns:1fr;gap:8px}.sec__l{padding-top:0;position:static}}\n"] }]
|
|
20590
|
+
`, styles: [":host{display:grid;grid-template-columns:140px 1fr;gap:24px;padding:22px 0;border-bottom:1px solid var(--line-3)}:host(:last-of-type){border-bottom:0}.sec__l{font-size:var(--text-xs);font-weight:var(--fw-medium);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-3);padding-top:8px;position:sticky;top:8px;align-self:start}.sec__c{min-width:0}@container (width <= 720px){:host{grid-template-columns:1fr;gap:8px}.sec__l{padding-top:0;position:static}}@media(width<=720px){:host{grid-template-columns:1fr;gap:8px}.sec__l{padding-top:0;position:static}}\n"] }]
|
|
20149
20591
|
}], propDecorators: { labelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelKey", required: false }] }] } });
|
|
20150
20592
|
/**
|
|
20151
20593
|
* Responsive field grid (port of the legacy `.dr__grid/--2/--3`): 1–3 equal
|
|
@@ -21399,5 +21841,5 @@ const AUDIENCE_ERROR_CODES = {
|
|
|
21399
21841
|
* Generated bundle index. Do not edit.
|
|
21400
21842
|
*/
|
|
21401
21843
|
|
|
21402
|
-
export { AGENT_DRAG_MIME, AGENT_PAYLOAD_VERSION, APP_LOOKUP, AUDIENCE_ERROR_CODES, AUDIENCE_LIMITS, AUDIENCE_PRESETS, AUDIENCE_TERM_KINDS, AgentActionBus, AgentActionUnsupportedDispatchError, AgentCommandRegistry, AgentDropRegistry, AgentFlightAnimator, AgentLookupRegistry, AgentPayloadOversizeError, AudienceBuilderComponent, AuthService, BilingualFieldComponent, CRON_MODES, CRON_WEEKDAYS, ContextMenuComponent, DEFAULT_AGENT_PAYLOAD_LIMITS, DEFAULT_FLY_THEME_MODE, DS_BASELINE_LOCALES, DashboardKpiComponent, DialogResult, EntityLookupComponent, FLYOS_LAUNCH_EVENT, FLYOS_LAUNCH_REQUEST_EVENT, FLYOS_REMOTE_ROUTE_EVENT, FLY_ACCENT_PROPERTY, FLY_ADMIN_ROLES, FLY_CHUNK_RELOAD_FLAG, FLY_COMPACT_THRESHOLD, FLY_COUNTRIES, FLY_EMOJI_BY_ID, FLY_EMOJI_CATEGORIES, FLY_EMOJI_CATEGORY_BY_ID, FLY_EMOJI_DEFAULT_CATEGORIES, FLY_EMOJI_PACK, FLY_EMOJI_PACK_MISSING_MESSAGE, FLY_EMOJI_PREVIEW_COUNT, FLY_EMPTY_VALUE, FLY_LOCALE_CATALOG, FLY_NF_CACHE_HEAL_FLAG, FLY_RELOAD_DEBOUNCE_MS, FLY_REMOTE_BASE_PATH, FLY_REMOTE_CONTEXT_EVENT, FLY_REMOTE_CONTEXT_STORE_KEY, FLY_REMOTE_ROUTES, FLY_SEARCH_DEBOUNCE_MS, FLY_SKIN_TONES, FLY_STANDALONE_AUTH_CONFIG, FLY_THEME_MODE_IDS, FLY_WINDOW_HELP_HINT_EVENT, FLY_WINDOW_PAGE_TITLE_EVENT, FlyAchievementProgressComponent, FlyActionMenuComponent, FlyActionStackComponent, FlyAgentDraggableDirective, FlyAnimatedEmojiComponent, FlyAppHomeComponent, FlyAppTopbarComponent, FlyAuthLiveRefresh, FlyBadgeWallComponent, FlyBlockUiComponent, FlyBreadcrumbComponent, FlyButtonComponent, FlyBytesPipe, FlyCaptchaComponent, FlyCardActionsComponent, FlyCardBodyComponent, FlyCardComponent, FlyCardFooterComponent, FlyCardGridComponent, FlyCardMediaComponent, FlyCardMetaComponent, FlyCardStatusComponent, FlyCardTitleComponent, FlyCellDirective, FlyCheckboxComponent, FlyChipComponent, FlyChunkReloadErrorHandler, FlyClickOutsideDirective, FlyCollapsibleComponent, FlyCommentThreadComponent, FlyCompactNumberPipe, FlyConfirmDialogComponent, FlyControlDirective, FlyCronBuilderComponent, FlyDataTableComponent, FlyDatePipe, FlyDateTimePipe, FlyDebouncer, FlyDecimalNumberPipe, FlyDetailCardComponent, FlyDetailShellComponent, FlyDrawerComponent, FlyDurationPipe, FlyDynamicFormComponent, FlyEmojiPickerComponent, FlyFieldComponent, FlyFileUploadComponent, FlyFilterPanelComponent, FlyFormBannerComponent, FlyFormFooterComponent, FlyFormGridComponent, FlyFormSectionComponent, FlyFullNumberPipe, FlyGanttComponent, FlyHubClient, FlyIconButtonComponent, FlyIfAdminDirective, FlyIfRoleDirective, FlyImageUploadComponent, FlyLeaderboardComponent, FlyMetaListComponent, FlyModalComponent, FlyModerationQueueComponent, FlyModuleIconDirective, FlyPaginationComponent, FlyPeoplePickerComponent, FlyPointsTierComponent, FlyProgressComponent, FlyRelativeTimePipe, FlyRemoteContextService, FlyRemoteRouter, FlyRemoteRouterOutletComponent, FlySearchInputComponent, FlySectionHeaderComponent, FlySecureSrcDirective, FlySegmentedComponent, FlySelectComponent, FlySkeletonComponent, FlySliderComponent, FlySparklineComponent, FlySpinnerComponent, FlyStandaloneAuthCallbackComponent, FlyStandaloneAuthService, FlyStateMessageComponent, FlyDynamicFormComponent as FlySurveyFormComponent, FlyTabComponent, FlyTabsComponent, FlyTagsInputComponent, FlyThemeService, FlyTimePipe, FlyToastService, FlyToggleComponent, FlyTooltipDirective, FlyTreeNavComponent, FlyTypeaheadComponent, FlyWindowHelpService, FlyWindowTitleService, FlyosPendingLaunchesGlobalKey, FlyosShellHandlesLaunchRequestsGlobalKey, FlyosShellOwnsHistoryGlobalKey, GANTT_ZOOMS, I18nService, LAUNCH_CONTEXT, MessageBoxButtons, MessageBoxComponent, MessageBoxIcon, MessageBoxService, MockAuthService, OverlayStack, PRESENCE_COLORS, PageHeaderComponent, RTL_LOCALE_SET, SHARE_ORG_CHART_SYSTEM_KEY_APPS, SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT, SHARE_PANEL_DEFAULT_FILE_LEVELS, STATE_DEFAULT_ICONS, STATE_DEFAULT_MESSAGE_KEYS, STEP_UP_CODE, STEP_UP_REAUTH_HANDLER, STEP_UP_RETURN_KEY, SUPPORTED_AGENT_PAYLOAD_VERSIONS, SharePanelComponent, SourceAppResolver, StandaloneWindowManagerService, StatusBadgeComponent, StepUpService, ToastHostComponent, TranslatePipe, WINDOW_DATA, WINDOW_HELP_HINT, WindowManagerService, applySkinTone, applySuggestion, ariaSort, buildCron, canConfirm, canGoNext, canGoPrev, captureFocus, clampPage, clampSliderValue, connectRemoteLaunch, enterActivatesNatively, filterGroup, filterSuggestions, findLocaleByDialect, findLocaleByPrefix, firstEnabledIndex, firstModuleIndex, flattenModules, flyAdminGuard, flyCaptchaBase64Utf8, flyCaptchaBuildToken, flyCaptchaSha256Hex, flyCompactNumber, flyDebounced, flyDecimalNumber, flyDownloadBlob, flyDuration, flyEmojiFor, flyEmojiIsTonable, flyEmojiPackUrl, flyExportFileName, flyFirstEnabledIndex, flyFormatBytes, flyFormatDate, flyFormatDateTime, flyFormatTime, flyFullNumber, flyNextEnabledIndex, flyNextSegmentIndex, flyRelativeTime, flyResolveActiveTab, flyRoleGuard, flySignedCompact, flySkinToneLabelKey, flyStandaloneAuthGuard, flyStandaloneAuthInterceptor, flyToDateOnly, hasAnyRoleIn, healNativeFederationCacheOnce, initialExpanded, isAccentColor, isChunkLoadError, isFlyAuthStorePopulated, isNativeFederationCacheError, isRtlLocale, isRtlLocaleEntry, isValidCron, isValidSingleField, loadRemoteStyles, matchFlyRoutePattern, nextEnabledIndex, nextModuleIndex, nextSegmentIndex, nextSort, normalizeFlyTheme, normalizeRoles, overlayStack, parseCron, parseStepUpChallenge, parseTags, presenceColorFor, printConsoleSecurityWarning, provideFlyChunkReloadRecovery, provideFlyEmojiPack, provideFlyStandaloneAuth, reloadOnceForChunkError, requestAppLaunch, resolveActiveModule, resolveActiveSection, resolveActiveTab, resolveCellValue, resolveStateIcon, resolveStateMessageKey, restoreFocus, sameTags, sectionHintKey, sectionLayout, sliderTrackGradient, sparklinePath, sparklinePoints, stepUpInterceptor, stepUpReturnKey, trimAgentPayload, trimAgentString, unloadRemoteStyles, utf8ByteLength, validateAgentPayload, warnIfEmbeddedSessionMissing };
|
|
21844
|
+
export { AGENT_DRAG_MIME, AGENT_PAYLOAD_VERSION, APP_LOOKUP, AUDIENCE_ERROR_CODES, AUDIENCE_LIMITS, AUDIENCE_PRESETS, AUDIENCE_TERM_KINDS, AgentActionBus, AgentActionUnsupportedDispatchError, AgentCommandRegistry, AgentDropRegistry, AgentFlightAnimator, AgentLookupRegistry, AgentPayloadOversizeError, AudienceBuilderComponent, AuthService, BilingualFieldComponent, CRON_MODES, CRON_WEEKDAYS, ContextMenuComponent, DEFAULT_AGENT_PAYLOAD_LIMITS, DEFAULT_FLY_THEME_MODE, DS_BASELINE_LOCALES, DashboardKpiComponent, DialogResult, EntityLookupComponent, FLYOS_LAUNCH_EVENT, FLYOS_LAUNCH_REQUEST_EVENT, FLYOS_REMOTE_ROUTE_EVENT, FLY_ACCENT_PROPERTY, FLY_ADMIN_ROLES, FLY_CHUNK_RELOAD_FLAG, FLY_COMPACT_THRESHOLD, FLY_COUNTRIES, FLY_EMOJI_BY_ID, FLY_EMOJI_CATEGORIES, FLY_EMOJI_CATEGORY_BY_ID, FLY_EMOJI_DEFAULT_CATEGORIES, FLY_EMOJI_PACK, FLY_EMOJI_PACK_MISSING_MESSAGE, FLY_EMOJI_PREVIEW_COUNT, FLY_EMPTY_VALUE, FLY_LOCALE_CATALOG, FLY_NF_CACHE_HEAL_FLAG, FLY_RELOAD_DEBOUNCE_MS, FLY_RELOAD_REARM_MS, FLY_REMOTE_BASE_PATH, FLY_REMOTE_CONTEXT_EVENT, FLY_REMOTE_CONTEXT_STORE_KEY, FLY_REMOTE_ROUTES, FLY_SEARCH_DEBOUNCE_MS, FLY_SKIN_TONES, FLY_STANDALONE_AUTH_CONFIG, FLY_THEME_MODE_IDS, FLY_WINDOW_HELP_HINT_EVENT, FLY_WINDOW_PAGE_TITLE_EVENT, FlyAchievementProgressComponent, FlyActionMenuComponent, FlyActionStackComponent, FlyAgentDraggableDirective, FlyAnimatedEmojiComponent, FlyAppHomeComponent, FlyAppTopbarComponent, FlyAuthLiveRefresh, FlyBadgeWallComponent, FlyBlockUiComponent, FlyBreadcrumbComponent, FlyButtonComponent, FlyBytesPipe, FlyCaptchaComponent, FlyCardActionsComponent, FlyCardBodyComponent, FlyCardComponent, FlyCardFooterComponent, FlyCardGridComponent, FlyCardMediaComponent, FlyCardMetaComponent, FlyCardStatusComponent, FlyCardTitleComponent, FlyCellDirective, FlyCheckboxComponent, FlyChipComponent, FlyChunkReloadErrorHandler, FlyClickOutsideDirective, FlyCollapsibleComponent, FlyCommentThreadComponent, FlyCompactNumberPipe, FlyConfirmDialogComponent, FlyControlDirective, FlyCronBuilderComponent, FlyDataTableComponent, FlyDatePipe, FlyDateTimePipe, FlyDebouncer, FlyDecimalNumberPipe, FlyDetailCardComponent, FlyDetailShellComponent, FlyDrawerComponent, FlyDurationPipe, FlyDynamicFormComponent, FlyEmojiPickerComponent, FlyFieldComponent, FlyFileUploadComponent, FlyFilterPanelComponent, FlyFormBannerComponent, FlyFormFooterComponent, FlyFormGridComponent, FlyFormSectionComponent, FlyFullNumberPipe, FlyGanttComponent, FlyHubClient, FlyIconButtonComponent, FlyIfAdminDirective, FlyIfRoleDirective, FlyImageUploadComponent, FlyLeaderboardComponent, FlyMetaListComponent, FlyModalComponent, FlyModerationQueueComponent, FlyModuleIconDirective, FlyPaginationComponent, FlyPeoplePickerComponent, FlyPointsTierComponent, FlyProgressComponent, FlyRelativeTimePipe, FlyRemoteContextService, FlyRemoteRouter, FlyRemoteRouterOutletComponent, FlySearchInputComponent, FlySectionHeaderComponent, FlySecureSrcDirective, FlySegmentedComponent, FlySelectComponent, FlySkeletonComponent, FlySliderComponent, FlySparklineComponent, FlySpinnerComponent, FlyStandaloneAuthCallbackComponent, FlyStandaloneAuthService, FlyStateMessageComponent, FlyDynamicFormComponent as FlySurveyFormComponent, FlyTabComponent, FlyTabsComponent, FlyTagsInputComponent, FlyThemeService, FlyTimePipe, FlyToastService, FlyToggleComponent, FlyTooltipDirective, FlyTreeNavComponent, FlyTypeaheadComponent, FlyUserDirectoryService, FlyWindowHelpService, FlyWindowTitleService, FlyosPendingLaunchesGlobalKey, FlyosShellHandlesLaunchRequestsGlobalKey, FlyosShellOwnsHistoryGlobalKey, GANTT_ZOOMS, I18nService, LAUNCH_CONTEXT, MessageBoxButtons, MessageBoxComponent, MessageBoxIcon, MessageBoxService, MockAuthService, OverlayStack, PRESENCE_COLORS, PageHeaderComponent, RTL_LOCALE_SET, SHARE_ORG_CHART_SYSTEM_KEY_APPS, SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT, SHARE_PANEL_DEFAULT_FILE_LEVELS, STATE_DEFAULT_ICONS, STATE_DEFAULT_MESSAGE_KEYS, STEP_UP_CODE, STEP_UP_REAUTH_HANDLER, STEP_UP_RETURN_KEY, SUPPORTED_AGENT_PAYLOAD_VERSIONS, SharePanelComponent, SourceAppResolver, StandaloneWindowManagerService, StatusBadgeComponent, StepUpService, ToastHostComponent, TranslatePipe, WINDOW_DATA, WINDOW_HELP_HINT, WindowManagerService, applySkinTone, applySuggestion, ariaSort, buildCron, canConfirm, canGoNext, canGoPrev, captureFocus, clampPage, clampSliderValue, connectRemoteLaunch, enterActivatesNatively, filterGroup, filterSuggestions, findLocaleByDialect, findLocaleByPrefix, firstEnabledIndex, firstModuleIndex, flattenModules, flyAdminGuard, flyCaptchaBase64Utf8, flyCaptchaBuildToken, flyCaptchaSha256Hex, flyCompactNumber, flyDebounced, flyDecimalNumber, flyDownloadBlob, flyDuration, flyEmojiFor, flyEmojiIsTonable, flyEmojiPackUrl, flyExportFileName, flyFirstEnabledIndex, flyFormatBytes, flyFormatDate, flyFormatDateTime, flyFormatTime, flyFullNumber, flyNextEnabledIndex, flyNextSegmentIndex, flyRelativeTime, flyResolveActiveTab, flyRoleGuard, flySignedCompact, flySkinToneLabelKey, flyStandaloneAuthGuard, flyStandaloneAuthInterceptor, flyToDateOnly, hasAnyRoleIn, healNativeFederationCacheOnce, initialExpanded, isAccentColor, isChunkLoadError, isFlyAuthStorePopulated, isNativeFederationCacheError, isRtlLocale, isRtlLocaleEntry, isValidCron, isValidSingleField, loadRemoteStyles, matchFlyRoutePattern, nextEnabledIndex, nextModuleIndex, nextSegmentIndex, nextSort, normalizeFlyTheme, normalizeRoles, overlayStack, parseCron, parseStepUpChallenge, parseTags, presenceColorFor, printConsoleSecurityWarning, provideFlyChunkReloadRecovery, provideFlyEmojiPack, provideFlyStandaloneAuth, reloadOnceForChunkError, requestAppLaunch, resolveActiveModule, resolveActiveSection, resolveActiveTab, resolveCellValue, resolveStateIcon, resolveStateMessageKey, restoreFocus, sameTags, sectionHintKey, sectionLayout, sliderTrackGradient, sparklinePath, sparklinePoints, stepUpInterceptor, stepUpReturnKey, trimAgentPayload, trimAgentString, unloadRemoteStyles, utf8ByteLength, validateAgentPayload, warnIfEmbeddedSessionMissing };
|
|
21403
21845
|
//# sourceMappingURL=flyos-design-system.mjs.map
|