Browse Source

Added hashtags and interaction counters, changed layouts

master
Nuša Pukšič 10 months ago
parent
commit
5283ebf1a7
  1. 12
      src/app.css
  2. 2
      src/lib/components/Login.svelte
  3. 68
      src/lib/components/Publication.svelte
  4. 14
      src/lib/components/blog/BlogHeader.svelte
  5. 27
      src/lib/components/util/ArticleNav.svelte
  6. 4
      src/lib/components/util/CardActions.svelte
  7. 42
      src/lib/components/util/Details.svelte
  8. 2
      src/lib/components/util/InlineProfile.svelte
  9. 25
      src/lib/components/util/Interactions.svelte
  10. 4
      src/lib/components/util/TocToggle.svelte
  11. 2
      src/lib/stores.ts
  12. 6
      src/routes/+layout.svelte
  13. 6
      src/styles/base.css
  14. 3
      src/styles/publications.css

12
src/app.css

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
@import './styles/base.css';
@import 'styles/scrollbar.css';
@import './styles/scrollbar.css';
@import './styles/publications.css';
@import './styles/visualize.css';
@ -58,10 +58,13 @@ @@ -58,10 +58,13 @@
@apply max-w-full flex;
}
main.publication {
@apply mt-[70px];
}
/* To scroll columns independently */
main.publication.blog {
@apply w-full sm:w-auto min-h-full;
max-height: calc(100vh - 146px);
}
main.main-leather,
@ -259,6 +262,10 @@ @@ -259,6 +262,10 @@
@apply max-h-72;
}
.tags span {
@apply bg-primary-50 text-primary-800 text-sm font-medium me-2 px-2.5 py-0.5 rounded-sm dark:bg-primary-900 dark:text-primary-200;
}
}
@layer components {
@ -379,7 +386,6 @@ @@ -379,7 +386,6 @@
}
.line-ellipsis {
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
}

2
src/lib/components/Login.svelte

@ -46,7 +46,7 @@ @@ -46,7 +46,7 @@
{#if $ndkSignedIn}
<Profile pubkey={$activePubkey} isNav={true} />
{:else}
<Avatar rounded class='h-6 w-6 cursor-pointer' id='avatar' />
<Avatar rounded class='h-6 w-6 cursor-pointer bg-transparent' id='avatar' />
<Popover
class='popover-leather w-fit'
placement='bottom'

68
src/lib/components/Publication.svelte

@ -1,21 +1,24 @@ @@ -1,21 +1,24 @@
<script lang="ts">
import {
Button, Card, Img,
Button, Card,
Sidebar,
SidebarGroup,
SidebarItem,
SidebarWrapper,
Skeleton,
TextPlaceholder,
Tooltip,
Tooltip
} from "flowbite-svelte";
import { getContext, onMount } from "svelte";
import { HeartOutline } from 'flowbite-svelte-icons';
import { getContext, onDestroy, onMount } from "svelte";
import type { NDKEvent } from "@nostr-dev-kit/ndk";
import PublicationSection from "./PublicationSection.svelte";
import type { PublicationTree } from "$lib/data_structures/publication_tree";
import Details from "$components/util/Details.svelte";
import { publicationColumnVisibility } from "$lib/stores";
import BlogHeader from "$components/blog/BlogHeader.svelte";
import Interactions from "$components/util/Interactions.svelte";
import ZapOutline from "$components/util/ZapOutline.svelte";
let { rootAddress, publicationType, indexEvent } = $props<{
rootAddress: string,
@ -83,14 +86,14 @@ @@ -83,14 +86,14 @@
}
function isSocialActive() {
return $publicationColumnVisibility.social;
return $publicationColumnVisibility.discussion;
}
function loadBlog(rootId: string) {
// depending on the size of the screen, also toggle blog list & social visibility
if (window.innerWidth < 1024) {
$publicationColumnVisibility.blog = false;
$publicationColumnVisibility.social = false;
$publicationColumnVisibility.discussion = false;
}
$publicationColumnVisibility.inner = true;
currentBlog = rootId;
@ -98,9 +101,8 @@ @@ -98,9 +101,8 @@
currentBlogEvent = leaves.find(i => i.tagAddress() === currentBlog) ?? null;
}
function showBlogHeaderOnMobile() {
return (currentBlog && currentBlogEvent && window.innerWidth < 1024);
return (currentBlog && currentBlogEvent && window.innerWidth < 1140);
}
onMount(() => {
@ -119,6 +121,18 @@ @@ -119,6 +121,18 @@
};
});
onDestroy(() => {
// reset visibility
$publicationColumnVisibility = {
toc: false,
blog: true,
main: true,
inner: true,
discussion: false,
editing: false
};
})
</script>
{#if isDefaultVisible()}
@ -156,15 +170,23 @@ @@ -156,15 +170,23 @@
{#if isInnerActive() }
{#key currentBlog }
<div class="flex flex-col p-4 max-w-3xl overflow-auto flex-grow-2">
<div class="flex flex-col p-4 max-w-3xl overflow-auto flex-grow-2 max-h-[calc(100vh-146px)] sticky top-[146px]">
{#each leaves as leaf, i}
{#if leaf.tagAddress() === currentBlog}
<div class="card-leather bg-highlight dark:bg-primary-800 p-4 mb-4 rounded-lg border">
<Details event={leaf} />
</div>
<PublicationSection
rootAddress={rootAddress}
leaves={leaves}
address={leaf.tagAddress()}
ref={(el) => setLastElementRef(el, i)}
/>
<Card class="ArticleBox card-leather min-w-full grid mt-4">
<Interactions rootId={currentBlog} />
</Card>
{/if}
{/each}
</div>
@ -172,7 +194,7 @@ @@ -172,7 +194,7 @@
{/if}
{#if isSocialActive() }
<div class="flex flex-col p-4 space-y-4 max-w-xl overflow-auto flex-grow-1">
<div class="flex flex-col p-4 max-w-3xl overflow-auto flex-grow-2 h-[calc(100vh-146px)] sticky top-[146px] space-y-4">
{#if showBlogHeaderOnMobile()}
<BlogHeader
rootId={currentBlog}
@ -182,12 +204,34 @@ @@ -182,12 +204,34 @@
/>
{/if}
<div class="flex flex-col w-full">
<div class="flex flex-col w-full space-y-4">
<Card class="ArticleBox card-leather w-full grid max-w-xl">
<div class='space-y-2'>
<div class="flex flex-col my-2">
<span>Unknown</span>
<span class='text-gray-500'>1.1.1970</span>
</div>
<div class='flex flex-col flex-grow space-y-4'>
This is a placeholder comment...
This is a very intelligent comment placeholder that applies to all the content equally well.
</div>
</Card>
<Card class="ArticleBox card-leather w-full grid grid-cols-2 max-w-xl">
<div class="flex flex-col my-2">
<span>Unknown</span>
<span class='text-gray-500'>1.1.1970</span>
</div>
<div class='flex flex-col flex-grow items-end justify-center'>
<ZapOutline ></ZapOutline>
</div>
</Card>
<Card class="ArticleBox card-leather w-full grid grid-cols-2 max-w-xl">
<div class="flex flex-col my-2">
<span>Unknown</span>
<span class='text-gray-500'>1.1.1970</span>
</div>
<div class='flex flex-col flex-grow items-end justify-center'>
<HeartOutline />
</div>
</Card>
</div>

14
src/lib/components/blog/BlogHeader.svelte

@ -5,6 +5,7 @@ @@ -5,6 +5,7 @@
import InlineProfile from "$components/util/InlineProfile.svelte";
import Interactions from "$components/util/Interactions.svelte";
import { quintOut } from "svelte/easing";
import CardActions from "$components/util/CardActions.svelte";
const { rootId, event, onBlogUpdate, active = true } = $props<{ rootId: string, event: NDKEvent, onBlogUpdate?: any, active: boolean }>();
@ -12,6 +13,7 @@ @@ -12,6 +13,7 @@
let author: string = $derived(event.getMatchingTags('author')[0]?.[1] ?? 'unknown');
let image: string = $derived(event.getMatchingTags('image')[0]?.[1] ?? null);
let authorPubkey: string = $derived(event.getMatchingTags('p')[0]?.[1] ?? null);
let hashtags: string = $derived(event.getMatchingTags('t') ?? null);
function publishedAt() {
const date = event.created_at ? new Date(event.created_at * 1000) : '';
@ -35,9 +37,12 @@ @@ -35,9 +37,12 @@
<Card class="ArticleBox card-leather w-full grid max-w-xl {active ? 'active' : ''}">
<div class='space-y-4'>
<div class="flex flex-row justify-between my-2">
<div class="flex flex-col">
<InlineProfile pubkey={authorPubkey} title={author} />
<span class='text-gray-500'>{publishedAt()}</span>
</div>
<CardActions event={event} />
</div>
{#if image && active}
<div class="ArticleBoxImage flex col justify-center"
in:scale={{ start: 0.8, duration: 500, delay: 100, easing: quintOut }}
@ -49,11 +54,16 @@ @@ -49,11 +54,16 @@
<button onclick={() => showBlog()} class='text-left'>
<h2 class='text-lg font-bold line-clamp-2' title="{title}">{title}</h2>
</button>
{#if hashtags}
<div class="tags">
{#each hashtags as tag}
<span>{tag}</span>
{/each}
</div>
{/if}
</div>
{#if active}
<div class='flex flex-row'>
<Interactions rootId={rootId} event={event} />
</div>
{/if}
</div>
</Card>

27
src/lib/components/util/ArticleNav.svelte

@ -22,13 +22,16 @@ @@ -22,13 +22,16 @@
let pubkey: string = $derived(indexEvent.getMatchingTags('p')[0]?.[1] ?? null);
// Function to toggle column visibility
function toggleColumn(column: 'blog'|'inner'|'social') {
function toggleColumn(column: 'blog'|'inner'|'discussion') {
publicationColumnVisibility.update(store => {
store[column] = !store[column]; // Toggle true/false
if (window.innerWidth < 1400 && $publicationColumnVisibility.social) {
if (window.innerWidth < 1400 && column === 'discussion' && $publicationColumnVisibility.discussion) {
$publicationColumnVisibility.blog = false;
}
if (window.innerWidth < 1200) {
if (window.innerWidth < 1400 && column === 'blog' && $publicationColumnVisibility.blog) {
$publicationColumnVisibility.discussion = false;
}
if (window.innerWidth < 980) {
$publicationColumnVisibility.inner = false;
}
return { ...store }; // Ensure reactivity
@ -36,23 +39,23 @@ @@ -36,23 +39,23 @@
}
function backToMain() {
if ($publicationColumnVisibility.social) {
if ($publicationColumnVisibility.discussion) {
$publicationColumnVisibility.inner = true;
$publicationColumnVisibility.social = false;
$publicationColumnVisibility.discussion = false;
} else {
$publicationColumnVisibility.blog = true;
$publicationColumnVisibility.inner = false;
$publicationColumnVisibility.social = false;
$publicationColumnVisibility.discussion = false;
}
}
</script>
<nav class="Navbar navbar-leather flex sticky top-[76px] w-full min-h-[70px] px-2 sm:px-4 py-2.5 z-10">
<nav class="Navbar navbar-leather flex fixed top-[76px] w-full min-h-[70px] px-2 sm:px-4 py-2.5 z-10">
<div class="mx-auto flex space-x-2 container">
<div class="flex items-center space-x-2 md:min-w-52 min-w-8">
{#if publicationType === 'blog'}
{#if $publicationColumnVisibility.inner || $publicationColumnVisibility.social}
{#if $publicationColumnVisibility.inner || $publicationColumnVisibility.discussion}
<Button class='btn-leather !w-auto sm:hidden' outline={true} onclick={backToMain}>
<CaretLeftOutline class="!fill-none inline mr-1" /><span class="hidden sm:inline">Back</span>
</Button>
@ -66,18 +69,18 @@ @@ -66,18 +69,18 @@
{/if}
</div>
<div class="flex flex-grow text justify-center items-center">
<p class="line-ellipsis"><b>{title}</b> <span>by <InlineProfile pubkey={pubkey} title={author} /></span></p>
<p class="max-w-[60vw] line-ellipsis"><b class="text-nowrap">{title}</b> <span class="whitespace-nowrap">by <InlineProfile pubkey={pubkey} title={author} /></span></p>
</div>
<div class="flex items-center space-x-2 md:min-w-52 min-w-8">
{#if publicationType === 'blog'}
{#if $publicationColumnVisibility.inner || $publicationColumnVisibility.social}
{#if $publicationColumnVisibility.inner || $publicationColumnVisibility.discussion}
<Button class='btn-leather !w-auto hidden sm:flex' outline={true} onclick={backToMain}>
<CaretLeftOutline class="!fill-none inline mr-1" /><span class="hidden sm:inline">Back</span>
</Button>
{/if}
{#if $publicationColumnVisibility.inner}
<Button class='btn-leather !w-auto' outline={true} onclick={() => toggleColumn('social')}>
<GlobeOutline class="!fill-none inline mr-1" /><span class="hidden sm:inline">Social</span>
<Button class='btn-leather !w-auto' outline={true} onclick={() => toggleColumn('discussion')}>
<GlobeOutline class="!fill-none inline mr-1" /><span class="hidden sm:inline">Discussion</span>
</Button>
{/if}
{/if}

4
src/lib/components/util/CardActions.svelte

@ -73,7 +73,7 @@ @@ -73,7 +73,7 @@
</script>
<div class="group" role="group" onmouseenter={openPopover}>
<div class="group bg-highlight dark:bg-primary-1000 rounded" role="group" onmouseenter={openPopover}>
<!-- Main button -->
<Button type="button"
id="dots-{event.id}"
@ -134,6 +134,6 @@ @@ -134,6 +134,6 @@
</Modal>
<!-- Event details -->
<Modal class='modal-leather' title='Publication details' bind:open={detailsModalOpen} autoclose outsideclose size='sm'>
<Details event={event} />
<Details event={event} isModal={true} />
</Modal>
</div>

42
src/lib/components/util/Details.svelte

@ -1,7 +1,9 @@ @@ -1,7 +1,9 @@
<script lang="ts">
import InlineProfile from "$components/util/InlineProfile.svelte";
import CardActions from "$components/util/CardActions.svelte";
import Interactions from "$components/util/Interactions.svelte";
let { event } = $props();
let { event, isModal = false } = $props();
let title: string = $derived(event.getMatchingTags('title')[0]?.[1]);
let author: string = $derived(event.getMatchingTags('author')[0]?.[1] ?? 'unknown');
@ -14,25 +16,37 @@ @@ -14,25 +16,37 @@
let source: string = $derived(event.getMatchingTags('source')[0]?.[1] ?? null);
let publisher: string = $derived(event.getMatchingTags('published_by')[0]?.[1] ?? null);
let identifier: string = $derived(event.getMatchingTags('i')[0]?.[1] ?? null);
let hashtags: [] = $derived(event.getMatchingTags('t') ?? []);
let rootId: string = $derived(event.getMatchingTags('d')[0]?.[1] ?? null);
</script>
<div class="flex flex-row md:space-x-4 max-sm:flex-wrap">
<div class="flex flex-row relative">
<div class="flex-grow grid grid-cols-1 md:grid-cols-[auto_1fr] gap-4 items-center">
{#if image}
<div class="flex col">
<img class="md:max-w-48 max-sm:w-full object-cover" alt={title} src={image} />
<div>
<img class="w-full md:max-w-48 object-contain rounded" alt={title} src={image} />
</div>
{/if}
<div class="flex flex-col col space-y-5 justify-center align-middle">
<h1 class="text-3xl font-bold mt-5">{title}</h1>
<h2 class="text-base font-bold">by
<div class="space-y-4">
<h1 class="text-3xl font-bold">{title}</h1>
<h2 class="text-base font-bold">
by
{#if originalAuthor !== null}
<InlineProfile pubkey={originalAuthor} title={author} />
{:else}
{author}
{/if}
</h2>
<h4 class='text-base font-thin mt-2'>Version: {version}</h4>
<h4 class="text-base font-thin">Version: {version}</h4>
</div>
</div>
<div class="absolute right-0 sm:relative sm:flex sm:flex-col space-y-4">
{#if !isModal}
<CardActions event={event}></CardActions>
{/if}
</div>
</div>
@ -42,6 +56,14 @@ @@ -42,6 +56,14 @@
</div>
{/if}
{#if hashtags.length}
<div class="tags my-2">
{#each hashtags as tag}
<span class="text-sm">#{tag[1]}</span>
{/each}
</div>
{/if}
<div class="flex flex-row my-4">
<h4 class='text-base font-normal mt-2'>Index author: <InlineProfile pubkey={event.pubkey} /></h4>
</div>
@ -64,3 +86,7 @@ @@ -64,3 +86,7 @@
{/if}
</div>
{#if !isModal}
<Interactions event={event} rootId={rootId} direction="row"/>
{/if}

2
src/lib/components/util/InlineProfile.svelte

@ -49,7 +49,7 @@ @@ -49,7 +49,7 @@
{:else if npub }
<a href='{externalProfileDestination}{npub}' title={title ?? username} target='_blank'>
<Avatar rounded
class='h-6 w-6 mx-1 cursor-pointer inline'
class='h-6 w-6 mx-1 cursor-pointer inline bg-transparent'
src={pfp}
alt={username} />
<span class='underline'>{username ?? shortenNpub(npub)}</span>

25
src/lib/components/util/Interactions.svelte

@ -1,11 +1,15 @@ @@ -1,11 +1,15 @@
<script lang="ts">
import {
Button
} from "flowbite-svelte";
import { HeartOutline, FilePenOutline, AnnotationOutline } from 'flowbite-svelte-icons';
import ZapOutline from "$components/util/ZapOutline.svelte";
import type { NDKEvent } from "@nostr-dev-kit/ndk";
import { onMount } from "svelte";
import { ndkInstance } from '$lib/ndk';
import { publicationColumnVisibility } from "$lib/stores";
const { rootId, event } = $props<{ rootId: string, event: NDKEvent }>();
const { rootId, event, direction = 'row' } = $props<{ rootId: string, event?: NDKEvent, direction?: string }>();
// Reactive arrays to hold incoming events
let likes: NDKEvent[] = [];
@ -28,7 +32,7 @@ @@ -28,7 +32,7 @@
function subscribeCount(kind: number, targetArray: NDKEvent[]) {
const sub = $ndkInstance.subscribe({
kinds: [kind],
'#a': [rootId]
'#a': [rootId] // Will this work?
});
@ -52,11 +56,18 @@ @@ -52,11 +56,18 @@
subs.push(subscribeCount(1, comments)); // comments (Text Notes)
});
function showSocial() {
$publicationColumnVisibility.discussion = true;
if (window.innerWidth < 1400) {
$publicationColumnVisibility.blog = false;
$publicationColumnVisibility.inner = false;
}
}
</script>
<div class='InteractiveMenu flex flex-row justify-around align-middle text-primary-600 dark:text-gray-500'>
<div class='flex flex-row shrink-0 min-w-11'><HeartOutline size="lg" /><span>{likeCount}</span></div>
<div class='flex flex-row shrink-0 min-w-11'><ZapOutline /><span>{zapCount}</span></div>
<div class='flex flex-row shrink-0 min-w-11'><FilePenOutline size="lg"/><span>{highlightCount}</span></div>
<div class='flex flex-row shrink-0 min-w-11'><AnnotationOutline size="lg"/><span>{commentCount}</span></div>
<div class='InteractiveMenu flex flex-{direction} justify-around align-middle text-primary-700 dark:text-gray-500'>
<Button color="none" class='flex flex-{direction} shrink-0 md:min-w-11 min-h-11 items-center p-0'><HeartOutline class="mx-2" size="lg" /><span>{likeCount}</span></Button>
<Button color="none" class='flex flex-{direction} shrink-0 md:min-w-11 min-h-11 items-center p-0'><ZapOutline className="mx-2" /><span>{zapCount}</span></Button>
<Button color="none" class='flex flex-{direction} shrink-0 md:min-w-11 min-h-11 items-center p-0'><FilePenOutline class="mx-2" size="lg"/><span>{highlightCount}</span></Button>
<Button color="none" class='flex flex-{direction} shrink-0 md:min-w-11 min-h-11 items-center p-0' onclick={() => showSocial()}><AnnotationOutline class="mx-2" size="lg"/><span>{commentCount}</span></Button>
</div>

4
src/lib/components/util/TocToggle.svelte

@ -95,7 +95,7 @@ @@ -95,7 +95,7 @@
{#if showTocButton && !showToc}
<Button
class="btn-leather h-6 !w-auto"
class="btn-leather !w-auto"
outline={true}
on:click={(ev) => {
showToc = true;
@ -103,7 +103,7 @@ @@ -103,7 +103,7 @@
}}
>
<BookOutline class="!fill-none mr-1"/>
Table of Contents
<span class="hidden sm:flex">Table of Contents</span>
</Button>
{/if}
<!-- TODO: Get TOC from parser. -->

2
src/lib/stores.ts

@ -12,6 +12,6 @@ export const publicationColumnVisibility = writable({ @@ -12,6 +12,6 @@ export const publicationColumnVisibility = writable({
blog: true,
main: true,
inner: true,
social: false,
discussion: false,
editing: false
});

6
src/routes/+layout.svelte

@ -16,7 +16,7 @@ @@ -16,7 +16,7 @@
onMount(() => {
const rect = document.body.getBoundingClientRect();
document.body.style.height = `${rect.height}px`;
// document.body.style.height = `${rect.height}px`;
});
</script>
@ -40,8 +40,8 @@ @@ -40,8 +40,8 @@
<meta name="twitter:image" content="{image}" />
</svelte:head>
<div class={'leather min-h-screen w-full flex flex-col items-center'}>
<Navigation class='sticky top-0' />
<div class={'leather mt-[76px] h-full w-full flex flex-col items-center'}>
<Navigation class='fixed top-0' />
<Alert rounded={false} class='!hidden border-t-4 border-primary-500 text-gray-900 dark:text-gray-100 dark:border-primary-500 flex justify-left mb-2'>
<HammerSolid class='mr-2 h-5 w-5 text-primary-500 dark:text-primary-500' />
<span class='font-medium'>

6
src/styles/base.css

@ -1,3 +1,9 @@ @@ -1,3 +1,9 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
body {
@apply bg-primary-0 dark:bg-primary-1000;
}
}

3
src/styles/publications.css

@ -256,6 +256,9 @@ @@ -256,6 +256,9 @@
.blog .discreet .card-leather:not(:hover) {
@apply bg-primary-50 dark:bg-primary-1000 opacity-75 transition duration-500 ease-in-out ;
}
.blog .discreet .group {
@apply bg-transparent;
}
}
}

Loading…
Cancel
Save