post interactions!
This commit is contained in:
parent
648f53f40c
commit
681ef74f95
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "spacesocial-client",
|
||||
"version": "0.2.0_rev1",
|
||||
"version": "0.2.0_rev2",
|
||||
"description": "social media for the galaxy-wide-web! 🌌",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
|
|
@ -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() {
|
||||
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…
Reference in a new issue