Compare commits
No commits in common. "998e8f2517bee19b54539e54de0a9b130ee1f272" and "5424772abbeb1c4eb12be0ddc3ec33579b4b0384" have entirely different histories.
998e8f2517
...
5424772abb
|
@ -91,27 +91,8 @@ export async function verifyCredentials() {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getNotifications(since_id, limit, types) {
|
|
||||||
if (!get(client).user) return false;
|
|
||||||
|
|
||||||
let url = `https://${get(client).instance.host}/api/v1/notifications`;
|
|
||||||
|
|
||||||
let params = new URLSearchParams();
|
|
||||||
if (since_id) params.append("since_id", since_id);
|
|
||||||
if (limit) params.append("limit", limit);
|
|
||||||
if (types) params.append("types", types.join(','));
|
|
||||||
const params_string = params.toString();
|
|
||||||
if (params_string) url += '?' + params_string;
|
|
||||||
|
|
||||||
const data = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: { "Authorization": "Bearer " + get(client).app.token }
|
|
||||||
}).then(res => res.json());
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getTimeline(last_post_id) {
|
export async function getTimeline(last_post_id) {
|
||||||
|
if (!get(client).instance || !get(client).app) return false;
|
||||||
let url = `https://${get(client).instance.host}/api/v1/timelines/home`;
|
let url = `https://${get(client).instance.host}/api/v1/timelines/home`;
|
||||||
if (last_post_id) url += "?max_id=" + last_post_id;
|
if (last_post_id) url += "?max_id=" + last_post_id;
|
||||||
const data = await fetch(url, {
|
const data = await fetch(url, {
|
||||||
|
@ -219,7 +200,6 @@ export async function parsePost(data, ancestor_count) {
|
||||||
let post = new Post();
|
let post = new Post();
|
||||||
|
|
||||||
post.text = data.content;
|
post.text = data.content;
|
||||||
post.html = data.content;
|
|
||||||
|
|
||||||
post.reply = null;
|
post.reply = null;
|
||||||
if ((data.in_reply_to_id || data.reply) &&
|
if ((data.in_reply_to_id || data.reply) &&
|
||||||
|
@ -279,7 +259,7 @@ export async function parseUser(data) {
|
||||||
|
|
||||||
user = new User();
|
user = new User();
|
||||||
user.id = data.id;
|
user.id = data.id;
|
||||||
user.nickname = data.display_name.trim();
|
user.nickname = data.display_name;
|
||||||
user.username = data.username;
|
user.username = data.username;
|
||||||
user.avatar_url = data.avatar;
|
user.avatar_url = data.avatar;
|
||||||
user.url = data.url;
|
user.url = data.url;
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import { Instance, server_types } from './instance.js';
|
import { Instance, server_types } from './instance.js';
|
||||||
import * as api from './api.js';
|
import * as api from './api.js';
|
||||||
import { get, writable } from 'svelte/store';
|
import { get, writable } from 'svelte/store';
|
||||||
import { last_read_notif_id } from '$lib/notifications.js';
|
|
||||||
import { user } from '$lib/stores/user.js';
|
|
||||||
|
|
||||||
export const client = writable(false);
|
export const client = writable(false);
|
||||||
|
|
||||||
|
@ -95,10 +93,6 @@ export class Client {
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getNotifications(since_id, limit, types) {
|
|
||||||
return await api.getNotifications(since_id, limit, types);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getTimeline(last_post_id) {
|
async getTimeline(last_post_id) {
|
||||||
return await api.getTimeline(last_post_id);
|
return await api.getTimeline(last_post_id);
|
||||||
}
|
}
|
||||||
|
@ -179,7 +173,6 @@ export class Client {
|
||||||
host: this.instance.host,
|
host: this.instance.host,
|
||||||
version: this.instance.version,
|
version: this.instance.version,
|
||||||
},
|
},
|
||||||
last_read_notif_id: get(last_read_notif_id),
|
|
||||||
app: this.app,
|
app: this.app,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -194,7 +187,6 @@ export class Client {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this.instance = new Instance(saved.instance.host, saved.instance.version);
|
this.instance = new Instance(saved.instance.host, saved.instance.version);
|
||||||
last_read_notif_id.set(saved.last_read_notif_id || 0);
|
|
||||||
this.app = saved.app;
|
this.app = saved.app;
|
||||||
client.set(this);
|
client.set(this);
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
import { client } from '$lib/client/client.js';
|
|
||||||
import * as api from '$lib/client/api.js';
|
|
||||||
import { get, writable } from 'svelte/store';
|
|
||||||
|
|
||||||
export let notifications = writable([]);
|
|
||||||
export let unread_notif_count = writable(0);
|
|
||||||
export let last_read_notif_id = writable(0);
|
|
||||||
|
|
||||||
let loading;
|
|
||||||
export async function getNotifications() {
|
|
||||||
if (loading) return; // no spamming!!
|
|
||||||
loading = true;
|
|
||||||
|
|
||||||
api.getNotifications().then(async data => {
|
|
||||||
if (!data || data.length <= 0) return;
|
|
||||||
notifications.set([]);
|
|
||||||
for (let i in data) {
|
|
||||||
let notif = data[i];
|
|
||||||
notif.accounts = [ await api.parseUser(notif.account) ];
|
|
||||||
if (get(notifications).length > 0) {
|
|
||||||
let prev = get(notifications)[get(notifications).length - 1];
|
|
||||||
if (notif.type === prev.type) {
|
|
||||||
if (prev.status && notif.status && prev.status.id === notif.status.id) {
|
|
||||||
notifications.update(notifications => {
|
|
||||||
notifications[notifications.length - 1].accounts.push(notif.accounts[0]);
|
|
||||||
return notifications;
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
notif.status = await api.parsePost(notif.status, 0, false);
|
|
||||||
notifications.update(notifications => [...notifications, notif]);
|
|
||||||
}
|
|
||||||
last_read_notif_id.set(data[0].id);
|
|
||||||
unread_notif_count.set(0);
|
|
||||||
get(client).save();
|
|
||||||
loading = false;
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
import { writable } from 'svelte/store';
|
|
||||||
|
|
||||||
export let user = writable(0);
|
|
||||||
export let logged_in = writable(false);
|
|
|
@ -2,7 +2,7 @@ import { client } from '$lib/client/client.js';
|
||||||
import { get, writable } from 'svelte/store';
|
import { get, writable } from 'svelte/store';
|
||||||
import { parsePost } from '$lib/client/api.js';
|
import { parsePost } from '$lib/client/api.js';
|
||||||
|
|
||||||
export let timeline = writable([]);
|
export let posts = writable([]);
|
||||||
|
|
||||||
let loading = false;
|
let loading = false;
|
||||||
|
|
||||||
|
@ -11,8 +11,8 @@ export async function getTimeline(clean) {
|
||||||
loading = true;
|
loading = true;
|
||||||
|
|
||||||
let timeline_data;
|
let timeline_data;
|
||||||
if (clean || get(timeline).length === 0) timeline_data = await get(client).getTimeline()
|
if (clean || get(posts).length === 0) timeline_data = await get(client).getTimeline()
|
||||||
else timeline_data = await get(client).getTimeline(get(timeline)[get(timeline).length - 1].id);
|
else timeline_data = await get(client).getTimeline(get(posts)[get(posts).length - 1].id);
|
||||||
|
|
||||||
if (!timeline_data) {
|
if (!timeline_data) {
|
||||||
console.error(`Failed to retrieve timeline.`);
|
console.error(`Failed to retrieve timeline.`);
|
||||||
|
@ -20,7 +20,7 @@ export async function getTimeline(clean) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clean) timeline.set([]);
|
if (clean) posts.set([]);
|
||||||
|
|
||||||
for (let i in timeline_data) {
|
for (let i in timeline_data) {
|
||||||
const post_data = timeline_data[i];
|
const post_data = timeline_data[i];
|
||||||
|
@ -36,7 +36,7 @@ export async function getTimeline(clean) {
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
timeline.update(current => [...current, post]);
|
posts.update(current => [...current, post]);
|
||||||
}
|
}
|
||||||
loading = false;
|
loading = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { play_sound } from '../sound.js';
|
import { play_sound } from '../sound.js';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import { afterUpdate } from 'svelte';
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
export let active = false;
|
export let active = false;
|
||||||
|
@ -14,6 +12,10 @@
|
||||||
export let href = false;
|
export let href = false;
|
||||||
|
|
||||||
let classes = [];
|
let classes = [];
|
||||||
|
if (active) classes = ["active"];
|
||||||
|
if (filled) classes = ["filled"];
|
||||||
|
if (disabled) classes = ["disabled"];
|
||||||
|
if (centered) classes.push("centered");
|
||||||
|
|
||||||
function click() {
|
function click() {
|
||||||
if (disabled) return;
|
if (disabled) return;
|
||||||
|
@ -24,14 +26,6 @@
|
||||||
play_sound(sound);
|
play_sound(sound);
|
||||||
dispatch('click');
|
dispatch('click');
|
||||||
}
|
}
|
||||||
|
|
||||||
afterUpdate(() => {
|
|
||||||
classes = [];
|
|
||||||
if (active) classes = ["active"];
|
|
||||||
if (filled) classes = ["filled"];
|
|
||||||
if (disabled) classes = ["disabled"];
|
|
||||||
if (centered) classes.push("centered");
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
<script>
|
<script>
|
||||||
import Button from './Button.svelte';
|
import Button from './Button.svelte';
|
||||||
import Post from './post/Post.svelte';
|
import Post from './post/Post.svelte';
|
||||||
import { getTimeline } from '$lib/timeline.js';
|
import { posts, getTimeline } from '$lib/timeline.js';
|
||||||
|
|
||||||
export let posts = [];
|
getTimeline();
|
||||||
|
document.addEventListener("scroll", event => {
|
||||||
|
if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 2048) {
|
||||||
|
getTimeline();
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
|
@ -21,7 +26,7 @@
|
||||||
<span>getting the feed...</span>
|
<span>getting the feed...</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#each posts as post}
|
{#each $posts as post}
|
||||||
<Post post_data={post} />
|
<Post post_data={post} />
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
@ -29,7 +34,6 @@
|
||||||
<style>
|
<style>
|
||||||
header {
|
header {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 64px;
|
|
||||||
margin: 16px 0 8px 0;
|
margin: 16px 0 8px 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
|
@ -5,12 +5,9 @@
|
||||||
import { client } from '$lib/client/client.js';
|
import { client } from '$lib/client/client.js';
|
||||||
import { play_sound } from '$lib/sound.js';
|
import { play_sound } from '$lib/sound.js';
|
||||||
import { getTimeline } from '$lib/timeline.js';
|
import { getTimeline } from '$lib/timeline.js';
|
||||||
import { getNotifications } from '$lib/notifications.js';
|
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
import { logged_in } from '$lib/stores/user.js';
|
import { onMount } from 'svelte';
|
||||||
import { unread_notif_count, last_read_notif_id } from '$lib/notifications.js';
|
|
||||||
|
|
||||||
import TimelineIcon from '../../img/icons/timeline.svg';
|
import TimelineIcon from '../../img/icons/timeline.svg';
|
||||||
import NotificationsIcon from '../../img/icons/notifications.svg';
|
import NotificationsIcon from '../../img/icons/notifications.svg';
|
||||||
|
@ -24,26 +21,27 @@
|
||||||
import SettingsIcon from '../../img/icons/settings.svg';
|
import SettingsIcon from '../../img/icons/settings.svg';
|
||||||
import LogoutIcon from '../../img/icons/logout.svg';
|
import LogoutIcon from '../../img/icons/logout.svg';
|
||||||
|
|
||||||
|
export let path;
|
||||||
|
|
||||||
const VERSION = APP_VERSION;
|
const VERSION = APP_VERSION;
|
||||||
|
|
||||||
|
let notification_count = 0;
|
||||||
|
if (notification_count > 99) notification_count = "99+";
|
||||||
|
|
||||||
function handle_btn(name) {
|
function handle_btn(name) {
|
||||||
if (!get(logged_in)) return;
|
|
||||||
let route;
|
let route;
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case "timeline":
|
case "timeline":
|
||||||
|
if (!get(client).user) break;
|
||||||
route = "/";
|
route = "/";
|
||||||
getTimeline(true);
|
getTimeline(true);
|
||||||
break;
|
break;
|
||||||
case "notifications":
|
case "notifcations":
|
||||||
route = "/notifications";
|
|
||||||
getNotifications();
|
|
||||||
break;
|
|
||||||
case "explore":
|
case "explore":
|
||||||
case "lists":
|
case "lists":
|
||||||
case "favourites":
|
case "favourites":
|
||||||
case "bookmarks":
|
case "bookmarks":
|
||||||
case "hashtags":
|
case "hashtags":
|
||||||
default:
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!route) return;
|
if (!route) return;
|
||||||
|
@ -68,11 +66,11 @@
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{#if $logged_in}
|
|
||||||
<div id="nav-items">
|
<div id="nav-items">
|
||||||
<Button label="Timeline"
|
<Button label="Timeline"
|
||||||
on:click={() => handle_btn("timeline")}
|
on:click={() => handle_btn("timeline")}
|
||||||
active={$page.url.pathname === "/"}>
|
active={path == "/" && $client.user}
|
||||||
|
disabled={!$client.user}>
|
||||||
<svelte:fragment slot="icon">
|
<svelte:fragment slot="icon">
|
||||||
<TimelineIcon/>
|
<TimelineIcon/>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
|
@ -80,15 +78,14 @@
|
||||||
</Button>
|
</Button>
|
||||||
<Button label="Notifications"
|
<Button label="Notifications"
|
||||||
on:click={() => handle_btn("notifications")}
|
on:click={() => handle_btn("notifications")}
|
||||||
active={$page.url.pathname === "/notifications"}>
|
active={path == "/notifications"}
|
||||||
|
disabled>
|
||||||
<svelte:fragment slot="icon">
|
<svelte:fragment slot="icon">
|
||||||
<NotificationsIcon/>
|
<NotificationsIcon/>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
Notifications
|
Notifications
|
||||||
{#if $unread_notif_count}
|
{#if notification_count}
|
||||||
<span class="notification-count">
|
<span class="notification-count">{notification_count}</span>
|
||||||
{$unread_notif_count <= 99 ? $unread_notif_count : "99+"}
|
|
||||||
</span>
|
|
||||||
{/if}
|
{/if}
|
||||||
</Button>
|
</Button>
|
||||||
<Button label="Explore" disabled>
|
<Button label="Explore" disabled>
|
||||||
|
@ -130,6 +127,7 @@
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if $client.user}
|
||||||
<div id="account-items">
|
<div id="account-items">
|
||||||
<div class="flex-row">
|
<div class="flex-row">
|
||||||
<Button centered label="Profile information" disabled>
|
<Button centered label="Profile information" disabled>
|
||||||
|
@ -224,7 +222,6 @@
|
||||||
transform: translate(22px, -16px);
|
transform: translate(22px, -16px);
|
||||||
min-width: 12px;
|
min-width: 12px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
margin-left: auto;
|
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
|
@ -1,227 +0,0 @@
|
||||||
<script>
|
|
||||||
import * as api from '$lib/client/api.js';
|
|
||||||
|
|
||||||
import ReplyIcon from '$lib/../img/icons/reply.svg';
|
|
||||||
import RepostIcon from '$lib/../img/icons/repost.svg';
|
|
||||||
import FavouriteIcon from '$lib/../img/icons/like.svg';
|
|
||||||
import ReactIcon from '$lib/../img/icons/react.svg';
|
|
||||||
import QuoteIcon from '$lib/../img/icons/quote.svg';
|
|
||||||
import ReactionBar from '$lib/ui/post/ReactionBar.svelte';
|
|
||||||
import ActionBar from '$lib/ui/post/ActionBar.svelte';
|
|
||||||
|
|
||||||
let mention = (accounts) => {
|
|
||||||
let res = `<a href=${account.url}>${account.rich_name}</a>`;
|
|
||||||
if (accounts.length > 1) res += ` and <strong>${accounts.length - 1}</strong> others`;
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
export let data;
|
|
||||||
let activity_text = function (type) {
|
|
||||||
switch (type) {
|
|
||||||
case "mention":
|
|
||||||
return `%1 mentioned you.`;
|
|
||||||
case "reblog":
|
|
||||||
return `%1 boosted your post.`;
|
|
||||||
case "follow":
|
|
||||||
return `%1 followed you.`;
|
|
||||||
case "follow_request":
|
|
||||||
return `%1 requested to follow you.`;
|
|
||||||
case "favourite":
|
|
||||||
return `%1 favourited your post.`;
|
|
||||||
case "poll":
|
|
||||||
return `%1's poll as ended.`;
|
|
||||||
case "update":
|
|
||||||
return `%1 updated their post.`;
|
|
||||||
default:
|
|
||||||
return `%1 poked you!`;
|
|
||||||
}
|
|
||||||
}(data.type);
|
|
||||||
|
|
||||||
let account = data.accounts[0];
|
|
||||||
$: accounts_short = data.accounts.slice(0, 3).reverse();
|
|
||||||
|
|
||||||
let aria_label = function () {
|
|
||||||
if (accounts.length == 1)
|
|
||||||
return activity_text.replace("%1", account.username) + ' ' + new Date(data.created_at);
|
|
||||||
else
|
|
||||||
return activity_text.replace("%1", `${account.username} and ${accounts.length - 1} others`) + ' ' + new Date(data.created_at);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<a class="notification" href={`/post/${data.status.id}`} aria-label={aria_label}>
|
|
||||||
<header aria-hidden>
|
|
||||||
<span class="notif-icon">
|
|
||||||
{#if data.type === "favourite"}
|
|
||||||
<FavouriteIcon />
|
|
||||||
{:else if data.type === "reblog"}
|
|
||||||
<RepostIcon />
|
|
||||||
{:else if data.type === "react"}
|
|
||||||
<ReactIcon />
|
|
||||||
{:else if data.type === "mention"}
|
|
||||||
<ReplyIcon />
|
|
||||||
{:else}
|
|
||||||
<ReactIcon />
|
|
||||||
{/if}
|
|
||||||
</span>
|
|
||||||
<span class="notif-avatars">
|
|
||||||
{#if data.accounts.length == 1}
|
|
||||||
<a href={data.accounts[0].url} class="notif-avatar">
|
|
||||||
<img src={data.accounts[0].avatar_url} alt="" width="28" height="28" />
|
|
||||||
</a>
|
|
||||||
{:else}
|
|
||||||
{#each accounts_short as account}
|
|
||||||
<img src={account.avatar_url} alt="" width="28" height="28" />
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
</span>
|
|
||||||
<span class="notif-activity">{@html activity_text.replace("%1", mention(data.accounts))}</span>
|
|
||||||
</header>
|
|
||||||
{#if data.status}
|
|
||||||
<div class="notif-content">
|
|
||||||
{@html data.status.html}
|
|
||||||
</div>
|
|
||||||
{#if data.type === "mention"}
|
|
||||||
{#if data.status.reactions}
|
|
||||||
<ReactionBar post={data.status} />
|
|
||||||
{/if}
|
|
||||||
<ActionBar post={data.status} />
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.notification {
|
|
||||||
display: block;
|
|
||||||
margin: 8px 0;
|
|
||||||
padding: 16px;
|
|
||||||
border-radius: 8px;
|
|
||||||
background: var(--bg-800);
|
|
||||||
text-decoration: inherit;
|
|
||||||
color: inherit;
|
|
||||||
transition: background-color .1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification:hover {
|
|
||||||
background-color: color-mix(in srgb, var(--bg-800), black 5%);
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
header .notif-icon {
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
display: inline-flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
header .notif-avatars {
|
|
||||||
display: inline-flex;
|
|
||||||
flex-direction: row-reverse;
|
|
||||||
}
|
|
||||||
|
|
||||||
header .notif-avatar {
|
|
||||||
line-height: 0;
|
|
||||||
}
|
|
||||||
header .notif-avatars img {
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
header .notif-avatars img:not(:first-child) {
|
|
||||||
box-shadow: 4px 0 8px -2px rgba(0,0,0,.33);
|
|
||||||
}
|
|
||||||
header .notif-avatars img:not(:last-child) {
|
|
||||||
margin-left: -8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
header .notif-activity {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
header :global(a) {
|
|
||||||
font-weight: bold;
|
|
||||||
color: var(--text);
|
|
||||||
}
|
|
||||||
|
|
||||||
header :global(.emoji) {
|
|
||||||
margin: -.2em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notif-content {
|
|
||||||
margin: 16px 0 4px 0;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.45em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notif-content :global(p) {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notif-content :global(.emoji) {
|
|
||||||
position: relative;
|
|
||||||
top: 6px;
|
|
||||||
margin-top: -10px;
|
|
||||||
height: 24px!important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notif-content :global(blockquote) {
|
|
||||||
margin: .4em 0;
|
|
||||||
padding: .1em 0 .1em 1em;
|
|
||||||
border-left: 4px solid #8888;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notif-content :global(blockquote span) {
|
|
||||||
opacity: .5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notif-content :global(code) {
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notif-content :global(pre:has(code)) {
|
|
||||||
margin: 8px 0;
|
|
||||||
padding: 8px;
|
|
||||||
display: block;
|
|
||||||
overflow-x: scroll;
|
|
||||||
border-radius: 8px;
|
|
||||||
background-color: #080808;
|
|
||||||
color: var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.notif-content :global(pre code) {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notif-content :global(a) {
|
|
||||||
color: var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.notif-content :global(a.mention) {
|
|
||||||
color: inherit;
|
|
||||||
font-weight: 600;
|
|
||||||
padding: 3px 6px;
|
|
||||||
background: var(--bg-700);
|
|
||||||
border-radius: 6px;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notif-content :global(a.mention:hover) {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notif-content :global(a.hashtag) {
|
|
||||||
background-color: transparent;
|
|
||||||
padding: 0;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notif-content :global(.mention-avatar) {
|
|
||||||
position: relative;
|
|
||||||
top: 4px;
|
|
||||||
height: 20px;
|
|
||||||
margin-right: 4px;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -84,8 +84,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-text {
|
.post-text {
|
||||||
font-size: .9em;
|
line-height: 1.2em;
|
||||||
line-height: 1.45em;
|
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,11 +34,10 @@
|
||||||
<style>
|
<style>
|
||||||
.post-reactions {
|
.post-reactions {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
min-height: 32px;
|
height: 32px;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -4,12 +4,12 @@
|
||||||
import Widgets from '$lib/ui/Widgets.svelte';
|
import Widgets from '$lib/ui/Widgets.svelte';
|
||||||
import { client, Client } from '$lib/client/client.js';
|
import { client, Client } from '$lib/client/client.js';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
import { logged_in } from '$lib/stores/user.js';
|
|
||||||
import { unread_notif_count, last_read_notif_id } from '$lib/notifications.js';
|
export let data;
|
||||||
|
$: path = data.path || "/";
|
||||||
|
|
||||||
let ready = new Promise(resolve => {
|
let ready = new Promise(resolve => {
|
||||||
if (get(client)) {
|
if (get(client)) {
|
||||||
if (get(client).user) logged_in.set(true);
|
|
||||||
return resolve();
|
return resolve();
|
||||||
}
|
}
|
||||||
let new_client = new Client();
|
let new_client = new Client();
|
||||||
|
@ -21,18 +21,8 @@
|
||||||
client.set(new_client);
|
client.set(new_client);
|
||||||
return resolve();
|
return resolve();
|
||||||
}
|
}
|
||||||
if (user) logged_in.set(true);
|
|
||||||
new_client.user = user;
|
new_client.user = user;
|
||||||
window.peekie = new_client;
|
window.peekie = new_client;
|
||||||
|
|
||||||
// spin up async task to fetch notifications
|
|
||||||
get(client).getNotifications(
|
|
||||||
get(last_read_notif_id)
|
|
||||||
).then(notif_data => {
|
|
||||||
if (!notif_data) return;
|
|
||||||
unread_notif_count.set(notif_data.length);
|
|
||||||
});
|
|
||||||
|
|
||||||
client.update(client => {
|
client.update(client => {
|
||||||
client.user = user;
|
client.user = user;
|
||||||
return client;
|
return client;
|
||||||
|
@ -45,7 +35,7 @@
|
||||||
<div id="app">
|
<div id="app">
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<Navigation />
|
<Navigation path={path} />
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
export const prerender = true;
|
export const prerender = true;
|
||||||
export const ssr = false;
|
export const ssr = false;
|
||||||
|
|
||||||
|
export async function load({ url }) {
|
||||||
|
return { path: url.pathname };
|
||||||
|
}
|
||||||
|
|
|
@ -1,25 +1,14 @@
|
||||||
<script>
|
<script>
|
||||||
import { page } from '$app/stores';
|
|
||||||
import { get } from 'svelte/store';
|
|
||||||
import { client } from '$lib/client/client.js';
|
import { client } from '$lib/client/client.js';
|
||||||
import { timeline, getTimeline } from '$lib/timeline.js';
|
|
||||||
|
|
||||||
import LoginForm from '$lib/ui/LoginForm.svelte';
|
import LoginForm from '$lib/ui/LoginForm.svelte';
|
||||||
import Feed from '$lib/ui/Feed.svelte';
|
import Feed from '$lib/ui/Feed.svelte';
|
||||||
import User from '$lib/user/user.js';
|
import User from '$lib/user/user.js';
|
||||||
import Button from '$lib/ui/Button.svelte';
|
import Button from '$lib/ui/Button.svelte';
|
||||||
|
|
||||||
getTimeline();
|
|
||||||
document.addEventListener("scroll", event => {
|
|
||||||
if (get(page).url.pathname !== "/") return;
|
|
||||||
if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 2048) {
|
|
||||||
getTimeline();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $client.user}
|
{#if $client.user}
|
||||||
<Feed posts={$timeline} />
|
<Feed />
|
||||||
{:else}
|
{:else}
|
||||||
<LoginForm />
|
<LoginForm />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -28,22 +28,8 @@
|
||||||
client.user = user
|
client.user = user
|
||||||
return client;
|
return client;
|
||||||
});
|
});
|
||||||
|
|
||||||
return get(client).getNotifications(
|
|
||||||
get(last_read_notification_id)
|
|
||||||
).then(notif_data => {
|
|
||||||
client.update(client => {
|
|
||||||
// we've just logged in, so assume all past notifications are read.
|
|
||||||
// i *would* just use the mastodon marker API to get the last read
|
|
||||||
// notification, but this does not appear to be widely supported.
|
|
||||||
if (notif_data.constructor === Array && notif_data.length > 0)
|
|
||||||
last_read_notification_id.set(notif_data[0].id);
|
|
||||||
client.save();
|
|
||||||
return client;
|
|
||||||
});
|
|
||||||
goto("/");
|
goto("/");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,57 +0,0 @@
|
||||||
<script>
|
|
||||||
import { notifications, getNotifications } from '$lib/notifications.js';
|
|
||||||
import Notification from '$lib/ui/Notification.svelte';
|
|
||||||
|
|
||||||
getNotifications();
|
|
||||||
/*
|
|
||||||
document.addEventListener("scroll", event => {
|
|
||||||
if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 2048) {
|
|
||||||
getNotifications();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<header>
|
|
||||||
<h1>Notifications</h1>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="notifications">
|
|
||||||
{#if $notifications.length === 0}
|
|
||||||
<div class="loading throb">
|
|
||||||
<span>fetching notifications...</span>
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
{#each $notifications as notif}
|
|
||||||
<Notification data={notif} />
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
header {
|
|
||||||
width: 100%;
|
|
||||||
height: 64px;
|
|
||||||
margin: 16px 0 8px 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notifications {
|
|
||||||
margin: 16px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading {
|
|
||||||
width: 100%;
|
|
||||||
height: 80vh;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 2em;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -2,8 +2,6 @@
|
||||||
import { client } from '$lib/client/client.js';
|
import { client } from '$lib/client/client.js';
|
||||||
import * as api from '$lib/client/api.js';
|
import * as api from '$lib/client/api.js';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
import { goto, afterNavigate } from '$app/navigation';
|
|
||||||
import { base } from '$app/paths'
|
|
||||||
|
|
||||||
import Post from '$lib/ui/post/Post.svelte';
|
import Post from '$lib/ui/post/Post.svelte';
|
||||||
import Button from '$lib/ui/Button.svelte';
|
import Button from '$lib/ui/Button.svelte';
|
||||||
|
@ -15,12 +13,6 @@
|
||||||
goto("/");
|
goto("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
let previous_page = base;
|
|
||||||
|
|
||||||
afterNavigate(({from}) => {
|
|
||||||
previous_page = from?.url.pathname || previous_page
|
|
||||||
})
|
|
||||||
|
|
||||||
$: post = (async resolve => {
|
$: post = (async resolve => {
|
||||||
const post_data = await get(client).getPost(data.post_id, 0, false);
|
const post_data = await get(client).getPost(data.post_id, 0, false);
|
||||||
if (!post_data) {
|
if (!post_data) {
|
||||||
|
@ -57,19 +49,16 @@
|
||||||
{#if !error}
|
{#if !error}
|
||||||
<header>
|
<header>
|
||||||
{#await post then post}
|
{#await post then post}
|
||||||
<nav>
|
<h1>Post by {@html post.user.rich_name}</h1>
|
||||||
<Button centered on:click={() => {goto(previous_page)}}>Back</Button>
|
|
||||||
</nav>
|
|
||||||
<img src={post.user.avatar_url} type={post.user.avatar_type} alt="" width="40" height="40" class="header-avatar" loading="lazy" decoding="async">
|
|
||||||
<h1>
|
|
||||||
Post by {@html post.user.rich_name}
|
|
||||||
</h1>
|
|
||||||
{/await}
|
{/await}
|
||||||
|
<nav>
|
||||||
|
<Button centered>Back</Button>
|
||||||
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div id="feed" role="feed">
|
<div id="feed" role="feed">
|
||||||
{#await post}
|
{#await post}
|
||||||
<div class="loading throb">
|
<div class="throb">
|
||||||
<span>loading post...</span>
|
<span>loading post...</span>
|
||||||
</div>
|
</div>
|
||||||
{:then post}
|
{:then post}
|
||||||
|
@ -89,19 +78,11 @@
|
||||||
<style>
|
<style>
|
||||||
header {
|
header {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 64px;
|
|
||||||
margin: 16px 0 8px 0;
|
margin: 16px 0 8px 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
header .header-avatar {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
margin: auto 0;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
header h1 {
|
header h1 {
|
||||||
margin: auto auto auto 8px;
|
margin: auto auto auto 8px;
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
|
@ -111,7 +92,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
header nav {
|
header nav {
|
||||||
margin-right: 8px;
|
margin-left: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
@ -121,7 +102,7 @@
|
||||||
margin-bottom: 20vh;
|
margin-bottom: 20vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading {
|
.throb {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 80vh;
|
height: 80vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
Loading…
Reference in a new issue