mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Merge pull request #643 from PR0M3TH3AN/codex/use-abbreviated-npub-format-everywhere
Use consistent short npub display
This commit is contained in:
5 files changed
+110
-19
No files matched your search
@@ -62,6 +62,7 @@ import {
|
||||
formatAbsoluteTimestamp as formatAbsoluteTimestampUtil,
|
||||
formatTimeAgo as formatTimeAgoUtil,
|
||||
truncateMiddle,
|
||||
formatShortNpub,
|
||||
} from "./utils/formatters.js";
|
||||
import {
|
||||
escapeHTML as escapeHtml,
|
||||
@@ -436,6 +437,7 @@ class Application {
|
||||
formatTimeAgo: (timestamp) => this.formatTimeAgo(timestamp),
|
||||
escapeHTML: (value) => this.escapeHTML(value),
|
||||
truncateMiddle,
|
||||
formatShortNpub: (value) => formatShortNpub(value),
|
||||
fallbackThumbnailSrc: FALLBACK_THUMBNAIL_SRC,
|
||||
container: document.getElementById("modalContainer") || null,
|
||||
});
|
||||
@@ -456,6 +458,7 @@ class Application {
|
||||
safeEncodeNpub: (pubkey) => this.safeEncodeNpub(pubkey),
|
||||
safeDecodeNpub: (npub) => this.safeDecodeNpub(npub),
|
||||
truncateMiddle: (value, maxLength) => truncateMiddle(value, maxLength),
|
||||
formatShortNpub: (value) => formatShortNpub(value),
|
||||
getProfileCacheEntry: (pubkey) => this.getProfileCacheEntry(pubkey),
|
||||
batchFetchProfiles: (authorSet) => this.batchFetchProfiles(authorSet),
|
||||
switchProfile: (pubkey) => this.authService.switchProfile(pubkey),
|
||||
@@ -4504,18 +4507,14 @@ class Application {
|
||||
if (typeof window !== "undefined" && window.NostrTools?.nip19?.npubEncode) {
|
||||
const encoded = window.NostrTools.nip19.npubEncode(trimmed);
|
||||
if (encoded && typeof encoded === "string") {
|
||||
return `${encoded.slice(0, 8)}…${encoded.slice(-4)}`;
|
||||
return formatShortNpub(encoded);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
userLogger.warn("[Application] Failed to encode reporter npub", error);
|
||||
}
|
||||
|
||||
if (trimmed.length > 12) {
|
||||
return `${trimmed.slice(0, 8)}…${trimmed.slice(-4)}`;
|
||||
}
|
||||
|
||||
return trimmed;
|
||||
return formatShortNpub(trimmed);
|
||||
}
|
||||
|
||||
decorateVideoModeration(video) {
|
||||
@@ -6656,7 +6655,7 @@ class Application {
|
||||
postedAt: this.currentVideo?.rootCreatedAt ?? null,
|
||||
editedAt: normalizedEditedAt,
|
||||
});
|
||||
const displayNpub = `${creatorNpub.slice(0, 8)}...${creatorNpub.slice(-4)}`;
|
||||
const displayNpub = formatShortNpub(creatorNpub);
|
||||
this.videoModal.updateMetadata({
|
||||
title: video.title || "Untitled",
|
||||
description: video.description || "No description available.",
|
||||
|
||||
@@ -11,6 +11,7 @@ import { attachUrlHealthBadges } from "./urlHealthObserver.js";
|
||||
import { accessControl } from "./accessControl.js";
|
||||
import { getApplication } from "./applicationContext.js";
|
||||
import { escapeHTML } from "./utils/domUtils.js";
|
||||
import { formatShortNpub } from "./utils/formatters.js";
|
||||
import createPopover from "./ui/overlay/popoverEngine.js";
|
||||
import { VideoCard } from "./ui/components/VideoCard.js";
|
||||
import { createChannelProfileMenuPanel } from "./ui/components/videoMenuRenderers.js";
|
||||
@@ -3109,10 +3110,11 @@ function applyChannelProfileMetadata({
|
||||
const channelNpubEl = document.getElementById("channelNpub");
|
||||
if (channelNpubEl) {
|
||||
if (npub) {
|
||||
channelNpubEl.textContent = npub;
|
||||
channelNpubEl.textContent = formatShortNpub(npub);
|
||||
} else if (pubkey) {
|
||||
try {
|
||||
channelNpubEl.textContent = window.NostrTools.nip19.npubEncode(pubkey);
|
||||
const encoded = window.NostrTools.nip19.npubEncode(pubkey);
|
||||
channelNpubEl.textContent = formatShortNpub(encoded);
|
||||
} catch (error) {
|
||||
channelNpubEl.textContent = "";
|
||||
}
|
||||
|
||||
@@ -89,6 +89,13 @@ const SERVICE_CONTRACT = [
|
||||
"Utility used for shortening long identifiers in the modal without losing context.",
|
||||
fallback: () => (value) => value,
|
||||
},
|
||||
{
|
||||
key: "formatShortNpub",
|
||||
type: "function",
|
||||
description:
|
||||
"Formats npub strings for display using the canonical short representation (npubXXXX…XXXX).",
|
||||
fallback: () => (value) => (typeof value === "string" ? value : ""),
|
||||
},
|
||||
{
|
||||
key: "getProfileCacheEntry",
|
||||
type: "function",
|
||||
@@ -617,6 +624,7 @@ export class ProfileModalController {
|
||||
this.safeEncodeNpub = this.services.safeEncodeNpub;
|
||||
this.safeDecodeNpub = this.services.safeDecodeNpub;
|
||||
this.truncateMiddle = this.services.truncateMiddle;
|
||||
this.formatShortNpub = this.services.formatShortNpub;
|
||||
this.sendAdminListNotificationService =
|
||||
typeof this.services.sendAdminListNotification === "function"
|
||||
? this.services.sendAdminListNotification
|
||||
@@ -1198,8 +1206,12 @@ export class ProfileModalController {
|
||||
const activeMeta = activeEntry ? resolveMeta(activeEntry) : null;
|
||||
const hasActiveProfile = Boolean(activeEntry && activeMeta);
|
||||
const truncate = this.truncateMiddle || ((value) => value);
|
||||
const formatNpub =
|
||||
typeof this.formatShortNpub === "function"
|
||||
? (value) => this.formatShortNpub(value)
|
||||
: (value) => (typeof value === "string" ? value : "");
|
||||
const activeNameFallback = activeMeta?.npub
|
||||
? truncate(activeMeta.npub, 32)
|
||||
? formatNpub(activeMeta.npub) || "Saved profile"
|
||||
: "Saved profile";
|
||||
const activeDisplayName = hasActiveProfile
|
||||
? activeMeta?.name?.trim() || activeNameFallback
|
||||
@@ -1225,7 +1237,8 @@ export class ProfileModalController {
|
||||
|
||||
if (this.profileNpub) {
|
||||
if (hasActiveProfile && activeMeta?.npub) {
|
||||
this.profileNpub.textContent = truncate(activeMeta.npub, 48);
|
||||
const displayNpub = formatNpub(activeMeta.npub);
|
||||
this.profileNpub.textContent = displayNpub || "npub unavailable";
|
||||
} else if (hasActiveProfile) {
|
||||
this.profileNpub.textContent = "npub unavailable";
|
||||
} else {
|
||||
@@ -1320,7 +1333,7 @@ export class ProfileModalController {
|
||||
avatarImg.src = meta.picture || FALLBACK_PROFILE_AVATAR;
|
||||
const cardDisplayName =
|
||||
meta.name?.trim() ||
|
||||
(meta.npub ? truncate(meta.npub, 32) : "Saved profile");
|
||||
(meta.npub ? formatNpub(meta.npub) || "Saved profile" : "Saved profile");
|
||||
avatarImg.alt = `${cardDisplayName} avatar`;
|
||||
avatarSpan.appendChild(avatarImg);
|
||||
|
||||
@@ -1349,9 +1362,12 @@ export class ProfileModalController {
|
||||
|
||||
const npubSpan = document.createElement("span");
|
||||
npubSpan.className = "break-all font-mono text-xs text-muted";
|
||||
npubSpan.textContent = meta.npub
|
||||
? truncate(meta.npub, 48)
|
||||
: "npub unavailable";
|
||||
if (meta.npub) {
|
||||
const displayNpub = formatNpub(meta.npub);
|
||||
npubSpan.textContent = displayNpub || "npub unavailable";
|
||||
} else {
|
||||
npubSpan.textContent = "npub unavailable";
|
||||
}
|
||||
|
||||
metaSpan.append(topLine, nameSpan, npubSpan);
|
||||
button.append(avatarSpan, metaSpan);
|
||||
@@ -2740,6 +2756,11 @@ export class ProfileModalController {
|
||||
const { onRemove, removeLabel = "Remove", confirmMessage, removable = true } =
|
||||
options;
|
||||
|
||||
const formatNpub =
|
||||
typeof this.formatShortNpub === "function"
|
||||
? (value) => this.formatShortNpub(value)
|
||||
: (value) => (typeof value === "string" ? value : "");
|
||||
|
||||
listEl.innerHTML = "";
|
||||
const values = Array.isArray(entries) ? [...entries] : [];
|
||||
values.sort((a, b) => a.localeCompare(b));
|
||||
@@ -2760,7 +2781,8 @@ export class ProfileModalController {
|
||||
|
||||
const label = document.createElement("p");
|
||||
label.className = "break-all text-sm font-medium text-primary";
|
||||
label.textContent = npub;
|
||||
const displayNpub = formatNpub(npub) || npub;
|
||||
label.textContent = displayNpub;
|
||||
item.appendChild(label);
|
||||
|
||||
if (removable && typeof onRemove === "function") {
|
||||
@@ -2770,7 +2792,7 @@ export class ProfileModalController {
|
||||
removeBtn.textContent = removeLabel;
|
||||
removeBtn.addEventListener("click", () => {
|
||||
if (confirmMessage) {
|
||||
const message = confirmMessage.replace("{npub}", npub);
|
||||
const message = confirmMessage.replace("{npub}", displayNpub);
|
||||
if (!window.confirm(message)) {
|
||||
return;
|
||||
}
|
||||
@@ -3395,9 +3417,16 @@ export class ProfileModalController {
|
||||
const actorDisplay = this.normalizeNpubValue(actorNpub) || fallbackActor;
|
||||
const isWhitelist = listType === "whitelist";
|
||||
|
||||
const formatNpub =
|
||||
typeof this.formatShortNpub === "function"
|
||||
? (value) => this.formatShortNpub(value)
|
||||
: (value) => (typeof value === "string" ? value : "");
|
||||
const displayTarget = formatNpub(normalizedTarget) || normalizedTarget;
|
||||
const displayActor = formatNpub(actorDisplay) || actorDisplay;
|
||||
|
||||
const introLine = isWhitelist
|
||||
? `Great news—your npub ${normalizedTarget} has been added to the bitvid whitelist by ${actorDisplay}.`
|
||||
: `We wanted to let you know that your npub ${normalizedTarget} has been placed on the bitvid blacklist by ${actorDisplay}.`;
|
||||
? `Great news—your npub ${displayTarget} has been added to the bitvid whitelist by ${displayActor}.`
|
||||
: `We wanted to let you know that your npub ${displayTarget} has been placed on the bitvid blacklist by ${displayActor}.`;
|
||||
|
||||
const statusLine = isWhitelist
|
||||
? `You now have full creator access across bitvid (${this.bitvidWebsiteUrl}).`
|
||||
|
||||
@@ -20,6 +20,28 @@ export function truncateMiddle(text, maxLength = 72) {
|
||||
return `${text.slice(0, front)}${ellipsis}${text.slice(text.length - back)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats an npub string using the canonical short display format used across
|
||||
* the app (first 8 characters, ellipsis, last 4 characters). When the input is
|
||||
* not an npub or is already short, the original value is returned.
|
||||
*/
|
||||
export function formatShortNpub(value) {
|
||||
if (typeof value !== "string") {
|
||||
return "";
|
||||
}
|
||||
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!trimmed.startsWith("npub") || trimmed.length <= 12) {
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
return `${trimmed.slice(0, 8)}...${trimmed.slice(-4)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a timestamp (in seconds) to a localized string. Falls back to
|
||||
* ISO-8601 formatting if locale formatting fails.
|
||||
|
||||
@@ -3,6 +3,7 @@ import { strict as assert } from 'node:assert';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import ProfileModalController from '../js/ui/profileModalController.js';
|
||||
import { formatShortNpub } from '../js/utils/formatters.js';
|
||||
import { resetRuntimeFlags } from '../js/constants.js';
|
||||
import { applyDesignSystemAttributes } from '../js/designSystem.js';
|
||||
|
||||
@@ -289,6 +290,44 @@ for (const _ of [0]) {
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test('Profile modal uses abbreviated npub display', async () => {
|
||||
const sampleProfiles = [
|
||||
{
|
||||
pubkey: defaultActorHex,
|
||||
npub: 'npub1abcdefghijkmnopqrstuvwxyz1234567890example',
|
||||
name: '',
|
||||
picture: '',
|
||||
},
|
||||
{
|
||||
pubkey: 'b'.repeat(64),
|
||||
npub: 'npub1zyxwvutsrqponmlkjihgfedcba1234567890sample',
|
||||
name: '',
|
||||
picture: '',
|
||||
},
|
||||
];
|
||||
|
||||
const controller = createController({
|
||||
services: {
|
||||
formatShortNpub,
|
||||
},
|
||||
});
|
||||
|
||||
await controller.load();
|
||||
|
||||
controller.state.setSavedProfiles(sampleProfiles);
|
||||
controller.state.setActivePubkey(sampleProfiles[0].pubkey);
|
||||
|
||||
controller.renderSavedProfiles();
|
||||
|
||||
const expectedActive = formatShortNpub(sampleProfiles[0].npub);
|
||||
assert.equal(controller.profileNpub.textContent, expectedActive);
|
||||
|
||||
const switcherNpubEl = controller.switcherList.querySelector('.font-mono');
|
||||
assert.ok(switcherNpubEl, 'switcher should render the secondary profile');
|
||||
const expectedSwitcher = formatShortNpub(sampleProfiles[1].npub);
|
||||
assert.equal(switcherNpubEl.textContent, expectedSwitcher);
|
||||
});
|
||||
}
|
||||
|
||||
test('load() injects markup and caches expected elements', async () => {
|
||||
|
||||
Reference in new issue
Block a user