Browse Source

Refactored visibility store, changed styles yet again...

master
Nuša Pukšič 10 months ago
parent
commit
82f0d20e23
  1. 7
      src/app.css
  2. 200
      src/lib/components/Publication.svelte
  3. 111
      src/lib/components/util/ArticleNav.svelte
  4. 17
      src/lib/components/util/Interactions.svelte
  5. 51
      src/lib/components/util/TocToggle.svelte
  6. 20
      src/lib/stores.ts

7
src/app.css

@ -159,8 +159,13 @@
} }
/* Sidebar */ /* Sidebar */
aside.sidebar-leather {
@apply fixed md:sticky top-[130px] sm:top-[146px] h-[calc(100vh-130px)] sm:h-[calc(100vh-146px)] z-10;
@apply bg-primary-0 dark:bg-primary-1000 px-5 w-full sm:w-auto sm:max-w-xl;
}
aside.sidebar-leather > div { aside.sidebar-leather > div {
@apply bg-gray-100 dark:bg-gray-900; @apply bg-primary-50 dark:bg-primary-900 h-full px-5 py-0;
} }
a.sidebar-item-leather { a.sidebar-item-leather {

200
src/lib/components/Publication.svelte

@ -7,9 +7,9 @@
SidebarWrapper, SidebarWrapper,
Skeleton, Skeleton,
TextPlaceholder, TextPlaceholder,
Tooltip Tooltip, Heading
} from "flowbite-svelte"; } from "flowbite-svelte";
import { HeartOutline } from 'flowbite-svelte-icons'; import { CloseOutline } from 'flowbite-svelte-icons';
import { getContext, onDestroy, onMount } from "svelte"; import { getContext, onDestroy, onMount } from "svelte";
import type { NDKEvent } from "@nostr-dev-kit/ndk"; import type { NDKEvent } from "@nostr-dev-kit/ndk";
import PublicationSection from "./PublicationSection.svelte"; import PublicationSection from "./PublicationSection.svelte";
@ -18,7 +18,7 @@
import { publicationColumnVisibility } from "$lib/stores"; import { publicationColumnVisibility } from "$lib/stores";
import BlogHeader from "$components/blog/BlogHeader.svelte"; import BlogHeader from "$components/blog/BlogHeader.svelte";
import Interactions from "$components/util/Interactions.svelte"; import Interactions from "$components/util/Interactions.svelte";
import ZapOutline from "$components/util/ZapOutline.svelte"; import TocToggle from "$components/util/TocToggle.svelte";
let { rootAddress, publicationType, indexEvent } = $props<{ let { rootAddress, publicationType, indexEvent } = $props<{
rootAddress: string, rootAddress: string,
@ -70,42 +70,49 @@
// #endregion // #endregion
// region Columns visibility
let currentBlog: null|string = $state(null); let currentBlog: null|string = $state(null);
let currentBlogEvent: null|NDKEvent = $state(null); let currentBlogEvent: null|NDKEvent = $state(null);
function isDefaultVisible() {
if (publicationType !== 'blog') {
return true;
} else {
return $publicationColumnVisibility.blog;
}
}
function isInnerActive() { function isInnerActive() {
return currentBlog !== null && $publicationColumnVisibility.inner; return currentBlog !== null && $publicationColumnVisibility.inner;
} }
function isSocialActive() { function closeDiscussion() {
return $publicationColumnVisibility.discussion; publicationColumnVisibility.update(v => ({ ...v, discussion: false}));
} }
function loadBlog(rootId: string) { function loadBlog(rootId: string) {
// depending on the size of the screen, also toggle blog list & social visibility // depending on the size of the screen, also toggle blog list & discussion visibility
if (window.innerWidth < 1024) { publicationColumnVisibility.update(current => {
$publicationColumnVisibility.blog = false; const updated = current;
$publicationColumnVisibility.discussion = false; if (window.innerWidth < 1024) {
} updated.blog = false;
$publicationColumnVisibility.inner = true; updated.discussion = false;
}
updated.inner = true;
return updated;
});
currentBlog = rootId; currentBlog = rootId;
// set current blog values for publication render // set current blog values for publication render
currentBlogEvent = leaves.find(i => i.tagAddress() === currentBlog) ?? null; currentBlogEvent = leaves.find(i => i.tagAddress() === currentBlog) ?? null;
} }
function showBlogHeaderOnMobile() { function showBlogHeader() {
return (currentBlog && currentBlogEvent && window.innerWidth < 1140); return (currentBlog && currentBlogEvent && window.innerWidth < 1140);
} }
onDestroy(() => {
// reset visibility
publicationColumnVisibility.reset();
})
onMount(() => { onMount(() => {
// Set current columns depending on the publication type
const isBlog = publicationType === 'blog';
publicationColumnVisibility.update(v => ({ ...v, main: !isBlog, blog: isBlog }));
// Set up the intersection observer. // Set up the intersection observer.
observer = new IntersectionObserver((entries) => { observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => { entries.forEach((entry) => {
@ -121,50 +128,48 @@
}; };
}); });
onDestroy(() => {
// reset visibility
$publicationColumnVisibility = {
toc: false,
blog: true,
main: true,
inner: true,
discussion: false,
editing: false
};
})
</script> </script>
{#if isDefaultVisible()} <!-- Table of contents -->
<div class="flex flex-col p-4 space-y-4 overflow-auto {#if publicationType !== 'blog'}
{publicationType === 'blog' ? 'max-w-xl flex-grow-1' : 'max-w-2xl flex-grow-2' } <TocToggle rootId={rootAddress} />
{isInnerActive() ? 'discreet' : ''} {/if}
">
<!-- Default publications -->
{#if $publicationColumnVisibility.main}
<div class="flex flex-col p-4 space-y-4 overflow-auto max-w-2xl flex-grow-2">
<div class="card-leather bg-highlight dark:bg-primary-800 p-4 mb-4 rounded-lg border"> <div class="card-leather bg-highlight dark:bg-primary-800 p-4 mb-4 rounded-lg border">
<Details event={indexEvent} /> <Details event={indexEvent} />
</div> </div>
<!-- Publication -->
{#each leaves as leaf, i}
<PublicationSection
rootAddress={rootAddress}
leaves={leaves}
address={leaf.tagAddress()}
ref={(el) => setLastElementRef(el, i)}
/>
{/each}
</div>
{/if}
{#if publicationType === 'blog'} <!-- Blog list -->
<!-- List blog excerpts --> {#if $publicationColumnVisibility.blog}
{#each leaves as leaf, i} <div class="flex flex-col p-4 space-y-4 overflow-auto max-w-xl flex-grow-1
<BlogHeader {isInnerActive() ? 'discreet' : ''}
rootId={leaf.tagAddress()} ">
event={leaf} <div class="card-leather bg-highlight dark:bg-primary-800 p-4 mb-4 rounded-lg border">
onBlogUpdate={loadBlog} <Details event={indexEvent} />
active={!(isInnerActive())} </div>
/> <!-- List blog excerpts -->
{/each} {#each leaves as leaf, i}
{:else} <BlogHeader
{#each leaves as leaf, i} rootId={leaf.tagAddress()}
<PublicationSection event={leaf}
rootAddress={rootAddress} onBlogUpdate={loadBlog}
leaves={leaves} active={!(isInnerActive())}
address={leaf.tagAddress()} />
ref={(el) => setLastElementRef(el, i)} {/each}
/>
{/each}
{/if}
</div> </div>
{/if} {/if}
@ -193,47 +198,42 @@
{/key} {/key}
{/if} {/if}
{#if isSocialActive() } {#if $publicationColumnVisibility.discussion }
<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"> <Sidebar class='sidebar-leather right-0 md:!pl-8'>
{#if showBlogHeaderOnMobile()} <SidebarWrapper>
<BlogHeader <SidebarGroup class='sidebar-group-leather'>
rootId={currentBlog} <div class="flex justify-between items-baseline">
event={currentBlogEvent} <Heading tag="h1" class="h-leather !text-lg">Discussion</Heading>
onBlogUpdate={loadBlog} <Button class="btn-leather hidden sm:flex z-30 !p-1 bg-primary-50 dark:bg-primary-900" outline onclick={closeDiscussion}>
active={true} <CloseOutline />
/> </Button>
</div>
{/if} <div class="flex flex-col space-y-4">
<div class="flex flex-col w-full space-y-4"> <!-- TODO
<Card class="ArticleBox card-leather w-full grid max-w-xl"> alternative for other publications and
<div class="flex flex-col my-2"> when blog is not opened, but discussion is opened from the list
<span>Unknown</span> -->
<span class='text-gray-500'>1.1.1970</span> {#if showBlogHeader()}
<BlogHeader
rootId={currentBlog}
event={currentBlogEvent}
onBlogUpdate={loadBlog}
active={true}
/>
{/if}
<div class="flex flex-col w-full space-y-4">
<Card class="ArticleBox card-leather w-full grid 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 space-y-4'>
This is a very intelligent comment placeholder that applies to all the content equally well.
</div>
</Card>
</div> </div>
<div class='flex flex-col flex-grow space-y-4'> </div>
This is a very intelligent comment placeholder that applies to all the content equally well. </SidebarGroup>
</div> </SidebarWrapper>
</Card> </Sidebar>
<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>
</div>
{/if} {/if}

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

@ -1,6 +1,5 @@
<script lang="ts"> <script lang="ts">
import TocToggle from "$components/util/TocToggle.svelte"; import { BookOutline, CaretLeftOutline, CloseOutline, GlobeOutline } from "flowbite-svelte-icons";
import { BookOutline, CaretLeftOutline, GlobeOutline } from "flowbite-svelte-icons";
import { Button } from "flowbite-svelte"; import { Button } from "flowbite-svelte";
import { publicationColumnVisibility } from "$lib/stores"; import { publicationColumnVisibility } from "$lib/stores";
import InlineProfile from "$components/util/InlineProfile.svelte"; import InlineProfile from "$components/util/InlineProfile.svelte";
@ -13,8 +12,7 @@
} = $props<{ } = $props<{
rootId: any, rootId: any,
publicationType: string, publicationType: string,
indexEvent: NDKEvent, indexEvent: NDKEvent
blogEvent: NDKEvent|null
}>(); }>();
let title: string = $derived(indexEvent.getMatchingTags('title')[0]?.[1]); let title: string = $derived(indexEvent.getMatchingTags('title')[0]?.[1]);
@ -22,67 +20,94 @@
let pubkey: string = $derived(indexEvent.getMatchingTags('p')[0]?.[1] ?? null); let pubkey: string = $derived(indexEvent.getMatchingTags('p')[0]?.[1] ?? null);
// Function to toggle column visibility // Function to toggle column visibility
function toggleColumn(column: 'blog'|'inner'|'discussion') { function toggleColumn(column: 'toc' | 'blog' | 'inner' | 'discussion') {
publicationColumnVisibility.update(store => { publicationColumnVisibility.update(current => {
store[column] = !store[column]; // Toggle true/false const newValue = !current[column];
if (window.innerWidth < 1400 && column === 'discussion' && $publicationColumnVisibility.discussion) { const updated = { ...current, [column]: newValue };
$publicationColumnVisibility.blog = false;
} if (window.innerWidth < 1400 && column === 'blog' && newValue) {
if (window.innerWidth < 1400 && column === 'blog' && $publicationColumnVisibility.blog) { updated.discussion = false;
$publicationColumnVisibility.discussion = false;
}
if (window.innerWidth < 980) {
$publicationColumnVisibility.inner = false;
} }
return { ...store }; // Ensure reactivity
return updated;
}); });
} }
function shouldShowBack() {
const vis = $publicationColumnVisibility;
return ['discussion', 'toc', 'inner'].some(key => vis[key]);
}
function backToMain() { function backToMain() {
if ($publicationColumnVisibility.discussion) { publicationColumnVisibility.update(current => {
$publicationColumnVisibility.inner = true; const updated = { ...current };
$publicationColumnVisibility.discussion = false;
} else { // if current is 'inner', just go back to blog
$publicationColumnVisibility.blog = true; if (current.inner && !(current.discussion || current.toc)) {
$publicationColumnVisibility.inner = false; updated.inner = false;
$publicationColumnVisibility.discussion = false; updated.blog = true;
} return updated;
}
updated.discussion = false;
updated.toc = false;
if (publicationType === 'blog') {
updated.inner = true;
updated.blog = false;
} else {
updated.main = true;
}
return updated;
});
} }
function backToBlog() {
publicationColumnVisibility.update(current => {
const updated = { ...current };
updated.inner = false;
updated.discussion = false;
updated.blog = true;
return updated;
})
}
</script> </script>
<nav class="Navbar navbar-leather flex fixed 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-[60px] sm: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="mx-auto flex space-x-2 container">
<div class="flex items-center space-x-2 md:min-w-52 min-w-8"> <div class="flex items-center space-x-2 md:min-w-52 min-w-8">
{#if shouldShowBack()}
<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>
{/if}
{#if publicationType === 'blog'} {#if publicationType === 'blog'}
{#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>
{/if}
<Button class="btn-leather hidden sm:flex !w-auto {$publicationColumnVisibility.blog ? 'active' : ''}" <Button class="btn-leather hidden sm:flex !w-auto {$publicationColumnVisibility.blog ? 'active' : ''}"
outline={true} onclick={() => toggleColumn('blog')} > outline={true} onclick={() => toggleColumn('blog')} >
<BookOutline class="!fill-none inline mr-1" /><span class="hidden sm:inline">Table of Contents</span> <BookOutline class="!fill-none inline mr-1" /><span class="hidden sm:inline">Table of Contents</span>
</Button> </Button>
{:else} {:else}
<TocToggle rootId={rootId} /> {#if !$publicationColumnVisibility.discussion && !$publicationColumnVisibility.toc}
<Button class='btn-leather !w-auto' outline={true} onclick={() => toggleColumn('toc')}>
<BookOutline class="!fill-none inline mr-1" /><span class="hidden sm:inline">Table of Contents</span>
</Button>
{/if}
{/if} {/if}
</div> </div>
<div class="flex flex-grow text justify-center items-center"> <div class="flex flex-grow text justify-center items-center">
<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> <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>
<div class="flex items-center space-x-2 md:min-w-52 min-w-8"> <div class="flex justify-end space-x-2 md:min-w-52 min-w-8">
{#if publicationType === 'blog'} {#if $publicationColumnVisibility.inner}
{#if $publicationColumnVisibility.inner || $publicationColumnVisibility.discussion} <Button class='btn-leather !w-auto hidden sm:flex' outline={true} onclick={backToBlog}>
<Button class='btn-leather !w-auto hidden sm:flex' outline={true} onclick={backToMain}> <CloseOutline class="!fill-none inline mr-1" /><span class="hidden sm:inline">Close</span>
<CaretLeftOutline class="!fill-none inline mr-1" /><span class="hidden sm:inline">Back</span> </Button>
</Button> {/if}
{/if} {#if publicationType !== 'blog' && !$publicationColumnVisibility.discussion}
{#if $publicationColumnVisibility.inner} <Button class="btn-leather hidden sm:flex !w-auto" outline={true} onclick={() => toggleColumn('discussion')} >
<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>
<GlobeOutline class="!fill-none inline mr-1" /><span class="hidden sm:inline">Discussion</span> </Button>
</Button>
{/if}
{/if} {/if}
</div> </div>
</div> </div>

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

@ -56,12 +56,15 @@
subs.push(subscribeCount(1, comments)); // comments (Text Notes) subs.push(subscribeCount(1, comments)); // comments (Text Notes)
}); });
function showSocial() { function showDiscussion() {
$publicationColumnVisibility.discussion = true; publicationColumnVisibility.update(v => {
if (window.innerWidth < 1400) { const updated = { ...v, discussion: true};
$publicationColumnVisibility.blog = false; // hide blog, unless the only column
$publicationColumnVisibility.inner = false; if (v.inner) {
} updated.blog = (v.blog && window.innerWidth >= 1400 );
}
return updated;
});
} }
</script> </script>
@ -69,5 +72,5 @@
<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'><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'><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'><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> <Button color="none" class='flex flex-{direction} shrink-0 md:min-w-11 min-h-11 items-center p-0' onclick={showDiscussion}><AnnotationOutline class="mx-2" size="lg"/><span>{commentCount}</span></Button>
</div> </div>

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

@ -1,17 +1,17 @@
<script lang="ts"> <script lang="ts">
import { import {
Button, Button, Heading,
Sidebar, Sidebar,
SidebarGroup, SidebarGroup,
SidebarItem, SidebarItem,
SidebarWrapper, SidebarWrapper,
Skeleton, Skeleton,
TextPlaceholder, TextPlaceholder,
Tooltip, Tooltip
} from "flowbite-svelte"; } from "flowbite-svelte";
import { onMount } from "svelte"; import { onMount } from "svelte";
import { BookOutline } from "flowbite-svelte-icons";
import { pharosInstance } from "$lib/parser"; import { pharosInstance } from "$lib/parser";
import { publicationColumnVisibility } from "$lib/stores";
import { page } from "$app/state"; import { page } from "$app/state";
let { rootId } = $props<{ rootId: string }>(); let { rootId } = $props<{ rootId: string }>();
@ -23,8 +23,6 @@
const tocBreakpoint = 1140; const tocBreakpoint = 1140;
let activeHash = $state(page.url.hash); let activeHash = $state(page.url.hash);
let showToc: boolean = $state(true);
let showTocButton: boolean = $state(false);
function normalizeHashPath(str: string): string { function normalizeHashPath(str: string): string {
return str return str
@ -55,8 +53,7 @@
* prevents the sidebar from occluding the article content. * prevents the sidebar from occluding the article content.
*/ */
function setTocVisibilityOnResize() { function setTocVisibilityOnResize() {
showToc = window.innerWidth >= tocBreakpoint; publicationColumnVisibility.update(v => ({ ...v, toc: window.innerWidth >= tocBreakpoint}));
showTocButton = window.innerWidth < tocBreakpoint;
} }
/** /**
@ -69,8 +66,8 @@
return; return;
} }
if (showToc) { if ($publicationColumnVisibility.toc) {
showToc = false; publicationColumnVisibility.update(v => ({ ...v, toc: false}));
} }
} }
@ -93,32 +90,20 @@
}); });
</script> </script>
{#if showTocButton && !showToc}
<Button
class="btn-leather !w-auto"
outline={true}
on:click={(ev) => {
showToc = true;
ev.stopPropagation();
}}
>
<BookOutline class="!fill-none mr-1"/>
<span class="hidden sm:flex">Table of Contents</span>
</Button>
{/if}
<!-- TODO: Get TOC from parser. --> <!-- TODO: Get TOC from parser. -->
<!-- {#if showToc} {#if $publicationColumnVisibility.toc}
<Sidebar class='sidebar-leather fixed top-20 left-0 px-4 w-60' {activeHash}> <Sidebar class='sidebar-leather left-0' {activeHash}>
<SidebarWrapper> <SidebarWrapper>
<SidebarGroup class='sidebar-group-leather overflow-y-scroll'> <SidebarGroup class='sidebar-group-leather'>
{#each events as event} <Heading tag="h1" class="h-leather !text-lg">Table of contents</Heading>
<SidebarItem <!--{#each events as event}-->
class='sidebar-item-leather' <!-- <SidebarItem-->
label={event.getMatchingTags('title')[0][1]} <!-- class='sidebar-item-leather'-->
href={`${$page.url.pathname}#${normalizeHashPath(event.getMatchingTags('title')[0][1])}`} <!-- label={event.getMatchingTags('title')[0][1]}-->
/> <!-- href={`${$page.url.pathname}#${normalizeHashPath(event.getMatchingTags('title')[0][1])}`}-->
{/each} <!-- />-->
<!--{/each}-->
</SidebarGroup> </SidebarGroup>
</SidebarWrapper> </SidebarWrapper>
</Sidebar> </Sidebar>
{/if} --> {/if}

20
src/lib/stores.ts

@ -7,11 +7,25 @@ export let alexandriaKinds = readable<number[]>([30040, 30041, 30818]);
export let feedType = writable<FeedType>(FeedType.StandardRelays); export let feedType = writable<FeedType>(FeedType.StandardRelays);
export const publicationColumnVisibility = writable({
const defaultVisibility = {
toc: false, toc: false,
blog: true, blog: true,
main: true, main: true,
inner: true, inner: false,
discussion: false, discussion: false,
editing: false editing: false
}); };
function createVisibilityStore() {
const { subscribe, set, update } = writable({ ...defaultVisibility });
return {
subscribe,
set,
update,
reset: () => set({ ...defaultVisibility })
};
}
export const publicationColumnVisibility = createVisibilityStore();

Loading…
Cancel
Save