Skip to content

Commit c72327b

Browse files
authored
feat: Dim Quote Headers and Add Author Metadata (#98)
* feat: Dim quote headers and add author metadata * chore: Remove QuoteCollection and improve metadata handling - Remove QuoteCollection in favor of IndexedQuoteModel[] - Hide quote metadata unless both fields are present * chore: use Temporal and enhance username in quote footer
1 parent b0c9ba7 commit c72327b

1 file changed

Lines changed: 67 additions & 20 deletions

File tree

src/ps/commands/quotes.tsx

Lines changed: 67 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { escapeHTML, formatText, toRoomID } from 'ps-client/tools';
2+
import { Temporal } from '@js-temporal/polyfill';
23

34
import { PSQuoteRoomPrefs } from '@/cache';
45
import { isGlobalBot, prefix } from '@/config/ps';
@@ -20,7 +21,6 @@ import type { PSMessage } from '@/types/ps';
2021
import type { CSSProperties, ReactElement, ReactNode } from 'react';
2122

2223
type IndexedQuoteModel = [index: number, quote: QuoteModel];
23-
type QuoteCollection = [index: number, quote: string][];
2424

2525
const PAGE_SIZE = 50;
2626
const MAX_QUOTE_LENGTH = (MAX_CHAT_HTML_LENGTH / PAGE_SIZE) * 2; // Leniency margin of 2x
@@ -157,16 +157,20 @@ function FormatQuote({
157157
quote,
158158
psUsernameTag = true,
159159
header,
160+
addedBy,
161+
dateAdded,
160162
}: {
161163
quote: string;
162164
psUsernameTag?: boolean;
165+
addedBy?: string;
166+
dateAdded?: string;
163167
header?: ReactNode;
164168
children?: ReactElement[];
165169
}): ReactElement {
166170
const quoteLines = quote.split('\n');
167171
return (
168172
<>
169-
{header}
173+
<div style={{ opacity: 0.78 }}>{header}</div>
170174
<div style={{ marginLeft: 12 }}>
171175
{quoteLines.length > 5 ? (
172176
<details className="readmore">
@@ -188,6 +192,13 @@ function FormatQuote({
188192
quoteLines.map(line => <FormatQuoteLine line={line} psUsernameTag={psUsernameTag} />)
189193
)}
190194
</div>
195+
{addedBy && dateAdded && (
196+
<>
197+
<span style={{ fontSize: 10, opacity: 0.6, marginTop: 6 }}>Added by&nbsp;</span>
198+
<UsernamePS name={addedBy} style={{ fontSize: 10 }} clickable />
199+
<span style={{ fontSize: 10, opacity: 0.6, marginTop: 6 }}>&nbsp;on {dateAdded}</span>
200+
</>
201+
)}
191202
</>
192203
);
193204
}
@@ -213,7 +224,7 @@ function MultiQuotes({
213224
title: baseTitle,
214225
showAll = false,
215226
}: {
216-
list: QuoteCollection;
227+
list: IndexedQuoteModel[];
217228
pageNum: number | null;
218229
total: number;
219230
command: string | null;
@@ -227,7 +238,16 @@ function MultiQuotes({
227238
plural: 'quotes',
228239
})}${total > list.length ? ` of ${total} total` : ''})`;
229240
const title = baseTitle ? `${baseTitle} ${suffix}` : pageNum ? `Page ${pageNum} of ${pageCount} ${suffix}` : `All Quotes`;
230-
const quotes = list.map(([header, quote]) => <FormatQuote quote={quote} header={`#${header}`} />).space(<hr />, !useDropdown);
241+
const quotes = list
242+
.map(([index, quote]) => {
243+
const dateAdded = Temporal.Instant.fromEpochMilliseconds(quote.at.getTime())
244+
.toZonedDateTimeISO('UTC')
245+
.toPlainDate()
246+
.toLocaleString('en-GB');
247+
248+
return <FormatQuote quote={quote.quote} header={`#${index}`} addedBy={quote.addedBy} dateAdded={dateAdded} />;
249+
})
250+
.space(<hr />, !useDropdown);
231251

232252
const content = useDropdown ? (
233253
<>
@@ -237,7 +257,7 @@ function MultiQuotes({
237257
<h3 style={{ display: 'inline-block' }}>{title}</h3>
238258
</summary>
239259
<hr />
240-
{quotes}
260+
<>{quotes}</>
241261
</details>
242262
<hr />
243263
</>
@@ -306,10 +326,15 @@ export const command: PSCommand = {
306326
);
307327
if (!matchingQuotes.length) return broadcast($T('COMMANDS.QUOTES.NO_QUOTES_FOUND_MATCHING', { search: arg }));
308328
const [index, randQuote] = matchingQuotes.random()!;
329+
const dateAdded = Temporal.Instant.fromEpochMilliseconds(randQuote.at.getTime())
330+
.toZonedDateTimeISO('UTC')
331+
.toPlainDate()
332+
.toLocaleString('en-GB');
333+
309334
broadcastHTML(
310335
<>
311336
<hr />
312-
<FormatQuote quote={randQuote.quote} header={`#${+index + 1}`} />
337+
<FormatQuote quote={randQuote.quote} header={`#${+index + 1}`} addedBy={randQuote.addedBy} dateAdded={dateAdded} />
313338
<hr />
314339
</>,
315340
{ name: `viewquote-${message.parent.status.userid}` }
@@ -326,17 +351,20 @@ export const command: PSCommand = {
326351
'wrapping the username in ``[]`` (eg: ``[14:20:21] • #PartMan hugs Hydro`` would be formatted ' +
327352
'as ``[14:20:21] • #[PartMan] hugs Hydro``).',
328353
syntax: 'CMD [new quote]',
329-
async run({ message, arg, broadcastHTML }) {
354+
async run({ message, arg, broadcastHTML, $T }) {
330355
const parsedQuote = parseQuote(arg);
331-
const rendered = jsxToHTML(<FormatQuote quote={parsedQuote} />);
356+
const addedBy = message.author.name;
357+
const at = Temporal.Now.plainDateISO('UTC').toLocaleString('en-GB');
358+
359+
const rendered = jsxToHTML(<FormatQuote quote={parsedQuote} addedBy={addedBy} dateAdded={at} />);
332360

333361
if (rendered.length > MAX_QUOTE_LENGTH) throw new ChatError('Quote is too long.' as ToTranslate);
334-
await addQuote(parsedQuote, message.target.id, message.author.name);
362+
await addQuote(parsedQuote, message.target.id, addedBy);
335363
const { length } = await getAllQuotes(message.target.id);
336364
broadcastHTML(
337365
<>
338366
<hr />
339-
<FormatQuote quote={parsedQuote} header={`Quote #${length} added.`} />
367+
<FormatQuote quote={parsedQuote} header={`Quote #${length} added.`} addedBy={addedBy} dateAdded={at} />
340368
<hr />
341369
</>,
342370
{ name: `viewquote-${message.parent.status.userid}` }
@@ -349,7 +377,7 @@ export const command: PSCommand = {
349377
perms: (message, check) => (message.type === 'pm' ? true : check('driver')),
350378
help: 'Previews the given quote. Syntax is the same as add.',
351379
syntax: 'CMD [new quote]',
352-
async run({ message, arg, broadcastHTML }) {
380+
async run({ message, arg, broadcastHTML, $T }) {
353381
const parsedQuote = parseQuote(arg);
354382
const { length } = await getAllQuotes(message.target.id);
355383
broadcastHTML(
@@ -372,10 +400,10 @@ export const command: PSCommand = {
372400
const room: string = await getRoom(givenRoom, message, $T);
373401
const quotes = await getAllQuotes(room);
374402

375-
const foundQuotes: QuoteCollection = searchQuotes(
403+
const foundQuotes: IndexedQuoteModel[] = searchQuotes(
376404
quotes.map<IndexedQuoteModel>((quote, index) => [index + 1, quote]),
377405
arg
378-
).map(([index, quote]) => [index, quote.quote]);
406+
);
379407

380408
if (!foundQuotes.length) return broadcast($T('COMMANDS.QUOTES.NO_QUOTES_FOUND'));
381409

@@ -402,10 +430,15 @@ export const command: PSCommand = {
402430
const quotes = await getAllQuotes(room);
403431
if (!quotes.length) return broadcast($T('COMMANDS.QUOTES.NO_QUOTES_FOUND'));
404432
const lastQuote = quotes[quotes.length - 1];
433+
const dateAdded = Temporal.Instant.fromEpochMilliseconds(lastQuote.at.getTime())
434+
.toZonedDateTimeISO('UTC')
435+
.toPlainDate()
436+
.toLocaleString('en-GB');
437+
405438
broadcastHTML(
406439
<>
407440
<hr />
408-
<FormatQuote quote={lastQuote.quote} header={`#${quotes.length}`} />
441+
<FormatQuote quote={lastQuote.quote} header={`#${quotes.length}`} addedBy={lastQuote.addedBy} dateAdded={dateAdded} />
409442
<hr />
410443
</>,
411444
{ name: `viewquote-${message.parent.status.userid}` }
@@ -426,9 +459,9 @@ export const command: PSCommand = {
426459
const pageNum = arg ? parseInt(arg) || 1 : 1;
427460
const startIndex = (pageNum - 1) * PAGE_SIZE;
428461
const endIndex = startIndex + PAGE_SIZE;
429-
const pagedQuotes: QuoteCollection = quotes
462+
const pagedQuotes: IndexedQuoteModel[] = quotes
430463
.slice(startIndex, endIndex)
431-
.map((quote, index) => [startIndex + index + 1, quote.quote]);
464+
.map((quote, index) => [startIndex + index + 1, quote]);
432465

433466
if (!pagedQuotes.length) throw new ChatError('Invalid page number.' as ToTranslate);
434467

@@ -457,9 +490,9 @@ export const command: PSCommand = {
457490
const pageNum = arg ? parseInt(arg) || 1 : 1;
458491
const startIndex = (pageNum - 1) * PAGE_SIZE;
459492
const endIndex = startIndex + PAGE_SIZE;
460-
const pageQuotes: QuoteCollection = quotes
493+
const pageQuotes: IndexedQuoteModel[] = quotes
461494
.slice(startIndex, endIndex)
462-
.map((quote, index) => [startIndex + index + 1, quote.quote]);
495+
.map((quote, index) => [startIndex + index + 1, quote]);
463496

464497
if (!pageQuotes.length) throw new ChatError('Invalid page number.' as ToTranslate);
465498

@@ -527,11 +560,20 @@ export const command: PSCommand = {
527560
toDelete = matching[0];
528561
indexToDelete = quotes.indexOf(toDelete);
529562
}
563+
const dateAdded = Temporal.Instant.fromEpochMilliseconds(quotes[indexToDelete].at.getTime())
564+
.toZonedDateTimeISO('UTC')
565+
.toPlainDate()
566+
.toLocaleString('en-GB');
530567

531568
broadcastHTML(
532569
<>
533570
<hr />
534-
<FormatQuote quote={quotes[indexToDelete].quote} header={`Deleting #${indexToDelete + 1}:`} />
571+
<FormatQuote
572+
quote={quotes[indexToDelete].quote}
573+
header={`Deleting #${indexToDelete + 1}:`}
574+
addedBy={quotes[indexToDelete].addedBy}
575+
dateAdded={dateAdded}
576+
/>
535577
<hr />
536578
</>
537579
);
@@ -580,10 +622,15 @@ export const command: PSCommand = {
580622
throw new ChatError('Invalid quote index.' as ToTranslate);
581623
}
582624
const quote = quotes[index - 1];
625+
const dateAdded = Temporal.Instant.fromEpochMilliseconds(quote.at.getTime())
626+
.toZonedDateTimeISO('UTC')
627+
.toPlainDate()
628+
.toLocaleString('en-GB');
629+
583630
return broadcastHTML(
584631
<>
585632
<hr />
586-
<FormatQuote quote={quote.quote} header={`#${index}`} />
633+
<FormatQuote quote={quote.quote} header={`#${index}`} addedBy={quote.addedBy} dateAdded={dateAdded} />
587634
<hr />
588635
</>,
589636
{ name: `viewquote-${message.parent.status.userid}` }

0 commit comments

Comments
 (0)