mirror of
https://github.com/coracle-social/flotilla.git
synced 2025-12-11 03:17:02 +00:00
Lint
This commit is contained in:
10
src/app.css
10
src/app.css
@@ -43,11 +43,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card2 {
|
.card2 {
|
||||||
@apply p-6 bg-base-100 text-base-content rounded-box;
|
@apply rounded-box bg-base-100 p-6 text-base-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card2.card2-sm {
|
.card2.card2-sm {
|
||||||
@apply p-4 bg-base-100 text-base-content rounded-box;
|
@apply rounded-box bg-base-100 p-4 text-base-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card2.card2-alt {
|
.card2.card2-alt {
|
||||||
@@ -93,11 +93,11 @@
|
|||||||
/* tiptap */
|
/* tiptap */
|
||||||
|
|
||||||
.tiptap {
|
.tiptap {
|
||||||
@apply rounded-box bg-base-300 px-4 p-2 max-h-[350px] overflow-y-auto;
|
@apply max-h-[350px] overflow-y-auto rounded-box bg-base-300 p-2 px-4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tiptap pre code {
|
.tiptap pre code {
|
||||||
@apply link-content w-full block;
|
@apply link-content block w-full;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tiptap p code {
|
.tiptap p code {
|
||||||
@@ -105,7 +105,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.link-content {
|
.link-content {
|
||||||
@apply text-sm rounded px-1 bg-neutral text-neutral-content no-underline;
|
@apply rounded bg-neutral px-1 text-sm text-neutral-content no-underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-content.link-content-selected {
|
.link-content.link-content-selected {
|
||||||
|
|||||||
@@ -2,21 +2,15 @@ import {uniqBy, uniq, now, choice} from "@welshman/lib"
|
|||||||
import {
|
import {
|
||||||
GROUPS,
|
GROUPS,
|
||||||
GROUP_JOIN,
|
GROUP_JOIN,
|
||||||
asDecryptedEvent,
|
|
||||||
getGroupTags,
|
getGroupTags,
|
||||||
getRelayTagValues,
|
getRelayTagValues,
|
||||||
readList,
|
|
||||||
editList,
|
|
||||||
makeList,
|
|
||||||
createList,
|
|
||||||
createEvent,
|
createEvent,
|
||||||
displayProfile,
|
displayProfile,
|
||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import {PublishStatus} from "@welshman/net"
|
import {PublishStatus} from "@welshman/net"
|
||||||
import {pk, signer, repository, INDEXER_RELAYS} from "@app/base"
|
import {pk, repository, INDEXER_RELAYS} from "@app/base"
|
||||||
import {
|
import {
|
||||||
loadOne,
|
loadOne,
|
||||||
subscribe,
|
|
||||||
getWriteRelayUrls,
|
getWriteRelayUrls,
|
||||||
loadGroup,
|
loadGroup,
|
||||||
loadGroupMembership,
|
loadGroupMembership,
|
||||||
@@ -27,7 +21,6 @@ import {
|
|||||||
loadRelaySelections,
|
loadRelaySelections,
|
||||||
makeThunk,
|
makeThunk,
|
||||||
publishThunk,
|
publishThunk,
|
||||||
ensurePlaintext,
|
|
||||||
getProfilesByPubkey,
|
getProfilesByPubkey,
|
||||||
} from "@app/state"
|
} from "@app/state"
|
||||||
|
|
||||||
@@ -48,11 +41,18 @@ export const getPubkeyPetname = (pubkey: string) => {
|
|||||||
return display
|
return display
|
||||||
}
|
}
|
||||||
|
|
||||||
export const makeMention = (pubkey: string, hints?: string[]) =>
|
export const makeMention = (pubkey: string, hints?: string[]) => [
|
||||||
["p", pubkey, choice(hints || getPubkeyHints(pubkey)), getPubkeyPetname(pubkey)]
|
"p",
|
||||||
|
pubkey,
|
||||||
|
choice(hints || getPubkeyHints(pubkey)),
|
||||||
|
getPubkeyPetname(pubkey),
|
||||||
|
]
|
||||||
|
|
||||||
export const makeIMeta = (url: string, data: Record<string, string>) =>
|
export const makeIMeta = (url: string, data: Record<string, string>) => [
|
||||||
["imeta", `url ${url}`, ...Object.entries(data).map(([k, v]) => [k, v].join(' '))]
|
"imeta",
|
||||||
|
`url ${url}`,
|
||||||
|
...Object.entries(data).map(([k, v]) => [k, v].join(" ")),
|
||||||
|
]
|
||||||
|
|
||||||
// Loaders
|
// Loaders
|
||||||
|
|
||||||
@@ -83,7 +83,6 @@ export type ModifyTags = (tags: string[][]) => string[][]
|
|||||||
|
|
||||||
export const updateList = async (kind: number, modifyTags: ModifyTags) => {
|
export const updateList = async (kind: number, modifyTags: ModifyTags) => {
|
||||||
const $pk = pk.get()!
|
const $pk = pk.get()!
|
||||||
const $signer = signer.get()!
|
|
||||||
const [prev] = repository.query([{kinds: [kind], authors: [$pk]}])
|
const [prev] = repository.query([{kinds: [kind], authors: [$pk]}])
|
||||||
const relays = getWriteRelayUrls(getRelaySelectionsByPubkey().get($pk))
|
const relays = getWriteRelayUrls(getRelaySelectionsByPubkey().get($pk))
|
||||||
|
|
||||||
@@ -103,15 +102,18 @@ export const removeGroupMemberships = (noms: string[]) =>
|
|||||||
|
|
||||||
export const sendJoinRequest = async (nom: string, url: string): Promise<[boolean, string]> => {
|
export const sendJoinRequest = async (nom: string, url: string): Promise<[boolean, string]> => {
|
||||||
const relays = [url]
|
const relays = [url]
|
||||||
const filters = [{kinds: [9000], '#h': [nom], '#p': [pk.get()!], since: now() - 30}]
|
const filters = [{kinds: [9000], "#h": [nom], "#p": [pk.get()!], since: now() - 30}]
|
||||||
|
|
||||||
const event = createEvent(GROUP_JOIN, {tags: [["h", nom]]})
|
const event = createEvent(GROUP_JOIN, {tags: [["h", nom]]})
|
||||||
const statusData = await publishThunk(makeThunk({event, relays}))
|
const statusData = await publishThunk(makeThunk({event, relays}))
|
||||||
const {status, message} = statusData[url]
|
const {status, message} = statusData[url]
|
||||||
|
|
||||||
if (message.includes('already a member')) return [true, ""]
|
if (message.includes("already a member")) return [true, ""]
|
||||||
if (status !== PublishStatus.Success) return [false, message]
|
if (status !== PublishStatus.Success) return [false, message]
|
||||||
if (await loadOne({filters, relays})) return [true, ""]
|
if (await loadOne({filters, relays})) return [true, ""]
|
||||||
|
|
||||||
return [false, "Your request was not automatically approved, but may be approved manually later. Please try again later or contact the group admin."]
|
return [
|
||||||
|
false,
|
||||||
|
"Your request was not automatically approved, but may be approved manually later. Please try again later or contact the group admin.",
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +1,57 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {onMount} from 'svelte'
|
import {onMount} from "svelte"
|
||||||
import type {Readable} from 'svelte/store'
|
import type {Readable} from "svelte/store"
|
||||||
import {nprofileEncode} from 'nostr-tools/nip19'
|
import {nprofileEncode} from "nostr-tools/nip19"
|
||||||
import {createEditor, type Editor, EditorContent, SvelteNodeViewRenderer} from 'svelte-tiptap'
|
import {createEditor, type Editor, EditorContent, SvelteNodeViewRenderer} from "svelte-tiptap"
|
||||||
import {Extension} from '@tiptap/core'
|
import Code from "@tiptap/extension-code"
|
||||||
import Code from '@tiptap/extension-code'
|
import CodeBlock from "@tiptap/extension-code-block"
|
||||||
import CodeBlock from '@tiptap/extension-code-block'
|
import Document from "@tiptap/extension-document"
|
||||||
import Document from '@tiptap/extension-document'
|
import Dropcursor from "@tiptap/extension-dropcursor"
|
||||||
import Dropcursor from '@tiptap/extension-dropcursor'
|
import Gapcursor from "@tiptap/extension-gapcursor"
|
||||||
import Gapcursor from '@tiptap/extension-gapcursor'
|
import History from "@tiptap/extension-history"
|
||||||
import History from '@tiptap/extension-history'
|
import Paragraph from "@tiptap/extension-paragraph"
|
||||||
import Paragraph from '@tiptap/extension-paragraph'
|
import Text from "@tiptap/extension-text"
|
||||||
import Text from '@tiptap/extension-text'
|
import HardBreakExtension from "@tiptap/extension-hard-break"
|
||||||
import HardBreakExtension from '@tiptap/extension-hard-break'
|
import {
|
||||||
import {Bolt11Extension, NProfileExtension, NEventExtension, NAddrExtension, ImageExtension, VideoExtension, FileUploadExtension} from 'nostr-editor'
|
Bolt11Extension,
|
||||||
import type {StampedEvent} from '@welshman/util'
|
NProfileExtension,
|
||||||
import {createEvent, CHAT_MESSAGE} from '@welshman/util'
|
NEventExtension,
|
||||||
import {LinkExtension, TopicExtension, createSuggestions, findNodes} from '@lib/tiptap'
|
NAddrExtension,
|
||||||
import Icon from '@lib/components/Icon.svelte'
|
ImageExtension,
|
||||||
import Button from '@lib/components/Button.svelte'
|
VideoExtension,
|
||||||
import GroupComposeMention from '@app/components/GroupComposeMention.svelte'
|
FileUploadExtension,
|
||||||
import GroupComposeTopic from '@app/components/GroupComposeTopic.svelte'
|
} from "nostr-editor"
|
||||||
import GroupComposeEvent from '@app/components/GroupComposeEvent.svelte'
|
import type {StampedEvent} from "@welshman/util"
|
||||||
import GroupComposeImage from '@app/components/GroupComposeImage.svelte'
|
import {createEvent, CHAT_MESSAGE} from "@welshman/util"
|
||||||
import GroupComposeBolt11 from '@app/components/GroupComposeBolt11.svelte'
|
import {LinkExtension, TopicExtension, createSuggestions, findNodes} from "@lib/tiptap"
|
||||||
import GroupComposeVideo from '@app/components/GroupComposeVideo.svelte'
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import GroupComposeLink from '@app/components/GroupComposeLink.svelte'
|
import Button from "@lib/components/Button.svelte"
|
||||||
import GroupComposeSuggestions from '@app/components/GroupComposeSuggestions.svelte'
|
import GroupComposeMention from "@app/components/GroupComposeMention.svelte"
|
||||||
import GroupComposeTopicSuggestion from '@app/components/GroupComposeTopicSuggestion.svelte'
|
import GroupComposeTopic from "@app/components/GroupComposeTopic.svelte"
|
||||||
import GroupComposeProfileSuggestion from '@app/components/GroupComposeProfileSuggestion.svelte'
|
import GroupComposeEvent from "@app/components/GroupComposeEvent.svelte"
|
||||||
import {signer, INDEXER_RELAYS} from '@app/base'
|
import GroupComposeImage from "@app/components/GroupComposeImage.svelte"
|
||||||
import {searchProfiles, publishThunk, makeThunk, searchTopics, userRelayUrlsByNom, getWriteRelayUrls, displayProfileByPubkey, getRelaySelectionsByPubkey} from '@app/state'
|
import GroupComposeBolt11 from "@app/components/GroupComposeBolt11.svelte"
|
||||||
import {getPubkeyHints, makeMention, makeIMeta} from '@app/commands'
|
import GroupComposeVideo from "@app/components/GroupComposeVideo.svelte"
|
||||||
|
import GroupComposeLink from "@app/components/GroupComposeLink.svelte"
|
||||||
|
import GroupComposeSuggestions from "@app/components/GroupComposeSuggestions.svelte"
|
||||||
|
import GroupComposeTopicSuggestion from "@app/components/GroupComposeTopicSuggestion.svelte"
|
||||||
|
import GroupComposeProfileSuggestion from "@app/components/GroupComposeProfileSuggestion.svelte"
|
||||||
|
import {signer} from "@app/base"
|
||||||
|
import {
|
||||||
|
searchProfiles,
|
||||||
|
publishThunk,
|
||||||
|
makeThunk,
|
||||||
|
searchTopics,
|
||||||
|
userRelayUrlsByNom,
|
||||||
|
} from "@app/state"
|
||||||
|
import {getPubkeyHints, makeMention, makeIMeta} from "@app/commands"
|
||||||
|
|
||||||
export let nom
|
export let nom
|
||||||
|
|
||||||
let editor: Readable<Editor>
|
let editor: Readable<Editor>
|
||||||
let uploading = false
|
let uploading = false
|
||||||
|
|
||||||
const asInline = (extend: Record<string, any>) =>
|
const asInline = (extend: Record<string, any>) => ({inline: true, group: "inline", ...extend})
|
||||||
({inline: true, group: 'inline', ...extend})
|
|
||||||
|
|
||||||
const addFile = () => $editor.chain().selectFiles().run()
|
const addFile = () => $editor.chain().selectFiles().run()
|
||||||
|
|
||||||
@@ -53,8 +65,12 @@
|
|||||||
tags: [
|
tags: [
|
||||||
["h", nom],
|
["h", nom],
|
||||||
...findNodes(TopicExtension.name, json).map(t => ["t", t.attrs!.name.toLowerCase()]),
|
...findNodes(TopicExtension.name, json).map(t => ["t", t.attrs!.name.toLowerCase()]),
|
||||||
...findNodes(NProfileExtension.name, json).map(m => makeMention(m.attrs!.pubkey, m.attrs!.relays)),
|
...findNodes(NProfileExtension.name, json).map(m =>
|
||||||
...findNodes(ImageExtension.name, json).map(({attrs: {src, sha256: x}}: any) => makeIMeta(src, {x, ox: x})),
|
makeMention(m.attrs!.pubkey, m.attrs!.relays),
|
||||||
|
),
|
||||||
|
...findNodes(ImageExtension.name, json).map(({attrs: {src, sha256: x}}: any) =>
|
||||||
|
makeIMeta(src, {x, ox: x}),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -78,9 +94,9 @@
|
|||||||
HardBreakExtension.extend({
|
HardBreakExtension.extend({
|
||||||
addKeyboardShortcuts() {
|
addKeyboardShortcuts() {
|
||||||
return {
|
return {
|
||||||
'Shift-Enter': () => this.editor.commands.setHardBreak(),
|
"Shift-Enter": () => this.editor.commands.setHardBreak(),
|
||||||
'Mod-Enter': () => this.editor.commands.setHardBreak(),
|
"Mod-Enter": () => this.editor.commands.setHardBreak(),
|
||||||
'Enter': () => {
|
Enter: () => {
|
||||||
if (this.editor.getText().trim()) {
|
if (this.editor.getText().trim()) {
|
||||||
uploadFiles()
|
uploadFiles()
|
||||||
|
|
||||||
@@ -90,19 +106,21 @@
|
|||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
LinkExtension.extend({
|
LinkExtension.extend({
|
||||||
addNodeView: () => SvelteNodeViewRenderer(GroupComposeLink),
|
addNodeView: () => SvelteNodeViewRenderer(GroupComposeLink),
|
||||||
}),
|
}),
|
||||||
Bolt11Extension.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(GroupComposeBolt11)})),
|
Bolt11Extension.extend(
|
||||||
|
asInline({addNodeView: () => SvelteNodeViewRenderer(GroupComposeBolt11)}),
|
||||||
|
),
|
||||||
NProfileExtension.extend({
|
NProfileExtension.extend({
|
||||||
addNodeView: () => SvelteNodeViewRenderer(GroupComposeMention),
|
addNodeView: () => SvelteNodeViewRenderer(GroupComposeMention),
|
||||||
addProseMirrorPlugins() {
|
addProseMirrorPlugins() {
|
||||||
return [
|
return [
|
||||||
createSuggestions({
|
createSuggestions({
|
||||||
char: '@',
|
char: "@",
|
||||||
name: 'nprofile',
|
name: "nprofile",
|
||||||
editor: this.editor,
|
editor: this.editor,
|
||||||
search: searchProfiles,
|
search: searchProfiles,
|
||||||
select: (pubkey: string, props: any) => {
|
select: (pubkey: string, props: any) => {
|
||||||
@@ -115,23 +133,27 @@
|
|||||||
suggestionsComponent: GroupComposeSuggestions,
|
suggestionsComponent: GroupComposeSuggestions,
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
NEventExtension.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(GroupComposeEvent)})),
|
NEventExtension.extend(
|
||||||
NAddrExtension.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(GroupComposeEvent)})),
|
asInline({addNodeView: () => SvelteNodeViewRenderer(GroupComposeEvent)}),
|
||||||
ImageExtension
|
),
|
||||||
.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(GroupComposeImage)}))
|
NAddrExtension.extend(
|
||||||
.configure({defaultUploadUrl: 'https://nostr.build', defaultUploadType: 'nip96'}),
|
asInline({addNodeView: () => SvelteNodeViewRenderer(GroupComposeEvent)}),
|
||||||
VideoExtension
|
),
|
||||||
.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(GroupComposeVideo)}))
|
ImageExtension.extend(
|
||||||
.configure({defaultUploadUrl: 'https://nostr.build', defaultUploadType: 'nip96'}),
|
asInline({addNodeView: () => SvelteNodeViewRenderer(GroupComposeImage)}),
|
||||||
|
).configure({defaultUploadUrl: "https://nostr.build", defaultUploadType: "nip96"}),
|
||||||
|
VideoExtension.extend(
|
||||||
|
asInline({addNodeView: () => SvelteNodeViewRenderer(GroupComposeVideo)}),
|
||||||
|
).configure({defaultUploadUrl: "https://nostr.build", defaultUploadType: "nip96"}),
|
||||||
TopicExtension.extend({
|
TopicExtension.extend({
|
||||||
addNodeView: () => SvelteNodeViewRenderer(GroupComposeTopic),
|
addNodeView: () => SvelteNodeViewRenderer(GroupComposeTopic),
|
||||||
addProseMirrorPlugins() {
|
addProseMirrorPlugins() {
|
||||||
return [
|
return [
|
||||||
createSuggestions({
|
createSuggestions({
|
||||||
char: '#',
|
char: "#",
|
||||||
name: 'topic',
|
name: "topic",
|
||||||
editor: this.editor,
|
editor: this.editor,
|
||||||
search: searchTopics,
|
search: searchTopics,
|
||||||
select: (name: string, props: any) => props.command({name}),
|
select: (name: string, props: any) => props.command({name}),
|
||||||
@@ -155,13 +177,16 @@
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
content: '',
|
content: "",
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex gap-2 relative z-feature border-t border-solid border-base-100 p-2 shadow-top-xl bg-base-100">
|
<div
|
||||||
<Button on:click={addFile} class="bg-base-300 rounded-box w-10 h-10 center hover:bg-base-200 transition-colors">
|
class="shadow-top-xl relative z-feature flex gap-2 border-t border-solid border-base-100 bg-base-100 p-2">
|
||||||
|
<Button
|
||||||
|
on:click={addFile}
|
||||||
|
class="center h-10 w-10 rounded-box bg-base-300 transition-colors hover:bg-base-200">
|
||||||
{#if uploading}
|
{#if uploading}
|
||||||
<span class="loading loading-spinner loading-xs"></span>
|
<span class="loading loading-spinner loading-xs"></span>
|
||||||
{:else}
|
{:else}
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import cx from 'classnames'
|
import cx from "classnames"
|
||||||
import type {NodeViewProps} from '@tiptap/core'
|
import type {NodeViewProps} from "@tiptap/core"
|
||||||
import {NodeViewWrapper} from 'svelte-tiptap'
|
import {NodeViewWrapper} from "svelte-tiptap"
|
||||||
import Icon from '@lib/components/Icon.svelte'
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from '@lib/components/Button.svelte'
|
import Button from "@lib/components/Button.svelte"
|
||||||
import {clip} from '@app/toast'
|
import {clip} from "@app/toast"
|
||||||
|
|
||||||
export let node: NodeViewProps['node']
|
export let node: NodeViewProps["node"]
|
||||||
export let selected: NodeViewProps['selected']
|
export let selected: NodeViewProps["selected"]
|
||||||
|
|
||||||
const copy = () => clip(node.attrs.lnbc)
|
const copy = () => clip(node.attrs.lnbc)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<NodeViewWrapper class="inline">
|
<NodeViewWrapper class="inline">
|
||||||
<Button on:click={copy} class={cx("link-content", {'link-content-selected': selected})}>
|
<Button on:click={copy} class={cx("link-content", {"link-content-selected": selected})}>
|
||||||
<Icon icon="bolt" size={3} class="inline-block translate-y-px" />
|
<Icon icon="bolt" size={3} class="inline-block translate-y-px" />
|
||||||
{node.attrs.lnbc.slice(0, 16)}...
|
{node.attrs.lnbc.slice(0, 16)}...
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type {NodeViewProps} from '@tiptap/core'
|
import type {NodeViewProps} from "@tiptap/core"
|
||||||
import {NodeViewWrapper} from 'svelte-tiptap'
|
import {NodeViewWrapper} from "svelte-tiptap"
|
||||||
import {ellipsize} from '@welshman/lib'
|
import {ellipsize} from "@welshman/lib"
|
||||||
import {type TrustedEvent, fromNostrURI, Address} from '@welshman/util'
|
import {type TrustedEvent, fromNostrURI, Address} from "@welshman/util"
|
||||||
import Link from '@lib/components/Link.svelte'
|
import Link from "@lib/components/Link.svelte"
|
||||||
import {deriveEvent} from '@app/state'
|
import {deriveEvent} from "@app/state"
|
||||||
|
|
||||||
export let node: NodeViewProps['node']
|
export let node: NodeViewProps["node"]
|
||||||
|
|
||||||
const displayEvent = (e: TrustedEvent) =>
|
const displayEvent = (e: TrustedEvent) =>
|
||||||
e?.content.length > 1 ? ellipsize(e.content, 50) : fromNostrURI(nevent || naddr).slice(0, 16) + '...'
|
e?.content.length > 1
|
||||||
|
? ellipsize(e.content, 50)
|
||||||
|
: fromNostrURI(nevent || naddr).slice(0, 16) + "..."
|
||||||
|
|
||||||
$: ({identifier, pubkey, kind, id, relays = [], nevent, naddr} = node.attrs)
|
$: ({identifier, pubkey, kind, id, relays = [], nevent, naddr} = node.attrs)
|
||||||
$: event = deriveEvent(id || new Address(kind, pubkey, identifier).toString(), relays)
|
$: event = deriveEvent(id || new Address(kind, pubkey, identifier).toString(), relays)
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import cx from 'classnames'
|
import cx from "classnames"
|
||||||
import type {NodeViewProps} from '@tiptap/core'
|
import type {NodeViewProps} from "@tiptap/core"
|
||||||
import {NodeViewWrapper} from 'svelte-tiptap'
|
import {NodeViewWrapper} from "svelte-tiptap"
|
||||||
import Icon from '@lib/components/Icon.svelte'
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
|
|
||||||
export let node: NodeViewProps['node']
|
export let node: NodeViewProps["node"]
|
||||||
export let selected: NodeViewProps['selected']
|
export let selected: NodeViewProps["selected"]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<NodeViewWrapper class={cx("inline link-content", {'link-content-selected': selected})}>
|
<NodeViewWrapper class={cx("link-content inline", {"link-content-selected": selected})}>
|
||||||
<Icon icon="paperclip" size={3} class="inline-block translate-y-px" />
|
<Icon icon="paperclip" size={3} class="inline-block translate-y-px" />
|
||||||
{node.attrs.file.name}
|
{node.attrs.file.name}
|
||||||
</NodeViewWrapper>
|
</NodeViewWrapper>
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import cx from 'classnames'
|
import cx from "classnames"
|
||||||
import type {NodeViewProps} from '@tiptap/core'
|
import type {NodeViewProps} from "@tiptap/core"
|
||||||
import {NodeViewWrapper} from 'svelte-tiptap'
|
import {NodeViewWrapper} from "svelte-tiptap"
|
||||||
import {displayUrl} from '@welshman/lib'
|
import {displayUrl} from "@welshman/lib"
|
||||||
import Icon from '@lib/components/Icon.svelte'
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Link from '@lib/components/Link.svelte'
|
import Link from "@lib/components/Link.svelte"
|
||||||
|
|
||||||
export let node: NodeViewProps['node']
|
export let node: NodeViewProps["node"]
|
||||||
export let selected: NodeViewProps['selected']
|
export let selected: NodeViewProps["selected"]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<NodeViewWrapper class="inline">
|
<NodeViewWrapper class="inline">
|
||||||
<Link external href={node.attrs.url} class={cx("link-content", {'link-content-selected': selected})}>
|
<Link
|
||||||
|
external
|
||||||
|
href={node.attrs.url}
|
||||||
|
class={cx("link-content", {"link-content-selected": selected})}>
|
||||||
<Icon icon="link-round" size={3} class="inline-block translate-y-px" />
|
<Icon icon="link-round" size={3} class="inline-block translate-y-px" />
|
||||||
{displayUrl(node.attrs.url)}
|
{displayUrl(node.attrs.url)}
|
||||||
</Link>
|
</Link>
|
||||||
</NodeViewWrapper>
|
</NodeViewWrapper>
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import cx from 'classnames'
|
import cx from "classnames"
|
||||||
import type {NodeViewProps} from '@tiptap/core'
|
import type {NodeViewProps} from "@tiptap/core"
|
||||||
import {NodeViewWrapper} from 'svelte-tiptap'
|
import {NodeViewWrapper} from "svelte-tiptap"
|
||||||
import {displayProfile} from '@welshman/util'
|
import {displayProfile} from "@welshman/util"
|
||||||
import Link from '@lib/components/Link.svelte'
|
import Link from "@lib/components/Link.svelte"
|
||||||
import {deriveProfile} from '@app/state'
|
import {deriveProfile} from "@app/state"
|
||||||
|
|
||||||
export let node: NodeViewProps['node']
|
export let node: NodeViewProps["node"]
|
||||||
export let selected: NodeViewProps['selected']
|
export let selected: NodeViewProps["selected"]
|
||||||
|
|
||||||
$: profile = deriveProfile(node.attrs.pubkey, node.attrs.relays)
|
$: profile = deriveProfile(node.attrs.pubkey, node.attrs.relays)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<NodeViewWrapper class="inline">
|
<NodeViewWrapper class="inline">
|
||||||
<Link external href="https://njump.me/{node.attrs.nprofile}" class={cx("link-content", {'link-content-selected': selected})}>
|
<Link
|
||||||
|
external
|
||||||
|
href="https://njump.me/{node.attrs.nprofile}"
|
||||||
|
class={cx("link-content", {"link-content-selected": selected})}>
|
||||||
@{displayProfile($profile)}
|
@{displayProfile($profile)}
|
||||||
</Link>
|
</Link>
|
||||||
</NodeViewWrapper>
|
</NodeViewWrapper>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {deriveProfileDisplay} from '@app/state'
|
import {deriveProfileDisplay} from "@app/state"
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import {throttle} from "throttle-debounce"
|
import {throttle} from "throttle-debounce"
|
||||||
import {slide} from "svelte/transition"
|
import {slide} from "svelte/transition"
|
||||||
import {clamp} from "@welshman/lib"
|
import {clamp} from "@welshman/lib"
|
||||||
import {theme} from '@app/theme'
|
import {theme} from "@app/theme"
|
||||||
|
|
||||||
export let term
|
export let term
|
||||||
export let search
|
export let search
|
||||||
@@ -23,13 +23,13 @@
|
|||||||
items = $search.searchValues(term).slice(0, 30)
|
items = $search.searchValues(term).slice(0, 30)
|
||||||
})
|
})
|
||||||
|
|
||||||
const setIndex = (newIndex: number, block: ScrollLogicalPosition) => {
|
const setIndex = (newIndex: number, block: any) => {
|
||||||
index = clamp([0, items.length - 1], newIndex)
|
index = clamp([0, items.length - 1], newIndex)
|
||||||
element.querySelector(`button:nth-child(${index})`)?.scrollIntoView({block})
|
element.querySelector(`button:nth-child(${index})`)?.scrollIntoView({block})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const onKeyDown = (e: any) => {
|
export const onKeyDown = (e: any) => {
|
||||||
if (['Enter', 'Tab'].includes(e.code)) {
|
if (["Enter", "Tab"].includes(e.code)) {
|
||||||
const value = items[index]
|
const value = items[index]
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.code === 'Space' && term && allowCreate) {
|
if (e.code === "Space" && term && allowCreate) {
|
||||||
select(term)
|
select(term)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
class="mt-2 flex max-h-[350px] flex-col overflow-y-auto overflow-x-hidden shadow-xl">
|
class="mt-2 flex max-h-[350px] flex-col overflow-y-auto overflow-x-hidden shadow-xl">
|
||||||
{#if term && allowCreate}
|
{#if term && allowCreate}
|
||||||
<button
|
<button
|
||||||
class="cursor-pointer px-4 py-2 text-left hover:bg-primary hover:text-primary-content transition-colors white-space-nowrap overflow-hidden text-ellipsis min-w-0"
|
class="white-space-nowrap min-w-0 cursor-pointer overflow-hidden text-ellipsis px-4 py-2 text-left transition-colors hover:bg-primary hover:text-primary-content"
|
||||||
on:mousedown|preventDefault
|
on:mousedown|preventDefault
|
||||||
on:click|preventDefault={() => select(term)}>
|
on:click|preventDefault={() => select(term)}>
|
||||||
Use "<svelte:component this={component} value={term} />"
|
Use "<svelte:component this={component} value={term} />"
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#each items as value, i (value)}
|
{#each items as value, i (value)}
|
||||||
<button
|
<button
|
||||||
class="cursor-pointer px-4 py-2 text-left hover:bg-primary hover:text-primary-content transition-colors white-space-nowrap overflow-hidden text-ellipsis min-w-0"
|
class="white-space-nowrap min-w-0 cursor-pointer overflow-hidden text-ellipsis px-4 py-2 text-left transition-colors hover:bg-primary hover:text-primary-content"
|
||||||
class:bg-primary={index === i}
|
class:bg-primary={index === i}
|
||||||
class:text-primary-content={index === i}
|
class:text-primary-content={index === i}
|
||||||
on:mousedown|preventDefault
|
on:mousedown|preventDefault
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<div transition:slide|local class="flex gap-2 bg-tinted-700 px-4 py-2 text-neutral-200">
|
<div transition:slide|local class="bg-tinted-700 flex gap-2 px-4 py-2 text-neutral-200">
|
||||||
<div>
|
<div>
|
||||||
<i class="fa fa-circle-notch fa-spin" />
|
<i class="fa fa-circle-notch fa-spin" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import cx from 'classnames'
|
import cx from "classnames"
|
||||||
import type {NodeViewProps} from '@tiptap/core'
|
import type {NodeViewProps} from "@tiptap/core"
|
||||||
import {NodeViewWrapper} from 'svelte-tiptap'
|
import {NodeViewWrapper} from "svelte-tiptap"
|
||||||
import Link from '@lib/components/Link.svelte'
|
import Link from "@lib/components/Link.svelte"
|
||||||
|
|
||||||
export let node: NodeViewProps['node']
|
export let node: NodeViewProps["node"]
|
||||||
export let selected: NodeViewProps['selected']
|
export let selected: NodeViewProps["selected"]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<NodeViewWrapper class="inline">
|
<NodeViewWrapper class="inline">
|
||||||
<Link external href="https://coracle.social/topics/{node.attrs.name.toLowerCase()}" class={cx("link-content", {'link-content-selected': selected})}>
|
<Link
|
||||||
|
external
|
||||||
|
href="https://coracle.social/topics/{node.attrs.name.toLowerCase()}"
|
||||||
|
class={cx("link-content", {"link-content-selected": selected})}>
|
||||||
#{node.attrs.name}
|
#{node.attrs.name}
|
||||||
</Link>
|
</Link>
|
||||||
</NodeViewWrapper>
|
</NodeViewWrapper>
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import cx from 'classnames'
|
import type {NodeViewProps} from "@tiptap/core"
|
||||||
import type {NodeViewProps} from '@tiptap/core'
|
import {NodeViewWrapper} from "svelte-tiptap"
|
||||||
import {NodeViewWrapper} from 'svelte-tiptap'
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Icon from '@lib/components/Icon.svelte'
|
|
||||||
|
|
||||||
export let node: NodeViewProps['node']
|
export let node: NodeViewProps["node"]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<NodeViewWrapper class="inline link-content">
|
<NodeViewWrapper class="link-content inline">
|
||||||
<Icon icon="paperclip" size={3} class="inline-block translate-y-px" />
|
<Icon icon="paperclip" size={3} class="inline-block translate-y-px" />
|
||||||
{node.attrs.file.name}
|
{node.attrs.file.name}
|
||||||
</NodeViewWrapper>
|
</NodeViewWrapper>
|
||||||
|
|||||||
@@ -5,13 +5,19 @@
|
|||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {deriveEvents} from "@welshman/store"
|
import {deriveEvents} from "@welshman/store"
|
||||||
import {PublishStatus} from "@welshman/net"
|
import {PublishStatus} from "@welshman/net"
|
||||||
import {GROUP_REPLY, REACTION, ZAP_RESPONSE, displayRelayUrl, getAncestorTags, displayPubkey} from "@welshman/util"
|
import {
|
||||||
import {fly, fade} from "@lib/transition"
|
GROUP_REPLY,
|
||||||
import {formatTimestampAsTime} from '@lib/util'
|
REACTION,
|
||||||
|
ZAP_RESPONSE,
|
||||||
|
displayRelayUrl,
|
||||||
|
getAncestorTags,
|
||||||
|
} from "@welshman/util"
|
||||||
|
import {fly} from "@lib/transition"
|
||||||
|
import {formatTimestampAsTime} from "@lib/util"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Avatar from "@lib/components/Avatar.svelte"
|
import Avatar from "@lib/components/Avatar.svelte"
|
||||||
import {repository} from '@app/base'
|
import {repository} from "@app/base"
|
||||||
import type {PublishStatusData} from "@app/state"
|
import type {PublishStatusData} from "@app/state"
|
||||||
import {deriveProfile, deriveProfileDisplay, deriveEvent, publishStatusData} from "@app/state"
|
import {deriveProfile, deriveProfileDisplay, deriveEvent, publishStatusData} from "@app/state"
|
||||||
|
|
||||||
@@ -19,31 +25,31 @@
|
|||||||
export let showPubkey: boolean
|
export let showPubkey: boolean
|
||||||
|
|
||||||
const colors = [
|
const colors = [
|
||||||
['amber', twColors.amber[600]],
|
["amber", twColors.amber[600]],
|
||||||
['blue', twColors.blue[600]],
|
["blue", twColors.blue[600]],
|
||||||
['cyan', twColors.cyan[600]],
|
["cyan", twColors.cyan[600]],
|
||||||
['emerald', twColors.emerald[600]],
|
["emerald", twColors.emerald[600]],
|
||||||
['fuchsia', twColors.fuchsia[600]],
|
["fuchsia", twColors.fuchsia[600]],
|
||||||
['green', twColors.green[600]],
|
["green", twColors.green[600]],
|
||||||
['indigo', twColors.indigo[600]],
|
["indigo", twColors.indigo[600]],
|
||||||
['sky', twColors.sky[600]],
|
["sky", twColors.sky[600]],
|
||||||
['lime', twColors.lime[600]],
|
["lime", twColors.lime[600]],
|
||||||
['orange', twColors.orange[600]],
|
["orange", twColors.orange[600]],
|
||||||
['pink', twColors.pink[600]],
|
["pink", twColors.pink[600]],
|
||||||
['purple', twColors.purple[600]],
|
["purple", twColors.purple[600]],
|
||||||
['red', twColors.red[600]],
|
["red", twColors.red[600]],
|
||||||
['rose', twColors.rose[600]],
|
["rose", twColors.rose[600]],
|
||||||
['sky', twColors.sky[600]],
|
["sky", twColors.sky[600]],
|
||||||
['teal', twColors.teal[600]],
|
["teal", twColors.teal[600]],
|
||||||
['violet', twColors.violet[600]],
|
["violet", twColors.violet[600]],
|
||||||
['yellow', twColors.yellow[600]],
|
["yellow", twColors.yellow[600]],
|
||||||
['zinc', twColors.zinc[600]],
|
["zinc", twColors.zinc[600]],
|
||||||
]
|
]
|
||||||
|
|
||||||
const profile = deriveProfile(event.pubkey)
|
const profile = deriveProfile(event.pubkey)
|
||||||
const profileDisplay = deriveProfileDisplay(event.pubkey)
|
const profileDisplay = deriveProfileDisplay(event.pubkey)
|
||||||
const reactions = deriveEvents(repository, {filters: [{kinds: [REACTION], '#e': [event.id]}]})
|
const reactions = deriveEvents(repository, {filters: [{kinds: [REACTION], "#e": [event.id]}]})
|
||||||
const zaps = deriveEvents(repository, {filters: [{kinds: [ZAP_RESPONSE], '#e': [event.id]}]})
|
const zaps = deriveEvents(repository, {filters: [{kinds: [ZAP_RESPONSE], "#e": [event.id]}]})
|
||||||
const {replies} = getAncestorTags(event.tags)
|
const {replies} = getAncestorTags(event.tags)
|
||||||
const parentId = replies[0]?.[1]
|
const parentId = replies[0]?.[1]
|
||||||
const parentHints = [replies[0]?.[2]].filter(Boolean)
|
const parentHints = [replies[0]?.[2]].filter(Boolean)
|
||||||
@@ -52,8 +58,8 @@
|
|||||||
const ps = derived(publishStatusData, $m => Object.values($m[event.id] || {}))
|
const ps = derived(publishStatusData, $m => Object.values($m[event.id] || {}))
|
||||||
|
|
||||||
const displayReaction = (content: string) => {
|
const displayReaction = (content: string) => {
|
||||||
if (content === '+') return "❤️"
|
if (content === "+") return "❤️"
|
||||||
if (content === '-') return "👎"
|
if (content === "-") return "👎"
|
||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +71,8 @@
|
|||||||
$: parentProfileDisplay = deriveProfileDisplay(parentPubkey)
|
$: parentProfileDisplay = deriveProfileDisplay(parentPubkey)
|
||||||
$: isPublished = findStatus($ps, [PublishStatus.Success])
|
$: isPublished = findStatus($ps, [PublishStatus.Success])
|
||||||
$: isPending = findStatus($ps, [PublishStatus.Pending]) && event.created_at > now() - 30
|
$: isPending = findStatus($ps, [PublishStatus.Pending]) && event.created_at > now() - 30
|
||||||
$: failure = !isPending && !isPublished && findStatus($ps, [PublishStatus.Failure, PublishStatus.Timeout])
|
$: failure =
|
||||||
|
!isPending && !isPublished && findStatus($ps, [PublishStatus.Failure, PublishStatus.Timeout])
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div in:fly class="group relative flex flex-col gap-1 p-2 transition-colors hover:bg-base-300">
|
<div in:fly class="group relative flex flex-col gap-1 p-2 transition-colors hover:bg-base-300">
|
||||||
@@ -86,25 +93,26 @@
|
|||||||
{#if showPubkey}
|
{#if showPubkey}
|
||||||
<Avatar src={$profile?.picture} class="border border-solid border-base-content" size={10} />
|
<Avatar src={$profile?.picture} class="border border-solid border-base-content" size={10} />
|
||||||
{:else}
|
{:else}
|
||||||
<div class="min-w-10 max-w-10 w-10" />
|
<div class="w-10 min-w-10 max-w-10" />
|
||||||
{/if}
|
{/if}
|
||||||
<div class="-mt-1">
|
<div class="-mt-1">
|
||||||
{#if showPubkey}
|
{#if showPubkey}
|
||||||
<div class="flex gap-2 items-center">
|
<div class="flex items-center gap-2">
|
||||||
<strong class="text-sm" style="color: {colorValue}" data-color={colorName}>{$profileDisplay}</strong>
|
<strong class="text-sm" style="color: {colorValue}" data-color={colorName}
|
||||||
<span class="opacity-50 text-xs">{formatTimestampAsTime(event.created_at)}</span>
|
>{$profileDisplay}</strong>
|
||||||
|
<span class="text-xs opacity-50">{formatTimestampAsTime(event.created_at)}</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<p class="text-sm">
|
<p class="text-sm">
|
||||||
{event.content}
|
{event.content}
|
||||||
{#if isPending}
|
{#if isPending}
|
||||||
<span class="ml-1 flex-inline gap-1">
|
<span class="flex-inline ml-1 gap-1">
|
||||||
<span class="loading loading-spinner h-3 w-3 mx-1 translate-y-px" />
|
<span class="loading loading-spinner mx-1 h-3 w-3 translate-y-px" />
|
||||||
<span class="opacity-50">Sending...</span>
|
<span class="opacity-50">Sending...</span>
|
||||||
</span>
|
</span>
|
||||||
{:else if failure}
|
{:else if failure}
|
||||||
<span
|
<span
|
||||||
class="ml-1 flex-inline gap-1 tooltip cursor-pointer"
|
class="flex-inline tooltip ml-1 cursor-pointer gap-1"
|
||||||
data-tip="{failure.message} ({displayRelayUrl(failure.url)})">
|
data-tip="{failure.message} ({displayRelayUrl(failure.url)})">
|
||||||
<Icon icon="danger" class="translate-y-px" size={3} />
|
<Icon icon="danger" class="translate-y-px" size={3} />
|
||||||
<span class="opacity-50">Failed to send!</span>
|
<span class="opacity-50">Failed to send!</span>
|
||||||
@@ -114,9 +122,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if $reactions.length > 0 || $zaps.length > 0}
|
{#if $reactions.length > 0 || $zaps.length > 0}
|
||||||
<div class="text-xs ml-12">
|
<div class="ml-12 text-xs">
|
||||||
{#each groupBy(e => e.content, $reactions).entries() as [content, events]}
|
{#each groupBy(e => e.content, $reactions).entries() as [content, events]}
|
||||||
<Button class="btn btn-neutral btn-xs rounded-full mr-2 flex-inline gap-1">
|
<Button class="flex-inline btn btn-neutral btn-xs mr-2 gap-1 rounded-full">
|
||||||
<span>{displayReaction(content)}</span>
|
<span>{displayReaction(content)}</span>
|
||||||
{#if events.length > 1}
|
{#if events.length > 1}
|
||||||
<span>{events.length}</span>
|
<span>{events.length}</span>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {displayRelayUrl} from '@welshman/util'
|
import {displayRelayUrl} from "@welshman/util"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {first} from '@welshman/lib'
|
|
||||||
import {makeSecret, getNip07, Nip46Broker} from "@welshman/signer"
|
import {makeSecret, getNip07, Nip46Broker} from "@welshman/signer"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Field from "@lib/components/Field.svelte"
|
import Field from "@lib/components/Field.svelte"
|
||||||
@@ -26,10 +25,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSuccess = async (session: Session) => {
|
const onSuccess = async (session: Session, relays: string[] = []) => {
|
||||||
addSession(session)
|
addSession(session)
|
||||||
|
|
||||||
await loadUserData(session.pubkey)
|
await loadUserData(session.pubkey, relays)
|
||||||
|
|
||||||
pushToast({message: "Successfully logged in!"})
|
pushToast({message: "Successfully logged in!"})
|
||||||
clearModal()
|
clearModal()
|
||||||
@@ -50,7 +49,7 @@
|
|||||||
const broker = Nip46Broker.get(pubkey, secret, handler)
|
const broker = Nip46Broker.get(pubkey, secret, handler)
|
||||||
|
|
||||||
if (await broker.connect("", nip46Perms)) {
|
if (await broker.connect("", nip46Perms)) {
|
||||||
await onSuccess({method: "nip46", pubkey, secret, handler})
|
await onSuccess({method: "nip46", pubkey, secret, handler}, relays)
|
||||||
} else {
|
} else {
|
||||||
pushToast({
|
pushToast({
|
||||||
theme: "error",
|
theme: "error",
|
||||||
@@ -93,7 +92,12 @@
|
|||||||
<div class="flex items-center gap-2" slot="input">
|
<div class="flex items-center gap-2" slot="input">
|
||||||
<label class="input input-bordered flex w-full items-center gap-2">
|
<label class="input input-bordered flex w-full items-center gap-2">
|
||||||
<Icon icon="user-rounded" />
|
<Icon icon="user-rounded" />
|
||||||
<input bind:value={username} disabled={loading} class="grow" type="text" placeholder="username" />
|
<input
|
||||||
|
bind:value={username}
|
||||||
|
disabled={loading}
|
||||||
|
class="grow"
|
||||||
|
type="text"
|
||||||
|
placeholder="username" />
|
||||||
<span>@{handler.domain}</span>
|
<span>@{handler.domain}</span>
|
||||||
</label>
|
</label>
|
||||||
{#if getNip07()}
|
{#if getNip07()}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="relative w-14 bg-base-100 flex-shrink-0 pt-4" bind:this={element}>
|
<div class="relative w-14 flex-shrink-0 bg-base-100 pt-4" bind:this={element}>
|
||||||
<div
|
<div
|
||||||
class="absolute z-nav-active ml-2 h-[144px] w-12 bg-base-300"
|
class="absolute z-nav-active ml-2 h-[144px] w-12 bg-base-300"
|
||||||
style={`top: ${$activeOffset}px`} />
|
style={`top: ${$activeOffset}px`} />
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {append, remove} from "@welshman/lib"
|
import {append, remove} from "@welshman/lib"
|
||||||
import {displayRelayUrl} from "@welshman/util"
|
import {displayRelayUrl} from "@welshman/util"
|
||||||
import {PublishStatus} from "@welshman/net"
|
|
||||||
import {goto} from "$app/navigation"
|
|
||||||
import Spinner from "@lib/components/Spinner.svelte"
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import InfoNip29 from "@app/components/InfoNip29.svelte"
|
import InfoNip29 from "@app/components/InfoNip29.svelte"
|
||||||
import {pushModal, clearModal} from "@app/modal"
|
import {pushModal, clearModal} from "@app/modal"
|
||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/toast"
|
||||||
import type {PublishStatusData} from "@app/state"
|
|
||||||
import {deriveGroup, displayGroup, relayUrlsByNom} from "@app/state"
|
import {deriveGroup, displayGroup, relayUrlsByNom} from "@app/state"
|
||||||
import {sendJoinRequest, addGroupMemberships} from "@app/commands"
|
import {sendJoinRequest, addGroupMemberships} from "@app/commands"
|
||||||
|
|
||||||
@@ -30,7 +27,7 @@
|
|||||||
const [ok, message] = await sendJoinRequest(nom, url)
|
const [ok, message] = await sendJoinRequest(nom, url)
|
||||||
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
return pushToast({theme: 'error', message})
|
return pushToast({theme: "error", message})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {throttle} from 'throttle-debounce'
|
import {throttle} from "throttle-debounce"
|
||||||
import type {Readable} from "svelte/store"
|
import type {Readable} from "svelte/store"
|
||||||
import type {FuseResult} from "fuse.js"
|
import type {FuseResult} from "fuse.js"
|
||||||
import {get, writable, readable, derived} from "svelte/store"
|
import {get, writable, readable, derived} from "svelte/store"
|
||||||
@@ -6,7 +6,6 @@ import type {Maybe} from "@welshman/lib"
|
|||||||
import {
|
import {
|
||||||
max,
|
max,
|
||||||
first,
|
first,
|
||||||
append,
|
|
||||||
between,
|
between,
|
||||||
uniqBy,
|
uniqBy,
|
||||||
groupBy,
|
groupBy,
|
||||||
@@ -40,17 +39,30 @@ import {
|
|||||||
displayPubkey,
|
displayPubkey,
|
||||||
GROUP_JOIN,
|
GROUP_JOIN,
|
||||||
GROUP_ADD_USER,
|
GROUP_ADD_USER,
|
||||||
isStampedEvent,
|
|
||||||
isEventTemplate,
|
|
||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import type {SignedEvent, HashedEvent, EventTemplate, TrustedEvent, PublishedProfile, PublishedList} from "@welshman/util"
|
import type {
|
||||||
import type {SubscribeRequest, PublishRequest} from "@welshman/net"
|
SignedEvent,
|
||||||
|
HashedEvent,
|
||||||
|
EventTemplate,
|
||||||
|
TrustedEvent,
|
||||||
|
PublishedProfile,
|
||||||
|
PublishedList,
|
||||||
|
} from "@welshman/util"
|
||||||
|
import type {SubscribeRequest} from "@welshman/net"
|
||||||
import {publish as basePublish, subscribe as baseSubscribe, PublishStatus} from "@welshman/net"
|
import {publish as basePublish, subscribe as baseSubscribe, PublishStatus} from "@welshman/net"
|
||||||
import {decrypt, stamp, own, hash} from "@welshman/signer"
|
import {decrypt, stamp, own, hash} from "@welshman/signer"
|
||||||
import {custom, deriveEvents, deriveEventsMapped, getter, withGetter} from "@welshman/store"
|
import {custom, deriveEvents, deriveEventsMapped, getter, withGetter} from "@welshman/store"
|
||||||
import {createSearch} from "@lib/util"
|
import {createSearch} from "@lib/util"
|
||||||
import type {Handle, Relay} from "@app/types"
|
import type {Handle, Relay} from "@app/types"
|
||||||
import {INDEXER_RELAYS, DUFFLEPUD_URL, repository, pk, getSession, getSigner, REACTION_KINDS} from "@app/base"
|
import {
|
||||||
|
INDEXER_RELAYS,
|
||||||
|
DUFFLEPUD_URL,
|
||||||
|
repository,
|
||||||
|
pk,
|
||||||
|
getSession,
|
||||||
|
getSigner,
|
||||||
|
REACTION_KINDS,
|
||||||
|
} from "@app/base"
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
|
|
||||||
@@ -156,10 +168,15 @@ thunkWorker.addGlobalHandler(async ({event, relays, resolve}: ThunkWithResolve)
|
|||||||
const {id} = event
|
const {id} = event
|
||||||
const statusByUrl: PublishStatusDataByUrl = {}
|
const statusByUrl: PublishStatusDataByUrl = {}
|
||||||
|
|
||||||
pub.emitter.on('*', (status: PublishStatus, url: string, message: string) => {
|
pub.emitter.on("*", (status: PublishStatus, url: string, message: string) => {
|
||||||
publishStatusData.update(assoc(id, Object.assign(statusByUrl, {[url]: {id, url, status, message}})))
|
publishStatusData.update(
|
||||||
|
assoc(id, Object.assign(statusByUrl, {[url]: {id, url, status, message}})),
|
||||||
|
)
|
||||||
|
|
||||||
if (Object.values(statusByUrl).filter(s => s.status !== PublishStatus.Pending).length === relays.length) {
|
if (
|
||||||
|
Object.values(statusByUrl).filter(s => s.status !== PublishStatus.Pending).length ===
|
||||||
|
relays.length
|
||||||
|
) {
|
||||||
resolve(statusByUrl)
|
resolve(statusByUrl)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -207,8 +224,7 @@ export const load = (request: SubscribeRequest) =>
|
|||||||
sub.emitter.on("complete", () => resolve(events))
|
sub.emitter.on("complete", () => resolve(events))
|
||||||
})
|
})
|
||||||
|
|
||||||
export const loadOne = async (request: SubscribeRequest) =>
|
export const loadOne = async (request: SubscribeRequest) => first(await load(request))
|
||||||
first(await load(request))
|
|
||||||
|
|
||||||
// Publish status
|
// Publish status
|
||||||
|
|
||||||
@@ -219,7 +235,6 @@ export type PublishStatusData = {
|
|||||||
status: PublishStatus
|
status: PublishStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export type PublishStatusDataByUrl = Record<string, PublishStatusData>
|
export type PublishStatusDataByUrl = Record<string, PublishStatusData>
|
||||||
|
|
||||||
export type PublishStatusDataByUrlById = Record<string, PublishStatusDataByUrl>
|
export type PublishStatusDataByUrlById = Record<string, PublishStatusDataByUrl>
|
||||||
@@ -279,7 +294,7 @@ export const topics = custom<Topic[]>(setter => {
|
|||||||
const getTopics = () => {
|
const getTopics = () => {
|
||||||
const topics = new Map<string, number>()
|
const topics = new Map<string, number>()
|
||||||
for (const tagString of repository.eventsByTag.keys()) {
|
for (const tagString of repository.eventsByTag.keys()) {
|
||||||
if (tagString.startsWith('t:')) {
|
if (tagString.startsWith("t:")) {
|
||||||
const topic = tagString.slice(2).toLowerCase()
|
const topic = tagString.slice(2).toLowerCase()
|
||||||
|
|
||||||
topics.set(topic, inc(topics.get(topic)))
|
topics.set(topic, inc(topics.get(topic)))
|
||||||
@@ -310,7 +325,10 @@ export const searchTopics = derived(topics, $topics =>
|
|||||||
export const relays = writable<Relay[]>([])
|
export const relays = writable<Relay[]>([])
|
||||||
|
|
||||||
export const relaysByPubkey = derived(relays, $relays =>
|
export const relaysByPubkey = derived(relays, $relays =>
|
||||||
groupBy(($relay: Relay) => $relay.pubkey, $relays.filter(r => r.pubkey)),
|
groupBy(
|
||||||
|
($relay: Relay) => $relay.pubkey,
|
||||||
|
$relays.filter(r => r.pubkey),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
export const {
|
export const {
|
||||||
@@ -697,7 +715,11 @@ export type GroupMessage = {
|
|||||||
export const readGroupMessage = (event: TrustedEvent): Maybe<GroupMessage> => {
|
export const readGroupMessage = (event: TrustedEvent): Maybe<GroupMessage> => {
|
||||||
const nom = event.tags.find(nthEq(0, "h"))?.[1]
|
const nom = event.tags.find(nthEq(0, "h"))?.[1]
|
||||||
|
|
||||||
if (!nom || between(GROUP_ADD_USER - 1, GROUP_JOIN + 1, event.kind) || REACTION_KINDS.includes(event.kind)) {
|
if (
|
||||||
|
!nom ||
|
||||||
|
between(GROUP_ADD_USER - 1, GROUP_JOIN + 1, event.kind) ||
|
||||||
|
REACTION_KINDS.includes(event.kind)
|
||||||
|
) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -789,17 +811,14 @@ export const userGroupsByNom = withGetter(
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
export const userRelayUrlsByNom = derived(
|
export const userRelayUrlsByNom = derived(userGroupsByNom, $userGroupsByNom => {
|
||||||
userGroupsByNom,
|
const $userRelayUrlsByNom = new Map()
|
||||||
$userGroupsByNom => {
|
|
||||||
const $userRelayUrlsByNom = new Map()
|
|
||||||
|
|
||||||
for (const [nom, groups] of $userGroupsByNom.entries()) {
|
for (const [nom, groups] of $userGroupsByNom.entries()) {
|
||||||
for (const group of groups) {
|
for (const group of groups) {
|
||||||
pushToMapKey($userRelayUrlsByNom, nom, group.relay.url)
|
pushToMapKey($userRelayUrlsByNom, nom, group.relay.url)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $userRelayUrlsByNom
|
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
return $userRelayUrlsByNom
|
||||||
|
})
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import cx from 'classnames'
|
import cx from "classnames"
|
||||||
import {switcher} from "@welshman/lib"
|
import {switcher} from "@welshman/lib"
|
||||||
import AddSquare from "@assets/icons/Add Square.svg?dataurl"
|
import AddSquare from "@assets/icons/Add Square.svg?dataurl"
|
||||||
import Code2 from "@assets/icons/Code 2.svg?dataurl"
|
import Code2 from "@assets/icons/Code 2.svg?dataurl"
|
||||||
@@ -69,8 +69,8 @@
|
|||||||
const data = switcher(icon, {
|
const data = switcher(icon, {
|
||||||
"add-square": AddSquare,
|
"add-square": AddSquare,
|
||||||
"code-2": Code2,
|
"code-2": Code2,
|
||||||
"earth": Earth,
|
earth: Earth,
|
||||||
"pen": Pen,
|
pen: Pen,
|
||||||
"headphones-round": HeadphonesRound,
|
"headphones-round": HeadphonesRound,
|
||||||
"add-circle": AddCircle,
|
"add-circle": AddCircle,
|
||||||
"alt-arrow-down": AltArrowDown,
|
"alt-arrow-down": AltArrowDown,
|
||||||
@@ -105,7 +105,7 @@
|
|||||||
"menu-dots": MenuDots,
|
"menu-dots": MenuDots,
|
||||||
"notes-minimalistic": NotesMinimalistic,
|
"notes-minimalistic": NotesMinimalistic,
|
||||||
"pallete-2": Pallete2,
|
"pallete-2": Pallete2,
|
||||||
"paperclip": Paperclip,
|
paperclip: Paperclip,
|
||||||
plain: Plain,
|
plain: Plain,
|
||||||
reply: Reply,
|
reply: Reply,
|
||||||
"remote-controller-minimalistic": RemoteControllerMinimalistic,
|
"remote-controller-minimalistic": RemoteControllerMinimalistic,
|
||||||
|
|||||||
@@ -69,7 +69,7 @@
|
|||||||
on:dragleave|preventDefault|stopPropagation={onDragLeave}
|
on:dragleave|preventDefault|stopPropagation={onDragLeave}
|
||||||
on:drop|preventDefault|stopPropagation={onDrop}>
|
on:drop|preventDefault|stopPropagation={onDrop}>
|
||||||
<div
|
<div
|
||||||
class="absolute right-0 top-0 overflow-hidden rounded-full bg-primary h-5 w-5"
|
class="absolute right-0 top-0 h-5 w-5 overflow-hidden rounded-full bg-primary"
|
||||||
class:bg-error={file}
|
class:bg-error={file}
|
||||||
class:bg-primary={!file}>
|
class:bg-primary={!file}>
|
||||||
{#if file}
|
{#if file}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
<div class="flex w-60 flex-col gap-1 bg-base-300 flex-shrink-0">
|
<div class="flex w-60 flex-shrink-0 flex-col gap-1 bg-base-300">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
on:click
|
on:click
|
||||||
class={cx(
|
class={cx(
|
||||||
$$props.class,
|
$$props.class,
|
||||||
"flex items-center gap-3 transition-all hover:bg-base-100 hover:text-base-content text-left",
|
"flex items-center gap-3 text-left transition-all hover:bg-base-100 hover:text-base-content",
|
||||||
)}
|
)}
|
||||||
class:text-base-content={active}
|
class:text-base-content={active}
|
||||||
class:bg-base-100={active}>
|
class:bg-base-100={active}>
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
on:click
|
on:click
|
||||||
class={cx(
|
class={cx(
|
||||||
$$props.class,
|
$$props.class,
|
||||||
"flex w-full items-center gap-3 transition-all hover:bg-base-100 hover:text-base-content text-left",
|
"flex w-full items-center gap-3 text-left transition-all hover:bg-base-100 hover:text-base-content",
|
||||||
)}
|
)}
|
||||||
class:text-base-content={active}
|
class:text-base-content={active}
|
||||||
class:bg-base-100={active}>
|
class:bg-base-100={active}>
|
||||||
|
|||||||
@@ -1,41 +1,42 @@
|
|||||||
import { Node, nodePasteRule, type PasteRuleMatch } from '@tiptap/core'
|
import {Node, nodePasteRule, type PasteRuleMatch} from "@tiptap/core"
|
||||||
import type { Node as ProsemirrorNode } from '@tiptap/pm/model'
|
import type {Node as ProsemirrorNode} from "@tiptap/pm/model"
|
||||||
import type { MarkdownSerializerState } from 'prosemirror-markdown'
|
import type {MarkdownSerializerState} from "prosemirror-markdown"
|
||||||
|
|
||||||
export const LINK_REGEX = /^([a-z\+:]{2,30}:\/\/)?[^<>\(\)\s]+\.[a-z]{2,6}[^\s]*[^<>"'\.!?,:\s\)\(]*/gi
|
export const LINK_REGEX =
|
||||||
|
/^([a-z\+:]{2,30}:\/\/)?[^<>\(\)\s]+\.[a-z]{2,6}[^\s]*[^<>"'\.!?,:\s\)\(]*/gi
|
||||||
|
|
||||||
export const createPasteRuleMatch = <T extends Record<string, unknown>>(
|
export const createPasteRuleMatch = <T extends Record<string, unknown>>(
|
||||||
match: RegExpMatchArray,
|
match: RegExpMatchArray,
|
||||||
data: T,
|
data: T,
|
||||||
): PasteRuleMatch => ({ index: match.index!, replaceWith: match[2], text: match[0], match, data })
|
): PasteRuleMatch => ({index: match.index!, replaceWith: match[2], text: match[0], match, data})
|
||||||
|
|
||||||
export interface LinkAttributes {
|
export interface LinkAttributes {
|
||||||
url: string
|
url: string
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module '@tiptap/core' {
|
declare module "@tiptap/core" {
|
||||||
interface Commands<ReturnType> {
|
interface Commands<ReturnType> {
|
||||||
link: {
|
link: {
|
||||||
insertLink: (options: { url: string }) => ReturnType
|
insertLink: (options: {url: string}) => ReturnType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LinkExtension = Node.create({
|
export const LinkExtension = Node.create({
|
||||||
atom: true,
|
atom: true,
|
||||||
name: 'link',
|
name: "link",
|
||||||
group: 'inline',
|
group: "inline",
|
||||||
inline: true,
|
inline: true,
|
||||||
selectable: true,
|
selectable: true,
|
||||||
draggable: true,
|
draggable: true,
|
||||||
priority: 1000,
|
priority: 1000,
|
||||||
addAttributes() {
|
addAttributes() {
|
||||||
return {
|
return {
|
||||||
url: { default: null },
|
url: {default: null},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
renderHTML(props) {
|
renderHTML(props) {
|
||||||
return ['div', { 'data-url': props.node.attrs.url }]
|
return ["div", {"data-url": props.node.attrs.url}]
|
||||||
},
|
},
|
||||||
renderText(props) {
|
renderText(props) {
|
||||||
return props.node.attrs.url
|
return props.node.attrs.url
|
||||||
@@ -53,10 +54,10 @@ export const LinkExtension = Node.create({
|
|||||||
addCommands() {
|
addCommands() {
|
||||||
return {
|
return {
|
||||||
insertLink:
|
insertLink:
|
||||||
({ url }) =>
|
({url}) =>
|
||||||
({ commands }) => {
|
({commands}) => {
|
||||||
return commands.insertContent(
|
return commands.insertContent(
|
||||||
{ type: this.name, attrs: { url } },
|
{type: this.name, attrs: {url}},
|
||||||
{
|
{
|
||||||
updateSelection: false,
|
updateSelection: false,
|
||||||
},
|
},
|
||||||
@@ -68,13 +69,13 @@ export const LinkExtension = Node.create({
|
|||||||
return [
|
return [
|
||||||
nodePasteRule({
|
nodePasteRule({
|
||||||
type: this.type,
|
type: this.type,
|
||||||
getAttributes: (match) => match.data,
|
getAttributes: match => match.data,
|
||||||
find: (text) => {
|
find: text => {
|
||||||
const matches = []
|
const matches = []
|
||||||
|
|
||||||
for (const match of text.matchAll(LINK_REGEX)) {
|
for (const match of text.matchAll(LINK_REGEX)) {
|
||||||
try {
|
try {
|
||||||
matches.push(createPasteRuleMatch(match, { url: match[0] }))
|
matches.push(createPasteRuleMatch(match, {url: match[0]}))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
import type {SvelteComponent, ComponentType} from 'svelte'
|
import type {SvelteComponent, ComponentType} from "svelte"
|
||||||
import type {Readable} from 'svelte/store'
|
import type {Readable} from "svelte/store"
|
||||||
import tippy, {type Instance} from 'tippy.js'
|
import tippy, {type Instance} from "tippy.js"
|
||||||
import {mergeAttributes, Node} from '@tiptap/core'
|
import type {Editor} from "@tiptap/core"
|
||||||
import type {Editor} from '@tiptap/core'
|
import {PluginKey} from "@tiptap/pm/state"
|
||||||
import {PluginKey} from '@tiptap/pm/state'
|
import Suggestion from "@tiptap/suggestion"
|
||||||
import Suggestion from '@tiptap/suggestion'
|
import type {Search} from "@lib/util"
|
||||||
import type {Search} from '@lib/util'
|
|
||||||
|
|
||||||
export type SuggestionsOptions = {
|
export type SuggestionsOptions = {
|
||||||
char: string,
|
char: string
|
||||||
name: string,
|
name: string
|
||||||
editor: Editor,
|
editor: Editor
|
||||||
search: Readable<Search<any, any>>
|
search: Readable<Search<any, any>>
|
||||||
select: (value: any, props: any) => void
|
select: (value: any, props: any) => void
|
||||||
allowCreate?: boolean,
|
allowCreate?: boolean
|
||||||
suggestionComponent: ComponentType
|
suggestionComponent: ComponentType
|
||||||
suggestionsComponent: ComponentType
|
suggestionsComponent: ComponentType
|
||||||
}
|
}
|
||||||
@@ -27,7 +26,7 @@ export const createSuggestions = (options: SuggestionsOptions) =>
|
|||||||
// increase range.to by one when the next node is of type "text"
|
// increase range.to by one when the next node is of type "text"
|
||||||
// and starts with a space character
|
// and starts with a space character
|
||||||
const nodeAfter = editor.view.state.selection.$to.nodeAfter
|
const nodeAfter = editor.view.state.selection.$to.nodeAfter
|
||||||
const overrideSpace = nodeAfter?.text?.startsWith(' ')
|
const overrideSpace = nodeAfter?.text?.startsWith(" ")
|
||||||
|
|
||||||
if (overrideSpace) {
|
if (overrideSpace) {
|
||||||
range.to += 1
|
range.to += 1
|
||||||
@@ -38,13 +37,13 @@ export const createSuggestions = (options: SuggestionsOptions) =>
|
|||||||
.focus()
|
.focus()
|
||||||
.insertContentAt(range, [
|
.insertContentAt(range, [
|
||||||
{type: options.name, attrs: props},
|
{type: options.name, attrs: props},
|
||||||
{type: 'text', text: ' '},
|
{type: "text", text: " "},
|
||||||
])
|
])
|
||||||
.run()
|
.run()
|
||||||
|
|
||||||
window.getSelection()?.collapseToEnd()
|
window.getSelection()?.collapseToEnd()
|
||||||
},
|
},
|
||||||
allow: ({ state, range }) => {
|
allow: ({state, range}) => {
|
||||||
const $from = state.doc.resolve(range.from)
|
const $from = state.doc.resolve(range.from)
|
||||||
const type = state.schema.nodes[options.name]
|
const type = state.schema.nodes[options.name]
|
||||||
|
|
||||||
@@ -67,7 +66,7 @@ export const createSuggestions = (options: SuggestionsOptions) =>
|
|||||||
onStart: props => {
|
onStart: props => {
|
||||||
target = document.createElement("div")
|
target = document.createElement("div")
|
||||||
|
|
||||||
popover = tippy('body', {
|
popover = tippy("body", {
|
||||||
getReferenceClientRect: props.clientRect as any,
|
getReferenceClientRect: props.clientRect as any,
|
||||||
appendTo: document.body,
|
appendTo: document.body,
|
||||||
content: target,
|
content: target,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {Node, nodePasteRule} from '@tiptap/core'
|
import {Node, nodePasteRule} from "@tiptap/core"
|
||||||
import type {Node as ProsemirrorNode} from '@tiptap/pm/model'
|
import type {Node as ProsemirrorNode} from "@tiptap/pm/model"
|
||||||
import type {MarkdownSerializerState} from 'prosemirror-markdown'
|
import type {MarkdownSerializerState} from "prosemirror-markdown"
|
||||||
import {createPasteRuleMatch} from '@lib/tiptap/util'
|
import {createPasteRuleMatch} from "@lib/tiptap/util"
|
||||||
|
|
||||||
export const TOPIC_REGEX = /(#[^\s]+)/g
|
export const TOPIC_REGEX = /(#[^\s]+)/g
|
||||||
|
|
||||||
@@ -9,25 +9,25 @@ export interface TopicAttributes {
|
|||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module '@tiptap/core' {
|
declare module "@tiptap/core" {
|
||||||
interface Commands<ReturnType> {
|
interface Commands<ReturnType> {
|
||||||
topic: {
|
topic: {
|
||||||
insertTopic: (options: { name: string }) => ReturnType
|
insertTopic: (options: {name: string}) => ReturnType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TopicExtension = Node.create({
|
export const TopicExtension = Node.create({
|
||||||
atom: true,
|
atom: true,
|
||||||
name: 'topic',
|
name: "topic",
|
||||||
group: 'inline',
|
group: "inline",
|
||||||
inline: true,
|
inline: true,
|
||||||
selectable: true,
|
selectable: true,
|
||||||
draggable: true,
|
draggable: true,
|
||||||
priority: 1000,
|
priority: 1000,
|
||||||
addAttributes() {
|
addAttributes() {
|
||||||
return {
|
return {
|
||||||
name: { default: null },
|
name: {default: null},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
renderText(props) {
|
renderText(props) {
|
||||||
@@ -46,10 +46,10 @@ export const TopicExtension = Node.create({
|
|||||||
addCommands() {
|
addCommands() {
|
||||||
return {
|
return {
|
||||||
insertTopic:
|
insertTopic:
|
||||||
({ name }) =>
|
({name}) =>
|
||||||
({ commands }) => {
|
({commands}) => {
|
||||||
return commands.insertContent(
|
return commands.insertContent(
|
||||||
{ type: this.name, attrs: { name } },
|
{type: this.name, attrs: {name}},
|
||||||
{
|
{
|
||||||
updateSelection: false,
|
updateSelection: false,
|
||||||
},
|
},
|
||||||
@@ -61,13 +61,13 @@ export const TopicExtension = Node.create({
|
|||||||
return [
|
return [
|
||||||
nodePasteRule({
|
nodePasteRule({
|
||||||
type: this.type,
|
type: this.type,
|
||||||
getAttributes: (match) => match.data,
|
getAttributes: match => match.data,
|
||||||
find: (text) => {
|
find: text => {
|
||||||
const matches = []
|
const matches = []
|
||||||
|
|
||||||
for (const match of text.matchAll(TOPIC_REGEX)) {
|
for (const match of text.matchAll(TOPIC_REGEX)) {
|
||||||
try {
|
try {
|
||||||
matches.push(createPasteRuleMatch(match, { name: match[0] }))
|
matches.push(createPasteRuleMatch(match, {name: match[0]}))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export * from '@lib/tiptap/util'
|
export * from "@lib/tiptap/util"
|
||||||
export {createSuggestions} from '@lib/tiptap/Suggestions'
|
export {createSuggestions} from "@lib/tiptap/Suggestions"
|
||||||
export {TopicExtension} from '@lib/tiptap/TopicExtension'
|
export {TopicExtension} from "@lib/tiptap/TopicExtension"
|
||||||
export {LinkExtension} from '@lib/tiptap/LinkExtension'
|
export {LinkExtension} from "@lib/tiptap/LinkExtension"
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type {JSONContent, PasteRuleMatch} from '@tiptap/core'
|
import type {JSONContent, PasteRuleMatch} from "@tiptap/core"
|
||||||
|
|
||||||
export const createPasteRuleMatch = <T extends Record<string, unknown>>(
|
export const createPasteRuleMatch = <T extends Record<string, unknown>>(
|
||||||
match: RegExpMatchArray,
|
match: RegExpMatchArray,
|
||||||
data: T,
|
data: T,
|
||||||
): PasteRuleMatch => ({ index: match.index!, replaceWith: match[2], text: match[0], match, data })
|
): PasteRuleMatch => ({index: match.index!, replaceWith: match[2], text: match[0], match, data})
|
||||||
|
|
||||||
export const findNodes = (type: string, json: JSONContent) => {
|
export const findNodes = (type: string, json: JSONContent) => {
|
||||||
const results: JSONContent[] = []
|
const results: JSONContent[] = []
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type {FlyParams} from "svelte/transition"
|
import type {FlyParams} from "svelte/transition"
|
||||||
import {fly as baseFly} from "svelte/transition"
|
import {fly as baseFly} from "svelte/transition"
|
||||||
|
|
||||||
export {fade} from 'svelte/transition'
|
export {fade} from "svelte/transition"
|
||||||
|
|
||||||
export const fly = (node: Element, params?: FlyParams | undefined) =>
|
export const fly = (node: Element, params?: FlyParams | undefined) =>
|
||||||
baseFly(node, {y: 20, ...params})
|
baseFly(node, {y: 20, ...params})
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ export const synced = <T>(key: string, defaultValue: T, delay = 300) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type SearchOptions<V, T> = {
|
export type SearchOptions<V, T> = {
|
||||||
|
|
||||||
getValue: (item: T) => V
|
getValue: (item: T) => V
|
||||||
fuseOptions?: IFuseOptions<T>
|
fuseOptions?: IFuseOptions<T>
|
||||||
sortFn?: (items: FuseResult<T>) => any
|
sortFn?: (items: FuseResult<T>) => any
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import "@src/app.css"
|
import "@src/app.css"
|
||||||
import {onMount} from "svelte"
|
import {onMount} from "svelte"
|
||||||
import {get, derived} from 'svelte/store'
|
import {get} from "svelte/store"
|
||||||
import {page} from "$app/stores"
|
import {page} from "$app/stores"
|
||||||
import {goto} from "$app/navigation"
|
import {goto} from "$app/navigation"
|
||||||
import {browser} from "$app/environment"
|
import {browser} from "$app/environment"
|
||||||
@@ -70,8 +70,8 @@
|
|||||||
forward: ($psd: PublishStatusDataByUrlById) => {
|
forward: ($psd: PublishStatusDataByUrlById) => {
|
||||||
const data = []
|
const data = []
|
||||||
|
|
||||||
for (const [id, itemsByUrl] of Object.entries($psd)) {
|
for (const itemsByUrl of Object.values($psd)) {
|
||||||
for (const [url, item] of Object.entries(itemsByUrl)) {
|
for (const item of Object.values(itemsByUrl)) {
|
||||||
data.push(item)
|
data.push(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,7 +94,8 @@
|
|||||||
keyPath: "key",
|
keyPath: "key",
|
||||||
store: adapter({
|
store: adapter({
|
||||||
store: freshness,
|
store: freshness,
|
||||||
forward: ($freshness: Record<string, number>) => Object.entries($freshness).map(([key, ts]) => ({key, ts})),
|
forward: ($freshness: Record<string, number>) =>
|
||||||
|
Object.entries($freshness).map(([key, ts]) => ({key, ts})),
|
||||||
backward: (data: any[]) => {
|
backward: (data: any[]) => {
|
||||||
const result: Record<string, number> = {}
|
const result: Record<string, number> = {}
|
||||||
|
|
||||||
@@ -110,7 +111,8 @@
|
|||||||
keyPath: "id",
|
keyPath: "id",
|
||||||
store: adapter({
|
store: adapter({
|
||||||
store: plaintext,
|
store: plaintext,
|
||||||
forward: ($plaintext: Record<string, string>) => Object.entries($plaintext).map(([id, plaintext]) => ({id, plaintext})),
|
forward: ($plaintext: Record<string, string>) =>
|
||||||
|
Object.entries($plaintext).map(([id, plaintext]) => ({id, plaintext})),
|
||||||
backward: (data: any[]) => {
|
backward: (data: any[]) => {
|
||||||
const result: Record<string, string> = {}
|
const result: Record<string, string> = {}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {onMount} from 'svelte'
|
import {onMount} from "svelte"
|
||||||
import {goto} from "$app/navigation"
|
import {goto} from "$app/navigation"
|
||||||
|
|
||||||
onMount(() => goto('/home'))
|
onMount(() => goto("/home"))
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
{#if $userMembership?.noms.has(group.nom)}
|
{#if $userMembership?.noms.has(group.nom)}
|
||||||
<div class="center absolute flex w-full">
|
<div class="center absolute flex w-full">
|
||||||
<div
|
<div
|
||||||
class="tooltip relative left-8 w-5 h-5 top-[38px] rounded-full bg-primary"
|
class="tooltip relative left-8 top-[38px] h-5 w-5 rounded-full bg-primary"
|
||||||
data-tip="You are already a member of this space.">
|
data-tip="You are already a member of this space.">
|
||||||
<Icon icon="check-circle" class="scale-110" />
|
<Icon icon="check-circle" class="scale-110" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,10 +3,8 @@
|
|||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
|
|
||||||
const hash = import.meta.env.VITE_BUILD_HASH
|
|
||||||
const nprofile =
|
const nprofile =
|
||||||
"nprofile1qqsf03c2gsmx5ef4c9zmxvlew04gdh7u94afnknp33qvv3c94kvwxgspz4mhxue69uhhyetvv9ujuerpd46hxtnfduhsz9rhwden5te0wfjkcctev93xcefwdaexwtcpzdmhxue69uhhqatjwpkx2urpvuhx2ue0vamm57"
|
"nprofile1qqsf03c2gsmx5ef4c9zmxvlew04gdh7u94afnknp33qvv3c94kvwxgspz4mhxue69uhhyetvv9ujuerpd46hxtnfduhsz9rhwden5te0wfjkcctev93xcefwdaexwtcpzdmhxue69uhhqatjwpkx2urpvuhx2ue0vamm57"
|
||||||
const hodlbodPubkey = "97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322"
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="hero min-h-screen bg-base-200">
|
<div class="hero min-h-screen bg-base-200">
|
||||||
@@ -15,14 +13,12 @@
|
|||||||
<p class="text-center text-2xl">Thanks for using</p>
|
<p class="text-center text-2xl">Thanks for using</p>
|
||||||
<h1 class="mb-4 text-center text-5xl font-bold uppercase">Flotilla</h1>
|
<h1 class="mb-4 text-center text-5xl font-bold uppercase">Flotilla</h1>
|
||||||
<div class="grid grid-cols-1 gap-8 lg:grid-cols-2">
|
<div class="grid grid-cols-1 gap-8 lg:grid-cols-2">
|
||||||
<div class="card2 shadow-2xl flex flex-col gap-2 text-center">
|
<div class="card2 flex flex-col gap-2 text-center shadow-2xl">
|
||||||
<h3 class="text-2xl sm:h-12">Support development</h3>
|
<h3 class="text-2xl sm:h-12">Support development</h3>
|
||||||
<p class="sm:h-16">Funds will be used to support development.</p>
|
<p class="sm:h-16">Funds will be used to support development.</p>
|
||||||
<Button class="btn btn-primary">
|
<Button class="btn btn-primary">Zap the Developer</Button>
|
||||||
Zap the Developer
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card2 shadow-2xl flex flex-col gap-2 text-center">
|
<div class="card2 flex flex-col gap-2 text-center shadow-2xl">
|
||||||
<h3 class="text-2xl sm:h-12">Get in touch</h3>
|
<h3 class="text-2xl sm:h-12">Get in touch</h3>
|
||||||
<p class="sm:h-16">Having problems? Let us know by filing an issue.</p>
|
<p class="sm:h-16">Having problems? Let us know by filing an issue.</p>
|
||||||
<Link
|
<Link
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {onMount} from 'svelte'
|
import {onMount} from "svelte"
|
||||||
import {readable} from 'svelte/store'
|
import {readable} from "svelte/store"
|
||||||
import {displayRelayUrl, isShareableRelayUrl} from '@welshman/util'
|
import {displayRelayUrl, isShareableRelayUrl} from "@welshman/util"
|
||||||
import type {SignedEvent} from '@welshman/util'
|
import type {SignedEvent} from "@welshman/util"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Link from "@lib/components/Link.svelte"
|
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import {clip} from "@app/toast"
|
|
||||||
import {DEFAULT_RELAYS, INDEXER_RELAYS} from "@app/base"
|
import {DEFAULT_RELAYS, INDEXER_RELAYS} from "@app/base"
|
||||||
import {searchRelays, subscribe, loadRelay} from "@app/state"
|
import {searchRelays, subscribe, loadRelay} from "@app/state"
|
||||||
|
|
||||||
@@ -20,12 +18,12 @@
|
|||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const sub = subscribe({
|
const sub = subscribe({
|
||||||
filters: [{kinds: [30166], '#N': ['29']}],
|
filters: [{kinds: [30166], "#N": ["29"]}],
|
||||||
relays: [...INDEXER_RELAYS, ...DEFAULT_RELAYS],
|
relays: [...INDEXER_RELAYS, ...DEFAULT_RELAYS],
|
||||||
})
|
})
|
||||||
|
|
||||||
sub.emitter.on('event', (url: string, event: SignedEvent) => {
|
sub.emitter.on("event", (url: string, event: SignedEvent) => {
|
||||||
const d = event.tags.find(t => t[0] === 'd')?.[1] || ""
|
const d = event.tags.find(t => t[0] === "d")?.[1] || ""
|
||||||
|
|
||||||
if (isShareableRelayUrl(d)) {
|
if (isShareableRelayUrl(d)) {
|
||||||
loadRelay(d)
|
loadRelay(d)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {onMount} from 'svelte'
|
import {onMount} from "svelte"
|
||||||
import {page} from "$app/stores"
|
import {page} from "$app/stores"
|
||||||
import {sortBy, now} from "@welshman/lib"
|
import {sortBy, now} from "@welshman/lib"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const sub = subscribe({
|
const sub = subscribe({
|
||||||
filters: [{'#h': [nom], since: now() - 30}],
|
filters: [{"#h": [nom], since: now() - 30}],
|
||||||
relays: $userRelayUrlsByNom.get(nom)!,
|
relays: $userRelayUrlsByNom.get(nom)!,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="relative flex h-screen flex-col">
|
<div class="relative flex h-screen flex-col">
|
||||||
<div class="relative z-feature mx-2 pt-4 rounded-xl">
|
<div class="relative z-feature mx-2 rounded-xl pt-4">
|
||||||
<div class="flex min-h-12 items-center gap-4 rounded-xl bg-base-100 px-4 shadow-xl">
|
<div class="flex min-h-12 items-center gap-4 rounded-xl bg-base-100 px-4 shadow-xl">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<Icon icon="hashtag" />
|
<Icon icon="hashtag" />
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-grow flex-col-reverse overflow-auto -mt-2 py-2">
|
<div class="-mt-2 flex flex-grow flex-col-reverse overflow-auto py-2">
|
||||||
{#each elements as { type, id, value, showPubkey } (id)}
|
{#each elements as { type, id, value, showPubkey } (id)}
|
||||||
{#if type === "date"}
|
{#if type === "date"}
|
||||||
<div class="flex items-center gap-2 p-2 text-xs opacity-50">
|
<div class="flex items-center gap-2 p-2 text-xs opacity-50">
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export default {
|
|||||||
preprocess: vitePreprocess(),
|
preprocess: vitePreprocess(),
|
||||||
kit: {
|
kit: {
|
||||||
adapter: adapter({
|
adapter: adapter({
|
||||||
fallback: 'index.html',
|
fallback: "index.html",
|
||||||
}),
|
}),
|
||||||
alias: {
|
alias: {
|
||||||
"@src": "src",
|
"@src": "src",
|
||||||
|
|||||||
Reference in New Issue
Block a user