From b1e6d89023a5dd7df287f34ead2819f6f620d929 Mon Sep 17 00:00:00 2001 From: TanPat Date: Thu, 20 Nov 2025 15:49:49 +0530 Subject: [PATCH 1/3] feat: Dim quote headers and add author metadata --- src/ps/commands/quotes.tsx | 81 ++++++++++++++++++++++++++++++-------- 1 file changed, 65 insertions(+), 16 deletions(-) diff --git a/src/ps/commands/quotes.tsx b/src/ps/commands/quotes.tsx index 1df94c8..b326db4 100644 --- a/src/ps/commands/quotes.tsx +++ b/src/ps/commands/quotes.tsx @@ -20,7 +20,7 @@ import type { PSMessage } from '@/types/ps'; import type { CSSProperties, ReactElement, ReactNode } from 'react'; type IndexedQuoteModel = [index: number, quote: QuoteModel]; -type QuoteCollection = [index: number, quote: string][]; +type QuoteCollection = {index: number, quote: string, addedBy?: string, dateAdded?: string}[]; const PAGE_SIZE = 50; const MAX_QUOTE_LENGTH = (MAX_CHAT_HTML_LENGTH / PAGE_SIZE) * 2; // Leniency margin of 2x @@ -157,16 +157,20 @@ function FormatQuote({ quote, psUsernameTag = true, header, + addedBy = 'Unknown User', + dateAdded = 'Unknown Date', }: { quote: string; psUsernameTag?: boolean; + addedBy?: string; + dateAdded?: string; header?: ReactNode; children?: ReactElement[]; }): ReactElement { const quoteLines = quote.split('\n'); return ( <> - {header} +
{header}
{quoteLines.length > 5 ? (
@@ -188,6 +192,9 @@ function FormatQuote({ quoteLines.map(line => ) )}
+
+ Added by {addedBy} on {dateAdded} +
); } @@ -227,7 +234,11 @@ function MultiQuotes({ plural: 'quotes', })}${total > list.length ? ` of ${total} total` : ''})`; const title = baseTitle ? `${baseTitle} ${suffix}` : pageNum ? `Page ${pageNum} of ${pageCount} ${suffix}` : `All Quotes`; - const quotes = list.map(([header, quote]) => ).space(
, !useDropdown); + const quotes = list + .map( quote => ( + + )) + .space(
, !useDropdown); const content = useDropdown ? ( <> @@ -309,7 +320,12 @@ export const command: PSCommand = { broadcastHTML( <>
- +
, { name: `viewquote-${message.parent.status.userid}` } @@ -326,17 +342,20 @@ export const command: PSCommand = { 'wrapping the username in ``[]`` (eg: ``[14:20:21] • #PartMan hugs Hydro`` would be formatted ' + 'as ``[14:20:21] • #[PartMan] hugs Hydro``).', syntax: 'CMD [new quote]', - async run({ message, arg, broadcastHTML }) { + async run({ message, arg, broadcastHTML, $T }) { const parsedQuote = parseQuote(arg); - const rendered = jsxToHTML(); + const addedBy = message.author.name; + const at = new Date(message.time).toDateString(); + + const rendered = jsxToHTML(); if (rendered.length > MAX_QUOTE_LENGTH) throw new ChatError('Quote is too long.' as ToTranslate); - await addQuote(parsedQuote, message.target.id, message.author.name); + await addQuote(parsedQuote, message.target.id, addedBy); const { length } = await getAllQuotes(message.target.id); broadcastHTML( <>
- +
, { name: `viewquote-${message.parent.status.userid}` } @@ -349,13 +368,18 @@ export const command: PSCommand = { perms: (message, check) => (message.type === 'pm' ? true : check('driver')), help: 'Previews the given quote. Syntax is the same as add.', syntax: 'CMD [new quote]', - async run({ message, arg, broadcastHTML }) { + async run({ message, arg, broadcastHTML, $T }) { const parsedQuote = parseQuote(arg); const { length } = await getAllQuotes(message.target.id); broadcastHTML( <>
- +
, { name: `previewquote-${message.parent.status.userid}` } @@ -375,7 +399,12 @@ export const command: PSCommand = { const foundQuotes: QuoteCollection = searchQuotes( quotes.map((quote, index) => [index + 1, quote]), arg - ).map(([index, quote]) => [index, quote.quote]); + ).map(([index, quote]) => ({ + index, + quote: quote.quote, + addedBy: quote.addedBy, + dateAdded: new Date(quote.at).toDateString(), + })); if (!foundQuotes.length) return broadcast($T('COMMANDS.QUOTES.NO_QUOTES_FOUND')); @@ -405,7 +434,12 @@ export const command: PSCommand = { broadcastHTML( <>
- +
, { name: `viewquote-${message.parent.status.userid}` } @@ -428,7 +462,12 @@ export const command: PSCommand = { const endIndex = startIndex + PAGE_SIZE; const pagedQuotes: QuoteCollection = quotes .slice(startIndex, endIndex) - .map((quote, index) => [startIndex + index + 1, quote.quote]); + .map((quote, index) => ({ + index: startIndex + index + 1, + quote: quote.quote, + addedBy: quote.addedBy, + dateAdded: new Date(quote.at).toDateString() + })); if (!pagedQuotes.length) throw new ChatError('Invalid page number.' as ToTranslate); @@ -459,7 +498,12 @@ export const command: PSCommand = { const endIndex = startIndex + PAGE_SIZE; const pageQuotes: QuoteCollection = quotes .slice(startIndex, endIndex) - .map((quote, index) => [startIndex + index + 1, quote.quote]); + .map((quote, index) => ({ + index: startIndex + index + 1, + quote: quote.quote, + addedBy: quote.addedBy, + dateAdded: new Date(quote.at).toDateString() + })); if (!pageQuotes.length) throw new ChatError('Invalid page number.' as ToTranslate); @@ -531,7 +575,12 @@ export const command: PSCommand = { broadcastHTML( <>
- +
); @@ -583,7 +632,7 @@ export const command: PSCommand = { return broadcastHTML( <>
- +
, { name: `viewquote-${message.parent.status.userid}` } From 85f432f2cd24381ee223cfa3451e776d74a61138 Mon Sep 17 00:00:00 2001 From: TanPat Date: Thu, 20 Nov 2025 20:06:57 +0530 Subject: [PATCH 2/3] chore: Remove QuoteCollection and improve metadata handling - Remove QuoteCollection in favor of IndexedQuoteModel[] - Hide quote metadata unless both fields are present --- src/ps/commands/quotes.tsx | 53 ++++++++++++-------------------------- 1 file changed, 17 insertions(+), 36 deletions(-) diff --git a/src/ps/commands/quotes.tsx b/src/ps/commands/quotes.tsx index b326db4..4e1d4b2 100644 --- a/src/ps/commands/quotes.tsx +++ b/src/ps/commands/quotes.tsx @@ -20,7 +20,6 @@ import type { PSMessage } from '@/types/ps'; import type { CSSProperties, ReactElement, ReactNode } from 'react'; type IndexedQuoteModel = [index: number, quote: QuoteModel]; -type QuoteCollection = {index: number, quote: string, addedBy?: string, dateAdded?: string}[]; const PAGE_SIZE = 50; const MAX_QUOTE_LENGTH = (MAX_CHAT_HTML_LENGTH / PAGE_SIZE) * 2; // Leniency margin of 2x @@ -157,8 +156,8 @@ function FormatQuote({ quote, psUsernameTag = true, header, - addedBy = 'Unknown User', - dateAdded = 'Unknown Date', + addedBy, + dateAdded, }: { quote: string; psUsernameTag?: boolean; @@ -192,9 +191,11 @@ function FormatQuote({ quoteLines.map(line => ) )} -
- Added by {addedBy} on {dateAdded} -
+ {addedBy && dateAdded && ( +
+ Added by {addedBy} on {dateAdded} +
+ )} ); } @@ -220,7 +221,7 @@ function MultiQuotes({ title: baseTitle, showAll = false, }: { - list: QuoteCollection; + list: IndexedQuoteModel[]; pageNum: number | null; total: number; command: string | null; @@ -235,8 +236,8 @@ function MultiQuotes({ })}${total > list.length ? ` of ${total} total` : ''})`; const title = baseTitle ? `${baseTitle} ${suffix}` : pageNum ? `Page ${pageNum} of ${pageCount} ${suffix}` : `All Quotes`; const quotes = list - .map( quote => ( - + .map(([index, quote]) => ( + )) .space(
, !useDropdown); @@ -374,12 +375,7 @@ export const command: PSCommand = { broadcastHTML( <>
- +
, { name: `previewquote-${message.parent.status.userid}` } @@ -396,15 +392,10 @@ export const command: PSCommand = { const room: string = await getRoom(givenRoom, message, $T); const quotes = await getAllQuotes(room); - const foundQuotes: QuoteCollection = searchQuotes( + const foundQuotes: IndexedQuoteModel[] = searchQuotes( quotes.map((quote, index) => [index + 1, quote]), arg - ).map(([index, quote]) => ({ - index, - quote: quote.quote, - addedBy: quote.addedBy, - dateAdded: new Date(quote.at).toDateString(), - })); + ); if (!foundQuotes.length) return broadcast($T('COMMANDS.QUOTES.NO_QUOTES_FOUND')); @@ -460,14 +451,9 @@ export const command: PSCommand = { const pageNum = arg ? parseInt(arg) || 1 : 1; const startIndex = (pageNum - 1) * PAGE_SIZE; const endIndex = startIndex + PAGE_SIZE; - const pagedQuotes: QuoteCollection = quotes + const pagedQuotes: IndexedQuoteModel[] = quotes .slice(startIndex, endIndex) - .map((quote, index) => ({ - index: startIndex + index + 1, - quote: quote.quote, - addedBy: quote.addedBy, - dateAdded: new Date(quote.at).toDateString() - })); + .map((quote, index) => [startIndex + index + 1, quote]); if (!pagedQuotes.length) throw new ChatError('Invalid page number.' as ToTranslate); @@ -496,14 +482,9 @@ export const command: PSCommand = { const pageNum = arg ? parseInt(arg) || 1 : 1; const startIndex = (pageNum - 1) * PAGE_SIZE; const endIndex = startIndex + PAGE_SIZE; - const pageQuotes: QuoteCollection = quotes + const pageQuotes: IndexedQuoteModel[] = quotes .slice(startIndex, endIndex) - .map((quote, index) => ({ - index: startIndex + index + 1, - quote: quote.quote, - addedBy: quote.addedBy, - dateAdded: new Date(quote.at).toDateString() - })); + .map((quote, index) => [startIndex + index + 1, quote]); if (!pageQuotes.length) throw new ChatError('Invalid page number.' as ToTranslate); From 78269f51db1df18283860305997cbda2a40a64a3 Mon Sep 17 00:00:00 2001 From: TanPat Date: Fri, 21 Nov 2025 01:44:56 +0530 Subject: [PATCH 3/3] chore: use Temporal and enhance username in quote footer --- src/ps/commands/quotes.tsx | 61 ++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/src/ps/commands/quotes.tsx b/src/ps/commands/quotes.tsx index 4e1d4b2..460e6c7 100644 --- a/src/ps/commands/quotes.tsx +++ b/src/ps/commands/quotes.tsx @@ -1,4 +1,5 @@ import { escapeHTML, formatText, toRoomID } from 'ps-client/tools'; +import { Temporal } from '@js-temporal/polyfill'; import { PSQuoteRoomPrefs } from '@/cache'; import { isGlobalBot, prefix } from '@/config/ps'; @@ -192,9 +193,11 @@ function FormatQuote({ )} {addedBy && dateAdded && ( -
- Added by {addedBy} on {dateAdded} -
+ <> + Added by  + +  on {dateAdded} + )} ); @@ -236,9 +239,14 @@ function MultiQuotes({ })}${total > list.length ? ` of ${total} total` : ''})`; const title = baseTitle ? `${baseTitle} ${suffix}` : pageNum ? `Page ${pageNum} of ${pageCount} ${suffix}` : `All Quotes`; const quotes = list - .map(([index, quote]) => ( - - )) + .map(([index, quote]) => { + const dateAdded = Temporal.Instant.fromEpochMilliseconds(quote.at.getTime()) + .toZonedDateTimeISO('UTC') + .toPlainDate() + .toLocaleString('en-GB'); + + return ; + }) .space(
, !useDropdown); const content = useDropdown ? ( @@ -249,7 +257,7 @@ function MultiQuotes({

{title}


- {quotes} + <>{quotes}
@@ -318,15 +326,15 @@ export const command: PSCommand = { ); if (!matchingQuotes.length) return broadcast($T('COMMANDS.QUOTES.NO_QUOTES_FOUND_MATCHING', { search: arg })); const [index, randQuote] = matchingQuotes.random()!; + const dateAdded = Temporal.Instant.fromEpochMilliseconds(randQuote.at.getTime()) + .toZonedDateTimeISO('UTC') + .toPlainDate() + .toLocaleString('en-GB'); + broadcastHTML( <>
- +
, { name: `viewquote-${message.parent.status.userid}` } @@ -346,7 +354,7 @@ export const command: PSCommand = { async run({ message, arg, broadcastHTML, $T }) { const parsedQuote = parseQuote(arg); const addedBy = message.author.name; - const at = new Date(message.time).toDateString(); + const at = Temporal.Now.plainDateISO('UTC').toLocaleString('en-GB'); const rendered = jsxToHTML(); @@ -422,15 +430,15 @@ export const command: PSCommand = { const quotes = await getAllQuotes(room); if (!quotes.length) return broadcast($T('COMMANDS.QUOTES.NO_QUOTES_FOUND')); const lastQuote = quotes[quotes.length - 1]; + const dateAdded = Temporal.Instant.fromEpochMilliseconds(lastQuote.at.getTime()) + .toZonedDateTimeISO('UTC') + .toPlainDate() + .toLocaleString('en-GB'); + broadcastHTML( <>
- +
, { name: `viewquote-${message.parent.status.userid}` } @@ -552,6 +560,10 @@ export const command: PSCommand = { toDelete = matching[0]; indexToDelete = quotes.indexOf(toDelete); } + const dateAdded = Temporal.Instant.fromEpochMilliseconds(quotes[indexToDelete].at.getTime()) + .toZonedDateTimeISO('UTC') + .toPlainDate() + .toLocaleString('en-GB'); broadcastHTML( <> @@ -560,7 +572,7 @@ export const command: PSCommand = { quote={quotes[indexToDelete].quote} header={`Deleting #${indexToDelete + 1}:`} addedBy={quotes[indexToDelete].addedBy} - dateAdded={new Date(quotes[indexToDelete].at).toDateString()} + dateAdded={dateAdded} />
@@ -610,10 +622,15 @@ export const command: PSCommand = { throw new ChatError('Invalid quote index.' as ToTranslate); } const quote = quotes[index - 1]; + const dateAdded = Temporal.Instant.fromEpochMilliseconds(quote.at.getTime()) + .toZonedDateTimeISO('UTC') + .toPlainDate() + .toLocaleString('en-GB'); + return broadcastHTML( <>
- +
, { name: `viewquote-${message.parent.status.userid}` }