Browse Source

Merge upstream/master' into feature/network-visual

master
limina1 1 year ago
parent
commit
46a274ee24
  1. 4147
      package-lock.json
  2. 67
      package.json
  3. 4651
      pnpm-lock.yaml
  4. 6
      src/app.d.ts
  5. 99
      src/lib/components/Article.svelte
  6. 38
      src/lib/components/Preview.svelte
  7. 67
      src/lib/parser.ts
  8. 1
      src/routes/+layout.server.ts
  9. 24
      src/routes/+layout.ts
  10. 56
      src/routes/+page.svelte
  11. 6
      src/routes/about/+page.svelte
  12. 4
      src/routes/new/compose/+page.svelte
  13. 18
      src/routes/new/edit/+page.svelte
  14. 31
      src/routes/publication/+error.svelte
  15. 44
      src/routes/publication/+page.svelte
  16. 42
      src/routes/publication/+page.ts
  17. 20
      svelte.config.js

4147
package-lock.json generated

File diff suppressed because it is too large Load Diff

67
package.json

@ -13,45 +13,38 @@ @@ -13,45 +13,38 @@
"format": "prettier --plugin-search-dir . --write ."
},
"dependencies": {
"@nostr-dev-kit/ndk": "^2.10.0",
"@nostr-dev-kit/ndk-cache-dexie": "^2.5.1",
"@popperjs/core": "^2.11.8",
"@sveltejs/vite-plugin-svelte": "^3.0.1",
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.10",
"asciidoctor": "^3.0.4",
"@nostr-dev-kit/ndk": "2.10.x",
"@nostr-dev-kit/ndk-cache-dexie": "2.5.x",
"@popperjs/core": "2.11.x",
"@tailwindcss/forms": "0.5.x",
"@tailwindcss/typography": "0.5.x",
"asciidoctor": "3.0.x",
"d3": "^7.9.0",
"he": "^1.2.0",
"markdown-it": "^14.0.0",
"markdown-it-plain-text": "^0.3.0",
"marked": "^11.1.1",
"nostr-tools": "^2.1.4",
"showdown": "^2.1.0",
"svelte-add": "2023.12.16-0.0",
"tailwind-merge": "^2.2.1"
"he": "1.2.x",
"nostr-tools": "2.10.x"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^3.1.1",
"@sveltejs/adapter-static": "^3.0.6",
"@sveltejs/kit": "^2.4.3",
"@types/he": "^1.2.3",
"@types/markdown-it": "^13.0.7",
"@types/node": "^22.5.4",
"@types/showdown": "^2.0.6",
"autoprefixer": "^10.4.17",
"eslint-plugin-svelte": "^2.35.1",
"flowbite": "^2.2.1",
"flowbite-svelte": "^0.44.22",
"flowbite-svelte-icons": "^1.6.1",
"postcss": "^8.4.33",
"postcss-load-config": "^5.0.2",
"prettier": "^3.2.5",
"prettier-plugin-svelte": "^3.1.2",
"svelte": "^4.2.9",
"svelte-check": "^3.6.3",
"tailwindcss": "^3.4.1",
"tslib": "^2.6.2",
"typescript": "^5.3.3",
"vite": "^5.0.12"
"@sveltejs/adapter-auto": "3.x",
"@sveltejs/adapter-static": "3.x",
"@sveltejs/kit": "2.x",
"@sveltejs/vite-plugin-svelte": "4.x",
"@types/he": "1.2.x",
"@types/node": "22.x",
"autoprefixer": "10.x",
"eslint-plugin-svelte": "2.x",
"flowbite": "2.x",
"flowbite-svelte": "0.x",
"flowbite-svelte-icons": "2.x",
"postcss": "8.x",
"postcss-load-config": "6.x",
"prettier": "3.x",
"prettier-plugin-svelte": "3.x",
"svelte": "5.x",
"svelte-check": "4.x",
"tailwind-merge": "^2.5.5",
"tailwindcss": "3.x",
"tslib": "2.8.x",
"typescript": "5.7.x",
"vite": "5.x"
}
}

4651
pnpm-lock.yaml

File diff suppressed because it is too large Load Diff

6
src/app.d.ts vendored

@ -4,7 +4,11 @@ declare global { @@ -4,7 +4,11 @@ declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
interface PageData {
ndk?: NDK;
parser?: Pharos;
waitable?: Promise<any>;
}
// interface Platform {}
}
}

99
src/lib/components/Article.svelte

@ -1,33 +1,33 @@ @@ -1,33 +1,33 @@
<script lang="ts">
import { ndk } from '$lib/ndk';
import type { NDKEvent } from '@nostr-dev-kit/ndk';
import { page } from '$app/stores';
import { Button, Heading, Sidebar, SidebarGroup, SidebarItem, SidebarWrapper, Skeleton, TextPlaceholder, Tooltip } from 'flowbite-svelte';
import { onMount } from 'svelte';
import { BookOutline } from 'flowbite-svelte-icons';
import Pharos, { parser } from '$lib/parser';
import Preview from './Preview.svelte';
export let index: NDKEvent | null | undefined;
$parser ??= new Pharos($ndk);
$: activeHash = $page.url.hash;
const getContentRoot = async (index?: NDKEvent | null | undefined): Promise<string | null> => {
if (!index) {
return null;
import {
Button,
Sidebar,
SidebarGroup,
SidebarItem,
SidebarWrapper,
Skeleton,
TextPlaceholder,
Tooltip,
} from "flowbite-svelte";
import { onMount } from "svelte";
import { BookOutline } from "flowbite-svelte-icons";
import Preview from "./Preview.svelte";
import { pharosInstance } from "$lib/parser";
import { page } from "$app/state";
let { rootId }: { rootId: string } = $props();
if (rootId !== $pharosInstance.getRootIndexId()) {
console.error("Root ID does not match parser root index ID");
}
await $parser.fetch(index);
return $parser.getRootIndexId();
};
let activeHash = $state(page.url.hash);
function normalizeHashPath(str: string): string {
return str
.toLowerCase()
.replace(/\s+/g, '-')
.replace(/[^\w-]/g, '');
.replace(/\s+/g, "-")
.replace(/[^\w-]/g, "");
}
function scrollToElementWithOffset() {
@ -41,7 +41,7 @@ @@ -41,7 +41,7 @@
window.scrollTo({
top: offsetPosition,
behavior: 'auto',
behavior: "auto",
});
}
}
@ -66,7 +66,7 @@ @@ -66,7 +66,7 @@
const hideTocOnClick = (ev: MouseEvent) => {
const target = ev.target as HTMLElement;
if (target.closest('.sidebar-leather') || target.closest('.btn-leather')) {
if (target.closest(".sidebar-leather") || target.closest(".btn-leather")) {
return;
}
@ -79,47 +79,36 @@ @@ -79,47 +79,36 @@
// Always check whether the TOC sidebar should be visible.
setTocVisibilityOnResize();
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
scrollToElementWithOffset();
window.addEventListener('resize', setTocVisibilityOnResize);
window.addEventListener('click', hideTocOnClick);
window.addEventListener("resize", setTocVisibilityOnResize);
window.addEventListener("click", hideTocOnClick);
return () => {
window.removeEventListener('hashchange', scrollToElementWithOffset);
window.removeEventListener('resize', setTocVisibilityOnResize);
window.removeEventListener('click', hideTocOnClick);
window.removeEventListener("hashchange", scrollToElementWithOffset);
window.removeEventListener("resize", setTocVisibilityOnResize);
window.removeEventListener("click", hideTocOnClick);
};
});
</script>
{#await getContentRoot(index)}
<Sidebar class='sidebar-leather fixed top-20 left-0 px-4 w-60'>
<SidebarWrapper>
<Skeleton/>
</SidebarWrapper>
</Sidebar>
<TextPlaceholder class='max-w-2xl'/>
{:then rootId}
{#if rootId}
{#if showTocButton && !showToc}
{#if showTocButton && !showToc}
<Button
class='btn-leather fixed top-20 left-4 h-6 w-6'
class="btn-leather fixed top-20 left-4 h-6 w-6"
outline={true}
on:click={ev => {
on:click={(ev) => {
showToc = true;
ev.stopPropagation();
}}
>
<BookOutline />
</Button>
<Tooltip>
Show Table of Contents
</Tooltip>
{/if}
<!-- TODO: Get TOC from parser. -->
<!-- {#if showToc}
<Tooltip>Show Table of Contents</Tooltip>
{/if}
<!-- TODO: Get TOC from parser. -->
<!-- {#if showToc}
<Sidebar class='sidebar-leather fixed top-20 left-0 px-4 w-60' {activeHash}>
<SidebarWrapper>
<SidebarGroup class='sidebar-group-leather overflow-y-scroll'>
@ -133,14 +122,10 @@ @@ -133,14 +122,10 @@
</SidebarGroup>
</SidebarWrapper>
</Sidebar>
{/if} -->
<div class='flex flex-col space-y-4 max-w-2xl'>
<Preview rootId={rootId} />
</div>
{:else}
<!-- TODO: Display empty state. -->
{/if}
{/await}
{/if} -->
<div class="flex flex-col space-y-4 max-w-2xl">
<Preview {rootId} />
</div>
<style>
:global(.sidebar-group-leather) {

38
src/lib/components/Preview.svelte

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
<script lang="ts">
import { parser, SiblingSearchDirection } from "$lib/parser";
import { page } from "$app/state";
import { pharosInstance, SiblingSearchDirection } from "$lib/parser";
import { Button, ButtonGroup, CloseButton, Heading, Input, P, Textarea, Tooltip } from "flowbite-svelte";
import { CaretDownSolid, CaretUpSolid, EditOutline } from "flowbite-svelte-icons";
import { createEventDispatcher } from "svelte";
@ -16,9 +17,9 @@ @@ -16,9 +17,9 @@
const dispatch = createEventDispatcher();
let currentContent: string = $parser.getContent(rootId);
let title: string | undefined = $parser.getIndexTitle(rootId);
let orderedChildren: string[] = $parser.getOrderedChildIds(rootId);
let currentContent: string = $pharosInstance.getContent(rootId);
let title: string | undefined = $pharosInstance.getIndexTitle(rootId);
let orderedChildren: string[] = $pharosInstance.getOrderedChildIds(rootId);
let isEditing: boolean = false;
let hasCursor: boolean = false;
@ -37,8 +38,8 @@ @@ -37,8 +38,8 @@
if (needsUpdate) {
updateCount++;
needsUpdate = false;
title = $parser.getIndexTitle(rootId);
currentContent = $parser.getContent(rootId);
title = $pharosInstance.getIndexTitle(rootId);
currentContent = $pharosInstance.getContent(rootId);
}
if (subtreeNeedsUpdate) {
@ -46,7 +47,7 @@ @@ -46,7 +47,7 @@
subtreeNeedsUpdate = false;
const prevChildCount = orderedChildren.length;
orderedChildren = $parser.getOrderedChildIds(rootId);
orderedChildren = $pharosInstance.getOrderedChildIds(rootId);
const newChildCount = orderedChildren.length;
// If the number of children has changed, a child has been added or removed, and a child may
@ -62,8 +63,8 @@ @@ -62,8 +63,8 @@
$: {
if (parentId && allowEditing) {
// Check for previous/next siblings on load
const previousSibling = $parser.getNearestSibling(rootId, depth - 1, SiblingSearchDirection.Previous);
const nextSibling = $parser.getNearestSibling(rootId, depth - 1, SiblingSearchDirection.Next);
const previousSibling = $pharosInstance.getNearestSibling(rootId, depth - 1, SiblingSearchDirection.Previous);
const nextSibling = $pharosInstance.getNearestSibling(rootId, depth - 1, SiblingSearchDirection.Next);
// Hide arrows if no siblings exist
hasPreviousSibling = !!previousSibling[0];
@ -113,7 +114,7 @@ @@ -113,7 +114,7 @@
}
$parser.updateEventContent(id, currentContent);
$pharosInstance.updateEventContent(id, currentContent);
}
isEditing = !editing;
@ -121,25 +122,25 @@ @@ -121,25 +122,25 @@
const moveUp = (rootId: string, parentId: string) => {
// Get the previous sibling and its index
const [prevSiblingId, prevIndex] = $parser.getNearestSibling(rootId, depth - 1, SiblingSearchDirection.Previous);
const [prevSiblingId, prevIndex] = $pharosInstance.getNearestSibling(rootId, depth - 1, SiblingSearchDirection.Previous);
if (!prevSiblingId || prevIndex == null) {
return;
}
// Move the current event before the previous sibling.
$parser.moveEvent(rootId, prevSiblingId, false);
$pharosInstance.moveEvent(rootId, prevSiblingId, false);
needsUpdate = true;
};
const moveDown = (rootId: string, parentId: string) => {
// Get the next sibling and its index
const [nextSiblingId, nextIndex] = $parser.getNearestSibling(rootId, depth - 1, SiblingSearchDirection.Next);
const [nextSiblingId, nextIndex] = $pharosInstance.getNearestSibling(rootId, depth - 1, SiblingSearchDirection.Next);
if (!nextSiblingId || nextIndex == null) {
return;
}
// Move the current event after the next sibling
$parser.moveEvent(rootId, nextSiblingId, true);
$pharosInstance.moveEvent(rootId, nextSiblingId, true);
needsUpdate = true;
};
</script>
@ -148,7 +149,7 @@ @@ -148,7 +149,7 @@
<!-- svelte-ignore a11y-no-static-element-interactions -->
<section
id={rootId}
class={`note-leather flex space-x-2 justify-between ${sectionClass}`}
class={`note-leather flex space-x-2 justify-between text-wrap break-words ${sectionClass}`}
on:mouseenter={handleMouseEnter}
on:mouseleave={handleMouseLeave}
>
@ -157,7 +158,7 @@ @@ -157,7 +158,7 @@
{#key updateCount}
{#if isEditing}
<form class='w-full'>
<Textarea class='textarea-leather w-full' bind:value={currentContent}>
<Textarea class='textarea-leather w-full whitespace-normal' bind:value={currentContent}>
<div slot='footer' class='flex space-x-2 justify-end'>
<Button
type='reset'
@ -172,7 +173,6 @@ @@ -172,7 +173,6 @@
type='submit'
class='btn-leather min-w-fit'
size='sm'
solid
on:click={() => toggleEditing(rootId, true)}
>
Save
@ -181,13 +181,13 @@ @@ -181,13 +181,13 @@
</Textarea>
</form>
{:else}
<P firstupper={isSectionStart}>
<P class='whitespace-normal' firstupper={isSectionStart}>
{@html currentContent}
</P>
{/if}
{/key}
{:else}
<div class='flex flex-col space-y-2'>
<div class='flex flex-col space-y-2 w-full'>
{#if isEditing}
<ButtonGroup class='w-full'>
<Input type='text' class='input-leather' size='lg' bind:value={title}>

67
src/lib/parser.ts

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
import NDK, { NDKEvent } from '@nostr-dev-kit/ndk';
import { getNdkInstance } from './ndk';
import asciidoctor, {
import asciidoctor from 'asciidoctor';
import type {
AbstractBlock,
AbstractNode,
Asciidoctor,
@ -8,7 +9,7 @@ import asciidoctor, { @@ -8,7 +9,7 @@ import asciidoctor, {
Document,
Extensions,
Section,
type ProcessorOptions
ProcessorOptions,
} from 'asciidoctor';
import he from 'he';
import { writable, type Writable } from 'svelte/store';
@ -259,7 +260,8 @@ export default class Pharos { @@ -259,7 +260,8 @@ export default class Pharos {
* - Paragraph: The content is returned as a plain string.
*/
getContent(id: string): string {
const block = this.nodes.get(id) as AbstractBlock;
const normalizedId = this.normalizeId(id);
const block = this.nodes.get(normalizedId!) as AbstractBlock;
switch (block.getContext()) {
case 'paragraph':
@ -705,6 +707,7 @@ export default class Pharos { @@ -705,6 +707,7 @@ export default class Pharos {
*/
private generateIndexEvent(nodeId: string, pubkey: string): NDKEvent {
const title = (this.nodes.get(nodeId)! as AbstractBlock).getTitle();
// TODO: Use a tags as per NIP-62.
const childTags = Array.from(this.indexToChildEventsMap.get(nodeId)!)
.map(id => ['#e', this.eventIds.get(id)!]);
@ -825,169 +828,169 @@ export default class Pharos { @@ -825,169 +828,169 @@ export default class Pharos {
switch (context) {
case 'admonition':
blockNumber = this.contextCounters.get('admonition') ?? 0;
blockId = `${documentId}_admonition_${blockNumber++}`;
blockId = `${documentId}-admonition-${blockNumber++}`;
this.contextCounters.set('admonition', blockNumber);
break;
case 'audio':
blockNumber = this.contextCounters.get('audio') ?? 0;
blockId = `${documentId}_audio_${blockNumber++}`;
blockId = `${documentId}-audio-${blockNumber++}`;
this.contextCounters.set('audio', blockNumber);
break;
case 'colist':
blockNumber = this.contextCounters.get('colist') ?? 0;
blockId = `${documentId}_colist_${blockNumber++}`;
blockId = `${documentId}-colist-${blockNumber++}`;
this.contextCounters.set('colist', blockNumber);
break;
case 'dlist':
blockNumber = this.contextCounters.get('dlist') ?? 0;
blockId = `${documentId}_dlist_${blockNumber++}`;
blockId = `${documentId}-dlist-${blockNumber++}`;
this.contextCounters.set('dlist', blockNumber);
break;
case 'document':
blockNumber = this.contextCounters.get('document') ?? 0;
blockId = `${documentId}_document_${blockNumber++}`;
blockId = `${documentId}-document-${blockNumber++}`;
this.contextCounters.set('document', blockNumber);
break;
case 'example':
blockNumber = this.contextCounters.get('example') ?? 0;
blockId = `${documentId}_example_${blockNumber++}`;
blockId = `${documentId}-example-${blockNumber++}`;
this.contextCounters.set('example', blockNumber);
break;
case 'floating_title':
blockNumber = this.contextCounters.get('floating_title') ?? 0;
blockId = `${documentId}_floating_title_${blockNumber++}`;
blockId = `${documentId}-floating-title-${blockNumber++}`;
this.contextCounters.set('floating_title', blockNumber);
break;
case 'image':
blockNumber = this.contextCounters.get('image') ?? 0;
blockId = `${documentId}_image_${blockNumber++}`;
blockId = `${documentId}-image-${blockNumber++}`;
this.contextCounters.set('image', blockNumber);
break;
case 'list_item':
blockNumber = this.contextCounters.get('list_item') ?? 0;
blockId = `${documentId}_list_item_${blockNumber++}`;
blockId = `${documentId}-list-item-${blockNumber++}`;
this.contextCounters.set('list_item', blockNumber);
break;
case 'listing':
blockNumber = this.contextCounters.get('listing') ?? 0;
blockId = `${documentId}_listing_${blockNumber++}`;
blockId = `${documentId}-listing-${blockNumber++}`;
this.contextCounters.set('listing', blockNumber);
break;
case 'literal':
blockNumber = this.contextCounters.get('literal') ?? 0;
blockId = `${documentId}_literal_${blockNumber++}`;
blockId = `${documentId}-literal-${blockNumber++}`;
this.contextCounters.set('literal', blockNumber);
break;
case 'olist':
blockNumber = this.contextCounters.get('olist') ?? 0;
blockId = `${documentId}_olist_${blockNumber++}`;
blockId = `${documentId}-olist-${blockNumber++}`;
this.contextCounters.set('olist', blockNumber);
break;
case 'open':
blockNumber = this.contextCounters.get('open') ?? 0;
blockId = `${documentId}_open_${blockNumber++}`;
blockId = `${documentId}-open-${blockNumber++}`;
this.contextCounters.set('open', blockNumber);
break;
case 'page_break':
blockNumber = this.contextCounters.get('page_break') ?? 0;
blockId = `${documentId}_page_break_${blockNumber++}`;
blockId = `${documentId}-page-break-${blockNumber++}`;
this.contextCounters.set('page_break', blockNumber);
break;
case 'paragraph':
blockNumber = this.contextCounters.get('paragraph') ?? 0;
blockId = `${documentId}_paragraph_${blockNumber++}`;
blockId = `${documentId}-paragraph-${blockNumber++}`;
this.contextCounters.set('paragraph', blockNumber);
break;
case 'pass':
blockNumber = this.contextCounters.get('pass') ?? 0;
blockId = `${documentId}_pass_${blockNumber++}`;
blockId = `${documentId}-pass-${blockNumber++}`;
this.contextCounters.set('pass', blockNumber);
break;
case 'preamble':
blockNumber = this.contextCounters.get('preamble') ?? 0;
blockId = `${documentId}_preamble_${blockNumber++}`;
blockId = `${documentId}-preamble-${blockNumber++}`;
this.contextCounters.set('preamble', blockNumber);
break;
case 'quote':
blockNumber = this.contextCounters.get('quote') ?? 0;
blockId = `${documentId}_quote_${blockNumber++}`;
blockId = `${documentId}-quote-${blockNumber++}`;
this.contextCounters.set('quote', blockNumber);
break;
case 'section':
blockNumber = this.contextCounters.get('section') ?? 0;
blockId = `${documentId}_section_${blockNumber++}`;
blockId = `${documentId}-section-${blockNumber++}`;
this.contextCounters.set('section', blockNumber);
break;
case 'sidebar':
blockNumber = this.contextCounters.get('sidebar') ?? 0;
blockId = `${documentId}_sidebar_${blockNumber++}`;
blockId = `${documentId}-sidebar-${blockNumber++}`;
this.contextCounters.set('sidebar', blockNumber);
break;
case 'table':
blockNumber = this.contextCounters.get('table') ?? 0;
blockId = `${documentId}_table_${blockNumber++}`;
blockId = `${documentId}-table-${blockNumber++}`;
this.contextCounters.set('table', blockNumber);
break;
case 'table_cell':
blockNumber = this.contextCounters.get('table_cell') ?? 0;
blockId = `${documentId}_table_cell_${blockNumber++}`;
blockId = `${documentId}-table-cell-${blockNumber++}`;
this.contextCounters.set('table_cell', blockNumber);
break;
case 'thematic_break':
blockNumber = this.contextCounters.get('thematic_break') ?? 0;
blockId = `${documentId}_thematic_break_${blockNumber++}`;
blockId = `${documentId}-thematic-break-${blockNumber++}`;
this.contextCounters.set('thematic_break', blockNumber);
break;
case 'toc':
blockNumber = this.contextCounters.get('toc') ?? 0;
blockId = `${documentId}_toc_${blockNumber++}`;
blockId = `${documentId}-toc-${blockNumber++}`;
this.contextCounters.set('toc', blockNumber);
break;
case 'ulist':
blockNumber = this.contextCounters.get('ulist') ?? 0;
blockId = `${documentId}_ulist_${blockNumber++}`;
blockId = `${documentId}-ulist-${blockNumber++}`;
this.contextCounters.set('ulist', blockNumber);
break;
case 'verse':
blockNumber = this.contextCounters.get('verse') ?? 0;
blockId = `${documentId}_verse_${blockNumber++}`;
blockId = `${documentId}-verse-${blockNumber++}`;
this.contextCounters.set('verse', blockNumber);
break;
case 'video':
blockNumber = this.contextCounters.get('video') ?? 0;
blockId = `${documentId}_video_${blockNumber++}`;
blockId = `${documentId}-video-${blockNumber++}`;
this.contextCounters.set('video', blockNumber);
break;
default:
blockNumber = this.contextCounters.get('block') ?? 0;
blockId = `${documentId}_block_${blockNumber++}`;
blockId = `${documentId}-block-${blockNumber++}`;
this.contextCounters.set('block', blockNumber);
break;
}
@ -1063,4 +1066,4 @@ export default class Pharos { @@ -1063,4 +1066,4 @@ export default class Pharos {
// #endregion
}
export const parser: Writable<Pharos> = writable(new Pharos(getNdkInstance()));
export const pharosInstance: Writable<Pharos> = writable();

1
src/routes/+layout.server.ts

@ -1 +0,0 @@ @@ -1 +0,0 @@
export const ssr = false;

24
src/routes/+layout.ts

@ -1 +1,23 @@ @@ -1 +1,23 @@
export const prerender = true;
import NDK from "@nostr-dev-kit/ndk";
import type { LayoutLoad } from "./$types";
import { standardRelays } from "$lib/consts";
import Pharos, { pharosInstance } from "$lib/parser";
export const ssr = false;
export const load: LayoutLoad = () => {
const ndk = new NDK({
autoConnectUserRelays: true,
enableOutboxModel: true,
explicitRelayUrls: standardRelays,
});
ndk.connect().then(() => console.debug("ndk connected"));
const parser = new Pharos(ndk);
pharosInstance.set(parser);
return {
ndk,
parser,
};
};

56
src/routes/+page.svelte

@ -1,26 +1,41 @@ @@ -1,26 +1,41 @@
<script lang="ts">
import ArticleHeader from "$lib/components/ArticleHeader.svelte";
import { FeedType, indexKind, standardRelays } from "$lib/consts";
import { ndk } from "$lib/ndk";
import { filterValidIndexEvents } from "$lib/utils";
import { NDKEvent, NDKRelaySet, type NDKUser } from "@nostr-dev-kit/ndk";
import { Button, Dropdown, Radio, Skeleton } from "flowbite-svelte";
import { ChevronDownOutline } from "flowbite-svelte-icons";
<script lang='ts'>
import ArticleHeader from '$lib/components/ArticleHeader.svelte';
import { FeedType, indexKind, standardRelays } from '$lib/consts';
import { filterValidIndexEvents } from '$lib/utils';
import NDK, { NDKEvent, NDKRelaySet, type NDKUser } from '@nostr-dev-kit/ndk';
import { Button, Dropdown, Radio, Skeleton } from 'flowbite-svelte';
import { ChevronDownOutline } from 'flowbite-svelte-icons';
import type { PageData } from './$types';
import { setContext } from 'svelte';
let { data }: { data: PageData } = $props();
let ndk: NDK = data.ndk;
let user: NDKUser | null | undefined = $state(ndk.activeUser);
let readRelays: string[] | null | undefined = $state(user?.relayUrls);
let userFollows: Set<NDKUser> | null | undefined = $state(null);
let feedType: FeedType = $state(FeedType.Relays);
$effect(() => {
if (user) {
user.follows().then(follows => userFollows = follows);
}
});
const getEvents = (): Promise<Set<NDKEvent>> =>
// @ts-ignore
$ndk.fetchEvents(
ndk.fetchEvents(
{ kinds: [indexKind] },
{
groupable: true,
skipVerification: false,
skipValidation: false
},
NDKRelaySet.fromRelayUrls(standardRelays, $ndk)
NDKRelaySet.fromRelayUrls(standardRelays, ndk)
).then(filterValidIndexEvents);
const getEventsFromUserRelays = (userRelays: string[]): Promise<Set<NDKEvent>> => {
return $ndk
return ndk
.fetchEvents(
// @ts-ignore
{ kinds: [indexKind] },
@ -35,7 +50,7 @@ @@ -35,7 +50,7 @@
}
const getEventsFromUserFollows = (follows: Set<NDKUser>, userRelays?: string[]): Promise<Set<NDKEvent>> => {
return $ndk
return ndk
.fetchEvents(
{
authors: Array.from(follows ?? []).map(user => user.pubkey),
@ -74,17 +89,6 @@ @@ -74,17 +89,6 @@
}
return skeletonIds;
}
let user: NDKUser | null | undefined;
let readRelays: string[] | null | undefined;
let userFollows: Set<NDKUser> | null | undefined;
let feedType: FeedType = FeedType.Relays;
$: {
user = $ndk.activeUser;
readRelays = user?.relayUrls;
user?.follows().then(follows => userFollows = follows);
}
</script>
<div class='leather flex flex-col flex-grow-0 space-y-4 overflow-y-auto w-max p-2'>
@ -92,7 +96,7 @@ @@ -92,7 +96,7 @@
{#if user == null || readRelays == null}
{#await getEvents()}
{#each getSkeletonIds() as id}
<Skeleton size='lg' id={id} />
<Skeleton size='lg' />
{/each}
{:then events}
{#if events.size > 0}
@ -120,7 +124,7 @@ @@ -120,7 +124,7 @@
{#if feedType === FeedType.Relays && readRelays != null}
{#await getEventsFromUserRelays(readRelays)}
{#each getSkeletonIds() as id}
<Skeleton size='lg' id={id} />
<Skeleton size='lg' />
{/each}
{:then events}
{#if events.size > 0}
@ -134,7 +138,7 @@ @@ -134,7 +138,7 @@
{:else if feedType === FeedType.Follows && userFollows != null}
{#await getEventsFromUserFollows(userFollows, readRelays)}
{#each getSkeletonIds() as id}
<Skeleton size='lg' id={id} />
<Skeleton size='lg' />
{/each}
{:then events}
{#if events.size > 0}

6
src/routes/about/+page.svelte

@ -7,9 +7,9 @@ @@ -7,9 +7,9 @@
<div class='w-full flex justify-center'>
<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'>About</Heading>
<p>Alexandria is a <a href="https://wikistr.com/nkbip-01" class="text-indigo-600 underline">Nostr Knowledge Base (NKB)</a> and a reader for long-form articles.
It is produced by <a href="https://wikistr.com/gitcitadel-project" class="text-indigo-600 underline">GitCitadel</a>.</p>
<p>Alexandria is an editor and generator for <a href="https://github.com/nostr-protocol/nips/pull/1600" class="text-indigo-600 underline">curated publications</a> and will soon also be a reader for long-form articles and wiki pages.
It is produced by the <a href="https://wikistr.com/gitcitadel-project" class="text-indigo-600 underline">GitCitadel project team</a>.</p>
<p>Please submit support issues on the <a href="https://gitworkshop.dev/r/naddr1qvzqqqrhnypzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqy88wumn8ghj7mn0wvhxcmmv9uqq5emfw33kjarpv3jkcs83wav" class="text-indigo-600 underline">GitWorkshop page</a> and follow us on <a href="https://github.com/ShadowySupercode/gitcitadel" class="text-indigo-600 underline">GitHub</a>.</p>
<p>Please submit support issues on the <a href="https://gitcitadel.com/r/naddr1qvzqqqrhnypzplfq3m5v3u5r0q9f255fdeyz8nyac6lagssx8zy4wugxjs8ajf7pqy88wumn8ghj7mn0wvhxcmmv9uqq5emfw33kjarpv3jkcs83wav" class="text-indigo-600 underline">project repo page</a> and follow us on <a href="https://github.com/ShadowySupercode/gitcitadel" class="text-indigo-600 underline">GitHub</a> and <a href="https://geyser.fund/project/gitcitadel" class="text-indigo-600 underline">Geyserfund</a>.</p>
</main>
</div>

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

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
<script lang='ts'>
import Preview from "$lib/components/Preview.svelte";
import { parser } from "$lib/parser";
import { pharosInstance } from "$lib/parser";
import { Heading } from "flowbite-svelte";
let treeNeedsUpdate: boolean = false;
@ -17,7 +17,7 @@ @@ -17,7 +17,7 @@
<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>
{#key treeUpdateCount}
<Preview rootId={$parser.getRootIndexId()} allowEditing={true} bind:needsUpdate={treeNeedsUpdate} />
<Preview rootId={$pharosInstance.getRootIndexId()} allowEditing={true} bind:needsUpdate={treeNeedsUpdate} />
{/key}
</main>
</div>

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

@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
import { Heading, Textarea, Toolbar, ToolbarButton, Tooltip } from "flowbite-svelte";
import { CodeOutline, EyeSolid, PaperPlaneOutline } from "flowbite-svelte-icons";
import Preview from "$lib/components/Preview.svelte";
import Pharos, { parser } from "$lib/parser";
import Pharos, { pharosInstance } from "$lib/parser";
import { ndk } from "$lib/ndk";
import { goto } from "$app/navigation";
@ -15,16 +15,16 @@ @@ -15,16 +15,16 @@
const showPreview = () => {
try {
$parser ??= new Pharos($ndk);
$parser.reset();
$parser.parse(editorText);
$pharosInstance ??= new Pharos($ndk);
$pharosInstance.reset();
$pharosInstance.parse(editorText);
} catch (e) {
console.error(e);
// TODO: Indicate error in UI.
return;
}
rootIndexId = $parser.getRootIndexId();
rootIndexId = $pharosInstance.getRootIndexId();
isEditing = false;
};
@ -34,15 +34,15 @@ @@ -34,15 +34,15 @@
const prepareReview = () => {
try {
$parser.reset();
$parser.parse(editorText);
$pharosInstance.reset();
$pharosInstance.parse(editorText);
} catch (e) {
console.error(e);
// TODO: Indicate error in UI.
return;
}
$parser.generate($ndk.activeUser?.pubkey!);
$pharosInstance.generate($ndk.activeUser?.pubkey!);
goto('/new/compose');
}
</script>
@ -55,7 +55,7 @@ @@ -55,7 +55,7 @@
<Textarea
id='article-content'
class='textarea-leather'
rows=8
rows={8}
placeholder='Write AsciiDoc content'
bind:value={editorText}
>

31
src/routes/publication/+error.svelte

@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
<script lang='ts'>
import { invalidateAll, goto } from '$app/navigation';
import { Alert, P, Button } from 'flowbite-svelte';
import { ExclamationCircleOutline } from 'flowbite-svelte-icons';
import { page } from '$app/state';
</script>
<main>
<Alert>
<div class='flex items-center space-x-2'>
<ExclamationCircleOutline class='w-6 h-6' />
<span class='text-lg font-medium'>
Failed to load publication.
</span>
</div>
<P size='sm'>
Alexandria failed to find one or more of the events comprising this publication.
</P>
<P size='xs'>
{page.error?.message}
</P>
<div class='flex space-x-2'>
<Button class='btn-leather w-fit' size='sm' onclick={() => invalidateAll()}>
Try Again
</Button>
<Button class='btn-leather w-fit' size='sm' outline onclick={() => goto('/')}>
Return to Home
</Button>
</div>
</Alert>
</main>

44
src/routes/publication/+page.svelte

@ -1,42 +1,18 @@ @@ -1,42 +1,18 @@
<script lang="ts">
import { page } from '$app/stores';
import Article from '$lib/components/Article.svelte';
import { ndk } from '$lib/ndk';
import type { NDKEvent } from '@nostr-dev-kit/ndk';
import { TextPlaceholder } from 'flowbite-svelte';
import Article from "$lib/components/Article.svelte";
import { TextPlaceholder } from "flowbite-svelte";
import type { PageData } from "./$types";
import { onDestroy } from "svelte";
const id = $page.url.searchParams.get('id');
const dTag = $page.url.searchParams.get('d');
let { data }: { data: PageData } = $props();
let event: NDKEvent | null | undefined;
if (id) {
$ndk.fetchEvent(id)
.then(ev => {
event = ev;
})
.catch(err => {
console.error(err);
// TODO: Redirect to 404 page.
});
} else if (dTag) {
$ndk.fetchEvent({ '#d': [dTag] })
.then(ev => {
event = ev;
})
.catch(err => {
console.error(err);
// TODO: Redirect to 404 page.
});
} else {
// TODO: Redirect to 400 page.
}
onDestroy(() => data.parser.reset());
</script>
<main>
{#await event}
<TextPlaceholder size='xxl' />
{:then ev}
<Article index={ev} />
{#await data.waitable}
<TextPlaceholder size="xxl" />
{:then}
<Article rootId={data.parser.getRootIndexId()} />
{/await}
</main>

42
src/routes/publication/+page.ts

@ -0,0 +1,42 @@ @@ -0,0 +1,42 @@
import { error } from '@sveltejs/kit';
import type { NDKEvent } from '@nostr-dev-kit/ndk';
import type { PageLoad } from './$types';
import { pharosInstance } from '$lib/parser';
export const load: PageLoad = async ({ url, parent }) => {
const id = url.searchParams.get('id');
const dTag = url.searchParams.get('d');
const { ndk, parser } = await parent();
let eventPromise: Promise<NDKEvent | null>;
let indexEvent: NDKEvent | null;
if (id) {
eventPromise = ndk.fetchEvent(id)
.then((ev: NDKEvent | null) => {
return ev;
})
.catch((err: any) => {
error(404, `Failed to fetch publication root event for ID: ${id}\n${err}`);
});
} else if (dTag) {
eventPromise = ndk.fetchEvent({ '#d': [dTag] })
.then((ev: NDKEvent | null) => {
return ev;
})
.catch((err: any) => {
error(404, `Failed to fetch publication root event for d tag: ${dTag}\n${err}`);
});
} else {
error(400, 'No publication root event ID or d tag provided.');
}
indexEvent = await eventPromise as NDKEvent;
const fetchPromise = parser.fetch(indexEvent);
return {
waitable: fetchPromise,
};
};

20
svelte.config.js

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import adapter from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
import adapter from "@sveltejs/adapter-static";
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
/** @type {import('@sveltejs/kit').Config} */
const config = {
@ -10,18 +10,18 @@ const config = { @@ -10,18 +10,18 @@ const config = {
kit: {
// Static adapter
adapter: adapter({
pages: 'build',
assets: 'build',
fallback: null, // TODO: Create a 404.html page.
pages: "build",
assets: "build",
fallback: "index.html",
precompress: false,
strict: true,
}),
alias: {
$lib: 'src/lib',
$components: 'src/lib/components',
$cards: 'src/lib/cards'
}
}
$lib: "src/lib",
$components: "src/lib/components",
$cards: "src/lib/cards",
},
},
};
export default config;

Loading…
Cancel
Save