mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-30 00:41:12 +00:00
Improve feed form
This commit is contained in:
parent
3a269db834
commit
98b4c9fd9f
4
.env
4
.env
@ -1,5 +1,5 @@
|
|||||||
VITE_DARK_THEME="accent:#FC560E,neutral-100:#F5F5F5,neutral-200:#E5E5E5,neutral-300:#D4D4D4,neutral-400:#A3A3A3,neutral-500:#737373,neutral-50:#FAFAFA,neutral-600:#525252,neutral-700:#404040,neutral-800:#262626,neutral-900:#171717,neutral-950:#0A0A0A,success:#12D2B0,tinted-100:#F1EAE7,tinted-200:#DED3CF,tinted-400:#B9A69E,tinted-500:#756A65,tinted-600:#5A524F,tinted-700:#3E3A38,tinted-800:#332f2d,warning:#FCAB0E"
|
VITE_DARK_THEME="accent:#FC560E,neutral-100:#F5F5F5,neutral-200:#E5E5E5,neutral-300:#D4D4D4,neutral-400:#A3A3A3,neutral-500:#737373,neutral-50:#FAFAFA,neutral-600:#525252,neutral-700:#404040,neutral-800:#262626,neutral-900:#171717,neutral-950:#0A0A0A,success:#12D2B0,tinted-100:#F1EAE7,tinted-200:#DED3CF,tinted-400:#B9A69E,tinted-500:#756A65,tinted-600:#5A524F,tinted-700:#3E3A38,tinted-800:#332f2d,warning:#FCAB0E,danger:#dc0c0c"
|
||||||
VITE_LIGHT_THEME="accent:#FC560E,neutral-950:#FAFAFA,neutral-900:#FAFAFA,neutral-800:#F5F5F5,neutral-700:#D4D4D4,neutral-600:#A3A3A3,neutral-500:#737373,neutral-400:#525252,neutral-300:#404040,neutral-200:#262626,neutral-100:#171717,neutral-50:#0A0A0A,success:#12D2B0,tinted-800:#FFFFFF,tinted-700:#FAFAFA,tinted-600:#F5F5F5,tinted-500:#D4D4D4,tinted-400:#A3A3A3,tinted-200:#737373,tinted-100:#525252,warning:#FCAB0E"
|
VITE_LIGHT_THEME="accent:#FC560E,neutral-950:#FAFAFA,neutral-900:#FAFAFA,neutral-800:#F5F5F5,neutral-700:#D4D4D4,neutral-600:#A3A3A3,neutral-500:#737373,neutral-400:#525252,neutral-300:#404040,neutral-200:#262626,neutral-100:#171717,neutral-50:#0A0A0A,success:#12D2B0,tinted-800:#FFFFFF,tinted-700:#FAFAFA,tinted-600:#F5F5F5,tinted-500:#D4D4D4,tinted-400:#A3A3A3,tinted-200:#737373,tinted-100:#525252,warning:#FCAB0E,danger:#dc0c0c"
|
||||||
VITE_DVM_RELAYS=wss://relay.damus.io,wss://offchain.pub,wss://relay.f7z.io,wss://nos.lol,wss://relay.nostr.net,wss://relay.nostr.band,wss://bucket.coracle.social
|
VITE_DVM_RELAYS=wss://relay.damus.io,wss://offchain.pub,wss://relay.f7z.io,wss://nos.lol,wss://relay.nostr.net,wss://relay.nostr.band,wss://bucket.coracle.social
|
||||||
VITE_SEARCH_RELAYS=wss://relay.nostr.band,wss://nostr.wine,wss://search.nos.today
|
VITE_SEARCH_RELAYS=wss://relay.nostr.band,wss://nostr.wine,wss://search.nos.today
|
||||||
VITE_DEFAULT_RELAYS=wss://relay.damus.io,wss://nos.lol
|
VITE_DEFAULT_RELAYS=wss://relay.damus.io,wss://nos.lol
|
||||||
|
@ -145,8 +145,8 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
router.register("/feeds", import("src/app/views/FeedList.svelte"))
|
router.register("/feeds", import("src/app/views/FeedList.svelte"))
|
||||||
router.register("/feeds/create", import("src/app/views/FeedForm.svelte"))
|
router.register("/feeds/create", import("src/app/views/FeedCreate.svelte"))
|
||||||
router.register("/feeds/:address", import("src/app/views/FeedForm.svelte"), {
|
router.register("/feeds/:address", import("src/app/views/FeedEdit.svelte"), {
|
||||||
serializers: {
|
serializers: {
|
||||||
address: asNaddr("address"),
|
address: asNaddr("address"),
|
||||||
},
|
},
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
import MenuDesktopSecondary from "src/app/MenuDesktopSecondary.svelte"
|
import MenuDesktopSecondary from "src/app/MenuDesktopSecondary.svelte"
|
||||||
import {feed, slowConnections} from "src/app/state"
|
import {feed, slowConnections} from "src/app/state"
|
||||||
import {router} from "src/app/util/router"
|
import {router} from "src/app/util/router"
|
||||||
import {readFeed, normalizeFeedDefinition} from "src/domain"
|
import {readFeed, displayFeed, normalizeFeedDefinition} from "src/domain"
|
||||||
import {
|
import {
|
||||||
env,
|
env,
|
||||||
user,
|
user,
|
||||||
@ -60,13 +60,13 @@
|
|||||||
in:fly={{x: -100, duration: 200}}
|
in:fly={{x: -100, duration: 200}}
|
||||||
class="fixed bottom-0 left-72 top-0 w-60 bg-tinted-700 pt-24 transition-colors">
|
class="fixed bottom-0 left-72 top-0 w-60 bg-tinted-700 pt-24 transition-colors">
|
||||||
<MenuDesktopItem
|
<MenuDesktopItem
|
||||||
class="!h-10 !text-lg"
|
small
|
||||||
isActive={equals(followsFeed, normalizedFeed)}
|
isActive={equals(followsFeed, normalizedFeed)}
|
||||||
on:click={() => loadFeed(followsFeed)}>
|
on:click={() => loadFeed(followsFeed)}>
|
||||||
Follows
|
Follows
|
||||||
</MenuDesktopItem>
|
</MenuDesktopItem>
|
||||||
<MenuDesktopItem
|
<MenuDesktopItem
|
||||||
class="!h-10 !text-lg"
|
small
|
||||||
isActive={equals(networkFeed, normalizedFeed)}
|
isActive={equals(networkFeed, normalizedFeed)}
|
||||||
on:click={() => loadFeed(networkFeed)}>
|
on:click={() => loadFeed(networkFeed)}>
|
||||||
Network
|
Network
|
||||||
@ -74,16 +74,16 @@
|
|||||||
{#each $userFeeds as event}
|
{#each $userFeeds as event}
|
||||||
{@const thisFeed = readFeed(event)}
|
{@const thisFeed = readFeed(event)}
|
||||||
<MenuDesktopItem
|
<MenuDesktopItem
|
||||||
class="!h-10 !text-lg"
|
small
|
||||||
isActive={equals(thisFeed.definition, normalizedFeed)}
|
isActive={equals(thisFeed.definition, normalizedFeed)}
|
||||||
on:click={() => loadFeed(thisFeed.definition)}>
|
on:click={() => loadFeed(thisFeed.definition)}>
|
||||||
{thisFeed.name}
|
{displayFeed(thisFeed)}
|
||||||
</MenuDesktopItem>
|
</MenuDesktopItem>
|
||||||
{/each}
|
{/each}
|
||||||
{#each $userLists as list}
|
{#each $userLists as list}
|
||||||
{@const definition = feedFromTags(Tags.fromEvent(list))}
|
{@const definition = feedFromTags(Tags.fromEvent(list))}
|
||||||
<MenuDesktopItem
|
<MenuDesktopItem
|
||||||
class="!h-10 !text-lg"
|
small
|
||||||
isActive={equals(definition, normalizedFeed)}
|
isActive={equals(definition, normalizedFeed)}
|
||||||
on:click={() => loadFeed(definition)}>
|
on:click={() => loadFeed(definition)}>
|
||||||
{displayList(list)}
|
{displayList(list)}
|
||||||
|
@ -7,10 +7,15 @@
|
|||||||
export let path = null
|
export let path = null
|
||||||
export let isAlt = false
|
export let isAlt = false
|
||||||
export let isActive = false
|
export let isActive = false
|
||||||
|
export let small = false
|
||||||
|
|
||||||
$: className = cx("relative staatliches h-12 block transition-all", $$props.class, {
|
$: className = cx("relative staatliches block transition-all", $$props.class, {
|
||||||
"text-3xl text-accent": isActive,
|
"h-12": !small,
|
||||||
"text-2xl text-tinted-400 hover:text-tinted-100": !isActive,
|
"h-10 text-lg": small,
|
||||||
|
"text-3xl": !small && isActive,
|
||||||
|
"text-2xl": !small && !isActive,
|
||||||
|
"text-accent": isActive,
|
||||||
|
"text-tinted-400 hover:text-tinted-100": !isActive,
|
||||||
"hover:bg-tinted-800": !isActive && !isAlt,
|
"hover:bg-tinted-800": !isActive && !isAlt,
|
||||||
"hover:bg-tinted-700": !isActive && isAlt,
|
"hover:bg-tinted-700": !isActive && isAlt,
|
||||||
})
|
})
|
||||||
@ -22,7 +27,9 @@
|
|||||||
{#if isActive}
|
{#if isActive}
|
||||||
<div
|
<div
|
||||||
in:fly|local={{x: 50, duration: 1000, easing: elasticOut}}
|
in:fly|local={{x: 50, duration: 1000, easing: elasticOut}}
|
||||||
class="relative top-4 h-px w-full bg-accent" />
|
class="relative h-px w-full bg-accent"
|
||||||
|
class:top-4={!small}
|
||||||
|
class:top-3={small} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</Anchor>
|
</Anchor>
|
||||||
|
@ -63,7 +63,6 @@
|
|||||||
onChange={relays => onChange({...dvmItem, relays})}>
|
onChange={relays => onChange({...dvmItem, relays})}>
|
||||||
<span slot="item" let:item>{displayRelayUrl(item)}</span>
|
<span slot="item" let:item>{displayRelayUrl(item)}</span>
|
||||||
</SearchSelect>
|
</SearchSelect>
|
||||||
<p slot="info">Select which relays requests to this DVM should be sent to.</p>
|
|
||||||
</Field>
|
</Field>
|
||||||
{#each dvmItem.tags || [] as [type, value], i (i + key)}
|
{#each dvmItem.tags || [] as [type, value], i (i + key)}
|
||||||
<div class="flex items-center justify-between gap-2">
|
<div class="flex items-center justify-between gap-2">
|
||||||
|
32
src/app/shared/FeedCard.svelte
Normal file
32
src/app/shared/FeedCard.svelte
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<script type="ts">
|
||||||
|
import {Kind} from '@welshman/util'
|
||||||
|
import FlexColumn from "src/partials/FlexColumn.svelte"
|
||||||
|
import Card from "src/partials/Card.svelte"
|
||||||
|
import FeedSummary from "src/app/shared/FeedSummary.svelte"
|
||||||
|
import {readFeed, displayFeed, listAsFeed} from "src/domain"
|
||||||
|
import {repository, lists} from 'src/engine'
|
||||||
|
|
||||||
|
export let address
|
||||||
|
|
||||||
|
const feed = address.startsWith(Kind.ListBookmarks)
|
||||||
|
? listAsFeed(lists.key(address).get())
|
||||||
|
: readFeed(repository.getEvent(address))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<FlexColumn>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="staatliches flex items-center gap-3 text-xl">
|
||||||
|
<i class="fa fa-rss" />
|
||||||
|
<span class:text-neutral-500={!feed.name}>
|
||||||
|
{displayFeed(feed)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<slot name="controls" />
|
||||||
|
</div>
|
||||||
|
{#if feed.description}
|
||||||
|
<p>{feed.description}</p>
|
||||||
|
{/if}
|
||||||
|
<FeedSummary feed={feed.definition} />
|
||||||
|
</FlexColumn>
|
||||||
|
</Card>
|
@ -1,32 +1,21 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {debounce} from "throttle-debounce"
|
import {debounce} from "throttle-debounce"
|
||||||
import {Tags, decodeAddress} from "@welshman/util"
|
import {isScopeFeed, isSearchFeed, makeSearchFeed, Scope, getFeedArgs} from "@welshman/feeds"
|
||||||
import {
|
|
||||||
isScopeFeed,
|
|
||||||
isSearchFeed,
|
|
||||||
makeSearchFeed,
|
|
||||||
Scope,
|
|
||||||
getFeedArgs,
|
|
||||||
feedFromTags,
|
|
||||||
} from "@welshman/feeds"
|
|
||||||
import Modal from "src/partials/Modal.svelte"
|
import Modal from "src/partials/Modal.svelte"
|
||||||
import Textarea from "src/partials/Textarea.svelte"
|
|
||||||
import Subheading from "src/partials/Subheading.svelte"
|
import Subheading from "src/partials/Subheading.svelte"
|
||||||
import Field from "src/partials/Field.svelte"
|
|
||||||
import Input from "src/partials/Input.svelte"
|
import Input from "src/partials/Input.svelte"
|
||||||
import Select from "src/partials/Select.svelte"
|
import Select from "src/partials/Select.svelte"
|
||||||
import Popover2 from "src/partials/Popover2.svelte"
|
import Popover2 from "src/partials/Popover2.svelte"
|
||||||
import Anchor from "src/partials/Anchor.svelte"
|
import Anchor from "src/partials/Anchor.svelte"
|
||||||
import Menu from "src/partials/Menu.svelte"
|
import Menu from "src/partials/Menu.svelte"
|
||||||
import MenuItem from "src/partials/MenuItem.svelte"
|
import MenuItem from "src/partials/MenuItem.svelte"
|
||||||
import FeedField from "src/app/shared/FeedField.svelte"
|
import FeedForm from "src/app/shared/FeedForm.svelte"
|
||||||
import {router} from "src/app/util"
|
import {router} from "src/app/util"
|
||||||
import {normalizeFeedDefinition, readFeed, initFeed, editFeed, createFeed} from "src/domain"
|
import {normalizeFeedDefinition, readFeed, initFeed, listAsFeed, displayFeed} from "src/domain"
|
||||||
import {
|
import {
|
||||||
hints,
|
|
||||||
repository,
|
repository,
|
||||||
createAndPublish,
|
|
||||||
displayList as displayList2,
|
displayList as displayList2,
|
||||||
|
publishDeletion,
|
||||||
userLists,
|
userLists,
|
||||||
userFeeds,
|
userFeeds,
|
||||||
} from "src/engine"
|
} from "src/engine"
|
||||||
@ -50,30 +39,17 @@
|
|||||||
formIsOpen = false
|
formIsOpen = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const openName = () => {
|
|
||||||
nameIsOpen = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const closeName = () => {
|
|
||||||
nameIsOpen = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleReplies = () => {
|
const toggleReplies = () => {
|
||||||
opts = {...opts, shouldHideReplies: !opts.shouldHideReplies}
|
opts = {...opts, shouldHideReplies: !opts.shouldHideReplies}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSearch = definition => (getFeedArgs(definition)?.find(isSearchFeed)?.[1] as string) || ""
|
const getSearch = definition => (getFeedArgs(definition)?.find(isSearchFeed)?.[1] as string) || ""
|
||||||
|
|
||||||
const onDraftFeedChange = definition => {
|
|
||||||
feed.definition = definition
|
|
||||||
}
|
|
||||||
|
|
||||||
const setFeedDefinition = definition => {
|
const setFeedDefinition = definition => {
|
||||||
opts = {...opts, feed: definition}
|
opts = {...opts, feed: definition}
|
||||||
search = getSearch(definition)
|
search = getSearch(definition)
|
||||||
closeListMenu()
|
closeListMenu()
|
||||||
closeForm()
|
closeForm()
|
||||||
closeName()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const setSubFeed = subFeed => {
|
const setSubFeed = subFeed => {
|
||||||
@ -94,23 +70,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const setList = list => {
|
const setList = list => {
|
||||||
feed = initFeed({
|
feed = listAsFeed(list)
|
||||||
name: list.title,
|
|
||||||
list: list.address,
|
|
||||||
description: list.description,
|
|
||||||
definition: feedFromTags(Tags.fromEvent(list)),
|
|
||||||
identifier: decodeAddress(list.address).identifier,
|
|
||||||
})
|
|
||||||
|
|
||||||
setFeedDefinition(feed.definition)
|
setFeedDefinition(feed.definition)
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveFeed = async () => {
|
const saveFeed = event => {
|
||||||
const relays = hints.WriteRelays().getUrls()
|
if (feed.list) {
|
||||||
const template = feed.event ? editFeed(feed) : createFeed(feed)
|
publishDeletion([feed.list])
|
||||||
const pub = await createAndPublish({...template, relays})
|
}
|
||||||
|
|
||||||
setFeed(pub.request.event)
|
setFeed(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSearchBlur = debounce(500, () => {
|
const onSearchBlur = debounce(500, () => {
|
||||||
@ -124,7 +93,6 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
let formIsOpen = false
|
let formIsOpen = false
|
||||||
let nameIsOpen = false
|
|
||||||
let listMenuIsOpen = false
|
let listMenuIsOpen = false
|
||||||
let feed = address
|
let feed = address
|
||||||
? readFeed(repository.getEvent(address))
|
? readFeed(repository.getEvent(address))
|
||||||
@ -144,7 +112,10 @@
|
|||||||
</Select>
|
</Select>
|
||||||
<div class="flex flex-grow items-center justify-end gap-2">
|
<div class="flex flex-grow items-center justify-end gap-2">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<Input class="hidden h-7 bg-neutral-900 xs:block" on:input={onSearchBlur} bind:value={search}>
|
<Input
|
||||||
|
class="hidden h-7 rounded-r-none bg-neutral-900 xs:block"
|
||||||
|
on:input={onSearchBlur}
|
||||||
|
bind:value={search}>
|
||||||
<div slot="after" class="hidden text-white xs:block">
|
<div slot="after" class="hidden text-white xs:block">
|
||||||
<i class="fa fa-search" />
|
<i class="fa fa-search" />
|
||||||
</div>
|
</div>
|
||||||
@ -177,7 +148,9 @@
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
<div class="max-h-96 overflow-auto">
|
<div class="max-h-96 overflow-auto">
|
||||||
{#each $userFeeds as event}
|
{#each $userFeeds as event}
|
||||||
<MenuItem on:click={() => setFeed(event)}>{readFeed(event).name}</MenuItem>
|
<MenuItem on:click={() => setFeed(event)}>
|
||||||
|
{displayFeed(readFeed(event))}
|
||||||
|
</MenuItem>
|
||||||
{/each}
|
{/each}
|
||||||
{#each $userLists as list}
|
{#each $userLists as list}
|
||||||
<MenuItem on:click={() => setList(list)}>{displayList2(list)}</MenuItem>
|
<MenuItem on:click={() => setList(list)}>{displayList2(list)}</MenuItem>
|
||||||
@ -193,33 +166,14 @@
|
|||||||
|
|
||||||
{#if formIsOpen}
|
{#if formIsOpen}
|
||||||
<Modal onEscape={closeForm}>
|
<Modal onEscape={closeForm}>
|
||||||
{#if event}
|
<FeedForm hideType {feed} onSave={saveFeed}>
|
||||||
<Subheading>Edit {feed.name}</Subheading>
|
<div slot="controls" class="flex justify-between gap-2" let:save>
|
||||||
{:else}
|
<Anchor button on:click={closeForm}>Discard</Anchor>
|
||||||
<Subheading>Customize your feed</Subheading>
|
<div class="flex gap-2">
|
||||||
{/if}
|
<Anchor button on:click={save}>Save Feed</Anchor>
|
||||||
<FeedField feed={feed.definition} onChange={onDraftFeedChange} />
|
<Anchor button accent on:click={() => setFeedDefinition(feed.definition)}>Done</Anchor>
|
||||||
<div class="flex justify-between gap-2">
|
</div>
|
||||||
<Anchor button on:click={closeForm}>Discard</Anchor>
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<Anchor button on:click={openName}>Save Feed</Anchor>
|
|
||||||
<Anchor button accent on:click={() => setFeedDefinition(feed.definition)}>Done</Anchor>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</FeedForm>
|
||||||
</Modal>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if nameIsOpen}
|
|
||||||
<Modal onEscape={closeName}>
|
|
||||||
<Field label="What would you like to name this feed?">
|
|
||||||
<Input bind:value={feed.name} />
|
|
||||||
</Field>
|
|
||||||
<Field label="How would you describe this feed?">
|
|
||||||
<Textarea bind:value={feed.description} />
|
|
||||||
</Field>
|
|
||||||
<div class="flex justify-between gap-2">
|
|
||||||
<Anchor button on:click={closeName}>Cancel</Anchor>
|
|
||||||
<Anchor button accent on:click={saveFeed}>Save</Anchor>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -1,20 +1,31 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {FeedType, makeIntersectionFeed, hasSubFeeds, getFeedArgs} from "@welshman/feeds"
|
import {identity, partition} from "@welshman/lib"
|
||||||
|
import {
|
||||||
|
FeedType,
|
||||||
|
makeIntersectionFeed,
|
||||||
|
hasSubFeeds,
|
||||||
|
getFeedArgs,
|
||||||
|
isAuthorFeed,
|
||||||
|
isScopeFeed,
|
||||||
|
isTagFeed,
|
||||||
|
isRelayFeed,
|
||||||
|
isDVMFeed,
|
||||||
|
makeDVMFeed,
|
||||||
|
makeScopeFeed,
|
||||||
|
makeTagFeed,
|
||||||
|
makeRelayFeed,
|
||||||
|
Scope,
|
||||||
|
} from "@welshman/feeds"
|
||||||
import Icon from "src/partials/Icon.svelte"
|
import Icon from "src/partials/Icon.svelte"
|
||||||
import SelectTiles from "src/partials/SelectTiles.svelte"
|
import SelectTiles from "src/partials/SelectTiles.svelte"
|
||||||
import Card from "src/partials/Card.svelte"
|
import Card from "src/partials/Card.svelte"
|
||||||
import Anchor from "src/partials/Anchor.svelte"
|
import Anchor from "src/partials/Anchor.svelte"
|
||||||
import FlexColumn from "src/partials/FlexColumn.svelte"
|
import FlexColumn from "src/partials/FlexColumn.svelte"
|
||||||
import Field from "src/partials/Field.svelte"
|
|
||||||
import FeedFormPeople from "src/app/shared/FeedFormPeople.svelte"
|
|
||||||
import FeedFormTopics from "src/app/shared/FeedFormTopics.svelte"
|
|
||||||
import FeedFormRelays from "src/app/shared/FeedFormRelays.svelte"
|
|
||||||
import FeedFormDVMs from "src/app/shared/FeedFormDVMs.svelte"
|
|
||||||
import FeedFormAdvanced from "src/app/shared/FeedFormAdvanced.svelte"
|
import FeedFormAdvanced from "src/app/shared/FeedFormAdvanced.svelte"
|
||||||
|
import FeedFormFilters from "src/app/shared/FeedFormFilters.svelte"
|
||||||
|
|
||||||
export let feed
|
export let feed
|
||||||
export let onChange = null
|
export let onChange = null
|
||||||
export let hideType = false
|
|
||||||
|
|
||||||
enum FormType {
|
enum FormType {
|
||||||
Advanced = "advanced",
|
Advanced = "advanced",
|
||||||
@ -26,9 +37,25 @@
|
|||||||
|
|
||||||
const normalize = feed => (isNormalized(feed) ? feed : [FeedType.Intersection, feed])
|
const normalize = feed => (isNormalized(feed) ? feed : [FeedType.Intersection, feed])
|
||||||
|
|
||||||
|
const isPeopleFeed = f => isAuthorFeed(f) || isScopeFeed(f)
|
||||||
|
|
||||||
|
const isTopicsFeed = f => isTagFeed(f) && f[1] === "#t"
|
||||||
|
|
||||||
const isNormalized = feed =>
|
const isNormalized = feed =>
|
||||||
feed[0] === FeedType.Intersection && getFeedArgs(feed).every(f => !hasSubFeeds(f))
|
feed[0] === FeedType.Intersection && getFeedArgs(feed).every(f => !hasSubFeeds(f))
|
||||||
|
|
||||||
|
const removeSubFeed = condition => {
|
||||||
|
feed = [feed[0], ...feed.slice(1).filter(f => !condition(f))]
|
||||||
|
}
|
||||||
|
|
||||||
|
const prependDefaultSubFeed = (condition, subFeed) => {
|
||||||
|
if (!getFeedArgs(feed).some(condition)) {
|
||||||
|
feed = feed.toSpliced(1, 0, subFeed)
|
||||||
|
}
|
||||||
|
|
||||||
|
feed = [feed[0], ...partition(condition, feed.slice(1)).flatMap(identity)]
|
||||||
|
}
|
||||||
|
|
||||||
const inferFormType = feed => {
|
const inferFormType = feed => {
|
||||||
for (const subFeed of getFeedArgs(normalize(feed))) {
|
for (const subFeed of getFeedArgs(normalize(feed))) {
|
||||||
if ([FeedType.Scope, FeedType.Author].includes(subFeed[0])) {
|
if ([FeedType.Scope, FeedType.Author].includes(subFeed[0])) {
|
||||||
@ -61,8 +88,30 @@
|
|||||||
feed = makeIntersectionFeed()
|
feed = makeIntersectionFeed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove filters directly related to the previous type
|
||||||
|
if (formType === FormType.People) {
|
||||||
|
removeSubFeed(isPeopleFeed)
|
||||||
|
} else if (formType === FormType.Topics) {
|
||||||
|
removeSubFeed(isTopicsFeed)
|
||||||
|
} else if (formType === FormType.Relays) {
|
||||||
|
removeSubFeed(isRelayFeed)
|
||||||
|
} else if (formType === FormType.DVMs) {
|
||||||
|
removeSubFeed(isDVMFeed)
|
||||||
|
}
|
||||||
|
|
||||||
formType = newFormType
|
formType = newFormType
|
||||||
|
|
||||||
|
// Add a default filter depending on the new form type
|
||||||
|
if (formType === FormType.People) {
|
||||||
|
prependDefaultSubFeed(isPeopleFeed, makeScopeFeed(Scope.Follows))
|
||||||
|
} else if (formType === FormType.Topics) {
|
||||||
|
prependDefaultSubFeed(isTopicsFeed, makeTagFeed("#t"))
|
||||||
|
} else if (formType === FormType.Relays) {
|
||||||
|
prependDefaultSubFeed(isRelayFeed, makeRelayFeed())
|
||||||
|
} else if (formType === FormType.DVMs) {
|
||||||
|
prependDefaultSubFeed(isDVMFeed, makeDVMFeed({kind: 5300}))
|
||||||
|
}
|
||||||
|
|
||||||
onChange?.(feed)
|
onChange?.(feed)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,56 +120,60 @@
|
|||||||
onChange?.(feed)
|
onChange?.(feed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let innerWidth = 0
|
||||||
let formType = inferFormType(feed)
|
let formType = inferFormType(feed)
|
||||||
|
|
||||||
|
$: formTypeOptions = innerWidth < 640
|
||||||
|
? [FormType.People, FormType.Topics, FormType.Relays, FormType.DVMs]
|
||||||
|
: [FormType.People, FormType.Topics, FormType.Relays, FormType.DVMs, FormType.Advanced]
|
||||||
|
|
||||||
$: console.log(JSON.stringify(normalize(feed), null, 2))
|
$: console.log(JSON.stringify(normalize(feed), null, 2))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<svelte:window bind:innerWidth />
|
||||||
|
|
||||||
<FlexColumn>
|
<FlexColumn>
|
||||||
{#if !hideType}
|
<Card class="-mb-8">
|
||||||
<Card>
|
<FlexColumn small>
|
||||||
<Field label="Choose a feed type">
|
<span class="staatliches text-lg">Choose a feed type</span>
|
||||||
<SelectTiles
|
<SelectTiles
|
||||||
options={[FormType.People, FormType.Topics, FormType.Relays, FormType.DVMs]}
|
class="grid-cols-2 sm:grid-cols-5"
|
||||||
onChange={onFormTypeChange}
|
options={formTypeOptions}
|
||||||
value={formType}>
|
onChange={onFormTypeChange}
|
||||||
<div slot="item" class="flex flex-col items-center" let:option let:active>
|
value={formType}>
|
||||||
{#if option === FormType.People}
|
<div slot="item" class="flex flex-col items-center" let:option let:active>
|
||||||
<Icon
|
{#if option === FormType.People}
|
||||||
icon="people-nearby"
|
<Icon
|
||||||
class="h-12 w-12"
|
icon="people-nearby"
|
||||||
color={active ? "accent" : "tinted-800"} />
|
class="h-12 w-12"
|
||||||
<span class="staatliches text-2xl">People</span>
|
color={active ? "accent" : "tinted-800"} />
|
||||||
{:else if option === FormType.Topics}
|
<span class="staatliches text-2xl">People</span>
|
||||||
<span class="flex h-12 w-12 items-center justify-center" class:text-accent={active}>
|
{:else if option === FormType.Topics}
|
||||||
<i class="fa fa-2xl fa-tags" />
|
<span class="flex h-12 w-12 items-center justify-center" class:text-accent={active}>
|
||||||
</span>
|
<i class="fa fa-2xl fa-tags" />
|
||||||
<span class="staatliches text-2xl">Topics</span>
|
</span>
|
||||||
{:else if option === FormType.Relays}
|
<span class="staatliches text-2xl">Topics</span>
|
||||||
<Icon icon="server" class="h-12 w-12" color={active ? "accent" : "tinted-800"} />
|
{:else if option === FormType.Relays}
|
||||||
<span class="staatliches text-2xl">Relays</span>
|
<Icon icon="server" class="h-12 w-12" color={active ? "accent" : "tinted-800"} />
|
||||||
{:else if option === FormType.DVMs}
|
<span class="staatliches text-2xl">Relays</span>
|
||||||
<Icon icon="network" class="h-12 w-12" color={active ? "accent" : "tinted-800"} />
|
{:else if option === FormType.DVMs}
|
||||||
<span class="staatliches text-2xl">DVMs</span>
|
<Icon icon="network" class="h-12 w-12" color={active ? "accent" : "tinted-800"} />
|
||||||
{/if}
|
<span class="staatliches text-2xl">DVMs</span>
|
||||||
</div>
|
{:else if option === FormType.Advanced}
|
||||||
</SelectTiles>
|
<span class="flex h-12 w-12 items-center justify-center" class:text-accent={active}>
|
||||||
</Field>
|
<i class="fa fa-2xl fa-cogs" />
|
||||||
<div class="flex justify-end">
|
</span>
|
||||||
<Anchor underline on:click={() => onFormTypeChange(FormType.Advanced)}
|
<span class="staatliches text-2xl">Advanced</span>
|
||||||
>Advanced mode</Anchor>
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</SelectTiles>
|
||||||
{/if}
|
</FlexColumn>
|
||||||
{#if formType === FormType.People}
|
</Card>
|
||||||
<FeedFormPeople feed={normalize(feed)} onChange={onFeedChange} />
|
<FlexColumn>
|
||||||
{:else if formType === FormType.Topics}
|
{#if formType === FormType.Advanced}
|
||||||
<FeedFormTopics feed={normalize(feed)} onChange={onFeedChange} />
|
<FeedFormAdvanced {feed} onChange={onFeedChange} />
|
||||||
{:else if formType === FormType.Relays}
|
{:else}
|
||||||
<FeedFormRelays feed={normalize(feed)} onChange={onFeedChange} />
|
<FeedFormFilters {feed} onChange={onFeedChange} />
|
||||||
{:else if formType === FormType.DVMs}
|
{/if}
|
||||||
<FeedFormDVMs feed={normalize(feed)} onChange={onFeedChange} />
|
</FlexColumn>
|
||||||
{:else if formType === FormType.Advanced}
|
|
||||||
<FeedFormAdvanced {feed} onChange={onFeedChange} />
|
|
||||||
{/if}
|
|
||||||
</FlexColumn>
|
</FlexColumn>
|
||||||
|
96
src/app/shared/FeedForm.svelte
Normal file
96
src/app/shared/FeedForm.svelte
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Field from "src/partials/Field.svelte"
|
||||||
|
import {showInfo} from "src/partials/Toast.svelte"
|
||||||
|
import Subheading from "src/partials/Subheading.svelte"
|
||||||
|
import Modal from "src/partials/Modal.svelte"
|
||||||
|
import Card from "src/partials/Card.svelte"
|
||||||
|
import Input from "src/partials/Input.svelte"
|
||||||
|
import Textarea from "src/partials/Textarea.svelte"
|
||||||
|
import Anchor from "src/partials/Anchor.svelte"
|
||||||
|
import FeedField from "src/app/shared/FeedField.svelte"
|
||||||
|
import {router} from "src/app/util"
|
||||||
|
import {initFeed, createFeed, editFeed, displayFeed} from "src/domain"
|
||||||
|
import {publishDeletionForEvent, hints, createAndPublish} from "src/engine"
|
||||||
|
|
||||||
|
export let feed
|
||||||
|
export let onSave
|
||||||
|
|
||||||
|
const switchToCreate = () => {
|
||||||
|
feed = initFeed({definition: feed.definition})
|
||||||
|
saveIsOpen = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const openDelete = () => {
|
||||||
|
deleteIsOpen = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeDelete = () => {
|
||||||
|
deleteIsOpen = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmDelete = () => {
|
||||||
|
if (feed.event) {
|
||||||
|
publishDeletionForEvent(feed.event)
|
||||||
|
}
|
||||||
|
|
||||||
|
router.at("feeds").push()
|
||||||
|
}
|
||||||
|
|
||||||
|
const openSave = () => {
|
||||||
|
saveIsOpen = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeSave = () => {
|
||||||
|
saveIsOpen = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveFeed = async () => {
|
||||||
|
const relays = hints.WriteRelays().getUrls()
|
||||||
|
const template = feed.event ? editFeed(feed) : createFeed(feed)
|
||||||
|
const pub = await createAndPublish({...template, relays})
|
||||||
|
|
||||||
|
showInfo("Your feed has been saved!")
|
||||||
|
onSave(pub.request.event)
|
||||||
|
}
|
||||||
|
|
||||||
|
let saveIsOpen = false
|
||||||
|
let deleteIsOpen = false
|
||||||
|
|
||||||
|
$: isEdit = feed.event || feed.list
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<FeedField bind:feed={feed.definition} />
|
||||||
|
{#if isEdit}
|
||||||
|
<Card class="flex flex-col sm:flex-row justify-between">
|
||||||
|
<p>You are currently editing your {displayFeed(feed)} feed.</p>
|
||||||
|
<Anchor underline on:click={switchToCreate} class="text-neutral-400">
|
||||||
|
Create a new feed instead
|
||||||
|
</Anchor>
|
||||||
|
</Card>
|
||||||
|
{:else if saveIsOpen}
|
||||||
|
<Modal onEscape={closeSave}>
|
||||||
|
<Field label="Feed Name">
|
||||||
|
<Input bind:value={feed.name} />
|
||||||
|
</Field>
|
||||||
|
<Field label="Feed Description">
|
||||||
|
<Textarea bind:value={feed.description} />
|
||||||
|
</Field>
|
||||||
|
<div class="flex justify-between gap-2">
|
||||||
|
<Anchor button on:click={closeSave}>Cancel</Anchor>
|
||||||
|
<Anchor button accent on:click={saveFeed}>Save</Anchor>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
{/if}
|
||||||
|
<slot name="controls" {feed} remove={openDelete} save={isEdit ? saveFeed : openSave} />
|
||||||
|
{#if deleteIsOpen}
|
||||||
|
<Modal onEscape={closeDelete}>
|
||||||
|
<Subheading>Confirm deletion</Subheading>
|
||||||
|
<p>
|
||||||
|
Are you sure you want to delete your {displayFeed(feed)} feed?
|
||||||
|
</p>
|
||||||
|
<div class="flex justify-between gap-2">
|
||||||
|
<Anchor button on:click={closeDelete}>Cancel</Anchor>
|
||||||
|
<Anchor button danger on:click={confirmDelete}>Confirm</Anchor>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
{/if}
|
@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {tryCatch} from "@welshman/lib"
|
import {tryCatch} from "@welshman/lib"
|
||||||
|
import Card from "src/partials/Card.svelte"
|
||||||
import Field from "src/partials/Field.svelte"
|
import Field from "src/partials/Field.svelte"
|
||||||
import Textarea from "src/partials/Textarea.svelte"
|
import Textarea from "src/partials/Textarea.svelte"
|
||||||
|
|
||||||
@ -33,17 +34,19 @@
|
|||||||
let json = JSON.stringify(feed, null, 2)
|
let json = JSON.stringify(feed, null, 2)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field label="Enter your custom feed below">
|
<Card>
|
||||||
<Textarea
|
<Field label="Enter your custom feed below">
|
||||||
class="h-72 whitespace-pre-wrap"
|
<Textarea
|
||||||
value={json}
|
class="h-72 whitespace-pre-wrap"
|
||||||
on:input={onInput}
|
value={json}
|
||||||
on:focus={onFocus}
|
on:input={onInput}
|
||||||
on:blur={onBlur} />
|
on:focus={onFocus}
|
||||||
</Field>
|
on:blur={onBlur} />
|
||||||
{#if !isValid && !isFocused}
|
</Field>
|
||||||
<p>
|
{#if !isValid && !isFocused}
|
||||||
<i class="fa fa-triangle-exclamation" />
|
<p>
|
||||||
Your feed is currently invalid. Please double check that it is valid JSON.
|
<i class="fa fa-triangle-exclamation" />
|
||||||
</p>
|
Your feed is currently invalid. Please double check that it is valid JSON.
|
||||||
{/if}
|
</p>
|
||||||
|
{/if}
|
||||||
|
</Card>
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import {flatten, partition} from "@welshman/lib"
|
|
||||||
import {FeedType, getFeedArgs, isDVMFeed} from "@welshman/feeds"
|
|
||||||
import FeedFormFilters from "src/app/shared/FeedFormFilters.svelte"
|
|
||||||
|
|
||||||
export let feed
|
|
||||||
export let onChange
|
|
||||||
|
|
||||||
if (!getFeedArgs(feed).some(isDVMFeed)) {
|
|
||||||
onChange([...feed, [FeedType.DVM, {kind: 5300}]])
|
|
||||||
}
|
|
||||||
|
|
||||||
$: sorted = [feed[0], ...flatten(partition(isDVMFeed, getFeedArgs(feed)))]
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<FeedFormFilters feed={sorted} {onChange} />
|
|
@ -19,6 +19,7 @@
|
|||||||
import Card from "src/partials/Card.svelte"
|
import Card from "src/partials/Card.svelte"
|
||||||
import Menu from "src/partials/Menu.svelte"
|
import Menu from "src/partials/Menu.svelte"
|
||||||
import MenuItem from "src/partials/MenuItem.svelte"
|
import MenuItem from "src/partials/MenuItem.svelte"
|
||||||
|
import FlexColumn from "src/partials/FlexColumn.svelte"
|
||||||
import Anchor from "src/partials/Anchor.svelte"
|
import Anchor from "src/partials/Anchor.svelte"
|
||||||
import Popover2 from "src/partials/Popover2.svelte"
|
import Popover2 from "src/partials/Popover2.svelte"
|
||||||
import FeedFormSectionPeople from "src/app/shared/FeedFormSectionPeople.svelte"
|
import FeedFormSectionPeople from "src/app/shared/FeedFormSectionPeople.svelte"
|
||||||
@ -68,23 +69,25 @@
|
|||||||
{#each subFeeds as subFeed, i}
|
{#each subFeeds as subFeed, i}
|
||||||
{@const change = f => onSubFeedChange(i + 1, f)}
|
{@const change = f => onSubFeedChange(i + 1, f)}
|
||||||
<Card class="relative">
|
<Card class="relative">
|
||||||
{#if isPeopleFeed(subFeed)}
|
<FlexColumn small>
|
||||||
<FeedFormSectionPeople feed={subFeed} onChange={change} />
|
{#if isPeopleFeed(subFeed)}
|
||||||
{:else if isRelayFeed(subFeed)}
|
<FeedFormSectionPeople feed={subFeed} onChange={change} />
|
||||||
<FeedFormSectionRelays feed={subFeed} onChange={change} />
|
{:else if isRelayFeed(subFeed)}
|
||||||
{:else if isTopicFeed(subFeed)}
|
<FeedFormSectionRelays feed={subFeed} onChange={change} />
|
||||||
<FeedFormSectionTopics feed={subFeed} onChange={change} />
|
{:else if isTopicFeed(subFeed)}
|
||||||
{:else if isMentionFeed(subFeed)}
|
<FeedFormSectionTopics feed={subFeed} onChange={change} />
|
||||||
<FeedFormSectionMentions feed={subFeed} onChange={change} />
|
{:else if isMentionFeed(subFeed)}
|
||||||
{:else if isKindFeed(subFeed)}
|
<FeedFormSectionMentions feed={subFeed} onChange={change} />
|
||||||
<FeedFormSectionKinds feed={subFeed} onChange={change} />
|
{:else if isKindFeed(subFeed)}
|
||||||
{:else if isCreatedAtFeed(subFeed)}
|
<FeedFormSectionKinds feed={subFeed} onChange={change} />
|
||||||
<FeedFormSectionCreatedAt feed={subFeed} onChange={change} />
|
{:else if isCreatedAtFeed(subFeed)}
|
||||||
{:else if isDVMFeed(subFeed)}
|
<FeedFormSectionCreatedAt feed={subFeed} onChange={change} />
|
||||||
<FeedFormSectionDVM feed={subFeed} onChange={change} />
|
{:else if isDVMFeed(subFeed)}
|
||||||
{:else}
|
<FeedFormSectionDVM feed={subFeed} onChange={change} />
|
||||||
No support for editing {toTitle(subFeed[0])} filters. Click "Advanced" to edit manually.
|
{:else}
|
||||||
{/if}
|
No support for editing {toTitle(subFeed[0])} filters. Click "Advanced" to edit manually.
|
||||||
|
{/if}
|
||||||
|
</FlexColumn>
|
||||||
{#if i > 0}
|
{#if i > 0}
|
||||||
<div
|
<div
|
||||||
class="absolute right-2 top-2 h-4 w-4 cursor-pointer"
|
class="absolute right-2 top-2 h-4 w-4 cursor-pointer"
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import {partition, identity} from "ramda"
|
|
||||||
import {FeedType, Scope, getFeedArgs, isAuthorFeed, isScopeFeed} from "@welshman/feeds"
|
|
||||||
import FeedFormFilters from "src/app/shared/FeedFormFilters.svelte"
|
|
||||||
|
|
||||||
export let feed
|
|
||||||
export let onChange
|
|
||||||
|
|
||||||
const isPeopleFeed = f => isAuthorFeed(f) || isScopeFeed(f)
|
|
||||||
|
|
||||||
if (!getFeedArgs(feed).some(isPeopleFeed)) {
|
|
||||||
onChange([...feed, [FeedType.Scope, Scope.Follows]])
|
|
||||||
}
|
|
||||||
|
|
||||||
$: sorted = [feed[0], ...partition(isPeopleFeed, feed.slice(1)).flatMap(identity)]
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<FeedFormFilters feed={sorted} {onChange} />
|
|
@ -1,17 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import {flatten, partition} from "@welshman/lib"
|
|
||||||
import {makeRelayFeed, getFeedArgs, isRelayFeed} from "@welshman/feeds"
|
|
||||||
import type {IntersectionFeed} from "@welshman/feeds"
|
|
||||||
import FeedFormFilters from "src/app/shared/FeedFormFilters.svelte"
|
|
||||||
|
|
||||||
export let feed: IntersectionFeed
|
|
||||||
export let onChange
|
|
||||||
|
|
||||||
if (!getFeedArgs(feed).some(isRelayFeed)) {
|
|
||||||
onChange([...feed, makeRelayFeed()])
|
|
||||||
}
|
|
||||||
|
|
||||||
$: sorted = [feed[0], ...flatten(partition(isRelayFeed, getFeedArgs(feed)))]
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<FeedFormFilters feed={sorted} {onChange} />
|
|
@ -2,7 +2,6 @@
|
|||||||
import {omit} from "ramda"
|
import {omit} from "ramda"
|
||||||
import {FeedType} from "@welshman/feeds"
|
import {FeedType} from "@welshman/feeds"
|
||||||
import {createLocalDate, dateToSeconds, formatTimestampAsDate} from "src/util/misc"
|
import {createLocalDate, dateToSeconds, formatTimestampAsDate} from "src/util/misc"
|
||||||
import Field from "src/partials/Field.svelte"
|
|
||||||
import DateInput from "src/partials/DateInput.svelte"
|
import DateInput from "src/partials/DateInput.svelte"
|
||||||
|
|
||||||
export let feed
|
export let feed
|
||||||
@ -25,15 +24,14 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field label="What time range would you like to consider?">
|
<span class="staatliches text-lg">What time range would you like to consider?</span>
|
||||||
<div class="grid grid-cols-2 gap-2">
|
<div class="grid grid-cols-2 gap-2">
|
||||||
<DateInput
|
<DateInput
|
||||||
placeholder="Since"
|
placeholder="Since"
|
||||||
value={feed[1]?.since ? formatTimestampAsDate(feed[1]?.since) : null}
|
value={feed[1]?.since ? formatTimestampAsDate(feed[1]?.since) : null}
|
||||||
onChange={changeSince} />
|
onChange={changeSince} />
|
||||||
<DateInput
|
<DateInput
|
||||||
placeholder="Until"
|
placeholder="Until"
|
||||||
value={feed[1]?.until ? formatTimestampAsDate(feed[1]?.until) : null}
|
value={feed[1]?.until ? formatTimestampAsDate(feed[1]?.until) : null}
|
||||||
onChange={changeUntil} />
|
onChange={changeUntil} />
|
||||||
</div>
|
</div>
|
||||||
</Field>
|
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {getFeedArgs} from "@welshman/feeds"
|
import {getFeedArgs} from "@welshman/feeds"
|
||||||
import Field from "src/partials/Field.svelte"
|
|
||||||
import DVMField from "src/app/shared/DVMField.svelte"
|
import DVMField from "src/app/shared/DVMField.svelte"
|
||||||
|
|
||||||
export let feed
|
export let feed
|
||||||
export let onChange
|
export let onChange
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field label="What options should be sent to the DVM?">
|
<span class="staatliches text-lg">What options should be sent to the DVM?</span>
|
||||||
{#each getFeedArgs(feed) as dvmItem, i}
|
{#each getFeedArgs(feed) as dvmItem, i}
|
||||||
<DVMField {dvmItem} onChange={it => onChange(feed.toSpliced(i + 1, 1, it))} />
|
<DVMField {dvmItem} onChange={it => onChange(feed.toSpliced(i + 1, 1, it))} />
|
||||||
{/each}
|
{/each}
|
||||||
</Field>
|
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
import {pluck} from "ramda"
|
import {pluck} from "ramda"
|
||||||
import {FeedType} from "@welshman/feeds"
|
import {FeedType} from "@welshman/feeds"
|
||||||
import {fuzzy} from "src/util/misc"
|
import {fuzzy} from "src/util/misc"
|
||||||
import Field from "src/partials/Field.svelte"
|
|
||||||
import SearchSelect from "src/partials/SearchSelect.svelte"
|
import SearchSelect from "src/partials/SearchSelect.svelte"
|
||||||
|
|
||||||
export let feed
|
export let feed
|
||||||
@ -47,8 +46,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field label="What kind of content do you want to see?">
|
<span class="staatliches text-lg">What kind of content do you want to see?</span>
|
||||||
<SearchSelect multiple search={searchKinds} value={feed.slice(1)} onChange={onKindsChange}>
|
<SearchSelect multiple search={searchKinds} value={feed.slice(1)} onChange={onKindsChange}>
|
||||||
<div slot="item" let:item>{displayKind(item)}</div>
|
<div slot="item" let:item>{displayKind(item)}</div>
|
||||||
</SearchSelect>
|
</SearchSelect>
|
||||||
</Field>
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {FeedType} from "@welshman/feeds"
|
import {FeedType} from "@welshman/feeds"
|
||||||
import Field from "src/partials/Field.svelte"
|
|
||||||
import Anchor from "src/partials/Anchor.svelte"
|
import Anchor from "src/partials/Anchor.svelte"
|
||||||
import SearchSelect from "src/partials/SearchSelect.svelte"
|
import SearchSelect from "src/partials/SearchSelect.svelte"
|
||||||
import PersonBadge from "src/app/shared/PersonBadge.svelte"
|
import PersonBadge from "src/app/shared/PersonBadge.svelte"
|
||||||
@ -11,20 +10,19 @@
|
|||||||
export let onChange
|
export let onChange
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field label="Which mentions would you like to see?">
|
<span class="staatliches text-lg">Which mentions would you like to see?</span>
|
||||||
<SearchSelect
|
<SearchSelect
|
||||||
multiple
|
multiple
|
||||||
value={feed.slice(2)}
|
value={feed.slice(2)}
|
||||||
search={$searchPubkeys}
|
search={$searchPubkeys}
|
||||||
onChange={pubkeys => onChange([FeedType.Tag, "#p", ...pubkeys])}>
|
onChange={pubkeys => onChange([FeedType.Tag, "#p", ...pubkeys])}>
|
||||||
<span slot="item" let:item let:context>
|
<span slot="item" let:item let:context>
|
||||||
{#if context === "value"}
|
{#if context === "value"}
|
||||||
<Anchor modal href={router.at("people").of(item).toString()}>
|
<Anchor modal href={router.at("people").of(item).toString()}>
|
||||||
{displayPubkey(item)}
|
{displayPubkey(item)}
|
||||||
</Anchor>
|
</Anchor>
|
||||||
{:else}
|
{:else}
|
||||||
<PersonBadge inert pubkey={item} />
|
<PersonBadge inert pubkey={item} />
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
</SearchSelect>
|
</SearchSelect>
|
||||||
</Field>
|
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
import {toTitle} from "hurdak"
|
import {toTitle} from "hurdak"
|
||||||
import {without} from "ramda"
|
import {without} from "ramda"
|
||||||
import {FeedType, Scope, isScopeFeed, isAuthorFeed} from "@welshman/feeds"
|
import {FeedType, Scope, isScopeFeed, isAuthorFeed} from "@welshman/feeds"
|
||||||
import Field from "src/partials/Field.svelte"
|
|
||||||
import Anchor from "src/partials/Anchor.svelte"
|
import Anchor from "src/partials/Anchor.svelte"
|
||||||
import SelectButton from "src/partials/SelectButton.svelte"
|
import SelectButton from "src/partials/SelectButton.svelte"
|
||||||
import SearchSelect from "src/partials/SearchSelect.svelte"
|
import SearchSelect from "src/partials/SearchSelect.svelte"
|
||||||
@ -26,28 +25,28 @@
|
|||||||
$: scopes = isScopeFeed(feed) ? feed.slice(1) : ["custom"]
|
$: scopes = isScopeFeed(feed) ? feed.slice(1) : ["custom"]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field label="Which authors would you like to see?">
|
<span class="staatliches text-lg">Which authors would you like to see?</span>
|
||||||
<SelectButton
|
<SelectButton
|
||||||
|
multiple
|
||||||
|
value={scopes}
|
||||||
|
displayOption={toTitle}
|
||||||
|
options={scopeOptions}
|
||||||
|
onChange={onScopeChange} />
|
||||||
|
{#if isAuthorFeed(feed)}
|
||||||
|
<div class="h-px bg-neutral-900" />
|
||||||
|
<SearchSelect
|
||||||
multiple
|
multiple
|
||||||
value={scopes}
|
value={feed.slice(1)}
|
||||||
displayOption={toTitle}
|
search={$searchPubkeys}
|
||||||
options={scopeOptions}
|
onChange={pubkeys => onChange([FeedType.Author, ...pubkeys])}>
|
||||||
onChange={onScopeChange} />
|
<span slot="item" let:item let:context>
|
||||||
{#if isAuthorFeed(feed)}
|
{#if context === "value"}
|
||||||
<SearchSelect
|
<Anchor modal href={router.at("people").of(item).toString()}>
|
||||||
multiple
|
{displayPubkey(item)}
|
||||||
value={feed.slice(1)}
|
</Anchor>
|
||||||
search={$searchPubkeys}
|
{:else}
|
||||||
onChange={pubkeys => onChange([FeedType.Author, ...pubkeys])}>
|
<PersonBadge inert pubkey={item} />
|
||||||
<span slot="item" let:item let:context>
|
{/if}
|
||||||
{#if context === "value"}
|
</span>
|
||||||
<Anchor modal href={router.at("people").of(item).toString()}>
|
</SearchSelect>
|
||||||
{displayPubkey(item)}
|
{/if}
|
||||||
</Anchor>
|
|
||||||
{:else}
|
|
||||||
<PersonBadge inert pubkey={item} />
|
|
||||||
{/if}
|
|
||||||
</span>
|
|
||||||
</SearchSelect>
|
|
||||||
{/if}
|
|
||||||
</Field>
|
|
||||||
|
@ -1,19 +1,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {FeedType} from '@welshman/feeds'
|
import {FeedType} from "@welshman/feeds"
|
||||||
import Field from 'src/partials/Field.svelte'
|
import SearchSelect from "src/partials/SearchSelect.svelte"
|
||||||
import SearchSelect from 'src/partials/SearchSelect.svelte'
|
import {searchRelayUrls, displayRelayUrl} from "src/engine"
|
||||||
import {searchRelayUrls, displayRelayUrl} from 'src/engine'
|
|
||||||
|
|
||||||
export let feed
|
export let feed
|
||||||
export let onChange
|
export let onChange
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field label="Which relays would you like to use?">
|
<span class="staatliches text-lg">Which relays would you like to use?</span>
|
||||||
<SearchSelect
|
<SearchSelect
|
||||||
multiple
|
multiple
|
||||||
value={feed.slice(1)}
|
value={feed.slice(1)}
|
||||||
search={$searchRelayUrls}
|
search={$searchRelayUrls}
|
||||||
onChange={urls => onChange([FeedType.Relay, ...urls])}>
|
onChange={urls => onChange([FeedType.Relay, ...urls])}>
|
||||||
<span slot="item" let:item>{displayRelayUrl(item)}</span>
|
<span slot="item" let:item>{displayRelayUrl(item)}</span>
|
||||||
</SearchSelect>
|
</SearchSelect>
|
||||||
</Field>
|
|
||||||
|
@ -1,19 +1,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {FeedType} from '@welshman/feeds'
|
import {FeedType} from "@welshman/feeds"
|
||||||
import Field from 'src/partials/Field.svelte'
|
import SearchSelect from "src/partials/SearchSelect.svelte"
|
||||||
import SearchSelect from 'src/partials/SearchSelect.svelte'
|
import {searchTopicNames} from "src/engine"
|
||||||
import {searchTopicNames} from 'src/engine'
|
|
||||||
|
|
||||||
export let feed
|
export let feed
|
||||||
export let onChange
|
export let onChange
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field label="Which topics do you want to see?">
|
<span class="staatliches text-lg">Which topics do you want to see?</span>
|
||||||
<SearchSelect
|
<SearchSelect
|
||||||
multiple
|
multiple
|
||||||
value={feed.slice(2)}
|
value={feed.slice(2)}
|
||||||
search={$searchTopicNames}
|
search={$searchTopicNames}
|
||||||
onChange={topics => onChange([FeedType.Tag, "#t", ...topics])}>
|
onChange={topics => onChange([FeedType.Tag, "#t", ...topics])}>
|
||||||
<span slot="item" let:item>#{item}</span>
|
<span slot="item" let:item>#{item}</span>
|
||||||
</SearchSelect>
|
</SearchSelect>
|
||||||
</Field>
|
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import {partition, identity} from "ramda"
|
|
||||||
import {FeedType, getFeedArgs, isTagFeed} from "@welshman/feeds"
|
|
||||||
import FeedFormFilters from "src/app/shared/FeedFormFilters.svelte"
|
|
||||||
|
|
||||||
export let feed
|
|
||||||
export let onChange
|
|
||||||
|
|
||||||
const isTopicFeed = f => isTagFeed(f) && f[1] === "#t"
|
|
||||||
|
|
||||||
if (!getFeedArgs(feed).some(isTopicFeed)) {
|
|
||||||
onChange([...feed, [FeedType.Tag, "#t"]])
|
|
||||||
}
|
|
||||||
|
|
||||||
$: sorted = [feed[0], ...partition(isTopicFeed, feed.slice(1)).flatMap(identity)]
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<FeedFormFilters feed={sorted} {onChange} />
|
|
18
src/app/views/FeedCreate.svelte
Normal file
18
src/app/views/FeedCreate.svelte
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {makeIntersectionFeed, makeScopeFeed, Scope} from '@welshman/feeds'
|
||||||
|
import {initFeed} from 'src/domain'
|
||||||
|
import Anchor from 'src/partials/Anchor.svelte'
|
||||||
|
import FeedForm from 'src/app/shared/FeedForm.svelte'
|
||||||
|
import {router} from 'src/app/util'
|
||||||
|
|
||||||
|
const feed = initFeed({definition: makeIntersectionFeed(makeScopeFeed(Scope.Follows))})
|
||||||
|
|
||||||
|
const exit = () => router.pop()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<FeedForm {feed} onSave={exit}>
|
||||||
|
<div slot="controls" let:save class="flex justify-between">
|
||||||
|
<Anchor button on:click={exit}>Cancel</Anchor>
|
||||||
|
<Anchor button accent on:click={save}>Save</Anchor>
|
||||||
|
</div>
|
||||||
|
</FeedForm>
|
30
src/app/views/FeedEdit.svelte
Normal file
30
src/app/views/FeedEdit.svelte
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {Kind} from '@welshman/util'
|
||||||
|
import Anchor from 'src/partials/Anchor.svelte'
|
||||||
|
import FeedForm from 'src/app/shared/FeedForm.svelte'
|
||||||
|
import {router} from 'src/app/util'
|
||||||
|
import {readFeed, listAsFeed} from 'src/domain'
|
||||||
|
import {repository, lists} from 'src/engine'
|
||||||
|
|
||||||
|
export let address
|
||||||
|
|
||||||
|
const feed = address.startsWith(Kind.ListBookmarks)
|
||||||
|
? listAsFeed(lists.key(address).get())
|
||||||
|
: readFeed(repository.getEvent(address))
|
||||||
|
|
||||||
|
const exit = () => router.pop()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if feed}
|
||||||
|
<FeedForm {feed} onSave={exit}>
|
||||||
|
<div slot="controls" let:remove let:save class="flex justify-between">
|
||||||
|
<Anchor button on:click={exit}>Cancel</Anchor>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<Anchor button on:click={remove}>Delete</Anchor>
|
||||||
|
<Anchor button accent on:click={save}>Save</Anchor>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</FeedForm>
|
||||||
|
{:else}
|
||||||
|
<p class="text-center">Sorry, we weren't able to find that feed.</p>
|
||||||
|
{/if}
|
@ -1,73 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import Card from "src/partials/Card.svelte"
|
|
||||||
import Field from "src/partials/Field.svelte"
|
|
||||||
import Subheading from "src/partials/Subheading.svelte"
|
|
||||||
import Modal from "src/partials/Modal.svelte"
|
|
||||||
import Input from "src/partials/Input.svelte"
|
|
||||||
import Textarea from "src/partials/Textarea.svelte"
|
|
||||||
import Anchor from "src/partials/Anchor.svelte"
|
|
||||||
import FeedField from "src/app/shared/FeedField.svelte"
|
|
||||||
import {router} from "src/app/util"
|
|
||||||
import {initFeed, readFeed, createFeed, editFeed} from "src/domain"
|
|
||||||
import {publishDeletionForEvent, repository, hints, createAndPublish} from "src/engine"
|
|
||||||
|
|
||||||
export let address = null
|
|
||||||
|
|
||||||
const values = address ? readFeed(repository.getEvent(address)) : initFeed()
|
|
||||||
|
|
||||||
const abort = () => router.pop()
|
|
||||||
|
|
||||||
const openDelete = () => {
|
|
||||||
deleteIsOpen = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const closeDelete = () => {
|
|
||||||
deleteIsOpen = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const confirmDelete = () => {
|
|
||||||
publishDeletionForEvent(event)
|
|
||||||
router.at("feeds").push()
|
|
||||||
}
|
|
||||||
|
|
||||||
const saveFeed = async () => {
|
|
||||||
const relays = hints.WriteRelays().getUrls()
|
|
||||||
const template = values.event ? editFeed(values) : createFeed(values)
|
|
||||||
|
|
||||||
await createAndPublish({...template, relays})
|
|
||||||
}
|
|
||||||
|
|
||||||
let deleteIsOpen = false
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<FeedField hideType={address} bind:feed={values.definition} />
|
|
||||||
<Card>
|
|
||||||
<Field label="What would you like to name this feed?">
|
|
||||||
<Input bind:value={values.name} />
|
|
||||||
</Field>
|
|
||||||
<Field label="How would you describe this feed?">
|
|
||||||
<Textarea bind:value={values.description} />
|
|
||||||
</Field>
|
|
||||||
</Card>
|
|
||||||
<div class="flex justify-between gap-2">
|
|
||||||
<Anchor button on:click={abort}>Discard</Anchor>
|
|
||||||
<div class="flex justify-between gap-2">
|
|
||||||
{#if values.event}
|
|
||||||
<Anchor button danger on:click={openDelete}>Delete</Anchor>
|
|
||||||
{/if}
|
|
||||||
<Anchor button accent on:click={saveFeed}>Save</Anchor>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if deleteIsOpen}
|
|
||||||
<Modal onEscape={closeDelete}>
|
|
||||||
<Subheading>Confirm deletion</Subheading>
|
|
||||||
<p>
|
|
||||||
Are you sure you want to delete your "{values.name}" feed?
|
|
||||||
</p>
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<Anchor button on:click={closeDelete}>Cancel</Anchor>
|
|
||||||
<Anchor button accent on:click={confirmDelete}>Confirm</Anchor>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
{/if}
|
|
@ -1,13 +1,11 @@
|
|||||||
<script type="ts">
|
<script type="ts">
|
||||||
import {getAddress} from "@welshman/util"
|
import {getAddress} from "@welshman/util"
|
||||||
|
import {fly} from "src/util/transition"
|
||||||
import Subheading from "src/partials/Subheading.svelte"
|
import Subheading from "src/partials/Subheading.svelte"
|
||||||
import FlexColumn from "src/partials/FlexColumn.svelte"
|
|
||||||
import Anchor from "src/partials/Anchor.svelte"
|
import Anchor from "src/partials/Anchor.svelte"
|
||||||
import Card from "src/partials/Card.svelte"
|
import FeedCard from "src/app/shared/FeedCard.svelte"
|
||||||
import FeedSummary from "src/app/shared/FeedSummary.svelte"
|
|
||||||
import {router} from "src/app/util/router"
|
import {router} from "src/app/util/router"
|
||||||
import {readFeed} from "src/domain"
|
import {userFeeds, userLists} from "src/engine"
|
||||||
import {userFeeds} from "src/engine"
|
|
||||||
|
|
||||||
const createFeed = () => router.at("feeds/create").open()
|
const createFeed = () => router.at("feeds/create").open()
|
||||||
|
|
||||||
@ -22,28 +20,27 @@
|
|||||||
</div>
|
</div>
|
||||||
{#each $userFeeds as event (getAddress(event))}
|
{#each $userFeeds as event (getAddress(event))}
|
||||||
{@const address = getAddress(event)}
|
{@const address = getAddress(event)}
|
||||||
{@const {name, description, definition} = readFeed(event)}
|
<div in:fly={{y: 20}}>
|
||||||
<Card>
|
<FeedCard {address}>
|
||||||
<FlexColumn>
|
<div slot="controls">
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="staatliches flex items-center gap-3 text-xl">
|
|
||||||
<i class="fa fa-rss" />
|
|
||||||
{#if name}
|
|
||||||
{name}
|
|
||||||
{:else}
|
|
||||||
<span class="text-neutral-500">No name</span>
|
|
||||||
{/if}
|
|
||||||
</span>
|
|
||||||
<Anchor on:click={() => editFeed(address)}>
|
<Anchor on:click={() => editFeed(address)}>
|
||||||
<i class="fa fa-edit" /> Edit
|
<i class="fa fa-edit" /> Edit
|
||||||
</Anchor>
|
</Anchor>
|
||||||
</div>
|
</div>
|
||||||
{#if description}
|
</FeedCard>
|
||||||
<p>{description}</p>
|
</div>
|
||||||
{/if}
|
|
||||||
<FeedSummary feed={definition} />
|
|
||||||
</FlexColumn>
|
|
||||||
</Card>
|
|
||||||
{:else}
|
|
||||||
<p class="text-center py-12">You don't have any feeds yet.</p>
|
|
||||||
{/each}
|
{/each}
|
||||||
|
{#each $userLists as list}
|
||||||
|
<div in:fly={{y: 20}}>
|
||||||
|
<FeedCard address={list.address}>
|
||||||
|
<div slot="controls">
|
||||||
|
<Anchor on:click={() => editFeed(list.address)}>
|
||||||
|
<i class="fa fa-edit" /> Edit
|
||||||
|
</Anchor>
|
||||||
|
</div>
|
||||||
|
</FeedCard>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{#if $userFeeds.length === 0 && $userLists.length === 0}
|
||||||
|
<p class="py-12 text-center">No feeds found.</p>
|
||||||
|
{/if}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {fromPairs, randomId} from "@welshman/lib"
|
import {fromPairs, randomId} from "@welshman/lib"
|
||||||
import {Kind, Tags} from "@welshman/util"
|
import {Kind, Tags, decodeAddress} from "@welshman/util"
|
||||||
import type {Rumor} from "@welshman/util"
|
import type {Rumor} from "@welshman/util"
|
||||||
import {makeIntersectionFeed, hasSubFeeds} from "@welshman/feeds"
|
import {makeIntersectionFeed, feedFromTags, hasSubFeeds} from "@welshman/feeds"
|
||||||
import type {Feed as IFeed} from "@welshman/feeds"
|
import type {Feed as IFeed} from "@welshman/feeds"
|
||||||
import {tryJson} from "src/util/misc"
|
import {tryJson} from "src/util/misc"
|
||||||
|
|
||||||
@ -17,6 +17,16 @@ export type Feed = {
|
|||||||
export const normalizeFeedDefinition = feed =>
|
export const normalizeFeedDefinition = feed =>
|
||||||
hasSubFeeds(feed) ? feed : makeIntersectionFeed(feed)
|
hasSubFeeds(feed) ? feed : makeIntersectionFeed(feed)
|
||||||
|
|
||||||
|
// Compatibility with the old way we did custom feeds
|
||||||
|
export const listAsFeed = list =>
|
||||||
|
initFeed({
|
||||||
|
name: list.title,
|
||||||
|
list: list.address,
|
||||||
|
description: list.description,
|
||||||
|
definition: feedFromTags(Tags.fromEvent(list)),
|
||||||
|
identifier: decodeAddress(list.address).identifier,
|
||||||
|
})
|
||||||
|
|
||||||
export const initFeed = (feed: Partial<Feed> = {}): Feed => ({
|
export const initFeed = (feed: Partial<Feed> = {}): Feed => ({
|
||||||
name: "",
|
name: "",
|
||||||
description: "",
|
description: "",
|
||||||
@ -25,11 +35,15 @@ export const initFeed = (feed: Partial<Feed> = {}): Feed => ({
|
|||||||
...feed,
|
...feed,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const readFeed = (event: Rumor): Feed => {
|
export const readFeed = (event: Rumor) => {
|
||||||
|
if (!event) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
const {d: identifier, name = "", description = "", feed = ""} = fromPairs(event.tags)
|
const {d: identifier, name = "", description = "", feed = ""} = fromPairs(event.tags)
|
||||||
const definition = tryJson(() => JSON.parse(feed)) || makeIntersectionFeed()
|
const definition = tryJson(() => JSON.parse(feed)) || makeIntersectionFeed()
|
||||||
|
|
||||||
return {name, identifier, description, definition, event}
|
return {name, identifier, description, definition, event} as Feed
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createFeed = ({identifier, definition, name, description}: Feed) => ({
|
export const createFeed = ({identifier, definition, name, description}: Feed) => ({
|
||||||
@ -50,3 +64,5 @@ export const editFeed = (feed: Feed) => ({
|
|||||||
.setTag("feed", JSON.stringify(feed.definition))
|
.setTag("feed", JSON.stringify(feed.definition))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const displayFeed = (feed: Feed) => (feed.name ? feed.name : "[no name]")
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import {max, prop, omit, partition, equals} from "ramda"
|
import {max, prop, omit, partition, equals} from "ramda"
|
||||||
import {sleep, pickVals} from "hurdak"
|
import {sleep, pickVals} from "hurdak"
|
||||||
import {Worker, derived} from "@welshman/lib"
|
import {Worker} from "@welshman/lib"
|
||||||
import type {Rumor} from "@welshman/util"
|
|
||||||
import {createEvent, Kind, Relay, Repository} from "@welshman/util"
|
import {createEvent, Kind, Relay, Repository} from "@welshman/util"
|
||||||
import {
|
import {
|
||||||
Plex,
|
Plex,
|
||||||
@ -14,6 +13,7 @@ import {
|
|||||||
subscribe as baseSubscribe,
|
subscribe as baseSubscribe,
|
||||||
} from "@welshman/net"
|
} from "@welshman/net"
|
||||||
import type {PublishRequest, SubscribeRequest} from "@welshman/net"
|
import type {PublishRequest, SubscribeRequest} from "@welshman/net"
|
||||||
|
import logger from "src/util/logger"
|
||||||
import {LOCAL_RELAY_URL, isGiftWrap, generatePrivateKey} from "src/util/nostr"
|
import {LOCAL_RELAY_URL, isGiftWrap, generatePrivateKey} from "src/util/nostr"
|
||||||
import {env, pubkey} from "src/engine/session/state"
|
import {env, pubkey} from "src/engine/session/state"
|
||||||
import {getSetting} from "src/engine/session/utils"
|
import {getSetting} from "src/engine/session/utils"
|
||||||
@ -22,7 +22,7 @@ import type {Event} from "src/engine/events/model"
|
|||||||
import {publishes} from "src/engine/events/state"
|
import {publishes} from "src/engine/events/state"
|
||||||
import {LocalTarget} from "./targets"
|
import {LocalTarget} from "./targets"
|
||||||
|
|
||||||
export const repository = new Repository()
|
export const repository = new Repository({throttle: 300})
|
||||||
|
|
||||||
export const relay = new Relay(repository)
|
export const relay = new Relay(repository)
|
||||||
|
|
||||||
@ -33,20 +33,20 @@ export const projections = new Worker<Event>({
|
|||||||
})
|
})
|
||||||
|
|
||||||
projections.addGlobalHandler(event => {
|
projections.addGlobalHandler(event => {
|
||||||
if (event.kind === Kind.Feed) {
|
const kinds = [
|
||||||
|
Kind.Delete,
|
||||||
|
Kind.Feed,
|
||||||
|
Kind.ListBookmarks,
|
||||||
|
]
|
||||||
|
|
||||||
|
if (kinds.includes(event.kind)) {
|
||||||
repository.publish(event)
|
repository.publish(event)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export const allEvents = repository.throttle(300)
|
export const feeds = repository.filter(() => [{kinds: [Kind.Feed]}])
|
||||||
|
|
||||||
export const deriveEvents = filter => allEvents.derived(r => Array.from(r.query([filter])))
|
export const userFeeds = repository.filter(() => [{kinds: [Kind.Feed], authors: [pubkey.get()]}])
|
||||||
|
|
||||||
export const feeds = deriveEvents({kinds: [Kind.Feed]})
|
|
||||||
|
|
||||||
export const userFeeds = derived([feeds, pubkey], ([$feeds, $pubkey]) =>
|
|
||||||
$feeds.filter((e: Rumor) => e.pubkey === pubkey.get()),
|
|
||||||
)
|
|
||||||
|
|
||||||
export const getExecutor = (urls: string[]) => {
|
export const getExecutor = (urls: string[]) => {
|
||||||
const muxUrl = getSetting("multiplextr_url")
|
const muxUrl = getSetting("multiplextr_url")
|
||||||
@ -170,6 +170,8 @@ export const loadOne = (request: MySubscribeRequest) =>
|
|||||||
export const publish = (request: PublishRequest) => {
|
export const publish = (request: PublishRequest) => {
|
||||||
const pub = basePublish(request)
|
const pub = basePublish(request)
|
||||||
|
|
||||||
|
logger.info(`Publishing event`, request)
|
||||||
|
|
||||||
// Make sure the event gets into projections asap
|
// Make sure the event gets into projections asap
|
||||||
projections.push(request.event)
|
projections.push(request.event)
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@
|
|||||||
<div on:click|stopPropagation>
|
<div on:click|stopPropagation>
|
||||||
<AltColor
|
<AltColor
|
||||||
background
|
background
|
||||||
class="relative h-full w-full cursor-auto overflow-hidden rounded-t-2xl pb-20 pt-2">
|
class="relative h-full w-full cursor-auto rounded-t-2xl pb-20 pt-2">
|
||||||
<div class="modal-content-inner m-auto flex max-w-2xl flex-col gap-4 p-2">
|
<div class="modal-content-inner m-auto flex max-w-2xl flex-col gap-4 p-2">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
import cx from "classnames"
|
import cx from "classnames"
|
||||||
import {onMount} from "svelte"
|
import {onMount} from "svelte"
|
||||||
import {reject, equals, identity} from "ramda"
|
import {reject, equals, identity} from "ramda"
|
||||||
import Chip from "src/partials/Chip.svelte"
|
|
||||||
import Input from "src/partials/Input.svelte"
|
import Input from "src/partials/Input.svelte"
|
||||||
import Popover2 from "src/partials/Popover2.svelte"
|
import Popover2 from "src/partials/Popover2.svelte"
|
||||||
import Suggestions from "src/partials/Suggestions.svelte"
|
import Suggestions from "src/partials/Suggestions.svelte"
|
||||||
@ -120,11 +119,14 @@
|
|||||||
{#if multiple}
|
{#if multiple}
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
{#each value as item}
|
{#each value as item}
|
||||||
<Chip class="mb-1 mr-1" onRemove={() => remove(item)}>
|
<div class="px-3 h-7 rounded-full mr-1 mb-1 bg-neutral-900 text-neutral-400 inline-flex items-center">
|
||||||
|
<div class="h-7 w-5 cursor-pointer flex items-center" on:click={() => remove(item)}>
|
||||||
|
<i class="fa fa-times" />
|
||||||
|
</div>
|
||||||
<slot name="item" context="value" {item}>
|
<slot name="item" context="value" {item}>
|
||||||
{displayItem(item)}
|
{displayItem(item)}
|
||||||
</slot>
|
</slot>
|
||||||
</Chip>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -4,16 +4,16 @@
|
|||||||
|
|
||||||
export let displayOption = x => x
|
export let displayOption = x => x
|
||||||
|
|
||||||
const getClassName = (i, active) =>
|
const getClassName = active =>
|
||||||
cx("px-4 py-2 transition-all", {
|
cx("px-4 h-7 transition-all rounded-full mr-2 mb-2 inline-flex items-center", {
|
||||||
"border-l border-solid border-neutral-100": i > 0,
|
"bg-tinted-100 text-accent": active,
|
||||||
"bg-accent text-white": active,
|
"bg-neutral-900 text-neutral-400": !active,
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="inline-block">
|
<div class="inline-block -mb-2">
|
||||||
<SelectList {...$$props} class="inline-flex overflow-hidden rounded-full border border-solid border-neutral-100">
|
<SelectList {...$$props} class="inline-flex staatliches">
|
||||||
<div slot="item" let:i let:active let:option class={getClassName(i, active)}>
|
<div slot="item" let:i let:active let:option class={getClassName(active)}>
|
||||||
{displayOption(option)}
|
{displayOption(option)}
|
||||||
</div>
|
</div>
|
||||||
</SelectList>
|
</SelectList>
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SelectList {...$$props} class="grid grid-cols-{$$props.options.length} justify-between gap-4">
|
<SelectList {...$$props} class={cx("grid justify-between gap-4", $$props.class)}>
|
||||||
<div slot="item" let:i let:active let:option>
|
<div slot="item" let:i let:active let:option>
|
||||||
<Tile class={getClass(active)}>
|
<Tile class={getClass(active)}>
|
||||||
<slot name="item" {option} {active} />
|
<slot name="item" {option} {active} />
|
||||||
|
@ -6,6 +6,7 @@ const colors = {
|
|||||||
transparent: "transparent",
|
transparent: "transparent",
|
||||||
accent: "var(--accent)",
|
accent: "var(--accent)",
|
||||||
warning: "var(--warning)",
|
warning: "var(--warning)",
|
||||||
|
danger: "var(--danger)",
|
||||||
success: "var(--success)",
|
success: "var(--success)",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user