Browse Source

fixed bugs and accesibility warnings

master
silberengel 8 months ago
parent
commit
a3143bcb85
  1. 357
      src/lib/navigator/EventNetwork/Legend.svelte
  2. 81
      src/lib/navigator/EventNetwork/Settings.svelte
  3. 8
      src/lib/navigator/EventNetwork/TagTable.svelte
  4. 6
      src/lib/navigator/EventNetwork/utils/personNetworkBuilder.ts
  5. 2
      src/lib/stores/index.ts
  6. 2
      src/lib/utils/nostr_identifiers.ts
  7. 4
      src/lib/utils/websocket_utils.ts
  8. 3
      src/routes/publication/[type]/[identifier]/+layout.server.ts
  9. 3
      src/routes/publication/[type]/[identifier]/+page.server.ts

357
src/lib/navigator/EventNetwork/Legend.svelte

@ -97,7 +97,13 @@
</script> </script>
<div class={`leather-legend ${className}`}> <div class={`leather-legend ${className}`}>
<div class="flex items-center justify-between space-x-3 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md px-2 py-1 -mx-2 -my-1" onclick={toggle}> <button
class="flex items-center justify-between space-x-3 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md px-2 py-1 -mx-2 -my-1 w-full text-left border-none bg-none"
onclick={toggle}
onkeydown={(e) => e.key === 'Enter' || e.key === ' ' ? toggle() : null}
aria-expanded={expanded}
aria-controls="legend-content"
>
<h3 class="h-leather">Legend</h3> <h3 class="h-leather">Legend</h3>
<div class="pointer-events-none"> <div class="pointer-events-none">
{#if expanded} {#if expanded}
@ -106,13 +112,19 @@
<CaretDownOutline /> <CaretDownOutline />
{/if} {/if}
</div> </div>
</div> </button>
{#if expanded} {#if expanded}
<div class="space-y-4"> <div id="legend-content" class="space-y-4">
<!-- Node Types Section --> <!-- Node Types Section -->
<div class="border-b border-gray-200 dark:border-gray-700 pb-4 mb-4 last:border-b-0 last:mb-0"> <div class="border-b border-gray-200 dark:border-gray-700 pb-4 mb-4 last:border-b-0 last:mb-0">
<div class="flex justify-between items-center cursor-pointer px-2 py-2 rounded hover:bg-gray-50 dark:hover:bg-gray-800 mb-3" onclick={toggleNodeTypes}> <button
class="flex justify-between items-center cursor-pointer px-2 py-2 rounded hover:bg-gray-50 dark:hover:bg-gray-800 mb-3 w-full text-left border-none bg-none"
onclick={toggleNodeTypes}
onkeydown={(e) => e.key === 'Enter' || e.key === ' ' ? toggleNodeTypes() : null}
aria-expanded={nodeTypesExpanded}
aria-controls="node-types-content"
>
<h4 class="font-semibold text-gray-700 dark:text-gray-300 text-sm m-0">Node Types</h4> <h4 class="font-semibold text-gray-700 dark:text-gray-300 text-sm m-0">Node Types</h4>
<div class="pointer-events-none"> <div class="pointer-events-none">
{#if nodeTypesExpanded} {#if nodeTypesExpanded}
@ -121,87 +133,95 @@
<CaretDownOutline class="w-3 h-3" /> <CaretDownOutline class="w-3 h-3" />
{/if} {/if}
</div> </div>
</div> </button>
{#if nodeTypesExpanded} {#if nodeTypesExpanded}
<ul class="space-y-2"> <div id="node-types-content">
<!-- Dynamic event kinds --> <ul class="space-y-2">
{#each Object.entries(eventCounts).sort(([a], [b]) => Number(a) - Number(b)) as [kindStr, count]} <!-- Dynamic event kinds -->
{@const kind = Number(kindStr)} {#each Object.entries(eventCounts).sort(([a], [b]) => Number(a) - Number(b)) as [kindStr, count]}
{@const countNum = count as number} {@const kind = Number(kindStr)}
{@const color = getEventKindColor(kind)} {@const countNum = count as number}
{@const name = getEventKindName(kind)} {@const color = getEventKindColor(kind)}
{#if countNum > 0} {@const name = getEventKindName(kind)}
<li class="flex items-center mb-2 last:mb-0"> {#if countNum > 0}
<div class="flex items-center mr-2"> <li class="flex items-center mb-2 last:mb-0">
<span <div class="flex items-center mr-2">
class="w-4 h-4 rounded-full" <span
style="background-color: {color}" class="w-4 h-4 rounded-full"
> style="background-color: {color}"
>
</span>
</div>
<span class="text-sm text-gray-700 dark:text-gray-300">
{kind} - {name} ({countNum})
</span> </span>
</div> </li>
<span class="text-sm text-gray-700 dark:text-gray-300">
{kind} - {name} ({countNum})
</span>
</li>
{/if}
{/each}
<!-- Connection lines -->
<li class="flex items-center mb-2 last:mb-0">
<svg class="w-6 h-6 mr-2" viewBox="0 0 24 24">
<path
d="M4 12h16M16 6l6 6-6 6"
class="network-link-leather"
stroke-width="2"
fill="none"
/>
</svg>
<span class="text-sm text-gray-700 dark:text-gray-300">
{#if starMode}
Radial connections from centers to related events
{:else}
Arrows indicate relationships and sequence
{/if} {/if}
</span> {/each}
</li>
<!-- Connection lines -->
<!-- Edge colors for person connections -->
{#if showPersonNodes && personAnchors.length > 0}
<li class="flex items-center mb-2 last:mb-0">
<svg class="w-6 h-6 mr-2" viewBox="0 0 24 24">
<path
d="M4 12h16"
class="person-link-signed"
stroke-width="2"
fill="none"
/>
</svg>
<span class="text-xs text-gray-700 dark:text-gray-300">
Authored by person
</span>
</li>
<li class="flex items-center mb-2 last:mb-0"> <li class="flex items-center mb-2 last:mb-0">
<svg class="w-6 h-6 mr-2" viewBox="0 0 24 24"> <svg class="w-6 h-6 mr-2" viewBox="0 0 24 24">
<path <path
d="M4 12h16" d="M4 12h16M16 6l6 6-6 6"
class="person-link-referenced" class="network-link-leather"
stroke-width="2" stroke-width="2"
fill="none" fill="none"
/> />
</svg> </svg>
<span class="text-xs text-gray-700 dark:text-gray-300"> <span class="text-sm text-gray-700 dark:text-gray-300">
References person {#if starMode}
Radial connections from centers to related events
{:else}
Arrows indicate relationships and sequence
{/if}
</span> </span>
</li> </li>
{/if}
</ul> <!-- Edge colors for person connections -->
{#if showPersonNodes && personAnchors.length > 0}
<li class="flex items-center mb-2 last:mb-0">
<svg class="w-6 h-6 mr-2" viewBox="0 0 24 24">
<path
d="M4 12h16"
class="person-link-signed"
stroke-width="2"
fill="none"
/>
</svg>
<span class="text-xs text-gray-700 dark:text-gray-300">
Authored by person
</span>
</li>
<li class="flex items-center mb-2 last:mb-0">
<svg class="w-6 h-6 mr-2" viewBox="0 0 24 24">
<path
d="M4 12h16"
class="person-link-referenced"
stroke-width="2"
fill="none"
/>
</svg>
<span class="text-xs text-gray-700 dark:text-gray-300">
References person
</span>
</li>
{/if}
</ul>
</div>
{/if} {/if}
</div> </div>
<!-- Tag Anchor Controls Section --> <!-- Tag Anchor Controls Section -->
<div class="border-b border-gray-200 dark:border-gray-700 pb-4 mb-4 last:border-b-0 last:mb-0"> <div class="border-b border-gray-200 dark:border-gray-700 pb-4 mb-4 last:border-b-0 last:mb-0">
<div class="flex justify-between items-center cursor-pointer px-2 py-2 rounded hover:bg-gray-50 dark:hover:bg-gray-800 mb-3" onclick={() => tagControlsExpanded = !tagControlsExpanded}> <button
class="flex justify-between items-center cursor-pointer px-2 py-2 rounded hover:bg-gray-50 dark:hover:bg-gray-800 mb-3 w-full text-left border-none bg-none"
onclick={() => tagControlsExpanded = !tagControlsExpanded}
onkeydown={(e) => e.key === 'Enter' || e.key === ' ' ? (tagControlsExpanded = !tagControlsExpanded) : null}
aria-expanded={tagControlsExpanded}
aria-controls="tag-controls-content"
>
<h4 class="font-semibold text-gray-700 dark:text-gray-300 text-sm m-0">Tag Anchor Controls</h4> <h4 class="font-semibold text-gray-700 dark:text-gray-300 text-sm m-0">Tag Anchor Controls</h4>
<div class="pointer-events-none"> <div class="pointer-events-none">
{#if tagControlsExpanded} {#if tagControlsExpanded}
@ -210,10 +230,10 @@
<CaretDownOutline class="w-3 h-3" /> <CaretDownOutline class="w-3 h-3" />
{/if} {/if}
</div> </div>
</div> </button>
{#if tagControlsExpanded} {#if tagControlsExpanded}
<div class="space-y-3"> <div id="tag-controls-content" class="space-y-3">
<!-- Show Tag Anchors Toggle --> <!-- Show Tag Anchors Toggle -->
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<button <button
@ -221,7 +241,9 @@
showTagAnchors = !showTagAnchors; showTagAnchors = !showTagAnchors;
onTagSettingsChange(); onTagSettingsChange();
}} }}
onkeydown={(e) => e.key === 'Enter' || e.key === ' ' ? (showTagAnchors = !showTagAnchors, onTagSettingsChange()) : null}
class="px-2 py-1 border border-gray-300 dark:border-gray-700 rounded text-xs font-medium cursor-pointer transition min-w-[3rem] hover:bg-gray-200 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-primary-500 {showTagAnchors ? 'bg-blue-600 text-white border-blue-600 hover:bg-blue-700 dark:bg-blue-600 dark:text-white dark:border-blue-600 dark:hover:bg-blue-700' : 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300'}" class="px-2 py-1 border border-gray-300 dark:border-gray-700 rounded text-xs font-medium cursor-pointer transition min-w-[3rem] hover:bg-gray-200 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-primary-500 {showTagAnchors ? 'bg-blue-600 text-white border-blue-600 hover:bg-blue-700 dark:bg-blue-600 dark:text-white dark:border-blue-600 dark:hover:bg-blue-700' : 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300'}"
aria-pressed={showTagAnchors}
> >
{showTagAnchors ? 'ON' : 'OFF'} {showTagAnchors ? 'ON' : 'OFF'}
</button> </button>
@ -231,8 +253,9 @@
{#if showTagAnchors} {#if showTagAnchors}
<!-- Tag Type Selection --> <!-- Tag Type Selection -->
<div> <div>
<label class="text-xs text-gray-600 dark:text-gray-400">Tag Type:</label> <label for="tag-type-select" class="text-xs text-gray-600 dark:text-gray-400">Tag Type:</label>
<select <select
id="tag-type-select"
bind:value={selectedTagType} bind:value={selectedTagType}
onchange={onTagSettingsChange} onchange={onTagSettingsChange}
class="w-full text-xs bg-primary-0 dark:bg-primary-1000 border border-gray-300 dark:border-gray-700 rounded-md px-2 py-1 dark:text-white mt-1" class="w-full text-xs bg-primary-0 dark:bg-primary-1000 border border-gray-300 dark:border-gray-700 rounded-md px-2 py-1 dark:text-white mt-1"
@ -253,7 +276,13 @@
<!-- Tag Anchors section --> <!-- Tag Anchors section -->
{#if showTags && tagAnchors.length > 0} {#if showTags && tagAnchors.length > 0}
<div class="border-b border-gray-200 dark:border-gray-700 pb-4 mb-4 last:border-b-0 last:mb-0"> <div class="border-b border-gray-200 dark:border-gray-700 pb-4 mb-4 last:border-b-0 last:mb-0">
<div class="flex justify-between items-center cursor-pointer px-2 py-2 rounded hover:bg-gray-50 dark:hover:bg-gray-800 mb-3" onclick={toggleTagAnchors}> <button
class="flex justify-between items-center cursor-pointer px-2 py-2 rounded hover:bg-gray-50 dark:hover:bg-gray-800 mb-3 w-full text-left border-none bg-none"
onclick={toggleTagAnchors}
onkeydown={(e) => e.key === 'Enter' || e.key === ' ' ? toggleTagAnchors() : null}
aria-expanded={tagAnchorsExpanded}
aria-controls="tag-anchors-content"
>
<h4 class="font-semibold text-gray-700 dark:text-gray-300 text-sm m-0">Active Tag Anchors: {tagAnchors[0].type}</h4> <h4 class="font-semibold text-gray-700 dark:text-gray-300 text-sm m-0">Active Tag Anchors: {tagAnchors[0].type}</h4>
<div class="pointer-events-none"> <div class="pointer-events-none">
{#if tagAnchorsExpanded} {#if tagAnchorsExpanded}
@ -262,89 +291,70 @@
<CaretDownOutline class="w-3 h-3" /> <CaretDownOutline class="w-3 h-3" />
{/if} {/if}
</div> </div>
</div> </button>
{#if tagAnchorsExpanded} {#if tagAnchorsExpanded}
{@const sortedAnchors = tagSortMode === 'count' {@const sortedAnchors = tagSortMode === 'count'
? [...tagAnchors].sort((a, b) => b.count - a.count) ? [...tagAnchors].sort((a, b) => b.count - a.count)
: [...tagAnchors].sort((a, b) => a.label.localeCompare(b.label)) : [...tagAnchors].sort((a, b) => a.label.localeCompare(b.label))
} }
{#if autoDisabledTags} <div id="tag-anchors-content">
<div class="text-xs text-amber-600 dark:text-amber-400 mb-2 p-2 bg-amber-50 dark:bg-amber-900/20 rounded"> {#if autoDisabledTags}
<strong>Note:</strong> All {tagAnchors.length} tags were auto-disabled to prevent graph overload. Click individual tags below to enable them. <div class="text-xs text-amber-600 dark:text-amber-400 mb-2 p-2 bg-amber-50 dark:bg-amber-900/20 rounded">
</div> <strong>Note:</strong> All {tagAnchors.length} tags were auto-disabled to prevent graph overload. Click individual tags below to enable them.
{/if} </div>
{/if}
<!-- Sort options and controls -->
<div class="flex items-center justify-between gap-4 mb-3"> <!-- Sort options and controls -->
<div class="flex items-center gap-4"> <div class="flex items-center justify-between gap-4 mb-3">
<span class="text-xs text-gray-600 dark:text-gray-400">Sort by:</span> <div class="flex items-center gap-4">
<label class="flex items-center gap-1 cursor-pointer"> <span class="text-xs text-gray-600 dark:text-gray-400">Sort by:</span>
<input <label class="flex items-center gap-1 cursor-pointer">
type="radio" <input
name="tagSort" type="radio"
value="count" name="tagSort"
bind:group={tagSortMode} value="count"
class="w-3 h-3" bind:group={tagSortMode}
/> class="w-3 h-3"
<span class="text-xs">Count</span> />
</label> <span class="text-xs">Count</span>
<label class="flex items-center gap-1 cursor-pointer"> </label>
<input <label class="flex items-center gap-1 cursor-pointer">
type="radio" <input
name="tagSort" type="radio"
value="alphabetical" name="tagSort"
bind:group={tagSortMode} value="alphabetical"
class="w-3 h-3" bind:group={tagSortMode}
/> class="w-3 h-3"
<span class="text-xs">Alphabetical</span> />
</label> <span class="text-xs">Alphabetical</span>
</label>
</div>
</div> </div>
<label class="flex items-center gap-1 cursor-pointer"> <div class="space-y-1 max-h-48 overflow-y-auto">
<input {#each sortedAnchors as tag}
type="checkbox" {@const isDisabled = disabledTags.has(tag.value)}
onclick={invertTagSelection} <button
class="w-3 h-3" class="flex items-center justify-between w-full p-2 rounded text-left border-none bg-none cursor-pointer transition hover:bg-black/5 dark:hover:bg-white/5 disabled:opacity-50"
/> onclick={() => onTagToggle(tag.value)}
<span class="text-xs">Invert Selection</span> onkeydown={(e) => e.key === 'Enter' || e.key === ' ' ? onTagToggle(tag.value) : null}
</label> disabled={false}
</div> title={isDisabled ? `Click to show ${tag.label}` : `Click to hide ${tag.label}`}
aria-pressed={!isDisabled}
<div
class="grid gap-1 {tagAnchors.length > 20 ? 'max-h-96 overflow-y-auto pr-2' : ''}"
style="grid-template-columns: repeat({TAG_LEGEND_COLUMNS}, 1fr);"
>
{#each sortedAnchors as anchor}
{@const tagId = `${anchor.type}-${anchor.label}`}
{@const isDisabled = disabledTags.has(tagId)}
<button
class="flex items-center gap-1 p-1 rounded w-full text-left border-none bg-none cursor-pointer transition hover:bg-black/5 dark:hover:bg-white/5 disabled:opacity-50"
onclick={() => onTagToggle(tagId)}
title={isDisabled ? `Click to show ${anchor.label}` : `Click to hide ${anchor.label}`}
> >
<span class="text-xs text-gray-700 dark:text-gray-300" style="opacity: {isDisabled ? 0.5 : 1};">
{tag.label} ({tag.count})
</span>
<div class="flex items-center"> <div class="flex items-center">
<span <span
class="w-4.5 h-4.5 rounded-full border-2 border-white flex items-center justify-center" class="inline-block w-3.5 h-3.5 rotate-45 border-2 border-white"
style="background-color: {anchor.color}; opacity: {isDisabled ? 0.3 : 1};" style="background-color: {getEventKindColor(30040)}; opacity: {isDisabled ? 0.3 : 1};"
> ></span>
<span class="text-xs text-white font-bold">
{anchor.type === "t"
? "#"
: anchor.type === "author"
? "A"
: anchor.type.charAt(0).toUpperCase()}
</span>
</span>
</div> </div>
<span class="text-xs text-gray-700 dark:text-gray-300 truncate" style="opacity: {isDisabled ? 0.5 : 1};" title={anchor.label}>
{anchor.label.length > 25 ? anchor.label.slice(0, 22) + '...' : anchor.label}
{#if !isDisabled}
<span class="text-gray-500 dark:text-gray-400">({anchor.count})</span>
{/if}
</span>
</button> </button>
{/each} {/each}
</div>
</div> </div>
{/if} {/if}
</div> </div>
@ -352,7 +362,13 @@
<!-- Person Visualizer Section --> <!-- Person Visualizer Section -->
<div class="border-b border-gray-200 dark:border-gray-700 pb-4 mb-4 last:border-b-0 last:mb-0"> <div class="border-b border-gray-200 dark:border-gray-700 pb-4 mb-4 last:border-b-0 last:mb-0">
<div class="flex justify-between items-center cursor-pointer px-2 py-2 rounded hover:bg-gray-50 dark:hover:bg-gray-800 mb-3" onclick={() => personVisualizerExpanded = !personVisualizerExpanded}> <button
class="flex justify-between items-center cursor-pointer px-2 py-2 rounded hover:bg-gray-50 dark:hover:bg-gray-800 mb-3 w-full text-left border-none bg-none"
onclick={() => personVisualizerExpanded = !personVisualizerExpanded}
onkeydown={(e) => e.key === 'Enter' || e.key === ' ' ? (personVisualizerExpanded = !personVisualizerExpanded) : null}
aria-expanded={personVisualizerExpanded}
aria-controls="person-visualizer-content"
>
<h4 class="font-semibold text-gray-700 dark:text-gray-300 text-sm m-0">Person Visualizer</h4> <h4 class="font-semibold text-gray-700 dark:text-gray-300 text-sm m-0">Person Visualizer</h4>
<div class="pointer-events-none"> <div class="pointer-events-none">
{#if personVisualizerExpanded} {#if personVisualizerExpanded}
@ -361,10 +377,10 @@
<CaretDownOutline class="w-3 h-3" /> <CaretDownOutline class="w-3 h-3" />
{/if} {/if}
</div> </div>
</div> </button>
{#if personVisualizerExpanded} {#if personVisualizerExpanded}
<div class="space-y-3"> <div id="person-visualizer-content" class="space-y-3">
<!-- Show Person Nodes Toggle --> <!-- Show Person Nodes Toggle -->
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
@ -373,7 +389,9 @@
showPersonNodes = !showPersonNodes; showPersonNodes = !showPersonNodes;
onPersonSettingsChange(); onPersonSettingsChange();
}} }}
onkeydown={(e) => e.key === 'Enter' || e.key === ' ' ? (showPersonNodes = !showPersonNodes, onPersonSettingsChange()) : null}
class="px-2 py-1 border border-gray-300 dark:border-gray-700 rounded text-xs font-medium cursor-pointer transition min-w-[3rem] hover:bg-gray-200 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-primary-500 {showPersonNodes ? 'bg-blue-600 text-white border-blue-600 hover:bg-blue-700 dark:bg-blue-600 dark:text-white dark:border-blue-600 dark:hover:bg-blue-700' : 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300'}" class="px-2 py-1 border border-gray-300 dark:border-gray-700 rounded text-xs font-medium cursor-pointer transition min-w-[3rem] hover:bg-gray-200 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-primary-500 {showPersonNodes ? 'bg-blue-600 text-white border-blue-600 hover:bg-blue-700 dark:bg-blue-600 dark:text-white dark:border-blue-600 dark:hover:bg-blue-700' : 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300'}"
aria-pressed={showPersonNodes}
> >
{showPersonNodes ? 'ON' : 'OFF'} {showPersonNodes ? 'ON' : 'OFF'}
</button> </button>
@ -430,37 +448,30 @@
> >
{#each personAnchors as person} {#each personAnchors as person}
{@const isDisabled = disabledPersons.has(person.pubkey)} {@const isDisabled = disabledPersons.has(person.pubkey)}
<button <button
class="flex items-center gap-1 p-1 rounded w-full text-left border-none bg-none cursor-pointer transition hover:bg-black/5 dark:hover:bg-white/5 disabled:opacity-50" class="flex items-center gap-1 p-1 rounded w-full text-left border-none bg-none cursor-pointer transition hover:bg-black/5 dark:hover:bg-white/5 disabled:opacity-50"
onclick={() => { onclick={() => {
if (showPersonNodes) { if (showPersonNodes) {
onPersonToggle(person.pubkey); onPersonToggle(person.pubkey);
} }
}} }}
disabled={!showPersonNodes} onkeydown={(e) => e.key === 'Enter' || e.key === ' ' ? (showPersonNodes && onPersonToggle(person.pubkey)) : null}
title={!showPersonNodes ? 'Enable "Show Person Nodes" first' : isDisabled ? `Click to show ${person.displayName || person.pubkey}` : `Click to hide ${person.displayName || person.pubkey}`} disabled={!showPersonNodes}
> title={!showPersonNodes ? 'Enable "Show Person Nodes" first' : isDisabled ? `Click to show ${person.displayName || person.pubkey}` : `Click to hide ${person.displayName || person.pubkey}`}
<div class="flex items-center"> aria-pressed={!isDisabled}
<span >
class="inline-block w-3.5 h-3.5 rotate-45 border-2 border-white" <div class="flex items-center">
style="background-color: {person.isFromFollowList ? getEventKindColor(3) : '#10B981'}; opacity: {isDisabled ? 0.3 : 1};" <span
/> class="inline-block w-3.5 h-3.5 rotate-45 border-2 border-white"
</div> style="background-color: {person.isFromFollowList ? getEventKindColor(3) : '#10B981'}; opacity: {isDisabled ? 0.3 : 1};"
<span class="text-xs text-gray-700 dark:text-gray-300" style="opacity: {isDisabled ? 0.5 : 1};"> ></span>
{person.displayName || person.pubkey.slice(0, 8) + '...'} </div>
{#if !isDisabled} <span class="text-xs text-gray-700 dark:text-gray-300" style="opacity: {isDisabled ? 0.5 : 1};">
<span class="text-gray-500 dark:text-gray-400"> {person.displayName || person.pubkey.substring(0, 8)}
({person.signedByCount || 0}s/{person.referencedCount || 0}r) </span>
</span> </button>
{/if}
</span>
</button>
{/each} {/each}
</div> </div>
{:else if showPersonNodes}
<p class="text-xs text-gray-500 dark:text-gray-400">
No people found in the current events.
</p>
{/if} {/if}
</div> </div>
{/if} {/if}

81
src/lib/navigator/EventNetwork/Settings.svelte

@ -42,7 +42,13 @@
</script> </script>
<div class="leather-legend sm:!right-1 sm:!left-auto"> <div class="leather-legend sm:!right-1 sm:!left-auto">
<div class="flex items-center justify-between space-x-3 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md px-2 py-1 -mx-2 -my-1" onclick={toggle}> <button
class="flex items-center justify-between space-x-3 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md px-2 py-1 -mx-2 -my-1 w-full text-left border-none bg-none"
onclick={toggle}
onkeydown={(e) => e.key === 'Enter' || e.key === ' ' ? toggle() : null}
aria-expanded={expanded}
aria-controls="settings-content"
>
<h3 class="h-leather">Settings</h3> <h3 class="h-leather">Settings</h3>
<div class="pointer-events-none"> <div class="pointer-events-none">
{#if expanded} {#if expanded}
@ -51,10 +57,10 @@
<CaretDownOutline /> <CaretDownOutline />
{/if} {/if}
</div> </div>
</div> </button>
{#if expanded} {#if expanded}
<div class="space-y-4"> <div id="settings-content" class="space-y-4">
<span class="leather bg-transparent legend-text"> <span class="leather bg-transparent legend-text">
Showing {count} of {totalCount} events Showing {count} of {totalCount} events
</span> </span>
@ -63,9 +69,12 @@
<div <div
class="settings-section border-b border-gray-200 dark:border-gray-700 pb-4 mb-4 last:border-b-0 last:mb-0" class="settings-section border-b border-gray-200 dark:border-gray-700 pb-4 mb-4 last:border-b-0 last:mb-0"
> >
<div <button
class="settings-section-header flex justify-between items-center cursor-pointer py-2 mb-3 hover:bg-gray-50 dark:hover:bg-white/5 hover:rounded-md hover:px-2" class="settings-section-header flex justify-between items-center cursor-pointer py-2 mb-3 hover:bg-gray-50 dark:hover:bg-white/5 hover:rounded-md hover:px-2 w-full text-left border-none bg-none"
onclick={toggleEventTypes} onclick={toggleEventTypes}
onkeydown={(e) => e.key === 'Enter' || e.key === ' ' ? toggleEventTypes() : null}
aria-expanded={eventTypesExpanded}
aria-controls="event-types-content"
> >
<h4 class="settings-section-title font-semibold text-gray-700 dark:text-gray-300 m-0 text-sm"> <h4 class="settings-section-title font-semibold text-gray-700 dark:text-gray-300 m-0 text-sm">
Event Configuration Event Configuration
@ -77,21 +86,24 @@
<CaretDownOutline class="w-3 h-3" /> <CaretDownOutline class="w-3 h-3" />
{/if} {/if}
</div> </div>
</div> </button>
{#if eventTypesExpanded} {#if eventTypesExpanded}
<EventTypeConfig onReload={onupdate} {eventCounts} {profileStats} /> <div id="event-types-content">
<EventTypeConfig onReload={onupdate} {eventCounts} {profileStats} />
</div>
{/if} {/if}
</div> </div>
<!-- Visual Settings Section --> <!-- Visual Settings Section -->
<div <div
class="settings-section border-b border-gray-200 dark:border-gray-700 pb-4 mb-4 last:border-b-0 last:mb-0" class="settings-section border-b border-gray-200 dark:border-gray-700 pb-4 mb-4 last:border-b-0 last:mb-0"
> >
<div <button
class="settings-section-header flex justify-between items-center cursor-pointer py-2 mb-3 hover:bg-gray-50 dark:hover:bg-white/5 hover:rounded-md hover:px-2" class="settings-section-header flex justify-between items-center cursor-pointer py-2 mb-3 hover:bg-gray-50 dark:hover:bg-white/5 hover:rounded-md hover:px-2 w-full text-left border-none bg-none"
onclick={toggleVisualSettings} onclick={toggleVisualSettings}
onkeydown={(e) => e.key === 'Enter' || e.key === ' ' ? toggleVisualSettings() : null}
aria-expanded={visualSettingsExpanded}
aria-controls="visual-settings-content"
> >
<h4 class="settings-section-title font-semibold text-gray-700 dark:text-gray-300 m-0 text-sm"> <h4 class="settings-section-title font-semibold text-gray-700 dark:text-gray-300 m-0 text-sm">
Visual Settings Visual Settings
@ -103,32 +115,31 @@
<CaretDownOutline class="w-3 h-3" /> <CaretDownOutline class="w-3 h-3" />
{/if} {/if}
</div> </div>
</div> </button>
{#if visualSettingsExpanded} {#if visualSettingsExpanded}
<div id="visual-settings-content">
<div class="space-y-4"> <div class="space-y-4">
<div class="space-y-2"> <div class="space-y-2">
<label <label
class="leather bg-transparent legend-text flex items-center space-x-2" class="leather bg-transparent legend-text flex items-center space-x-2"
> >
<Toggle <Toggle
checked={starVisualization} checked={starVisualization}
onchange={(e: Event) => { onchange={(e: Event) => {
const target = e.target as HTMLInputElement; const target = e.target as HTMLInputElement;
starVisualization = target.checked; starVisualization = target.checked;
}} }}
class="text-xs" class="text-xs"
/> />
<span>Star Network View</span> <span>Star Network View</span>
</label> </label>
<p class="text-xs text-gray-500 dark:text-gray-400"> <p class="text-xs text-gray-500 dark:text-gray-400">
Toggle between star clusters (on) and linear sequence (off) Toggle between star clusters (on) and linear sequence (off)
visualization visualization
</p> </p>
</div>
</div>
</div> </div>
</div>
{/if} {/if}
</div> </div>
</div> </div>

8
src/lib/navigator/EventNetwork/TagTable.svelte

@ -12,12 +12,12 @@
}>(); }>();
// Computed property for unique tags // Computed property for unique tags
let uniqueTags = $derived(() => { let uniqueTags = $derived.by(() => {
const tagMap = new Map(); const tagMap = new Map<string, { value: string; count: number; firstEvent: string }>();
events.forEach(event => { events.forEach((event: NDKEvent) => {
const tags = event.tags || []; const tags = event.tags || [];
tags.forEach(tag => { tags.forEach((tag: string[]) => {
if (tag[0] === selectedTagType) { if (tag[0] === selectedTagType) {
const tagValue = tag[1]; const tagValue = tag[1];
const count = tagMap.get(tagValue)?.count || 0; const count = tagMap.get(tagValue)?.count || 0;

6
src/lib/navigator/EventNetwork/utils/personNetworkBuilder.ts

@ -293,13 +293,15 @@ export function createPersonLinks(
connectionType = 'referenced'; connectionType = 'referenced';
} }
return { const link: PersonLink = {
source: anchor, source: anchor,
target: node, target: node,
isSequential: false, isSequential: false,
connectionType, connectionType,
}; };
}).filter(Boolean); // Remove undefineds
return link;
}).filter((link): link is PersonLink => link !== undefined); // Remove undefineds and type guard
}); });
debug("Created person links", { linkCount: links.length }); debug("Created person links", { linkCount: links.length });

2
src/lib/stores/index.ts

@ -1,2 +0,0 @@
export * from './relayStore';
export * from './displayLimits';

2
src/lib/utils/nostr_identifiers.ts

@ -1,9 +1,9 @@
import { VALIDATION } from './search_constants'; import { VALIDATION } from './search_constants';
import type { NostrEventId } from './nostr_identifiers';
/** /**
* Nostr identifier types * Nostr identifier types
*/ */
export type NostrEventId = string; // 64-character hex string
export type NostrCoordinate = string; // kind:pubkey:d-tag format export type NostrCoordinate = string; // kind:pubkey:d-tag format
export type NostrIdentifier = NostrEventId | NostrCoordinate; export type NostrIdentifier = NostrEventId | NostrCoordinate;

4
src/lib/utils/websocket_utils.ts

@ -27,7 +27,7 @@ export async function fetchNostrEvent(filter: NostrFilter): Promise<NostrEvent>
const ws = await WebSocketPool.instance.acquire("wss://thecitadel.nostr1.com"); const ws = await WebSocketPool.instance.acquire("wss://thecitadel.nostr1.com");
const subId = crypto.randomUUID(); const subId = crypto.randomUUID();
const res = new Promise<NostrEvent | null>((resolve, reject) => { const res = new Promise<NostrEvent>((resolve, reject) => {
ws.addEventListener("message", (ev) => { ws.addEventListener("message", (ev) => {
const data = JSON.parse(ev.data); const data = JSON.parse(ev.data);
@ -42,7 +42,7 @@ export async function fetchNostrEvent(filter: NostrFilter): Promise<NostrEvent>
reject(new Error(`[WebSocket Utils]: Subscription ${subId} closed`)); reject(new Error(`[WebSocket Utils]: Subscription ${subId} closed`));
break; break;
case "EOSE": case "EOSE":
resolve(null); reject(new Error(`[WebSocket Utils]: Event not found`));
break; break;
} }

3
src/routes/publication/[type]/[identifier]/+layout.server.ts

@ -1,6 +1,7 @@
import { error } from "@sveltejs/kit"; import { error } from "@sveltejs/kit";
import type { LayoutServerLoad } from "./$types"; import type { LayoutServerLoad } from "./$types";
import { fetchEventByDTag, fetchEventById, fetchEventByNaddr, fetchEventByNevent, NostrEvent } from "../../../../lib/utils/websocket_utils.ts"; import { fetchEventByDTag, fetchEventById, fetchEventByNaddr, fetchEventByNevent } from "../../../../lib/utils/websocket_utils.ts";
import type { NostrEvent } from "../../../../lib/utils/websocket_utils.ts";
export const load: LayoutServerLoad = async ({ params, url }) => { export const load: LayoutServerLoad = async ({ params, url }) => {
const { type, identifier } = params; const { type, identifier } = params;

3
src/routes/publication/[type]/[identifier]/+page.server.ts

@ -1,6 +1,7 @@
import { error } from "@sveltejs/kit"; import { error } from "@sveltejs/kit";
import type { PageServerLoad } from "./$types"; import type { PageServerLoad } from "./$types";
import { fetchEventByDTag, fetchEventById, fetchEventByNaddr, fetchEventByNevent, NostrEvent } from "../../../../lib/utils/websocket_utils.ts"; import { fetchEventByDTag, fetchEventById, fetchEventByNaddr, fetchEventByNevent } from "../../../../lib/utils/websocket_utils.ts";
import type { NostrEvent } from "../../../../lib/utils/websocket_utils.ts";
export const load: PageServerLoad = async ({ params }) => { export const load: PageServerLoad = async ({ params }) => {
const { type, identifier } = params; const { type, identifier } = params;

Loading…
Cancel
Save