mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-18 19:23:40 +00:00
Add support for highlights
This commit is contained in:
parent
7c9c2ee692
commit
4234cbc0e1
@ -1,7 +1,10 @@
|
||||
# Current
|
||||
|
||||
- [ ] Spam
|
||||
- Add configurable POW req for replies
|
||||
- Add event queue and undo, use the delay to calculate POW
|
||||
- [ ] Support other kinds
|
||||
- Fix note truncation
|
||||
- Fix note truncation, sometimes an ellipsis ends up after the last one
|
||||
- [ ] Feeds load forever if a modal is open
|
||||
- [ ] Support other list types than 30001
|
||||
- [ ] Fix connection management stuff. Have GPT help
|
||||
|
@ -1,107 +1,20 @@
|
||||
<script lang="ts">
|
||||
import {pluck, without} from "ramda"
|
||||
import {switcher, switcherFn} from "hurdak/lib/hurdak"
|
||||
import {displayPerson, getLabelQuality, displayRelay, Tags} from "src/util/nostr"
|
||||
import {parseContent, truncateContent, LINK, INVOICE, NEWLINE, TOPIC} from "src/util/notes"
|
||||
import {modal} from "src/partials/state"
|
||||
import MediaSet from "src/partials/MediaSet.svelte"
|
||||
import QRCode from "src/partials/QRCode.svelte"
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import Rating from "src/partials/Rating.svelte"
|
||||
import NoteContentNewline from "src/app/shared/NoteContentNewline.svelte"
|
||||
import NoteContentTopic from "src/app/shared/NoteContentTopic.svelte"
|
||||
import NoteContentLink from "src/app/shared/NoteContentLink.svelte"
|
||||
import NoteContentPerson from "src/app/shared/NoteContentPerson.svelte"
|
||||
import NoteContentQuote from "src/app/shared/NoteContentQuote.svelte"
|
||||
import NoteContentEntity from "src/app/shared/NoteContentEntity.svelte"
|
||||
import NoteContentKind1 from "src/app/shared/NoteContentKind1.svelte"
|
||||
import NoteContentKind1985 from "src/app/shared/NoteContentKind1985.svelte"
|
||||
import NoteContentKind9802 from "src/app/shared/NoteContentKind9802.svelte"
|
||||
import user from "src/agent/user"
|
||||
import {getPersonWithFallback} from "src/agent/db"
|
||||
|
||||
export let note
|
||||
export let anchorId = null
|
||||
export let maxLength = 700
|
||||
export let showEntire = false
|
||||
export let showMedia = user.getSetting("showMedia")
|
||||
|
||||
const getLinks = parts =>
|
||||
pluck(
|
||||
"value",
|
||||
parts.filter(x => x.type === LINK && x.canDisplay)
|
||||
)
|
||||
|
||||
const isStandalone = i => {
|
||||
return (
|
||||
!shortContent[i - 1] ||
|
||||
shortContent[i - 1].type === NEWLINE ||
|
||||
!shortContent[i + 1] ||
|
||||
shortContent[i + 1].type === NEWLINE
|
||||
)
|
||||
}
|
||||
|
||||
const fullContent = parseContent(note)
|
||||
const shortContent = truncateContent(fullContent, {maxLength, showEntire, showMedia})
|
||||
const rating = note.kind === 1985 ? getLabelQuality("review/relay", note) : null
|
||||
const links = getLinks(shortContent)
|
||||
const extraLinks = without(links, getLinks(fullContent))
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-2 overflow-hidden text-ellipsis">
|
||||
<p>
|
||||
{#if rating}
|
||||
{@const [type, value] = Tags.from(note)
|
||||
.reject(t => ["l", "L"].includes(t[0]))
|
||||
.first()}
|
||||
{@const action = switcher(type, {
|
||||
r: () => modal.push({type: "relay/detail", url: value}),
|
||||
p: () => modal.push({type: "person/feed", pubkey: value}),
|
||||
e: () => modal.push({type: "note/detail", note: {id: value}}),
|
||||
})}
|
||||
{@const display = switcherFn(type, {
|
||||
r: () => displayRelay({url: value}),
|
||||
p: () => displayPerson(getPersonWithFallback(value)),
|
||||
e: () => "a note",
|
||||
default: "something",
|
||||
})}
|
||||
<div class="mb-4 flex items-center gap-2 border-l-2 border-solid border-gray-5 pl-2">
|
||||
Rated
|
||||
{#if action}
|
||||
<Anchor on:click={action}>{display}</Anchor>
|
||||
{:else}
|
||||
{display}
|
||||
{/if}
|
||||
<div class="text-sm">
|
||||
<Rating inert value={rating} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#each shortContent as { type, value }, i}
|
||||
{#if type === NEWLINE}
|
||||
<NoteContentNewline {value} />
|
||||
{:else if type === TOPIC}
|
||||
<NoteContentTopic {value} />
|
||||
{:else if type === INVOICE}
|
||||
<div on:click|stopPropagation>
|
||||
<QRCode fullWidth onClick="copy" code={value} />
|
||||
</div>
|
||||
{:else if type === LINK}
|
||||
<NoteContentLink {value} showMedia={showMedia && isStandalone(i)} />
|
||||
{:else if type.match(/^nostr:np(rofile|ub)$/)}
|
||||
<NoteContentPerson {value} />
|
||||
{:else if type.startsWith("nostr:") && showMedia && isStandalone(i) && value.id !== anchorId}
|
||||
<NoteContentQuote {note} {value}>
|
||||
<div slot="note-content" let:quote>
|
||||
<svelte:self note={quote} />
|
||||
</div>
|
||||
</NoteContentQuote>
|
||||
{:else if type.startsWith("nostr:")}
|
||||
<NoteContentEntity {value} />
|
||||
{:else}
|
||||
{value}
|
||||
{/if}
|
||||
{" "}
|
||||
{/each}
|
||||
</p>
|
||||
{#if showMedia && extraLinks.length > 0}
|
||||
<MediaSet links={extraLinks} />
|
||||
{/if}
|
||||
</div>
|
||||
{#if note.kind === 1985}
|
||||
<NoteContentKind1985 {note} {anchorId} {maxLength} {showEntire} />
|
||||
{:else if note.kind === 9802}
|
||||
<NoteContentKind9802 {note} {anchorId} {maxLength} {showEntire} {showMedia} />
|
||||
{:else}
|
||||
<NoteContentKind1 {note} {anchorId} {maxLength} {showEntire} {showMedia} />
|
||||
{/if}
|
||||
|
67
src/app/shared/NoteContentKind1.svelte
Normal file
67
src/app/shared/NoteContentKind1.svelte
Normal file
@ -0,0 +1,67 @@
|
||||
<script lang="ts">
|
||||
import {without} from "ramda"
|
||||
import {
|
||||
parseContent,
|
||||
getLinks,
|
||||
truncateContent,
|
||||
LINK,
|
||||
INVOICE,
|
||||
NEWLINE,
|
||||
TOPIC,
|
||||
} from "src/util/notes"
|
||||
import MediaSet from "src/partials/MediaSet.svelte"
|
||||
import QRCode from "src/partials/QRCode.svelte"
|
||||
import NoteContentNewline from "src/app/shared/NoteContentNewline.svelte"
|
||||
import NoteContentTopic from "src/app/shared/NoteContentTopic.svelte"
|
||||
import NoteContentLink from "src/app/shared/NoteContentLink.svelte"
|
||||
import NoteContentPerson from "src/app/shared/NoteContentPerson.svelte"
|
||||
import NoteContentQuote from "src/app/shared/NoteContentQuote.svelte"
|
||||
import NoteContentEntity from "src/app/shared/NoteContentEntity.svelte"
|
||||
|
||||
export let note, anchorId, maxLength, showEntire
|
||||
export let showMedia = false
|
||||
|
||||
const fullContent = parseContent(note)
|
||||
const shortContent = truncateContent(fullContent, {maxLength, showEntire, showMedia})
|
||||
const links = getLinks(shortContent)
|
||||
const extraLinks = without(links, getLinks(fullContent))
|
||||
|
||||
export const isNewline = i =>
|
||||
!shortContent[i] || shortContent[i].type === NEWLINE
|
||||
|
||||
export const isStartOrEnd = i => isNewline(i - 1) || isNewline(i + 1)
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-2 overflow-hidden text-ellipsis">
|
||||
<p>
|
||||
{#each shortContent as { type, value }, i}
|
||||
{#if type === NEWLINE}
|
||||
<NoteContentNewline {value} />
|
||||
{:else if type === TOPIC}
|
||||
<NoteContentTopic {value} />
|
||||
{:else if type === INVOICE}
|
||||
<div on:click|stopPropagation>
|
||||
<QRCode fullWidth onClick="copy" code={value} />
|
||||
</div>
|
||||
{:else if type === LINK}
|
||||
<NoteContentLink {value} showMedia={showMedia && isStartOrEnd(i) && value.url.includes('/')} />
|
||||
{:else if type.match(/^nostr:np(rofile|ub)$/)}
|
||||
<NoteContentPerson {value} />
|
||||
{:else if type.startsWith("nostr:") && showMedia && isStartOrEnd(i) && value.id !== anchorId}
|
||||
<NoteContentQuote {note} {value}>
|
||||
<div slot="note-content" let:quote>
|
||||
<svelte:self note={quote} />
|
||||
</div>
|
||||
</NoteContentQuote>
|
||||
{:else if type.startsWith("nostr:")}
|
||||
<NoteContentEntity {value} />
|
||||
{:else}
|
||||
{value}
|
||||
{/if}
|
||||
{" "}
|
||||
{/each}
|
||||
</p>
|
||||
{#if showMedia && extraLinks.length > 0}
|
||||
<MediaSet links={extraLinks} />
|
||||
{/if}
|
||||
</div>
|
16
src/app/shared/NoteContentKind1985.svelte
Normal file
16
src/app/shared/NoteContentKind1985.svelte
Normal file
@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import {getLabelQuality} from "src/util/nostr"
|
||||
import NoteContentRating from "src/app/shared/NoteContentRating.svelte"
|
||||
import NoteContentKind1 from "src/app/shared/NoteContentKind1.svelte"
|
||||
|
||||
export let note, anchorId, maxLength, showEntire
|
||||
|
||||
const rating = getLabelQuality("review/relay", note)
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-2 overflow-hidden text-ellipsis">
|
||||
{#if rating}
|
||||
<NoteContentRating {note} {rating} />
|
||||
{/if}
|
||||
<NoteContentKind1 {note} {anchorId} {maxLength} {showEntire} />
|
||||
</div>
|
20
src/app/shared/NoteContentKind9802.svelte
Normal file
20
src/app/shared/NoteContentKind9802.svelte
Normal file
@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import {Tags} from "src/util/nostr"
|
||||
import {canDisplayUrl} from "src/util/notes"
|
||||
import NoteContentKind1 from "src/app/shared/NoteContentKind1.svelte"
|
||||
import NoteContentLink from "src/app/shared/NoteContentLink.svelte"
|
||||
|
||||
export let note, anchorId, maxLength, showEntire, showMedia
|
||||
|
||||
const ref = Tags.from(note).getMeta("r")
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-2 overflow-hidden text-ellipsis">
|
||||
<div class="border-l-2 border-solid border-gray-5 pl-4">
|
||||
<NoteContentKind1 {note} {anchorId} {maxLength} {showEntire} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if ref}
|
||||
<NoteContentLink {showMedia} value={{url: ref, canDisplay: canDisplayUrl(ref)}} />
|
||||
{/if}
|
@ -10,8 +10,6 @@
|
||||
hidden = true
|
||||
}
|
||||
|
||||
console.log(value)
|
||||
|
||||
let hidden = false
|
||||
</script>
|
||||
|
||||
|
39
src/app/shared/NoteContentRating.svelte
Normal file
39
src/app/shared/NoteContentRating.svelte
Normal file
@ -0,0 +1,39 @@
|
||||
<script lang="ts">
|
||||
import {switcher, switcherFn} from "hurdak/lib/hurdak"
|
||||
import {displayPerson, displayRelay, Tags} from "src/util/nostr"
|
||||
import {modal} from "src/partials/state"
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import Rating from "src/partials/Rating.svelte"
|
||||
import {getPersonWithFallback} from "src/agent/db"
|
||||
|
||||
export let note, rating
|
||||
|
||||
const [type, value] = Tags.from(note)
|
||||
.reject(t => ["l", "L"].includes(t[0]))
|
||||
.first()
|
||||
|
||||
const action = switcher(type, {
|
||||
r: () => modal.push({type: "relay/detail", url: value}),
|
||||
p: () => modal.push({type: "person/feed", pubkey: value}),
|
||||
e: () => modal.push({type: "note/detail", note: {id: value}}),
|
||||
})
|
||||
|
||||
const display = switcherFn(type, {
|
||||
r: () => displayRelay({url: value}),
|
||||
p: () => displayPerson(getPersonWithFallback(value)),
|
||||
e: () => "a note",
|
||||
default: "something",
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="mb-4 flex items-center gap-2 border-l-2 border-solid border-gray-5 pl-2">
|
||||
Rated
|
||||
{#if action}
|
||||
<Anchor on:click={action}>{display}</Anchor>
|
||||
{:else}
|
||||
{display}
|
||||
{/if}
|
||||
<div class="text-sm">
|
||||
<Rating inert value={rating} />
|
||||
</div>
|
||||
</div>
|
@ -6,7 +6,6 @@ import {tryJson, avg} from "src/util/misc"
|
||||
import {invoiceAmount} from "src/util/lightning"
|
||||
|
||||
export const noteKinds = [1, 1985, 30023, 30018, 10001, 1063, 9802]
|
||||
// export const noteKinds = [9802]
|
||||
export const personKinds = [0, 2, 3, 10001, 10002]
|
||||
export const userKinds = personKinds.concat([10000, 30001, 30078])
|
||||
export const appDataKeys = [
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {last, identity} from "ramda"
|
||||
import {last, pluck, identity} from "ramda"
|
||||
import {nip19} from "nostr-tools"
|
||||
import {first} from "hurdak/lib/hurdak"
|
||||
import {fromNostrURI} from "src/util/nostr"
|
||||
@ -14,7 +14,7 @@ export const NOSTR_NPUB = "nostr:npub"
|
||||
export const NOSTR_NPROFILE = "nostr:nprofile"
|
||||
export const NOSTR_NADDR = "nostr:naddr"
|
||||
|
||||
const canDisplayUrl = url => !url.match(/\.(apk|docx|xlsx|csv|dmg)/)
|
||||
export const canDisplayUrl = url => !url.match(/\.(apk|docx|xlsx|csv|dmg)/)
|
||||
|
||||
export const parseContent = ({content, tags = []}) => {
|
||||
const result = []
|
||||
@ -160,7 +160,7 @@ export const parseContent = ({content, tags = []}) => {
|
||||
return result
|
||||
}
|
||||
|
||||
export const truncateContent = (content, {showEntire, maxLength, showMedia}) => {
|
||||
export const truncateContent = (content, {showEntire, maxLength, showMedia = false}) => {
|
||||
if (showEntire) {
|
||||
return content
|
||||
}
|
||||
@ -194,3 +194,15 @@ export const truncateContent = (content, {showEntire, maxLength, showMedia}) =>
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export const getLinks = parts =>
|
||||
pluck(
|
||||
"value",
|
||||
parts.filter(x => x.type === LINK && x.canDisplay)
|
||||
)
|
||||
|
||||
export const isStandalone = (content, i) =>
|
||||
!content[i - 1] ||
|
||||
content[i - 1].type === NEWLINE ||
|
||||
!content[i + 1] ||
|
||||
content[i + 1].type === NEWLINE
|
||||
|
Loading…
Reference in New Issue
Block a user