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
|
# 0.2.30
|
||||||
|
|
||||||
- [x] Prefer followed users when mentioning people
|
- [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
|
# 0.2.29
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {map, pick, uniqBy} from "ramda"
|
import {pick, uniqBy} from "ramda"
|
||||||
import {get} from "svelte/store"
|
import {get} from "svelte/store"
|
||||||
import {doPipe} from "hurdak/lib/hurdak"
|
import {doPipe} from "hurdak/lib/hurdak"
|
||||||
import {parseContent, Tags, roomAttrs, displayPerson, findRoot, findReply} from "src/util/nostr"
|
import {parseContent, Tags, roomAttrs, displayPerson, findRoot, findReply} from "src/util/nostr"
|
||||||
@ -51,39 +51,21 @@ const createChatMessage = (roomId, content, url) =>
|
|||||||
const createDirectMessage = (pubkey, content) =>
|
const createDirectMessage = (pubkey, content) =>
|
||||||
new PublishableEvent(4, {content, tags: [["p", pubkey]]})
|
new PublishableEvent(4, {content, tags: [["p", pubkey]]})
|
||||||
|
|
||||||
const createNote = (content, mentions = [], topics = []) => {
|
const createNote = (content, tags = []) =>
|
||||||
// Mentions have to come first so interpolation works
|
new PublishableEvent(1, {content, tags: uniqTags(tagsFromContent(content, tags))})
|
||||||
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 createReaction = (note, content) =>
|
const createReaction = (note, content) =>
|
||||||
new PublishableEvent(7, {content, tags: getReplyTags(note)})
|
new PublishableEvent(7, {content, tags: getReplyTags(note)})
|
||||||
|
|
||||||
const createReply = (note, content, mentions = [], topics = []) => {
|
const createReply = (note, content, tags = []) =>
|
||||||
// Mentions have to come first so interpolation works
|
new PublishableEvent(1, {
|
||||||
const tags = doPipe(
|
content,
|
||||||
[],
|
tags: doPipe(tags, [
|
||||||
[
|
|
||||||
tags => tags.concat(processMentions(mentions)),
|
|
||||||
tags => tags.concat(topics.map(t => ["t", t])),
|
|
||||||
tags => tags.concat(getReplyTags(note, true)),
|
tags => tags.concat(getReplyTags(note, true)),
|
||||||
tags => tagsFromContent(content, tags),
|
tags => tagsFromContent(content, tags),
|
||||||
uniqTags,
|
uniqTags,
|
||||||
]
|
]),
|
||||||
)
|
})
|
||||||
|
|
||||||
return new PublishableEvent(1, {content, tags})
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestZap = (relays, content, pubkey, eventId, amount, lnurl) => {
|
const requestZap = (relays, content, pubkey, eventId, amount, lnurl) => {
|
||||||
const tags = [
|
const tags = [
|
||||||
@ -104,27 +86,22 @@ const deleteEvent = ids => new PublishableEvent(5, {tags: ids.map(id => ["e", id
|
|||||||
|
|
||||||
// Utils
|
// 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 tagsFromContent = (content, tags) => {
|
||||||
const seen = new Set(Tags.wrap(tags).values().all())
|
const seen = new Set(Tags.wrap(tags).values().all())
|
||||||
|
|
||||||
for (const {type, value} of parseContent({content})) {
|
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)) {
|
if (type.match(/nostr:(note|nevent)/) && !seen.has(value.id)) {
|
||||||
tags = tags.concat([["e", value.id, "mention"]])
|
tags = tags.concat([["e", value.id, "mention"]])
|
||||||
seen.add(value.id)
|
seen.add(value.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type.match(/nostr:(nprofile|npub)/) && !seen.has(value.pubkey)) {
|
if (type.match(/nostr:(nprofile|npub)/) && !seen.has(value.pubkey)) {
|
||||||
const name = displayPerson(getPersonWithFallback(value.pubkey))
|
tags = tags.concat([mention(value.pubkey)])
|
||||||
const pHint = getRelayForPersonHint(value.pubkey)
|
|
||||||
|
|
||||||
tags = tags.concat([["p", value.pubkey, pHint?.url || "", name]])
|
|
||||||
seen.add(value.pubkey)
|
seen.add(value.pubkey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,6 +109,13 @@ const tagsFromContent = (content, tags) => {
|
|||||||
return 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 getReplyTags = (n, inherit = false) => {
|
||||||
const extra = inherit
|
const extra = inherit
|
||||||
? Tags.from(n)
|
? Tags.from(n)
|
||||||
@ -147,7 +131,7 @@ const getReplyTags = (n, inherit = false) => {
|
|||||||
t => t.slice(0, 3).concat("root"),
|
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(":"))
|
const uniqTags = uniqBy(t => t.slice(0, 2).join(":"))
|
||||||
@ -185,6 +169,7 @@ class PublishableEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
mention,
|
||||||
authenticate,
|
authenticate,
|
||||||
updateUser,
|
updateUser,
|
||||||
setRelays,
|
setRelays,
|
||||||
|
@ -197,7 +197,7 @@ export const dropAll = () => new Promise(resolve => loki.deleteDatabase(resolve)
|
|||||||
// Domain-specific collections
|
// Domain-specific collections
|
||||||
|
|
||||||
const sortByCreatedAt = sortBy(e => -e.created_at)
|
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", {
|
export const people = new Table("people", "pubkey", {
|
||||||
max: 3000,
|
max: 3000,
|
||||||
@ -215,7 +215,7 @@ export const notifications = new Table("notifications", "id", {sort: sortByCreat
|
|||||||
export const contacts = new Table("contacts", "pubkey")
|
export const contacts = new Table("contacts", "pubkey")
|
||||||
export const rooms = new Table("rooms", "id")
|
export const rooms = new Table("rooms", "id")
|
||||||
export const relays = new Table("relays", "url")
|
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 topics = new Table("topics", "name")
|
||||||
|
|
||||||
export const getPersonWithFallback = pubkey => people.get(pubkey) || {pubkey}
|
export const getPersonWithFallback = pubkey => people.get(pubkey) || {pubkey}
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
import LoginPubKey from "src/app/views/LoginPubKey.svelte"
|
import LoginPubKey from "src/app/views/LoginPubKey.svelte"
|
||||||
import Onboarding from "src/app/views/Onboarding.svelte"
|
import Onboarding from "src/app/views/Onboarding.svelte"
|
||||||
import NoteCreate from "src/app/views/NoteCreate.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 NoteZap from "src/app/views/NoteZap.svelte"
|
||||||
import NoteShare from "src/app/views/NoteShare.svelte"
|
import NoteShare from "src/app/views/NoteShare.svelte"
|
||||||
import NoteDetail from "src/app/views/NoteDetail.svelte"
|
import NoteDetail from "src/app/views/NoteDetail.svelte"
|
||||||
@ -29,7 +30,9 @@
|
|||||||
<NoteDetail {...m} invertColors />
|
<NoteDetail {...m} invertColors />
|
||||||
{/key}
|
{/key}
|
||||||
{:else if m.type === "note/create"}
|
{: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"}
|
{:else if m.type === "note/zap"}
|
||||||
<NoteZap note={m.note} />
|
<NoteZap note={m.note} />
|
||||||
{:else if m.type === "note/share"}
|
{:else if m.type === "note/share"}
|
||||||
|
@ -40,9 +40,10 @@
|
|||||||
modal.push({type: "note/share", note})
|
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 mute = () => user.addMute("e", note.id)
|
||||||
const unmute = () => user.removeMute(note.id)
|
const unmute = () => user.removeMute(note.id)
|
||||||
|
const deleteSelf = () => modal.push({type: "note/delete", note})
|
||||||
|
|
||||||
const react = async content => {
|
const react = async content => {
|
||||||
like = first(await cmd.createReaction(note, content).publish(getEventPublishRelays(note)))
|
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: "Share", icon: "share-nodes", onClick: share})
|
||||||
actions.push({label: "Quote", icon: "quote-left", onClick: quote})
|
actions.push({label: "Quote", icon: "quote-left", onClick: quote})
|
||||||
|
|
||||||
|
if (note.pubkey === user.getPubkey()) {
|
||||||
|
actions.push({label: "Delete", icon: "trash", onClick: deleteSelf})
|
||||||
|
}
|
||||||
|
|
||||||
if (muted) {
|
if (muted) {
|
||||||
actions.push({label: "Unmute", icon: "microphone", onClick: unmute})
|
actions.push({label: "Unmute", icon: "microphone", onClick: unmute})
|
||||||
} else {
|
} else {
|
||||||
|
@ -48,17 +48,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const send = async () => {
|
const send = async () => {
|
||||||
let {content, mentions, topics} = reply.parse()
|
let content = reply.parse()
|
||||||
|
|
||||||
if (data.image) {
|
if (data.image) {
|
||||||
content = (content + "\n" + data.image).trim()
|
content = (content + "\n" + data.image).trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content) {
|
if (content) {
|
||||||
mentions = uniq(mentions.concat(data.mentions))
|
|
||||||
|
|
||||||
const relays = getEventPublishRelays(note)
|
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)
|
const [event, promise] = await publishWithToast(relays, thunk)
|
||||||
|
|
||||||
promise.then(({succeeded}) => {
|
promise.then(({succeeded}) => {
|
||||||
|
@ -15,15 +15,15 @@
|
|||||||
import Heading from "src/partials/Heading.svelte"
|
import Heading from "src/partials/Heading.svelte"
|
||||||
import RelayCard from "src/app/shared/RelayCard.svelte"
|
import RelayCard from "src/app/shared/RelayCard.svelte"
|
||||||
import RelaySearch from "src/app/shared/RelaySearch.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 {getPersonWithFallback} from "src/agent/db"
|
||||||
import cmd from "src/agent/cmd"
|
import cmd from "src/agent/cmd"
|
||||||
import user from "src/agent/user"
|
import user from "src/agent/user"
|
||||||
import {toast, modal} from "src/partials/state"
|
import {toast, modal} from "src/partials/state"
|
||||||
import {publishWithToast} from "src/app/state"
|
import {publishWithToast} from "src/app/state"
|
||||||
|
|
||||||
|
export let quote = null
|
||||||
export let pubkey = null
|
export let pubkey = null
|
||||||
export let nevent = null
|
|
||||||
export let writeTo: string[] | null = null
|
export let writeTo: string[] | null = null
|
||||||
|
|
||||||
let q = ""
|
let q = ""
|
||||||
@ -38,14 +38,23 @@
|
|||||||
)
|
)
|
||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
let {content, mentions, topics} = compose.parse()
|
let content = compose.parse()
|
||||||
|
const tags = []
|
||||||
|
|
||||||
if (image) {
|
if (image) {
|
||||||
content = content + "\n" + 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) {
|
if (content) {
|
||||||
const thunk = cmd.createNote(content.trim(), mentions, topics)
|
const thunk = cmd.createNote(content.trim(), tags)
|
||||||
const [event, promise] = await publishWithToast($relays, thunk)
|
const [event, promise] = await publishWithToast($relays, thunk)
|
||||||
|
|
||||||
promise.then(() =>
|
promise.then(() =>
|
||||||
@ -90,7 +99,9 @@
|
|||||||
compose.mention(getPersonWithFallback(pubkey))
|
compose.mention(getPersonWithFallback(pubkey))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nevent) {
|
if (quote) {
|
||||||
|
const nevent = nip19.neventEncode({id: quote.id, relays: [quote.seen_on]})
|
||||||
|
|
||||||
compose.nevent("nostr:" + nevent)
|
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([
|
await Promise.all([
|
||||||
user.updateRelays(() => user.getRelays()),
|
user.updateRelays(() => user.getRelays()),
|
||||||
cmd.updateUser(profile).publish(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.updatePetnames(() =>
|
||||||
user.getPetnamePubkeys().map(pubkey => {
|
user.getPetnamePubkeys().map(pubkey => {
|
||||||
const [{url}] = sampleRelays(getPubkeyWriteRelays(pubkey))
|
const [{url}] = sampleRelays(getPubkeyWriteRelays(pubkey))
|
||||||
|
@ -203,20 +203,16 @@
|
|||||||
|
|
||||||
export const parse = () => {
|
export const parse = () => {
|
||||||
let {content, annotations} = contenteditable.parse()
|
let {content, annotations} = contenteditable.parse()
|
||||||
const topics = pluck("value", annotations.filter(propEq("prefix", "#")))
|
|
||||||
|
|
||||||
// Remove zero-width and non-breaking spaces
|
// Remove zero-width and non-breaking spaces
|
||||||
content = content.replace(/[\u200B\u00A0]/g, " ").trim()
|
content = content.replace(/[\u200B\u00A0]/g, " ").trim()
|
||||||
|
|
||||||
// We're still using old style mention interpolation until NIP-27
|
// Strip the @ sign in mentions
|
||||||
// gets merged https://github.com/nostr-protocol/nips/pull/381/files
|
annotations.filter(propEq("prefix", "@")).forEach(({value}, index) => {
|
||||||
const mentions = annotations.filter(propEq("prefix", "@")).map(({value}, index) => {
|
content = content.replace("@" + value, value)
|
||||||
content = content.replace("@" + value, `#[${index}]`)
|
|
||||||
|
|
||||||
return pubkeyEncoder.decode(value)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return {content, topics, mentions}
|
return content
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -77,7 +77,9 @@ export class Tags {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const findReplyAndRoot = e => {
|
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)))
|
const legacy = tags.any(t => !["reply", "root"].includes(last(t)))
|
||||||
|
|
||||||
// Support the deprecated version where tags are not marked as replies
|
// Support the deprecated version where tags are not marked as replies
|
||||||
|
Loading…
Reference in New Issue
Block a user