diff --git a/package.json b/package.json index 3eb07e02..720941da 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ "@welshman/util": "^0.9.0-pre5", "cheerio": "^1.2.0", "compressorjs-next": "^1.1.2", + "dompurify": "^3.4.13", "dotenv": "^16.6.1", "emoji-picker-element": "^1.28.1", "emoji-picker-element-data": "^1.8.0", @@ -96,6 +97,7 @@ "husky": "^9.1.7", "idb": "^8.0.3", "livekit-client": "^2.17.2", + "marked": "^18.0.9", "nostr-signer-capacitor-plugin": "github:coracle-social/nostr-signer-capacitor-plugin#main", "nostr-tools": "^2.19.4", "prettier-plugin-tailwindcss": "^0.7.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index de31a8f8..24b0f1f5 100644 Binary files a/pnpm-lock.yaml and b/pnpm-lock.yaml differ diff --git a/src/app/components/ArticleActions.svelte b/src/app/components/ArticleActions.svelte new file mode 100644 index 00000000..05bd459e --- /dev/null +++ b/src/app/components/ArticleActions.svelte @@ -0,0 +1,55 @@ + + +
+ {#if h && showRoom} + + Posted in # + + {/if} +
+ {#each uniq(topics) as topic (topic)} + + {/each} +
+ + + {#if showActivity} + + {/if} + +
diff --git a/src/app/components/ArticleCreate.svelte b/src/app/components/ArticleCreate.svelte new file mode 100644 index 00000000..0d1ab32c --- /dev/null +++ b/src/app/components/ArticleCreate.svelte @@ -0,0 +1,215 @@ + + + + + + Write an Article + Publish a long-form post to this space. + +
+ + {#snippet label()} +

Title*

+ {/snippet} + {#snippet input()} + + {/snippet} +
+ + {#snippet label()} +

Summary

+ {/snippet} + {#snippet input()} + + {/snippet} +
+ + {#snippet label()} +

Topics

+ {/snippet} + {#snippet input()} + + {/snippet} +
+ + {#snippet label()} +

Content*

+ {/snippet} + {#snippet input()} +
+ +
+ {/snippet} +
+ +
+
+ + + + +
diff --git a/src/app/components/ArticleItem.svelte b/src/app/components/ArticleItem.svelte new file mode 100644 index 00000000..53f4f9ad --- /dev/null +++ b/src/app/components/ArticleItem.svelte @@ -0,0 +1,57 @@ + + + + {#if image} + + {/if} +
+

{title || "Untitled"}

+

+ {formatTimestamp(article.publishedAt())} +

+
+ +
+ + Written by + + {#if h} + in + {/if} + + +
+ diff --git a/src/app/components/ArticleSidebar.svelte b/src/app/components/ArticleSidebar.svelte new file mode 100644 index 00000000..304336dc --- /dev/null +++ b/src/app/components/ArticleSidebar.svelte @@ -0,0 +1,80 @@ + + + diff --git a/src/app/components/CommentActions.svelte b/src/app/components/CommentActions.svelte index 4c379480..b675888f 100644 --- a/src/app/components/CommentActions.svelte +++ b/src/app/components/CommentActions.svelte @@ -11,7 +11,7 @@ type Props = { url: string event: TrustedEvent - segment: string + segment?: string showActivity?: boolean } @@ -19,7 +19,7 @@ const h = tagValue(tagSpec("h"), event.tags) - const path = makeSpacePath(url, segment, event.id) + const path = segment && makeSpacePath(url, segment, event.id) const deleteReaction = (reaction: TrustedEvent) => retractReaction(reaction, {url, h}) @@ -30,7 +30,7 @@
- {#if showActivity} + {#if showActivity && path} {/if} diff --git a/src/app/components/CommentCompose.svelte b/src/app/components/CommentCompose.svelte new file mode 100644 index 00000000..63f1c200 --- /dev/null +++ b/src/app/components/CommentCompose.svelte @@ -0,0 +1,121 @@ + + +
+
+
+ +
+ +
+
+ + +
+
diff --git a/src/app/components/CommentTree.svelte b/src/app/components/CommentTree.svelte new file mode 100644 index 00000000..427b6e5a --- /dev/null +++ b/src/app/components/CommentTree.svelte @@ -0,0 +1,62 @@ + + +
+ +
+ + {#if url} +
+ + +
+ {/if} +
+
+ {#if composing && url} +
+ +
+ {/if} + {#if node.children.length > 0} +
+ {#each node.children as child (child.comment.id)} + + {/each} +
+ {/if} +
diff --git a/src/app/components/ComposeMenu.svelte b/src/app/components/ComposeMenu.svelte index cc55fada..0fba2249 100644 --- a/src/app/components/ComposeMenu.svelte +++ b/src/app/components/ComposeMenu.svelte @@ -4,12 +4,14 @@ import StarFallMinimalistic from "@assets/icons/star-fall-minimalistic.svg?dataurl" import NotesMinimalistic from "@assets/icons/notes-minimalistic.svg?dataurl" import CaseMinimalistic from "@assets/icons/case-minimalistic.svg?dataurl" + import DocumentText from "@assets/icons/document-text.svg?dataurl" import Revote from "@assets/icons/revote.svg?dataurl" import Button from "@lib/components/Button.svelte" import Icon from "@lib/components/Icon.svelte" import {pushModal} from "@app/modal" import CalendarEventCreate from "@app/components/CalendarEventCreate.svelte" import ThreadCreate from "@app/components/ThreadCreate.svelte" + import ArticleCreate from "@app/components/ArticleCreate.svelte" import ClassifiedCreate from "@app/components/ClassifiedCreate.svelte" import GoalCreate from "@app/components/GoalCreate.svelte" import PollCreate from "@app/components/PollCreate.svelte" @@ -32,6 +34,8 @@ const createPoll = () => pushModal(PollCreate, {url, h, shareToChat: true}) + const createArticle = () => pushModal(ArticleCreate, {url, h, shareToChat: true}) + let ul: Element onMount(() => { @@ -64,6 +68,12 @@ Create Thread +
  • + +
  • +
  • + {/if} +{/if} diff --git a/src/app/components/NoteContent.svelte b/src/app/components/NoteContent.svelte index 406e40b8..64cd8108 100644 --- a/src/app/components/NoteContent.svelte +++ b/src/app/components/NoteContent.svelte @@ -1,9 +1,19 @@ + +
    + {#if image} + + {/if} + {#if title} +

    {title}

    + {/if} + {#if props.event.content} + + {/if} +
    diff --git a/src/app/components/NoteContentMinimal.svelte b/src/app/components/NoteContentMinimal.svelte index d715d4df..a9c18eb5 100644 --- a/src/app/components/NoteContentMinimal.svelte +++ b/src/app/components/NoteContentMinimal.svelte @@ -1,9 +1,19 @@ + +{#if title} + {title} +{/if} +{#if props.event.content} + +{/if} diff --git a/src/app/components/RecentItem.svelte b/src/app/components/RecentItem.svelte index cc19e22c..5ec1e9af 100644 --- a/src/app/components/RecentItem.svelte +++ b/src/app/components/RecentItem.svelte @@ -1,8 +1,9 @@ + + + {#snippet leading()} + + {/snippet} + {#snippet title()} + Articles + {/snippet} + {#snippet action()} + + {/snippet} + + + +
    + {#each filtered as event (getAddress(event))} +
    + +
    + {/each} +

    + + {#if loading} + Looking for articles... + {:else if articles.length === 0} + No articles found. + {:else if filtered.length === 0} + No articles match that filter. + {:else} + That's all! + {/if} + +

    +
    + +
    diff --git a/src/routes/spaces/[relay]/articles/[address]/+page.svelte b/src/routes/spaces/[relay]/articles/[address]/+page.svelte new file mode 100644 index 00000000..c78886cf --- /dev/null +++ b/src/routes/spaces/[relay]/articles/[address]/+page.svelte @@ -0,0 +1,62 @@ + + + + {#snippet title()} +

    {$article?.title() ?? ""}

    + {/snippet} +
    + + + {#if $event && $article} + +
    + {#if $article.image()} + + {/if} +
    +

    {$article.title() ?? "Untitled"}

    +

    + Published {formatTimestamp($article.publishedAt())} +

    +
    + {#if $article.summary()} +

    {$article.summary()}

    + {/if} + + +
    +
    + + {:else} +
    + {#await sleep(5000)} + Loading article... + {:then} +

    Failed to load article.

    + {/await} +
    + {/if} +
    diff --git a/src/routes/spaces/[relay]/calendar/[address]/+page.svelte b/src/routes/spaces/[relay]/calendar/[address]/+page.svelte index 50a47ff5..3d5c288d 100644 --- a/src/routes/spaces/[relay]/calendar/[address]/+page.svelte +++ b/src/routes/spaces/[relay]/calendar/[address]/+page.svelte @@ -1,10 +1,9 @@ diff --git a/src/routes/spaces/[relay]/classifieds/[address]/+page.svelte b/src/routes/spaces/[relay]/classifieds/[address]/+page.svelte index 049bed8b..1048d2a2 100644 --- a/src/routes/spaces/[relay]/classifieds/[address]/+page.svelte +++ b/src/routes/spaces/[relay]/classifieds/[address]/+page.svelte @@ -1,10 +1,9 @@ diff --git a/src/routes/spaces/[relay]/goals/[id]/+page.svelte b/src/routes/spaces/[relay]/goals/[id]/+page.svelte index 8ce2c71e..7ae10b5f 100644 --- a/src/routes/spaces/[relay]/goals/[id]/+page.svelte +++ b/src/routes/spaces/[relay]/goals/[id]/+page.svelte @@ -1,10 +1,9 @@ diff --git a/src/routes/spaces/[relay]/polls/[id]/+page.svelte b/src/routes/spaces/[relay]/polls/[id]/+page.svelte index fa593438..4fda5fab 100644 --- a/src/routes/spaces/[relay]/polls/[id]/+page.svelte +++ b/src/routes/spaces/[relay]/polls/[id]/+page.svelte @@ -3,7 +3,7 @@ import {page} from "$app/stores" import {sleep} from "@welshman/lib" import type {MakeNonOptional} from "@welshman/lib" - import {COMMENT, POLL, POLL_RESPONSE} from "@welshman/util" + import {POLL, POLL_RESPONSE, getCommentFiltersForRoot} from "@welshman/util" import {deriveEventsAsc} from "@welshman/store" import SortVertical from "@assets/icons/sort-vertical.svg?dataurl" import Icon from "@lib/components/Icon.svelte" @@ -22,8 +22,8 @@ const {relay, id} = $page.params as MakeNonOptional const url = decodeRelay(relay) const event = deriveEvent(id, [url]) - const filters = [{kinds: [COMMENT], "#E": [id]}] - const comments = deriveEventsAsc(deriveEventsById(filters)) + const filters = $derived($event ? getCommentFiltersForRoot([$event]) : []) + const comments = $derived(deriveEventsAsc(deriveEventsById(filters))) const back = () => history.back() @@ -47,7 +47,10 @@ $network.request({ relays: [url], - filters: [{kinds: [POLL], ids: [id]}, {kinds: [POLL_RESPONSE], "#e": [id]}, ...filters], + filters: [ + {kinds: [POLL], ids: [id]}, + {kinds: [POLL_RESPONSE], "#e": [id]}, + ], signal: controller.signal, }) @@ -55,6 +58,16 @@ controller.abort() } }) + + $effect(() => { + if (filters.length > 0) { + const controller = new AbortController() + + $network.request({relays: [url], filters, signal: controller.signal}) + + return () => controller.abort() + } + }) diff --git a/src/routes/spaces/[relay]/threads/[id]/+page.svelte b/src/routes/spaces/[relay]/threads/[id]/+page.svelte index 5628b1ff..05c10621 100644 --- a/src/routes/spaces/[relay]/threads/[id]/+page.svelte +++ b/src/routes/spaces/[relay]/threads/[id]/+page.svelte @@ -1,5 +1,4 @@