From 7327d28c54f5832198bcf08c8daf139f9c3d3557 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nu=C5=A1a=20Puk=C5=A1i=C4=8D?= Date: Fri, 28 Mar 2025 20:49:26 +0100 Subject: [PATCH 01/31] open #199 some extra data --- src/lib/components/util/TocToggle.svelte | 124 +++++++++++++++++++++++ src/lib/parser.ts | 30 ++++++ 2 files changed, 154 insertions(+) create mode 100644 src/lib/components/util/TocToggle.svelte diff --git a/src/lib/components/util/TocToggle.svelte b/src/lib/components/util/TocToggle.svelte new file mode 100644 index 0000000..7bee061 --- /dev/null +++ b/src/lib/components/util/TocToggle.svelte @@ -0,0 +1,124 @@ + + +{#if showTocButton && !showToc} + +{/if} + + \ No newline at end of file diff --git a/src/lib/parser.ts b/src/lib/parser.ts index b9f9545..6450ebf 100644 --- a/src/lib/parser.ts +++ b/src/lib/parser.ts @@ -121,6 +121,11 @@ export default class Pharos { */ private eventsByLevelMap: Map = new Map(); + /** + * A map of blog entries + */ + private blogEntries: Map = new Map(); + /** * When `true`, `getEvents()` should regenerate the event tree to propagate updates. */ @@ -177,6 +182,14 @@ export default class Pharos { this.parse(content); } + getBlogEntries() { + return this.blogEntries; + } + + getIndexMetadata(): IndexMetadata { + return this.rootIndexMetadata; + } + /** * Generates and stores Nostr events from the parsed AsciiDoc document. The events can be * modified via the parser's API and retrieved via the `getEvents()` method. @@ -617,6 +630,23 @@ export default class Pharos { tags.map(tag => this.ndk.fetchEventFromTag(tag, event)) ); + // if a blog, save complete events for later + if (event.getMatchingTags("type")[0][1] === 'blog') { + childEvents.forEach(child => { + if (child) { + this.blogEntries.set(child?.getMatchingTags("d")?.[0]?.[1], child); + } + }) + } + + // populate metadata + if (event.created_at) { + this.rootIndexMetadata.publicationDate = new Date(event.created_at * 1000).toDateString(); + } + if (event.getMatchingTags('image')) { + this.rootIndexMetadata.coverImage = event.getMatchingTags('image')[0][1]; + } + // Michael J - 15 December 2024 - This could be further parallelized by recursively fetching // children of index events before processing them for content. We won't make that change now, // as it would increase complexity, but if performance suffers, we can revisit this option. From 3a12a6762e03d9f061f6ee14cd66ab8c3da98caf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nu=C5=A1a=20Puk=C5=A1i=C4=8D?= Date: Fri, 28 Mar 2025 20:54:59 +0100 Subject: [PATCH 02/31] Minor fix - error page buttons --- src/routes/publication/+error.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/publication/+error.svelte b/src/routes/publication/+error.svelte index a31bec9..92e716f 100644 --- a/src/routes/publication/+error.svelte +++ b/src/routes/publication/+error.svelte @@ -20,10 +20,10 @@ {page.error?.message}

- -
From e9b6925df9d88bc84d68d94087764a28bcb23595 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nu=C5=A1a=20Puk=C5=A1i=C4=8D?= Date: Fri, 28 Mar 2025 20:55:45 +0100 Subject: [PATCH 03/31] Scrollbars --- src/app.css | 1 + src/styles/scrollbar.css | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 src/styles/scrollbar.css diff --git a/src/app.css b/src/app.css index 358d034..b9ea3bc 100644 --- a/src/app.css +++ b/src/app.css @@ -1,4 +1,5 @@ @import './styles/base.css'; +@import 'styles/scrollbar.css'; @import './styles/publications.css'; @import './styles/visualize.css'; diff --git a/src/styles/scrollbar.css b/src/styles/scrollbar.css new file mode 100644 index 0000000..568c1b9 --- /dev/null +++ b/src/styles/scrollbar.css @@ -0,0 +1,21 @@ +@layer components { + /* Global scrollbar styles */ + * { + scrollbar-width: thin; /* Firefox */ + scrollbar-color: rgba(87, 66, 41, 0.8) transparent; /* Transparent track, default scrollbar thumb */ + } + + /* Webkit Browsers (Chrome, Safari, Edge) */ + *::-webkit-scrollbar { + width: 8px; /* Thin scrollbar */ + } + + *::-webkit-scrollbar-track { + background: transparent; /* Fully transparent track */ + } + + *::-webkit-scrollbar-thumb { + @apply bg-primary-500 dark:bg-primary-600 hover:bg-primary-600 dark:hover:bg-primary-800;; + border-radius: 6px; /* Rounded scrollbar */ + } +} \ No newline at end of file From 8271dacec6280d11f070930a5a32d5795f2325e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nu=C5=A1a=20Puk=C5=A1i=C4=8D?= Date: Fri, 28 Mar 2025 20:59:00 +0100 Subject: [PATCH 04/31] Additional navigation bar for publication view --- src/lib/components/util/ArticleNav.svelte | 42 +++++++++++++++++++++++ src/routes/publication/+page.svelte | 7 +++- 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 src/lib/components/util/ArticleNav.svelte diff --git a/src/lib/components/util/ArticleNav.svelte b/src/lib/components/util/ArticleNav.svelte new file mode 100644 index 0000000..b3fb33a --- /dev/null +++ b/src/lib/components/util/ArticleNav.svelte @@ -0,0 +1,42 @@ + + + \ No newline at end of file diff --git a/src/routes/publication/+page.svelte b/src/routes/publication/+page.svelte index dbd673f..75aa31d 100644 --- a/src/routes/publication/+page.svelte +++ b/src/routes/publication/+page.svelte @@ -3,13 +3,18 @@ import { TextPlaceholder } from "flowbite-svelte"; import type { PageData } from "./$types"; import { onDestroy } from "svelte"; + import ArticleNav from "$components/util/ArticleNav.svelte"; let { data }: { data: PageData } = $props(); onDestroy(() => data.parser.reset()); -
+{#key data} + +{/key} + +
{#await data.waitable} {:then} From f03db123a47f86a772ec24e6f32bf18d717f6d8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nu=C5=A1a=20Puk=C5=A1i=C4=8D?= Date: Fri, 28 Mar 2025 21:00:56 +0100 Subject: [PATCH 05/31] Extract publication details --- src/lib/components/util/CardActions.svelte | 62 +------------------- src/lib/components/util/Details.svelte | 66 ++++++++++++++++++++++ 2 files changed, 68 insertions(+), 60 deletions(-) create mode 100644 src/lib/components/util/Details.svelte diff --git a/src/lib/components/util/CardActions.svelte b/src/lib/components/util/CardActions.svelte index 41f0c1c..d298af5 100644 --- a/src/lib/components/util/CardActions.svelte +++ b/src/lib/components/util/CardActions.svelte @@ -11,7 +11,7 @@ import { standardRelays } from "$lib/consts"; import { neventEncode } from "$lib/utils"; import { type AddressPointer, naddrEncode } from "nostr-tools/nip19"; - import InlineProfile from "$components/util/InlineProfile.svelte"; + import Details from "$components/util/Details.svelte"; let { event } = $props(); @@ -19,17 +19,6 @@ let detailsModalOpen: boolean = $state(false); let eventIdCopied: boolean = $state(false); let shareLinkCopied: boolean = $state(false); - let title: string = $derived(event.getMatchingTags('title')[0]?.[1]); - let author: string = $derived(event.getMatchingTags('author')[0]?.[1] ?? 'unknown'); - let version: string = $derived(event.getMatchingTags('version')[0]?.[1] ?? '1'); - let image: string = $derived(event.getMatchingTags('image')[0]?.[1] ?? null); - let originalAuthor: string = $derived(event.getMatchingTags('p')[0]?.[1] ?? null); - let summary: string = $derived(event.getMatchingTags('summary')[0]?.[1] ?? null); - let type: string = $derived(event.getMatchingTags('type')[0]?.[1] ?? null); - let language: string = $derived(event.getMatchingTags('l')[0]?.[1] ?? null); - 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 isOpen = $state(false); @@ -154,53 +143,6 @@ -
- {#if image} -
- -
- {/if} -
-

{title}

-

by - {#if originalAuthor !== null} - - {:else} - {author} - {/if} -

-

Version: {version}

-
-
- - {#if summary} -
-

{summary}

-
- {/if} - -
-

Index author:

-
- -
- {#if source !== null} -
Source: {source}
- {/if} - {#if type !== null} -
Publication type: {type}
- {/if} - {#if language !== null} -
Language: {language}
- {/if} - {#if publisher !== null} -
Published by: {publisher}
- {/if} - {#if identifier !== null} -
{identifier}
- {/if} - -
- +
diff --git a/src/lib/components/util/Details.svelte b/src/lib/components/util/Details.svelte new file mode 100644 index 0000000..879b138 --- /dev/null +++ b/src/lib/components/util/Details.svelte @@ -0,0 +1,66 @@ + + +
+ {#if image} +
+ +
+ {/if} +
+

{title}

+

by + {#if originalAuthor !== null} + + {:else} + {author} + {/if} +

+

Version: {version}

+
+
+ +{#if summary} +
+

{summary}

+
+{/if} + +
+

Index author:

+
+ +
+ {#if source !== null} +
Source: {source}
+ {/if} + {#if type !== null} +
Publication type: {type}
+ {/if} + {#if language !== null} +
Language: {language}
+ {/if} + {#if publisher !== null} +
Published by: {publisher}
+ {/if} + {#if identifier !== null} +
{identifier}
+ {/if} + +
\ No newline at end of file From 3969a60e0709445b9cfeaa7cd7060cc6a52dc784 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nu=C5=A1a=20Puk=C5=A1i=C4=8D?= Date: Mon, 7 Apr 2025 11:07:47 +0200 Subject: [PATCH 06/31] Switch columns and styles, additional navigation --- src/app.css | 16 ++- src/lib/components/Navigation.svelte | 2 +- src/lib/components/Preview.svelte | 118 +++++++++++++++--- src/lib/components/Publication.svelte | 139 ++++++---------------- src/lib/components/util/ArticleNav.svelte | 33 +++-- src/lib/stores.ts | 10 ++ src/styles/publications.css | 29 +++++ tailwind.config.cjs | 8 ++ 8 files changed, 217 insertions(+), 138 deletions(-) diff --git a/src/app.css b/src/app.css index b9ea3bc..3d8be5c 100644 --- a/src/app.css +++ b/src/app.css @@ -55,7 +55,11 @@ /* Content */ main { - @apply max-w-full; + @apply max-w-full flex; + } + + main.blog { + max-height: calc(100vh - 130px); } main.main-leather, @@ -69,9 +73,9 @@ @apply bg-primary-0 dark:bg-primary-1000 text-gray-800 dark:text-gray-300 p-2 rounded; } - div.note-leather:hover:not(:has(.note-leather:hover)), - p.note-leather:hover:not(:has(.note-leather:hover)), - section.note-leather:hover:not(:has(.note-leather:hover)) { + .edit div.note-leather:hover:not(:has(.note-leather:hover)), + .edit p.note-leather:hover:not(:has(.note-leather:hover)), + section.edit.note-leather:hover:not(:has(.note-leather:hover)) { @apply hover:bg-primary-100 dark:hover:bg-primary-800; } @@ -128,6 +132,10 @@ } /* Navbar */ + nav.Navbar.navbar-main { + @apply z-30; + } + nav.navbar-leather { @apply bg-primary-0 dark:bg-primary-1000 z-10; } diff --git a/src/lib/components/Navigation.svelte b/src/lib/components/Navigation.svelte index 2ac6133..ca6cbbb 100644 --- a/src/lib/components/Navigation.svelte +++ b/src/lib/components/Navigation.svelte @@ -7,7 +7,7 @@ let leftMenuOpen = $state(false); - +

Alexandria

diff --git a/src/lib/components/Preview.svelte b/src/lib/components/Preview.svelte index f7dfe03..44e6c83 100644 --- a/src/lib/components/Preview.svelte +++ b/src/lib/components/Preview.svelte @@ -15,8 +15,10 @@ oncursorrelease, parentId, rootId, + index, sectionClass, publicationType, + onBlogUpdate } = $props<{ allowEditing?: boolean; depth?: number; @@ -26,14 +28,19 @@ oncursorrelease?: (e: MouseEvent) => void; parentId?: string | null | undefined; rootId: string; + index: number; sectionClass?: string; publicationType?: string; + onBlogUpdate?: any; }>(); let currentContent: string = $state($pharosInstance.getContent(rootId)); let title: string | undefined = $state($pharosInstance.getIndexTitle(rootId)); let orderedChildren: string[] = $state($pharosInstance.getOrderedChildIds(rootId)); + let blogEntries = $state(Array.from($pharosInstance.getBlogEntries())); + let metadata = $state($pharosInstance.getIndexMetadata()); + let isEditing: boolean = $state(false); let hasCursor: boolean = $state(false); let childHasCursor: boolean = $state(false); @@ -85,6 +92,44 @@ } }); + function byline(rootId: string, index: number) { + console.log(rootId, index, blogEntries); + const event = blogEntries[index][1]; + const author = event ? event.getMatchingTags("author")[0][1] : ''; + return author ?? ""; + } + + function hasCoverImage(rootId: string, index: number) { + console.log(rootId); + const event = blogEntries[index][1]; + const image = event && event.getMatchingTags("image")[0] ? event.getMatchingTags("image")[0][1] : ''; + return image ?? ''; + } + + function publishedAt(rootId: string, index: number) { + console.log(rootId, index); + console.log(blogEntries[index]); + const event = blogEntries[index][1]; + const date = event.created_at ? new Date(event.created_at * 1000) : ''; + if (date !== '') { + const formattedDate = new Intl.DateTimeFormat("en-US", { + year: "numeric", + month: "short", + day: "2-digit", + }).format(date); + return formattedDate ?? ""; + } + return ''; + } + + function readBlog(rootId:string) { + onBlogUpdate?.(rootId); + } + + function propagateBlogUpdate(rootId:string) { + onBlogUpdate?.(rootId); + } + function handleMouseEnter(e: MouseEvent) { hasCursor = true; if (oncursorcapture) { @@ -177,11 +222,38 @@ {/if} {/snippet} +{#snippet coverImage(rootId: string, index: number, depth: number)} + {#if hasCoverImage(rootId, index)} +
+ +
+ {/if} +{/snippet} + +{#snippet blogMetadata(rootId: string, index: number)} +

+ by {byline(rootId, index)} +

+

+ {publishedAt(rootId, index)} +

+{/snippet} + +{#snippet readMoreLink(rootId: string, index: number)} +

+ +

+{/snippet} + {#snippet contentParagraph(content: string, publicationType: string)} {#if publicationType === 'novel'}

{@html content}

+ {:else if publicationType === 'blog'} +

+ {@html content} +

{:else}

{@html content} @@ -240,25 +312,37 @@ {:else} - {@render sectionHeading(title!, depth)} + {#if publicationType === 'blog' && depth === 1} + {@render coverImage(rootId, index, depth)} + {@render sectionHeading(title!, depth)} + {@render blogMetadata(rootId, index)} + {:else} + {@render sectionHeading(title!, depth)} + {/if} {/if} - {#key subtreeUpdateCount} - {#each orderedChildren as id, index} - - {/each} - {/key} + {#if publicationType === 'blog' && depth === 1} + {@render readMoreLink(rootId, index)} + {:else } + {#key subtreeUpdateCount} + {#each orderedChildren as id, index} + + {/each} + {/key} + {/if}

{/if} {#if allowEditing && depth > 0} diff --git a/src/lib/components/Publication.svelte b/src/lib/components/Publication.svelte index 0794cc9..80ade5d 100644 --- a/src/lib/components/Publication.svelte +++ b/src/lib/components/Publication.svelte @@ -1,19 +1,9 @@ - onMount(() => { - // Always check whether the TOC sidebar should be visible. - setTocVisibilityOnResize(); - - window.addEventListener("hashchange", scrollToElementWithOffset); - // Also handle the case where the user lands on the page with a hash in the URL - scrollToElementWithOffset(); - - window.addEventListener("resize", setTocVisibilityOnResize); - window.addEventListener("click", hideTocOnClick); +{#if $publicationColumnVisibility.details} +
+ +
+{/if} - return () => { - window.removeEventListener("hashchange", scrollToElementWithOffset); - window.removeEventListener("resize", setTocVisibilityOnResize); - window.removeEventListener("click", hideTocOnClick); - }; - }); - +{#if isDefaultVisible()} +
+ +
+{/if} -{#if showTocButton && !showToc} - - Show Table of Contents +{#if currentBlog !== null && $publicationColumnVisibility.inner } + {#key currentBlog } +
+ +
+ {/key} {/if} - - -
- -
From 05755293e83ba37fa154ae1160c1f3b700a0b527 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nu=C5=A1a=20Puk=C5=A1i=C4=8D?= Date: Thu, 1 May 2025 18:20:18 +0200 Subject: [PATCH 12/31] Check if tags exist --- src/lib/parser.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/parser.ts b/src/lib/parser.ts index 72bb2d4..812967a 100644 --- a/src/lib/parser.ts +++ b/src/lib/parser.ts @@ -649,7 +649,7 @@ export default class Pharos { ); // if a blog, save complete events for later - if (event.getMatchingTags("type")[0][1] === 'blog') { + if (event.getMatchingTags("type").length > 0 && event.getMatchingTags("type")[0][1] === 'blog') { childEvents.forEach(child => { if (child) { this.blogEntries.set(child?.getMatchingTags("d")?.[0]?.[1], child); @@ -661,7 +661,7 @@ export default class Pharos { if (event.created_at) { this.rootIndexMetadata.publicationDate = new Date(event.created_at * 1000).toDateString(); } - if (event.getMatchingTags('image')) { + if (event.getMatchingTags('image').length > 0) { this.rootIndexMetadata.coverImage = event.getMatchingTags('image')[0][1]; } From 3605eb295b25b8095d7a561b51152d371e5c8ec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nu=C5=A1a=20Puk=C5=A1i=C4=8D?= Date: Sun, 4 May 2025 12:38:49 +0200 Subject: [PATCH 13/31] Display height was off, innerHeight overestimates the viewport --- src/routes/+layout.svelte | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index b7593c3..8e1a965 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -6,9 +6,6 @@ import { Alert } from "flowbite-svelte"; import { HammerSolid } from "flowbite-svelte-icons"; - // Compute viewport height. - $: displayHeight = window.innerHeight; - // Get standard metadata for OpenGraph tags let title = 'Library of Alexandria'; let currentUrl = $page.url.href; @@ -18,7 +15,8 @@ let summary = 'Alexandria is a digital library, utilizing Nostr events for curated publications and wiki pages.'; onMount(() => { - document.body.style.height = `${displayHeight}px`; + const rect = document.body.getBoundingClientRect(); + document.body.style.height = `${rect.height}px`; }); @@ -44,7 +42,7 @@
- + Pardon our dust! The publication view is currently using an experimental loader, and may be unstable. From 86976c5bf81df4dcc6ae21d5ba65895883d36e8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nu=C5=A1a=20Puk=C5=A1i=C4=8D?= Date: Sun, 4 May 2025 12:39:49 +0200 Subject: [PATCH 14/31] Update ArticleNav --- src/lib/components/util/ArticleNav.svelte | 61 ++++++++--------------- src/routes/publication/+page.svelte | 6 +-- 2 files changed, 23 insertions(+), 44 deletions(-) diff --git a/src/lib/components/util/ArticleNav.svelte b/src/lib/components/util/ArticleNav.svelte index f810d39..8df36bf 100644 --- a/src/lib/components/util/ArticleNav.svelte +++ b/src/lib/components/util/ArticleNav.svelte @@ -5,24 +5,6 @@ import { publicationColumnVisibility } from "$lib/stores"; import InlineProfile from "$components/util/InlineProfile.svelte"; import type { NDKEvent } from "@nostr-dev-kit/ndk"; - import { onMount } from 'svelte'; - import { fade } from 'svelte/transition'; - - let scrollY = $state(0); - - // Run this every time the scroll event happens - function updateScroll() { - scrollY = window.scrollY; - } - - onMount(() => { - window.addEventListener('scroll', updateScroll); - updateScroll(); // Set initial value - - return () => { - window.removeEventListener('scroll', updateScroll); - }; - }); let { rootId, @@ -51,31 +33,28 @@ - \ No newline at end of file diff --git a/src/routes/publication/+page.svelte b/src/routes/publication/+page.svelte index f0695e2..ac297c4 100644 --- a/src/routes/publication/+page.svelte +++ b/src/routes/publication/+page.svelte @@ -46,14 +46,14 @@ -{#key data} - -{/key}
{#await data.waitable} {:then} + {#key data} + + {/key} Date: Sun, 4 May 2025 12:41:12 +0200 Subject: [PATCH 15/31] Blog contents with placeholder interactions --- src/app.css | 21 ++++-- src/lib/components/Publication.svelte | 79 ++++++++++++--------- src/lib/components/blog/BlogHeader.svelte | 42 ++++++----- src/lib/components/util/CardActions.svelte | 3 +- src/lib/components/util/Interactions.svelte | 62 ++++++++++++++++ src/lib/components/util/ZapOutline.svelte | 19 +++++ src/styles/publications.css | 38 +++++----- src/styles/scrollbar.css | 3 +- 8 files changed, 183 insertions(+), 84 deletions(-) create mode 100644 src/lib/components/util/Interactions.svelte create mode 100644 src/lib/components/util/ZapOutline.svelte diff --git a/src/app.css b/src/app.css index c730a8d..05539ad 100644 --- a/src/app.css +++ b/src/app.css @@ -55,13 +55,15 @@ /* Content */ main { - @apply max-w-full flex mb-2; + @apply max-w-full flex; } /* To scroll columns independently */ - main.publication { - /* max-height: calc(100vh - 130px); */ - } + main.publication.blog { + display: flex; + flex-direction: column; + max-height: calc(100vh - 76px); + } main.main-leather, article.article-leather { @@ -247,6 +249,17 @@ .link { @apply underline cursor-pointer hover:text-primary-400 dark:hover:text-primary-500; } + + /* Card with transition */ + .ArticleBox.grid .ArticleBoxImage { + @apply max-h-0; + transition: max-height 0.5s ease; + } + + .ArticleBox.grid.active .ArticleBoxImage { + @apply max-h-72; + } + } @layer components { diff --git a/src/lib/components/Publication.svelte b/src/lib/components/Publication.svelte index 369bec3..e1555cd 100644 --- a/src/lib/components/Publication.svelte +++ b/src/lib/components/Publication.svelte @@ -9,12 +9,14 @@ TextPlaceholder, Tooltip, } from "flowbite-svelte"; + import { CaretLeftOutline } from 'flowbite-svelte-icons'; import { getContext, 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"; let { rootAddress, publicationType, indexEvent } = $props<{ rootAddress: string, @@ -83,30 +85,15 @@ } $publicationColumnVisibility.inner = true; currentBlog = rootId; + // set current blog values for publication render + console.log(currentBlog); } - // #region ToC - - - function scrollToElementWithOffset() { - const hash = window.location.hash; - if (hash) { - const targetElement = document.querySelector(hash); - if (targetElement) { - const headerOffset = 80; - const elementPosition = targetElement.getBoundingClientRect().top; - const offsetPosition = elementPosition + window.scrollY - headerOffset; - - window.scrollTo({ - top: offsetPosition, - behavior: "auto", - }); - } - } + function backToMain() { + $publicationColumnVisibility.blog = true; + $publicationColumnVisibility.inner = false; } - // #endregion - onMount(() => { // Set up the intersection observer. observer = new IntersectionObserver((entries) => { @@ -134,28 +121,54 @@ {/if} {#if isDefaultVisible()} -
-
+
- {#each leaves as leaf, i} - setLastElementRef(el, i)} - /> - {/each} + + {#if publicationType === 'blog'} + + {#each leaves as leaf, i} + + {/each} + {:else} + {#each leaves as leaf, i} + setLastElementRef(el, i)} + /> + {/each} + {/if} +
{/if} {#if currentBlog !== null && $publicationColumnVisibility.inner } {#key currentBlog } -
- Todo... +
+
+ +
+ {#each leaves as leaf, i} + {#if leaf.tagAddress() === currentBlog} + setLastElementRef(el, i)} + /> + {/if} + {/each}
{/key} {/if} diff --git a/src/lib/components/blog/BlogHeader.svelte b/src/lib/components/blog/BlogHeader.svelte index 276b206..bfe5b6a 100644 --- a/src/lib/components/blog/BlogHeader.svelte +++ b/src/lib/components/blog/BlogHeader.svelte @@ -1,19 +1,18 @@ {#if title != null} - -
+ +
{publishedAt()}
- {#if image} -
- -
+ {#if image && active} +
+ +
{/if}
- + {#if active} + + {/if}
-
-
-
{likeCount}
-
{likeCount}
-
{likeCount}
-
{likeCount}
-
{likeCount}
- + {#if active} +
+
-
+ {/if}
{/if} diff --git a/src/lib/components/util/CardActions.svelte b/src/lib/components/util/CardActions.svelte index 177c8ee..c0d62d2 100644 --- a/src/lib/components/util/CardActions.svelte +++ b/src/lib/components/util/CardActions.svelte @@ -10,8 +10,7 @@ import { Button, Modal, Popover } from "flowbite-svelte"; import { standardRelays } from "$lib/consts"; import { neventEncode, naddrEncode } from "$lib/utils"; - import InlineProfile from "$components/util/InlineProfile.svelte"; - import Details from "./Details.svelte"; + import Details from "./Details.svelte"; let { event } = $props(); diff --git a/src/lib/components/util/Interactions.svelte b/src/lib/components/util/Interactions.svelte new file mode 100644 index 0000000..4263f05 --- /dev/null +++ b/src/lib/components/util/Interactions.svelte @@ -0,0 +1,62 @@ + + +
+
{likeCount}
+
{zapCount}
+
{highlightCount}
+
{commentCount}
+
\ No newline at end of file diff --git a/src/lib/components/util/ZapOutline.svelte b/src/lib/components/util/ZapOutline.svelte new file mode 100644 index 0000000..b5588c0 --- /dev/null +++ b/src/lib/components/util/ZapOutline.svelte @@ -0,0 +1,19 @@ + + + + + diff --git a/src/styles/publications.css b/src/styles/publications.css index 79cb2da..5616bd0 100644 --- a/src/styles/publications.css +++ b/src/styles/publications.css @@ -1,55 +1,55 @@ @layer components { /* AsciiDoc content */ - .note-leather p a { + .publication-leather p a { @apply underline hover:text-primary-500 dark:hover:text-primary-400; } - .note-leather section p { + .publication-leather section p { @apply w-full; } - .note-leather section p table { + .publication-leather section p table { @apply w-full table-fixed space-x-2 space-y-2; } - .note-leather section p table td { + .publication-leather section p table td { @apply p-2; } - .note-leather section p table td .content:has(> .imageblock) { + .publication-leather section p table td .content:has(> .imageblock) { @apply flex flex-col items-center; } - .note-leather .imageblock { + .publication-leather .imageblock { @apply flex flex-col space-y-2; } - .note-leather .imageblock .content { + .publication-leather .imageblock .content { @apply flex justify-center; } - .note-leather .imageblock .title { + .publication-leather .imageblock .title { @apply text-center; } - .note-leather .imageblock.left .content { + .publication-leather .imageblock.left .content { @apply justify-start; } - .note-leather .imageblock.left .title { + .publication-leather .imageblock.left .title { @apply text-left; } - .note-leather .imageblock.right .content { + .publication-leather .imageblock.right .content { @apply justify-end; } - .note-leather .imageblock.right .title { + .publication-leather .imageblock.right .title { @apply text-right; } - .note-leather section p table td .literalblock { + .publication-leather section p table td .literalblock { @apply my-2 p-2 border rounded border-gray-400 dark:border-gray-600; } - .note-leather .literalblock pre { + .publication-leather .literalblock pre { @apply p-3 text-wrap break-words; } @@ -58,7 +58,7 @@ } /* lists */ - .note-leather .ulist ul { + .publication-leather .ulist ul { @apply space-y-1 list-disc list-inside; } @@ -253,12 +253,8 @@ /** blog */ @screen lg { @media (hover: hover) { - .blog .discreet:not(:hover) .coverImage img { - @apply filter grayscale sepia brightness-75 opacity-50 transition duration-500 ease-in-out saturate-200 hue-rotate-20; - } - - .blog .discreet:not(:hover) .h-leather { - @apply filter grayscale sepia brightness-75 opacity-50 transition duration-500 ease-in-out saturate-200 hue-rotate-20; + .blog .discreet .card-leather:not(:hover) { + @apply bg-primary-50 opacity-75 transition duration-500 ease-in-out ; } } } diff --git a/src/styles/scrollbar.css b/src/styles/scrollbar.css index 568c1b9..8d2735d 100644 --- a/src/styles/scrollbar.css +++ b/src/styles/scrollbar.css @@ -1,13 +1,12 @@ @layer components { /* Global scrollbar styles */ * { - scrollbar-width: thin; /* Firefox */ scrollbar-color: rgba(87, 66, 41, 0.8) transparent; /* Transparent track, default scrollbar thumb */ } /* Webkit Browsers (Chrome, Safari, Edge) */ *::-webkit-scrollbar { - width: 8px; /* Thin scrollbar */ + width: 12px; /* Thin scrollbar */ } *::-webkit-scrollbar-track { From 1da1065471bb3c539a806e5eafe65434cd6f672e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nu=C5=A1a=20Puk=C5=A1i=C4=8D?= Date: Mon, 5 May 2025 14:41:06 +0200 Subject: [PATCH 16/31] Layout issues - make content fill screen and move publication navi again --- src/routes/+layout.svelte | 2 +- src/routes/publication/+page.svelte | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 8e1a965..0fbba6b 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -40,7 +40,7 @@ -
+
diff --git a/src/routes/publication/+page.svelte b/src/routes/publication/+page.svelte index ac297c4..f0695e2 100644 --- a/src/routes/publication/+page.svelte +++ b/src/routes/publication/+page.svelte @@ -46,14 +46,14 @@ +{#key data} + +{/key}
{#await data.waitable} {:then} - {#key data} - - {/key} Date: Mon, 5 May 2025 14:42:19 +0200 Subject: [PATCH 17/31] Declutter publication navi --- src/lib/components/util/ArticleNav.svelte | 35 ++++++++++++----------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/lib/components/util/ArticleNav.svelte b/src/lib/components/util/ArticleNav.svelte index 8df36bf..2ac8780 100644 --- a/src/lib/components/util/ArticleNav.svelte +++ b/src/lib/components/util/ArticleNav.svelte @@ -1,6 +1,6 @@ \ No newline at end of file From 5f7236fb088a8601a7af994fc13511ce9eb817b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nu=C5=A1a=20Puk=C5=A1i=C4=8D?= Date: Mon, 5 May 2025 14:44:14 +0200 Subject: [PATCH 18/31] Better interactivity --- src/app.css | 5 ++-- src/lib/components/Publication.svelte | 39 ++++++++++----------------- src/lib/stores.ts | 1 - src/styles/publications.css | 2 +- 4 files changed, 17 insertions(+), 30 deletions(-) diff --git a/src/app.css b/src/app.css index 05539ad..20e1021 100644 --- a/src/app.css +++ b/src/app.css @@ -60,9 +60,8 @@ /* To scroll columns independently */ main.publication.blog { - display: flex; - flex-direction: column; - max-height: calc(100vh - 76px); + @apply min-h-full; + max-height: calc(100vh - 146px); } main.main-leather, diff --git a/src/lib/components/Publication.svelte b/src/lib/components/Publication.svelte index e1555cd..f8c0360 100644 --- a/src/lib/components/Publication.svelte +++ b/src/lib/components/Publication.svelte @@ -9,7 +9,6 @@ TextPlaceholder, Tooltip, } from "flowbite-svelte"; - import { CaretLeftOutline } from 'flowbite-svelte-icons'; import { getContext, onMount } from "svelte"; import type { NDKEvent } from "@nostr-dev-kit/ndk"; import PublicationSection from "./PublicationSection.svelte"; @@ -78,6 +77,10 @@ } } + function isInnerActive() { + return currentBlog !== null && $publicationColumnVisibility.inner; + } + function loadBlog(rootId: string) { // depending on the size of the screen, also toggle blog list visibility if (window.innerWidth < 1024) { @@ -89,10 +92,7 @@ console.log(currentBlog); } - function backToMain() { - $publicationColumnVisibility.blog = true; - $publicationColumnVisibility.inner = false; - } + onMount(() => { // Set up the intersection observer. @@ -112,19 +112,11 @@ -{#if $publicationColumnVisibility.details} -
-
-
-
-
-{/if} - {#if isDefaultVisible()} -
+
@@ -136,7 +128,7 @@ rootId={leaf.tagAddress()} event={leaf} onBlogUpdate={loadBlog} - active={!(currentBlog !== null && $publicationColumnVisibility.inner)} + active={!(isInnerActive())} /> {/each} {:else} @@ -150,15 +142,12 @@ {/each} {/if} -
+
{/if} -{#if currentBlog !== null && $publicationColumnVisibility.inner } +{#if isInnerActive() } {#key currentBlog } -
-
- -
+
{#each leaves as leaf, i} {#if leaf.tagAddress() === currentBlog} +
{/if} diff --git a/src/lib/stores.ts b/src/lib/stores.ts index a78e275..f158e66 100644 --- a/src/lib/stores.ts +++ b/src/lib/stores.ts @@ -8,7 +8,6 @@ export let alexandriaKinds = readable([30040, 30041, 30818]); export let feedType = writable(FeedType.StandardRelays); export const publicationColumnVisibility = writable({ - details: false, toc: false, blog: true, main: true, diff --git a/src/styles/publications.css b/src/styles/publications.css index 5616bd0..d901db8 100644 --- a/src/styles/publications.css +++ b/src/styles/publications.css @@ -254,7 +254,7 @@ @screen lg { @media (hover: hover) { .blog .discreet .card-leather:not(:hover) { - @apply bg-primary-50 opacity-75 transition duration-500 ease-in-out ; + @apply bg-primary-50 dark:bg-primary-1000 opacity-75 transition duration-500 ease-in-out ; } } } From 1a537239ed4065e9c227481c5b1e36e72e95f06f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nu=C5=A1a=20Puk=C5=A1i=C4=8D?= Date: Mon, 5 May 2025 17:04:14 +0200 Subject: [PATCH 19/31] More dynamic and reactive columns --- src/app.css | 3 +- src/lib/components/Publication.svelte | 34 ++++++++++++-- src/lib/components/blog/BlogHeader.svelte | 11 ++--- src/lib/components/util/ArticleNav.svelte | 51 +++++++++++++++------ src/lib/components/util/Interactions.svelte | 2 +- 5 files changed, 73 insertions(+), 28 deletions(-) diff --git a/src/app.css b/src/app.css index 20e1021..79655e8 100644 --- a/src/app.css +++ b/src/app.css @@ -60,7 +60,7 @@ /* To scroll columns independently */ main.publication.blog { - @apply min-h-full; + @apply w-full sm:w-auto min-h-full; max-height: calc(100vh - 146px); } @@ -380,7 +380,6 @@ .line-ellipsis { max-width: 100%; - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } \ No newline at end of file diff --git a/src/lib/components/Publication.svelte b/src/lib/components/Publication.svelte index f8c0360..9d5c228 100644 --- a/src/lib/components/Publication.svelte +++ b/src/lib/components/Publication.svelte @@ -1,6 +1,6 @@ \ No newline at end of file diff --git a/src/lib/components/util/Interactions.svelte b/src/lib/components/util/Interactions.svelte index 4263f05..a605c06 100644 --- a/src/lib/components/util/Interactions.svelte +++ b/src/lib/components/util/Interactions.svelte @@ -5,7 +5,7 @@ import { onMount } from "svelte"; import { ndkInstance } from '$lib/ndk'; - const { rootId, event } = $props<{ rootId: String, event: NDKEvent }>(); + const { rootId, event } = $props<{ rootId: string, event: NDKEvent }>(); // Reactive arrays to hold incoming events let likes: NDKEvent[] = []; From 5283ebf1a77b53b6ba372b23803b4d8b40164c46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nu=C5=A1a=20Puk=C5=A1i=C4=8D?= Date: Tue, 6 May 2025 15:48:28 +0200 Subject: [PATCH 20/31] Added hashtags and interaction counters, changed layouts --- src/app.css | 12 +++- src/lib/components/Login.svelte | 2 +- src/lib/components/Publication.svelte | 70 ++++++++++++++++---- src/lib/components/blog/BlogHeader.svelte | 18 +++-- src/lib/components/util/ArticleNav.svelte | 27 ++++---- src/lib/components/util/CardActions.svelte | 4 +- src/lib/components/util/Details.svelte | 60 ++++++++++++----- src/lib/components/util/InlineProfile.svelte | 2 +- src/lib/components/util/Interactions.svelte | 25 +++++-- src/lib/components/util/TocToggle.svelte | 4 +- src/lib/stores.ts | 2 +- src/routes/+layout.svelte | 6 +- src/styles/base.css | 8 ++- src/styles/publications.css | 3 + 14 files changed, 176 insertions(+), 67 deletions(-) diff --git a/src/app.css b/src/app.css index 79655e8..ae4ac69 100644 --- a/src/app.css +++ b/src/app.css @@ -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 @@ @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 @@ @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 @@ } .line-ellipsis { - max-width: 100%; overflow: hidden; text-overflow: ellipsis; } \ No newline at end of file diff --git a/src/lib/components/Login.svelte b/src/lib/components/Login.svelte index 1456149..0149a0d 100644 --- a/src/lib/components/Login.svelte +++ b/src/lib/components/Login.svelte @@ -46,7 +46,7 @@ {#if $ndkSignedIn} {:else} - + 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 @@ } 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 @@ 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 @@ }; }); + onDestroy(() => { + // reset visibility + $publicationColumnVisibility = { + toc: false, + blog: true, + main: true, + inner: true, + discussion: false, + editing: false + }; + }) + {#if isDefaultVisible()} @@ -156,15 +170,23 @@ {#if isInnerActive() } {#key currentBlog } -
+
{#each leaves as leaf, i} {#if leaf.tagAddress() === currentBlog} +
+
+
+ setLastElementRef(el, i)} /> + + + + {/if} {/each}
@@ -172,7 +194,7 @@ {/if} {#if isSocialActive() } -
+
{#if showBlogHeaderOnMobile()} {/if} -
+
-
+
+ Unknown + 1.1.1970 +
- This is a placeholder comment... + This is a very intelligent comment placeholder that applies to all the content equally well. +
+ + + +
+ Unknown + 1.1.1970 +
+
+ +
+
+ + +
+ Unknown + 1.1.1970 +
+
+
-
diff --git a/src/lib/components/blog/BlogHeader.svelte b/src/lib/components/blog/BlogHeader.svelte index b5f1ff3..f7fc25b 100644 --- a/src/lib/components/blog/BlogHeader.svelte +++ b/src/lib/components/blog/BlogHeader.svelte @@ -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 @@ 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,8 +37,11 @@
- - {publishedAt()} +
+ + {publishedAt()} +
+
{#if image && active}
showBlog()} class='text-left'>

{title}

+ {#if hashtags} +
+ {#each hashtags as tag} + {tag} + {/each} +
+ {/if}
{#if active} -
-
{/if}
diff --git a/src/lib/components/util/ArticleNav.svelte b/src/lib/components/util/ArticleNav.svelte index d89d372..d220f50 100644 --- a/src/lib/components/util/ArticleNav.svelte +++ b/src/lib/components/util/ArticleNav.svelte @@ -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 @@ } 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; } } -