mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-19 11:43:35 +00:00
Add tags to notes by parsing content, add note delete button, add mentions to quotes, increase routes table and change sort order to hopefully get better relay hints, upgrade mentions from square bracket notation to bech32 embeds
This commit is contained in:
parent
b8a4232683
commit
4ccf12efa8
@ -3,6 +3,15 @@
|
||||
# 0.2.30
|
||||
|
||||
- [x] Prefer followed users when mentioning people
|
||||
- [x] Open people in a modal when it makes sense
|
||||
- [x] Fix regex for urls
|
||||
- [x] Fix note sharing bug
|
||||
- [x] Add mention mark to e tags embedded in notes
|
||||
- [x] Add tags to notes by parsing content
|
||||
- [x] Add note delete button
|
||||
- [x] Add mentions to quotes
|
||||
- [x] Increase routes table size and change sort order to hopefully get better relay hints
|
||||
- [x] Upgrade mentions from square bracket notation to bech32 embeds
|
||||
|
||||
# 0.2.29
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {map, pick, uniqBy} from "ramda"
|
||||
import {pick, uniqBy} from "ramda"
|
||||
import {get} from "svelte/store"
|
||||
import {doPipe} from "hurdak/lib/hurdak"
|
||||
import {parseContent, Tags, roomAttrs, displayPerson, findRoot, findReply} from "src/util/nostr"
|
||||
@ -51,39 +51,21 @@ const createChatMessage = (roomId, content, url) =>
|
||||
const createDirectMessage = (pubkey, content) =>
|
||||
new PublishableEvent(4, {content, tags: [["p", pubkey]]})
|
||||
|
||||
const createNote = (content, mentions = [], topics = []) => {
|
||||
// Mentions have to come first so interpolation works
|
||||
const tags = doPipe(
|
||||
[],
|
||||
[
|
||||
tags => tags.concat(processMentions(mentions)),
|
||||
tags => tags.concat(topics.map(t => ["t", t])),
|
||||
tags => tagsFromContent(content, tags),
|
||||
uniqTags,
|
||||
]
|
||||
)
|
||||
|
||||
return new PublishableEvent(1, {content, tags})
|
||||
}
|
||||
const createNote = (content, tags = []) =>
|
||||
new PublishableEvent(1, {content, tags: uniqTags(tagsFromContent(content, tags))})
|
||||
|
||||
const createReaction = (note, content) =>
|
||||
new PublishableEvent(7, {content, tags: getReplyTags(note)})
|
||||
|
||||
const createReply = (note, content, mentions = [], topics = []) => {
|
||||
// Mentions have to come first so interpolation works
|
||||
const tags = doPipe(
|
||||
[],
|
||||
[
|
||||
tags => tags.concat(processMentions(mentions)),
|
||||
tags => tags.concat(topics.map(t => ["t", t])),
|
||||
const createReply = (note, content, tags = []) =>
|
||||
new PublishableEvent(1, {
|
||||
content,
|
||||
tags: doPipe(tags, [
|
||||
tags => tags.concat(getReplyTags(note, true)),
|
||||
tags => tagsFromContent(content, tags),
|
||||
uniqTags,
|
||||
]
|
||||
)
|
||||
|
||||
return new PublishableEvent(1, {content, tags})
|
||||
}
|
||||
]),
|
||||
})
|
||||
|
||||
const requestZap = (relays, content, pubkey, eventId, amount, lnurl) => {
|
||||
const tags = [
|
||||
@ -104,27 +86,22 @@ const deleteEvent = ids => new PublishableEvent(5, {tags: ids.map(id => ["e", id
|
||||
|
||||
// Utils
|
||||
|
||||
const processMentions = map(pubkey => {
|
||||
const name = displayPerson(getPersonWithFallback(pubkey))
|
||||
const pHint = getRelayForPersonHint(pubkey)
|
||||
|
||||
return ["p", pubkey, pHint?.url || "", name]
|
||||
})
|
||||
|
||||
const tagsFromContent = (content, tags) => {
|
||||
const seen = new Set(Tags.wrap(tags).values().all())
|
||||
|
||||
for (const {type, value} of parseContent({content})) {
|
||||
if (type === "topic") {
|
||||
tags = tags.concat([["t", value]])
|
||||
seen.add(value)
|
||||
}
|
||||
|
||||
if (type.match(/nostr:(note|nevent)/) && !seen.has(value.id)) {
|
||||
tags = tags.concat([["e", value.id, "mention"]])
|
||||
seen.add(value.id)
|
||||
}
|
||||
|
||||
if (type.match(/nostr:(nprofile|npub)/) && !seen.has(value.pubkey)) {
|
||||
const name = displayPerson(getPersonWithFallback(value.pubkey))
|
||||
const pHint = getRelayForPersonHint(value.pubkey)
|
||||
|
||||
tags = tags.concat([["p", value.pubkey, pHint?.url || "", name]])
|
||||
tags = tags.concat([mention(value.pubkey)])
|
||||
seen.add(value.pubkey)
|
||||
}
|
||||
}
|
||||
@ -132,6 +109,13 @@ const tagsFromContent = (content, tags) => {
|
||||
return tags
|
||||
}
|
||||
|
||||
const mention = pubkey => {
|
||||
const name = displayPerson(getPersonWithFallback(pubkey))
|
||||
const hint = getRelayForPersonHint(pubkey)
|
||||
|
||||
return ["p", pubkey, hint?.url || "", name]
|
||||
}
|
||||
|
||||
const getReplyTags = (n, inherit = false) => {
|
||||
const extra = inherit
|
||||
? Tags.from(n)
|
||||
@ -147,7 +131,7 @@ const getReplyTags = (n, inherit = false) => {
|
||||
t => t.slice(0, 3).concat("root"),
|
||||
])
|
||||
|
||||
return [["p", n.pubkey, pHint?.url || ""], root, ...extra, reply]
|
||||
return [mention(n.pubkey), root, ...extra, reply]
|
||||
}
|
||||
|
||||
const uniqTags = uniqBy(t => t.slice(0, 2).join(":"))
|
||||
@ -185,6 +169,7 @@ class PublishableEvent {
|
||||
}
|
||||
|
||||
export default {
|
||||
mention,
|
||||
authenticate,
|
||||
updateUser,
|
||||
setRelays,
|
||||
|
@ -197,7 +197,7 @@ export const dropAll = () => new Promise(resolve => loki.deleteDatabase(resolve)
|
||||
// Domain-specific collections
|
||||
|
||||
const sortByCreatedAt = sortBy(e => -e.created_at)
|
||||
const sortByLastSeen = sortBy(e => -e.last_seen)
|
||||
const sortByScore = sortBy(e => -e.score)
|
||||
|
||||
export const people = new Table("people", "pubkey", {
|
||||
max: 3000,
|
||||
@ -215,7 +215,7 @@ export const notifications = new Table("notifications", "id", {sort: sortByCreat
|
||||
export const contacts = new Table("contacts", "pubkey")
|
||||
export const rooms = new Table("rooms", "id")
|
||||
export const relays = new Table("relays", "url")
|
||||
export const routes = new Table("routes", "id", {max: 3000, sort: sortByLastSeen})
|
||||
export const routes = new Table("routes", "id", {max: 10000, sort: sortByScore})
|
||||
export const topics = new Table("topics", "name")
|
||||
|
||||
export const getPersonWithFallback = pubkey => people.get(pubkey) || {pubkey}
|
||||
|
@ -7,6 +7,7 @@
|
||||
import LoginPubKey from "src/app/views/LoginPubKey.svelte"
|
||||
import Onboarding from "src/app/views/Onboarding.svelte"
|
||||
import NoteCreate from "src/app/views/NoteCreate.svelte"
|
||||
import NoteDelete from "src/app/views/NoteDelete.svelte"
|
||||
import NoteZap from "src/app/views/NoteZap.svelte"
|
||||
import NoteShare from "src/app/views/NoteShare.svelte"
|
||||
import NoteDetail from "src/app/views/NoteDetail.svelte"
|
||||
@ -29,7 +30,9 @@
|
||||
<NoteDetail {...m} invertColors />
|
||||
{/key}
|
||||
{:else if m.type === "note/create"}
|
||||
<NoteCreate pubkey={m.pubkey} nevent={m.nevent} writeTo={m.relays} />
|
||||
<NoteCreate pubkey={m.pubkey} quote={m.quote} writeTo={m.relays} />
|
||||
{:else if m.type === "note/delete"}
|
||||
<NoteDelete note={m.note} />
|
||||
{:else if m.type === "note/zap"}
|
||||
<NoteZap note={m.note} />
|
||||
{:else if m.type === "note/share"}
|
||||
|
@ -40,9 +40,10 @@
|
||||
modal.push({type: "note/share", note})
|
||||
}
|
||||
|
||||
const quote = () => modal.push({type: "note/create", nevent})
|
||||
const quote = () => modal.push({type: "note/create", quote: note})
|
||||
const mute = () => user.addMute("e", note.id)
|
||||
const unmute = () => user.removeMute(note.id)
|
||||
const deleteSelf = () => modal.push({type: "note/delete", note})
|
||||
|
||||
const react = async content => {
|
||||
like = first(await cmd.createReaction(note, content).publish(getEventPublishRelays(note)))
|
||||
@ -83,6 +84,10 @@
|
||||
actions.push({label: "Share", icon: "share-nodes", onClick: share})
|
||||
actions.push({label: "Quote", icon: "quote-left", onClick: quote})
|
||||
|
||||
if (note.pubkey === user.getPubkey()) {
|
||||
actions.push({label: "Delete", icon: "trash", onClick: deleteSelf})
|
||||
}
|
||||
|
||||
if (muted) {
|
||||
actions.push({label: "Unmute", icon: "microphone", onClick: unmute})
|
||||
} else {
|
||||
|
@ -48,17 +48,15 @@
|
||||
}
|
||||
|
||||
const send = async () => {
|
||||
let {content, mentions, topics} = reply.parse()
|
||||
let content = reply.parse()
|
||||
|
||||
if (data.image) {
|
||||
content = (content + "\n" + data.image).trim()
|
||||
}
|
||||
|
||||
if (content) {
|
||||
mentions = uniq(mentions.concat(data.mentions))
|
||||
|
||||
const relays = getEventPublishRelays(note)
|
||||
const thunk = cmd.createReply(note, content, mentions, topics)
|
||||
const thunk = cmd.createReply(note, content, data.mentions.map(cmd.mention))
|
||||
const [event, promise] = await publishWithToast(relays, thunk)
|
||||
|
||||
promise.then(({succeeded}) => {
|
||||
|
@ -15,15 +15,15 @@
|
||||
import Heading from "src/partials/Heading.svelte"
|
||||
import RelayCard from "src/app/shared/RelayCard.svelte"
|
||||
import RelaySearch from "src/app/shared/RelaySearch.svelte"
|
||||
import {getUserWriteRelays} from "src/agent/relays"
|
||||
import {getUserWriteRelays, getRelayForPersonHint} from "src/agent/relays"
|
||||
import {getPersonWithFallback} from "src/agent/db"
|
||||
import cmd from "src/agent/cmd"
|
||||
import user from "src/agent/user"
|
||||
import {toast, modal} from "src/partials/state"
|
||||
import {publishWithToast} from "src/app/state"
|
||||
|
||||
export let quote = null
|
||||
export let pubkey = null
|
||||
export let nevent = null
|
||||
export let writeTo: string[] | null = null
|
||||
|
||||
let q = ""
|
||||
@ -38,14 +38,23 @@
|
||||
)
|
||||
|
||||
const onSubmit = async () => {
|
||||
let {content, mentions, topics} = compose.parse()
|
||||
let content = compose.parse()
|
||||
const tags = []
|
||||
|
||||
if (image) {
|
||||
content = content + "\n" + image
|
||||
}
|
||||
|
||||
if (quote) {
|
||||
const {pubkey} = quote
|
||||
const person = getPersonWithFallback(pubkey)
|
||||
const pHint = getRelayForPersonHint(pubkey)
|
||||
|
||||
tags.push(["p", pubkey, pHint?.url || "", displayPerson(person)])
|
||||
}
|
||||
|
||||
if (content) {
|
||||
const thunk = cmd.createNote(content.trim(), mentions, topics)
|
||||
const thunk = cmd.createNote(content.trim(), tags)
|
||||
const [event, promise] = await publishWithToast($relays, thunk)
|
||||
|
||||
promise.then(() =>
|
||||
@ -90,7 +99,9 @@
|
||||
compose.mention(getPersonWithFallback(pubkey))
|
||||
}
|
||||
|
||||
if (nevent) {
|
||||
if (quote) {
|
||||
const nevent = nip19.neventEncode({id: quote.id, relays: [quote.seen_on]})
|
||||
|
||||
compose.nevent("nostr:" + nevent)
|
||||
}
|
||||
})
|
||||
|
24
src/app/views/NoteDelete.svelte
Normal file
24
src/app/views/NoteDelete.svelte
Normal file
@ -0,0 +1,24 @@
|
||||
<script lang="ts">
|
||||
import {modal} from "src/partials/state"
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import {getUserWriteRelays} from "src/agent/relays"
|
||||
import cmd from "src/agent/cmd"
|
||||
|
||||
export let note
|
||||
|
||||
const confirm = () => {
|
||||
cmd.deleteEvent([note.id]).publish(getUserWriteRelays())
|
||||
modal.pop()
|
||||
}
|
||||
</script>
|
||||
|
||||
<Content size="lg">
|
||||
<div class="text-center">
|
||||
Notes cannot be reliably deleted on nostr, but you can ask. Are you sure you want to delete this
|
||||
note?
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<Anchor type="button" on:click={confirm}>Confirm</Anchor>
|
||||
</div>
|
||||
</Content>
|
@ -45,7 +45,7 @@
|
||||
await Promise.all([
|
||||
user.updateRelays(() => user.getRelays()),
|
||||
cmd.updateUser(profile).publish(user.getRelays()),
|
||||
note && cmd.createNote(note.content, note.mentions, note.topics).publish(user.getRelays()),
|
||||
note && cmd.createNote(note).publish(user.getRelays()),
|
||||
user.updatePetnames(() =>
|
||||
user.getPetnamePubkeys().map(pubkey => {
|
||||
const [{url}] = sampleRelays(getPubkeyWriteRelays(pubkey))
|
||||
|
@ -203,20 +203,16 @@
|
||||
|
||||
export const parse = () => {
|
||||
let {content, annotations} = contenteditable.parse()
|
||||
const topics = pluck("value", annotations.filter(propEq("prefix", "#")))
|
||||
|
||||
// Remove zero-width and non-breaking spaces
|
||||
content = content.replace(/[\u200B\u00A0]/g, " ").trim()
|
||||
|
||||
// We're still using old style mention interpolation until NIP-27
|
||||
// gets merged https://github.com/nostr-protocol/nips/pull/381/files
|
||||
const mentions = annotations.filter(propEq("prefix", "@")).map(({value}, index) => {
|
||||
content = content.replace("@" + value, `#[${index}]`)
|
||||
|
||||
return pubkeyEncoder.decode(value)
|
||||
// Strip the @ sign in mentions
|
||||
annotations.filter(propEq("prefix", "@")).forEach(({value}, index) => {
|
||||
content = content.replace("@" + value, value)
|
||||
})
|
||||
|
||||
return {content, topics, mentions}
|
||||
return content
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -77,7 +77,9 @@ export class Tags {
|
||||
}
|
||||
|
||||
export const findReplyAndRoot = e => {
|
||||
const tags = Tags.from(e).type("e")
|
||||
const tags = Tags.from(e)
|
||||
.type("e")
|
||||
.filter(t => last(t) !== "mention")
|
||||
const legacy = tags.any(t => !["reply", "root"].includes(last(t)))
|
||||
|
||||
// Support the deprecated version where tags are not marked as replies
|
||||
|
Loading…
Reference in New Issue
Block a user