|
|
|
@ -93,7 +93,7 @@ |
|
|
|
if (isLoggedIn) { |
|
|
|
if (isLoggedIn) { |
|
|
|
isBookmarked(post.id).then(b => { |
|
|
|
isBookmarked(post.id).then(b => { |
|
|
|
if (!cancelled) { |
|
|
|
if (!cancelled) { |
|
|
|
bookmarked = b; |
|
|
|
bookmarked = b; |
|
|
|
} |
|
|
|
} |
|
|
|
}); |
|
|
|
}); |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
@ -462,7 +462,7 @@ |
|
|
|
const urlWithoutProtocol = normalized.replace(/^https?:\/\//i, ''); |
|
|
|
const urlWithoutProtocol = normalized.replace(/^https?:\/\//i, ''); |
|
|
|
if (content.includes(normalized.toLowerCase()) || content.includes(urlWithoutProtocol.toLowerCase())) { |
|
|
|
if (content.includes(normalized.toLowerCase()) || content.includes(urlWithoutProtocol.toLowerCase())) { |
|
|
|
return true; |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Check if URL appears in markdown image syntax  |
|
|
|
// Check if URL appears in markdown image syntax  |
|
|
|
const markdownImageRegex = /!\[.*?\]\((.*?)\)/gi; |
|
|
|
const markdownImageRegex = /!\[.*?\]\((.*?)\)/gi; |
|
|
|
@ -471,7 +471,7 @@ |
|
|
|
const markdownUrl = normalizeUrl(match[1]); |
|
|
|
const markdownUrl = normalizeUrl(match[1]); |
|
|
|
if (markdownUrl === normalized) { |
|
|
|
if (markdownUrl === normalized) { |
|
|
|
return true; |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Check if URL appears in HTML img/video/audio tags |
|
|
|
// Check if URL appears in HTML img/video/audio tags |
|
|
|
@ -480,7 +480,7 @@ |
|
|
|
const htmlUrl = normalizeUrl(match[2]); |
|
|
|
const htmlUrl = normalizeUrl(match[2]); |
|
|
|
if (htmlUrl === normalized) { |
|
|
|
if (htmlUrl === normalized) { |
|
|
|
return true; |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return false; |
|
|
|
return false; |
|
|
|
@ -493,7 +493,7 @@ |
|
|
|
} catch { |
|
|
|
} catch { |
|
|
|
return url; |
|
|
|
return url; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Extract media URLs from event tags (image, imeta, file) - for feed view only |
|
|
|
// Extract media URLs from event tags (image, imeta, file) - for feed view only |
|
|
|
// Excludes URLs that are already in the content |
|
|
|
// Excludes URLs that are already in the content |
|
|
|
@ -509,7 +509,7 @@ |
|
|
|
urls.push(imageTag[1]); |
|
|
|
urls.push(imageTag[1]); |
|
|
|
seen.add(normalized); |
|
|
|
seen.add(normalized); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 2. imeta tags (NIP-92) |
|
|
|
// 2. imeta tags (NIP-92) |
|
|
|
for (const tag of post.tags) { |
|
|
|
for (const tag of post.tags) { |
|
|
|
@ -536,7 +536,7 @@ |
|
|
|
if (!seen.has(normalized) && !isUrlInContent(tag[1])) { |
|
|
|
if (!seen.has(normalized) && !isUrlInContent(tag[1])) { |
|
|
|
urls.push(tag[1]); |
|
|
|
urls.push(tag[1]); |
|
|
|
seen.add(normalized); |
|
|
|
seen.add(normalized); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -590,11 +590,11 @@ |
|
|
|
|
|
|
|
|
|
|
|
<MetadataCard event={post} hideTitle={true} hideImageIfInMedia={true} /> |
|
|
|
<MetadataCard event={post} hideTitle={true} hideImageIfInMedia={true} /> |
|
|
|
|
|
|
|
|
|
|
|
{@const title = getTitle()} |
|
|
|
{@const title = getTitle()} |
|
|
|
{#if title && title !== 'Untitled'} |
|
|
|
{#if title && title !== 'Untitled'} |
|
|
|
<h2 class="post-title font-bold mb-4 text-fog-text dark:text-fog-dark-text" style="font-size: 1.5em;"> |
|
|
|
<h2 class="post-title font-bold mb-4 text-fog-text dark:text-fog-dark-text" style="font-size: 1.5em;"> |
|
|
|
{title} |
|
|
|
{title} |
|
|
|
</h2> |
|
|
|
</h2> |
|
|
|
{/if} |
|
|
|
{/if} |
|
|
|
|
|
|
|
|
|
|
|
<div class="post-header flex flex-col gap-2 mb-2"> |
|
|
|
<div class="post-header flex flex-col gap-2 mb-2"> |
|
|
|
@ -608,21 +608,21 @@ |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="flex items-center gap-2 flex-nowrap"> |
|
|
|
<div class="flex items-center gap-2 flex-nowrap"> |
|
|
|
<div class="flex-shrink-1 min-w-0"> |
|
|
|
<div class="flex-shrink-1 min-w-0"> |
|
|
|
<ProfileBadge pubkey={post.pubkey} /> |
|
|
|
<ProfileBadge pubkey={post.pubkey} /> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<span class="text-fog-text-light dark:text-fog-dark-text-light flex-shrink-0 whitespace-nowrap" style="font-size: 0.75em;">{getRelativeTime()}</span> |
|
|
|
<span class="text-fog-text-light dark:text-fog-dark-text-light flex-shrink-0 whitespace-nowrap" style="font-size: 0.75em;">{getRelativeTime()}</span> |
|
|
|
{#if getClientName()} |
|
|
|
{#if getClientName()} |
|
|
|
<span class="text-fog-text-light dark:text-fog-dark-text-light flex-shrink-0" style="font-size: 0.75em;">via {getClientName()}</span> |
|
|
|
<span class="text-fog-text-light dark:text-fog-dark-text-light flex-shrink-0" style="font-size: 0.75em;">via {getClientName()}</span> |
|
|
|
{/if} |
|
|
|
{/if} |
|
|
|
{#if post.kind === KIND.DISCUSSION_THREAD} |
|
|
|
{#if post.kind === KIND.DISCUSSION_THREAD} |
|
|
|
{@const topics = getTopics()} |
|
|
|
{@const topics = getTopics()} |
|
|
|
{#if topics.length === 0} |
|
|
|
{#if topics.length === 0} |
|
|
|
<a href="/topics/General" class="topic-badge px-2 py-0.5 rounded bg-fog-border dark:bg-fog-dark-border text-fog-text-light dark:text-fog-dark-text-light hover:underline" style="font-size: 0.75em;">General</a> |
|
|
|
<a href="/topics/General" class="topic-badge px-2 py-0.5 rounded bg-fog-border dark:bg-fog-dark-border text-fog-text-light dark:text-fog-dark-text-light hover:underline" style="font-size: 0.75em;">General</a> |
|
|
|
{:else} |
|
|
|
{:else} |
|
|
|
{#each topics.slice(0, 3) as topic} |
|
|
|
{#each topics.slice(0, 3) as topic} |
|
|
|
<a href="/topics/{topic}" class="topic-badge px-2 py-0.5 rounded bg-fog-border dark:bg-fog-dark-border text-fog-text-light dark:text-fog-dark-text-light hover:underline" style="font-size: 0.75em;">{topic}</a> |
|
|
|
<a href="/topics/{topic}" class="topic-badge px-2 py-0.5 rounded bg-fog-border dark:bg-fog-dark-border text-fog-text-light dark:text-fog-dark-text-light hover:underline" style="font-size: 0.75em;">{topic}</a> |
|
|
|
{/each} |
|
|
|
{/each} |
|
|
|
{/if} |
|
|
|
{/if} |
|
|
|
{/if} |
|
|
|
{/if} |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<hr class="post-header-divider" /> |
|
|
|
<hr class="post-header-divider" /> |
|
|
|
@ -636,7 +636,7 @@ |
|
|
|
<div class="post-actions flex flex-wrap items-center gap-2 sm:gap-4"> |
|
|
|
<div class="post-actions flex flex-wrap items-center gap-2 sm:gap-4"> |
|
|
|
<FeedReactionButtons event={post} preloadedReactions={preloadedReactions} /> |
|
|
|
<FeedReactionButtons event={post} preloadedReactions={preloadedReactions} /> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
{:else} |
|
|
|
{:else} |
|
|
|
<!-- Feed view: plaintext only, no profile pics, media as URLs --> |
|
|
|
<!-- Feed view: plaintext only, no profile pics, media as URLs --> |
|
|
|
<div class="post-header flex flex-col gap-2 mb-2"> |
|
|
|
<div class="post-header flex flex-col gap-2 mb-2"> |
|
|
|
<div class="flex items-center gap-2 flex-nowrap"> |
|
|
|
<div class="flex items-center gap-2 flex-nowrap"> |
|
|
|
@ -651,7 +651,7 @@ |
|
|
|
{@const topics = getTopics()} |
|
|
|
{@const topics = getTopics()} |
|
|
|
{#if topics.length === 0} |
|
|
|
{#if topics.length === 0} |
|
|
|
<a href="/topics/General" class="topic-badge px-2 py-0.5 rounded bg-fog-border dark:bg-fog-dark-border text-fog-text-light dark:text-fog-dark-text-light hover:underline" style="font-size: 0.75em;">General</a> |
|
|
|
<a href="/topics/General" class="topic-badge px-2 py-0.5 rounded bg-fog-border dark:bg-fog-dark-border text-fog-text-light dark:text-fog-dark-text-light hover:underline" style="font-size: 0.75em;">General</a> |
|
|
|
{:else} |
|
|
|
{:else} |
|
|
|
{#each topics.slice(0, 3) as topic} |
|
|
|
{#each topics.slice(0, 3) as topic} |
|
|
|
<a href="/topics/{topic}" class="topic-badge px-2 py-0.5 rounded bg-fog-border dark:bg-fog-dark-border text-fog-text-light dark:text-fog-dark-text-light hover:underline" style="font-size: 0.75em;">{topic}</a> |
|
|
|
<a href="/topics/{topic}" class="topic-badge px-2 py-0.5 rounded bg-fog-border dark:bg-fog-dark-border text-fog-text-light dark:text-fog-dark-text-light hover:underline" style="font-size: 0.75em;">{topic}</a> |
|
|
|
{/each} |
|
|
|
{/each} |
|
|
|
@ -738,21 +738,21 @@ |
|
|
|
{url} |
|
|
|
{url} |
|
|
|
</button> |
|
|
|
</button> |
|
|
|
{/each} |
|
|
|
{/each} |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
{/if} |
|
|
|
{/if} |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
{#if !fullView && shouldCollapse} |
|
|
|
{#if !fullView && shouldCollapse} |
|
|
|
<div class="show-more-container"> |
|
|
|
<div class="show-more-container"> |
|
|
|
<button |
|
|
|
<button |
|
|
|
onclick={toggleExpand} |
|
|
|
onclick={toggleExpand} |
|
|
|
class="show-more-btn text-fog-accent dark:text-fog-dark-accent hover:underline" |
|
|
|
class="show-more-btn text-fog-accent dark:text-fog-dark-accent hover:underline" |
|
|
|
style="font-size: 0.875em;" |
|
|
|
style="font-size: 0.875em;" |
|
|
|
> |
|
|
|
> |
|
|
|
{isExpanded ? 'Show Less' : 'Show More'} |
|
|
|
{isExpanded ? 'Show Less' : 'Show More'} |
|
|
|
</button> |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
{/if} |
|
|
|
{/if} |
|
|
|
|
|
|
|
|
|
|
|
{#if !fullView} |
|
|
|
{#if !fullView} |
|
|
|
<div class="feed-card-footer flex items-center justify-between"> |
|
|
|
<div class="feed-card-footer flex items-center justify-between"> |
|
|
|
@ -771,10 +771,10 @@ |
|
|
|
{/if} |
|
|
|
{/if} |
|
|
|
|
|
|
|
|
|
|
|
{#if fullView} |
|
|
|
{#if fullView} |
|
|
|
<div class="kind-badge"> |
|
|
|
<div class="kind-badge"> |
|
|
|
<span class="kind-number">{getKindInfo(post.kind).number}</span> |
|
|
|
<span class="kind-number">{getKindInfo(post.kind).number}</span> |
|
|
|
<span class="kind-description">{getKindInfo(post.kind).description}</span> |
|
|
|
<span class="kind-description">{getKindInfo(post.kind).description}</span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
{/if} |
|
|
|
{/if} |
|
|
|
</article> |
|
|
|
</article> |
|
|
|
|
|
|
|
|
|
|
|
|