post interactions!
This commit is contained in:
		
							parent
							
								
									648f53f40c
								
							
						
					
					
						commit
						681ef74f95
					
				
					 11 changed files with 354 additions and 75 deletions
				
			
		|  | @ -20,6 +20,7 @@ | |||
|     } | ||||
| 
 | ||||
|     if (client.app && client.app.token) { | ||||
|         // this triggers the client actually getting the authenticated user's data. | ||||
|         client.verifyCredentials().then(res => { | ||||
|             if (res) { | ||||
|                 console.log(`Logged in as @${client.user.username}@${client.user.host}`); | ||||
|  |  | |||
|  | @ -131,6 +131,83 @@ export async function getPostContext(post_id) { | |||
|     return data; | ||||
| } | ||||
| 
 | ||||
| export async function boostPost(post_id) { | ||||
|     let client = get(Client.get()); | ||||
|     let url = `https://${client.instance.host}/api/v1/statuses/${post_id}/reblog`; | ||||
|     const data = await fetch(url, { | ||||
|         method: 'POST', | ||||
|         headers: { "Authorization": "Bearer " + client.app.token } | ||||
|     }).then(res => { return res.ok ? res.json() : false }); | ||||
| 
 | ||||
|     if (data === false) return false; | ||||
|     return data; | ||||
| } | ||||
| 
 | ||||
| export async function unboostPost(post_id) { | ||||
|     let client = get(Client.get()); | ||||
|     let url = `https://${client.instance.host}/api/v1/statuses/${post_id}/unreblog`; | ||||
|     const data = await fetch(url, { | ||||
|         method: 'POST', | ||||
|         headers: { "Authorization": "Bearer " + client.app.token } | ||||
|     }).then(res => { return res.ok ? res.json() : false }); | ||||
| 
 | ||||
|     if (data === false) return false; | ||||
|     return data; | ||||
| } | ||||
| 
 | ||||
| export async function favouritePost(post_id) { | ||||
|     let client = get(Client.get()); | ||||
|     let url = `https://${client.instance.host}/api/v1/statuses/${post_id}/favourite`; | ||||
|     const data = await fetch(url, { | ||||
|         method: 'POST', | ||||
|         headers: { "Authorization": "Bearer " + client.app.token } | ||||
|     }).then(res => { return res.ok ? res.json() : false }); | ||||
| 
 | ||||
|     if (data === false) return false; | ||||
|     return data; | ||||
| } | ||||
| 
 | ||||
| export async function unfavouritePost(post_id) { | ||||
|     let client = get(Client.get()); | ||||
|     let url = `https://${client.instance.host}/api/v1/statuses/${post_id}/unfavourite`; | ||||
|     const data = await fetch(url, { | ||||
|         method: 'POST', | ||||
|         headers: { "Authorization": "Bearer " + client.app.token } | ||||
|     }).then(res => { return res.ok ? res.json() : false }); | ||||
| 
 | ||||
|     if (data === false) return false; | ||||
|     return data; | ||||
| } | ||||
| 
 | ||||
| export async function reactPost(post_id, shortcode) { | ||||
|     // for whatever reason (at least in my testing on iceshrimp)
 | ||||
|     // using shortcodes for external emoji results in a fallback
 | ||||
|     // to the default like emote.
 | ||||
|     // identical api calls on chuckya instances do not display
 | ||||
|     // this behaviour.
 | ||||
|     let client = get(Client.get()); | ||||
|     let url = `https://${client.instance.host}/api/v1/statuses/${post_id}/react/${encodeURIComponent(shortcode)}`; | ||||
|     const data = await fetch(url, { | ||||
|         method: 'POST', | ||||
|         headers: { "Authorization": "Bearer " + client.app.token } | ||||
|     }).then(res => { return res.ok ? res.json() : false }); | ||||
| 
 | ||||
|     if (data === false) return false; | ||||
|     return data; | ||||
| } | ||||
| 
 | ||||
| export async function unreactPost(post_id, shortcode) { | ||||
|     let client = get(Client.get()); | ||||
|     let url = `https://${client.instance.host}/api/v1/statuses/${post_id}/unreact/${encodeURIComponent(shortcode)}`; | ||||
|     const data = await fetch(url, { | ||||
|         method: 'POST', | ||||
|         headers: { "Authorization": "Bearer " + client.app.token } | ||||
|     }).then(res => { return res.ok ? res.json() : false }); | ||||
| 
 | ||||
|     if (data === false) return false; | ||||
|     return data; | ||||
| } | ||||
| 
 | ||||
| export async function parsePost(data, parent_replies, child_replies) { | ||||
|     let client = get(Client.get()); | ||||
|     let post = new Post(); | ||||
|  | @ -166,6 +243,9 @@ export async function parsePost(data, parent_replies, child_replies) { | |||
|     post.warning = data.spoiler_text; | ||||
|     post.boost_count = data.reblogs_count; | ||||
|     post.reply_count = data.replies_count; | ||||
|     post.favourite_count = data.favourites_count; | ||||
|     post.favourited = data.favourited; | ||||
|     post.boosted = data.boosted; | ||||
|     post.mentions = data.mentions; | ||||
|     post.files = data.media_attachments; | ||||
|     post.url = data.url; | ||||
|  | @ -185,33 +265,7 @@ export async function parsePost(data, parent_replies, child_replies) { | |||
|     } | ||||
| 
 | ||||
|     if (data.reactions && client.instance.capabilities.includes(capabilities.REACTIONS)) { | ||||
|         post.reactions = []; | ||||
|         data.reactions.forEach(reaction_data => { | ||||
|             if (/^[\w\-.@]+$/g.exec(reaction_data.name)) { | ||||
|                 let name = reaction_data.name.split('@')[0]; | ||||
|                 let host = reaction_data.name.includes('@') ? reaction_data.name.split('@')[1] : client.instance.host; | ||||
|                 post.reactions.push({ | ||||
|                     count: reaction_data.count, | ||||
|                     emoji: parseEmoji({ | ||||
|                         id: name + '@' + host, | ||||
|                         name: name, | ||||
|                         host: host, | ||||
|                         url: reaction_data.url, | ||||
|                     }), | ||||
|                     me: reaction_data.me, | ||||
|                 }); | ||||
|             } else { | ||||
|                 if (reaction_data.name == '❤') reaction_data.name = '❤️'; // stupid heart unicode
 | ||||
|                 post.reactions.push({ | ||||
|                     count: reaction_data.count, | ||||
|                     emoji: { | ||||
|                         html: reaction_data.name, | ||||
|                         name: reaction_data.name, | ||||
|                     }, | ||||
|                     me: reaction_data.me, | ||||
|                 }); | ||||
|             } | ||||
|         }); | ||||
|         post.reactions = parseReactions(data.reactions); | ||||
|     } | ||||
|     return post; | ||||
| } | ||||
|  | @ -251,6 +305,21 @@ export async function parseUser(data) { | |||
|     return user; | ||||
| } | ||||
| 
 | ||||
| export function parseReactions(data) { | ||||
|     let client = get(Client.get()); | ||||
|     let reactions = []; | ||||
|     data.forEach(reaction_data => { | ||||
|         let reaction = { | ||||
|             count: reaction_data.count, | ||||
|             name: reaction_data.name, | ||||
|             me: reaction_data.me, | ||||
|         }; | ||||
|         if (reaction_data.url) reaction.url = reaction_data.url; | ||||
|         reactions.push(reaction); | ||||
|     }); | ||||
|     return reactions; | ||||
| } | ||||
| 
 | ||||
| export function parseEmoji(data) { | ||||
|     let emoji = new Emoji( | ||||
|         data.id, | ||||
|  |  | |||
|  | @ -100,6 +100,30 @@ export class Client { | |||
|         return await api.getPost(post_id, parent_replies, child_replies); | ||||
|     } | ||||
| 
 | ||||
|     async boostPost(post_id) { | ||||
|         return await api.boostPost(post_id); | ||||
|     } | ||||
| 
 | ||||
|     async unboostPost(post_id) { | ||||
|         return await api.unboostPost(post_id); | ||||
|     } | ||||
| 
 | ||||
|     async favouritePost(post_id) { | ||||
|         return await api.favouritePost(post_id); | ||||
|     } | ||||
| 
 | ||||
|     async unfavouritePost(post_id) { | ||||
|         return await api.unfavouritePost(post_id); | ||||
|     } | ||||
| 
 | ||||
|     async reactPost(post_id, shortcode) { | ||||
|         return await api.reactPost(post_id, shortcode); | ||||
|     } | ||||
| 
 | ||||
|     async unreactPost(post_id, shortcode) { | ||||
|         return await api.unreactPost(post_id, shortcode); | ||||
|     } | ||||
| 
 | ||||
|     putCacheUser(user) { | ||||
|         this.cache.users[user.id] = user; | ||||
|         client.set(this); | ||||
|  | @ -148,7 +172,6 @@ export class Client { | |||
|         if (!json) return false; | ||||
|         let saved = JSON.parse(json); | ||||
|         if (!saved.version || saved.version !== APP_VERSION) { | ||||
|             localStorage.setItem(save_name + '-backup', json); | ||||
|             localStorage.removeItem(save_name); | ||||
|             return false; | ||||
|         } | ||||
|  |  | |||
|  | @ -5,9 +5,7 @@ export const EMOJI_REGEX = /:[\w\-.]{0,32}@[\w\-.]{0,32}:/g; | |||
| export const EMOJI_NAME_REGEX = /:[\w\-.]{0,32}:/g; | ||||
| 
 | ||||
| export default class Emoji { | ||||
|     id; | ||||
|     name; | ||||
|     host; | ||||
|     url; | ||||
| 
 | ||||
|     constructor(id, name, host, url) { | ||||
|  | @ -18,7 +16,10 @@ export default class Emoji { | |||
|     } | ||||
| 
 | ||||
|     get html() { | ||||
|         return `<img src="${this.url}" class="emoji" height="20" title="${this.name}" alt="${this.name}">`; | ||||
|         if (this.url) | ||||
|             return `<img src="${this.url}" class="emoji" height="20" title="${this.name}" alt="${this.name}">`; | ||||
|         else | ||||
|             return `${this.name}`; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -8,6 +8,9 @@ export default class Post { | |||
|     warning; | ||||
|     boost_count; | ||||
|     reply_count; | ||||
|     favourite_count; | ||||
|     favourited; | ||||
|     boosted; | ||||
|     mentions; | ||||
|     reactions; | ||||
|     emojis; | ||||
|  |  | |||
|  | @ -1,10 +1,11 @@ | |||
| const sounds = { | ||||
|     "default": new Audio("sound/log.ogg"), | ||||
|     "post": new Audio("sound/success.ogg"), | ||||
|     "boost": new Audio("sound/hello.ogg"), | ||||
|     "default": new Audio("/sound/log.ogg"), | ||||
|     "post": new Audio("/sound/success.ogg"), | ||||
|     "boost": new Audio("/sound/hello.ogg"), | ||||
| }; | ||||
| 
 | ||||
| export function play_sound(name) { | ||||
|     if (name === false) return; | ||||
|     if (!name) name = "default"; | ||||
|     const sound = sounds[name]; | ||||
|     if (!sound) { | ||||
|  |  | |||
|  | @ -1,21 +1,36 @@ | |||
| <script> | ||||
|     import { play_sound } from '../../sound.js'; | ||||
|     import { createEventDispatcher } from 'svelte'; | ||||
|     const dispatch = createEventDispatcher(); | ||||
| 
 | ||||
|     export let icon = "🔧"; | ||||
|     export let type = "action"; | ||||
|     export let label = "Action"; | ||||
|     export let title = label; | ||||
|     export let count = 0; | ||||
|     export let active = false; | ||||
|     export let disabled = false; | ||||
|     export let sound = "default"; | ||||
| 
 | ||||
|     function click() { | ||||
|         if (disabled) return; | ||||
|         play_sound(sound); | ||||
|         dispatch('click'); | ||||
|     } | ||||
| </script> | ||||
| 
 | ||||
| <button | ||||
|         type="button" | ||||
|         class="{type}" | ||||
|         class={[ | ||||
|         type, | ||||
|         active ? "active" : "", | ||||
|         disabled ? "disabled" : "", | ||||
|         ].join(' ')} | ||||
|         aria-label="{label}" | ||||
|         title="{title}" | ||||
|         on:click|stopPropagation={() => (play_sound(sound))}> | ||||
|         <span class="icon">{@html icon}</span> | ||||
|         on:click={click}> | ||||
|         <span class="icon"> | ||||
|             <slot/> | ||||
|         </span> | ||||
|         {#if count} | ||||
|             <span class="count">{count}</span> | ||||
|         {/if} | ||||
|  | @ -28,24 +43,34 @@ | |||
|         display: flex; | ||||
|         flex-direction: row; | ||||
|         gap: 4px; | ||||
|         font-family: inherit; | ||||
|         font-size: 1em; | ||||
|         background: none; | ||||
|         color: inherit; | ||||
|         border: none; | ||||
|         border-radius: 8px; | ||||
|         transition: background-color .1s, color .1s; | ||||
|         cursor: pointer; | ||||
|     } | ||||
| 
 | ||||
|     button.active { | ||||
|         background: var(--accent); | ||||
|         color: var(--bg0); | ||||
|         background-color: color-mix(in srgb, transparent, var(--accent) 50%); | ||||
|         color: var(--bg-1000); | ||||
|     } | ||||
| 
 | ||||
|     button:hover { | ||||
|         background: #8881; | ||||
|     button:not(.disabled):hover { | ||||
|         background-color: var(--bg-600); | ||||
|         color: var(--text); | ||||
|     } | ||||
| 
 | ||||
|     button:active { | ||||
|         background: #0001; | ||||
|     button:not(.disabled):active { | ||||
|         background-color: var(--bg-1000); | ||||
|         color: var(--text); | ||||
|     } | ||||
| 
 | ||||
|     button.disabled { | ||||
|         opacity: .5; | ||||
|         cursor: initial; | ||||
|     } | ||||
| 
 | ||||
|     .icon { | ||||
|  |  | |||
|  | @ -8,6 +8,9 @@ | |||
|     import { parseOne as parseEmoji } from '../../emoji.js'; | ||||
|     import { play_sound } from '../../sound.js'; | ||||
|     import { onMount } from 'svelte'; | ||||
|     import { get } from 'svelte/store'; | ||||
|     import { Client } from '../../client/client.js'; | ||||
|     import * as api from '../../client/api.js'; | ||||
| 
 | ||||
|     export let post_data; | ||||
|     export let focused = false; | ||||
|  | @ -25,6 +28,55 @@ | |||
|         location = `/post/${post.id}`; | ||||
|     } | ||||
| 
 | ||||
|     async function toggleBoost() { | ||||
|         let client = get(Client.get()); | ||||
|         let data; | ||||
|         if (post.boosted) | ||||
|             data = await client.unboostPost(post.id); | ||||
|         else | ||||
|             data = await client.boostPost(post.id); | ||||
|         if (!data) { | ||||
|             console.error(`Failed to boost post ${post.id}`); | ||||
|             return; | ||||
|         } | ||||
|         post.boosted = data.boosted; | ||||
|         post.boost_count = data.reblogs_count; | ||||
|     } | ||||
| 
 | ||||
|     async function toggleFavourite() { | ||||
|         let client = get(Client.get()); | ||||
|         let data; | ||||
|         if (post.favourited) | ||||
|             data = await client.unfavouritePost(post.id); | ||||
|         else | ||||
|             data = await client.favouritePost(post.id); | ||||
|         if (!data) { | ||||
|             console.error(`Failed to favourite post ${post.id}`); | ||||
|             return; | ||||
|         } | ||||
|         post.favourited = data.favourited; | ||||
|         post.favourite_count = data.favourites_count; | ||||
|         if (data.reactions) post.reactions = api.parseReactions(data.reactions); | ||||
|     } | ||||
| 
 | ||||
|     async function toggleReaction(reaction) { | ||||
|         if (reaction.name.includes('@')) return; | ||||
|         let client = get(Client.get()); | ||||
| 
 | ||||
|         let data; | ||||
|         if (reaction.me) | ||||
|             data = await client.unreactPost(post.id, reaction.name); | ||||
|         else | ||||
|             data = await client.reactPost(post.id, reaction.name); | ||||
|         if (!data) { | ||||
|             console.error(`Failed to favourite post ${post.id}`); | ||||
|             return; | ||||
|         } | ||||
|         post.favourited = data.favourited; | ||||
|         post.favourite_count = data.favourites_count; | ||||
|         if (data.reactions) post.reactions = api.parseReactions(data.reactions); | ||||
|     } | ||||
| 
 | ||||
|     let el; | ||||
|     onMount(() => { | ||||
|         if (focused) { | ||||
|  | @ -46,18 +98,31 @@ | |||
|         <PostHeader post={post} /> | ||||
|         <Body post={post} /> | ||||
|         <footer class="post-footer"> | ||||
|             <div class="post-reactions"> | ||||
|             <div class="post-reactions" on:click|stopPropagation> | ||||
|                 {#each post.reactions as reaction} | ||||
|                     <ReactionButton icon={reaction.emoji.html} type="reaction" bind:count={reaction.count} title={reaction.emoji.id} label="" /> | ||||
|                     <ReactionButton | ||||
|                             type="reaction" | ||||
|                             on:click={() => toggleReaction(reaction)} | ||||
|                             bind:active={reaction.me} | ||||
|                             bind:count={reaction.count} | ||||
|                             disabled={reaction.name.includes('@')} | ||||
|                             title={reaction.name} | ||||
|                             label=""> | ||||
|                         {#if reaction.url} | ||||
|                             <img src={reaction.url} class="emoji" height="20" title={reaction.name} alt={reaction.name}> | ||||
|                         {:else} | ||||
|                             {reaction.name} | ||||
|                         {/if} | ||||
|                     </ReactionButton> | ||||
|                 {/each} | ||||
|             </div> | ||||
|             <div class="post-actions"> | ||||
|                 <ActionButton icon="🗨️" type="reply" label="Reply" bind:count={post.reply_count} sound="post" /> | ||||
|                 <ActionButton icon="🔁" type="boost" label="Boost" bind:count={post.boost_count} sound="boost" /> | ||||
|                 <ActionButton icon="⭐" type="favourite" label="Favourite" /> | ||||
|                 <ActionButton icon="😃" type="react" label="React" /> | ||||
|                 <ActionButton icon="🗣️" type="quote" label="Quote" /> | ||||
|                 <ActionButton icon="🛠️" type="more" label="More" /> | ||||
|             <div class="post-actions" on:click|stopPropagation> | ||||
|                 <ActionButton type="reply" label="Reply" bind:count={post.reply_count} sound="post" disabled>🗨️</ActionButton> | ||||
|                 <ActionButton type="boost" label="Boost" on:click={() => toggleBoost()} bind:active={post.boosted} bind:count={post.boost_count} sound="boost">🔁</ActionButton> | ||||
|                 <ActionButton type="favourite" label="Favourite" on:click={() => toggleFavourite()} bind:active={post.favourited} bind:count={post.favourite_count}>⭐</ActionButton> | ||||
|                 <ActionButton type="react" label="React" disabled>😃</ActionButton> | ||||
|                 <ActionButton type="quote" label="Quote" disabled>🗣️</ActionButton> | ||||
|                 <ActionButton type="more" label="More" disabled>🛠️</ActionButton> | ||||
|             </div> | ||||
|         </footer> | ||||
|     </article> | ||||
|  | @ -97,14 +162,18 @@ | |||
|     } | ||||
| 
 | ||||
|     :global(.post-reactions) { | ||||
|         width: fit-content; | ||||
|         display: flex; | ||||
|         flex-direction: row; | ||||
|         gap: 4px; | ||||
|     } | ||||
| 
 | ||||
|     :global(.post-actions) { | ||||
|         width: fit-content; | ||||
|         margin-top: 8px; | ||||
|         display: flex; | ||||
|         flex-direction: row; | ||||
|         gap: 2px; | ||||
|     } | ||||
| 
 | ||||
|     .post-container :global(.emoji) { | ||||
|  |  | |||
|  | @ -1,21 +1,35 @@ | |||
| <script> | ||||
|     import { play_sound } from '../../sound.js'; | ||||
|     import { createEventDispatcher } from 'svelte'; | ||||
|     const dispatch = createEventDispatcher(); | ||||
| 
 | ||||
|     export let icon = "🔧"; | ||||
|     export let type = "action"; | ||||
|     export let label = "Action"; | ||||
|     export let type = "react"; | ||||
|     export let label = "React"; | ||||
|     export let title = label; | ||||
|     export let count = 0; | ||||
|     export let active = false; | ||||
|     export let disabled = false; | ||||
|     export let sound = "default"; | ||||
| 
 | ||||
|     function click() { | ||||
|         play_sound(sound); | ||||
|         dispatch('click'); | ||||
|     } | ||||
| </script> | ||||
| 
 | ||||
| <button | ||||
|         type="button" | ||||
|         class="{type}" | ||||
|         class={[ | ||||
|         type, | ||||
|         active ? "active" : "", | ||||
|         disabled ? "disabled" : "", | ||||
|         ].join(' ')} | ||||
|         aria-label="{label}" | ||||
|         title="{title}" | ||||
|         on:click|stopPropagation={() => (play_sound(sound))}> | ||||
|         <span class="icon">{@html icon}</span> | ||||
|         on:click={click}> | ||||
|         <span class="icon"> | ||||
|             <slot/> | ||||
|         </span> | ||||
|         {#if count} | ||||
|             <span class="count">{count}</span> | ||||
|         {/if} | ||||
|  | @ -33,19 +47,27 @@ | |||
|         color: inherit; | ||||
|         border: none; | ||||
|         border-radius: 8px; | ||||
|         transition: background-color .1s, color .1s; | ||||
|         cursor: pointer; | ||||
|     } | ||||
| 
 | ||||
|     button.active { | ||||
|         background: var(--accent); | ||||
|         color: var(--bg0); | ||||
|         background-color: color-mix(in srgb, transparent, var(--accent) 50%); | ||||
|         color: var(--bg-1000); | ||||
|     } | ||||
| 
 | ||||
|     button:hover { | ||||
|         background: #8881; | ||||
|     button:not(.disabled):hover { | ||||
|         background-color: var(--bg-600); | ||||
|         color: var(--text); | ||||
|     } | ||||
| 
 | ||||
|     button:active { | ||||
|         background: #0001; | ||||
|     button:not(.disabled):active { | ||||
|         background-color: var(--bg-1000); | ||||
|         color: var(--text); | ||||
|     } | ||||
| 
 | ||||
|     button.disabled { | ||||
|         cursor: initial; | ||||
|     } | ||||
| 
 | ||||
|     .icon { | ||||
|  |  | |||
|  | @ -6,6 +6,9 @@ | |||
|     import Post from './Post.svelte'; | ||||
|     import { parseText as parseEmojis, parseOne as parseEmoji } from '../../emoji.js'; | ||||
|     import { shorthand as short_time } from '../../time.js'; | ||||
|     import { get } from 'svelte/store'; | ||||
|     import { Client } from '../../client/client.js'; | ||||
|     import * as api from '../../client/api.js'; | ||||
| 
 | ||||
|     export let post; | ||||
|     let time_string = post.created_at.toLocaleString(); | ||||
|  | @ -13,6 +16,55 @@ | |||
|     function gotoPost() { | ||||
|         location = `/post/${post.id}`; | ||||
|     } | ||||
| 
 | ||||
|     async function toggleBoost() { | ||||
|         let client = get(Client.get()); | ||||
|         let data; | ||||
|         if (post.boosted) | ||||
|             data = await client.unboostPost(post.id); | ||||
|         else | ||||
|             data = await client.boostPost(post.id); | ||||
|         if (!data) { | ||||
|             console.error(`Failed to boost post ${post.id}`); | ||||
|             return; | ||||
|         } | ||||
|         post.boosted = data.boosted; | ||||
|         post.boost_count = data.reblogs_count; | ||||
|     } | ||||
| 
 | ||||
|     async function toggleFavourite() { | ||||
|         let client = get(Client.get()); | ||||
|         let data; | ||||
|         if (post.favourited) | ||||
|             data = await client.unfavouritePost(post.id); | ||||
|         else | ||||
|             data = await client.favouritePost(post.id); | ||||
|         if (!data) { | ||||
|             console.error(`Failed to favourite post ${post.id}`); | ||||
|             return; | ||||
|         } | ||||
|         post.favourited = data.favourited; | ||||
|         post.favourite_count = data.favourites_count; | ||||
|         if (data.reactions) post.reactions = api.parseReactions(data.reactions); | ||||
|     } | ||||
| 
 | ||||
|     async function toggleReaction(reaction) { | ||||
|         if (reaction.name.includes('@')) return; | ||||
|         let client = get(Client.get()); | ||||
| 
 | ||||
|         let data; | ||||
|         if (reaction.me) | ||||
|             data = await client.unreactPost(post.id, reaction.name); | ||||
|         else | ||||
|             data = await client.reactPost(post.id, reaction.name); | ||||
|         if (!data) { | ||||
|             console.error(`Failed to favourite post ${post.id}`); | ||||
|             return; | ||||
|         } | ||||
|         post.favourited = data.favourited; | ||||
|         post.favourite_count = data.favourites_count; | ||||
|         if (data.reactions) post.reactions = api.parseReactions(data.reactions); | ||||
|     } | ||||
| </script> | ||||
| 
 | ||||
| {#if post.reply} | ||||
|  | @ -28,18 +80,31 @@ | |||
|         <Body post={post} /> | ||||
| 
 | ||||
|         <footer class="post-footer"> | ||||
|             <div class="post-reactions"> | ||||
|             <div class="post-reactions" on:click|stopPropagation> | ||||
|                 {#each post.reactions as reaction} | ||||
|                     <ReactionButton icon={reaction.emoji.html} type="reaction" bind:count={reaction.count} title={reaction.emoji.id} label="" /> | ||||
|                     <ReactionButton | ||||
|                             type="reaction" | ||||
|                             on:click={() => toggleReaction(reaction)} | ||||
|                             bind:active={reaction.me} | ||||
|                             bind:count={reaction.count} | ||||
|                             disabled={reaction.name.includes('@')} | ||||
|                             title={reaction.name} | ||||
|                             label=""> | ||||
|                         {#if reaction.url} | ||||
|                             <img src={reaction.url} class="emoji" height="20" title={reaction.name} alt={reaction.name}> | ||||
|                         {:else} | ||||
|                             {reaction.name} | ||||
|                         {/if} | ||||
|                     </ReactionButton> | ||||
|                 {/each} | ||||
|             </div> | ||||
|             <div class="post-actions"> | ||||
|                 <ActionButton icon="🗨️" type="reply" label="Reply" bind:count={post.reply_count} sound="post" /> | ||||
|                 <ActionButton icon="🔁" type="boost" label="Boost" bind:count={post.boost_count} sound="boost" /> | ||||
|                 <ActionButton icon="⭐" type="favourite" label="Favourite" /> | ||||
|                 <ActionButton icon="😃" type="react" label="React" /> | ||||
|                 <ActionButton icon="🗣️" type="quote" label="Quote" /> | ||||
|                 <ActionButton icon="🛠️" type="more" label="More" /> | ||||
|             <div class="post-actions" on:click|stopPropagation> | ||||
|                 <ActionButton type="reply" label="Reply" bind:count={post.reply_count} sound="post" disabled>🗨️</ActionButton> | ||||
|                 <ActionButton type="boost" label="Boost" on:click={() => toggleBoost()} bind:active={post.boosted} bind:count={post.boost_count} sound="boost">🔁</ActionButton> | ||||
|                 <ActionButton type="favourite" label="Favourite" on:click={() => toggleFavourite()} bind:active={post.favourited} bind:count={post.favourite_count}>⭐</ActionButton> | ||||
|                 <ActionButton type="react" label="React" disabled>😃</ActionButton> | ||||
|                 <ActionButton type="quote" label="Quote" disabled>🗣️</ActionButton> | ||||
|                 <ActionButton type="more" label="More" disabled>🛠️</ActionButton> | ||||
|             </div> | ||||
|         </footer> | ||||
|     </div> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue