mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-18 19:23:40 +00:00
Add buttons to add relays, people, and topics to feeds
This commit is contained in:
parent
51b7de9c29
commit
54553bbbc8
@ -4,10 +4,12 @@
|
||||
- [x] Improve topic suggestions and rendering
|
||||
- [x] Add topic search, keep cache of topics
|
||||
- [x] Ability to create custom feeds
|
||||
- [ ] Bookmark icon opens "create feed" dialog with form pre-filled
|
||||
- [ ] Add ability to follow topics - bookmark icon?
|
||||
- [ ] Claim relays bounty
|
||||
- [x] Bookmark icon opens "create feed" dialog with form pre-filled
|
||||
- [ ] Use lists instead of custom app data
|
||||
- [ ] Public/private toggle
|
||||
- [ ] Add person to feed button (maybe lists make more sense for this?)
|
||||
- [ ] Add 30078 to personKinds (except we'll have to get more involved)
|
||||
- [ ] Claim relays bounty
|
||||
- [ ] Some lnurls aren't working npub1y3k2nheva29y9ej8a22e07epuxrn04rvgy28wvs54y57j7vsxxuq0gvp4j
|
||||
- [ ] Global search modal that searches within current feed
|
||||
- [ ] Fix force relays on login: http://localhost:5173/messages/npub1l66wvfm7dxhd6wmvpukpjpyhvwtlxzu0qqajqxjfpr4rlfa8hl5qlkfr3q
|
||||
|
@ -17,6 +17,8 @@
|
||||
import PersonProfileInfo from "src/app/views/PersonProfileInfo.svelte"
|
||||
import PersonShare from "src/app/views/PersonShare.svelte"
|
||||
import TopicFeed from "src/app/views/TopicFeed.svelte"
|
||||
import FeedList from "src/app/views/FeedList.svelte"
|
||||
import FeedSelect from "src/app/views/FeedSelect.svelte"
|
||||
import FeedEdit from "src/app/views/FeedEdit.svelte"
|
||||
import RelayAdd from "src/app/views/RelayAdd.svelte"
|
||||
|
||||
@ -62,6 +64,10 @@
|
||||
{#key m.topic}
|
||||
<TopicFeed topic={m.topic} />
|
||||
{/key}
|
||||
{:else if m.type === "feed/list"}
|
||||
<FeedList />
|
||||
{:else if m.type === "feed/select"}
|
||||
<FeedSelect key={m.key} value={m.value} />
|
||||
{:else if m.type === "feed/edit"}
|
||||
<FeedEdit feed={m.feed} />
|
||||
{:else if m.type === "message"}
|
||||
|
11
src/app/shared/FeedSummary.svelte
Normal file
11
src/app/shared/FeedSummary.svelte
Normal file
@ -0,0 +1,11 @@
|
||||
<script lang="ts">
|
||||
import {quantify} from "hurdak/lib/hurdak"
|
||||
|
||||
export let feed
|
||||
</script>
|
||||
|
||||
<p>
|
||||
{feed.topics ? quantify(feed.topics.length, "topic") : ""}
|
||||
{feed.authors ? quantify(feed.authors.length, "author") : ""}
|
||||
{feed.relays ? quantify(feed.relays.length, "relay") : ""}
|
||||
</p>
|
@ -2,7 +2,6 @@
|
||||
import {nip19} from "nostr-tools"
|
||||
import {find, last} from "ramda"
|
||||
import {onMount} from "svelte"
|
||||
import {navigate} from "svelte-routing"
|
||||
import {quantify} from "hurdak/lib/hurdak"
|
||||
import {findRootId, findReplyId, displayPerson} from "src/util/nostr"
|
||||
import {formatTimestamp} from "src/util/misc"
|
||||
@ -19,7 +18,7 @@
|
||||
import {getRelaysForEventParent} from "src/agent/relays"
|
||||
import {getPersonWithFallback} from "src/agent/db"
|
||||
import {watch} from "src/agent/db"
|
||||
import {routes} from "src/app/state"
|
||||
import {routes, goToPerson} from "src/app/state"
|
||||
import NoteContent from "src/app/shared/NoteContent.svelte"
|
||||
|
||||
export let note
|
||||
@ -64,14 +63,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
const goToAuthor = () => {
|
||||
if (document.querySelector(".modal-content")) {
|
||||
navigate(routes.person(note.pubkey))
|
||||
} else {
|
||||
modal.push({type: "person/feed", pubkey: note.pubkey})
|
||||
}
|
||||
}
|
||||
|
||||
const goToParent = async () => {
|
||||
const relays = getRelaysForEventParent(note)
|
||||
|
||||
@ -135,7 +126,7 @@
|
||||
<Anchor
|
||||
type="unstyled"
|
||||
class="flex items-center gap-2 pr-16 text-lg font-bold"
|
||||
on:click={goToAuthor}>
|
||||
on:click={() => goToPerson($author.pubkey)}>
|
||||
<span>{displayPerson($author)}</span>
|
||||
{#if $author.verified_as}
|
||||
<i class="fa fa-circle-check text-sm text-accent" />
|
||||
@ -147,7 +138,7 @@
|
||||
<Anchor
|
||||
type="unstyled"
|
||||
class="flex items-center gap-2 pr-16 text-lg font-bold"
|
||||
on:click={goToAuthor}>
|
||||
on:click={() => goToPerson($author.pubkey)}>
|
||||
<span>{displayPerson($author)}</span>
|
||||
{#if $author.verified_as}
|
||||
<i class="fa fa-circle-check text-sm text-accent" />
|
||||
|
@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import {objOf, reverse} from "ramda"
|
||||
import {navigate} from "svelte-routing"
|
||||
import {fly} from "svelte/transition"
|
||||
import {splice} from "hurdak/lib/hurdak"
|
||||
import {warn} from "src/util/logger"
|
||||
@ -15,7 +14,7 @@
|
||||
import user from "src/agent/user"
|
||||
import network from "src/agent/network"
|
||||
import {getPersonWithFallback} from "src/agent/db"
|
||||
import {routes} from "src/app/state"
|
||||
import {goToPerson} from "src/app/state"
|
||||
|
||||
export let note
|
||||
export let anchorId = null
|
||||
@ -123,18 +122,18 @@
|
||||
<br />
|
||||
{/each}
|
||||
{:else if type === "topic"}
|
||||
<Anchor on:click={e => { e.stopPropagation(); openTopic(value) }}>#{value}</Anchor>
|
||||
<Anchor stopPropagation on:click={() => openTopic(value)}>#{value}</Anchor>
|
||||
{:else if type === "link"}
|
||||
<Anchor external href={value}>
|
||||
{value.replace(/https?:\/\/(www\.)?/, "")}
|
||||
</Anchor>
|
||||
{:else if type.startsWith("nostr:")}
|
||||
{#if value.pubkey}
|
||||
@<Anchor href={"/" + value.entity}>
|
||||
@<Anchor stopPropagation on:click={() => goToPerson(value.pubkey)}>
|
||||
{displayPerson(getPersonWithFallback(value.pubkey))}
|
||||
</Anchor>
|
||||
{:else}
|
||||
<Anchor href={"/" + value.entity}>
|
||||
<Anchor stopPropagation href={"/" + value.entity}>
|
||||
{value.entity.slice(0, 16) + "..."}
|
||||
</Anchor>
|
||||
{/if}
|
||||
@ -145,7 +144,7 @@
|
||||
{/each}
|
||||
</p>
|
||||
{#if showMedia && links.length > 0}
|
||||
<div on:click={e => e.stopPropagation()}>
|
||||
<div on:click|stopPropagation>
|
||||
<MediaSet {links} />
|
||||
</div>
|
||||
{/if}
|
||||
@ -160,9 +159,10 @@
|
||||
<div class="mb-4 flex items-center gap-4">
|
||||
<PersonCircle size={6} {person} />
|
||||
<Anchor
|
||||
stopPropagation
|
||||
type="unstyled"
|
||||
class="flex items-center gap-2"
|
||||
on:click={() => navigate(routes.person(quote.pubkey))}>
|
||||
on:click={() => goToPerson(quote.pubkey)}>
|
||||
<h2 class="text-lg">{displayPerson(person)}</h2>
|
||||
</Anchor>
|
||||
</div>
|
||||
|
@ -9,6 +9,7 @@
|
||||
import {getPubkeyWriteRelays} from "src/agent/relays"
|
||||
import user from "src/agent/user"
|
||||
import pool from "src/agent/pool"
|
||||
import {addToFeed} from "src/app/state"
|
||||
|
||||
export let person
|
||||
|
||||
@ -22,6 +23,12 @@
|
||||
$: {
|
||||
actions = []
|
||||
|
||||
actions.push({
|
||||
onClick: () => addToFeed("authors", person.pubkey),
|
||||
label: "Add to feed",
|
||||
icon: "scroll",
|
||||
})
|
||||
|
||||
actions.push({onClick: share, label: "Share", icon: "share-nodes"})
|
||||
|
||||
if (user.getPubkey() !== person.pubkey && $canPublish) {
|
||||
|
@ -3,6 +3,7 @@
|
||||
import OverflowMenu from "src/partials/OverflowMenu.svelte"
|
||||
import user from "src/agent/user"
|
||||
import {getRelayWithFallback} from "src/agent/db"
|
||||
import {addToFeed} from "src/app/state"
|
||||
|
||||
export let relay
|
||||
|
||||
@ -30,6 +31,12 @@
|
||||
})
|
||||
}
|
||||
|
||||
actions.push({
|
||||
onClick: () => addToFeed("relays", relay.url),
|
||||
label: "Add to feed",
|
||||
icon: "scroll",
|
||||
})
|
||||
|
||||
if (relay.contact) {
|
||||
actions.push({
|
||||
onClick: () => window.open("mailto:" + last(relay.contact.split(":"))),
|
||||
|
18
src/app/shared/TopicActions.svelte
Normal file
18
src/app/shared/TopicActions.svelte
Normal file
@ -0,0 +1,18 @@
|
||||
<script lang="ts">
|
||||
import OverflowMenu from "src/partials/OverflowMenu.svelte"
|
||||
import {addToFeed} from "src/app/state"
|
||||
|
||||
export let topic
|
||||
|
||||
let actions = []
|
||||
|
||||
$: {
|
||||
actions.push({
|
||||
onClick: () => addToFeed("topics", topic),
|
||||
label: "Add to feed",
|
||||
icon: "scroll",
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<OverflowMenu {actions} />
|
@ -1,6 +1,7 @@
|
||||
import type {DisplayEvent} from "src/util/types"
|
||||
import Bugsnag from "@bugsnag/js"
|
||||
import {nip19} from "nostr-tools"
|
||||
import {navigate} from "svelte-routing"
|
||||
import {derived} from "svelte/store"
|
||||
import {writable} from "svelte/store"
|
||||
import {omit, pluck, sortBy, max, find, slice, propEq} from "ramda"
|
||||
@ -25,6 +26,16 @@ export const routes = {
|
||||
person: (pubkey, tab = "notes") => `/people/${nip19.npubEncode(pubkey)}/${tab}`,
|
||||
}
|
||||
|
||||
export const goToPerson = pubkey => {
|
||||
if (document.querySelector(".modal-content")) {
|
||||
navigate(routes.person(pubkey))
|
||||
} else {
|
||||
modal.push({type: "person/feed", pubkey})
|
||||
}
|
||||
}
|
||||
|
||||
export const addToFeed = (key, value) => modal.push({type: "feed/select", key, value})
|
||||
|
||||
// Menu
|
||||
|
||||
export const menuIsOpen = writable(false)
|
||||
|
@ -1,13 +1,13 @@
|
||||
<script>
|
||||
import {when, find, objOf, pluck, always, propEq} from "ramda"
|
||||
import {when, find, pluck, always, propEq} from "ramda"
|
||||
import {randomId} from "hurdak/lib/hurdak"
|
||||
import {displayPerson} from "src/util/nostr"
|
||||
import {modal, toast} from "src/partials/state"
|
||||
import Heading from "src/partials/Heading.svelte"
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import Button from "src/partials/Button.svelte"
|
||||
import Input from "src/partials/Input.svelte"
|
||||
import MultiSelect from "src/partials/MultiSelect.svelte"
|
||||
import PersonBadge from "src/app/shared/PersonBadge.svelte"
|
||||
import {searchTopics, searchPeople, searchRelays, getPersonWithFallback} from "src/agent/db"
|
||||
import user from "src/agent/user"
|
||||
|
||||
@ -16,13 +16,27 @@
|
||||
let values = {
|
||||
id: feed.id,
|
||||
name: feed.name,
|
||||
authors: (feed.authors || []).map(objOf("pubkey")),
|
||||
topics: (feed.topics || []).map(objOf("name")),
|
||||
relays: (feed.relays || []).map(objOf("url")),
|
||||
params: (feed.authors || [])
|
||||
.map(pubkey => ({pubkey}))
|
||||
.concat((feed.topics || []).map(name => ({name})))
|
||||
.concat((feed.relays || []).map(url => ({url}))),
|
||||
}
|
||||
|
||||
const {feeds} = user
|
||||
|
||||
const search = q => {
|
||||
if (q.startsWith("~")) {
|
||||
console.log($searchRelays(q))
|
||||
return $searchRelays(q)
|
||||
}
|
||||
|
||||
if (q.startsWith("#")) {
|
||||
return $searchTopics(q)
|
||||
}
|
||||
|
||||
return $searchPeople(q)
|
||||
}
|
||||
|
||||
const submit = () => {
|
||||
if (!values.name) {
|
||||
return toast.show("error", "A name is required for your feed")
|
||||
@ -55,56 +69,28 @@
|
||||
<Heading class="text-center">{values.id ? "Edit" : "Add"} custom feed</Heading>
|
||||
<div class="flex w-full flex-col gap-8">
|
||||
<div class="flex flex-col gap-1">
|
||||
<strong>Feed name</strong>
|
||||
<strong>Name</strong>
|
||||
<Input bind:value={values.name} placeholder="My custom feed" />
|
||||
<p class="text-sm text-gray-4">
|
||||
Custom feeds are identified by their name, so this has to be unique.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<strong>Limit by author</strong>
|
||||
<MultiSelect
|
||||
search={$searchPeople}
|
||||
bind:value={values.authors}
|
||||
placeholder="A list of authors">
|
||||
<strong>Filters</strong>
|
||||
<MultiSelect {search} bind:value={values.params}>
|
||||
<div slot="item" let:item>
|
||||
<PersonBadge inert person={getPersonWithFallback(item.pubkey)} />
|
||||
{#if item.pubkey}
|
||||
{displayPerson(getPersonWithFallback(item.pubkey))}
|
||||
{:else if item.name}
|
||||
#{item.name}
|
||||
{:else if item.url}
|
||||
{item.url}
|
||||
{/if}
|
||||
</div>
|
||||
</MultiSelect>
|
||||
<p class="text-sm text-gray-4">
|
||||
Only notes by the given authors will be shown in the feed.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<strong>Limit by topic</strong>
|
||||
<MultiSelect
|
||||
search={$searchTopics}
|
||||
bind:value={values.topics}
|
||||
delimiters={[",", " "]}
|
||||
termToItem={objOf("name")}
|
||||
placeholder="A list of topics">
|
||||
<div slot="item" let:item>
|
||||
#{item.name}
|
||||
</div>
|
||||
</MultiSelect>
|
||||
<p class="text-sm text-gray-4">
|
||||
Only notes with the given topics will be shown in the feed.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<strong>Limit by relay</strong>
|
||||
<MultiSelect
|
||||
search={$searchRelays}
|
||||
bind:value={values.relays}
|
||||
delimiters={[",", " "]}
|
||||
termToItem={objOf("url")}
|
||||
placeholder="A list of relays">
|
||||
<div slot="item" let:item>
|
||||
{item.url}
|
||||
</div>
|
||||
</MultiSelect>
|
||||
<p class="text-sm text-gray-4">
|
||||
Only notes found on the given relays will be shown in the feed.
|
||||
Type "@" to look for people, "#" to look for topics, and "~" to look for relays. Custom
|
||||
feeds will search for notes that match any item within each filter.
|
||||
</p>
|
||||
</div>
|
||||
<Button type="submit" class="text-center">Save</Button>
|
||||
|
52
src/app/views/FeedList.svelte
Normal file
52
src/app/views/FeedList.svelte
Normal file
@ -0,0 +1,52 @@
|
||||
<script type="ts">
|
||||
import {modal} from "src/partials/state"
|
||||
import Heading from "src/partials/Heading.svelte"
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import FeedSummary from 'src/app/shared/FeedSummary.svelte'
|
||||
import user from "src/agent/user"
|
||||
|
||||
const {feeds} = user
|
||||
|
||||
const createFeed = () => {
|
||||
modal.push({type: "feed/edit"})
|
||||
}
|
||||
|
||||
const removeFeed = feed => {
|
||||
user.removeFeed(feed.id)
|
||||
}
|
||||
|
||||
const editFeed = feed => {
|
||||
modal.push({type: "feed/edit", feed})
|
||||
}
|
||||
</script>
|
||||
|
||||
<Content>
|
||||
<div class="flex items-center justify-between">
|
||||
<Heading>Custom Feeds</Heading>
|
||||
<Anchor type="button-accent" on:click={createFeed}>
|
||||
<i class="fa fa-plus" /> Feed
|
||||
</Anchor>
|
||||
</div>
|
||||
<p>
|
||||
You custom feeds are listed below. You can create new custom feeds by handing using the "add
|
||||
feed" button, or by clicking the <i class="fa fa-scroll px-1" /> icon that appears throughout
|
||||
Coracle.
|
||||
</p>
|
||||
{#each $feeds as feed (feed.name)}
|
||||
<div class="flex justify-start gap-3">
|
||||
<i
|
||||
class="fa fa-sm fa-trash cursor-pointer py-3"
|
||||
on:click|stopPropagation={() => removeFeed(feed)} />
|
||||
<div class="flex w-full justify-between">
|
||||
<div>
|
||||
<strong>{feed.name}</strong>
|
||||
<FeedSummary {feed} />
|
||||
</div>
|
||||
<Anchor on:click={() => editFeed(feed)}>Edit</Anchor>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<p class="text-center py-12">You don't have any custom feeds yet.</p>
|
||||
{/each}
|
||||
</Content>
|
45
src/app/views/FeedSelect.svelte
Normal file
45
src/app/views/FeedSelect.svelte
Normal file
@ -0,0 +1,45 @@
|
||||
<script type="ts">
|
||||
import {uniq} from 'ramda'
|
||||
import {modal} from "src/partials/state"
|
||||
import Heading from "src/partials/Heading.svelte"
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import BorderLeft from "src/partials/BorderLeft.svelte"
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import FeedSummary from 'src/app/shared/FeedSummary.svelte'
|
||||
import user from "src/agent/user"
|
||||
|
||||
export let key
|
||||
export let value
|
||||
|
||||
const label = key.slice(0, -1)
|
||||
const {feeds} = user
|
||||
|
||||
const modifyFeed = feed => {
|
||||
return {...feed, [key]: uniq((feed[key] || []).concat(value))}
|
||||
}
|
||||
|
||||
const selectFeed = feed => {
|
||||
modal.pop()
|
||||
modal.push({type: "feed/edit", feed: modifyFeed(feed)})
|
||||
}
|
||||
</script>
|
||||
|
||||
<Content size="lg">
|
||||
<div class="flex items-center justify-between">
|
||||
<Heading>Select a Feed</Heading>
|
||||
<Anchor type="button-accent" on:click={() => selectFeed({})}>
|
||||
<i class="fa fa-plus" /> Feed
|
||||
</Anchor>
|
||||
</div>
|
||||
<p>
|
||||
Select a feed to modify. The selected {label} will be added to it as an additional filter.
|
||||
</p>
|
||||
{#each $feeds as feed (feed.name)}
|
||||
<BorderLeft on:click={() => selectFeed(feed)}>
|
||||
<strong>{feed.name}</strong>
|
||||
<FeedSummary {feed} />
|
||||
</BorderLeft>
|
||||
{:else}
|
||||
<p class="text-center py-12">You don't have any custom feeds yet.</p>
|
||||
{/each}
|
||||
</Content>
|
@ -1,13 +1,10 @@
|
||||
<script lang="ts">
|
||||
import type {CustomFeed} from "src/util/types"
|
||||
import {prop, objOf, find, propEq} from "ramda"
|
||||
import {quantify} from "hurdak/lib/hurdak"
|
||||
import {shuffle, synced} from "src/util/misc"
|
||||
import {modal} from "src/partials/state"
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import Modal from "src/partials/Modal.svelte"
|
||||
import Heading from "src/partials/Heading.svelte"
|
||||
import Tabs from "src/partials/Tabs.svelte"
|
||||
import Popover from "src/partials/Popover.svelte"
|
||||
import Feed from "src/app/shared/Feed.svelte"
|
||||
@ -21,9 +18,7 @@
|
||||
{id: "network", name: "Network", authors: "network"},
|
||||
] as Array<CustomFeed>
|
||||
|
||||
let modalIsOpen = false
|
||||
let activeTab = synced("views/Feeds/activeTab", "Follows")
|
||||
let actions = []
|
||||
let relays, filter, tabs, feed
|
||||
|
||||
$: allFeeds = defaultFeeds.concat($feeds)
|
||||
@ -79,34 +74,12 @@
|
||||
})
|
||||
}
|
||||
|
||||
$: {
|
||||
actions = []
|
||||
|
||||
actions.push({
|
||||
onClick: toggleModal,
|
||||
label: "Customize",
|
||||
icon: "cog",
|
||||
})
|
||||
}
|
||||
|
||||
const setActiveTab = tab => {
|
||||
$activeTab = tab
|
||||
}
|
||||
|
||||
const toggleModal = () => {
|
||||
modalIsOpen = !modalIsOpen
|
||||
}
|
||||
|
||||
const createFeed = () => {
|
||||
modal.push({type: "feed/edit"})
|
||||
}
|
||||
|
||||
const editFeed = feed => {
|
||||
modal.push({type: "feed/edit", feed})
|
||||
}
|
||||
|
||||
const removeFeed = feed => {
|
||||
user.removeFeed(feed.id)
|
||||
const showFeedsList = () => {
|
||||
modal.push({type: "feed/list"})
|
||||
}
|
||||
|
||||
document.title = $activeTab
|
||||
@ -139,13 +112,13 @@
|
||||
{feed.name}
|
||||
</button>
|
||||
{/each}
|
||||
<button on:click={toggleModal} class="w-full py-2 px-3 text-left hover:bg-gray-7">
|
||||
<button on:click={showFeedsList} class="w-full py-2 px-3 text-left hover:bg-gray-7">
|
||||
<i class="fa fa-cog fa-sm mr-1" /> Customize
|
||||
</button>
|
||||
</div>
|
||||
</Popover>
|
||||
{:else}
|
||||
<i class="fa fa-ellipsis-v cursor-pointer p-1" on:click={toggleModal} />
|
||||
<i class="fa fa-ellipsis-v cursor-pointer p-1" on:click={showFeedsList} />
|
||||
{/if}
|
||||
</Tabs>
|
||||
{#key $activeTab}
|
||||
@ -153,41 +126,3 @@
|
||||
{/key}
|
||||
</div>
|
||||
</Content>
|
||||
|
||||
{#if modalIsOpen}
|
||||
<Modal onEscape={toggleModal}>
|
||||
<Content>
|
||||
<div class="flex items-center justify-between">
|
||||
<Heading>Custom Feeds</Heading>
|
||||
<Anchor type="button-accent" on:click={createFeed}>
|
||||
<i class="fa fa-plus" /> Feed
|
||||
</Anchor>
|
||||
</div>
|
||||
<p>
|
||||
You custom feeds are listed below. You can create new custom feeds by handing using the "add
|
||||
feed" button, or by clicking the <i class="fa fa-scroll px-1" /> icon that appears throughout
|
||||
Coracle.
|
||||
</p>
|
||||
{#each $feeds as feed (feed.name)}
|
||||
<div class="flex justify-start gap-3">
|
||||
<i
|
||||
class="fa fa-sm fa-trash cursor-pointer py-3"
|
||||
on:click|stopPropagation={() => removeFeed(feed)} />
|
||||
<div class="flex w-full justify-between">
|
||||
<div>
|
||||
<strong>{feed.name}</strong>
|
||||
<p>
|
||||
{feed.topics ? quantify(feed.topics.length, "topic") : ""}
|
||||
{feed.authors ? quantify(feed.authors.length, "author") : ""}
|
||||
{feed.relays ? quantify(feed.relays.length, "relay") : ""}
|
||||
</p>
|
||||
</div>
|
||||
<Anchor on:click={() => editFeed(feed)}>Edit</Anchor>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<p class="text-center py-12">You don't have any custom feeds yet.</p>
|
||||
{/each}
|
||||
</Content>
|
||||
</Modal>
|
||||
{/if}
|
||||
|
@ -5,7 +5,7 @@
|
||||
import Input from "src/partials/Input.svelte"
|
||||
import Heading from "src/partials/Heading.svelte"
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import BorderLeft from "src/partials/BorderLeft.svelte"
|
||||
import PersonInfo from "src/app/shared/PersonInfo.svelte"
|
||||
import {watch} from "src/agent/db"
|
||||
import user from "src/agent/user"
|
||||
@ -48,13 +48,9 @@
|
||||
</Input>
|
||||
{#each $search(q).slice(0, 50) as result (result.type + result.id)}
|
||||
{#if result.type === "topic"}
|
||||
<Anchor
|
||||
type="unstyled"
|
||||
class="flex gap-4 border-l-2 border-solid border-gray-7 py-2 px-4 transition-all
|
||||
hover:border-accent hover:bg-gray-8"
|
||||
on:click={() => openTopic(result.topic.name)}>
|
||||
<BorderLeft on:click={() => openTopic(result.topic.name)}>
|
||||
#{result.topic.name}
|
||||
</Anchor>
|
||||
</BorderLeft>
|
||||
{:else if result.type === "person"}
|
||||
<PersonInfo person={result.person} />
|
||||
{/if}
|
||||
|
@ -2,14 +2,21 @@
|
||||
import Feed from "src/app/shared/Feed.svelte"
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import Heading from "src/partials/Heading.svelte"
|
||||
import TopicActions from "src/app/shared/TopicActions.svelte"
|
||||
import {sampleRelays, getUserReadRelays} from "src/agent/relays"
|
||||
|
||||
export let topic
|
||||
|
||||
const relays = sampleRelays(getUserReadRelays())
|
||||
const filter = [{kinds: [1], "#t": [topic]}]
|
||||
</script>
|
||||
|
||||
<Content>
|
||||
<Heading class="text-center">#{topic}</Heading>
|
||||
<div class="flex justify-between gap-2">
|
||||
<Heading>#{topic}</Heading>
|
||||
<div class="pt-5">
|
||||
<TopicActions {topic} />
|
||||
</div>
|
||||
</div>
|
||||
<Feed {relays} {filter} />
|
||||
</Content>
|
||||
|
@ -1,12 +1,16 @@
|
||||
<script>
|
||||
import cx from "classnames"
|
||||
import {switcher} from "hurdak/lib/hurdak"
|
||||
import {createEventDispatcher} from "svelte"
|
||||
|
||||
export let stopPropagation = false
|
||||
export let external = false
|
||||
export let loading = false
|
||||
export let type = "anchor"
|
||||
export let href = null
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let className
|
||||
|
||||
$: className = cx(
|
||||
@ -25,8 +29,16 @@
|
||||
"py-2 px-4 rounded bg-accent text-white whitespace-nowrap border border-solid border-accent-light hover:bg-accent-light",
|
||||
})
|
||||
)
|
||||
|
||||
const onClick = e => {
|
||||
if (stopPropagation) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
dispatch("click", e)
|
||||
}
|
||||
</script>
|
||||
|
||||
<a on:click {...$$props} {href} class={className} target={external ? "_blank" : null}>
|
||||
<a on:click={onClick} {...$$props} {href} class={className} target={external ? "_blank" : null}>
|
||||
<slot />
|
||||
</a>
|
||||
|
11
src/partials/BorderLeft.svelte
Normal file
11
src/partials/BorderLeft.svelte
Normal file
@ -0,0 +1,11 @@
|
||||
<script lang="ts">
|
||||
import Anchor from 'src/partials/Anchor.svelte'
|
||||
</script>
|
||||
|
||||
<Anchor
|
||||
type="unstyled"
|
||||
class="flex gap-4 border-l-2 border-solid border-gray-6 py-2 px-4 transition-all
|
||||
hover:border-accent hover:bg-gray-8"
|
||||
on:click>
|
||||
<slot />
|
||||
</Anchor>
|
@ -61,25 +61,25 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="shadow-inset cursor-text rounded border border-solid border-gray-3 bg-input py-2 px-4 text-black"
|
||||
on:click={() => input.focus()}>
|
||||
<div class="text-sm">
|
||||
{#each value as item}
|
||||
<Chip class="mr-1 mb-1" theme="light" on:click={() => remove(item)}>
|
||||
<Chip class="mr-1 mb-1" theme="dark" on:click={() => remove(item)}>
|
||||
<slot name="item" {item}>
|
||||
{item}
|
||||
</slot>
|
||||
</Chip>
|
||||
{/each}
|
||||
<input
|
||||
type="text"
|
||||
class="w-full bg-input py-2 outline-0 placeholder:text-gray-5"
|
||||
{placeholder}
|
||||
bind:value={term}
|
||||
bind:this={input}
|
||||
on:keydown={onKeyDown} />
|
||||
</div>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
class="shadow-inset w-full cursor-text rounded border border-solid border-gray-3 bg-input bg-input py-2
|
||||
py-2 px-4 text-black outline-0 placeholder:text-gray-5"
|
||||
{placeholder}
|
||||
bind:value={term}
|
||||
bind:this={input}
|
||||
on:keydown={onKeyDown} />
|
||||
|
||||
{#if search}
|
||||
<div class="relative w-full">
|
||||
<div class="absolute w-full">
|
||||
|
@ -31,7 +31,7 @@
|
||||
|
||||
{#each actions as { label, icon, onClick }}
|
||||
<div class="relative z-10 cursor-pointer" on:click={onClick}>
|
||||
<span class="absolute right-0 mr-12 mt-2">{label}</span>
|
||||
<span class="absolute right-0 mr-12 mt-2 whitespace-nowrap">{label}</span>
|
||||
<Anchor type="button-circle"><i class={`fa fa-${icon}`} /></Anchor>
|
||||
</div>
|
||||
{/each}
|
||||
|
@ -6,7 +6,7 @@ import {tryJson} from "src/util/misc"
|
||||
import {invoiceAmount} from "src/util/lightning"
|
||||
|
||||
export const personKinds = [0, 2, 3, 10001, 10002]
|
||||
export const userKinds = personKinds.concat([10000])
|
||||
export const userKinds = personKinds.concat([10000, 30001])
|
||||
|
||||
export class Tags {
|
||||
tags: Array<any>
|
||||
@ -192,7 +192,8 @@ export const parseContent = ({content, tags = []}) => {
|
||||
const parseTopic = () => {
|
||||
const topic = first(text.match(/^#\w+/i))
|
||||
|
||||
if (topic) {
|
||||
// Skip numeric topics
|
||||
if (topic && !topic.match(/^#\d+$/)) {
|
||||
return ["topic", topic, topic.slice(1)]
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user