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_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_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,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_SEARCH_RELAYS=wss://relay.nostr.band,wss://nostr.wine,wss://search.nos.today
|
||||
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/create", import("src/app/views/FeedForm.svelte"))
|
||||
router.register("/feeds/:address", import("src/app/views/FeedForm.svelte"), {
|
||||
router.register("/feeds/create", import("src/app/views/FeedCreate.svelte"))
|
||||
router.register("/feeds/:address", import("src/app/views/FeedEdit.svelte"), {
|
||||
serializers: {
|
||||
address: asNaddr("address"),
|
||||
},
|
||||
|
@ -14,7 +14,7 @@
|
||||
import MenuDesktopSecondary from "src/app/MenuDesktopSecondary.svelte"
|
||||
import {feed, slowConnections} from "src/app/state"
|
||||
import {router} from "src/app/util/router"
|
||||
import {readFeed, normalizeFeedDefinition} from "src/domain"
|
||||
import {readFeed, displayFeed, normalizeFeedDefinition} from "src/domain"
|
||||
import {
|
||||
env,
|
||||
user,
|
||||
@ -60,13 +60,13 @@
|
||||
in:fly={{x: -100, duration: 200}}
|
||||
class="fixed bottom-0 left-72 top-0 w-60 bg-tinted-700 pt-24 transition-colors">
|
||||
<MenuDesktopItem
|
||||
class="!h-10 !text-lg"
|
||||
small
|
||||
isActive={equals(followsFeed, normalizedFeed)}
|
||||
on:click={() => loadFeed(followsFeed)}>
|
||||
Follows
|
||||
</MenuDesktopItem>
|
||||
<MenuDesktopItem
|
||||
class="!h-10 !text-lg"
|
||||
small
|
||||
isActive={equals(networkFeed, normalizedFeed)}
|
||||
on:click={() => loadFeed(networkFeed)}>
|
||||
Network
|
||||
@ -74,16 +74,16 @@
|
||||
{#each $userFeeds as event}
|
||||
{@const thisFeed = readFeed(event)}
|
||||
<MenuDesktopItem
|
||||
class="!h-10 !text-lg"
|
||||
small
|
||||
isActive={equals(thisFeed.definition, normalizedFeed)}
|
||||
on:click={() => loadFeed(thisFeed.definition)}>
|
||||
{thisFeed.name}
|
||||
{displayFeed(thisFeed)}
|
||||
</MenuDesktopItem>
|
||||
{/each}
|
||||
{#each $userLists as list}
|
||||
{@const definition = feedFromTags(Tags.fromEvent(list))}
|
||||
<MenuDesktopItem
|
||||
class="!h-10 !text-lg"
|
||||
small
|
||||
isActive={equals(definition, normalizedFeed)}
|
||||
on:click={() => loadFeed(definition)}>
|
||||
{displayList(list)}
|
||||
|
@ -7,10 +7,15 @@
|
||||
export let path = null
|
||||
export let isAlt = false
|
||||
export let isActive = false
|
||||
export let small = false
|
||||
|
||||
$: className = cx("relative staatliches h-12 block transition-all", $$props.class, {
|
||||
"text-3xl text-accent": isActive,
|
||||
"text-2xl text-tinted-400 hover:text-tinted-100": !isActive,
|
||||
$: className = cx("relative staatliches block transition-all", $$props.class, {
|
||||
"h-12": !small,
|
||||
"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-700": !isActive && isAlt,
|
||||
})
|
||||
@ -22,7 +27,9 @@
|
||||
{#if isActive}
|
||||
<div
|
||||
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}
|
||||
</div>
|
||||
</Anchor>
|
||||
|
@ -63,7 +63,6 @@
|
||||
onChange={relays => onChange({...dvmItem, relays})}>
|
||||
<span slot="item" let:item>{displayRelayUrl(item)}</span>
|
||||
</SearchSelect>
|
||||
<p slot="info">Select which relays requests to this DVM should be sent to.</p>
|
||||
</Field>
|
||||
{#each dvmItem.tags || [] as [type, value], i (i + key)}
|
||||
<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">
|
||||
import {debounce} from "throttle-debounce"
|
||||
import {Tags, decodeAddress} from "@welshman/util"
|
||||
import {
|
||||
isScopeFeed,
|
||||
isSearchFeed,
|
||||
makeSearchFeed,
|
||||
Scope,
|
||||
getFeedArgs,
|
||||
feedFromTags,
|
||||
} from "@welshman/feeds"
|
||||
import {isScopeFeed, isSearchFeed, makeSearchFeed, Scope, getFeedArgs} from "@welshman/feeds"
|
||||
import Modal from "src/partials/Modal.svelte"
|
||||
import Textarea from "src/partials/Textarea.svelte"
|
||||
import Subheading from "src/partials/Subheading.svelte"
|
||||
import Field from "src/partials/Field.svelte"
|
||||
import Input from "src/partials/Input.svelte"
|
||||
import Select from "src/partials/Select.svelte"
|
||||
import Popover2 from "src/partials/Popover2.svelte"
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import Menu from "src/partials/Menu.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 {normalizeFeedDefinition, readFeed, initFeed, editFeed, createFeed} from "src/domain"
|
||||
import {normalizeFeedDefinition, readFeed, initFeed, listAsFeed, displayFeed} from "src/domain"
|
||||
import {
|
||||
hints,
|
||||
repository,
|
||||
createAndPublish,
|
||||
displayList as displayList2,
|
||||
publishDeletion,
|
||||
userLists,
|
||||
userFeeds,
|
||||
} from "src/engine"
|
||||
@ -50,30 +39,17 @@
|
||||
formIsOpen = false
|
||||
}
|
||||
|
||||
const openName = () => {
|
||||
nameIsOpen = true
|
||||
}
|
||||
|
||||
const closeName = () => {
|
||||
nameIsOpen = false
|
||||
}
|
||||
|
||||
const toggleReplies = () => {
|
||||
opts = {...opts, shouldHideReplies: !opts.shouldHideReplies}
|
||||
}
|
||||
|
||||
const getSearch = definition => (getFeedArgs(definition)?.find(isSearchFeed)?.[1] as string) || ""
|
||||
|
||||
const onDraftFeedChange = definition => {
|
||||
feed.definition = definition
|
||||
}
|
||||
|
||||
const setFeedDefinition = definition => {
|
||||
opts = {...opts, feed: definition}
|
||||
search = getSearch(definition)
|
||||
closeListMenu()
|
||||
closeForm()
|
||||
closeName()
|
||||
}
|
||||
|
||||
const setSubFeed = subFeed => {
|
||||
@ -94,23 +70,16 @@
|
||||
}
|
||||
|
||||
const setList = list => {
|
||||
feed = initFeed({
|
||||
name: list.title,
|
||||
list: list.address,
|
||||
description: list.description,
|
||||
definition: feedFromTags(Tags.fromEvent(list)),
|
||||
identifier: decodeAddress(list.address).identifier,
|
||||
})
|
||||
|
||||
feed = listAsFeed(list)
|
||||
setFeedDefinition(feed.definition)
|
||||
}
|
||||
|
||||
const saveFeed = async () => {
|
||||
const relays = hints.WriteRelays().getUrls()
|
||||
const template = feed.event ? editFeed(feed) : createFeed(feed)
|
||||
const pub = await createAndPublish({...template, relays})
|
||||
const saveFeed = event => {
|
||||
if (feed.list) {
|
||||
publishDeletion([feed.list])
|
||||
}
|
||||
|
||||
setFeed(pub.request.event)
|
||||
setFeed(event)
|
||||
}
|
||||
|
||||
const onSearchBlur = debounce(500, () => {
|
||||
@ -124,7 +93,6 @@
|
||||
})
|
||||
|
||||
let formIsOpen = false
|
||||
let nameIsOpen = false
|
||||
let listMenuIsOpen = false
|
||||
let feed = address
|
||||
? readFeed(repository.getEvent(address))
|
||||
@ -144,7 +112,10 @@
|
||||
</Select>
|
||||
<div class="flex flex-grow items-center justify-end gap-2">
|
||||
<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">
|
||||
<i class="fa fa-search" />
|
||||
</div>
|
||||
@ -177,7 +148,9 @@
|
||||
</MenuItem>
|
||||
<div class="max-h-96 overflow-auto">
|
||||
{#each $userFeeds as event}
|
||||
<MenuItem on:click={() => setFeed(event)}>{readFeed(event).name}</MenuItem>
|
||||
<MenuItem on:click={() => setFeed(event)}>
|
||||
{displayFeed(readFeed(event))}
|
||||
</MenuItem>
|
||||
{/each}
|
||||
{#each $userLists as list}
|
||||
<MenuItem on:click={() => setList(list)}>{displayList2(list)}</MenuItem>
|
||||
@ -193,33 +166,14 @@
|
||||
|
||||
{#if formIsOpen}
|
||||
<Modal onEscape={closeForm}>
|
||||
{#if event}
|
||||
<Subheading>Edit {feed.name}</Subheading>
|
||||
{:else}
|
||||
<Subheading>Customize your feed</Subheading>
|
||||
{/if}
|
||||
<FeedField feed={feed.definition} onChange={onDraftFeedChange} />
|
||||
<div class="flex justify-between gap-2">
|
||||
<FeedForm hideType {feed} onSave={saveFeed}>
|
||||
<div slot="controls" class="flex justify-between gap-2" let:save>
|
||||
<Anchor button on:click={closeForm}>Discard</Anchor>
|
||||
<div class="flex gap-2">
|
||||
<Anchor button on:click={openName}>Save Feed</Anchor>
|
||||
<Anchor button on:click={save}>Save Feed</Anchor>
|
||||
<Anchor button accent on:click={() => setFeedDefinition(feed.definition)}>Done</Anchor>
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
</FeedForm>
|
||||
</Modal>
|
||||
{/if}
|
||||
|
@ -1,20 +1,31 @@
|
||||
<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 SelectTiles from "src/partials/SelectTiles.svelte"
|
||||
import Card from "src/partials/Card.svelte"
|
||||
import Anchor from "src/partials/Anchor.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 FeedFormFilters from "src/app/shared/FeedFormFilters.svelte"
|
||||
|
||||
export let feed
|
||||
export let onChange = null
|
||||
export let hideType = false
|
||||
|
||||
enum FormType {
|
||||
Advanced = "advanced",
|
||||
@ -26,9 +37,25 @@
|
||||
|
||||
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 =>
|
||||
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 => {
|
||||
for (const subFeed of getFeedArgs(normalize(feed))) {
|
||||
if ([FeedType.Scope, FeedType.Author].includes(subFeed[0])) {
|
||||
@ -61,8 +88,30 @@
|
||||
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
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
@ -71,17 +120,25 @@
|
||||
onChange?.(feed)
|
||||
}
|
||||
|
||||
let innerWidth = 0
|
||||
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))
|
||||
</script>
|
||||
|
||||
<svelte:window bind:innerWidth />
|
||||
|
||||
<FlexColumn>
|
||||
{#if !hideType}
|
||||
<Card>
|
||||
<Field label="Choose a feed type">
|
||||
<Card class="-mb-8">
|
||||
<FlexColumn small>
|
||||
<span class="staatliches text-lg">Choose a feed type</span>
|
||||
<SelectTiles
|
||||
options={[FormType.People, FormType.Topics, FormType.Relays, FormType.DVMs]}
|
||||
class="grid-cols-2 sm:grid-cols-5"
|
||||
options={formTypeOptions}
|
||||
onChange={onFormTypeChange}
|
||||
value={formType}>
|
||||
<div slot="item" class="flex flex-col items-center" let:option let:active>
|
||||
@ -102,25 +159,21 @@
|
||||
{:else if option === FormType.DVMs}
|
||||
<Icon icon="network" class="h-12 w-12" color={active ? "accent" : "tinted-800"} />
|
||||
<span class="staatliches text-2xl">DVMs</span>
|
||||
{:else if option === FormType.Advanced}
|
||||
<span class="flex h-12 w-12 items-center justify-center" class:text-accent={active}>
|
||||
<i class="fa fa-2xl fa-cogs" />
|
||||
</span>
|
||||
<span class="staatliches text-2xl">Advanced</span>
|
||||
{/if}
|
||||
</div>
|
||||
</SelectTiles>
|
||||
</Field>
|
||||
<div class="flex justify-end">
|
||||
<Anchor underline on:click={() => onFormTypeChange(FormType.Advanced)}
|
||||
>Advanced mode</Anchor>
|
||||
</div>
|
||||
</FlexColumn>
|
||||
</Card>
|
||||
{/if}
|
||||
{#if formType === FormType.People}
|
||||
<FeedFormPeople feed={normalize(feed)} onChange={onFeedChange} />
|
||||
{:else if formType === FormType.Topics}
|
||||
<FeedFormTopics feed={normalize(feed)} onChange={onFeedChange} />
|
||||
{:else if formType === FormType.Relays}
|
||||
<FeedFormRelays feed={normalize(feed)} onChange={onFeedChange} />
|
||||
{:else if formType === FormType.DVMs}
|
||||
<FeedFormDVMs feed={normalize(feed)} onChange={onFeedChange} />
|
||||
{:else if formType === FormType.Advanced}
|
||||
<FlexColumn>
|
||||
{#if formType === FormType.Advanced}
|
||||
<FeedFormAdvanced {feed} onChange={onFeedChange} />
|
||||
{:else}
|
||||
<FeedFormFilters {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">
|
||||
import {tryCatch} from "@welshman/lib"
|
||||
import Card from "src/partials/Card.svelte"
|
||||
import Field from "src/partials/Field.svelte"
|
||||
import Textarea from "src/partials/Textarea.svelte"
|
||||
|
||||
@ -33,6 +34,7 @@
|
||||
let json = JSON.stringify(feed, null, 2)
|
||||
</script>
|
||||
|
||||
<Card>
|
||||
<Field label="Enter your custom feed below">
|
||||
<Textarea
|
||||
class="h-72 whitespace-pre-wrap"
|
||||
@ -47,3 +49,4 @@
|
||||
Your feed is currently invalid. Please double check that it is valid JSON.
|
||||
</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 Menu from "src/partials/Menu.svelte"
|
||||
import MenuItem from "src/partials/MenuItem.svelte"
|
||||
import FlexColumn from "src/partials/FlexColumn.svelte"
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import Popover2 from "src/partials/Popover2.svelte"
|
||||
import FeedFormSectionPeople from "src/app/shared/FeedFormSectionPeople.svelte"
|
||||
@ -68,6 +69,7 @@
|
||||
{#each subFeeds as subFeed, i}
|
||||
{@const change = f => onSubFeedChange(i + 1, f)}
|
||||
<Card class="relative">
|
||||
<FlexColumn small>
|
||||
{#if isPeopleFeed(subFeed)}
|
||||
<FeedFormSectionPeople feed={subFeed} onChange={change} />
|
||||
{:else if isRelayFeed(subFeed)}
|
||||
@ -85,6 +87,7 @@
|
||||
{:else}
|
||||
No support for editing {toTitle(subFeed[0])} filters. Click "Advanced" to edit manually.
|
||||
{/if}
|
||||
</FlexColumn>
|
||||
{#if i > 0}
|
||||
<div
|
||||
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 {FeedType} from "@welshman/feeds"
|
||||
import {createLocalDate, dateToSeconds, formatTimestampAsDate} from "src/util/misc"
|
||||
import Field from "src/partials/Field.svelte"
|
||||
import DateInput from "src/partials/DateInput.svelte"
|
||||
|
||||
export let feed
|
||||
@ -25,7 +24,7 @@
|
||||
}
|
||||
</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">
|
||||
<DateInput
|
||||
placeholder="Since"
|
||||
@ -36,4 +35,3 @@
|
||||
value={feed[1]?.until ? formatTimestampAsDate(feed[1]?.until) : null}
|
||||
onChange={changeUntil} />
|
||||
</div>
|
||||
</Field>
|
||||
|
@ -1,14 +1,12 @@
|
||||
<script lang="ts">
|
||||
import {getFeedArgs} from "@welshman/feeds"
|
||||
import Field from "src/partials/Field.svelte"
|
||||
import DVMField from "src/app/shared/DVMField.svelte"
|
||||
|
||||
export let feed
|
||||
export let onChange
|
||||
</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}
|
||||
<DVMField {dvmItem} onChange={it => onChange(feed.toSpliced(i + 1, 1, it))} />
|
||||
{/each}
|
||||
</Field>
|
||||
|
@ -2,7 +2,6 @@
|
||||
import {pluck} from "ramda"
|
||||
import {FeedType} from "@welshman/feeds"
|
||||
import {fuzzy} from "src/util/misc"
|
||||
import Field from "src/partials/Field.svelte"
|
||||
import SearchSelect from "src/partials/SearchSelect.svelte"
|
||||
|
||||
export let feed
|
||||
@ -47,8 +46,7 @@
|
||||
}
|
||||
</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}>
|
||||
<div slot="item" let:item>{displayKind(item)}</div>
|
||||
</SearchSelect>
|
||||
</Field>
|
||||
|
@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import {FeedType} from "@welshman/feeds"
|
||||
import Field from "src/partials/Field.svelte"
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import SearchSelect from "src/partials/SearchSelect.svelte"
|
||||
import PersonBadge from "src/app/shared/PersonBadge.svelte"
|
||||
@ -11,7 +10,7 @@
|
||||
export let onChange
|
||||
</script>
|
||||
|
||||
<Field label="Which mentions would you like to see?">
|
||||
<span class="staatliches text-lg">Which mentions would you like to see?</span>
|
||||
<SearchSelect
|
||||
multiple
|
||||
value={feed.slice(2)}
|
||||
@ -27,4 +26,3 @@
|
||||
{/if}
|
||||
</span>
|
||||
</SearchSelect>
|
||||
</Field>
|
||||
|
@ -2,7 +2,6 @@
|
||||
import {toTitle} from "hurdak"
|
||||
import {without} from "ramda"
|
||||
import {FeedType, Scope, isScopeFeed, isAuthorFeed} from "@welshman/feeds"
|
||||
import Field from "src/partials/Field.svelte"
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import SelectButton from "src/partials/SelectButton.svelte"
|
||||
import SearchSelect from "src/partials/SearchSelect.svelte"
|
||||
@ -26,7 +25,7 @@
|
||||
$: scopes = isScopeFeed(feed) ? feed.slice(1) : ["custom"]
|
||||
</script>
|
||||
|
||||
<Field label="Which authors would you like to see?">
|
||||
<span class="staatliches text-lg">Which authors would you like to see?</span>
|
||||
<SelectButton
|
||||
multiple
|
||||
value={scopes}
|
||||
@ -34,6 +33,7 @@
|
||||
options={scopeOptions}
|
||||
onChange={onScopeChange} />
|
||||
{#if isAuthorFeed(feed)}
|
||||
<div class="h-px bg-neutral-900" />
|
||||
<SearchSelect
|
||||
multiple
|
||||
value={feed.slice(1)}
|
||||
@ -50,4 +50,3 @@
|
||||
</span>
|
||||
</SearchSelect>
|
||||
{/if}
|
||||
</Field>
|
||||
|
@ -1,14 +1,13 @@
|
||||
<script lang="ts">
|
||||
import {FeedType} from '@welshman/feeds'
|
||||
import Field from 'src/partials/Field.svelte'
|
||||
import SearchSelect from 'src/partials/SearchSelect.svelte'
|
||||
import {searchRelayUrls, displayRelayUrl} from 'src/engine'
|
||||
import {FeedType} from "@welshman/feeds"
|
||||
import SearchSelect from "src/partials/SearchSelect.svelte"
|
||||
import {searchRelayUrls, displayRelayUrl} from "src/engine"
|
||||
|
||||
export let feed
|
||||
export let onChange
|
||||
</script>
|
||||
|
||||
<Field label="Which relays would you like to use?">
|
||||
<span class="staatliches text-lg">Which relays would you like to use?</span>
|
||||
<SearchSelect
|
||||
multiple
|
||||
value={feed.slice(1)}
|
||||
@ -16,4 +15,3 @@
|
||||
onChange={urls => onChange([FeedType.Relay, ...urls])}>
|
||||
<span slot="item" let:item>{displayRelayUrl(item)}</span>
|
||||
</SearchSelect>
|
||||
</Field>
|
||||
|
@ -1,14 +1,13 @@
|
||||
<script lang="ts">
|
||||
import {FeedType} from '@welshman/feeds'
|
||||
import Field from 'src/partials/Field.svelte'
|
||||
import SearchSelect from 'src/partials/SearchSelect.svelte'
|
||||
import {searchTopicNames} from 'src/engine'
|
||||
import {FeedType} from "@welshman/feeds"
|
||||
import SearchSelect from "src/partials/SearchSelect.svelte"
|
||||
import {searchTopicNames} from "src/engine"
|
||||
|
||||
export let feed
|
||||
export let onChange
|
||||
</script>
|
||||
|
||||
<Field label="Which topics do you want to see?">
|
||||
<span class="staatliches text-lg">Which topics do you want to see?</span>
|
||||
<SearchSelect
|
||||
multiple
|
||||
value={feed.slice(2)}
|
||||
@ -16,4 +15,3 @@
|
||||
onChange={topics => onChange([FeedType.Tag, "#t", ...topics])}>
|
||||
<span slot="item" let:item>#{item}</span>
|
||||
</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">
|
||||
import {getAddress} from "@welshman/util"
|
||||
import {fly} from "src/util/transition"
|
||||
import Subheading from "src/partials/Subheading.svelte"
|
||||
import FlexColumn from "src/partials/FlexColumn.svelte"
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import Card from "src/partials/Card.svelte"
|
||||
import FeedSummary from "src/app/shared/FeedSummary.svelte"
|
||||
import FeedCard from "src/app/shared/FeedCard.svelte"
|
||||
import {router} from "src/app/util/router"
|
||||
import {readFeed} from "src/domain"
|
||||
import {userFeeds} from "src/engine"
|
||||
import {userFeeds, userLists} from "src/engine"
|
||||
|
||||
const createFeed = () => router.at("feeds/create").open()
|
||||
|
||||
@ -22,28 +20,27 @@
|
||||
</div>
|
||||
{#each $userFeeds as event (getAddress(event))}
|
||||
{@const address = getAddress(event)}
|
||||
{@const {name, description, definition} = readFeed(event)}
|
||||
<Card>
|
||||
<FlexColumn>
|
||||
<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>
|
||||
<div in:fly={{y: 20}}>
|
||||
<FeedCard {address}>
|
||||
<div slot="controls">
|
||||
<Anchor on:click={() => editFeed(address)}>
|
||||
<i class="fa fa-edit" /> Edit
|
||||
</Anchor>
|
||||
</div>
|
||||
{#if description}
|
||||
<p>{description}</p>
|
||||
{/if}
|
||||
<FeedSummary feed={definition} />
|
||||
</FlexColumn>
|
||||
</Card>
|
||||
{:else}
|
||||
<p class="text-center py-12">You don't have any feeds yet.</p>
|
||||
</FeedCard>
|
||||
</div>
|
||||
{/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 {Kind, Tags} from "@welshman/util"
|
||||
import {Kind, Tags, decodeAddress} 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 {tryJson} from "src/util/misc"
|
||||
|
||||
@ -17,6 +17,16 @@ export type Feed = {
|
||||
export const normalizeFeedDefinition = 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 => ({
|
||||
name: "",
|
||||
description: "",
|
||||
@ -25,11 +35,15 @@ export const initFeed = (feed: Partial<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 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) => ({
|
||||
@ -50,3 +64,5 @@ export const editFeed = (feed: Feed) => ({
|
||||
.setTag("feed", JSON.stringify(feed.definition))
|
||||
.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 {sleep, pickVals} from "hurdak"
|
||||
import {Worker, derived} from "@welshman/lib"
|
||||
import type {Rumor} from "@welshman/util"
|
||||
import {Worker} from "@welshman/lib"
|
||||
import {createEvent, Kind, Relay, Repository} from "@welshman/util"
|
||||
import {
|
||||
Plex,
|
||||
@ -14,6 +13,7 @@ import {
|
||||
subscribe as baseSubscribe,
|
||||
} 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 {env, pubkey} from "src/engine/session/state"
|
||||
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 {LocalTarget} from "./targets"
|
||||
|
||||
export const repository = new Repository()
|
||||
export const repository = new Repository({throttle: 300})
|
||||
|
||||
export const relay = new Relay(repository)
|
||||
|
||||
@ -33,20 +33,20 @@ export const projections = new Worker<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)
|
||||
}
|
||||
})
|
||||
|
||||
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 feeds = deriveEvents({kinds: [Kind.Feed]})
|
||||
|
||||
export const userFeeds = derived([feeds, pubkey], ([$feeds, $pubkey]) =>
|
||||
$feeds.filter((e: Rumor) => e.pubkey === pubkey.get()),
|
||||
)
|
||||
export const userFeeds = repository.filter(() => [{kinds: [Kind.Feed], authors: [pubkey.get()]}])
|
||||
|
||||
export const getExecutor = (urls: string[]) => {
|
||||
const muxUrl = getSetting("multiplextr_url")
|
||||
@ -170,6 +170,8 @@ export const loadOne = (request: MySubscribeRequest) =>
|
||||
export const publish = (request: PublishRequest) => {
|
||||
const pub = basePublish(request)
|
||||
|
||||
logger.info(`Publishing event`, request)
|
||||
|
||||
// Make sure the event gets into projections asap
|
||||
projections.push(request.event)
|
||||
|
||||
|
@ -115,7 +115,7 @@
|
||||
<div on:click|stopPropagation>
|
||||
<AltColor
|
||||
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">
|
||||
<slot />
|
||||
</div>
|
||||
|
@ -2,7 +2,6 @@
|
||||
import cx from "classnames"
|
||||
import {onMount} from "svelte"
|
||||
import {reject, equals, identity} from "ramda"
|
||||
import Chip from "src/partials/Chip.svelte"
|
||||
import Input from "src/partials/Input.svelte"
|
||||
import Popover2 from "src/partials/Popover2.svelte"
|
||||
import Suggestions from "src/partials/Suggestions.svelte"
|
||||
@ -120,11 +119,14 @@
|
||||
{#if multiple}
|
||||
<div class="text-sm">
|
||||
{#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}>
|
||||
{displayItem(item)}
|
||||
</slot>
|
||||
</Chip>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -4,16 +4,16 @@
|
||||
|
||||
export let displayOption = x => x
|
||||
|
||||
const getClassName = (i, active) =>
|
||||
cx("px-4 py-2 transition-all", {
|
||||
"border-l border-solid border-neutral-100": i > 0,
|
||||
"bg-accent text-white": active,
|
||||
const getClassName = active =>
|
||||
cx("px-4 h-7 transition-all rounded-full mr-2 mb-2 inline-flex items-center", {
|
||||
"bg-tinted-100 text-accent": active,
|
||||
"bg-neutral-900 text-neutral-400": !active,
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="inline-block">
|
||||
<SelectList {...$$props} class="inline-flex overflow-hidden rounded-full border border-solid border-neutral-100">
|
||||
<div slot="item" let:i let:active let:option class={getClassName(i, active)}>
|
||||
<div class="inline-block -mb-2">
|
||||
<SelectList {...$$props} class="inline-flex staatliches">
|
||||
<div slot="item" let:i let:active let:option class={getClassName(active)}>
|
||||
{displayOption(option)}
|
||||
</div>
|
||||
</SelectList>
|
||||
|
@ -10,7 +10,7 @@
|
||||
})
|
||||
</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>
|
||||
<Tile class={getClass(active)}>
|
||||
<slot name="item" {option} {active} />
|
||||
|
@ -6,6 +6,7 @@ const colors = {
|
||||
transparent: "transparent",
|
||||
accent: "var(--accent)",
|
||||
warning: "var(--warning)",
|
||||
danger: "var(--danger)",
|
||||
success: "var(--success)",
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user