mirror of
https://github.com/coracle-social/flotilla.git
synced 2025-12-10 10:57:04 +00:00
Tweak layout css
This commit is contained in:
14
src/app.css
14
src/app.css
@@ -338,18 +338,14 @@ progress[value]::-webkit-progress-value {
|
||||
@apply w-full md:w-[calc(100%-18.5rem)];
|
||||
}
|
||||
|
||||
.cb {
|
||||
@apply saib bottom-14 md:bottom-0;
|
||||
}
|
||||
|
||||
/* chat view */
|
||||
|
||||
.chat__page-bar {
|
||||
@apply sait cw !fixed top-2;
|
||||
}
|
||||
|
||||
.chat__messages {
|
||||
@apply saib cw fixed top-12 flex h-[calc(100%-6.5rem)] flex-col-reverse overflow-y-auto overflow-x-hidden md:h-[calc(100%-3rem)];
|
||||
}
|
||||
|
||||
.chat__compose {
|
||||
@apply saib cw fixed bottom-14 md:bottom-0;
|
||||
@apply cb cw fixed;
|
||||
}
|
||||
|
||||
.chat__scroll-down {
|
||||
|
||||
@@ -83,8 +83,8 @@
|
||||
const content = initialValues?.content || ""
|
||||
const editor = makeEditor({submit, uploading, content})
|
||||
|
||||
let title = $state(initialValues?.title)
|
||||
let location = $state(initialValues?.location)
|
||||
let title = $state(initialValues?.title || "")
|
||||
let location = $state(initialValues?.location || "")
|
||||
let start: number | undefined = $state(initialValues?.start)
|
||||
let end: number | undefined = $state(initialValues?.end)
|
||||
let endDirty = Boolean(initialValues?.end)
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
import Link from "@lib/components/Link.svelte"
|
||||
import Spinner from "@lib/components/Spinner.svelte"
|
||||
import PageBar from "@lib/components/PageBar.svelte"
|
||||
import PageContent from "@lib/components/PageContent.svelte"
|
||||
import Divider from "@lib/components/Divider.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import ProfileName from "@app/components/ProfileName.svelte"
|
||||
@@ -124,7 +125,7 @@
|
||||
</script>
|
||||
|
||||
{#if others.length > 0}
|
||||
<PageBar class="chat__page-bar">
|
||||
<PageBar>
|
||||
{#snippet title()}
|
||||
<div class="flex flex-col gap-1 sm:flex-row sm:gap-2">
|
||||
{#if others.length === 1}
|
||||
@@ -172,7 +173,7 @@
|
||||
</PageBar>
|
||||
{/if}
|
||||
|
||||
<div class="chat__messages scroll-container">
|
||||
<PageContent class="flex flex-col-reverse pt-4">
|
||||
<div bind:this={dynamicPadding}></div>
|
||||
{#if missingInboxes.includes($pubkey!)}
|
||||
<div class="py-12">
|
||||
@@ -223,7 +224,7 @@
|
||||
</Spinner>
|
||||
{@render info?.()}
|
||||
</p>
|
||||
</div>
|
||||
</PageContent>
|
||||
|
||||
<div class="chat__compose bg-base-200" bind:this={chatCompose}>
|
||||
<div>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import {onMount} from "svelte"
|
||||
import {writable} from "svelte/store"
|
||||
import {isMobile, preventDefault} from "@lib/html"
|
||||
import {fly, slideAndFade} from "@lib/transition"
|
||||
import {fly} from "@lib/transition"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||
@@ -32,30 +33,52 @@
|
||||
}
|
||||
|
||||
const editor = makeEditor({submit, uploading, autofocus: !isMobile})
|
||||
|
||||
let form: HTMLElement
|
||||
let spacer: HTMLElement
|
||||
|
||||
onMount(() => {
|
||||
setTimeout(() => {
|
||||
spacer.scrollIntoView({block: "end", behavior: "smooth"})
|
||||
})
|
||||
|
||||
const observer = new ResizeObserver(() => {
|
||||
spacer!.style.minHeight = `${form!.offsetHeight}px`
|
||||
})
|
||||
|
||||
observer.observe(form!)
|
||||
|
||||
return () => {
|
||||
observer.unobserve(form!)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div bind:this={spacer}></div>
|
||||
<form
|
||||
in:fly
|
||||
out:slideAndFade
|
||||
bind:this={form}
|
||||
onsubmit={preventDefault(submit)}
|
||||
class="card2 sticky bottom-2 z-feature mx-2 mt-4 bg-neutral">
|
||||
<div class="relative">
|
||||
<div class="note-editor flex-grow overflow-hidden">
|
||||
<EditorContent {editor} />
|
||||
class="cb cw fixed z-feature -mx-2 pt-3">
|
||||
<div class="card2 mx-2 my-2 bg-neutral">
|
||||
<div class="relative">
|
||||
<div class="note-editor flex-grow overflow-hidden">
|
||||
<EditorContent {editor} />
|
||||
</div>
|
||||
<Button
|
||||
data-tip="Add an image"
|
||||
class="tooltip tooltip-left absolute bottom-1 right-2"
|
||||
onclick={editor.commands.selectFiles}>
|
||||
{#if $uploading}
|
||||
<span class="loading loading-spinner loading-xs"></span>
|
||||
{:else}
|
||||
<Icon icon="paperclip" size={3} />
|
||||
{/if}
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
data-tip="Add an image"
|
||||
class="tooltip tooltip-left absolute bottom-1 right-2"
|
||||
onclick={editor.commands.selectFiles}>
|
||||
{#if $uploading}
|
||||
<span class="loading loading-spinner loading-xs"></span>
|
||||
{:else}
|
||||
<Icon icon="paperclip" size={3} />
|
||||
{/if}
|
||||
</Button>
|
||||
<ModalFooter>
|
||||
<Button class="btn btn-link" onclick={onClose}>Cancel</Button>
|
||||
<Button type="submit" class="btn btn-primary">Post Reply</Button>
|
||||
</ModalFooter>
|
||||
</div>
|
||||
<ModalFooter>
|
||||
<Button class="btn btn-link" onclick={onClose}>Cancel</Button>
|
||||
<Button type="submit" class="btn btn-primary">Post Reply</Button>
|
||||
</ModalFooter>
|
||||
</form>
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
<script lang="ts">
|
||||
import type {Snippet} from "svelte"
|
||||
|
||||
interface Props {
|
||||
icon?: import("svelte").Snippet
|
||||
title?: import("svelte").Snippet
|
||||
action?: import("svelte").Snippet
|
||||
icon?: Snippet
|
||||
title?: Snippet
|
||||
action?: Snippet
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
const {...props}: Props = $props()
|
||||
</script>
|
||||
|
||||
<div class="relative z-feature rounded-xl px-2 pt-2 {props.class}">
|
||||
<div class="sait cw fixed top-2 z-feature rounded-xl px-2 pt-2 {props.class}">
|
||||
<div
|
||||
class="flex min-h-12 items-center justify-between gap-4 rounded-xl bg-base-100 px-4 shadow-xl">
|
||||
<div class="ellipsize flex items-center gap-4 whitespace-nowrap">
|
||||
|
||||
18
src/lib/components/PageContent.svelte
Normal file
18
src/lib/components/PageContent.svelte
Normal file
@@ -0,0 +1,18 @@
|
||||
<script lang="ts">
|
||||
import type {Snippet} from "svelte"
|
||||
|
||||
interface Props {
|
||||
element?: Element
|
||||
children?: Snippet
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
let {children, element = $bindable(), ...props}: Props = $props()
|
||||
</script>
|
||||
|
||||
<div
|
||||
{...props}
|
||||
bind:this={element}
|
||||
class="scroll-container saib cw fixed top-12 h-[calc(100%-6.5rem)] overflow-y-auto overflow-x-hidden md:h-[calc(100%-3rem)] {props.class}">
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@@ -9,6 +9,7 @@
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Divider from "@lib/components/Divider.svelte"
|
||||
import PageBar from "@lib/components/PageBar.svelte"
|
||||
import PageContent from "@lib/components/PageContent.svelte"
|
||||
import MenuSpaceButton from "@app/components/MenuSpaceButton.svelte"
|
||||
import ProfileFeed from "@app/components/ProfileFeed.svelte"
|
||||
import ChannelName from "@app/components/ChannelName.svelte"
|
||||
@@ -45,154 +46,152 @@
|
||||
const pubkey = $derived($relay?.profile?.pubkey)
|
||||
</script>
|
||||
|
||||
<div class="relative flex flex-col">
|
||||
<PageBar>
|
||||
{#snippet icon()}
|
||||
<div class="center">
|
||||
<Icon icon="home-smile" />
|
||||
</div>
|
||||
{/snippet}
|
||||
{#snippet title()}
|
||||
<strong>Home</strong>
|
||||
{/snippet}
|
||||
{#snippet action()}
|
||||
<div class="row-2">
|
||||
{#if !$userRoomsByUrl.has(url)}
|
||||
<Button class="btn btn-primary btn-sm" onclick={joinSpace}>
|
||||
<Icon icon="login-2" />
|
||||
Join Space
|
||||
</Button>
|
||||
{:else if pubkey}
|
||||
<Link class="btn btn-primary btn-sm" href={makeChatPath([pubkey])}>
|
||||
<Icon icon="letter" />
|
||||
Contact Owner
|
||||
</Link>
|
||||
{/if}
|
||||
<MenuSpaceButton {url} />
|
||||
</div>
|
||||
{/snippet}
|
||||
</PageBar>
|
||||
<div class="col-2 p-2">
|
||||
<div class="card2 bg-alt col-4 text-left">
|
||||
<div class="relative flex gap-4">
|
||||
<div class="relative">
|
||||
<div class="avatar relative">
|
||||
<div
|
||||
class="center !flex h-12 w-12 min-w-12 rounded-full border-2 border-solid border-base-300 bg-base-300">
|
||||
{#if $relay?.profile?.icon}
|
||||
<img alt="" src={$relay.profile.icon} />
|
||||
{:else}
|
||||
<Icon icon="ghost" size={5} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<h2 class="ellipsize whitespace-nowrap text-xl">
|
||||
<RelayName {url} />
|
||||
</h2>
|
||||
<p class="ellipsize text-sm opacity-75">{displayRelayUrl(url)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<RelayDescription {url} />
|
||||
{#if $relay?.profile}
|
||||
{@const {software, version, supported_nips, limitation} = $relay.profile}
|
||||
<div class="flex flex-wrap gap-1">
|
||||
{#if limitation?.auth_required}
|
||||
<p class="badge badge-neutral">
|
||||
<span class="ellipsize">Authentication Required</span>
|
||||
</p>
|
||||
{/if}
|
||||
{#if limitation?.payment_required}
|
||||
<p class="badge badge-neutral"><span class="ellipsize">Payment Required</span></p>
|
||||
{/if}
|
||||
{#if limitation?.min_pow_difficulty}
|
||||
<p class="badge badge-neutral">
|
||||
<span class="ellipsize">Requires PoW {limitation?.min_pow_difficulty}</span>
|
||||
</p>
|
||||
{/if}
|
||||
{#if supported_nips}
|
||||
<p class="badge badge-neutral">
|
||||
<span class="ellipsize">NIPs: {supported_nips.join(", ")}</span>
|
||||
</p>
|
||||
{/if}
|
||||
{#if software}
|
||||
<p class="badge badge-neutral"><span class="ellipsize">Software: {software}</span></p>
|
||||
{/if}
|
||||
{#if version}
|
||||
<p class="badge badge-neutral"><span class="ellipsize">Version: {version}</span></p>
|
||||
{/if}
|
||||
</div>
|
||||
<PageBar>
|
||||
{#snippet icon()}
|
||||
<div class="center">
|
||||
<Icon icon="home-smile" />
|
||||
</div>
|
||||
{/snippet}
|
||||
{#snippet title()}
|
||||
<strong>Home</strong>
|
||||
{/snippet}
|
||||
{#snippet action()}
|
||||
<div class="row-2">
|
||||
{#if !$userRoomsByUrl.has(url)}
|
||||
<Button class="btn btn-primary btn-sm" onclick={joinSpace}>
|
||||
<Icon icon="login-2" />
|
||||
Join Space
|
||||
</Button>
|
||||
{:else if pubkey}
|
||||
<Link class="btn btn-primary btn-sm" href={makeChatPath([pubkey])}>
|
||||
<Icon icon="letter" />
|
||||
Contact Owner
|
||||
</Link>
|
||||
{/if}
|
||||
<MenuSpaceButton {url} />
|
||||
</div>
|
||||
<Divider>Your Rooms</Divider>
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<Link href={threadsPath} class="btn btn-primary">
|
||||
<div class="relative flex items-center gap-2">
|
||||
<Icon icon="notes-minimalistic" />
|
||||
Threads
|
||||
{#if $notifications.has(threadsPath)}
|
||||
<div
|
||||
class="absolute -right-3 -top-1 h-2 w-2 rounded-full bg-primary-content"
|
||||
transition:fade>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Link>
|
||||
<Link href={calendarPath} class="btn btn-secondary">
|
||||
<div class="relative flex items-center gap-2">
|
||||
<Icon icon="notes-minimalistic" />
|
||||
Calendar
|
||||
{#if $notifications.has(calendarPath)}
|
||||
<div
|
||||
class="absolute -right-3 -top-1 h-2 w-2 rounded-full bg-primary-content"
|
||||
transition:fade>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Link>
|
||||
{#each $userRooms as room (room)}
|
||||
{@const roomPath = makeRoomPath(url, room)}
|
||||
<Link href={roomPath} class="btn btn-neutral relative">
|
||||
<div class="flex min-w-0 items-center gap-2 overflow-hidden text-nowrap">
|
||||
{#if channelIsLocked($channelsById.get(makeChannelId(url, room)))}
|
||||
<Icon icon="lock" size={4} />
|
||||
{/snippet}
|
||||
</PageBar>
|
||||
|
||||
<PageContent class="flex flex-col p-2 pt-4">
|
||||
<div class="card2 bg-alt col-4 text-left">
|
||||
<div class="relative flex gap-4">
|
||||
<div class="relative">
|
||||
<div class="avatar relative">
|
||||
<div
|
||||
class="center !flex h-12 w-12 min-w-12 rounded-full border-2 border-solid border-base-300 bg-base-300">
|
||||
{#if $relay?.profile?.icon}
|
||||
<img alt="" src={$relay.profile.icon} />
|
||||
{:else}
|
||||
<Icon icon="hashtag" />
|
||||
<Icon icon="ghost" size={5} />
|
||||
{/if}
|
||||
<ChannelName {url} {room} />
|
||||
</div>
|
||||
{#if $notifications.has(roomPath)}
|
||||
<div class="absolute right-1 top-1 h-2 w-2 rounded-full bg-primary" transition:fade>
|
||||
</div>
|
||||
{/if}
|
||||
</Link>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<h2 class="ellipsize whitespace-nowrap text-xl">
|
||||
<RelayName {url} />
|
||||
</h2>
|
||||
<p class="ellipsize text-sm opacity-75">{displayRelayUrl(url)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Divider>Other Rooms</Divider>
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
{#each $otherRooms as room (room)}
|
||||
<Link href={makeRoomPath(url, room)} class="btn btn-neutral">
|
||||
<div class="relative flex min-w-0 items-center gap-2 overflow-hidden text-nowrap">
|
||||
{#if channelIsLocked($channelsById.get(makeChannelId(url, room)))}
|
||||
<Icon icon="lock" size={4} />
|
||||
{:else}
|
||||
<Icon icon="hashtag" />
|
||||
{/if}
|
||||
<ChannelName {url} {room} />
|
||||
</div>
|
||||
</Link>
|
||||
{/each}
|
||||
<Button onclick={addRoom} class="btn btn-neutral whitespace-nowrap">
|
||||
<Icon icon="add-circle" />
|
||||
Create
|
||||
</Button>
|
||||
</div>
|
||||
{#if pubkey}
|
||||
<div class="hidden flex-col gap-2" class:!flex={relayAdminEvents.length > 0}>
|
||||
<Divider>Recent posts from the relay admin</Divider>
|
||||
<ProfileFeed hideLoading {url} {pubkey} bind:events={relayAdminEvents} />
|
||||
<RelayDescription {url} />
|
||||
{#if $relay?.profile}
|
||||
{@const {software, version, supported_nips, limitation} = $relay.profile}
|
||||
<div class="flex flex-wrap gap-1">
|
||||
{#if limitation?.auth_required}
|
||||
<p class="badge badge-neutral">
|
||||
<span class="ellipsize">Authentication Required</span>
|
||||
</p>
|
||||
{/if}
|
||||
{#if limitation?.payment_required}
|
||||
<p class="badge badge-neutral"><span class="ellipsize">Payment Required</span></p>
|
||||
{/if}
|
||||
{#if limitation?.min_pow_difficulty}
|
||||
<p class="badge badge-neutral">
|
||||
<span class="ellipsize">Requires PoW {limitation?.min_pow_difficulty}</span>
|
||||
</p>
|
||||
{/if}
|
||||
{#if supported_nips}
|
||||
<p class="badge badge-neutral">
|
||||
<span class="ellipsize">NIPs: {supported_nips.join(", ")}</span>
|
||||
</p>
|
||||
{/if}
|
||||
{#if software}
|
||||
<p class="badge badge-neutral"><span class="ellipsize">Software: {software}</span></p>
|
||||
{/if}
|
||||
{#if version}
|
||||
<p class="badge badge-neutral"><span class="ellipsize">Version: {version}</span></p>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<Divider>Your Rooms</Divider>
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<Link href={threadsPath} class="btn btn-primary">
|
||||
<div class="relative flex items-center gap-2">
|
||||
<Icon icon="notes-minimalistic" />
|
||||
Threads
|
||||
{#if $notifications.has(threadsPath)}
|
||||
<div
|
||||
class="absolute -right-3 -top-1 h-2 w-2 rounded-full bg-primary-content"
|
||||
transition:fade>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Link>
|
||||
<Link href={calendarPath} class="btn btn-secondary">
|
||||
<div class="relative flex items-center gap-2">
|
||||
<Icon icon="notes-minimalistic" />
|
||||
Calendar
|
||||
{#if $notifications.has(calendarPath)}
|
||||
<div
|
||||
class="absolute -right-3 -top-1 h-2 w-2 rounded-full bg-primary-content"
|
||||
transition:fade>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Link>
|
||||
{#each $userRooms as room (room)}
|
||||
{@const roomPath = makeRoomPath(url, room)}
|
||||
<Link href={roomPath} class="btn btn-neutral relative">
|
||||
<div class="flex min-w-0 items-center gap-2 overflow-hidden text-nowrap">
|
||||
{#if channelIsLocked($channelsById.get(makeChannelId(url, room)))}
|
||||
<Icon icon="lock" size={4} />
|
||||
{:else}
|
||||
<Icon icon="hashtag" />
|
||||
{/if}
|
||||
<ChannelName {url} {room} />
|
||||
</div>
|
||||
{#if $notifications.has(roomPath)}
|
||||
<div class="absolute right-1 top-1 h-2 w-2 rounded-full bg-primary" transition:fade></div>
|
||||
{/if}
|
||||
</Link>
|
||||
{/each}
|
||||
</div>
|
||||
<Divider>Other Rooms</Divider>
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
{#each $otherRooms as room (room)}
|
||||
<Link href={makeRoomPath(url, room)} class="btn btn-neutral">
|
||||
<div class="relative flex min-w-0 items-center gap-2 overflow-hidden text-nowrap">
|
||||
{#if channelIsLocked($channelsById.get(makeChannelId(url, room)))}
|
||||
<Icon icon="lock" size={4} />
|
||||
{:else}
|
||||
<Icon icon="hashtag" />
|
||||
{/if}
|
||||
<ChannelName {url} {room} />
|
||||
</div>
|
||||
</Link>
|
||||
{/each}
|
||||
<Button onclick={addRoom} class="btn btn-neutral whitespace-nowrap">
|
||||
<Icon icon="add-circle" />
|
||||
Create
|
||||
</Button>
|
||||
</div>
|
||||
{#if pubkey}
|
||||
<div class="hidden flex-col gap-2" class:!flex={relayAdminEvents.length > 0}>
|
||||
<Divider>Recent posts from the relay admin</Divider>
|
||||
<ProfileFeed hideLoading {url} {pubkey} bind:events={relayAdminEvents} />
|
||||
</div>
|
||||
{/if}
|
||||
</PageContent>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Spinner from "@lib/components/Spinner.svelte"
|
||||
import PageBar from "@lib/components/PageBar.svelte"
|
||||
import PageContent from "@lib/components/PageContent.svelte"
|
||||
import Divider from "@lib/components/Divider.svelte"
|
||||
import MenuSpaceButton from "@app/components/MenuSpaceButton.svelte"
|
||||
import ChannelName from "@app/components/ChannelName.svelte"
|
||||
@@ -232,7 +233,7 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<PageBar class="chat__page-bar">
|
||||
<PageBar>
|
||||
{#snippet icon()}
|
||||
<div class="center">
|
||||
<Icon icon="hashtag" />
|
||||
@@ -267,7 +268,7 @@
|
||||
{/snippet}
|
||||
</PageBar>
|
||||
|
||||
<div class="chat__messages scroll-container" onscroll={onScroll} bind:this={element}>
|
||||
<PageContent bind:element onscroll={onScroll} class="flex flex-col-reverse pt-4">
|
||||
<div bind:this={dynamicPadding}></div>
|
||||
{#each elements as { type, id, value, showPubkey } (id)}
|
||||
{#if type === "new-messages"}
|
||||
@@ -299,7 +300,7 @@
|
||||
<Spinner>End of message history</Spinner>
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
</PageContent>
|
||||
|
||||
<div class="chat__compose bg-base-200" bind:this={chatCompose}>
|
||||
<div>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Spinner from "@lib/components/Spinner.svelte"
|
||||
import PageBar from "@lib/components/PageBar.svelte"
|
||||
import PageContent from "@lib/components/PageContent.svelte"
|
||||
import Divider from "@lib/components/Divider.svelte"
|
||||
import MenuSpaceButton from "@app/components/MenuSpaceButton.svelte"
|
||||
import CalendarEventItem from "@app/components/CalendarEventItem.svelte"
|
||||
@@ -27,7 +28,7 @@
|
||||
|
||||
const getStart = (event: TrustedEvent) => parseInt(getTagValue("start", event.tags) || "")
|
||||
|
||||
let element: HTMLElement
|
||||
let element: HTMLElement | undefined = $state()
|
||||
let loading = $state(true)
|
||||
let cleanup: () => void
|
||||
let events: Readable<TrustedEvent[]> = $state(readable([]))
|
||||
@@ -69,11 +70,11 @@
|
||||
if (initialScrollDone) {
|
||||
// If new events are prepended, adjust the scroll position so that the viewport content remains anchored
|
||||
if (prevFirstEventId && items[0].event.id !== prevFirstEventId) {
|
||||
const newScrollHeight = element.scrollHeight
|
||||
const newScrollHeight = element!.scrollHeight
|
||||
const delta = newScrollHeight - previousScrollHeight
|
||||
|
||||
if (delta > 0) {
|
||||
element.scrollTop += delta
|
||||
element!.scrollTop += delta
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -83,11 +84,11 @@
|
||||
) as HTMLElement
|
||||
|
||||
// On initial load, center the scroll container on today's date (or the next available event)
|
||||
element.scrollTop = offsetTop - element.clientHeight / 2 + clientHeight / 2
|
||||
element!.scrollTop = offsetTop - element!.clientHeight / 2 + clientHeight / 2
|
||||
initialScrollDone = true
|
||||
}
|
||||
|
||||
previousScrollHeight = element.scrollHeight
|
||||
previousScrollHeight = element!.scrollHeight
|
||||
prevFirstEventId = items[0].event.id
|
||||
})
|
||||
|
||||
@@ -98,7 +99,7 @@
|
||||
]
|
||||
|
||||
;({events, cleanup} = makeCalendarFeed({
|
||||
element,
|
||||
element: element!,
|
||||
relays: [url],
|
||||
feedFilters,
|
||||
subscriptionFilters,
|
||||
@@ -115,50 +116,49 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="relative flex h-screen flex-col">
|
||||
<PageBar>
|
||||
{#snippet icon()}
|
||||
<div class="center">
|
||||
<Icon icon="calendar-minimalistic" />
|
||||
</div>
|
||||
{/snippet}
|
||||
{#snippet title()}
|
||||
<strong>Calendar</strong>
|
||||
{/snippet}
|
||||
{#snippet action()}
|
||||
<div class="row-2">
|
||||
<Button class="btn btn-primary btn-sm" onclick={createEvent}>
|
||||
<Icon icon="calendar-add" />
|
||||
Create an Event
|
||||
</Button>
|
||||
<MenuSpaceButton {url} />
|
||||
</div>
|
||||
{/snippet}
|
||||
</PageBar>
|
||||
<div class="scroll-container flex flex-grow flex-col gap-2 overflow-auto p-2" bind:this={element}>
|
||||
{#each items as { event, dateDisplay, isFirstFutureEvent }, i (event.id)}
|
||||
<div class={"calendar-event-" + event.id}>
|
||||
{#if isFirstFutureEvent}
|
||||
<div class="flex items-center gap-2 p-2">
|
||||
<div class="h-px flex-grow bg-primary"></div>
|
||||
<p class="text-xs uppercase text-primary">Today</p>
|
||||
<div class="h-px flex-grow bg-primary"></div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if dateDisplay}
|
||||
<Divider>{dateDisplay}</Divider>
|
||||
{/if}
|
||||
<CalendarEventItem {url} {event} />
|
||||
</div>
|
||||
{/each}
|
||||
{#if loading}
|
||||
<p class="flex h-10 items-center justify-center py-20" transition:fly>
|
||||
<Spinner {loading}>Looking for events...</Spinner>
|
||||
</p>
|
||||
{:else if items.length === 0}
|
||||
<p class="flex h-10 items-center justify-center py-20" transition:fly>No events found.</p>
|
||||
{:else}
|
||||
<p class="flex h-10 items-center justify-center py-20" transition:fly>That's all!</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<PageBar>
|
||||
{#snippet icon()}
|
||||
<div class="center">
|
||||
<Icon icon="calendar-minimalistic" />
|
||||
</div>
|
||||
{/snippet}
|
||||
{#snippet title()}
|
||||
<strong>Calendar</strong>
|
||||
{/snippet}
|
||||
{#snippet action()}
|
||||
<div class="row-2">
|
||||
<Button class="btn btn-primary btn-sm" onclick={createEvent}>
|
||||
<Icon icon="calendar-add" />
|
||||
Create an Event
|
||||
</Button>
|
||||
<MenuSpaceButton {url} />
|
||||
</div>
|
||||
{/snippet}
|
||||
</PageBar>
|
||||
|
||||
<PageContent bind:element class="flex flex-col gap-2 p-2 pt-4">
|
||||
{#each items as { event, dateDisplay, isFirstFutureEvent }, i (event.id)}
|
||||
<div class={"calendar-event-" + event.id}>
|
||||
{#if isFirstFutureEvent}
|
||||
<div class="flex items-center gap-2 p-2">
|
||||
<div class="h-px flex-grow bg-primary"></div>
|
||||
<p class="text-xs uppercase text-primary">Today</p>
|
||||
<div class="h-px flex-grow bg-primary"></div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if dateDisplay}
|
||||
<Divider>{dateDisplay}</Divider>
|
||||
{/if}
|
||||
<CalendarEventItem {url} {event} />
|
||||
</div>
|
||||
{/each}
|
||||
{#if loading}
|
||||
<p class="flex h-10 items-center justify-center py-20" transition:fly>
|
||||
<Spinner {loading}>Looking for events...</Spinner>
|
||||
</p>
|
||||
{:else if items.length === 0}
|
||||
<p class="flex h-10 items-center justify-center py-20" transition:fly>No events found.</p>
|
||||
{:else}
|
||||
<p class="flex h-10 items-center justify-center py-20" transition:fly>That's all!</p>
|
||||
{/if}
|
||||
</PageContent>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import {deriveEvents} from "@welshman/store"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import PageBar from "@lib/components/PageBar.svelte"
|
||||
import PageContent from "@lib/components/PageContent.svelte"
|
||||
import Spinner from "@lib/components/Spinner.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Content from "@app/components/Content.svelte"
|
||||
@@ -53,33 +54,25 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="relative flex flex-col-reverse gap-3 px-2">
|
||||
<div class="absolute left-[51px] top-32 h-[calc(100%-248px)] w-[2px] bg-neutral"></div>
|
||||
<PageBar>
|
||||
{#snippet icon()}
|
||||
<div>
|
||||
<Button class="btn btn-neutral btn-sm flex-nowrap whitespace-nowrap" onclick={back}>
|
||||
<Icon icon="alt-arrow-left" />
|
||||
<span class="hidden sm:inline">Go back</span>
|
||||
</Button>
|
||||
</div>
|
||||
{/snippet}
|
||||
{#snippet title()}
|
||||
<h1 class="text-xl">{getTagValue("title", $event?.tags || []) || ""}</h1>
|
||||
{/snippet}
|
||||
{#snippet action()}
|
||||
<MenuSpaceButton {url} />
|
||||
{/snippet}
|
||||
</PageBar>
|
||||
|
||||
<PageContent class="flex flex-col gap-3 p-2 pt-4">
|
||||
{#if $event}
|
||||
{#if !showReply}
|
||||
<div class="flex justify-end px-2 pb-2">
|
||||
<Button class="btn btn-primary" onclick={openReply}>
|
||||
<Icon icon="reply" />
|
||||
Leave comment
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
{#each sortBy(e => -e.created_at, $replies).slice(0, showAll ? undefined : 4) as reply (reply.id)}
|
||||
<NoteCard event={reply} class="card2 bg-alt z-feature w-full">
|
||||
<div class="col-3 ml-12">
|
||||
<Content showEntire event={reply} />
|
||||
<CalendarEventActions event={reply} {url} />
|
||||
</div>
|
||||
</NoteCard>
|
||||
{/each}
|
||||
{#if !showAll && $replies.length > 4}
|
||||
<div class="flex justify-center">
|
||||
<Button class="btn btn-link" onclick={expand}>
|
||||
<Icon icon="sort-vertical" />
|
||||
Show all {$replies.length} replies
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="card2 bg-alt col-3 z-feature">
|
||||
<div class="flex items-start gap-4">
|
||||
<CalendarEventDate event={$event} />
|
||||
@@ -96,6 +89,32 @@
|
||||
<CalendarEventActions {url} event={$event} />
|
||||
</div>
|
||||
</div>
|
||||
{#if !showAll && $replies.length > 4}
|
||||
<div class="flex justify-center">
|
||||
<Button class="btn btn-link" onclick={expand}>
|
||||
<Icon icon="sort-vertical" />
|
||||
Show all {$replies.length} replies
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
{#each sortBy(e => e.created_at, $replies).slice(0, showAll ? undefined : 4) as reply (reply.id)}
|
||||
<NoteCard event={reply} class="card2 bg-alt z-feature w-full">
|
||||
<div class="col-3 ml-12">
|
||||
<Content showEntire event={reply} />
|
||||
<CalendarEventActions event={reply} {url} />
|
||||
</div>
|
||||
</NoteCard>
|
||||
{/each}
|
||||
{#if showReply}
|
||||
<EventReply {url} event={$event} onClose={closeReply} onSubmit={closeReply} />
|
||||
{:else}
|
||||
<div class="flex justify-end px-2 pb-2">
|
||||
<Button class="btn btn-primary" onclick={openReply}>
|
||||
<Icon icon="reply" />
|
||||
Leave comment
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
{#await sleep(5000)}
|
||||
<Spinner loading>Loading comments...</Spinner>
|
||||
@@ -103,23 +122,4 @@
|
||||
<p>Failed to load comments.</p>
|
||||
{/await}
|
||||
{/if}
|
||||
<PageBar class="!mx-0">
|
||||
{#snippet icon()}
|
||||
<div>
|
||||
<Button class="btn btn-neutral btn-sm flex-nowrap whitespace-nowrap" onclick={back}>
|
||||
<Icon icon="alt-arrow-left" />
|
||||
<span class="hidden sm:inline">Go back</span>
|
||||
</Button>
|
||||
</div>
|
||||
{/snippet}
|
||||
{#snippet title()}
|
||||
<h1 class="text-xl">{getTagValue("title", $event?.tags || []) || ""}</h1>
|
||||
{/snippet}
|
||||
{#snippet action()}
|
||||
<MenuSpaceButton {url} />
|
||||
{/snippet}
|
||||
</PageBar>
|
||||
</div>
|
||||
{#if showReply}
|
||||
<EventReply {url} event={$event} onClose={closeReply} onSubmit={closeReply} />
|
||||
{/if}
|
||||
</PageContent>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import PageBar from "@lib/components/PageBar.svelte"
|
||||
import PageContent from "@lib/components/PageContent.svelte"
|
||||
import Spinner from "@lib/components/Spinner.svelte"
|
||||
import MenuSpaceButton from "@app/components/MenuSpaceButton.svelte"
|
||||
import ThreadItem from "@app/components/ThreadItem.svelte"
|
||||
@@ -73,42 +74,41 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="relative flex h-screen flex-col" bind:this={element}>
|
||||
<PageBar>
|
||||
{#snippet icon()}
|
||||
<div class="center">
|
||||
<PageBar>
|
||||
{#snippet icon()}
|
||||
<div class="center">
|
||||
<Icon icon="notes-minimalistic" />
|
||||
</div>
|
||||
{/snippet}
|
||||
{#snippet title()}
|
||||
<strong>Threads</strong>
|
||||
{/snippet}
|
||||
{#snippet action()}
|
||||
<div class="row-2">
|
||||
<Button class="btn btn-primary btn-sm" onclick={createThread}>
|
||||
<Icon icon="notes-minimalistic" />
|
||||
</div>
|
||||
{/snippet}
|
||||
{#snippet title()}
|
||||
<strong>Threads</strong>
|
||||
{/snippet}
|
||||
{#snippet action()}
|
||||
<div class="row-2">
|
||||
<Button class="btn btn-primary btn-sm" onclick={createThread}>
|
||||
<Icon icon="notes-minimalistic" />
|
||||
Create a Thread
|
||||
</Button>
|
||||
<MenuSpaceButton {url} />
|
||||
</div>
|
||||
{/snippet}
|
||||
</PageBar>
|
||||
<div class="flex flex-grow flex-col gap-2 overflow-auto p-2">
|
||||
{#each events as event (event.id)}
|
||||
<div in:fly>
|
||||
<ThreadItem {url} event={$state.snapshot(event)} />
|
||||
</div>
|
||||
{/each}
|
||||
<p class="flex h-10 items-center justify-center py-20">
|
||||
<Spinner {loading}>
|
||||
{#if loading}
|
||||
Looking for threads...
|
||||
{:else if events.length === 0}
|
||||
No threads found.
|
||||
{:else}
|
||||
That's all!
|
||||
{/if}
|
||||
</Spinner>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
Create a Thread
|
||||
</Button>
|
||||
<MenuSpaceButton {url} />
|
||||
</div>
|
||||
{/snippet}
|
||||
</PageBar>
|
||||
|
||||
<PageContent bind:element class="flex flex-col gap-2 p-2 pt-4">
|
||||
{#each events as event (event.id)}
|
||||
<div in:fly>
|
||||
<ThreadItem {url} event={$state.snapshot(event)} />
|
||||
</div>
|
||||
{/each}
|
||||
<p class="flex h-10 items-center justify-center py-20">
|
||||
<Spinner {loading}>
|
||||
{#if loading}
|
||||
Looking for threads...
|
||||
{:else if events.length === 0}
|
||||
No threads found.
|
||||
{:else}
|
||||
That's all!
|
||||
{/if}
|
||||
</Spinner>
|
||||
</p>
|
||||
</PageContent>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import {deriveEvents} from "@welshman/store"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import PageBar from "@lib/components/PageBar.svelte"
|
||||
import PageContent from "@lib/components/PageContent.svelte"
|
||||
import Spinner from "@lib/components/Spinner.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Content from "@app/components/Content.svelte"
|
||||
@@ -50,39 +51,61 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="relative flex flex-col-reverse gap-3 px-2">
|
||||
<div class="absolute left-[51px] top-32 h-[calc(100%-248px)] w-[2px] bg-neutral"></div>
|
||||
<PageBar>
|
||||
{#snippet icon()}
|
||||
<div>
|
||||
<Button class="btn btn-neutral btn-sm flex-nowrap whitespace-nowrap" onclick={back}>
|
||||
<Icon icon="alt-arrow-left" />
|
||||
<span class="hidden sm:inline">Go back</span>
|
||||
</Button>
|
||||
</div>
|
||||
{/snippet}
|
||||
{#snippet title()}
|
||||
<h1 class="text-xl">{getTagValue("title", $event?.tags || []) || ""}</h1>
|
||||
{/snippet}
|
||||
{#snippet action()}
|
||||
<div>
|
||||
<MenuSpaceButton {url} />
|
||||
</div>
|
||||
{/snippet}
|
||||
</PageBar>
|
||||
|
||||
<PageContent class="flex flex-col p-2 pt-4">
|
||||
{#if $event}
|
||||
{#if !showReply}
|
||||
<div class="flex justify-end px-2 pb-2">
|
||||
<div class="flex flex-col gap-3">
|
||||
<NoteCard event={$event} class="card2 bg-alt z-feature w-full">
|
||||
<div class="col-3 ml-12">
|
||||
<Content showEntire event={$event} relays={[url]} />
|
||||
<ThreadActions event={$event} {url} />
|
||||
</div>
|
||||
</NoteCard>
|
||||
{#if !showAll && $replies.length > 4}
|
||||
<div class="flex justify-center">
|
||||
<Button class="btn btn-link" onclick={expand}>
|
||||
<Icon icon="sort-vertical" />
|
||||
Show all {$replies.length} replies
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
{#each sortBy(e => -e.created_at, $replies).slice(0, showAll ? undefined : 4) as reply (reply.id)}
|
||||
<NoteCard event={reply} class="card2 bg-alt z-feature w-full">
|
||||
<div class="col-3 ml-12">
|
||||
<Content showEntire event={reply} />
|
||||
<ThreadActions event={reply} {url} />
|
||||
</div>
|
||||
</NoteCard>
|
||||
{/each}
|
||||
</div>
|
||||
{#if showReply}
|
||||
<EventReply {url} event={$event} onClose={closeReply} onSubmit={closeReply} />
|
||||
{:else}
|
||||
<div class="flex justify-end p-2">
|
||||
<Button class="btn btn-primary" onclick={openReply}>
|
||||
<Icon icon="reply" />
|
||||
Reply to thread
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
{#each sortBy(e => -e.created_at, $replies).slice(0, showAll ? undefined : 4) as reply (reply.id)}
|
||||
<NoteCard event={reply} class="card2 bg-alt z-feature w-full">
|
||||
<div class="col-3 ml-12">
|
||||
<Content showEntire event={reply} />
|
||||
<ThreadActions event={reply} {url} />
|
||||
</div>
|
||||
</NoteCard>
|
||||
{/each}
|
||||
{#if !showAll && $replies.length > 4}
|
||||
<div class="flex justify-center">
|
||||
<Button class="btn btn-link" onclick={expand}>
|
||||
<Icon icon="sort-vertical" />
|
||||
Show all {$replies.length} replies
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
<NoteCard event={$event} class="card2 bg-alt z-feature w-full">
|
||||
<div class="col-3 ml-12">
|
||||
<Content showEntire event={$event} relays={[url]} />
|
||||
<ThreadActions event={$event} {url} />
|
||||
</div>
|
||||
</NoteCard>
|
||||
{:else}
|
||||
{#await sleep(5000)}
|
||||
<Spinner loading>Loading thread...</Spinner>
|
||||
@@ -90,25 +113,4 @@
|
||||
<p>Failed to load thread.</p>
|
||||
{/await}
|
||||
{/if}
|
||||
<PageBar class="!mx-0">
|
||||
{#snippet icon()}
|
||||
<div>
|
||||
<Button class="btn btn-neutral btn-sm flex-nowrap whitespace-nowrap" onclick={back}>
|
||||
<Icon icon="alt-arrow-left" />
|
||||
<span class="hidden sm:inline">Go back</span>
|
||||
</Button>
|
||||
</div>
|
||||
{/snippet}
|
||||
{#snippet title()}
|
||||
<h1 class="text-xl">{getTagValue("title", $event?.tags || []) || ""}</h1>
|
||||
{/snippet}
|
||||
{#snippet action()}
|
||||
<div>
|
||||
<MenuSpaceButton {url} />
|
||||
</div>
|
||||
{/snippet}
|
||||
</PageBar>
|
||||
</div>
|
||||
{#if showReply}
|
||||
<EventReply {url} event={$event} onClose={closeReply} onSubmit={closeReply} />
|
||||
{/if}
|
||||
</PageContent>
|
||||
|
||||
Reference in New Issue
Block a user