Compare commits
7 commits
9ba2e89f5b
...
fbaecb1e94
Author | SHA1 | Date | |
---|---|---|---|
ari melody | fbaecb1e94 | ||
ari melody | e5d6ac9de0 | ||
ari melody | 596459e4d7 | ||
ari melody | 73afcf6123 | ||
ari melody | 7715e747a2 | ||
ari melody | c8f38bfd4a | ||
b062153098 |
4
package-lock.json
generated
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"name": "spacesocial-client",
|
||||
"name": "campfire-client",
|
||||
"version": "0.2.0_rev3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "spacesocial-client",
|
||||
"name": "campfire-client",
|
||||
"version": "0.2.0_rev3",
|
||||
"license": "GPL-3.0",
|
||||
"devDependencies": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "campfire-client",
|
||||
"version": "0.2.0_rev3",
|
||||
"version": "0.2.0_rev4",
|
||||
"description": "social media for the galaxy-wide-web! 🌌",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
@ -1,3 +1,3 @@
|
|||
<svg viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M92.1414 16C96.4815 16 100 19.5746 100 23.9838C100 40.8497 100 80.4817 100 104.016C100 107.201 98.1365 110.082 95.2586 111.345C92.3797 112.609 89.038 112.013 86.7552 109.83C76.4888 100.013 64 88.0719 64 88.0719C64 88.0719 51.5112 100.013 41.2448 109.83C38.962 112.013 35.6204 112.609 32.7414 111.345C29.8635 110.082 28 107.201 28 104.016C28 80.4817 28 40.8497 28 23.9838C28 19.5746 31.5185 16 35.8586 16C49.7059 16 78.2941 16 92.1414 16Z" fill="#D9D9D9"/>
|
||||
<svg viewBox="0 0 128 128" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M92.1414 16C96.4815 16 100 19.5746 100 23.9838C100 40.8497 100 80.4817 100 104.016C100 107.201 98.1365 110.082 95.2586 111.345C92.3797 112.609 89.038 112.013 86.7552 109.83C76.4888 100.013 64 88.0719 64 88.0719C64 88.0719 51.5112 100.013 41.2448 109.83C38.962 112.013 35.6204 112.609 32.7414 111.345C29.8635 110.082 28 107.201 28 104.016C28 80.4817 28 40.8497 28 23.9838C28 19.5746 31.5185 16 35.8586 16C49.7059 16 78.2941 16 92.1414 16Z"/>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 587 B After Width: | Height: | Size: 580 B |
|
@ -1,6 +1,6 @@
|
|||
<svg viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg viewBox="0 0 128 128" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_135_36)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M35.2278 87.9024H24.0002C21.4714 87.9024 19.0909 86.7072 17.5817 84.6778C16.0718 82.6493 15.6106 80.0255 16.3372 77.6038L17.0496 75.2299C18.0649 71.8454 21.179 69.5284 24.7125 69.5284H39.6624L42.7666 56.6666H31.3495C28.8207 56.6666 26.441 55.4714 24.9311 53.442C23.4219 51.4135 22.96 48.7897 23.6873 46.368L24.3989 43.9941C25.4143 40.6096 28.5291 38.2927 32.0619 38.2927H47.2012L50.38 25.1231C51.247 21.531 54.4612 19 58.1565 19H58.7262C61.1784 19 63.495 20.1245 65.012 22.051C66.529 23.9775 67.0782 26.4929 66.5027 28.8769L64.2308 38.2927H78.4208L81.5995 25.1231C82.4666 21.531 85.6807 19 89.376 19H89.9458C92.3979 19 94.7145 20.1245 96.2315 22.051C97.7485 23.9775 98.2977 26.4929 97.7223 28.8769L95.4503 38.2927H103.553C106.081 38.2927 108.461 39.4879 109.971 41.5173C111.48 43.5458 111.941 46.1696 111.215 48.5913L110.503 50.9652C109.488 54.3497 106.373 56.6666 102.84 56.6666H91.0157L87.9115 69.5284H96.2032C98.7319 69.5284 101.112 70.7236 102.621 72.7531C104.131 74.7815 104.592 77.4053 103.865 79.827L103.153 82.201C102.138 85.5854 99.0236 87.9024 95.4908 87.9024H83.4769L80.2982 101.072C79.4312 104.664 76.217 107.195 72.5217 107.195H71.952C69.4998 107.195 67.1832 106.071 65.6662 104.144C64.1492 102.218 63.6 99.7022 64.1755 97.3181L66.4474 87.9024H52.2574L49.0787 101.072C48.2116 104.664 44.9974 107.195 41.3022 107.195H40.7324C38.2803 107.195 35.9636 106.071 34.4466 104.144C32.9297 102.218 32.3805 99.7022 32.9559 97.3181L35.2278 87.9024ZM59.7962 56.6666L56.692 69.5284H70.882L73.9862 56.6666H59.7962Z" fill="#E2DFE3"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M35.2278 87.9024H24.0002C21.4714 87.9024 19.0909 86.7072 17.5817 84.6778C16.0718 82.6493 15.6106 80.0255 16.3372 77.6038L17.0496 75.2299C18.0649 71.8454 21.179 69.5284 24.7125 69.5284H39.6624L42.7666 56.6666H31.3495C28.8207 56.6666 26.441 55.4714 24.9311 53.442C23.4219 51.4135 22.96 48.7897 23.6873 46.368L24.3989 43.9941C25.4143 40.6096 28.5291 38.2927 32.0619 38.2927H47.2012L50.38 25.1231C51.247 21.531 54.4612 19 58.1565 19H58.7262C61.1784 19 63.495 20.1245 65.012 22.051C66.529 23.9775 67.0782 26.4929 66.5027 28.8769L64.2308 38.2927H78.4208L81.5995 25.1231C82.4666 21.531 85.6807 19 89.376 19H89.9458C92.3979 19 94.7145 20.1245 96.2315 22.051C97.7485 23.9775 98.2977 26.4929 97.7223 28.8769L95.4503 38.2927H103.553C106.081 38.2927 108.461 39.4879 109.971 41.5173C111.48 43.5458 111.941 46.1696 111.215 48.5913L110.503 50.9652C109.488 54.3497 106.373 56.6666 102.84 56.6666H91.0157L87.9115 69.5284H96.2032C98.7319 69.5284 101.112 70.7236 102.621 72.7531C104.131 74.7815 104.592 77.4053 103.865 79.827L103.153 82.201C102.138 85.5854 99.0236 87.9024 95.4908 87.9024H83.4769L80.2982 101.072C79.4312 104.664 76.217 107.195 72.5217 107.195H71.952C69.4998 107.195 67.1832 106.071 65.6662 104.144C64.1492 102.218 63.6 99.7022 64.1755 97.3181L66.4474 87.9024H52.2574L49.0787 101.072C48.2116 104.664 44.9974 107.195 41.3022 107.195H40.7324C38.2803 107.195 35.9636 106.071 34.4466 104.144C32.9297 102.218 32.3805 99.7022 32.9559 97.3181L35.2278 87.9024ZM59.7962 56.6666L56.692 69.5284H70.882L73.9862 56.6666H59.7962Z"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_135_36">
|
||||
|
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
@ -1,4 +1,5 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 128 128">
|
||||
<path d="M0 0h128v128H0z"/>
|
||||
<path fill-rule="evenodd" d="M100 64c0 19.882-16.118 36-36 36S28 83.882 28 64s16.118-36 36-36 36 16.118 36 36ZM72 46a8 8 0 1 1-16 0 8 8 0 0 1 16 0Zm-8 12a8 8 0 0 0-8 8v16a8 8 0 1 0 16 0V66a8 8 0 0 0-8-8Z" clip-rule="evenodd"/>
|
||||
<svg viewBox="0 0 128 128" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;" fill="currentColor">
|
||||
<g transform="matrix(1,0,0,1,16,16)">
|
||||
<path d="M84,48C84,67.882 67.882,84 48,84C28.118,84 12,67.882 12,48C12,28.118 28.118,12 48,12C67.882,12 84,28.118 84,48ZM56,30C56,34.418 52.418,38 48,38C43.582,38 40,34.418 40,30C40,25.582 43.582,22 48,22C52.418,22 56,25.582 56,30ZM48,42C43.582,42 40,45.582 40,50L40,66C40,70.418 43.582,74 48,74C52.418,74 56,70.418 56,66L56,50C56,45.582 52.418,42 48,42Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 349 B After Width: | Height: | Size: 708 B |
|
@ -145,7 +145,6 @@
|
|||
{`@${client.user.username}@${client.user.host}`}
|
||||
</span>
|
||||
</div>
|
||||
<!-- <button class="settings" aria-label={`Account: ${client.user.username}@${client.user.host}`} on:click={() => play_sound()}>🔧</button> -->
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -153,7 +152,8 @@
|
|||
campfire v{VERSION}
|
||||
<br>
|
||||
<ul>
|
||||
<li><a href="https://git.arimelody.me/ari/spacesocial-client">source</a></li>
|
||||
<li><a href="https://git.arimelody.me/blisstown/campfire">source</a></li>
|
||||
<li><a href="https://github.com/blisstown/campfire/issues">issues</a></li>
|
||||
</ul>
|
||||
</span>
|
||||
</div>
|
||||
|
@ -188,12 +188,17 @@
|
|||
border-radius: 8px;
|
||||
}
|
||||
|
||||
:global(.app-logo) {
|
||||
max-width: 80%;
|
||||
max-height: 80%;
|
||||
.app-logo {
|
||||
max-width: 70%;
|
||||
max-height: 70%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.app-logo :global(svg) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#nav-items {
|
||||
margin-bottom: auto;
|
||||
padding: 16px;
|
||||
|
|
101
src/lib/ui/post/ActionBar.svelte
Normal file
|
@ -0,0 +1,101 @@
|
|||
<script>
|
||||
import { Client } from '../../client/client.js';
|
||||
import * as api from '../../client/api.js';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
import ActionButton from './ActionButton.svelte';
|
||||
|
||||
import ReplyIcon from '../../../img/icons/reply.svg';
|
||||
import RepostIcon from '../../../img/icons/repost.svg';
|
||||
import FavouriteIcon from '../../../img/icons/like.svg';
|
||||
import FavouriteIconFill from '../../../img/icons/like_fill.svg';
|
||||
import ReactIcon from '../../../img/icons/react.svg';
|
||||
import QuoteIcon from '../../../img/icons/quote.svg';
|
||||
import MoreIcon from '../../../img/icons/more.svg';
|
||||
|
||||
export let post;
|
||||
|
||||
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.reblog ? data.reblog.reblogged : data.boosted;
|
||||
post.boost_count = data.reblog ? data.reblog.reblogs_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>
|
||||
|
||||
<div class="post-actions" aria-label="Post actions" on:mouseup|stopPropagation on:keydown|stopPropagation>
|
||||
<ActionButton type="reply" label="Reply" bind:count={post.reply_count} sound="post" disabled>
|
||||
<ReplyIcon/>
|
||||
</ActionButton>
|
||||
<ActionButton type="boost" label="Boost" on:click={toggleBoost} bind:active={post.boosted} bind:count={post.boost_count} sound="boost">
|
||||
<RepostIcon/>
|
||||
<svelte:fragment slot="activeIcon">
|
||||
<RepostIcon/>
|
||||
</svelte:fragment>
|
||||
</ActionButton>
|
||||
<ActionButton type="favourite" label="Favourite" on:click={toggleFavourite} bind:active={post.favourited} bind:count={post.favourite_count}>
|
||||
<FavouriteIcon/>
|
||||
<svelte:fragment slot="activeIcon">
|
||||
<FavouriteIconFill/>
|
||||
</svelte:fragment>
|
||||
</ActionButton>
|
||||
<ActionButton type="quote" label="Quote" disabled>
|
||||
<QuoteIcon/>
|
||||
</ActionButton>
|
||||
<ActionButton type="more" label="More" disabled>
|
||||
<MoreIcon/>
|
||||
</ActionButton>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.post-actions {
|
||||
width: fit-content;
|
||||
height: 36px;
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 4px;
|
||||
}
|
||||
</style>
|
|
@ -74,7 +74,8 @@
|
|||
|
||||
button.disabled {
|
||||
opacity: .5;
|
||||
cursor: initial;
|
||||
/* cursor: initial; */
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.icon {
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
<div class="post-body">
|
||||
{#if post.warning}
|
||||
<button class="post-warning" on:click|stopPropagation={() => { open_warned = !open_warned }}>
|
||||
<button class="post-warning" on:click|stopPropagation={() => { open_warned = !open_warned }} on:mouseup|stopPropagation>
|
||||
<strong>
|
||||
{post.warning}
|
||||
<span class="warning-instructions">
|
||||
|
@ -25,23 +25,25 @@
|
|||
{#if post.text}
|
||||
<span class="post-text">{@html rich_text}</span>
|
||||
{/if}
|
||||
<div class="post-media-container" data-count={post.files.length}>
|
||||
{#each post.files as file}
|
||||
<div class="post-media {file.type}" on:click|stopPropagation={null}>
|
||||
{#if file.type === "image"}
|
||||
<a href={file.url} target="_blank">
|
||||
<img src={file.url} alt={file.description} title={file.description} height="200" loading="lazy" decoding="async">
|
||||
</a>
|
||||
{:else if file.type === "video"}
|
||||
<video controls height="200">
|
||||
<source src={file.url} alt={file.description} title={file.description} type={file.url.endsWith('.mp4') ? 'video/mp4' : 'video/webm'}>
|
||||
<p>{file.description}   <a href={file.url}>[link]</a></p>
|
||||
<!-- <media src={file.url} alt={file.description} loading="lazy" decoding="async"> -->
|
||||
</video>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{#if post.files && post.files.length > 0}
|
||||
<div class="post-media-container" data-count={post.files.length}>
|
||||
{#each post.files as file}
|
||||
<div class="post-media {file.type}" on:click|stopPropagation on:mouseup|stopPropagation>
|
||||
{#if file.type === "image"}
|
||||
<a href={file.url} target="_blank">
|
||||
<img src={file.url} alt={file.description} title={file.description} height="200" loading="lazy" decoding="async">
|
||||
</a>
|
||||
{:else if file.type === "video"}
|
||||
<video controls height="200">
|
||||
<source src={file.url} alt={file.description} title={file.description} type={file.url.endsWith('.mp4') ? 'video/mp4' : 'video/webm'}>
|
||||
<p>{file.description}   <a href={file.url}>[link]</a></p>
|
||||
<!-- <media src={file.url} alt={file.description} loading="lazy" decoding="async"> -->
|
||||
</video>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{#if post.boost && post.text}
|
||||
<p class="post-warning"><strong>this is quoting a post! quotes are not supported yet.</strong></p>
|
||||
<!-- TODO: quotes support -->
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
<script>
|
||||
import { parseOne as parseEmoji } from '../../emoji.js';
|
||||
import { play_sound } from '../../sound.js';
|
||||
import { onMount } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
import BoostContext from './BoostContext.svelte';
|
||||
import ReplyContext from './ReplyContext.svelte';
|
||||
import PostHeader from './PostHeader.svelte';
|
||||
import Body from './Body.svelte';
|
||||
import ReactionButton from './ReactionButton.svelte';
|
||||
import ActionButton from './ActionButton.svelte';
|
||||
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';
|
||||
import { goto } from '$app/navigation';
|
||||
import ActionBar from './ActionBar.svelte';
|
||||
import ReactionBar from './ReactionBar.svelte';
|
||||
|
||||
import ReplyIcon from '../../../img/icons/reply.svg';
|
||||
import RepostIcon from '../../../img/icons/repost.svg';
|
||||
|
@ -32,62 +30,15 @@
|
|||
post = post_data.boost;
|
||||
}
|
||||
|
||||
let mouse_pos = { top: 0, left: 0 };
|
||||
|
||||
function gotoPost() {
|
||||
if (focused) return;
|
||||
if (event.key && event.key !== "Enter") return;
|
||||
if (event && event.key && event.key !== "Enter") return;
|
||||
console.log(`/post/${post.id}`);
|
||||
goto(`/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.reblog ? data.reblog.reblogged : data.boosted;
|
||||
post.boost_count = data.reblog ? data.reblog.reblogs_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) {
|
||||
|
@ -109,55 +60,14 @@
|
|||
class={"post" + (focused ? " focused" : "")}
|
||||
aria-label={aria_label}
|
||||
bind:this={el}
|
||||
on:click={gotoPost}
|
||||
on:mousedown={e => {mouse_pos.left = e.pageX; mouse_pos.top = e.pageY; console.log(mouse_pos)}}
|
||||
on:mouseup={e => {if (e.pageX == mouse_pos.left && e.pageY == mouse_pos.top) gotoPost()}}
|
||||
on:keydown={gotoPost}>
|
||||
<PostHeader post={post} />
|
||||
<Body post={post} />
|
||||
<footer class="post-footer">
|
||||
<div class="post-reactions" aria-label="Reactions" on:click|stopPropagation on:keydown|stopPropagation>
|
||||
{#each post.reactions as reaction}
|
||||
<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" aria-label="Post actions" on:click|stopPropagation on:keydown|stopPropagation>
|
||||
<ActionButton type="reply" label="Reply" bind:count={post.reply_count} sound="post" disabled>
|
||||
<ReplyIcon/>
|
||||
</ActionButton>
|
||||
<ActionButton type="boost" label="Boost" on:click={toggleBoost} bind:active={post.boosted} bind:count={post.boost_count} sound="boost">
|
||||
<RepostIcon/>
|
||||
<svelte:fragment slot="activeIcon">
|
||||
<RepostIcon/>
|
||||
</svelte:fragment>
|
||||
</ActionButton>
|
||||
<ActionButton type="favourite" label="Favourite" on:click={toggleFavourite} bind:active={post.favourited} bind:count={post.favourite_count}>
|
||||
<FavouriteIcon/>
|
||||
<svelte:fragment slot="activeIcon">
|
||||
<FavouriteIconFill/>
|
||||
</svelte:fragment>
|
||||
</ActionButton>
|
||||
<ActionButton type="react" label="React" disabled>
|
||||
<ReactIcon/>
|
||||
</ActionButton>
|
||||
<ActionButton type="quote" label="Quote" disabled>
|
||||
<QuoteIcon/>
|
||||
</ActionButton>
|
||||
<ActionButton type="more" label="More" disabled>
|
||||
<MoreIcon/>
|
||||
</ActionButton>
|
||||
</div>
|
||||
<ReactionBar post={post} />
|
||||
<ActionBar post={post} />
|
||||
</footer>
|
||||
</article>
|
||||
</div>
|
||||
|
@ -197,21 +107,6 @@
|
|||
margin-top: -32px;
|
||||
}
|
||||
|
||||
: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) {
|
||||
height: 20px;
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
let time_string = post.created_at.toLocaleString();
|
||||
</script>
|
||||
|
||||
<div class={"post-header-container" + (reply ? " reply" : "")}>
|
||||
<div class={"post-header-container" + (reply ? " reply" : "")} on:mouseup|stopPropagation>
|
||||
<a href={post.user.url} target="_blank" class="post-avatar-container">
|
||||
<img src={post.user.avatar_url} type={post.user.avatar_type} alt="" width="48" height="48" class="post-avatar" loading="lazy" decoding="async">
|
||||
</a>
|
||||
|
|
43
src/lib/ui/post/ReactionBar.svelte
Normal file
|
@ -0,0 +1,43 @@
|
|||
<script>
|
||||
import ReactionButton from './ReactionButton.svelte';
|
||||
import ReactIcon from '../../../img/icons/react.svg';
|
||||
|
||||
export let post;
|
||||
</script>
|
||||
|
||||
<div class="post-reactions" aria-label="Reactions" on:mouseup|stopPropagation on:keydown|stopPropagation>
|
||||
{#each post.reactions as reaction}
|
||||
<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}
|
||||
<ReactionButton
|
||||
type="reaction"
|
||||
title="react"
|
||||
label="React"
|
||||
disabled>
|
||||
<ReactIcon/>
|
||||
</ReactionButton>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.post-reactions {
|
||||
width: fit-content;
|
||||
height: 32px;
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 2px;
|
||||
}
|
||||
</style>
|
|
@ -67,7 +67,8 @@
|
|||
}
|
||||
|
||||
button.disabled {
|
||||
cursor: initial;
|
||||
/* cursor: initial; */
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.icon {
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
<script>
|
||||
import PostHeader from './PostHeader.svelte';
|
||||
import Body from './Body.svelte';
|
||||
import ReactionButton from './ReactionButton.svelte';
|
||||
import ActionButton from './ActionButton.svelte';
|
||||
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';
|
||||
|
@ -11,72 +6,23 @@
|
|||
import * as api from '../../client/api.js';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
import ReplyIcon from '../../../img/icons/reply.svg';
|
||||
import RepostIcon from '../../../img/icons/repost.svg';
|
||||
import FavouriteIcon from '../../../img/icons/like.svg';
|
||||
import FavouriteIconFill from '../../../img/icons/like_fill.svg';
|
||||
import ReactIcon from '../../../img/icons/react.svg';
|
||||
import QuoteIcon from '../../../img/icons/quote.svg';
|
||||
import MoreIcon from '../../../img/icons/more.svg';
|
||||
import PostHeader from './PostHeader.svelte';
|
||||
import Body from './Body.svelte';
|
||||
import Post from './Post.svelte';
|
||||
import ActionBar from './ActionBar.svelte';
|
||||
import ReactionBar from './ReactionBar.svelte';
|
||||
|
||||
export let post;
|
||||
let time_string = post.created_at.toLocaleString();
|
||||
let aria_label = post.user.username + '; ' + post.text + '; ' + post.created_at;
|
||||
|
||||
let mouse_pos = { top: 0, left: 0 };
|
||||
|
||||
function gotoPost() {
|
||||
if (event.key && event.key !== "Enter") return;
|
||||
if (event && event.key && event.key !== "Enter") return;
|
||||
console.log(`/post/${post.id}`);
|
||||
goto(`/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}
|
||||
|
@ -86,7 +32,8 @@
|
|||
<article
|
||||
class="post-reply"
|
||||
aria-label={aria_label}
|
||||
on:click={gotoPost}
|
||||
on:mousedown={e => {mouse_pos.left = e.pageX; mouse_pos.top = e.pageY; console.log(mouse_pos)}}
|
||||
on:mouseup={e => {if (e.pageX == mouse_pos.left && e.pageY == mouse_pos.top) gotoPost()}}
|
||||
on:keydown={gotoPost}>
|
||||
<div class="line"></div>
|
||||
|
||||
|
@ -96,50 +43,8 @@
|
|||
<Body post={post} />
|
||||
|
||||
<footer class="post-footer">
|
||||
<div class="post-reactions" aria-label="Reactions" on:click|stopPropagation on:keydown|stopPropagation>
|
||||
{#each post.reactions as reaction}
|
||||
<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" aria-label="Post actions" on:click|stopPropagation on:keydown|stopPropagation>
|
||||
<ActionButton type="reply" label="Reply" bind:count={post.reply_count} sound="post" disabled>
|
||||
<ReplyIcon/>
|
||||
</ActionButton>
|
||||
<ActionButton type="boost" label="Boost" on:click={toggleBoost} bind:active={post.boosted} bind:count={post.boost_count} sound="boost">
|
||||
<RepostIcon/>
|
||||
<svelte:fragment slot="activeIcon">
|
||||
<RepostIcon/>
|
||||
</svelte:fragment>
|
||||
</ActionButton>
|
||||
<ActionButton type="favourite" label="Favourite" on:click={toggleFavourite} bind:active={post.favourited} bind:count={post.favourite_count}>
|
||||
<FavouriteIcon/>
|
||||
<svelte:fragment slot="activeIcon">
|
||||
<FavouriteIconFill/>
|
||||
</svelte:fragment>
|
||||
</ActionButton>
|
||||
<ActionButton type="react" label="React" disabled>
|
||||
<ReactIcon/>
|
||||
</ActionButton>
|
||||
<ActionButton type="quote" label="Quote" disabled>
|
||||
<QuoteIcon/>
|
||||
</ActionButton>
|
||||
<ActionButton type="more" label="More" disabled>
|
||||
<MoreIcon/>
|
||||
</ActionButton>
|
||||
</div>
|
||||
<ReactionBar post={post} />
|
||||
<ActionBar post={post} />
|
||||
</footer>
|
||||
</div>
|
||||
</article>
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
import '$lib/app.css';
|
||||
import Navigation from '$lib/ui/Navigation.svelte';
|
||||
import Widgets from '$lib/ui/Widgets.svelte';
|
||||
import { Client } from '$lib/client/client.js';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
let client = get(Client.get());
|
||||
</script>
|
||||
|
||||
<div id="app">
|
||||
|
@ -11,7 +15,13 @@
|
|||
</header>
|
||||
|
||||
<main>
|
||||
<slot></slot>
|
||||
{#await client.verifyCredentials()}
|
||||
<div class="loading throb">
|
||||
<span>just a moment...</span>
|
||||
</div>
|
||||
{:then}
|
||||
<slot></slot>
|
||||
{/await}
|
||||
</main>
|
||||
|
||||
<div id="widgets">
|
||||
|
|
|
@ -2,12 +2,16 @@ import Post from '$lib/ui/post/Post.svelte';
|
|||
import { Client } from '$lib/client/client.js';
|
||||
import { parsePost } from '$lib/client/api.js';
|
||||
import { get } from 'svelte/store';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
export const ssr = false;
|
||||
|
||||
export async function load({ params }) {
|
||||
let client = get(Client.get());
|
||||
await client.verifyCredentials();
|
||||
|
||||
if (!client.instance || !client.user) {
|
||||
goto("/");
|
||||
}
|
||||
|
||||
const post_id = params.id;
|
||||
|
||||
|
|