tangled
alpha
login
or
join now
mackuba.eu
/
skythread
14
fork
atom
Thread viewer for Bluesky
14
fork
atom
overview
issues
pulls
pipelines
moved all settings to a separate settings module
mackuba.eu
3 months ago
0973bf0e
155b8c40
+75
-46
11 changed files
expand all
collapse all
unified
split
src
components
AccountMenu.svelte
BiohazardDialog.svelte
posts
BlockedPostView.svelte
HiddenRepliesLink.svelte
PostComponent.svelte
models
account.svelte.ts
settings.svelte.ts
pages
LycanSearchPage.svelte
skythread.js
types.d.ts
utils
post_presenter.ts
+5
-8
src/components/AccountMenu.svelte
···
1
<script lang="ts">
2
import { showLoginDialog } from '../skythread.js';
3
import { account } from '../models/account.svelte.js';
0
4
import { getBaseLocation } from '../router.js';
5
import AccountMenuButton from './AccountMenuButton.svelte';
6
import LoadableImage from './LoadableImage.svelte';
···
28
function toggleBiohazard(e: Event) {
29
e.preventDefault();
30
31
-
let hazards = document.querySelectorAll('p.hidden-replies, .content > .post.blocked, .blocked > .load-post');
32
-
33
-
if (account.biohazardEnabled === false) {
34
-
account.biohazardEnabled = true;
35
-
Array.from(hazards).forEach(p => { (p as HTMLElement).style.display = 'block' });
36
} else {
37
-
account.biohazardEnabled = false;
38
-
Array.from(hazards).forEach(p => { (p as HTMLElement).style.display = 'none' });
39
}
40
}
41
···
94
onclick={toggleBiohazard}
95
label="Show infohazards"
96
title="Show links to blocked and hidden comments"
97
-
showCheckmark={account.biohazardEnabled !== false}
98
/>
99
100
{#if !account.loggedIn}
···
1
<script lang="ts">
2
import { showLoginDialog } from '../skythread.js';
3
import { account } from '../models/account.svelte.js';
4
+
import { settings } from '../models/settings.svelte.js';
5
import { getBaseLocation } from '../router.js';
6
import AccountMenuButton from './AccountMenuButton.svelte';
7
import LoadableImage from './LoadableImage.svelte';
···
29
function toggleBiohazard(e: Event) {
30
e.preventDefault();
31
32
+
if (settings.biohazardsEnabled === false) {
33
+
settings.biohazardsEnabled = true;
0
0
0
34
} else {
35
+
settings.biohazardsEnabled = false;
0
36
}
37
}
38
···
91
onclick={toggleBiohazard}
92
label="Show infohazards"
93
title="Show links to blocked and hidden comments"
94
+
showCheckmark={settings.biohazardsEnabled !== false}
95
/>
96
97
{#if !account.loggedIn}
+3
-3
src/components/BiohazardDialog.svelte
···
1
<script lang="ts">
2
-
import { account } from '../models/account.svelte.js';
3
4
type Props = { onConfirm?: (() => void) | undefined, onClose?: (() => void) | undefined };
5
let { onConfirm = undefined, onClose = undefined }: Props = $props();
6
7
function showBiohazard(e: Event) {
8
e.preventDefault();
9
-
account.biohazardEnabled = true;
10
11
onConfirm?.()
12
onClose?.();
···
14
15
function hideBiohazard(e: Event) {
16
e.preventDefault();
17
-
account.biohazardEnabled = false;
18
19
for (let p of document.querySelectorAll('p.hidden-replies, .content > .post.blocked, .blocked > .load-post')) {
20
(p as HTMLElement).style.display = 'none';
···
1
<script lang="ts">
2
+
import { settings } from '../models/settings.svelte.js';
3
4
type Props = { onConfirm?: (() => void) | undefined, onClose?: (() => void) | undefined };
5
let { onConfirm = undefined, onClose = undefined }: Props = $props();
6
7
function showBiohazard(e: Event) {
8
e.preventDefault();
9
+
settings.biohazardsEnabled = true;
10
11
onConfirm?.()
12
onClose?.();
···
14
15
function hideBiohazard(e: Event) {
16
e.preventDefault();
17
+
settings.biohazardsEnabled = false;
18
19
for (let p of document.querySelectorAll('p.hidden-replies, .content > .post.blocked, .blocked > .load-post')) {
20
(p as HTMLElement).style.display = 'none';
+2
-2
src/components/posts/BlockedPostView.svelte
···
1
<script lang="ts">
2
-
import { account } from '../../models/account.svelte.js';
3
import { BlockedPost, DetachedQuotePost, MissingPost, Post } from '../../models/posts.js';
0
4
5
import BlockedPostContent from './BlockedPostContent.svelte';
6
import MissingPostView from './MissingPostView.svelte';
···
15
16
let { reason, post, placement }: Props = $props();
17
18
-
let biohazardEnabled = $derived(account.biohazardEnabled !== false);
19
let loading = $state(false);
20
let postNotFound = $state(false);
21
let reloadedPost: Post | undefined = $state();
···
1
<script lang="ts">
0
2
import { BlockedPost, DetachedQuotePost, MissingPost, Post } from '../../models/posts.js';
3
+
import { settings } from '../../models/settings.svelte.js';
4
5
import BlockedPostContent from './BlockedPostContent.svelte';
6
import MissingPostView from './MissingPostView.svelte';
···
15
16
let { reason, post, placement }: Props = $props();
17
18
+
let biohazardEnabled = $derived(settings.biohazardsEnabled !== false);
19
let loading = $state(false);
20
let postNotFound = $state(false);
21
let reloadedPost: Post | undefined = $state();
+2
-2
src/components/posts/HiddenRepliesLink.svelte
···
1
<script lang="ts">
2
import { showBiohazardDialog } from '../../skythread.js';
3
-
import { account } from '../../models/account.svelte.js';
4
import { parseThreadPost } from '../../models/posts.js';
5
import { linkToPostThread } from '../../router.js';
6
import { getPostContext } from './PostComponent.svelte';
···
17
function onLinkClick(e: Event) {
18
e.preventDefault();
19
20
-
if (account.biohazardEnabled === true) {
21
loadHiddenReplies();
22
} else {
23
showBiohazardDialog(() => {
···
1
<script lang="ts">
2
import { showBiohazardDialog } from '../../skythread.js';
3
+
import { settings } from '../../models/settings.svelte.js';
4
import { parseThreadPost } from '../../models/posts.js';
5
import { linkToPostThread } from '../../router.js';
6
import { getPostContext } from './PostComponent.svelte';
···
17
function onLinkClick(e: Event) {
18
e.preventDefault();
19
20
+
if (settings.biohazardsEnabled === true) {
21
loadHiddenReplies();
22
} else {
23
showBiohazardDialog(() => {
+3
-3
src/components/posts/PostComponent.svelte
···
5
<script lang="ts">
6
import { createContext } from 'svelte';
7
import { HiddenRepliesError } from '../../api/bluesky_api.js';
8
-
import { account } from '../../models/account.svelte.js';
9
import { Post, BlockedPost } from '../../models/posts.js';
10
import { Embed, InlineLinkEmbed } from '../../models/embeds.js';
11
import { isValidURL, showError } from '../../utils.js';
···
60
if (reply instanceof Post) {
61
return true;
62
} else if (reply instanceof BlockedPost) {
63
-
return (account.biohazardEnabled !== false);
64
} else {
65
return false;
66
}
···
157
{#if placement == 'thread' && !repliesLoaded}
158
{#if post.hasMoreReplies}
159
<LoadMoreLink onLoad={onMoreRepliesLoaded} onError={onRepliesLoadingError} />
160
-
{:else if post.hasHiddenReplies && account.biohazardEnabled !== false}
161
<HiddenRepliesLink onLoad={onHiddenRepliesLoaded} onError={onRepliesLoadingError} />
162
{/if}
163
{/if}
···
5
<script lang="ts">
6
import { createContext } from 'svelte';
7
import { HiddenRepliesError } from '../../api/bluesky_api.js';
8
+
import { settings } from '../../models/settings.svelte.js';
9
import { Post, BlockedPost } from '../../models/posts.js';
10
import { Embed, InlineLinkEmbed } from '../../models/embeds.js';
11
import { isValidURL, showError } from '../../utils.js';
···
60
if (reply instanceof Post) {
61
return true;
62
} else if (reply instanceof BlockedPost) {
63
+
return (settings.biohazardsEnabled !== false);
64
} else {
65
return false;
66
}
···
157
{#if placement == 'thread' && !repliesLoaded}
158
{#if post.hasMoreReplies}
159
<LoadMoreLink onLoad={onMoreRepliesLoaded} onError={onRepliesLoadingError} />
160
+
{:else if post.hasHiddenReplies && settings.biohazardsEnabled !== false}
161
<HiddenRepliesLink onLoad={onHiddenRepliesLoaded} onError={onRepliesLoadingError} />
162
{/if}
163
{/if}
+4
-24
src/models/account.svelte.ts
···
1
import { AuthenticatedAPI } from '../api/authenticated_api.js';
2
import { pdsEndpointForIdentifier } from '../api/identity.js';
0
3
4
class Account {
5
-
#isIncognito: boolean;
6
-
#biohazardEnabled: boolean | undefined;
7
#loggedIn: boolean;
8
#avatarURL: string | undefined;
9
#avatarIsLoading: boolean;
10
11
constructor() {
12
-
let incognito = localStorage.getItem('incognito');
13
-
let biohazard = localStorage.getItem('biohazard');
14
-
let biohazardEnabled = biohazard ? !!JSON.parse(biohazard) : undefined;
15
let accountAPI = new AuthenticatedAPI();
16
17
-
this.#isIncognito = $state(accountAPI.isLoggedIn && !!incognito);
18
-
this.#biohazardEnabled = $state(biohazardEnabled);
19
this.#loggedIn = $state(accountAPI.isLoggedIn);
20
this.#avatarURL = $state(accountAPI.isLoggedIn ? accountAPI.user.avatar : undefined);
21
this.#avatarIsLoading = $state(false);
22
}
23
24
get isIncognito(): boolean {
25
-
return this.#isIncognito;
26
}
27
28
toggleIncognitoMode() {
29
-
if (!this.#isIncognito) {
30
-
localStorage.setItem('incognito', '1');
31
-
} else {
32
-
localStorage.removeItem('incognito');
33
-
}
34
-
35
location.reload();
36
-
}
37
-
38
-
get biohazardEnabled(): boolean | undefined {
39
-
return this.#biohazardEnabled;
40
-
}
41
-
42
-
set biohazardEnabled(value: boolean) {
43
-
this.#biohazardEnabled = value;
44
-
localStorage.setItem('biohazard', JSON.stringify(value));
45
}
46
47
get loggedIn(): boolean {
···
78
79
logOut() {
80
window.accountAPI.resetTokens();
81
-
localStorage.removeItem('incognito');
82
location.reload();
83
}
84
}
···
1
import { AuthenticatedAPI } from '../api/authenticated_api.js';
2
import { pdsEndpointForIdentifier } from '../api/identity.js';
3
+
import { settings } from './settings.svelte.js';
4
5
class Account {
0
0
6
#loggedIn: boolean;
7
#avatarURL: string | undefined;
8
#avatarIsLoading: boolean;
9
10
constructor() {
0
0
0
11
let accountAPI = new AuthenticatedAPI();
12
0
0
13
this.#loggedIn = $state(accountAPI.isLoggedIn);
14
this.#avatarURL = $state(accountAPI.isLoggedIn ? accountAPI.user.avatar : undefined);
15
this.#avatarIsLoading = $state(false);
16
}
17
18
get isIncognito(): boolean {
19
+
return !!settings.incognitoMode;
20
}
21
22
toggleIncognitoMode() {
23
+
settings.incognitoMode = !this.isIncognito;
0
0
0
0
0
24
location.reload();
0
0
0
0
0
0
0
0
0
25
}
26
27
get loggedIn(): boolean {
···
58
59
logOut() {
60
window.accountAPI.resetTokens();
61
+
settings.logOut();
62
location.reload();
63
}
64
}
+52
src/models/settings.svelte.ts
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
interface SettingsData {
2
+
dateLocale?: string;
3
+
incognito?: boolean;
4
+
biohazard?: boolean;
5
+
}
6
+
7
+
class Settings {
8
+
data: SettingsData;
9
+
10
+
constructor() {
11
+
let savedData = localStorage.getItem('settings');
12
+
this.data = $state(savedData ? JSON.parse(savedData) : {});
13
+
}
14
+
15
+
save() {
16
+
localStorage.setItem('settings', JSON.stringify(this.data));
17
+
}
18
+
19
+
logOut() {
20
+
delete this.data.incognito;
21
+
this.save();
22
+
}
23
+
24
+
get dateLocale(): string | undefined {
25
+
return this.data.dateLocale;
26
+
}
27
+
28
+
set dateLocale(value: string) {
29
+
this.data.dateLocale = value;
30
+
this.save();
31
+
}
32
+
33
+
get incognitoMode(): boolean | undefined {
34
+
return this.data.incognito;
35
+
}
36
+
37
+
set incognitoMode(value: boolean) {
38
+
this.data.incognito = value;
39
+
this.save();
40
+
}
41
+
42
+
get biohazardsEnabled(): boolean | undefined {
43
+
return this.data.biohazard;
44
+
}
45
+
46
+
set biohazardsEnabled(value: boolean) {
47
+
this.data.biohazard = value;
48
+
this.save();
49
+
}
50
+
}
51
+
52
+
export const settings = new Settings();
+2
-1
src/pages/LycanSearchPage.svelte
···
1
<script lang="ts">
2
import { Post } from '../models/posts';
0
3
import { Lycan } from '../services/lycan';
4
import PostComponent from '../components/posts/PostComponent.svelte';
5
···
119
if (info.progress == 1.0) {
120
importStatusLabel = `Import complete ✓`;
121
} else if (info.position) {
122
-
let date = new Date(info.position).toLocaleString(window.dateLocale, { day: 'numeric', month: 'short', year: 'numeric' });
123
importStatusLabel = `Downloaded data until: ${date}`;
124
} else if (info.status == 'requested') {
125
importStatusLabel = 'Requesting import…';
···
1
<script lang="ts">
2
import { Post } from '../models/posts';
3
+
import { settings } from '../models/settings.svelte';
4
import { Lycan } from '../services/lycan';
5
import PostComponent from '../components/posts/PostComponent.svelte';
6
···
120
if (info.progress == 1.0) {
121
importStatusLabel = `Import complete ✓`;
122
} else if (info.position) {
123
+
let date = new Date(info.position).toLocaleString(settings.dateLocale, { day: 'numeric', month: 'short', year: 'numeric' });
124
importStatusLabel = `Downloaded data until: ${date}`;
125
} else if (info.status == 'requested') {
126
importStatusLabel = 'Requesting import…';
-1
src/skythread.js
···
26
27
28
function init() {
29
-
window.dateLocale = localStorage.getItem('locale') || undefined;
30
window.avatarPreloader = buildAvatarPreloader();
31
32
svelte.mount(AccountMenu, { target: $id('account_menu_wrap') });
···
26
27
28
function init() {
0
29
window.avatarPreloader = buildAvatarPreloader();
30
31
svelte.mount(AccountMenu, { target: $id('account_menu_wrap') });
-1
src/types.d.ts
···
1
interface Window {
2
-
dateLocale: string | undefined;
3
root: AnyPost;
4
subtreeRoot: AnyPost;
5
init: () => void;
···
1
interface Window {
0
2
root: AnyPost;
3
subtreeRoot: AnyPost;
4
init: () => void;
+2
-1
src/utils/post_presenter.ts
···
1
import { sameDay } from '../utils.js';
2
import { Post } from '../models/posts.js';
0
3
4
export class PostPresenter {
5
···
34
35
get formattedTimestamp() {
36
let timeFormat = this.timeFormatForTimestamp;
37
-
return this.post.createdAt.toLocaleString(window.dateLocale, timeFormat);
38
}
39
}
···
1
import { sameDay } from '../utils.js';
2
import { Post } from '../models/posts.js';
3
+
import { settings } from '../models/settings.svelte.js';
4
5
export class PostPresenter {
6
···
35
36
get formattedTimestamp() {
37
let timeFormat = this.timeFormatForTimestamp;
38
+
return this.post.createdAt.toLocaleString(settings.dateLocale, timeFormat);
39
}
40
}