+4
-2
src/api/queries/timeline.ts
+4
-2
src/api/queries/timeline.ts
···
14
14
import { globalEvents } from '~/globals/events';
15
15
16
16
import { useAgent } from '~/lib/states/agent';
17
-
import { useModerationOptions } from '~/lib/states/moderation';
18
17
import { useSession } from '~/lib/states/session';
18
+
import { inject } from '~/lib/states/singleton';
19
+
import ModerationService from '~/lib/states/singletons/moderation';
19
20
import { tinyhash } from '~/lib/utils/hash';
20
21
import { assert } from '~/lib/utils/invariant';
21
22
···
132
133
const { rpc } = useAgent();
133
134
const { currentAccount } = useSession();
134
135
const queryClient = useQueryClient();
135
-
const moderationOptions = useModerationOptions();
136
+
137
+
const moderationOptions = inject(ModerationService);
136
138
137
139
const limit = MAX_TIMELINE_POSTS;
138
140
+3
-2
src/components/bookmarks/bookmark-feed-item.tsx
+3
-2
src/components/bookmarks/bookmark-feed-item.tsx
···
12
12
13
13
import type { HydratedBookmarkItem } from '~/lib/aglais-bookmarks/db';
14
14
import { isElementAltClicked, isElementClicked } from '~/lib/interaction';
15
-
import { useModerationOptions } from '~/lib/states/moderation';
15
+
import { inject } from '~/lib/states/singleton';
16
+
import ModerationService from '~/lib/states/singletons/moderation';
16
17
17
18
import Avatar, { getUserAvatarType } from '../avatar';
18
19
import Embed from '../embeds/embed';
···
29
30
}
30
31
31
32
const BookmarkFeedItem = ({ item }: BookmarkFeedItemProps) => {
32
-
const moderationOptions = useModerationOptions();
33
+
const moderationOptions = inject(ModerationService);
33
34
34
35
const { post, stale } = item;
35
36
+3
-2
src/components/composer/composer-reply-context.tsx
+3
-2
src/components/composer/composer-reply-context.tsx
···
8
8
import { createPostQuery } from '~/api/queries/post';
9
9
import { formatQueryError } from '~/api/utils/error';
10
10
11
-
import { useModerationOptions } from '~/lib/states/moderation';
11
+
import { inject } from '~/lib/states/singleton';
12
+
import ModerationService from '~/lib/states/singletons/moderation';
12
13
13
14
import Avatar, { getUserAvatarType } from '../avatar';
14
15
import Button from '../button';
···
23
24
}
24
25
25
26
const ComposerReplyContext = (props: ComposerReplyContextProps) => {
26
-
const moderationOptions = useModerationOptions();
27
+
const moderationOptions = inject(ModerationService);
27
28
28
29
const query = createPostQuery(() => props.replyUri);
29
30
+4
-2
src/components/embeds/feed-embed.tsx
+4
-2
src/components/embeds/feed-embed.tsx
···
7
7
import { precacheFeed } from '~/api/queries-cache/feed-precache';
8
8
import { parseAtUri } from '~/api/utils/strings';
9
9
10
-
import { useModerationOptions } from '~/lib/states/moderation';
10
+
import { inject } from '~/lib/states/singleton';
11
+
import ModerationService from '~/lib/states/singletons/moderation';
11
12
12
13
import Avatar from '../avatar';
13
14
···
20
21
21
22
const FeedEmbed = ({ feed, interactive }: FeedEmbedProps) => {
22
23
const queryClient = useQueryClient();
23
-
const moderationOptions = useModerationOptions();
24
+
25
+
const moderationOptions = inject(ModerationService);
24
26
25
27
const moderation = createMemo(() => moderateGeneric(feed, feed.creator.did, moderationOptions()));
26
28
+4
-2
src/components/embeds/list-embed.tsx
+4
-2
src/components/embeds/list-embed.tsx
···
4
4
5
5
import { moderateGeneric } from '~/api/moderation/entities/generic';
6
6
7
-
import { useModerationOptions } from '~/lib/states/moderation';
7
+
import { inject } from '~/lib/states/singleton';
8
+
import ModerationService from '~/lib/states/singletons/moderation';
8
9
9
10
import Avatar from '~/components/avatar';
10
11
import { getListPurposeLabel, getListUrl } from '~/components/lists/lib/utils';
···
18
19
}
19
20
20
21
const ListEmbed = ({ list, interactive, onClick }: ListEmbedProps) => {
21
-
const moderationOptions = useModerationOptions();
22
+
const moderationOptions = inject(ModerationService);
23
+
22
24
const moderation = createMemo(() => moderateGeneric(list, list.creator.did, moderationOptions()));
23
25
24
26
const href = getListUrl(list);
+4
-2
src/components/embeds/quote-embed.tsx
+4
-2
src/components/embeds/quote-embed.tsx
···
13
13
import { moderateQuote } from '~/api/moderation/entities/quote';
14
14
import { parseAtUri } from '~/api/utils/strings';
15
15
16
-
import { useModerationOptions } from '~/lib/states/moderation';
16
+
import { inject } from '~/lib/states/singleton';
17
+
import ModerationService from '~/lib/states/singletons/moderation';
17
18
18
19
import Avatar, { getUserAvatarType } from '../avatar';
19
20
import TimeAgo from '../time-ago';
···
31
32
}
32
33
33
34
const QuoteEmbed = ({ quote, interactive, large }: QuoteEmbedProps) => {
35
+
const moderationOptions = inject(ModerationService);
36
+
34
37
const record = quote.value as AppBskyFeedPost.Record;
35
38
const embed = quote.embeds?.[0];
36
39
const author = quote.author;
···
42
45
const image = getPostImage(embed);
43
46
const video = getPostVideo(embed);
44
47
45
-
const moderationOptions = useModerationOptions();
46
48
const moderation = createMemo(() => moderateQuote(quote, moderationOptions()));
47
49
48
50
const shouldBlurMedia = () => getModerationUI(moderation(), ContextContentMedia).b.length !== 0;
+4
-2
src/components/feeds/feed-item.tsx
+4
-2
src/components/feeds/feed-item.tsx
···
11
11
12
12
import { isElementAltClicked, isElementClicked } from '~/lib/interaction';
13
13
import { formatLong } from '~/lib/intl/number';
14
-
import { useModerationOptions } from '~/lib/states/moderation';
14
+
import { inject } from '~/lib/states/singleton';
15
+
import ModerationService from '~/lib/states/singletons/moderation';
15
16
16
17
import Avatar from '~/components/avatar';
17
18
···
22
23
23
24
const FeedItem = ({ item }: FeedItemProps) => {
24
25
const queryClient = useQueryClient();
25
-
const moderationOptions = useModerationOptions();
26
+
27
+
const moderationOptions = inject(ModerationService);
26
28
27
29
const creator = item.creator;
28
30
const href = `/${creator.did}/feeds/${parseAtUri(item.uri).rkey}`;
+4
-2
src/components/lists/list-item.tsx
+4
-2
src/components/lists/list-item.tsx
···
9
9
import { history } from '~/globals/navigation';
10
10
11
11
import { isElementAltClicked, isElementClicked } from '~/lib/interaction';
12
-
import { useModerationOptions } from '~/lib/states/moderation';
12
+
import { inject } from '~/lib/states/singleton';
13
+
import ModerationService from '~/lib/states/singletons/moderation';
13
14
14
15
import Avatar from '~/components/avatar';
15
16
import BlockOutlinedIcon from '~/components/icons-central/block-outline';
···
24
25
25
26
const ListItem = ({ item }: ListItemProps) => {
26
27
const queryClient = useQueryClient();
27
-
const moderationOptions = useModerationOptions();
28
+
29
+
const moderationOptions = inject(ModerationService);
28
30
29
31
const creator = item.creator;
30
32
const href = getListUrl(item);
+4
-2
src/components/notifications/notification-item.tsx
+4
-2
src/components/notifications/notification-item.tsx
···
21
21
import { history } from '~/globals/navigation';
22
22
23
23
import { INTERACTION_TAGS, isElementAltClicked, isElementClicked } from '~/lib/interaction';
24
-
import { useModerationOptions } from '~/lib/states/moderation';
25
24
import { useSession } from '~/lib/states/session';
25
+
import { inject } from '~/lib/states/singleton';
26
+
import ModerationService from '~/lib/states/singletons/moderation';
26
27
import { assert } from '~/lib/utils/invariant';
27
28
import { truncateMiddle } from '~/lib/utils/strings';
28
29
···
150
151
151
152
const renderAvatars = (notifs: AppBskyNotificationListNotifications.Notification[]) => {
152
153
const queryClient = useQueryClient();
153
-
const moderationOptions = useModerationOptions();
154
+
155
+
const moderationOptions = inject(ModerationService);
154
156
155
157
const avatars = notifs.slice(0, MAX_AVATARS).map(({ author }) => {
156
158
const { did, avatar, displayName, handle } = author;
+3
-2
src/components/profiles/profile-item-pressable.tsx
+3
-2
src/components/profiles/profile-item-pressable.tsx
···
5
5
import { moderateProfile } from '~/api/moderation/entities/profile';
6
6
7
7
import { isElementClicked } from '~/lib/interaction';
8
-
import { useModerationOptions } from '~/lib/states/moderation';
8
+
import { inject } from '~/lib/states/singleton';
9
+
import ModerationService from '~/lib/states/singletons/moderation';
9
10
10
11
import Avatar, { getUserAvatarType } from '../avatar';
11
12
···
21
22
}
22
23
23
24
const ProfileItemPressable = (props: ProfileItemPressableProps) => {
24
-
const moderationOptions = useModerationOptions();
25
+
const moderationOptions = inject(ModerationService);
25
26
26
27
const profile = props.item;
27
28
const onClick = props.onClick;
+3
-2
src/components/profiles/profile-item.tsx
+3
-2
src/components/profiles/profile-item.tsx
···
9
9
import { history } from '~/globals/navigation';
10
10
11
11
import { isElementAltClicked, isElementClicked } from '~/lib/interaction';
12
-
import { useModerationOptions } from '~/lib/states/moderation';
12
+
import { inject } from '~/lib/states/singleton';
13
+
import ModerationService from '~/lib/states/singletons/moderation';
13
14
14
15
import Avatar, { getUserAvatarType } from '../avatar';
15
16
···
22
23
23
24
const ProfileItem = (props: ProfileItemProps) => {
24
25
const queryClient = useQueryClient();
25
-
const moderationOptions = useModerationOptions();
26
+
const moderationOptions = inject(ModerationService);
26
27
27
28
const profile = props.item;
28
29
+4
-2
src/components/profiles/profile-view-header.tsx
+4
-2
src/components/profiles/profile-view-header.tsx
···
11
11
import { openModal } from '~/globals/modals';
12
12
13
13
import { formatCompact } from '~/lib/intl/number';
14
-
import { useModerationOptions } from '~/lib/states/moderation';
15
14
import { useSession } from '~/lib/states/session';
15
+
import { inject } from '~/lib/states/singleton';
16
+
import ModerationService from '~/lib/states/singletons/moderation';
16
17
import { truncateMiddle } from '~/lib/utils/strings';
17
18
18
19
import DefaultLabelerAvatar from '~/assets/default-labeler-avatar.svg?url';
···
44
45
};
45
46
46
47
const ProfileViewHeader = (props: ProfileViewHeader) => {
47
-
const moderationOptions = useModerationOptions();
48
48
const { currentAccount } = useSession();
49
+
50
+
const moderationOptions = inject(ModerationService);
49
51
50
52
const data = () => props.data;
51
53
const viewer = () => data().viewer;
+4
-2
src/components/threads/highlighted-post.tsx
+4
-2
src/components/threads/highlighted-post.tsx
···
14
14
15
15
import { formatCompact } from '~/lib/intl/number';
16
16
import type { ContentTranslationPreferences } from '~/lib/preferences/account';
17
-
import { useModerationOptions } from '~/lib/states/moderation';
18
17
import { useSession } from '~/lib/states/session';
18
+
import { inject } from '~/lib/states/singleton';
19
+
import ModerationService from '~/lib/states/singletons/moderation';
19
20
20
21
import Avatar, { getUserAvatarType } from '../avatar';
21
22
import ComposerDialogLazy from '../composer/composer-dialog-lazy';
···
52
53
const HighlightedPost = (props: HighlightedPostProps) => {
53
54
const post = () => props.post;
54
55
55
-
const moderationOptions = useModerationOptions();
56
56
const { currentAccount } = useSession();
57
+
58
+
const moderationOptions = inject(ModerationService);
57
59
58
60
const author = () => post().author;
59
61
const record = () => post().record as AppBskyFeedPost.Record;
+4
-2
src/components/threads/post-thread-item.tsx
+4
-2
src/components/threads/post-thread-item.tsx
···
14
14
import { history } from '~/globals/navigation';
15
15
16
16
import { isElementAltClicked, isElementClicked } from '~/lib/interaction';
17
-
import { useModerationOptions } from '~/lib/states/moderation';
18
17
import { useSession } from '~/lib/states/session';
18
+
import { inject } from '~/lib/states/singleton';
19
+
import ModerationService from '~/lib/states/singletons/moderation';
19
20
20
21
import Avatar, { getUserAvatarType } from '../avatar';
21
22
import Embed from '../embeds/embed';
···
41
42
const item = () => props.item;
42
43
43
44
const queryClient = useQueryClient();
44
-
const moderationOptions = useModerationOptions();
45
45
const { currentAccount } = useSession();
46
+
47
+
const moderationOptions = inject(ModerationService);
46
48
47
49
const post = () => item().post;
48
50
+4
-2
src/components/timeline/post-feed-item.tsx
+4
-2
src/components/timeline/post-feed-item.tsx
···
14
14
import { history } from '~/globals/navigation';
15
15
16
16
import { isElementAltClicked, isElementClicked } from '~/lib/interaction';
17
-
import { useModerationOptions } from '~/lib/states/moderation';
18
17
import { useSession } from '~/lib/states/session';
18
+
import { inject } from '~/lib/states/singleton';
19
+
import ModerationService from '~/lib/states/singletons/moderation';
19
20
20
21
import Avatar, { getUserAvatarType } from '../avatar';
21
22
import Embed from '../embeds/embed';
···
40
41
41
42
const PostFeedItem = ({ item, highlighted, timelineDid }: PostFeedItemProps) => {
42
43
const queryClient = useQueryClient();
43
-
const moderationOptions = useModerationOptions();
44
44
const { currentAccount } = useSession();
45
+
46
+
const moderationOptions = inject(ModerationService);
45
47
46
48
const { post, reason, next, prev } = item;
47
49
+19
-29
src/lib/states/moderation.tsx
src/lib/states/singletons/moderation.ts
+19
-29
src/lib/states/moderation.tsx
src/lib/states/singletons/moderation.ts
···
1
-
import { type ParentProps, createContext, createMemo, useContext } from 'solid-js';
1
+
import { createMemo } from 'solid-js';
2
2
import { unwrap } from 'solid-js/store';
3
3
4
4
import type { AppBskyLabelerDefs, At } from '@atcute/client/lexicons';
···
10
10
import { interpretLabelerDefinition } from '~/api/moderation/labeler';
11
11
12
12
import { createBatchedFetch } from '~/lib/utils/batch-fetch';
13
-
import { assert } from '~/lib/utils/invariant';
14
13
15
-
import { useAgent } from './agent';
16
-
import { useSession } from './session';
14
+
import { useAgent } from '../agent';
15
+
import { useSession } from '../session';
17
16
18
17
type Labeler = AppBskyLabelerDefs.LabelerViewDetailed;
19
18
20
-
const DEFAULT_MODERATION_PREFERENCES: ModerationPreferences = {
21
-
hideReposts: [],
22
-
keywords: [],
23
-
labelers: {
24
-
[BLUESKY_MODERATION_DID]: {
25
-
redact: true,
26
-
privileged: true,
27
-
labels: {},
28
-
},
29
-
},
30
-
labels: {},
31
-
};
32
-
33
-
const Context = createContext<() => ModerationOptions>();
34
-
35
-
export const ModerationProvider = (props: ParentProps) => {
19
+
const ModerationService = () => {
36
20
const { rpc, persister } = useAgent();
37
21
const { currentAccount } = useSession();
38
22
39
-
const modPreferences = createMemo(() => {
23
+
const modPreferences = createMemo((): ModerationPreferences => {
40
24
if (!currentAccount) {
41
-
return DEFAULT_MODERATION_PREFERENCES;
25
+
return {
26
+
hideReposts: [],
27
+
keywords: [],
28
+
labelers: {
29
+
[BLUESKY_MODERATION_DID]: {
30
+
redact: true,
31
+
privileged: true,
32
+
labels: {},
33
+
},
34
+
},
35
+
labels: {},
36
+
};
42
37
}
43
38
44
39
return currentAccount.preferences.moderation;
···
94
89
};
95
90
});
96
91
97
-
return <Context.Provider value={modOptions}>{props.children}</Context.Provider>;
92
+
return modOptions;
98
93
};
99
94
100
-
export const useModerationOptions = (): (() => ModerationOptions) => {
101
-
const options = useContext(Context);
102
-
assert(options !== undefined, `Expected useModerationOptions to be used under <ModerationProvider>`);
103
-
104
-
return options;
105
-
};
95
+
export default ModerationService;
+4
-7
src/main.tsx
+4
-7
src/main.tsx
···
10
10
11
11
import { configureRouter } from '~/lib/navigation/router';
12
12
import { AgentProvider } from '~/lib/states/agent';
13
-
import { ModerationProvider } from '~/lib/states/moderation';
14
13
import { SessionProvider, useSession } from '~/lib/states/session';
15
14
import { SingletonProvider } from '~/lib/states/singleton';
16
15
import { ThemeProvider } from '~/lib/states/theme';
···
76
75
return (
77
76
<AgentProvider>
78
77
{/* Anything under <AgentProvider> gets remounted on account changes */}
79
-
<ModerationProvider>
80
-
<SingletonProvider>
81
-
<Shell />
82
-
<ModalRenderer />
83
-
</SingletonProvider>
84
-
</ModerationProvider>
78
+
<SingletonProvider>
79
+
<Shell />
80
+
<ModalRenderer />
81
+
</SingletonProvider>
85
82
</AgentProvider>
86
83
);
87
84
}) as unknown as JSX.Element;
+4
-2
src/views/moderation.tsx
+4
-2
src/views/moderation.tsx
···
3
3
4
4
import { Key } from '~/lib/keyed';
5
5
import { useTitle } from '~/lib/navigation/router';
6
-
import { useModerationOptions } from '~/lib/states/moderation';
7
6
import { useSession } from '~/lib/states/session';
7
+
import { inject } from '~/lib/states/singleton';
8
+
import ModerationService from '~/lib/states/singletons/moderation';
8
9
9
10
import Avatar from '~/components/avatar';
10
11
import * as Boxed from '~/components/boxed';
···
20
21
const ModerationPage = () => {
21
22
const { currentAccount } = useSession();
22
23
23
-
const hydratedOptions = useModerationOptions();
24
+
const hydratedOptions = inject(ModerationService);
25
+
24
26
const moderation = currentAccount!.preferences.moderation;
25
27
26
28
useTitle(() => `Moderation — ${import.meta.env.VITE_APP_NAME}`);
+4
-2
src/views/post-thread.tsx
+4
-2
src/views/post-thread.tsx
···
18
18
import { createEventListener } from '~/lib/hooks/event-listener';
19
19
import { Key } from '~/lib/keyed';
20
20
import { useParams, useTitle } from '~/lib/navigation/router';
21
-
import { useModerationOptions } from '~/lib/states/moderation';
22
21
import { useSession } from '~/lib/states/session';
22
+
import { inject } from '~/lib/states/singleton';
23
+
import ModerationService from '~/lib/states/singletons/moderation';
23
24
import { truncateMiddle } from '~/lib/utils/strings';
24
25
25
26
import Button from '~/components/button';
···
221
222
onMainPostDelete?: () => void;
222
223
}) => {
223
224
const { currentAccount } = useSession();
224
-
const moderationOptions = useModerationOptions();
225
+
226
+
const moderationOptions = inject(ModerationService);
225
227
226
228
const [showTl, setShowTl] = createSignal(false);
227
229
+4
-2
src/views/profile-feed-info.tsx
+4
-2
src/views/profile-feed-info.tsx
···
11
11
import { makeAtUri } from '~/api/utils/strings';
12
12
13
13
import { useParams, useTitle } from '~/lib/navigation/router';
14
-
import { useModerationOptions } from '~/lib/states/moderation';
14
+
import { inject } from '~/lib/states/singleton';
15
+
import ModerationService from '~/lib/states/singletons/moderation';
15
16
16
17
import Avatar, { getUserAvatarType } from '~/components/avatar';
17
18
import * as Boxed from '~/components/boxed';
···
55
56
export default FeedInfoPage;
56
57
57
58
const InfoView = (props: { feed: AppBskyFeedDefs.GeneratorView }) => {
58
-
const moderationOptions = useModerationOptions();
59
59
const queryClient = useQueryClient();
60
+
61
+
const moderationOptions = inject(ModerationService);
60
62
61
63
const feed = () => props.feed;
62
64
const creator = () => feed().creator;
+4
-2
src/views/profile-moderation-list.tsx
+4
-2
src/views/profile-moderation-list.tsx
···
13
13
import { makeAtUri } from '~/api/utils/strings';
14
14
15
15
import { useParams, useTitle } from '~/lib/navigation/router';
16
-
import { useModerationOptions } from '~/lib/states/moderation';
16
+
import { inject } from '~/lib/states/singleton';
17
+
import ModerationService from '~/lib/states/singletons/moderation';
17
18
18
19
import Avatar, { getUserAvatarType } from '~/components/avatar';
19
20
import * as Boxed from '~/components/boxed';
···
99
100
export default ProfileModerationListPage;
100
101
101
102
const InfoView = (props: { list: AppBskyGraphDefs.ListView }) => {
102
-
const moderationOptions = useModerationOptions();
103
103
const queryClient = useQueryClient();
104
+
105
+
const moderationOptions = inject(ModerationService);
104
106
105
107
const list = () => props.list;
106
108
const creator = () => list().creator;