Compare commits

...

2 Commits

Author SHA1 Message Date
Jon Staab
b92b0b71b5 Add global feeds 2024-06-19 14:52:29 -07:00
Jon Staab
c6cdcfb2f8 Fix some note rendering bugs, throw error when dufflepud is not configured 2024-06-19 11:16:30 -07:00
19 changed files with 178 additions and 157 deletions

View File

@ -10,6 +10,7 @@
- [x] Fix several community and calendar related bugs
- [x] Add reports using tagr-bot
- [x] Open links to coracle in same tab
- [x] Add global feeds
# 0.4.6

BIN
package-lock.json generated

Binary file not shown.

View File

@ -56,7 +56,7 @@
"@getalby/bitcoin-connect": "^3.2.2",
"@scure/base": "^1.1.6",
"@welshman/content": "^0.0.5",
"@welshman/feeds": "^0.0.11",
"@welshman/feeds": "^0.0.12",
"@welshman/lib": "^0.0.9",
"@welshman/net": "^0.0.13",
"@welshman/util": "^0.0.14",

View File

@ -89,6 +89,8 @@
let search = getSearch(feed.definition)
$: subFeeds = getFeedArgs(feed.definition as any)
$: console.log(feed)
</script>
<div class="flex flex-grow items-center justify-end gap-2">

View File

@ -11,15 +11,18 @@
isRelayFeed,
isListFeed,
isDVMFeed,
isGlobalFeed,
makeListFeed,
makeDVMFeed,
makeScopeFeed,
makeTagFeed,
makeRelayFeed,
makeGlobalFeed,
Scope,
} from "@welshman/feeds"
import {toSpliced} from "src/util/misc"
import Icon from "src/partials/Icon.svelte"
import SelectButton from "src/partials/SelectButton.svelte"
import SelectTiles from "src/partials/SelectTiles.svelte"
import Card from "src/partials/Card.svelte"
import FlexColumn from "src/partials/FlexColumn.svelte"
@ -29,6 +32,7 @@
export let feed
enum FormType {
Global = "global",
Advanced = "advanced",
DVMs = "dvms",
Lists = "lists",
@ -60,25 +64,12 @@
const inferFormType = feed => {
for (const subFeed of getFeedArgs(normalize(feed))) {
if ([FeedType.Scope, FeedType.Author].includes(subFeed[0])) {
return FormType.People
}
if (subFeed[0] === FeedType.Tag && subFeed[1] === "#t") {
return FormType.Topics
}
if (subFeed[0] === FeedType.Relay) {
return FormType.Relays
}
if (subFeed[0] === FeedType.List) {
return FormType.Lists
}
if (subFeed[0] === FeedType.DVM) {
return FormType.DVMs
}
if (isGlobalFeed(subFeed)) return FormType.Global
if (isScopeFeed(subFeed) || isAuthorFeed(subFeed)) return FormType.People
if (isTagFeed(subFeed) && subFeed[1] === "#t") return FormType.Topics
if (isRelayFeed(subFeed)) return FormType.Relays
if (isListFeed(subFeed)) return FormType.Lists
if (isDVMFeed(subFeed)) return FormType.DVMs
}
return FormType.Advanced
@ -96,7 +87,9 @@
// Remove filters directly related to the previous type
if (newFormType !== FormType.Advanced) {
if (formType === FormType.People) {
if (formType === FormType.Global) {
removeSubFeed(isGlobalFeed)
} else if (formType === FormType.People) {
removeSubFeed(isPeopleFeed)
} else if (formType === FormType.Topics) {
removeSubFeed(isTopicsFeed)
@ -112,7 +105,9 @@
formType = newFormType
// Add a default filter depending on the new form type
if (formType === FormType.People) {
if (formType === FormType.Global) {
prependDefaultSubFeed(isGlobalFeed, makeGlobalFeed())
} else if (formType === FormType.People) {
prependDefaultSubFeed(isPeopleFeed, makeScopeFeed(Scope.Follows))
} else if (formType === FormType.Topics) {
prependDefaultSubFeed(isTopicsFeed, makeTagFeed("#t"))
@ -132,25 +127,55 @@
let formType = inferFormType(feed)
$: formTypeOptions = [
FormType.Global,
FormType.People,
FormType.Topics,
FormType.Relays,
FormType.Lists,
FormType.DVMs,
FormType.Advanced,
]
</script>
<FlexColumn>
<Card class="-mb-8">
<Card>
<FlexColumn small>
<span class="staatliches text-lg">Choose a feed type</span>
<SelectButton
class="sm:hidden"
options={formTypeOptions}
onChange={onFormTypeChange}
value={formType}>
<div slot="item" class="flex items-center gap-2" let:option let:active>
{#if option === FormType.Global}
<i class="fa fa-earth-europe" /> Global
{:else if option === FormType.People}
<i class="fa fa-person" /> People
{:else if option === FormType.Topics}
<i class="fa fa-tags" /> Topics
{:else if option === FormType.Relays}
<i class="fa fa-server" /> Relays
{:else if option === FormType.Lists}
<i class="fa fa-bars-staggered" /> Lists
{:else if option === FormType.DVMs}
<i class="fa fa-circle-nodes" /> DVMs
{:else if option === FormType.Advanced}
<i class="fa fa-cogs" /> Advanced
{/if}
</div>
</SelectButton>
<SelectTiles
class="grid-cols-2 xs:grid-cols-3 md:grid-cols-5"
class="hidden grid-cols-4 sm:grid"
options={formTypeOptions}
onChange={onFormTypeChange}
value={formType}>
<div slot="item" class="flex flex-col items-center" let:option let:active>
{#if option === FormType.People}
{#if option === FormType.Global}
<span class="flex h-12 w-12 items-center justify-center" class:text-accent={active}>
<i class="fa fa-2xl fa-earth-europe" />
</span>
<span class="staatliches text-2xl">Global</span>
{:else if option === FormType.People}
<Icon icon="people-nearby" class="h-12 w-12" color={active ? "accent" : "tinted-800"} />
<span class="staatliches text-2xl">People</span>
{:else if option === FormType.Topics}
@ -169,17 +194,25 @@
{: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>
<div
class="flex cursor-pointer items-center justify-end gap-2 text-neutral-500"
on:click={() => onFormTypeChange(FormType.Advanced)}>
<span class="staatliches underline">Advanced Mode</span>
</div>
</FlexColumn>
</Card>
<FlexColumn>
{#if formType === FormType.Global && feed.length === 2}
<Card class="flex gap-4 items-center">
<i class="fa fa-triangle-exclamation text-warning fa-xl" />
<p>
Be aware that feeds with no filters can result in obscene or otherwise objectionable content being displayed.
</p>
</Card>
{/if}
{#if formType === FormType.Advanced}
<FeedFormAdvanced {feed} onChange={onFeedChange} />
{:else}

View File

@ -1,8 +1,8 @@
<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"
import FlexColumn from "src/partials/FlexColumn.svelte"
export let feed
export let onChange
@ -35,18 +35,19 @@
</script>
<Card>
<Field label="Enter your custom feed below">
<FlexColumn>
<span class="staatliches text-lg">Enter your custom feed below</span>
<Textarea
class="h-72 whitespace-pre-wrap"
value={json}
on:input={onInput}
on:focus={onFocus}
on:blur={onBlur} />
</Field>
{#if !isValid && !isFocused}
<p>
<i class="fa fa-triangle-exclamation" />
Your feed is currently invalid. Please double check that it is valid JSON.
</p>
{/if}
{#if !isValid && !isFocused}
<p>
<i class="fa fa-triangle-exclamation" />
Your feed is currently invalid. Please double check that it is valid JSON.
</p>
{/if}
</FlexColumn>
</Card>

View File

@ -2,6 +2,7 @@
import {toTitle} from "hurdak"
import {
getFeedArgs,
isGlobalFeed,
isCreatedAtFeed,
isAuthorFeed,
isKindFeed,
@ -68,41 +69,48 @@
{#each subFeeds as subFeed, i}
{@const idx = i + 1}
{@const change = f => onSubFeedChange(idx, f)}
<Card class="relative">
<FlexColumn>
<FlexColumn small>
{#if isPeopleFeed(subFeed)}
<FeedFormSectionPeople feed={subFeed} onChange={change} />
{:else if isRelayFeed(subFeed)}
<FeedFormSectionRelays feed={subFeed} onChange={change} />
{:else if isTopicFeed(subFeed)}
<FeedFormSectionTopics feed={subFeed} onChange={change} />
{:else if isMentionFeed(subFeed)}
<FeedFormSectionMentions feed={subFeed} onChange={change} />
{:else if isKindFeed(subFeed)}
<FeedFormSectionKinds feed={subFeed} onChange={change} />
{:else if isCreatedAtFeed(subFeed)}
<FeedFormSectionCreatedAt feed={subFeed} onChange={change} />
{:else if isListFeed(subFeed)}
<FeedFormSectionList feed={subFeed} onChange={change} />
{:else if isDVMFeed(subFeed)}
<FeedFormSectionDVM feed={subFeed} onChange={change} />
{:else}
No support for editing {toTitle(subFeed[0])} filters. Click "Advanced" to edit manually.
{@const canSave =
isAuthorFeed(subFeed) ||
isRelayFeed(subFeed) ||
isTopicFeed(subFeed) ||
isMentionFeed(subFeed)}
{#if canSave || !isGlobalFeed(subFeed)}
<Card class="relative">
<FlexColumn>
<FlexColumn small>
{#if isPeopleFeed(subFeed)}
<FeedFormSectionPeople feed={subFeed} onChange={change} />
{:else if isRelayFeed(subFeed)}
<FeedFormSectionRelays feed={subFeed} onChange={change} />
{:else if isTopicFeed(subFeed)}
<FeedFormSectionTopics feed={subFeed} onChange={change} />
{:else if isMentionFeed(subFeed)}
<FeedFormSectionMentions feed={subFeed} onChange={change} />
{:else if isKindFeed(subFeed)}
<FeedFormSectionKinds feed={subFeed} onChange={change} />
{:else if isCreatedAtFeed(subFeed)}
<FeedFormSectionCreatedAt feed={subFeed} onChange={change} />
{:else if isListFeed(subFeed)}
<FeedFormSectionList feed={subFeed} onChange={change} />
{:else if isDVMFeed(subFeed)}
<FeedFormSectionDVM feed={subFeed} onChange={change} />
{:else}
No support for editing {toTitle(subFeed[0])} filters. Click "Advanced" to edit manually.
{/if}
</FlexColumn>
{#if canSave}
<FeedFormSaveAsList feed={subFeed} onChange={change} />
{/if}
</FlexColumn>
{#if isAuthorFeed(subFeed) || isRelayFeed(subFeed) || isTopicFeed(subFeed) || isMentionFeed(subFeed)}
<FeedFormSaveAsList feed={subFeed} onChange={change} />
{#if i > 0}
<div
class="absolute right-2 top-2 h-4 w-4 cursor-pointer"
on:click={() => onSubFeedRemove(idx)}>
<i class="fa fa-times" />
</div>
{/if}
</FlexColumn>
{#if i > 0}
<div
class="absolute right-2 top-2 h-4 w-4 cursor-pointer"
on:click={() => onSubFeedRemove(idx)}>
<i class="fa fa-times" />
</div>
{/if}
</Card>
</Card>
{/if}
{/each}
{/key}

View File

@ -21,7 +21,7 @@
$: addresses = feed.slice(1).flatMap(it => it.addresses)
</script>
<span>Which lists would you like to use?</span>
<span class="staatliches text-lg">Which lists would you like to use?</span>
<SearchSelect
multiple
value={addresses}

View File

@ -23,7 +23,7 @@
$: scopes = isScopeFeed(feed) ? feed.slice(1) : ["custom"]
</script>
<span>Which authors would you like to see?</span>
<span class="staatliches text-lg">Which authors would you like to see?</span>
<SelectButton
multiple
value={scopes}

View File

@ -1,5 +1,4 @@
<script lang="ts">
import {without} from "ramda"
import {
parse,
truncate,
@ -16,7 +15,6 @@
isAddress,
isNewline,
} from "@welshman/content"
import MediaSet from "src/partials/MediaSet.svelte"
import QRCode from "src/partials/QRCode.svelte"
import NoteContentNewline from "src/app/shared/NoteContentNewline.svelte"
import NoteContentEllipsis from "src/app/shared/NoteContentEllipsis.svelte"
@ -44,16 +42,13 @@
const isBoundary = i => {
const parsed = shortContent[i]
if (!parsed || isBoundary(parsed)) return true
if (!parsed || isNewline(parsed)) return true
if (isText(parsed)) return parsed.value.match(/^\s+$/)
return false
}
const isStartOrEnd = i => Boolean(isBoundary(i - 1) || isBoundary(i + 1))
const getLinks = content =>
content.filter(x => isLink(x) && x.value.isMedia).map(x => x.value.url.toString())
const isStartOrEnd = i => Boolean(isBoundary(i - 1) && isBoundary(i + 1))
$: shortContent = showEntire
? fullContent
@ -66,8 +61,6 @@
},
)
$: links = getLinks(shortContent)
$: extraLinks = without(links, getLinks(fullContent))
$: ellipsize = expandable && shortContent.find(isEllipsis)
</script>
@ -107,9 +100,6 @@
{/if}
{/each}
</div>
{#if showMedia && extraLinks.length > 0}
<MediaSet links={extraLinks} />
{/if}
</div>
{#if ellipsize}

View File

@ -10,6 +10,8 @@
const url = value.url.toString()
const coracleRegexp = /^(https?:\/\/)?(app\.)?coracle.social/
const close = () => {
hidden = true
}
@ -17,18 +19,16 @@
let hidden = false
</script>
{#if url.includes('coracle.social/')}
{#if url.match(coracleRegexp)}
<Anchor
modal
stopPropagation
class="overflow-hidden text-ellipsis whitespace-nowrap underline"
href={url.replace(/(https?:\/\/)?(app\.)?coracle.social/, '')}>
href={url.replace(coracleRegexp, '')}>
{displayUrl(url)}
</Anchor>
{:else if showMedia && value.isMedia && !hidden}
<div class="py-2">
<Media url={url} onClose={close} />
</div>
{:else if showMedia && !hidden}
<Media url={url} onClose={close} />
{:else if isShareableRelayUrl(url)}
<Anchor
modal

View File

@ -96,7 +96,7 @@
<p class="mt-4 text-lg">The following relays are still pending:</p>
<div class="grid gap-2 sm:grid-cols-2">
{#each pending as url}
<RelayCard hideActions {url} />
<RelayCard hideDescription hideActions {url} />
{/each}
</div>
{/if}
@ -104,7 +104,7 @@
<p class="mt-4 text-lg">The following relays accepted your note:</p>
<div class="grid gap-2 sm:grid-cols-2">
{#each success as url}
<RelayCard hideActions {url} />
<RelayCard hideDescription hideActions {url} />
{/each}
</div>
{/if}

View File

@ -18,6 +18,7 @@
export let claim = null
export let ratings = null
export let showStatus = false
export let hideDescription = false
export let hideRatingsCount = false
export let hideActions = false
export let showControls = false
@ -64,30 +65,32 @@
</slot>
{/if}
</div>
<slot name="description">
{#if $relay.description}
<p>{$relay.description}</p>
{#if !hideDescription}
<slot name="description">
{#if $relay.description}
<p>{$relay.description}</p>
{/if}
</slot>
{#if !isNil($relay.count)}
<span class="flex items-center gap-1 text-sm text-neutral-400">
{#if $relay.contact}
<Anchor external underline href={$relay.contact}>{displayUrl($relay.contact)}</Anchor>
&bull;
{/if}
{#if $relay.supported_nips}
<Popover>
<span slot="trigger" class="cursor-pointer underline">
{$relay.supported_nips.length} NIPs
</span>
<span slot="tooltip">
NIPs supported: {$relay.supported_nips.join(", ")}
</span>
</Popover>
&bull;
{/if}
Seen {quantify($relay.count || 0, "time")}
</span>
{/if}
</slot>
{#if !isNil($relay.count)}
<span class="flex items-center gap-1 text-sm text-neutral-400">
{#if $relay.contact}
<Anchor external underline href={$relay.contact}>{displayUrl($relay.contact)}</Anchor>
&bull;
{/if}
{#if $relay.supported_nips}
<Popover>
<span slot="trigger" class="cursor-pointer underline">
{$relay.supported_nips.length} NIPs
</span>
<span slot="tooltip">
NIPs supported: {$relay.supported_nips.join(", ")}
</span>
</Popover>
&bull;
{/if}
Seen {quantify($relay.count || 0, "time")}
</span>
{/if}
{#if showControls && $canSign}
<div class="-mx-6 my-1 h-px bg-tinted-700" />

View File

@ -90,7 +90,7 @@ export const deriveEventsMapped = <T>({
if (dirty) {
if (debug) {
if (new Set(data.map(item => itemToEvent(item).id)).size < data.length) {
console.error(`Duplicate records found:`, copy, data, updates)
console.error(`Duplicate records found:`, copy, [...data], updates)
}
}

View File

@ -255,7 +255,15 @@ export const imgproxy = (url: string, {w = 640, h = 1024} = {}) => {
}
}
export const dufflepud = (path: string) => `${getSetting("dufflepud_url")}/${path}`
export const dufflepud = (path: string) => {
const base = getSetting("dufflepud_url")
if (!base) {
throw new Error("Dufflepud is not enabled")
}
return `${base}/${path}`
}
export const session = new Derived(
[pubkey, sessions],
@ -1999,16 +2007,19 @@ class IndexedDBAdapter {
const removedRecords = prev.filter(r => !currentIds.has(r[key]))
if (newRecords.length > 0) {
console.log('putting', name, newRecords.length, current.length)
await storage.bulkPut(name, newRecords)
}
if (removedRecords.length > 0) {
console.log('deleting', name, removedRecords.length, current.length)
await storage.bulkDelete(name, removedRecords.map(prop(key)))
}
// If we have much more than our limit, prune our store. This will get persisted
// the next time around.
if (current.length > limit * 1.5) {
console.log('pruning', name, current.length)
set((sort ? sort(current) : current).slice(0, limit))
}

View File

@ -18,8 +18,8 @@
const loadPreview = async () => {
const json = await Fetch.postJson(dufflepud("link/preview"), {url})
if (!json.title && !json.image) {
throw new Error("Unable to load preview")
if (!json?.title && !json?.image) {
throw new Error("Failed to load link preview")
}
return json
@ -81,7 +81,7 @@
</div>
{/if}
{:catch}
<p class="mb-1 px-12 py-24 text-center text-neutral-600">
<p class="mb-1 p-12 text-center text-neutral-600">
Unable to load a preview for {url}
</p>
{/await}

View File

@ -1,31 +0,0 @@
<script lang="ts">
import Media from "src/partials/Media.svelte"
import Anchor from "src/partials/Anchor.svelte"
import Modal from "src/partials/Modal.svelte"
export let links
let showModal = false
const openModal = () => {
showModal = true
}
const closeModal = () => {
showModal = false
}
</script>
<div class="my-8 flex justify-center">
<Anchor button on:click={openModal}>
<i class="fa fa-plus" /> Show all {links.length} link previews
</Anchor>
</div>
{#if showModal}
<Modal onEscape={closeModal}>
{#each links as url}
<Media {url} />
{/each}
</Modal>
{/if}

View File

@ -5,16 +5,18 @@
export let displayOption = x => x
const getClassName = active =>
cx("px-4 h-7 transition-all rounded-full mr-2 mb-2 inline-flex items-center", {
cx("px-4 h-6 transition-all rounded-full mr-2 mb-2 inline-block items-center", {
"bg-neutral-900 dark:bg-tinted-100 text-accent": active,
"bg-neutral-900 text-neutral-400": !active,
})
</script>
<div class="-mb-2 inline-block">
<SelectList {...$$props} class="staatliches inline-flex">
<div class={cx($$props.class, "-mb-2 inline-block")}>
<SelectList {...$$props} optionClass="staatliches inline-block">
<div slot="item" let:i let:active let:option class={getClassName(active)}>
{displayOption(option)}
<slot name="item" {option} {active}>
{displayOption(option)}
</slot>
</div>
</SelectList>
</div>

View File

@ -6,6 +6,7 @@
export let onChange = null
export let disabled = false
export let multiple = false
export let optionClass = ""
const onClick = option => {
if (multiple) {
@ -24,7 +25,7 @@
class:opacity-75={disabled}
class:cursor-pointer={!disabled}>
{#each options as option, i}
<div on:click={() => onClick(option)}>
<div class={optionClass} on:click={() => onClick(option)}>
<slot
name="item"
{i}