mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-30 00:41:12 +00:00
Add topic support
This commit is contained in:
parent
383fb6e85d
commit
ee66d19822
@ -77,20 +77,20 @@ If you like Coracle and want to support its development, you can donate sats via
|
|||||||
- [x] Still use "my" relays for global, this could make global feed more useful
|
- [x] Still use "my" relays for global, this could make global feed more useful
|
||||||
- [x] If we use my relays for global, we don't have to wait for network to load initially
|
- [x] If we use my relays for global, we don't have to wait for network to load initially
|
||||||
- [x] Figure out fast vs complete tradeoff. Skipping loadContext speeds things up a ton.
|
- [x] Figure out fast vs complete tradeoff. Skipping loadContext speeds things up a ton.
|
||||||
- [ ] Add relays/mentions to note and reply composition
|
- [x] Add relays/mentions to note and reply composition
|
||||||
- [ ] Figure out migrations from previous version
|
- [ ] Figure out migrations from previous version
|
||||||
- [ ] Fix search
|
- [ ] Fix search
|
||||||
- [ ] Move add note to modal
|
|
||||||
|
|
||||||
## 0.2.7
|
## 0.2.7
|
||||||
|
|
||||||
|
- [x] Added error tracking - you can turn this off in settings
|
||||||
- [x] Add support for profile banner images
|
- [x] Add support for profile banner images
|
||||||
- [x] Re-designed relays page
|
- [x] Re-designed relays page
|
||||||
- [x] Support connection status/speed indication
|
- [x] Support connection status/speed indication
|
||||||
- [x] Add toggle to enable writing to a connected relay
|
- [x] Add toggle to enable writing to a connected relay
|
||||||
- [x] Re-designed login page
|
- [x] Re-designed login page
|
||||||
- [x] Use private key login only if extension is not enabled
|
- [x] Use private key login only if extension is not enabled
|
||||||
- [x] Add pubkey login
|
- [x] Add pubkey login support
|
||||||
|
|
||||||
## 0.2.6
|
## 0.2.6
|
||||||
|
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
import {cubicInOut} from "svelte/easing"
|
import {cubicInOut} from "svelte/easing"
|
||||||
import {Router, Route, links, navigate} from "svelte-routing"
|
import {Router, Route, links, navigate} from "svelte-routing"
|
||||||
import {globalHistory} from "svelte-routing/src/history"
|
import {globalHistory} from "svelte-routing/src/history"
|
||||||
import {hasParent} from 'src/util/html'
|
|
||||||
import {displayPerson, isLike} from 'src/util/nostr'
|
import {displayPerson, isLike} from 'src/util/nostr'
|
||||||
import {timedelta, now} from 'src/util/misc'
|
import {timedelta, now} from 'src/util/misc'
|
||||||
import {keys, user, pool, getRelays} from 'src/agent'
|
import {keys, user, pool, getRelays} from 'src/agent'
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {last, objOf, uniq} from 'ramda'
|
import {last, uniqBy, prop, objOf, uniq} from 'ramda'
|
||||||
import {derived, get} from 'svelte/store'
|
import {derived, get} from 'svelte/store'
|
||||||
import {Tags} from 'src/util/nostr'
|
import {Tags} from 'src/util/nostr'
|
||||||
import pool from 'src/agent/pool'
|
import pool from 'src/agent/pool'
|
||||||
@ -52,11 +52,12 @@ export const getRelays = pubkey => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getEventRelays = event => {
|
export const getEventRelays = event => {
|
||||||
return uniq(
|
return uniqBy(
|
||||||
|
prop('url'),
|
||||||
getRelays(event.pubkey)
|
getRelays(event.pubkey)
|
||||||
.concat(Tags.from(event).relays())
|
.concat(Tags.from(event).relays())
|
||||||
.concat(event.seen_on)
|
.concat({url: event.seen_on})
|
||||||
).map(objOf('url'))
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const publish = async (relays, event) => {
|
export const publish = async (relays, event) => {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {prop, join, uniqBy, last} from 'ramda'
|
import {prop, join, uniqBy, last} from 'ramda'
|
||||||
import {get} from 'svelte/store'
|
import {get} from 'svelte/store'
|
||||||
import {first} from "hurdak/lib/hurdak"
|
import {first} from "hurdak/lib/hurdak"
|
||||||
import {Tags} from 'src/util/nostr'
|
import {Tags, isRelay} from 'src/util/nostr'
|
||||||
import {keys, publish, getRelays} from 'src/agent'
|
import {keys, publish, getRelays} from 'src/agent'
|
||||||
|
|
||||||
const updateUser = (relays, updates) =>
|
const updateUser = (relays, updates) =>
|
||||||
@ -25,11 +25,15 @@ const updateRoom = (relays, {id, ...room}) =>
|
|||||||
const createMessage = (relays, roomId, content) =>
|
const createMessage = (relays, roomId, content) =>
|
||||||
publishEvent(relays, 42, {content, tags: [["e", roomId, first(relays), "root"]]})
|
publishEvent(relays, 42, {content, tags: [["e", roomId, first(relays), "root"]]})
|
||||||
|
|
||||||
const createNote = (relays, content, mentions = []) =>
|
const createNote = (relays, content, mentions = [], topics = []) => {
|
||||||
publishEvent(relays, 1, {content, tags: mentions.map(p => ["p", p, first(getRelays(p))])})
|
mentions = mentions.map(p => ["p", p, prop('url', first(getRelays(p)))])
|
||||||
|
topics = topics.map(t => ["t", t])
|
||||||
|
|
||||||
|
publishEvent(relays, 1, {content, tags: mentions.concat(topics)})
|
||||||
|
}
|
||||||
|
|
||||||
const createReaction = (relays, note, content) => {
|
const createReaction = (relays, note, content) => {
|
||||||
const {url} = getBestRelay(note)
|
const {url} = getBestRelay(relays, note)
|
||||||
const tags = uniqBy(
|
const tags = uniqBy(
|
||||||
join(':'),
|
join(':'),
|
||||||
note.tags
|
note.tags
|
||||||
@ -41,17 +45,22 @@ const createReaction = (relays, note, content) => {
|
|||||||
return publishEvent(relays, 7, {content, tags})
|
return publishEvent(relays, 7, {content, tags})
|
||||||
}
|
}
|
||||||
|
|
||||||
const createReply = (relays, note, content, mentions = []) => {
|
const createReply = (relays, note, content, mentions = [], topics = []) => {
|
||||||
const {url} = getBestRelay(note)
|
mentions = mentions.map(p => ["p", p, prop('url', first(getRelays(p)))])
|
||||||
|
topics = topics.map(t => ["t", t])
|
||||||
|
|
||||||
|
const {url} = getBestRelay(relays, note)
|
||||||
const tags = uniqBy(
|
const tags = uniqBy(
|
||||||
join(':'),
|
join(':'),
|
||||||
note.tags
|
note.tags
|
||||||
.filter(t => ["p", "e"].includes(t[0]))
|
.filter(t => ["e"].includes(t[0]))
|
||||||
.map(t => last(t) === 'reply' ? t.slice(0, -1) : t)
|
.map(t => last(t) === 'reply' ? t.slice(0, -1) : t)
|
||||||
.concat([["p", note.pubkey, url], ["e", note.id, url, 'reply']])
|
.concat([["p", note.pubkey, url], ["e", note.id, url, 'reply']])
|
||||||
.concat(mentions.map(p => ["p", p, prop('url', first(getRelays(p)))]))
|
.concat(mentions.concat(topics))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
console.log(relays)
|
||||||
|
|
||||||
return publishEvent(relays, 1, {content, tags})
|
return publishEvent(relays, 1, {content, tags})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,21 +69,22 @@ const deleteEvent = (relays, ids) =>
|
|||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
|
|
||||||
const getBestRelay = event => {
|
const getBestRelay = (relays, event) => {
|
||||||
// Find the best relay, based on reply, root, or pubkey
|
// Find the best relay, based on reply, root, or pubkey. Fall back to a
|
||||||
const reply = Tags.from(event).type("e").mark("reply").first()
|
// relay we're going to send the event to
|
||||||
|
const tags = Tags.from(event).type("e")
|
||||||
|
const reply = tags.mark("reply").values().first()
|
||||||
|
const root = tags.mark("root").values().first()
|
||||||
|
|
||||||
if (reply && reply[2].startsWith('ws')) {
|
if (isRelay(reply)) {
|
||||||
return reply[2]
|
return reply
|
||||||
}
|
}
|
||||||
|
|
||||||
const root = Tags.from(event).type("e").mark("root").first()
|
if (isRelay(root)) {
|
||||||
|
return root
|
||||||
if (root && root[2].startsWith('ws')) {
|
|
||||||
return root[2]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return first(getRelays(event.pubkey))
|
return first(getRelays(event.pubkey).concat(relays))
|
||||||
}
|
}
|
||||||
|
|
||||||
const publishEvent = (relays, kind, {content = '', tags = []} = {}) => {
|
const publishEvent = (relays, kind, {content = '', tags = []} = {}) => {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import {prop, reject, sortBy, last} from 'ramda'
|
import {prop, reject, sortBy, last} from 'ramda'
|
||||||
|
import {fly} from 'svelte/transition'
|
||||||
import {fuzzy} from "src/util/misc"
|
import {fuzzy} from "src/util/misc"
|
||||||
import {fromParentOffset} from "src/util/html"
|
import {fromParentOffset} from "src/util/html"
|
||||||
import Badge from "src/partials/Badge.svelte"
|
import Badge from "src/partials/Badge.svelte"
|
||||||
@ -34,12 +35,11 @@
|
|||||||
return last(getText().split(/[\s\u200B]+/))
|
return last(getText().split(/[\s\u200B]+/))
|
||||||
}
|
}
|
||||||
|
|
||||||
const pickSuggestion = ({name, pubkey}) => {
|
const highlightWord = (prefix, chars, content) => {
|
||||||
const text = getText()
|
const text = getText()
|
||||||
const word = getWord()
|
|
||||||
const selection = document.getSelection()
|
const selection = document.getSelection()
|
||||||
const {focusNode, focusOffset} = selection
|
const {focusNode, focusOffset} = selection
|
||||||
const at = document.createTextNode("@")
|
const prefixElement = document.createTextNode(prefix)
|
||||||
const span = document.createElement('span')
|
const span = document.createElement('span')
|
||||||
|
|
||||||
// Space includes a zero-width space to avoid having the cursor end up inside
|
// Space includes a zero-width space to avoid having the cursor end up inside
|
||||||
@ -47,19 +47,23 @@
|
|||||||
const space = document.createTextNode("\u200B\u00a0")
|
const space = document.createTextNode("\u200B\u00a0")
|
||||||
|
|
||||||
span.classList.add('underline')
|
span.classList.add('underline')
|
||||||
span.innerText = name
|
span.innerText = content
|
||||||
|
|
||||||
// Remove our partial mention text
|
// Remove our partial mention text
|
||||||
selection.setBaseAndExtent(...fromParentOffset(input, text.length - word.length), focusNode, focusOffset)
|
selection.setBaseAndExtent(...fromParentOffset(input, text.length - chars), focusNode, focusOffset)
|
||||||
selection.deleteFromDocument()
|
selection.deleteFromDocument()
|
||||||
|
|
||||||
// Add the at sign, decorated mention text, and a trailing space
|
// Add the prefix, decorated text, and a trailing space
|
||||||
selection.getRangeAt(0).insertNode(at)
|
selection.getRangeAt(0).insertNode(prefixElement)
|
||||||
selection.collapse(at, 1)
|
selection.collapse(prefixElement, 1)
|
||||||
selection.getRangeAt(0).insertNode(span)
|
selection.getRangeAt(0).insertNode(span)
|
||||||
selection.collapse(span.nextSibling, 0)
|
selection.collapse(span.nextSibling, 0)
|
||||||
selection.getRangeAt(0).insertNode(space)
|
selection.getRangeAt(0).insertNode(space)
|
||||||
selection.collapse(space, 2)
|
selection.collapse(space, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
const pickSuggestion = ({name, pubkey}) => {
|
||||||
|
highlightWord('@', getWord().length, name)
|
||||||
|
|
||||||
mentions.push({
|
mentions.push({
|
||||||
name,
|
name,
|
||||||
@ -77,6 +81,12 @@
|
|||||||
return onSubmit()
|
return onSubmit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (e.key === 'Escape' && suggestions[index]) {
|
||||||
|
index = 0
|
||||||
|
suggestions = []
|
||||||
|
e.stopPropagation()
|
||||||
|
}
|
||||||
|
|
||||||
if (['Enter', 'Tab', 'ArrowUp', 'ArrowDown', ' '].includes(e.key) && suggestions[index]) {
|
if (['Enter', 'Tab', 'ArrowUp', 'ArrowDown', ' '].includes(e.key) && suggestions[index]) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
@ -118,10 +128,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.innerText.length > prevContent.length) {
|
||||||
|
const topic = getText().match(/#([-\w]+\s)$/)
|
||||||
|
|
||||||
|
if (topic) {
|
||||||
|
highlightWord('#', topic[0].length, topic[1].trim())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
prevContent = input.innerText
|
prevContent = input.innerText
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export const parse = () => {
|
export const parse = () => {
|
||||||
// Interpolate mentions
|
// Interpolate mentions
|
||||||
@ -140,7 +158,11 @@
|
|||||||
// Remove our zero-length spaces
|
// Remove our zero-length spaces
|
||||||
content = content.replace(/\u200B/g, '')
|
content = content.replace(/\u200B/g, '')
|
||||||
|
|
||||||
return {content, mentions: validMentions.map(prop('pubkey'))}
|
return {
|
||||||
|
content,
|
||||||
|
topics: content.match(/#[-\w]+/g) || [],
|
||||||
|
mentions: validMentions.map(prop('pubkey')),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -154,12 +176,17 @@
|
|||||||
on:keyup={onKeyUp} />
|
on:keyup={onKeyUp} />
|
||||||
<slot name="addon" />
|
<slot name="addon" />
|
||||||
</div>
|
</div>
|
||||||
{#each suggestions as person, i (person.pubkey)}
|
|
||||||
<div
|
{#if suggestions.length > 0}
|
||||||
|
<div class="rounded border border-solid border-medium mt-2" in:fly={{y: 20}}>
|
||||||
|
{#each suggestions as person, i (person.pubkey)}
|
||||||
|
<div
|
||||||
class="py-2 px-4 cursor-pointer"
|
class="py-2 px-4 cursor-pointer"
|
||||||
class:bg-black={index !== i}
|
class:bg-black={index !== i}
|
||||||
class:bg-dark={index === i}
|
class:bg-dark={index === i}
|
||||||
on:click={() => pickSuggestion(person)}>
|
on:click={() => pickSuggestion(person)}>
|
||||||
<Badge inert {person} />
|
<Badge inert {person} />
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/if}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
class="absolute inset-0 opacity-75 bg-black cursor-pointer"
|
class="absolute inset-0 opacity-75 bg-black cursor-pointer"
|
||||||
transition:fade
|
transition:fade
|
||||||
on:click={onEscape} />
|
on:click={onEscape} />
|
||||||
<div class="absolute inset-0 mt-20 sm:mt-32 modal-content" transition:fly={{y: 1000, opacity: 1}}>
|
<div class="absolute inset-0 mt-20 sm:mt-28 modal-content" transition:fly={{y: 1000, opacity: 1}}>
|
||||||
<dialog open class="bg-dark border-t border-solid border-medium h-full w-full overflow-auto">
|
<dialog open class="bg-dark border-t border-solid border-medium h-full w-full overflow-auto">
|
||||||
<slot />
|
<slot />
|
||||||
</dialog>
|
</dialog>
|
||||||
|
@ -2,12 +2,11 @@
|
|||||||
import cx from 'classnames'
|
import cx from 'classnames'
|
||||||
import extractUrls from 'extract-urls'
|
import extractUrls from 'extract-urls'
|
||||||
import {nip19} from 'nostr-tools'
|
import {nip19} from 'nostr-tools'
|
||||||
import {whereEq, pluck, reject, propEq, find} from 'ramda'
|
import {whereEq, uniq, pluck, reject, propEq, find} from 'ramda'
|
||||||
import {slide} from 'svelte/transition'
|
import {slide} from 'svelte/transition'
|
||||||
import {navigate} from 'svelte-routing'
|
import {navigate} from 'svelte-routing'
|
||||||
import {quantify} from 'hurdak/lib/hurdak'
|
import {quantify} from 'hurdak/lib/hurdak'
|
||||||
import {hasParent} from 'src/util/html'
|
import {Tags, findReply, findReplyId, displayPerson, isLike} from "src/util/nostr"
|
||||||
import {findReply, findReplyId, isLike} from "src/util/nostr"
|
|
||||||
import Preview from 'src/partials/Preview.svelte'
|
import Preview from 'src/partials/Preview.svelte'
|
||||||
import Anchor from 'src/partials/Anchor.svelte'
|
import Anchor from 'src/partials/Anchor.svelte'
|
||||||
import {settings, modal, render} from "src/app"
|
import {settings, modal, render} from "src/app"
|
||||||
@ -15,7 +14,7 @@
|
|||||||
import Badge from "src/partials/Badge.svelte"
|
import Badge from "src/partials/Badge.svelte"
|
||||||
import Compose from "src/partials/Compose.svelte"
|
import Compose from "src/partials/Compose.svelte"
|
||||||
import Card from "src/partials/Card.svelte"
|
import Card from "src/partials/Card.svelte"
|
||||||
import {user, people, getRelays, getEventRelays} from 'src/agent'
|
import {user, people, getPerson, getRelays, getEventRelays} from 'src/agent'
|
||||||
import cmd from 'src/app/cmd'
|
import cmd from 'src/app/cmd'
|
||||||
|
|
||||||
export let note
|
export let note
|
||||||
@ -25,6 +24,7 @@
|
|||||||
export let invertColors = false
|
export let invertColors = false
|
||||||
|
|
||||||
let reply = null
|
let reply = null
|
||||||
|
let replyMentions = Tags.from(note).type("p").values().all()
|
||||||
|
|
||||||
const links = $settings.showLinkPreviews ? extractUrls(note.content) || [] : null
|
const links = $settings.showLinkPreviews ? extractUrls(note.content) || [] : null
|
||||||
const showEntire = anchorId === note.id
|
const showEntire = anchorId === note.id
|
||||||
@ -44,7 +44,7 @@
|
|||||||
$: flag = find(whereEq({pubkey: $user?.pubkey}), flags)
|
$: flag = find(whereEq({pubkey: $user?.pubkey}), flags)
|
||||||
|
|
||||||
const onClick = e => {
|
const onClick = e => {
|
||||||
if (!['I'].includes(e.target.tagName) && !hasParent('a', e.target)) {
|
if (!['I'].includes(e.target.tagName) && !e.target.closest('a')) {
|
||||||
modal.set({note, relays})
|
modal.set({note, relays})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -92,26 +92,36 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const removeMention = pubkey => {
|
||||||
|
replyMentions = reject(p => p === pubkey, replyMentions)
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetReply = () => {
|
||||||
|
reply = null
|
||||||
|
replyMentions = Tags.from(note).type("p").values().all()
|
||||||
|
}
|
||||||
|
|
||||||
const sendReply = () => {
|
const sendReply = () => {
|
||||||
const {content, mentions} = reply.parse()
|
let {content, mentions, topics} = reply.parse()
|
||||||
|
|
||||||
if (content) {
|
if (content) {
|
||||||
cmd.createReply(getEventRelays(note), note, content, mentions)
|
mentions = uniq(mentions.concat(replyMentions))
|
||||||
|
cmd.createReply(getEventRelays(note), note, content, mentions, topics)
|
||||||
|
|
||||||
reply = null
|
resetReply()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:body
|
<svelte:body
|
||||||
on:click={e => {
|
on:click={e => {
|
||||||
if (!hasParent('.fa-reply', e.target) && !hasParent('.note-reply', e.target)) {
|
if (!e.target.closest('.fa-reply') && !e.target.closest('.note-reply')) {
|
||||||
reply = null
|
resetReply()
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
on:keydown={e => {
|
on:keydown={e => {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
reply = null
|
resetReply()
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -171,9 +181,8 @@
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{#if reply}
|
{#if reply}
|
||||||
<div
|
<div transition:slide class="note-reply">
|
||||||
class="note-reply bg-medium border-medium border border-solid"
|
<div class="bg-medium border-medium border border-solid">
|
||||||
transition:slide>
|
|
||||||
<Compose bind:this={reply} onSubmit={sendReply}>
|
<Compose bind:this={reply} onSubmit={sendReply}>
|
||||||
<div
|
<div
|
||||||
slot="addon"
|
slot="addon"
|
||||||
@ -183,6 +192,18 @@
|
|||||||
<i class="fa-solid fa-paper-plane fa-xl" />
|
<i class="fa-solid fa-paper-plane fa-xl" />
|
||||||
</div>
|
</div>
|
||||||
</Compose>
|
</Compose>
|
||||||
|
</div>
|
||||||
|
{#if replyMentions.length > 0}
|
||||||
|
<div class="text-white text-sm p-2 rounded-b border-t-0 border border-solid border-medium">
|
||||||
|
{#each replyMentions as p}
|
||||||
|
<div class="inline-block py-1 px-2 mr-1 mb-2 rounded-full border border-solid border-light">
|
||||||
|
<i class="fa fa-times cursor-pointer" on:click|stopPropagation={() => removeMention(p)} />
|
||||||
|
{displayPerson(getPerson(p, true))}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
<div class="-mt-2" />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
return toast.show("error", "That isn't a valid websocket url")
|
return toast.show("error", "That isn't a valid websocket url")
|
||||||
}
|
}
|
||||||
|
|
||||||
addRelay({url, write: '!'})
|
addRelay({url})
|
||||||
modal.set(null)
|
modal.set(null)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
$: search = fuzzy($knownRelays, {keys: ["name", "description", "url"]})
|
$: search = fuzzy($knownRelays, {keys: ["name", "description", "url"]})
|
||||||
|
|
||||||
const join = async url => {
|
const join = async url => {
|
||||||
await addRelay({url, write: "!"})
|
await addRelay({url})
|
||||||
}
|
}
|
||||||
|
|
||||||
const leave = async url => {
|
const leave = async url => {
|
||||||
|
@ -48,22 +48,6 @@ export const stripExifData = async file => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const hasParent = (tagOrClass, $el) => {
|
|
||||||
while ($el) {
|
|
||||||
if (tagOrClass.startsWith('.')) {
|
|
||||||
if ($el.classList?.contains(tagOrClass.slice(1))) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
} else if ($el.tagName === tagOrClass.toUpperCase()) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
$el = $el.parentNode
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
export const escapeHtml = html => {
|
export const escapeHtml = html => {
|
||||||
const div = document.createElement("div")
|
const div = document.createElement("div")
|
||||||
|
|
||||||
|
48
src/views/NoteCreate.svelte
Normal file
48
src/views/NoteCreate.svelte
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<script>
|
||||||
|
import {onMount} from "svelte"
|
||||||
|
import {fly} from 'svelte/transition'
|
||||||
|
import {navigate} from "svelte-routing"
|
||||||
|
import Button from "src/partials/Button.svelte"
|
||||||
|
import Compose from "src/partials/Compose.svelte"
|
||||||
|
import Content from "src/partials/Content.svelte"
|
||||||
|
import Heading from 'src/partials/Heading.svelte'
|
||||||
|
import {user, getRelays} from "src/agent"
|
||||||
|
import {toast} from "src/app"
|
||||||
|
import cmd from "src/app/cmd"
|
||||||
|
|
||||||
|
let input = null
|
||||||
|
|
||||||
|
const onSubmit = async e => {
|
||||||
|
const {content, mentions, topics} = input.parse()
|
||||||
|
|
||||||
|
if (content) {
|
||||||
|
await cmd.createNote(getRelays(), content, mentions, topics)
|
||||||
|
|
||||||
|
toast.show("info", `Your note has been created!`)
|
||||||
|
|
||||||
|
history.back()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (!$user) {
|
||||||
|
navigate("/login")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form on:submit|preventDefault={onSubmit} in:fly={{y: 20}}>
|
||||||
|
<Content size="lg">
|
||||||
|
<Heading class="text-center">Create a note</Heading>
|
||||||
|
<div class="flex flex-col gap-4 w-full">
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<strong>What do you want to say?</strong>
|
||||||
|
<div class="border-l-2 border-solid border-medium pl-4">
|
||||||
|
<Compose bind:this={input} {onSubmit} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button type="submit" class="text-center">Send</Button>
|
||||||
|
</div>
|
||||||
|
</Content>
|
||||||
|
</form>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user