-
-
-
-
- New reactions
-
- {reactionGroups.map((rg, index) => {
- const profiles = rg.notifications.map((n) => n.related_identity);
- const avatarItems = profiles.map((profile) => {
- const key =
- profile.id ?? profile.handle ?? profile.primary_address ?? "";
- const href = profile.handle ? `/${profile.handle}` : undefined;
- const displayName =
- profile.handle ??
- (profile.id === null || profile.id === undefined
- ? undefined
- : String(profile.id));
- const title =
- displayName !== undefined && displayName !== ""
- ? displayName
+ {isSingleVisibleReactor ? (
+
+ }
+ >
+
+ reacted
+
+
+
+
+ ) : (
+
+
+
+
+ New reactions
+
+ {reactionGroups.map((rg, index) => {
+ const avatarItems = rg.notifications.map((n) => {
+ const profile = n.related_identity;
+ const identityKey = getIdentityKey(profile);
+ const normalizedHandle = getNonEmptyIdentityValue(
+ profile.handle
+ );
+ const key =
+ identityKey === "unknown-profile"
+ ? `unknown-identity-${n.id}`
+ : identityKey;
+ const href = normalizedHandle
+ ? `/${normalizedHandle}`
: undefined;
- return {
- key,
- pfpUrl: profile.pfp ? parseIpfsUrl(profile.pfp) : null,
- ariaLabel: profile.handle
- ? `View @${profile.handle}`
- : "View profile",
- fallback: profile.handle?.slice(0, 2).toUpperCase() ?? "?",
- ...(href !== undefined && { href }),
- ...(title !== undefined && { title }),
- };
- });
- return (
-
- {index > 0 && (
-
- •
-
- )}
-
-
-
-
+ const displayName = normalizedHandle ?? profile.id;
+ const title = displayName || undefined;
+ return {
+ key,
+ pfpUrl: profile.pfp ? parseIpfsUrl(profile.pfp) : null,
+ ariaLabel: normalizedHandle
+ ? `View @${normalizedHandle}`
+ : "View profile",
+ fallback: normalizedHandle?.slice(0, 2).toUpperCase() ?? "?",
+ ...(href !== undefined && { href }),
+ ...(title !== undefined && { title }),
+ };
+ });
+ return (
+
+ {index > 0 && (
+
+ •
+
+ )}
+
-
-
- );
- })}
-
-
- {fullReactors.length > 0 && (
-
-
+
+ );
+ })}
+
- )}
+ {fullReactors.length > 0 && (
+
+
+
+ )}
+
-
+ )}
= {
[DROP_FORGE_SECTIONS.LAUNCH.path]: [`${DROP_FORGE_TITLE} Launch`],
};
-const getPageMatchPriority = (
- normalizedTitle: string,
- hrefSegments: string[],
- normalizedBreadcrumbs: string[],
- normalizedSearchTerms: string[],
- normalizedQuery: string
-): number => {
- if (normalizedTitle === normalizedQuery) return 0;
- if (hrefSegments.includes(normalizedQuery)) return 1;
- if (normalizedSearchTerms.includes(normalizedQuery)) return 2;
- if (normalizedTitle.startsWith(normalizedQuery)) return 3;
- if (normalizedBreadcrumbs.includes(normalizedQuery)) return 4;
- if (normalizedSearchTerms.some((term) => term.includes(normalizedQuery))) {
- return 5;
+const singularizePageSearchToken = (token: string): string => {
+ if (token.length <= 3) {
+ return token;
+ }
+
+ if (token.endsWith("ies") && token.length > 4) {
+ return `${token.slice(0, -3)}y`;
+ }
+
+ if (
+ token.endsWith("ches") ||
+ token.endsWith("shes") ||
+ token.endsWith("xes") ||
+ token.endsWith("zes") ||
+ token.endsWith("sses")
+ ) {
+ return token.slice(0, -2);
}
- if (normalizedTitle.includes(normalizedQuery)) return 6;
- if (hrefSegments.some((segment) => segment.includes(normalizedQuery))) {
- return 7;
+
+ if (token.endsWith("s") && !token.endsWith("ss")) {
+ return token.slice(0, -1);
+ }
+
+ return token;
+};
+
+const getPageSearchTokens = (value: string): string[] =>
+ value
+ .toLowerCase()
+ .split(/[^a-z0-9]+/g)
+ .filter(Boolean)
+ .map(singularizePageSearchToken);
+
+const pageTokensMatchInOrder = (
+ candidateTokens: readonly string[],
+ queryTokens: readonly string[],
+ matcher: (candidateToken: string, queryToken: string) => boolean
+) => {
+ if (queryTokens.length === 0) {
+ return false;
+ }
+
+ let candidateIndex = 0;
+
+ return queryTokens.every((queryToken) => {
+ while (candidateIndex < candidateTokens.length) {
+ const candidateToken = candidateTokens[candidateIndex];
+ candidateIndex += 1;
+
+ if (candidateToken !== undefined && matcher(candidateToken, queryToken)) {
+ return true;
+ }
+ }
+
+ return false;
+ });
+};
+
+const hasCanonicalPageTokenMatch = (
+ values: readonly string[],
+ queryTokens: readonly string[]
+) => {
+ if (queryTokens.length === 0) {
+ return false;
+ }
+
+ return values.some((value) => {
+ const candidateTokens = getPageSearchTokens(value);
+ return pageTokensMatchInOrder(
+ candidateTokens,
+ queryTokens,
+ (candidateToken, queryToken) => candidateToken === queryToken
+ );
+ });
+};
+
+const hasExactCanonicalPageTokenMatch = (
+ values: readonly string[],
+ queryTokens: readonly string[]
+) => {
+ if (queryTokens.length === 0) {
+ return false;
+ }
+
+ return values.some((value) => {
+ const candidateTokens = getPageSearchTokens(value);
+ return (
+ candidateTokens.length === queryTokens.length &&
+ candidateTokens.every((token, index) => token === queryTokens[index])
+ );
+ });
+};
+
+const hasCanonicalPageTokenPrefixMatch = (
+ values: readonly string[],
+ queryTokens: readonly string[]
+) => {
+ if (queryTokens.length === 0) {
+ return false;
}
- return 8;
+
+ return values.some((value) => {
+ const candidateTokens = getPageSearchTokens(value);
+ return pageTokensMatchInOrder(
+ candidateTokens,
+ queryTokens,
+ (candidateToken, queryToken) => candidateToken.startsWith(queryToken)
+ );
+ });
+};
+
+const getCompositePageSearchValues = (
+ normalizedTitle: string,
+ normalizedBreadcrumbs: readonly string[],
+ normalizedSearchTerms: readonly string[]
+): string[] => {
+ const values = normalizedBreadcrumbs.flatMap((_, index) => {
+ const breadcrumbSuffix = normalizedBreadcrumbs.slice(index);
+ const suffixPrefix = breadcrumbSuffix.join(" ");
+
+ return [normalizedTitle, ...normalizedSearchTerms]
+ .map((value) => [suffixPrefix, value].filter(Boolean).join(" "))
+ .filter(Boolean);
+ });
+
+ return [...new Set(values)];
+};
+
+type PageSearchMatchInputs = {
+ readonly normalizedTitle: string;
+ readonly normalizedHref: string;
+ readonly hrefSegments: string[];
+ readonly normalizedBreadcrumbs: string[];
+ readonly normalizedSearchTerms: string[];
+ readonly compositeValues: string[];
+};
+
+const getPageMatchPriority = (
+ {
+ normalizedTitle,
+ normalizedHref,
+ hrefSegments,
+ normalizedBreadcrumbs,
+ normalizedSearchTerms,
+ compositeValues,
+ }: PageSearchMatchInputs,
+ normalizedQuery: string,
+ canonicalQueryTokens: readonly string[]
+): number => {
+ const isPathLikeQuery =
+ normalizedQuery.startsWith("/") || normalizedQuery.includes("/");
+ const titleValues = [normalizedTitle];
+ const hrefValues = [normalizedHref, ...hrefSegments];
+ const checks = [
+ normalizedTitle === normalizedQuery,
+ normalizedHref === normalizedQuery,
+ isPathLikeQuery && normalizedHref.startsWith(normalizedQuery),
+ isPathLikeQuery && normalizedHref.includes(normalizedQuery),
+ hasExactCanonicalPageTokenMatch(titleValues, canonicalQueryTokens),
+ hrefSegments.includes(normalizedQuery),
+ normalizedSearchTerms.includes(normalizedQuery),
+ hasExactCanonicalPageTokenMatch(
+ normalizedSearchTerms,
+ canonicalQueryTokens
+ ),
+ compositeValues.includes(normalizedQuery),
+ hasExactCanonicalPageTokenMatch(compositeValues, canonicalQueryTokens),
+ normalizedTitle.startsWith(normalizedQuery),
+ hasCanonicalPageTokenMatch(titleValues, canonicalQueryTokens),
+ hasCanonicalPageTokenPrefixMatch(titleValues, canonicalQueryTokens),
+ normalizedBreadcrumbs.includes(normalizedQuery),
+ hasExactCanonicalPageTokenMatch(
+ normalizedBreadcrumbs,
+ canonicalQueryTokens
+ ),
+ hasCanonicalPageTokenPrefixMatch(
+ normalizedBreadcrumbs,
+ canonicalQueryTokens
+ ),
+ normalizedSearchTerms.some((term) => term.includes(normalizedQuery)),
+ hasCanonicalPageTokenMatch(normalizedSearchTerms, canonicalQueryTokens),
+ hasCanonicalPageTokenPrefixMatch(
+ normalizedSearchTerms,
+ canonicalQueryTokens
+ ),
+ hasCanonicalPageTokenMatch(compositeValues, canonicalQueryTokens),
+ hasCanonicalPageTokenPrefixMatch(compositeValues, canonicalQueryTokens),
+ normalizedTitle.includes(normalizedQuery),
+ hrefSegments.some((segment) => segment.includes(normalizedQuery)),
+ hasCanonicalPageTokenMatch(hrefValues, canonicalQueryTokens),
+ hasCanonicalPageTokenPrefixMatch(hrefValues, canonicalQueryTokens),
+ hasCanonicalPageTokenMatch(normalizedBreadcrumbs, canonicalQueryTokens),
+ ];
+
+ const matchIndex = checks.findIndex(Boolean);
+ return matchIndex === -1 ? checks.length : matchIndex;
};
const pageMatchesQuery = (
@@ -176,15 +352,54 @@ const pageMatchesQuery = (
normalizedHref: string,
normalizedBreadcrumbs: string[],
normalizedSearchTerms: string[],
- normalizedQuery: string
+ compositeValues: string[],
+ normalizedQuery: string,
+ canonicalQueryTokens: readonly string[]
) => {
+ const isPathLikeQuery =
+ normalizedQuery.startsWith("/") || normalizedQuery.includes("/");
if (normalizedTitle.includes(normalizedQuery)) return true;
- if (normalizedHref.includes(normalizedQuery)) return true;
+ if (isPathLikeQuery && normalizedHref.startsWith(normalizedQuery)) {
+ return true;
+ }
+ if (isPathLikeQuery && normalizedHref.includes(normalizedQuery)) {
+ return true;
+ }
if (normalizedSearchTerms.some((term) => term.includes(normalizedQuery))) {
return true;
}
- return normalizedBreadcrumbs.some((breadcrumb) =>
- breadcrumb.includes(normalizedQuery)
+ if (
+ normalizedBreadcrumbs.some((breadcrumb) =>
+ breadcrumb.includes(normalizedQuery)
+ )
+ ) {
+ return true;
+ }
+ if (compositeValues.some((value) => value.includes(normalizedQuery))) {
+ return true;
+ }
+
+ return (
+ hasCanonicalPageTokenMatch(
+ [
+ normalizedTitle,
+ normalizedHref,
+ ...normalizedBreadcrumbs,
+ ...normalizedSearchTerms,
+ ...compositeValues,
+ ],
+ canonicalQueryTokens
+ ) ||
+ hasCanonicalPageTokenPrefixMatch(
+ [
+ normalizedTitle,
+ normalizedHref,
+ ...normalizedBreadcrumbs,
+ ...normalizedSearchTerms,
+ ...compositeValues,
+ ],
+ canonicalQueryTokens
+ )
);
};
@@ -450,6 +665,7 @@ export default function HeaderSearchModal({
return [];
}
const normalizedQuery = trimmedSearchValue.toLowerCase();
+ const canonicalQueryTokens = getPageSearchTokens(trimmedSearchValue);
if (!normalizedQuery) {
return [];
}
@@ -464,6 +680,11 @@ export default function HeaderSearchModal({
const normalizedSearchTerms = (page.searchTerms ?? []).map((value) =>
value.toLowerCase()
);
+ const compositeValues = getCompositePageSearchValues(
+ normalizedTitle,
+ normalizedBreadcrumbs,
+ normalizedSearchTerms
+ );
if (
!pageMatchesQuery(
@@ -471,23 +692,31 @@ export default function HeaderSearchModal({
normalizedHref,
normalizedBreadcrumbs,
normalizedSearchTerms,
- normalizedQuery
+ compositeValues,
+ normalizedQuery,
+ canonicalQueryTokens
)
) {
return accumulator;
}
const hrefSegments = normalizedHref.split("/").filter(Boolean);
+ const matchInputs = {
+ normalizedTitle,
+ normalizedHref,
+ hrefSegments,
+ normalizedBreadcrumbs,
+ normalizedSearchTerms,
+ compositeValues,
+ };
accumulator.push({
page,
normalizedTitle,
priority: getPageMatchPriority(
- normalizedTitle,
- hrefSegments,
- normalizedBreadcrumbs,
- normalizedSearchTerms,
- normalizedQuery
+ matchInputs,
+ normalizedQuery,
+ canonicalQueryTokens
),
});
diff --git a/components/meme-calendar/MemeCalendar.tsx b/components/meme-calendar/MemeCalendar.tsx
index b16965af67..49bb5ab2d9 100644
--- a/components/meme-calendar/MemeCalendar.tsx
+++ b/components/meme-calendar/MemeCalendar.tsx
@@ -24,6 +24,8 @@ import {
formatFullDate,
formatFullDateTime,
formatMint,
+ formatUtcMonth,
+ formatUtcMonthYear,
getMintNumberForMintDate,
getMonthWeeks,
getRangeDatesByZoom,
@@ -57,12 +59,6 @@ import { getHistoricalMintsOnUtcDay } from "./meme-calendar.szn1";
* `tw-` - configure your Tailwind setup accordingly.
*/
-function formatMonthYearShort(d: Date): string {
- return `${d.toLocaleString("default", {
- month: "short",
- })} ${d.getUTCFullYear()}`;
-}
-
function escapeHtml(value: string): string {
return value
.replaceAll("&", "&")
@@ -145,10 +141,7 @@ interface EonViewProps {
function Month({ date, onSelectDay, autoOpenYmd, displayTz }: MonthProps) {
const year = date.getUTCFullYear();
const month = date.getUTCMonth();
- const monthName = new Date(Date.UTC(year, month, 1)).toLocaleString(
- "default",
- { month: "long" }
- );
+ const monthName = formatUtcMonth(new Date(Date.UTC(year, month, 1)), "long");
const weeks = getMonthWeeks(year, month);
useEffect(() => {
if (!autoOpenYmd) return;
@@ -431,10 +424,7 @@ function YearView({
>
SZN #1
- {start.toLocaleString("default", { month: "short" })}{" "}
- {start.getUTCFullYear()} -{" "}
- {end.toLocaleString("default", { month: "short" })}{" "}
- {end.getUTCFullYear()}
+ {formatUtcMonthYear(start)} - {formatUtcMonthYear(end)}
Memes #1 - #47
@@ -471,10 +461,7 @@ function YearView({
SZN #{displayedSeasonNumberFromIndex(s.sIdx)}
- {s.start.toLocaleString("default", { month: "short" })}{" "}
- {s.start.getUTCFullYear()} -{" "}
- {s.end.toLocaleString("default", { month: "short" })}{" "}
- {s.end.getUTCFullYear()}
+ {formatUtcMonthYear(s.start)} - {formatUtcMonthYear(s.end)}
{s.label}
@@ -566,10 +553,7 @@ function EpochView({
Year #{y.yearNumber} ({y.start.getUTCFullYear()})
- {y.start.toLocaleString("default", { month: "short" })}{" "}
- {y.start.getUTCFullYear()} -{" "}
- {y.end.toLocaleString("default", { month: "short" })}{" "}
- {y.end.getUTCFullYear()}
+ {formatUtcMonthYear(y.start)} - {formatUtcMonthYear(y.end)}
{y.label}
@@ -658,10 +642,7 @@ function PeriodView({
Epoch #{ep.epochNumber} ({ep.start.getUTCFullYear()})
- {ep.start.toLocaleString("default", { month: "short" })}{" "}
- {ep.start.getUTCFullYear()} -{" "}
- {ep.end.toLocaleString("default", { month: "short" })}{" "}
- {ep.end.getUTCFullYear()}
+ {formatUtcMonthYear(ep.start)} - {formatUtcMonthYear(ep.end)}
{ep.label}
@@ -751,10 +732,7 @@ function EraView({
Period #{p.periodNumber} ({p.start.getUTCFullYear()})
- {p.start.toLocaleString("default", { month: "short" })}{" "}
- {p.start.getUTCFullYear()} -{" "}
- {p.end.toLocaleString("default", { month: "short" })}{" "}
- {p.end.getUTCFullYear()}
+ {formatUtcMonthYear(p.start)} - {formatUtcMonthYear(p.end)}
- {er.start.toLocaleString("default", { month: "short" })}{" "}
- {er.start.getUTCFullYear()} -{" "}
- {er.end.toLocaleString("default", { month: "short" })}{" "}
- {er.end.getUTCFullYear()}
+ {formatUtcMonthYear(er.start)} - {formatUtcMonthYear(er.end)}