diff --git a/src/app.css b/src/app.css index 45169bd..4988e6a 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'; @@ -55,10 +55,15 @@ @apply max-w-full flex; } - main.blog { - max-height: calc(100vh - 130px); + main.publication { + @apply mt-[70px]; } + /* To scroll columns independently */ + main.publication.blog { + @apply w-full sm:w-auto min-h-full; + } + main.main-leather, article.article-leather { @apply bg-primary-0 dark:bg-primary-1000 text-gray-800 dark:text-gray-300; @@ -148,12 +153,18 @@ @apply text-gray-800 hover:text-primary-400 dark:text-gray-300 dark:hover:text-primary-500; } - aside.sidebar-leather>div { - @apply bg-primary-0 dark:bg-primary-1000; + /* 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 { + @apply bg-primary-50 dark:bg-gray-800 h-full px-5 py-0; } a.sidebar-item-leather { - @apply hover:bg-primary-100 dark:hover:bg-primary-800; + @apply hover:bg-primary-100 dark:hover:bg-gray-800; } div.skeleton-leather div { @@ -239,6 +250,21 @@ .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; + } + + .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 { @@ -391,6 +417,10 @@ padding-left: 1rem; } +.line-ellipsis { + overflow: hidden; + text-overflow: ellipsis; +} .footnotes li { margin-bottom: 0.5rem; } diff --git a/src/lib/components/Login.svelte b/src/lib/components/Login.svelte index 13d9c93..afa83a5 100644 --- a/src/lib/components/Login.svelte +++ b/src/lib/components/Login.svelte @@ -50,7 +50,7 @@ {#if $ndkSignedIn} {:else} - + {title} @@ -221,12 +226,6 @@

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

- -

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

@@ -294,17 +293,13 @@ {:else} - {#if publicationType === 'blog' && depth === 1} - {@render coverImage(rootId, index, depth)} - {@render sectionHeading(title!, depth)} - {@render blogMetadata(rootId, index)} - {:else} + {#if !(publicationType === 'blog' && depth === 1)} {@render sectionHeading(title!, depth)} {/if} {/if} {#if publicationType === 'blog' && depth === 1} - {@render readMoreLink(rootId, index)} + {:else } {#key subtreeUpdateCount} {#each orderedChildren as id, index} diff --git a/src/lib/components/Publication.svelte b/src/lib/components/Publication.svelte index b5be20d..38372fa 100644 --- a/src/lib/components/Publication.svelte +++ b/src/lib/components/Publication.svelte @@ -2,6 +2,7 @@ import { Alert, Button, + Card, Sidebar, SidebarGroup, SidebarItem, @@ -9,21 +10,31 @@ Skeleton, TextPlaceholder, Tooltip, + Heading, } from "flowbite-svelte"; - import { getContext, onMount } from "svelte"; - import { BookOutline, ExclamationCircleOutline } from "flowbite-svelte-icons"; + import { getContext, onDestroy, onMount } from "svelte"; + import { + CloseOutline, + BookOutline, + ExclamationCircleOutline, + } from "flowbite-svelte-icons"; import { page } from "$app/state"; import type { NDKEvent } from "@nostr-dev-kit/ndk"; import PublicationSection from "./PublicationSection.svelte"; import type { PublicationTree } from "$lib/data_structures/publication_tree"; - - let { rootAddress, publicationType, indexEvent } = $props<{ - rootAddress: string, - publicationType: string, - indexEvent: NDKEvent + 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 TocToggle from "$components/util/TocToggle.svelte"; + + let { rootAddress, publicationType, indexEvent } = $props<{ + rootAddress: string; + publicationType: string; + indexEvent: NDKEvent; }>(); - const publicationTree = getContext('publicationTree') as PublicationTree; + const publicationTree = getContext("publicationTree") as PublicationTree; // #region Loading @@ -76,157 +87,214 @@ // #endregion - // #region ToC - - const tocBreakpoint = 1140; + // region Columns visibility + let currentBlog: null | string = $state(null); + let currentBlogEvent: null | NDKEvent = $state(null); + const isLeaf = $derived(indexEvent.kind === 30041); - let activeHash = $state(page.url.hash); - - let currentBlog: null|string = $state(null); + function isInnerActive() { + return currentBlog !== null && $publicationColumnVisibility.inner; + } - function isDefaultVisible() { - if (publicationType !== 'blog') { - return true; - } else { - return $publicationColumnVisibility.blog; - } + function closeDiscussion() { + publicationColumnVisibility.update((v) => ({ ...v, discussion: false })); } function loadBlog(rootId: string) { - // depending on the size of the screen, also toggle blog list visibility - if (window.innerWidth < 1024) { - $publicationColumnVisibility.blog = false; - } - $publicationColumnVisibility.inner = true; + // depending on the size of the screen, also toggle blog list & discussion visibility + publicationColumnVisibility.update((current) => { + const updated = current; + if (window.innerWidth < 1024) { + updated.blog = false; + updated.discussion = false; + } + updated.inner = true; + return updated; + }); + currentBlog = rootId; + // set current blog values for publication render + currentBlogEvent = + leaves.find((i) => i.tagAddress() === currentBlog) ?? null; } - -{#if $publicationColumnVisibility.details} -

-
-
-{/if} - -{#if isDefaultVisible()} -
- -
-{/if} - -{#if currentBlog !== null && $publicationColumnVisibility.inner } - {#key currentBlog } -
- -
- {/key} -{/if} - -{#if $publicationColumnVisibility.social } -
-

Social column

-
-{/if} + function showBlogHeader() { + return currentBlog && currentBlogEvent && window.innerWidth < 1140; + } - // #endregion + onDestroy(() => { + // reset visibility + publicationColumnVisibility.reset(); + }); 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); + // Set current columns depending on the publication type + const isBlog = publicationType === "blog"; + publicationColumnVisibility.update((v) => ({ + ...v, + main: !isBlog, + blog: isBlog, + })); + if (isLeaf || isBlog) { + publicationColumnVisibility.update((v) => ({ ...v, toc: false })); + } // Set up the intersection observer. - observer = new IntersectionObserver((entries) => { - entries.forEach((entry) => { - if (entry.isIntersecting && !isLoading && !isDone) { - loadMore(1); - } - }); - }, { threshold: 0.5 }); + observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting && !isLoading && !isDone) { + loadMore(1); + } + }); + }, + { threshold: 0.5 }, + ); loadMore(8); return () => { - window.removeEventListener("hashchange", scrollToElementWithOffset); - window.removeEventListener("resize", setTocVisibilityOnResize); - window.removeEventListener("click", hideTocOnClick); - observer.disconnect(); }; }); - - - -{#if showTocButton && !showToc} - +{#if publicationType !== "blog" || !isLeaf} + +{/if} + + +{#if $publicationColumnVisibility.main} +
+
+
+
+ + {#each leaves as leaf, i} + {#if leaf == null} + + + Error loading content. One or more events could not be loaded. + + {:else} + setLastElementRef(el, i)} + /> + {/if} + {/each} +
+ {#if isLoading} + + {:else if !isDone} + + {:else} +

+ You've reached the end of the publication. +

+ {/if} +
+
+{/if} + + +{#if $publicationColumnVisibility.blog} +
- - - Show Table of Contents --> +
+
+
+ + {#each leaves as leaf, i} + + {/each} +
{/if} - - + {#if showBlogHeader()} + + {/if} +
+ +
+ Unknown + 1.1.1970 +
+
+ This is a very intelligent comment placeholder that applies to + all the content equally well. +
+
+
+ -{/if} --> -
- {#each leaves as leaf, i} - {#if leaf == null} - - - Error loading content. One or more events could not be loaded. - - {:else} - setLastElementRef(el, i)} - /> - {/if} - {/each} -
- {#if isLoading} - - {:else if !isDone} - - {:else} -

You've reached the end of the publication.

- {/if} -
-
- - +{/if} diff --git a/src/lib/components/blog/BlogHeader.svelte b/src/lib/components/blog/BlogHeader.svelte new file mode 100644 index 0000000..f7fc25b --- /dev/null +++ b/src/lib/components/blog/BlogHeader.svelte @@ -0,0 +1,70 @@ + + +{#if title != null} + +
+
+
+ + {publishedAt()} +
+ +
+ {#if image && active} +
+ +
+ {/if} +
+ + {#if hashtags} +
+ {#each hashtags as tag} + {tag} + {/each} +
+ {/if} +
+ {#if active} + + {/if} +
+
+{/if} diff --git a/src/lib/components/util/ArticleNav.svelte b/src/lib/components/util/ArticleNav.svelte index 5049c7e..f7cdd29 100644 --- a/src/lib/components/util/ArticleNav.svelte +++ b/src/lib/components/util/ArticleNav.svelte @@ -1,55 +1,149 @@ - \ No newline at end of file diff --git a/src/lib/components/util/CardActions.svelte b/src/lib/components/util/CardActions.svelte index 177c8ee..6841b30 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(); @@ -74,7 +73,7 @@ -
+
diff --git a/src/lib/components/util/Details.svelte b/src/lib/components/util/Details.svelte index df12ee6..989cac4 100644 --- a/src/lib/components/util/Details.svelte +++ b/src/lib/components/util/Details.svelte @@ -1,7 +1,13 @@ -
- {#if image} -
- {title} + +
+ {#if !isModal} +
+

+
{/if} -
-

{title}

-

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

{title}

+

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

+ {#if version !== '1' } +

Version: {version}

{/if} -

-

Version: {version}

+
{#if summary} -
+

{summary}

{/if} -
-

Index author:

-
+{#if hashtags.length} +
+ {#each hashtags as tag} + #{tag[1]} + {/each} +
+{/if} -
- {#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} +{#if isModal} +
+

+ {#if kind === 30040} + Index author: + {:else} + Author: + {/if} + +

+
+ + +
+ {#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} +
+{/if} -
\ No newline at end of file +{#if !isModal} + +{/if} \ No newline at end of file diff --git a/src/lib/components/util/InlineProfile.svelte b/src/lib/components/util/InlineProfile.svelte index 4b9efe3..dd19c3c 100644 --- a/src/lib/components/util/InlineProfile.svelte +++ b/src/lib/components/util/InlineProfile.svelte @@ -49,7 +49,7 @@ {:else if npub } {username ?? shortenNpub(npub)} diff --git a/src/lib/components/util/Interactions.svelte b/src/lib/components/util/Interactions.svelte new file mode 100644 index 0000000..2f1c913 --- /dev/null +++ b/src/lib/components/util/Interactions.svelte @@ -0,0 +1,93 @@ + + +
+ + + + +
+ + +

Can't like, zap or highlight yet.

+

You should totally check out the discussion though.

+
\ No newline at end of file diff --git a/src/lib/components/util/TocToggle.svelte b/src/lib/components/util/TocToggle.svelte index 7bee061..7aaaa69 100644 --- a/src/lib/components/util/TocToggle.svelte +++ b/src/lib/components/util/TocToggle.svelte @@ -1,17 +1,17 @@ -{#if showTocButton && !showToc} - -{/if} - + + + + + + -{/if} --> \ No newline at end of file +{/if} \ 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/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]; } diff --git a/src/lib/stores.ts b/src/lib/stores.ts index a78e275..e38f0d4 100644 --- a/src/lib/stores.ts +++ b/src/lib/stores.ts @@ -7,12 +7,25 @@ export let alexandriaKinds = readable([30040, 30041, 30818]); export let feedType = writable(FeedType.StandardRelays); -export const publicationColumnVisibility = writable({ - details: false, + +const defaultVisibility = { toc: false, blog: true, main: true, - inner: true, - social: false, + inner: false, + discussion: false, editing: false -}); \ No newline at end of file +}; + +function createVisibilityStore() { + const { subscribe, set, update } = writable({ ...defaultVisibility }); + + return { + subscribe, + set, + update, + reset: () => set({ ...defaultVisibility }) + }; +} + +export const publicationColumnVisibility = createVisibilityStore(); diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index fd29d79..89660e5 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`; }); @@ -42,14 +40,7 @@ -
- - - - -

Pardon our dust! The publication view is currently using an experimental loader, and may be unstable.

-

New to Alexandria? Check out our Getting Started guide to learn more about using the library.

- - +
+
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 1b204f1..9c7ca31 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,7 +1,7 @@ + + + + Pardon our dust! The publication view is currently using an experimental loader, and may be unstable. + + +
{#if !$ndkSignedIn} diff --git a/src/routes/publication/+page.svelte b/src/routes/publication/+page.svelte index 524775b..48b156c 100644 --- a/src/routes/publication/+page.svelte +++ b/src/routes/publication/+page.svelte @@ -5,6 +5,7 @@ import { onDestroy, setContext } from "svelte"; import { PublicationTree } from "$lib/data_structures/publication_tree"; import Processor from "asciidoctor"; + import ArticleNav from "$components/util/ArticleNav.svelte"; let { data }: PageProps = $props(); @@ -59,10 +60,11 @@ {/key} -
+
{#await data.waitable} {:then} diff --git a/src/styles/base.css b/src/styles/base.css index bd6213e..e655206 100644 --- a/src/styles/base.css +++ b/src/styles/base.css @@ -1,3 +1,9 @@ @tailwind base; @tailwind components; -@tailwind utilities; \ No newline at end of file +@tailwind utilities; + +@layer components { + body { + @apply bg-primary-0 dark:bg-primary-1000; + } +} \ No newline at end of file diff --git a/src/styles/publications.css b/src/styles/publications.css index 79cb2da..f5c643c 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; } @@ -104,7 +104,7 @@ } .publication-leather .verseblock pre.content { - @apply text-base font-sans; + @apply text-base font-sans overflow-x-scroll py-1; } .publication-leather .attribution { @@ -253,12 +253,11 @@ /** 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 .card-leather:not(:hover) { + @apply bg-primary-50 dark:bg-primary-1000 opacity-75 transition duration-500 ease-in-out ; } - - .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 .group { + @apply bg-transparent; } } } 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 { diff --git a/tailwind.config.cjs b/tailwind.config.cjs index a0d5fc6..e28c2eb 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -22,11 +22,11 @@ const config = { 400: '#ad8351', 500: '#c6a885', 600: '#795c39', - 700: '#574229', - 800: '#342718', - 900: '#231a10', - 950: '#17110A', - 1000: '#110d08', + 700: '#564a3e', + 800: '#3c352c', + 900: '#2a241c', + 950: '#1d1812', + 1000: '#15110d', }, success: { 50: '#e3f2e7',