forked from coracle/flotilla
Add custom emoji parsing and display
This commit is contained in:
+2
-2
@@ -52,7 +52,7 @@
|
||||
"@vite-pwa/assets-generator": "^0.2.6",
|
||||
"@vite-pwa/sveltekit": "^0.6.6",
|
||||
"@welshman/app": "^0.2.5",
|
||||
"@welshman/content": "^0.2.1",
|
||||
"@welshman/content": "^0.2.2",
|
||||
"@welshman/dvm": "^0.2.0",
|
||||
"@welshman/editor": "^0.2.4",
|
||||
"@welshman/feeds": "^0.2.2",
|
||||
@@ -62,7 +62,7 @@
|
||||
"@welshman/router": "^0.2.0",
|
||||
"@welshman/signer": "^0.2.3",
|
||||
"@welshman/store": "^0.2.0",
|
||||
"@welshman/util": "^0.2.2",
|
||||
"@welshman/util": "^0.2.3",
|
||||
"daisyui": "^4.12.10",
|
||||
"date-picker-svelte": "^2.13.0",
|
||||
"dotenv": "^16.4.5",
|
||||
|
||||
+4
-2
@@ -380,10 +380,12 @@ export const publishReport = ({
|
||||
export type ReactionParams = {
|
||||
event: TrustedEvent
|
||||
content: string
|
||||
tags?: string[][]
|
||||
}
|
||||
|
||||
export const makeReaction = ({event, content}: ReactionParams) => {
|
||||
const tags = tagEventForReaction(event)
|
||||
export const makeReaction = ({content, event, tags: paramTags = []}: ReactionParams) => {
|
||||
const tags = [...paramTags, ...tagEventForReaction(event)]
|
||||
|
||||
const groupTag = getTag("h", event.tags)
|
||||
|
||||
if (groupTag) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||
import {pubkey} from "@welshman/app"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
@@ -26,20 +26,15 @@
|
||||
|
||||
const editEvent = () => pushModal(CalendarEventEdit, {url, event})
|
||||
|
||||
const onReactionClick = (content: string, events: TrustedEvent[]) => {
|
||||
const reaction = events.find(e => e.pubkey === $pubkey)
|
||||
const deleteReaction = (event: TrustedEvent) => publishDelete({relays: [url], event})
|
||||
|
||||
if (reaction) {
|
||||
publishDelete({relays: [url], event: reaction})
|
||||
} else {
|
||||
publishReaction({event, content, relays: [url]})
|
||||
}
|
||||
}
|
||||
const createReaction = (template: EventContent) =>
|
||||
publishReaction({...template, event, relays: [url]})
|
||||
</script>
|
||||
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<div class="flex flex-grow flex-wrap justify-end gap-2">
|
||||
<ReactionSummary {url} {event} {onReactionClick} reactionClass="tooltip-left" />
|
||||
<ReactionSummary {url} {event} {deleteReaction} {createReaction} reactionClass="tooltip-left" />
|
||||
<ThunkStatusOrDeleted {event} />
|
||||
{#if showActivity}
|
||||
<EventActivity {url} {path} {event} />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import {hash, now, formatTimestampAsTime, formatTimestampAsDate} from "@welshman/lib"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {thunks, pubkey, deriveProfile, deriveProfileDisplay} from "@welshman/app"
|
||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||
import {thunks, deriveProfile, deriveProfileDisplay} from "@welshman/app"
|
||||
import {isMobile} from "@lib/html"
|
||||
import TapTarget from "@lib/components/TapTarget.svelte"
|
||||
import Avatar from "@lib/components/Avatar.svelte"
|
||||
@@ -41,15 +41,10 @@
|
||||
|
||||
const openProfile = () => pushModal(ProfileDetail, {pubkey: event.pubkey, url})
|
||||
|
||||
const onReactionClick = (content: string, events: TrustedEvent[]) => {
|
||||
const reaction = events.find(e => e.pubkey === $pubkey)
|
||||
const deleteReaction = (event: TrustedEvent) => publishDelete({relays: [url], event})
|
||||
|
||||
if (reaction) {
|
||||
publishDelete({relays: [url], event: reaction})
|
||||
} else {
|
||||
publishReaction({event, content, relays: [url]})
|
||||
}
|
||||
}
|
||||
const createReaction = (template: EventContent) =>
|
||||
publishReaction({...template, event, relays: [url]})
|
||||
</script>
|
||||
|
||||
<TapTarget
|
||||
@@ -89,7 +84,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row-2 ml-10 mt-1">
|
||||
<ReactionSummary {url} {event} {onReactionClick} reactionClass="tooltip-right" />
|
||||
<ReactionSummary
|
||||
{url}
|
||||
{event}
|
||||
{deleteReaction}
|
||||
{createReaction}
|
||||
reactionClass="tooltip-right" />
|
||||
</div>
|
||||
{#if !isMobile}
|
||||
<button
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import {type Instance} from "tippy.js"
|
||||
import {hash, formatTimestampAsTime} from "@welshman/lib"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||
import {thunks, pubkey, deriveProfile, deriveProfileDisplay, sendWrapped} from "@welshman/app"
|
||||
import {isMobile} from "@lib/html"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
@@ -36,12 +36,11 @@
|
||||
|
||||
const reply = () => replyTo(event)
|
||||
|
||||
const onReactionClick = async (content: string, events: TrustedEvent[]) => {
|
||||
const reaction = events.find(e => e.pubkey === $pubkey)
|
||||
const template = reaction ? makeDelete({event: reaction}) : makeReaction({event, content})
|
||||
const deleteReaction = (event: TrustedEvent) =>
|
||||
sendWrapped({template: makeDelete({event}), pubkeys})
|
||||
|
||||
await sendWrapped({template, pubkeys})
|
||||
}
|
||||
const createReaction = (template: EventContent) =>
|
||||
sendWrapped({template: makeReaction({event, ...template}), pubkeys})
|
||||
|
||||
const openProfile = () => pushModal(ProfileDetail, {pubkey: event.pubkey})
|
||||
|
||||
@@ -120,7 +119,7 @@
|
||||
</div>
|
||||
</TapTarget>
|
||||
<div class="row-2 z-feature -mt-4 ml-4">
|
||||
<ReactionSummary {event} {onReactionClick} noTooltip />
|
||||
<ReactionSummary {event} {deleteReaction} {createReaction} noTooltip />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
truncate,
|
||||
renderAsHtml,
|
||||
isText,
|
||||
isEmoji,
|
||||
isTopic,
|
||||
isCode,
|
||||
isCashu,
|
||||
@@ -22,6 +23,7 @@
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import ContentToken from "@app/components/ContentToken.svelte"
|
||||
import ContentEmoji from "@app/components/ContentEmoji.svelte"
|
||||
import ContentCode from "@app/components/ContentCode.svelte"
|
||||
import ContentLinkInline from "@app/components/ContentLinkInline.svelte"
|
||||
import ContentLinkBlock from "@app/components/ContentLinkBlock.svelte"
|
||||
@@ -133,6 +135,8 @@
|
||||
<ContentNewline value={parsed.value} />
|
||||
{:else if isTopic(parsed)}
|
||||
<ContentTopic value={parsed.value} />
|
||||
{:else if isEmoji(parsed)}
|
||||
<ContentEmoji value={parsed.value} />
|
||||
{:else if isCode(parsed)}
|
||||
<ContentCode
|
||||
value={parsed.value}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
import type {ParsedEmojiValue} from "@welshman/content"
|
||||
import {imgproxy} from "@app/state"
|
||||
|
||||
export let value: ParsedEmojiValue
|
||||
|
||||
const alt = `:${value.name}:`
|
||||
</script>
|
||||
|
||||
{#if value.url}
|
||||
<img
|
||||
{alt}
|
||||
src={imgproxy(value.url, {w: 24, h: 24})}
|
||||
class="-mt-0.5 inline h-[1em] min-w-[1em] align-middle" />
|
||||
{:else}
|
||||
{alt}
|
||||
{/if}
|
||||
@@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type {NativeEmoji} from "emoji-picker-element/shared"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {pubkey} from "@welshman/app"
|
||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import EmojiButton from "@lib/components/EmojiButton.svelte"
|
||||
import NoteContent from "@app/components/NoteContent.svelte"
|
||||
@@ -11,15 +10,10 @@
|
||||
|
||||
const {url, event} = $props()
|
||||
|
||||
const onReactionClick = (content: string, events: TrustedEvent[]) => {
|
||||
const reaction = events.find(e => e.pubkey === $pubkey)
|
||||
const deleteReaction = (event: TrustedEvent) => publishDelete({relays: [url], event})
|
||||
|
||||
if (reaction) {
|
||||
publishDelete({relays: [url], event: reaction})
|
||||
} else {
|
||||
publishReaction({event, content, relays: [url]})
|
||||
}
|
||||
}
|
||||
const createReaction = (template: EventContent) =>
|
||||
publishReaction({...template, event, relays: [url]})
|
||||
|
||||
const onEmoji = (emoji: NativeEmoji) =>
|
||||
publishReaction({event, content: emoji.unicode, relays: [url]})
|
||||
@@ -28,7 +22,7 @@
|
||||
<NoteCard {event} {url} class="card2 bg-alt">
|
||||
<NoteContent {event} expandMode="inline" />
|
||||
<div class="flex w-full justify-between gap-2">
|
||||
<ReactionSummary {url} {event} {onReactionClick} reactionClass="tooltip-right">
|
||||
<ReactionSummary {url} {event} {deleteReaction} {createReaction} reactionClass="tooltip-right">
|
||||
<EmojiButton {onEmoji} class="btn btn-neutral btn-xs h-[26px] rounded-box">
|
||||
<Icon icon="smile-circle" size={4} />
|
||||
</EmojiButton>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
import {parse, isEmoji, renderAsHtml} from "@welshman/content"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import ContentEmoji from "@app/components/ContentEmoji.svelte"
|
||||
|
||||
export let event
|
||||
</script>
|
||||
|
||||
{#if event.content === "+" || event.content === ""}
|
||||
<Icon icon="heart" />
|
||||
{:else if event.content === "-"}
|
||||
<Icon icon="thumbs-down" />
|
||||
{:else}
|
||||
{#each parse(event) as parsed}
|
||||
{#if isEmoji(parsed)}
|
||||
<ContentEmoji value={parsed.value} />
|
||||
{:else}
|
||||
{@html renderAsHtml(parsed)}
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
@@ -2,20 +2,29 @@
|
||||
import {onMount} from "svelte"
|
||||
import type {Snippet} from "svelte"
|
||||
import {groupBy, uniq, uniqBy, batch, displayList} from "@welshman/lib"
|
||||
import {REACTION, getReplyFilters, getTag, REPORT, DELETE} from "@welshman/util"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {
|
||||
REACTION,
|
||||
getReplyFilters,
|
||||
getEmojiTags,
|
||||
getEmojiTag,
|
||||
getTag,
|
||||
REPORT,
|
||||
DELETE,
|
||||
} from "@welshman/util"
|
||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||
import {deriveEvents} from "@welshman/store"
|
||||
import {load} from "@welshman/net"
|
||||
import {pubkey, repository, displayProfileByPubkey} from "@welshman/app"
|
||||
import {isMobile, preventDefault, stopPropagation} from "@lib/html"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Reaction from "@app/components/Reaction.svelte"
|
||||
import EventReportDetails from "@app/components/EventReportDetails.svelte"
|
||||
import {displayReaction} from "@app/state"
|
||||
import {pushModal} from "@app/modal"
|
||||
|
||||
interface Props {
|
||||
event: any
|
||||
onReactionClick: any
|
||||
event: TrustedEvent
|
||||
deleteReaction: (event: TrustedEvent) => void
|
||||
createReaction: (event: EventContent) => void
|
||||
url?: string
|
||||
reactionClass?: string
|
||||
noTooltip?: boolean
|
||||
@@ -24,7 +33,8 @@
|
||||
|
||||
const {
|
||||
event,
|
||||
onReactionClick,
|
||||
deleteReaction,
|
||||
createReaction,
|
||||
url = "",
|
||||
reactionClass = "",
|
||||
noTooltip = false,
|
||||
@@ -39,14 +49,31 @@
|
||||
filters: [{kinds: [REACTION], "#e": [event.id]}],
|
||||
})
|
||||
|
||||
const onReactionClick = (events: TrustedEvent[]) => {
|
||||
const reaction = events.find(e => e.pubkey === $pubkey)
|
||||
|
||||
if (reaction) {
|
||||
deleteReaction(reaction)
|
||||
} else {
|
||||
const [event] = events
|
||||
|
||||
createReaction({
|
||||
content: event.content,
|
||||
tags: getEmojiTags(event.content.replace(/:/g, ""), event.tags),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const onReportClick = () => pushModal(EventReportDetails, {url, event})
|
||||
|
||||
const reportReasons = $derived(uniq($reports.map(e => getTag("e", e.tags)?.[2])))
|
||||
|
||||
const getReactionKey = (e: TrustedEvent) => getEmojiTag(e.content, e.tags)?.join("") || e.content
|
||||
|
||||
const groupedReactions = $derived(
|
||||
groupBy(
|
||||
e => e.content,
|
||||
uniqBy(e => e.pubkey + e.content, $reactions),
|
||||
getReactionKey,
|
||||
uniqBy(e => `${e.pubkey}${getReactionKey(e)}`, $reactions),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -86,12 +113,12 @@
|
||||
<span>{$reports.length}</span>
|
||||
</button>
|
||||
{/if}
|
||||
{#each groupedReactions.entries() as [content, events]}
|
||||
{#each groupedReactions.entries() as [key, events]}
|
||||
{@const pubkeys = events.map(e => e.pubkey)}
|
||||
{@const isOwn = $pubkey && pubkeys.includes($pubkey)}
|
||||
{@const info = displayList(pubkeys.map(pubkey => displayProfileByPubkey(pubkey)))}
|
||||
{@const tooltip = `${info} reacted ${displayReaction(content)}`}
|
||||
{@const onClick = () => onReactionClick(content, events)}
|
||||
{@const tooltip = `${info} reacted`}
|
||||
{@const onClick = () => onReactionClick(events)}
|
||||
<button
|
||||
type="button"
|
||||
data-tip={tooltip}
|
||||
@@ -101,7 +128,7 @@
|
||||
class:border-solid={isOwn}
|
||||
class:border-primary={isOwn}
|
||||
onclick={stopPropagation(preventDefault(onClick))}>
|
||||
<span>{displayReaction(content)}</span>
|
||||
<Reaction event={events[0]} />
|
||||
{#if events.length > 1}
|
||||
<span>{events.length}</span>
|
||||
{/if}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {pubkey} from "@welshman/app"
|
||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||
import ReactionSummary from "@app/components/ReactionSummary.svelte"
|
||||
import ThunkStatusOrDeleted from "@app/components/ThunkStatusOrDeleted.svelte"
|
||||
import EventActivity from "@app/components/EventActivity.svelte"
|
||||
@@ -18,20 +17,15 @@
|
||||
|
||||
const path = makeThreadPath(url, event.id)
|
||||
|
||||
const onReactionClick = (content: string, events: TrustedEvent[]) => {
|
||||
const reaction = events.find(e => e.pubkey === $pubkey)
|
||||
const deleteReaction = (event: TrustedEvent) => publishDelete({relays: [url], event})
|
||||
|
||||
if (reaction) {
|
||||
publishDelete({relays: [url], event: reaction})
|
||||
} else {
|
||||
publishReaction({event, content, relays: [url]})
|
||||
}
|
||||
}
|
||||
const createReaction = (template: EventContent) =>
|
||||
publishReaction({...template, event, relays: [url]})
|
||||
</script>
|
||||
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<div class="flex flex-grow flex-wrap justify-end gap-2">
|
||||
<ReactionSummary {url} {event} {onReactionClick} reactionClass="tooltip-left" />
|
||||
<ReactionSummary {url} {event} {deleteReaction} {createReaction} reactionClass="tooltip-left" />
|
||||
<ThunkStatusOrDeleted {event} />
|
||||
{#if showActivity}
|
||||
<EventActivity {url} {path} {event} />
|
||||
|
||||
Reference in New Issue
Block a user