post interactions!
This commit is contained in:
parent
648f53f40c
commit
681ef74f95
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "spacesocial-client",
|
"name": "spacesocial-client",
|
||||||
"version": "0.2.0_rev1",
|
"version": "0.2.0_rev2",
|
||||||
"description": "social media for the galaxy-wide-web! 🌌",
|
"description": "social media for the galaxy-wide-web! 🌌",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client.app && client.app.token) {
|
if (client.app && client.app.token) {
|
||||||
|
// this triggers the client actually getting the authenticated user's data.
|
||||||
client.verifyCredentials().then(res => {
|
client.verifyCredentials().then(res => {
|
||||||
if (res) {
|
if (res) {
|
||||||
console.log(`Logged in as @${client.user.username}@${client.user.host}`);
|
console.log(`Logged in as @${client.user.username}@${client.user.host}`);
|
||||||
|
|
|
@ -131,6 +131,83 @@ export async function getPostContext(post_id) {
|
||||||
return data;
|
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) {
|
export async function parsePost(data, parent_replies, child_replies) {
|
||||||
let client = get(Client.get());
|
let client = get(Client.get());
|
||||||
let post = new Post();
|
let post = new Post();
|
||||||
|
@ -166,6 +243,9 @@ export async function parsePost(data, parent_replies, child_replies) {
|
||||||
post.warning = data.spoiler_text;
|
post.warning = data.spoiler_text;
|
||||||
post.boost_count = data.reblogs_count;
|
post.boost_count = data.reblogs_count;
|
||||||
post.reply_count = data.replies_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.mentions = data.mentions;
|
||||||
post.files = data.media_attachments;
|
post.files = data.media_attachments;
|
||||||
post.url = data.url;
|
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)) {
|
if (data.reactions && client.instance.capabilities.includes(capabilities.REACTIONS)) {
|
||||||
post.reactions = [];
|
post.reactions = parseReactions(data.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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return post;
|
return post;
|
||||||
}
|
}
|
||||||
|
@ -251,6 +305,21 @@ export async function parseUser(data) {
|
||||||
return user;
|
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) {
|
export function parseEmoji(data) {
|
||||||
let emoji = new Emoji(
|
let emoji = new Emoji(
|
||||||
data.id,
|
data.id,
|
||||||
|
|
|
@ -100,6 +100,30 @@ export class Client {
|
||||||
return await api.getPost(post_id, parent_replies, child_replies);
|
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) {
|
putCacheUser(user) {
|
||||||
this.cache.users[user.id] = user;
|
this.cache.users[user.id] = user;
|
||||||
client.set(this);
|
client.set(this);
|
||||||
|
@ -148,7 +172,6 @@ export class Client {
|
||||||
if (!json) return false;
|
if (!json) return false;
|
||||||
let saved = JSON.parse(json);
|
let saved = JSON.parse(json);
|
||||||
if (!saved.version || saved.version !== APP_VERSION) {
|
if (!saved.version || saved.version !== APP_VERSION) {
|
||||||
localStorage.setItem(save_name + '-backup', json);
|
|
||||||
localStorage.removeItem(save_name);
|
localStorage.removeItem(save_name);
|
||||||
return false;
|
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 const EMOJI_NAME_REGEX = /:[\w\-.]{0,32}:/g;
|
||||||
|
|
||||||
export default class Emoji {
|
export default class Emoji {
|
||||||
id;
|
|
||||||
name;
|
name;
|
||||||
host;
|
|
||||||
url;
|
url;
|
||||||
|
|
||||||
constructor(id, name, host, url) {
|
constructor(id, name, host, url) {
|
||||||
|
@ -18,7 +16,10 @@ export default class Emoji {
|
||||||
}
|
}
|
||||||
|
|
||||||
get html() {
|
get html() {
|
||||||
|
if (this.url)
|
||||||
return `<img src="${this.url}" class="emoji" height="20" title="${this.name}" alt="${this.name}">`;
|
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;
|
warning;
|
||||||
boost_count;
|
boost_count;
|
||||||
reply_count;
|
reply_count;
|
||||||
|
favourite_count;
|
||||||
|
favourited;
|
||||||
|
boosted;
|
||||||
mentions;
|
mentions;
|
||||||
reactions;
|
reactions;
|
||||||
emojis;
|
emojis;
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
const sounds = {
|
const sounds = {
|
||||||
"default": new Audio("sound/log.ogg"),
|
"default": new Audio("/sound/log.ogg"),
|
||||||
"post": new Audio("sound/success.ogg"),
|
"post": new Audio("/sound/success.ogg"),
|
||||||
"boost": new Audio("sound/hello.ogg"),
|
"boost": new Audio("/sound/hello.ogg"),
|
||||||
};
|
};
|
||||||
|
|
||||||
export function play_sound(name) {
|
export function play_sound(name) {
|
||||||
|
if (name === false) return;
|
||||||
if (!name) name = "default";
|
if (!name) name = "default";
|
||||||
const sound = sounds[name];
|
const sound = sounds[name];
|
||||||
if (!sound) {
|
if (!sound) {
|
||||||
|
|
|
@ -1,21 +1,36 @@
|
||||||
<script>
|
<script>
|
||||||
import { play_sound } from '../../sound.js';
|
import { play_sound } from '../../sound.js';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
export let icon = "🔧";
|
|
||||||
export let type = "action";
|
export let type = "action";
|
||||||
export let label = "Action";
|
export let label = "Action";
|
||||||
export let title = label;
|
export let title = label;
|
||||||
export let count = 0;
|
export let count = 0;
|
||||||
|
export let active = false;
|
||||||
|
export let disabled = false;
|
||||||
export let sound = "default";
|
export let sound = "default";
|
||||||
|
|
||||||
|
function click() {
|
||||||
|
if (disabled) return;
|
||||||
|
play_sound(sound);
|
||||||
|
dispatch('click');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="{type}"
|
class={[
|
||||||
|
type,
|
||||||
|
active ? "active" : "",
|
||||||
|
disabled ? "disabled" : "",
|
||||||
|
].join(' ')}
|
||||||
aria-label="{label}"
|
aria-label="{label}"
|
||||||
title="{title}"
|
title="{title}"
|
||||||
on:click|stopPropagation={() => (play_sound(sound))}>
|
on:click={click}>
|
||||||
<span class="icon">{@html icon}</span>
|
<span class="icon">
|
||||||
|
<slot/>
|
||||||
|
</span>
|
||||||
{#if count}
|
{#if count}
|
||||||
<span class="count">{count}</span>
|
<span class="count">{count}</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -28,24 +43,34 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
|
font-family: inherit;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
background: none;
|
background: none;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
transition: background-color .1s, color .1s;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.active {
|
button.active {
|
||||||
background: var(--accent);
|
background-color: color-mix(in srgb, transparent, var(--accent) 50%);
|
||||||
color: var(--bg0);
|
color: var(--bg-1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
button:not(.disabled):hover {
|
||||||
background: #8881;
|
background-color: var(--bg-600);
|
||||||
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
button:active {
|
button:not(.disabled):active {
|
||||||
background: #0001;
|
background-color: var(--bg-1000);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.disabled {
|
||||||
|
opacity: .5;
|
||||||
|
cursor: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
|
|
|
@ -8,6 +8,9 @@
|
||||||
import { parseOne as parseEmoji } from '../../emoji.js';
|
import { parseOne as parseEmoji } from '../../emoji.js';
|
||||||
import { play_sound } from '../../sound.js';
|
import { play_sound } from '../../sound.js';
|
||||||
import { onMount } from 'svelte';
|
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 post_data;
|
||||||
export let focused = false;
|
export let focused = false;
|
||||||
|
@ -25,6 +28,55 @@
|
||||||
location = `/post/${post.id}`;
|
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;
|
let el;
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (focused) {
|
if (focused) {
|
||||||
|
@ -46,18 +98,31 @@
|
||||||
<PostHeader post={post} />
|
<PostHeader post={post} />
|
||||||
<Body post={post} />
|
<Body post={post} />
|
||||||
<footer class="post-footer">
|
<footer class="post-footer">
|
||||||
<div class="post-reactions">
|
<div class="post-reactions" on:click|stopPropagation>
|
||||||
{#each post.reactions as reaction}
|
{#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}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
<div class="post-actions">
|
<div class="post-actions" on:click|stopPropagation>
|
||||||
<ActionButton icon="🗨️" type="reply" label="Reply" bind:count={post.reply_count} sound="post" />
|
<ActionButton type="reply" label="Reply" bind:count={post.reply_count} sound="post" disabled>🗨️</ActionButton>
|
||||||
<ActionButton icon="🔁" type="boost" label="Boost" bind:count={post.boost_count} sound="boost" />
|
<ActionButton type="boost" label="Boost" on:click={() => toggleBoost()} bind:active={post.boosted} bind:count={post.boost_count} sound="boost">🔁</ActionButton>
|
||||||
<ActionButton icon="⭐" type="favourite" label="Favourite" />
|
<ActionButton type="favourite" label="Favourite" on:click={() => toggleFavourite()} bind:active={post.favourited} bind:count={post.favourite_count}>⭐</ActionButton>
|
||||||
<ActionButton icon="😃" type="react" label="React" />
|
<ActionButton type="react" label="React" disabled>😃</ActionButton>
|
||||||
<ActionButton icon="🗣️" type="quote" label="Quote" />
|
<ActionButton type="quote" label="Quote" disabled>🗣️</ActionButton>
|
||||||
<ActionButton icon="🛠️" type="more" label="More" />
|
<ActionButton type="more" label="More" disabled>🛠️</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</article>
|
</article>
|
||||||
|
@ -97,14 +162,18 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.post-reactions) {
|
:global(.post-reactions) {
|
||||||
|
width: fit-content;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.post-actions) {
|
:global(.post-actions) {
|
||||||
|
width: fit-content;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
gap: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-container :global(.emoji) {
|
.post-container :global(.emoji) {
|
||||||
|
|
|
@ -1,21 +1,35 @@
|
||||||
<script>
|
<script>
|
||||||
import { play_sound } from '../../sound.js';
|
import { play_sound } from '../../sound.js';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
export let icon = "🔧";
|
export let type = "react";
|
||||||
export let type = "action";
|
export let label = "React";
|
||||||
export let label = "Action";
|
|
||||||
export let title = label;
|
export let title = label;
|
||||||
export let count = 0;
|
export let count = 0;
|
||||||
|
export let active = false;
|
||||||
|
export let disabled = false;
|
||||||
export let sound = "default";
|
export let sound = "default";
|
||||||
|
|
||||||
|
function click() {
|
||||||
|
play_sound(sound);
|
||||||
|
dispatch('click');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="{type}"
|
class={[
|
||||||
|
type,
|
||||||
|
active ? "active" : "",
|
||||||
|
disabled ? "disabled" : "",
|
||||||
|
].join(' ')}
|
||||||
aria-label="{label}"
|
aria-label="{label}"
|
||||||
title="{title}"
|
title="{title}"
|
||||||
on:click|stopPropagation={() => (play_sound(sound))}>
|
on:click={click}>
|
||||||
<span class="icon">{@html icon}</span>
|
<span class="icon">
|
||||||
|
<slot/>
|
||||||
|
</span>
|
||||||
{#if count}
|
{#if count}
|
||||||
<span class="count">{count}</span>
|
<span class="count">{count}</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -33,19 +47,27 @@
|
||||||
color: inherit;
|
color: inherit;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
transition: background-color .1s, color .1s;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.active {
|
button.active {
|
||||||
background: var(--accent);
|
background-color: color-mix(in srgb, transparent, var(--accent) 50%);
|
||||||
color: var(--bg0);
|
color: var(--bg-1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
button:not(.disabled):hover {
|
||||||
background: #8881;
|
background-color: var(--bg-600);
|
||||||
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
button:active {
|
button:not(.disabled):active {
|
||||||
background: #0001;
|
background-color: var(--bg-1000);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.disabled {
|
||||||
|
cursor: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
import Post from './Post.svelte';
|
import Post from './Post.svelte';
|
||||||
import { parseText as parseEmojis, parseOne as parseEmoji } from '../../emoji.js';
|
import { parseText as parseEmojis, parseOne as parseEmoji } from '../../emoji.js';
|
||||||
import { shorthand as short_time } from '../../time.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;
|
export let post;
|
||||||
let time_string = post.created_at.toLocaleString();
|
let time_string = post.created_at.toLocaleString();
|
||||||
|
@ -13,6 +16,55 @@
|
||||||
function gotoPost() {
|
function gotoPost() {
|
||||||
location = `/post/${post.id}`;
|
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>
|
</script>
|
||||||
|
|
||||||
{#if post.reply}
|
{#if post.reply}
|
||||||
|
@ -28,18 +80,31 @@
|
||||||
<Body post={post} />
|
<Body post={post} />
|
||||||
|
|
||||||
<footer class="post-footer">
|
<footer class="post-footer">
|
||||||
<div class="post-reactions">
|
<div class="post-reactions" on:click|stopPropagation>
|
||||||
{#each post.reactions as reaction}
|
{#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}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
<div class="post-actions">
|
<div class="post-actions" on:click|stopPropagation>
|
||||||
<ActionButton icon="🗨️" type="reply" label="Reply" bind:count={post.reply_count} sound="post" />
|
<ActionButton type="reply" label="Reply" bind:count={post.reply_count} sound="post" disabled>🗨️</ActionButton>
|
||||||
<ActionButton icon="🔁" type="boost" label="Boost" bind:count={post.boost_count} sound="boost" />
|
<ActionButton type="boost" label="Boost" on:click={() => toggleBoost()} bind:active={post.boosted} bind:count={post.boost_count} sound="boost">🔁</ActionButton>
|
||||||
<ActionButton icon="⭐" type="favourite" label="Favourite" />
|
<ActionButton type="favourite" label="Favourite" on:click={() => toggleFavourite()} bind:active={post.favourited} bind:count={post.favourite_count}>⭐</ActionButton>
|
||||||
<ActionButton icon="😃" type="react" label="React" />
|
<ActionButton type="react" label="React" disabled>😃</ActionButton>
|
||||||
<ActionButton icon="🗣️" type="quote" label="Quote" />
|
<ActionButton type="quote" label="Quote" disabled>🗣️</ActionButton>
|
||||||
<ActionButton icon="🛠️" type="more" label="More" />
|
<ActionButton type="more" label="More" disabled>🛠️</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue