Browse Source

Tidied up and display rudimentary ToC. Updated screenshots.

master
Silberengel 10 months ago
parent
commit
1acd8b3acf
  1. 2
      src/lib/components/Preview.svelte
  2. 26
      src/lib/components/Publication.svelte
  3. 2
      src/lib/components/PublicationSection.svelte
  4. 4
      src/lib/components/util/ArticleNav.svelte
  5. 2
      src/lib/components/util/Interactions.svelte
  6. 66
      src/lib/components/util/TocToggle.svelte
  7. 5
      src/lib/parser.ts
  8. 3
      src/routes/new/compose/+page.svelte
  9. 3
      src/routes/new/edit/+page.svelte
  10. 15
      src/routes/start/+page.svelte
  11. BIN
      static/screenshots/ToC_blog.png
  12. BIN
      static/screenshots/ToC_normal.png

2
src/lib/components/Preview.svelte

@ -299,7 +299,7 @@
{/if} {/if}
<!-- Recurse on child indices and zettels --> <!-- Recurse on child indices and zettels -->
{#if publicationType === 'blog' && depth === 1} {#if publicationType === 'blog' && depth === 1}
<BlogHeader event={getBlogEvent(index)} rootId={rootId} onBlogUpdate={readBlog} /> <BlogHeader event={getBlogEvent(index)} rootId={rootId} onBlogUpdate={readBlog} active={true} />
{:else } {:else }
{#key subtreeUpdateCount} {#key subtreeUpdateCount}
{#each orderedChildren as id, index} {#each orderedChildren as id, index}

26
src/lib/components/Publication.svelte

@ -5,20 +5,14 @@
Card, Card,
Sidebar, Sidebar,
SidebarGroup, SidebarGroup,
SidebarItem,
SidebarWrapper, SidebarWrapper,
Skeleton,
TextPlaceholder,
Tooltip,
Heading, Heading,
} from "flowbite-svelte"; } from "flowbite-svelte";
import { getContext, onDestroy, onMount } from "svelte"; import { getContext, onDestroy, onMount } from "svelte";
import { import {
CloseOutline, CloseOutline,
BookOutline,
ExclamationCircleOutline, ExclamationCircleOutline,
} from "flowbite-svelte-icons"; } from "flowbite-svelte-icons";
import { page } from "$app/state";
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";
import type { PublicationTree } from "$lib/data_structures/publication_tree"; import type { PublicationTree } from "$lib/data_structures/publication_tree";
@ -27,6 +21,7 @@
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 TocToggle from "$components/util/TocToggle.svelte"; import TocToggle from "$components/util/TocToggle.svelte";
import { pharosInstance } from '$lib/parser';
let { rootAddress, publicationType, indexEvent } = $props<{ let { rootAddress, publicationType, indexEvent } = $props<{
rootAddress: string; rootAddress: string;
@ -114,8 +109,10 @@
currentBlog = rootId; currentBlog = rootId;
// set current blog values for publication render // set current blog values for publication render
if (leaves.length > 0) {
currentBlogEvent = currentBlogEvent =
leaves.find((i) => i.tagAddress() === currentBlog) ?? null; leaves.find((i) => i && i.tagAddress() === currentBlog) ?? null;
}
} }
function showBlogHeader() { function showBlogHeader() {
@ -156,11 +153,14 @@
observer.disconnect(); observer.disconnect();
}; };
}); });
// Whenever the publication changes, update rootId
let rootId = $derived($pharosInstance.getRootIndexId());
</script> </script>
<!-- Table of contents --> <!-- Table of contents -->
{#if publicationType !== "blog" || !isLeaf} {#if publicationType !== "blog" || !isLeaf}
<TocToggle rootId={rootAddress} /> <TocToggle {rootId} />
{/if} {/if}
<!-- Default publications --> <!-- Default publications -->
@ -171,7 +171,7 @@
> >
<Details event={indexEvent} /> <Details event={indexEvent} />
</div> </div>
<!-- Publication --> <!-- Publication sections/cards -->
{#each leaves as leaf, i} {#each leaves as leaf, i}
{#if leaf == null} {#if leaf == null}
<Alert class="flex space-x-2"> <Alert class="flex space-x-2">
@ -215,12 +215,14 @@
</div> </div>
<!-- List blog excerpts --> <!-- List blog excerpts -->
{#each leaves as leaf, i} {#each leaves as leaf, i}
{#if leaf}
<BlogHeader <BlogHeader
rootId={leaf.tagAddress()} rootId={leaf.tagAddress()}
event={leaf} event={leaf}
onBlogUpdate={loadBlog} onBlogUpdate={loadBlog}
active={!isInnerActive()} active={!isInnerActive()}
/> />
{/if}
{/each} {/each}
</div> </div>
{/if} {/if}
@ -231,7 +233,7 @@
class="flex flex-col p-4 max-w-3xl overflow-auto flex-grow-2 max-h-[calc(100vh-146px)] sticky top-[146px]" 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} {#each leaves as leaf, i}
{#if leaf.tagAddress() === currentBlog} {#if leaf && leaf.tagAddress() === currentBlog}
<div <div
class="card-leather bg-highlight dark:bg-primary-800 p-4 mb-4 rounded-lg border" class="card-leather bg-highlight dark:bg-primary-800 p-4 mb-4 rounded-lg border"
> >
@ -245,7 +247,7 @@
ref={(el) => setLastElementRef(el, i)} ref={(el) => setLastElementRef(el, i)}
/> />
<Card class="ArticleBox !hidden card-leather min-w-full grid mt-4"> <Card class="ArticleBox !hidden card-leather min-w-full mt-4">
<Interactions rootId={currentBlog} /> <Interactions rootId={currentBlog} />
</Card> </Card>
{/if} {/if}
@ -273,7 +275,7 @@
alternative for other publications and alternative for other publications and
when blog is not opened, but discussion is opened from the list when blog is not opened, but discussion is opened from the list
--> -->
{#if showBlogHeader()} {#if showBlogHeader() && currentBlog && currentBlogEvent}
<BlogHeader <BlogHeader
rootId={currentBlog} rootId={currentBlog}
event={currentBlogEvent} event={currentBlogEvent}

2
src/lib/components/PublicationSection.svelte

@ -104,7 +104,7 @@
}); });
</script> </script>
<section bind:this={sectionRef} class='publication-leather content-visibility-auto'> <section id={address} bind:this={sectionRef} class='publication-leather content-visibility-auto'>
{#await Promise.all([leafTitle, leafContent, leafHierarchy, publicationType, divergingBranches])} {#await Promise.all([leafTitle, leafContent, leafHierarchy, publicationType, divergingBranches])}
<TextPlaceholder size='xxl' /> <TextPlaceholder size='xxl' />
{:then [leafTitle, leafContent, leafHierarchy, publicationType, divergingBranches]} {:then [leafTitle, leafContent, leafHierarchy, publicationType, divergingBranches]}

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

@ -40,7 +40,7 @@
function shouldShowBack() { function shouldShowBack() {
const vis = $publicationColumnVisibility; const vis = $publicationColumnVisibility;
return ['discussion', 'toc', 'inner'].some(key => vis[key]); return ['discussion', 'toc', 'inner'].some(key => vis[key as keyof typeof vis]);
} }
function backToMain() { function backToMain() {
@ -140,7 +140,7 @@
</Button> </Button>
{/if} {/if}
{#if publicationType !== 'blog' && !$publicationColumnVisibility.discussion} {#if publicationType !== 'blog' && !$publicationColumnVisibility.discussion}
<Button class="btn-leather !hidden hidden sm:flex !w-auto" outline={true} onclick={() => toggleColumn('discussion')} > <Button class="btn-leather !hidden sm:flex !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}

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

@ -80,7 +80,7 @@
} }
</script> </script>
<div class='InteractiveMenu !hidden flex flex-{direction} justify-around align-middle text-primary-700 dark:text-gray-500'> <div class='InteractiveMenu !hidden 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' onclick={doLike}><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' onclick={doLike}><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' onclick={doZap}><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' onclick={doZap}><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' onclick={doHighlight}><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={doHighlight}><FilePenOutline class="mx-2" size="lg"/><span>{highlightCount}</span></Button>

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

@ -1,18 +1,14 @@
<script lang="ts"> <script lang="ts">
import { import {
Button, Heading, Heading,
Sidebar, Sidebar,
SidebarGroup, SidebarGroup,
SidebarItem, SidebarItem,
SidebarWrapper, SidebarWrapper,
Skeleton,
TextPlaceholder,
Tooltip
} from "flowbite-svelte"; } from "flowbite-svelte";
import { onMount } from "svelte"; import { onMount } from "svelte";
import { pharosInstance } from "$lib/parser"; import { pharosInstance, tocUpdate } from "$lib/parser";
import { publicationColumnVisibility } from "$lib/stores"; import { publicationColumnVisibility } from "$lib/stores";
import { page } from "$app/state";
let { rootId } = $props<{ rootId: string }>(); let { rootId } = $props<{ rootId: string }>();
@ -22,7 +18,36 @@
const tocBreakpoint = 1140; const tocBreakpoint = 1140;
let activeHash = $state(page.url.hash); let activeHash = $state(window.location.hash);
interface TocItem {
label: string;
hash: string;
}
// Get TOC items from parser
let tocItems = $state<TocItem[]>([]);
$effect(() => {
// This will re-run whenever tocUpdate changes
tocUpdate;
const items: TocItem[] = [];
const childIds = $pharosInstance.getChildIndexIds(rootId);
console.log('TOC rootId:', rootId, 'childIds:', childIds);
const processNode = (nodeId: string) => {
const title = $pharosInstance.getIndexTitle(nodeId);
if (title) {
items.push({
label: title,
hash: `#${nodeId}`
});
}
const children = $pharosInstance.getChildIndexIds(nodeId);
children.forEach(processNode);
};
childIds.forEach(processNode);
tocItems = items;
});
function normalizeHashPath(str: string): string { function normalizeHashPath(str: string): string {
return str return str
@ -48,11 +73,16 @@
} }
} }
function updateActiveHash() {
activeHash = window.location.hash;
}
/** /**
* Hides the table of contents sidebar when the window shrinks below a certain size. This * Hides the table of contents sidebar when the window shrinks below a certain size. This
* prevents the sidebar from occluding the article content. * prevents the sidebar from occluding the article content.
*/ */
function setTocVisibilityOnResize() { function setTocVisibilityOnResize() {
// Always show TOC on laptop and larger screens, collapsible only on small/medium
publicationColumnVisibility.update(v => ({ ...v, toc: window.innerWidth >= tocBreakpoint })); publicationColumnVisibility.update(v => ({ ...v, toc: window.innerWidth >= tocBreakpoint }));
} }
@ -66,7 +96,8 @@
return; return;
} }
if ($publicationColumnVisibility.toc) { // Only allow hiding TOC on screens smaller than tocBreakpoint
if (window.innerWidth < tocBreakpoint && $publicationColumnVisibility.toc) {
publicationColumnVisibility.update(v => ({ ...v, toc: false})); publicationColumnVisibility.update(v => ({ ...v, toc: false}));
} }
} }
@ -75,6 +106,7 @@
// Always check whether the TOC sidebar should be visible. // Always check whether the TOC sidebar should be visible.
setTocVisibilityOnResize(); setTocVisibilityOnResize();
window.addEventListener("hashchange", updateActiveHash);
window.addEventListener("hashchange", scrollToElementWithOffset); window.addEventListener("hashchange", scrollToElementWithOffset);
// Also handle the case where the user lands on the page with a hash in the URL // Also handle the case where the user lands on the page with a hash in the URL
scrollToElementWithOffset(); scrollToElementWithOffset();
@ -83,6 +115,7 @@
window.addEventListener("click", hideTocOnClick); window.addEventListener("click", hideTocOnClick);
return () => { return () => {
window.removeEventListener("hashchange", updateActiveHash);
window.removeEventListener("hashchange", scrollToElementWithOffset); window.removeEventListener("hashchange", scrollToElementWithOffset);
window.removeEventListener("resize", setTocVisibilityOnResize); window.removeEventListener("resize", setTocVisibilityOnResize);
window.removeEventListener("click", hideTocOnClick); window.removeEventListener("click", hideTocOnClick);
@ -92,17 +125,18 @@
<!-- TODO: Get TOC from parser. --> <!-- TODO: Get TOC from parser. -->
{#if $publicationColumnVisibility.toc} {#if $publicationColumnVisibility.toc}
<Sidebar class='sidebar-leather left-0' {activeHash}> <Sidebar class='sidebar-leather left-0'>
<SidebarWrapper> <SidebarWrapper>
<SidebarGroup class='sidebar-group-leather'> <SidebarGroup class='sidebar-group-leather'>
<Heading tag="h1" class="h-leather !text-lg">Table of contents</Heading> <Heading tag="h1" class="h-leather !text-lg">Table of contents</Heading>
<!--{#each events as event}--> <p>(This ToC is only for demo purposes, and is not fully-functional.)</p>
<!-- <SidebarItem--> {#each tocItems as item}
<!-- class='sidebar-item-leather'--> <SidebarItem
<!-- label={event.getMatchingTags('title')[0][1]}--> class="sidebar-item-leather {activeHash === item.hash ? 'bg-primary-200 font-bold' : ''}"
<!-- href={`${$page.url.pathname}#${normalizeHashPath(event.getMatchingTags('title')[0][1])}`}--> label={item.label}
<!-- />--> href={item.hash}
<!--{/each}--> />
{/each}
</SidebarGroup> </SidebarGroup>
</SidebarWrapper> </SidebarWrapper>
</Sidebar> </Sidebar>

5
src/lib/parser.ts

@ -1114,3 +1114,8 @@ export default class Pharos {
} }
export const pharosInstance: Writable<Pharos> = writable(); export const pharosInstance: Writable<Pharos> = writable();
export const tocUpdate = writable(0);
// Whenever you update the publication tree, call:
tocUpdate.update(n => n + 1);

3
src/routes/new/compose/+page.svelte

@ -5,6 +5,7 @@
let treeNeedsUpdate: boolean = false; let treeNeedsUpdate: boolean = false;
let treeUpdateCount: number = 0; let treeUpdateCount: number = 0;
let someIndexValue = 0;
$: { $: {
if (treeNeedsUpdate) { if (treeNeedsUpdate) {
@ -17,7 +18,7 @@
<main class='main-leather flex flex-col space-y-4 max-w-2xl w-full mt-4 mb-4'> <main class='main-leather flex flex-col space-y-4 max-w-2xl w-full mt-4 mb-4'>
<Heading tag='h1' class='h-leather mb-2'>Compose</Heading> <Heading tag='h1' class='h-leather mb-2'>Compose</Heading>
{#key treeUpdateCount} {#key treeUpdateCount}
<Preview rootId={$pharosInstance.getRootIndexId()} allowEditing={true} bind:needsUpdate={treeNeedsUpdate} /> <Preview rootId={$pharosInstance.getRootIndexId()} allowEditing={true} bind:needsUpdate={treeNeedsUpdate} index={someIndexValue} />
{/key} {/key}
</main> </main>
</div> </div>

3
src/routes/new/edit/+page.svelte

@ -5,6 +5,7 @@
import Pharos, { pharosInstance } from "$lib/parser"; import Pharos, { pharosInstance } from "$lib/parser";
import { ndkInstance } from "$lib/ndk"; import { ndkInstance } from "$lib/ndk";
import { goto } from "$app/navigation"; import { goto } from "$app/navigation";
let someIndexValue = 0;
// TODO: Prompt user to sign in before editing. // TODO: Prompt user to sign in before editing.
@ -80,7 +81,7 @@
</ToolbarButton> </ToolbarButton>
</Toolbar> </Toolbar>
{#if rootIndexId} {#if rootIndexId}
<Preview sectionClass='m-2' rootId={rootIndexId} /> <Preview sectionClass='m-2' rootId={rootIndexId} index={someIndexValue} />
{/if} {/if}
</form> </form>
{/if} {/if}

15
src/routes/start/+page.svelte

@ -53,20 +53,23 @@
<P class="mb-3"> <P class="mb-3">
Each content section (30041 or 30818) is also a level in the table of Each content section (30041 or 30818) is also a level in the table of
contents, which can be accessed from the floating icon top-left in the contents, which can be accessed from the floating icon top-left in the
reading view. This allows for navigation within the publication. (This reading view. This allows for navigation within the publication.
functionality has been temporarily disabled.) Publications of type "blog" have a ToC which emphasizes that each entry
is a blog post.
(This functionality has been temporarily disabled, but the TOC is visible.)
</P> </P>
<div class="flex flex-col items-center space-y-4 my-4"> <div class="flex flex-col items-center space-y-4 my-4">
<Img <Img
src="/screenshots/ToC_icon.png" src="/screenshots/ToC_normal.png"
alt="ToC icon" alt="ToC basic"
class="image-border rounded-lg" class="image-border rounded-lg"
width="400" width="400"
/> />
<Img <Img
src="/screenshots/TableOfContents.png" src="/screenshots/ToC_blog.png"
alt="Table of contents example" alt="ToC blog"
class="image-border rounded-lg" class="image-border rounded-lg"
width="400" width="400"
/> />

BIN
static/screenshots/ToC_blog.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 KiB

BIN
static/screenshots/ToC_normal.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Loading…
Cancel
Save