diff --git a/src/features/user/Profile.tsx b/src/features/user/Profile.tsx
index 9749673f30..67c58c8d47 100644
--- a/src/features/user/Profile.tsx
+++ b/src/features/user/Profile.tsx
@@ -108,7 +108,13 @@ export default function Profile({ person }: ProfileProps) {
[person, buildGeneralBrowseLink, isSelf]
);
- return ;
+ return (
+
+ );
}
function getCreatedDate(item: PostCommentItem): number {
diff --git a/src/features/user/UserPageActions.tsx b/src/features/user/UserPageActions.tsx
new file mode 100644
index 0000000000..e24cbb3e28
--- /dev/null
+++ b/src/features/user/UserPageActions.tsx
@@ -0,0 +1,94 @@
+import {
+ IonActionSheet,
+ IonButton,
+ IonIcon,
+ useIonRouter,
+ useIonToast,
+} from "@ionic/react";
+import {
+ ellipsisHorizontal,
+ mailOutline,
+ removeCircleOutline,
+} from "ionicons/icons";
+import { useMemo, useState } from "react";
+import { useBuildGeneralBrowseLink } from "../../helpers/routes";
+import { useAppDispatch, useAppSelector } from "../../store";
+import { blockUser } from "./userSlice";
+import { getHandle } from "../../helpers/lemmy";
+import { buildBlocked, problemBlockingUser } from "../../helpers/toastMessages";
+
+interface UserPageActionsProps {
+ handle: string;
+}
+
+export default function UserPageActions({ handle }: UserPageActionsProps) {
+ const [open, setOpen] = useState(false);
+ const [present] = useIonToast();
+ const router = useIonRouter();
+ const buildGeneralBrowseLink = useBuildGeneralBrowseLink();
+ const dispatch = useAppDispatch();
+ const blocks = useAppSelector(
+ (state) => state.auth.site?.my_user?.person_blocks
+ );
+ const isBlocked = useMemo(
+ () => blocks?.some((b) => getHandle(b.target) === handle),
+ [blocks, handle]
+ );
+ const userByHandle = useAppSelector((state) => state.user.userByHandle);
+ const user = userByHandle[handle];
+
+ return (
+ <>
+ setOpen(true)}
+ >
+
+
+ setOpen(false)}
+ onWillDismiss={async (e) => {
+ switch (e.detail.data) {
+ case "message": {
+ router.push(buildGeneralBrowseLink(`/u/${handle}/message`));
+ break;
+ }
+ case "block": {
+ if (!user) return;
+
+ try {
+ await dispatch(blockUser(!isBlocked, user.id));
+ } catch (error) {
+ present(problemBlockingUser);
+
+ throw error;
+ }
+
+ present(buildBlocked(!isBlocked, handle));
+ }
+ }
+ }}
+ />
+ >
+ );
+}
diff --git a/src/features/user/userSlice.tsx b/src/features/user/userSlice.tsx
index ff6044d302..3d0776b29b 100644
--- a/src/features/user/userSlice.tsx
+++ b/src/features/user/userSlice.tsx
@@ -1,6 +1,6 @@
import { Dictionary, PayloadAction, createSlice } from "@reduxjs/toolkit";
import { AppDispatch, RootState } from "../../store";
-import { clientSelector, jwtSelector } from "../auth/authSlice";
+import { clientSelector, getSite, jwtSelector } from "../auth/authSlice";
import { getHandle } from "../../helpers/lemmy";
import { LIMIT } from "../../services/lemmy";
import { receivedComments } from "../comment/commentSlice";
@@ -50,3 +50,21 @@ export const getUser =
return personResponse;
};
+
+export const blockUser =
+ (block: boolean, id: number) =>
+ async (dispatch: AppDispatch, getState: () => RootState) => {
+ const jwt = jwtSelector(getState());
+
+ if (!id) return;
+ if (!jwt) throw new Error("Not authorized");
+
+ const response = await clientSelector(getState())?.blockPerson({
+ person_id: id,
+ block,
+ auth: jwt,
+ });
+
+ dispatch(receivedUsers([response.person_view.person]));
+ await dispatch(getSite());
+ };
diff --git a/src/helpers/imageCompress.ts b/src/helpers/imageCompress.ts
index f59cf88aae..e2f29ccf9c 100644
--- a/src/helpers/imageCompress.ts
+++ b/src/helpers/imageCompress.ts
@@ -2,99 +2,19 @@
* Source: https://stackoverflow.com/a/44849182/1319878
*/
-function getExifOrientation(
- file: Blob,
- callback: (orientation: number) => void
-) {
- file = file.slice(0, 131072);
-
- const reader = new FileReader();
- reader.onload = function (e: ProgressEvent) {
- const target = e.target;
- if (!target) throw new Error("no target");
-
- const view = new DataView(target.result as ArrayBuffer);
- if (view.getUint16(0, false) !== 0xffd8) {
- callback(-2);
- return;
- }
- const length = view.byteLength;
- let offset = 2;
- while (offset < length) {
- const marker = view.getUint16(offset, false);
- offset += 2;
- if (marker === 0xffe1) {
- if (view.getUint32((offset += 2), false) !== 0x45786966) {
- callback(-1);
- return;
- }
- const little = view.getUint16((offset += 6), false) === 0x4949;
- offset += view.getUint32(offset + 4, little);
- const tags = view.getUint16(offset, little);
- offset += 2;
- for (let i = 0; i < tags; i++) {
- if (view.getUint16(offset + i * 12, little) === 0x0112) {
- callback(view.getUint16(offset + i * 12 + 8, little));
- return;
- }
- }
- } else if ((marker & 0xff00) !== 0xff00) {
- break;
- } else {
- offset += view.getUint16(offset, false);
- }
- }
- callback(-1);
- };
- reader.readAsArrayBuffer(file);
-}
-
-function imgToCanvasWithOrientation(
+function imgToCanvas(
img: HTMLImageElement,
rawWidth: number,
- rawHeight: number,
- orientation: number
+ rawHeight: number
): HTMLCanvasElement {
const canvas = document.createElement("canvas");
- if (orientation > 4) {
- canvas.width = rawHeight;
- canvas.height = rawWidth;
- } else {
- canvas.width = rawWidth;
- canvas.height = rawHeight;
- }
- if (orientation > 1) {
- // eslint-disable-next-line no-console
- console.log("EXIF orientation = " + orientation + ", rotating picture");
- }
+ canvas.width = rawWidth;
+ canvas.height = rawHeight;
const ctx = canvas.getContext("2d");
if (!ctx) throw new Error("no canvas");
- switch (orientation) {
- case 2:
- ctx.transform(-1, 0, 0, 1, rawWidth, 0);
- break;
- case 3:
- ctx.transform(-1, 0, 0, -1, rawWidth, rawHeight);
- break;
- case 4:
- ctx.transform(1, 0, 0, -1, 0, rawHeight);
- break;
- case 5:
- ctx.transform(0, 1, 1, 0, 0, 0);
- break;
- case 6:
- ctx.transform(0, 1, -1, 0, rawHeight, 0);
- break;
- case 7:
- ctx.transform(0, -1, -1, 0, rawHeight, rawWidth);
- break;
- case 8:
- ctx.transform(0, -1, 1, 0, 0, rawWidth);
- break;
- }
ctx.drawImage(img, 0, 0, rawWidth, rawHeight);
return canvas;
}
@@ -120,36 +40,33 @@ export async function reduceFileSize(
img.addEventListener("load", function () {
URL.revokeObjectURL(this.src);
- getExifOrientation(file, function (orientation) {
- let w = img.width,
- h = img.height;
- const scale =
- orientation > 4
- ? Math.min(maxHeight / w, maxWidth / h, 1)
- : Math.min(maxWidth / w, maxHeight / h, 1);
- h = Math.round(h * scale);
- w = Math.round(w * scale);
+ let w = img.width,
+ h = img.height;
+
+ const scale = Math.min(maxWidth / w, maxHeight / h, 1);
+
+ w = Math.round(w * scale);
+ h = Math.round(h * scale);
- const canvas = imgToCanvasWithOrientation(img, w, h, orientation);
- canvas.toBlob(
- function (blob) {
- // eslint-disable-next-line no-console
- console.log(
- "Resized image to " +
- w +
- "x" +
- h +
- ", " +
- (blob ? blob.size >> 10 : "???") +
- "kB"
- );
- if (blob) resolve(blob);
- else resolve(file);
- },
- "image/jpeg",
- quality
- );
- });
+ const canvas = imgToCanvas(img, w, h);
+ canvas.toBlob(
+ function (blob) {
+ // eslint-disable-next-line no-console
+ console.log(
+ "Resized image to " +
+ w +
+ "x" +
+ h +
+ ", " +
+ (blob ? blob.size >> 10 : "???") +
+ "kB"
+ );
+ if (blob) resolve(blob);
+ else resolve(file);
+ },
+ "image/jpeg",
+ quality
+ );
});
img.src = URL.createObjectURL(file);
diff --git a/src/helpers/safari.ts b/src/helpers/safari.ts
new file mode 100644
index 0000000000..16bd4be759
--- /dev/null
+++ b/src/helpers/safari.ts
@@ -0,0 +1,13 @@
+export function fixSafariAutoscroll() {
+ let checkAttempts = 0;
+
+ const interval = setInterval(() => {
+ window.scrollTo(0, 0);
+
+ if (window.scrollY === 0) {
+ checkAttempts++;
+
+ if (checkAttempts > 10) clearInterval(interval);
+ }
+ }, 100);
+}
diff --git a/src/helpers/scrollUpIfNeeded.ts b/src/helpers/scrollUpIfNeeded.ts
new file mode 100644
index 0000000000..c8a470c626
--- /dev/null
+++ b/src/helpers/scrollUpIfNeeded.ts
@@ -0,0 +1,46 @@
+import { Page } from "../features/auth/AppContext";
+
+export async function scrollUpIfNeeded(
+ activePage: Page | undefined,
+ index: number | undefined = undefined,
+ behavior: "auto" | "smooth" = "smooth"
+) {
+ if (!activePage?.current) return false;
+
+ const current = activePage.current;
+
+ if ("querySelector" in current) {
+ const scroll =
+ current.querySelector('[data-virtuoso-scroller="true"]') ??
+ current
+ .querySelector("ion-content")
+ ?.shadowRoot?.querySelector(".inner-scroll");
+
+ if (scroll?.scrollTop) {
+ scroll.scrollTo({ top: 0, behavior });
+ return true;
+ }
+ } else {
+ return new Promise((resolve) =>
+ current.getState((state) => {
+ if (state.scrollTop) {
+ if (index != null) {
+ current.scrollToIndex({
+ index,
+ behavior,
+ });
+ } else {
+ current.scrollTo({
+ top: 0,
+ behavior,
+ });
+ }
+ }
+
+ resolve(!!state.scrollTop);
+ })
+ );
+ }
+
+ return false;
+}
diff --git a/src/helpers/toastMessages.ts b/src/helpers/toastMessages.ts
index ead920e500..d02287956b 100644
--- a/src/helpers/toastMessages.ts
+++ b/src/helpers/toastMessages.ts
@@ -13,3 +13,26 @@ export const saveError: ToastOptions = {
position: "bottom",
color: "danger",
};
+
+export const allNSFWHidden: ToastOptions = {
+ message: "All NSFW content is now hidden for your account.",
+ duration: 3500,
+ position: "bottom",
+ color: "success",
+};
+
+export const problemBlockingUser: ToastOptions = {
+ message: "Problem blocking user. Please try again.",
+ duration: 3500,
+ position: "bottom",
+ color: "danger",
+};
+
+export function buildBlocked(blocked: boolean, handle: string): ToastOptions {
+ return {
+ message: `${handle} has been ${blocked ? "blocked" : "unblocked"}`,
+ duration: 3500,
+ position: "bottom",
+ color: "success",
+ };
+}
diff --git a/src/helpers/url.ts b/src/helpers/url.ts
new file mode 100644
index 0000000000..5c34b6bc15
--- /dev/null
+++ b/src/helpers/url.ts
@@ -0,0 +1,11 @@
+export function isValidUrl(potentialUrl: string) {
+ let url;
+
+ try {
+ url = new URL(potentialUrl);
+ } catch (_) {
+ return false;
+ }
+
+ return url.protocol === "http:" || url.protocol === "https:";
+}
diff --git a/src/helpers/useSystemDarkMode.ts b/src/helpers/useSystemDarkMode.ts
index df45ef55ce..786db3d08c 100644
--- a/src/helpers/useSystemDarkMode.ts
+++ b/src/helpers/useSystemDarkMode.ts
@@ -8,19 +8,27 @@ export default function useSystemDarkMode() {
);
useEffect(() => {
+ const mediaQuery = window.matchMedia(DARK_MEDIA_SELECTOR);
+
function handleDarkModeChange() {
- const doesMatch = window.matchMedia(DARK_MEDIA_SELECTOR).matches;
+ const doesMatch = mediaQuery.matches;
setPrefersDarkMode(doesMatch);
}
- window
- .matchMedia(DARK_MEDIA_SELECTOR)
- .addEventListener("change", handleDarkModeChange);
+ // Fallback to addListener/removeListener for older browser support
+ // See https://github.com/aeharding/voyager/pull/264
+ if (mediaQuery?.addEventListener) {
+ mediaQuery.addEventListener("change", handleDarkModeChange);
+ } else {
+ mediaQuery.addListener(handleDarkModeChange);
+ }
return () => {
- window
- .matchMedia(DARK_MEDIA_SELECTOR)
- .removeEventListener("change", handleDarkModeChange);
+ if (mediaQuery?.removeEventListener) {
+ mediaQuery.removeEventListener("change", handleDarkModeChange);
+ } else {
+ mediaQuery.removeListener(handleDarkModeChange);
+ }
};
}, []);
diff --git a/src/index.css b/src/index.css
index c4f2e128e2..27cba5cbe7 100644
--- a/src/index.css
+++ b/src/index.css
@@ -94,7 +94,7 @@ ion-action-sheet ion-icon {
margin-inline-end: 16px !important;
}
-/* react-photoswipe */
+/* photoswipe */
.pswp__top-bar {
top: env(safe-area-inset-top, 0);
diff --git a/src/pages/inbox/ConversationPage.tsx b/src/pages/inbox/ConversationPage.tsx
index 4a3500bcee..7abe2b11c4 100644
--- a/src/pages/inbox/ConversationPage.tsx
+++ b/src/pages/inbox/ConversationPage.tsx
@@ -19,7 +19,7 @@ import {
receivedMessages,
syncMessages,
} from "../../features/inbox/inboxSlice";
-import { useParams } from "react-router";
+import { useLocation, useParams } from "react-router";
import { getHandle } from "../../helpers/lemmy";
import Message from "../../features/inbox/messages/Message";
import styled from "@emotion/styled";
@@ -115,6 +115,7 @@ export default function ConversationPage() {
const dispatch = useAppDispatch();
const allMessages = useAppSelector((state) => state.inbox.messages);
const jwtPayload = useAppSelector(jwtPayloadSelector);
+ const location = useLocation();
const jwt = useAppSelector(jwtSelector);
const myUserId = useAppSelector(
(state) => state.auth.site?.my_user?.local_user_view?.local_user?.person_id
@@ -208,7 +209,12 @@ export default function ConversationPage() {
-
+
setOpen(false)}
onWillDismiss={async (e) => {
- setOpen(false);
-
if (e.detail.role === "read") {
dispatch(markAllRead());
}
diff --git a/src/pages/inbox/MessagesPage.tsx b/src/pages/inbox/MessagesPage.tsx
index c0bcdda94a..bb277364ed 100644
--- a/src/pages/inbox/MessagesPage.tsx
+++ b/src/pages/inbox/MessagesPage.tsx
@@ -19,7 +19,7 @@ import ConversationItem from "../../features/inbox/messages/ConversationItem";
import { MaxWidthContainer } from "../../features/shared/AppContent";
import { syncMessages } from "../../features/inbox/inboxSlice";
import ComposeButton from "./ComposeButton";
-import { CenteredSpinner } from "../../features/post/detail/PostDetail";
+import { CenteredSpinner } from "../posts/PostPage";
export default function MessagesPage() {
const dispatch = useAppDispatch();
diff --git a/src/pages/posts/CommunitiesPage.tsx b/src/pages/posts/CommunitiesPage.tsx
index c38a6f1216..e55593804e 100644
--- a/src/pages/posts/CommunitiesPage.tsx
+++ b/src/pages/posts/CommunitiesPage.tsx
@@ -1,111 +1,13 @@
-import {
- IonHeader,
- IonIcon,
- IonItem,
- IonItemDivider,
- IonItemGroup,
- IonLabel,
- IonList,
- IonPage,
- IonTitle,
- IonToolbar,
-} from "@ionic/react";
-import AppContent from "../../features/shared/AppContent";
-import { useParams } from "react-router";
-import { useAppSelector } from "../../store";
-import { getHandle } from "../../helpers/lemmy";
-import { home, library, people } from "ionicons/icons";
-import styled from "@emotion/styled";
-import { pullAllBy, sortBy, uniqBy } from "lodash";
-import { notEmpty } from "../../helpers/array";
-import { Fragment, useMemo, useRef } from "react";
+import { useRef } from "react";
+import CommunitiesList from "../../features/community/list/CommunitiesList";
import { useSetActivePage } from "../../features/auth/AppContext";
-import { useBuildGeneralBrowseLink } from "../../helpers/routes";
-import ItemIcon from "../../features/labels/img/ItemIcon";
-import { jwtSelector } from "../../features/auth/authSlice";
-import { Community } from "lemmy-js-client";
-
-const SubIcon = styled(IonIcon)<{ color: string }>`
- border-radius: 50%;
- padding: 6px;
- width: 1rem;
- height: 1rem;
-
- background: ${({ color }) => color};
- --ion-color-base: white;
-`;
-
-const Content = styled.div`
- margin: 0.7rem 0;
-
- display: flex;
- align-items: center;
- gap: 1rem;
-
- aside {
- margin-top: 0.2rem;
- color: var(--ion-color-medium);
- font-size: 0.8em;
- }
-`;
+import { IonHeader, IonPage, IonTitle, IonToolbar } from "@ionic/react";
+import AppContent from "../../features/shared/AppContent";
export default function CommunitiesPage() {
- const buildGeneralBrowseLink = useBuildGeneralBrowseLink();
- const { actor } = useParams<{ actor: string }>();
- const jwt = useAppSelector(jwtSelector);
- const pageRef = useRef();
-
- const follows = useAppSelector((state) => state.auth.site?.my_user?.follows);
+ const pageRef = useRef(null);
- const communityByHandle = useAppSelector(
- (state) => state.community.communityByHandle
- );
-
- useSetActivePage(pageRef.current);
-
- const communities = useMemo(() => {
- const communities = uniqBy(
- [
- ...(follows || []).map((f) => f.community),
- ...Object.values(communityByHandle).map(
- (c) => c?.community_view.community
- ),
- ].filter(notEmpty),
- "id"
- );
-
- pullAllBy(
- communities,
- Object.values(communityByHandle)
- .filter(
- (response) => response?.community_view.subscribed === "NotSubscribed"
- )
- .map((c) => c?.community_view.community),
- "id"
- );
-
- return communities;
- }, [follows, communityByHandle]);
-
- const communitiesGroupedByLetter = useMemo(() => {
- const alphabeticallySortedCommunities = sortBy(communities, (c) =>
- c.name.toLowerCase()
- );
-
- return Object.entries(
- alphabeticallySortedCommunities.reduce>(
- (acc, community) => {
- const firstLetter = community.name[0].toUpperCase();
- if (!acc[firstLetter]) {
- acc[firstLetter] = [];
- }
- acc[firstLetter].push(community);
- return acc;
- },
- {}
- )
- );
- }, [communities]);
+ useSetActivePage(pageRef);
return (
@@ -115,60 +17,7 @@ export default function CommunitiesPage() {
-
-
- {jwt && (
-
-
-
-
-
-
- )}
-
-
-
-
- All
-
-
-
-
-
-
-
- Local
-
-
-
-
-
- {communitiesGroupedByLetter.map(([letter, communities]) => (
-
-
-
- {letter}
-
-
- {communities.map((community) => (
-
-
-
- {getHandle(community)}
-
-
- ))}
-
- ))}
-
+
);
diff --git a/src/pages/posts/PostPage.tsx b/src/pages/posts/PostPage.tsx
new file mode 100644
index 0000000000..4c9c902e35
--- /dev/null
+++ b/src/pages/posts/PostPage.tsx
@@ -0,0 +1,125 @@
+import {
+ IonButtons,
+ IonContent,
+ IonHeader,
+ IonIcon,
+ IonPage,
+ IonRefresher,
+ IonRefresherContent,
+ IonSpinner,
+ IonTitle,
+ IonToolbar,
+ RefresherCustomEvent,
+} from "@ionic/react";
+import { useAppDispatch, useAppSelector } from "../../store";
+import { useParams } from "react-router";
+import styled from "@emotion/styled";
+import React, { useCallback, useEffect, useState } from "react";
+import { getPost } from "../../features/post/postSlice";
+import AppBackButton from "../../features/shared/AppBackButton";
+import { CommentSortType } from "lemmy-js-client";
+import { useBuildGeneralBrowseLink } from "../../helpers/routes";
+import { jwtSelector } from "../../features/auth/authSlice";
+import CommentSort from "../../features/comment/CommentSort";
+import MoreActions from "../../features/post/shared/MoreActions";
+import PostDetail from "../../features/post/detail/PostDetail";
+import { startCase } from "lodash";
+
+export const CenteredSpinner = styled(IonSpinner)`
+ position: relative;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%);
+`;
+
+export const AnnouncementIcon = styled(IonIcon)`
+ font-size: 1.1rem;
+ margin-right: 5px;
+ vertical-align: middle;
+ color: var(--ion-color-success);
+`;
+
+export default function PostPage() {
+ const buildGeneralBrowseLink = useBuildGeneralBrowseLink();
+ const { id, commentPath, community } = useParams<{
+ id: string;
+ commentPath?: string;
+ community: string;
+ }>();
+ const post = useAppSelector((state) => state.post.postById[id]);
+ const jwt = useAppSelector(jwtSelector);
+ const dispatch = useAppDispatch();
+
+ const defaultSort = startCase(
+ useAppSelector((state) => state.appearance.comments.defaultCommentSort)
+ ) as CommentSortType;
+ const [sort, setSort] = useState(defaultSort);
+
+ const postIfFound = typeof post === "object" ? post : undefined;
+
+ useEffect(() => {
+ if (post) return;
+
+ dispatch(getPost(+id));
+ }, [post, jwt, dispatch, id]);
+
+ const refresh = useCallback(
+ async (event: RefresherCustomEvent) => {
+ try {
+ await dispatch(getPost(+id));
+ } finally {
+ event.detail.complete();
+ }
+ },
+ [dispatch, id]
+ );
+
+ const buildWithRefresher = useCallback(
+ (content: React.ReactNode) => {
+ return (
+ <>
+
+
+
+ {content}
+ >
+ );
+ },
+ [refresh]
+ );
+
+ function renderPost() {
+ if (!post) return ;
+ if (post === "not-found")
+ return buildWithRefresher(
+ Post not found
+ );
+ if (post.post.deleted)
+ return buildWithRefresher(
+ Post deleted
+ );
+
+ return ;
+ }
+
+ return (
+
+
+
+
+
+
+ {postIfFound?.counts.comments} Comments
+
+
+ {postIfFound && }
+
+
+
+ {renderPost()}
+
+ );
+}
diff --git a/src/pages/profile/ProfileFeedHiddenPostsPage.tsx b/src/pages/profile/ProfileFeedHiddenPostsPage.tsx
index e5faaabf8d..b28d99f0e7 100644
--- a/src/pages/profile/ProfileFeedHiddenPostsPage.tsx
+++ b/src/pages/profile/ProfileFeedHiddenPostsPage.tsx
@@ -75,7 +75,7 @@ export default function ProfileFeedHiddenPostsPage() {
const result = await Promise.all(
postIds.map((postId) => {
const potentialPost = postById[postId];
- if (potentialPost) return potentialPost;
+ if (typeof potentialPost === "object") return potentialPost;
return client.getPost({ id: postId, auth: jwt });
})
diff --git a/src/pages/profile/ProfileFeedItemsPage.tsx b/src/pages/profile/ProfileFeedItemsPage.tsx
index 3281827092..718733f158 100644
--- a/src/pages/profile/ProfileFeedItemsPage.tsx
+++ b/src/pages/profile/ProfileFeedItemsPage.tsx
@@ -86,7 +86,7 @@ export default function ProfileFeedItemsPage({
-
+
);
diff --git a/src/pages/profile/ProfilePage.tsx b/src/pages/profile/ProfilePage.tsx
index 969432947d..cdc498ba71 100644
--- a/src/pages/profile/ProfilePage.tsx
+++ b/src/pages/profile/ProfilePage.tsx
@@ -13,29 +13,25 @@ import { useAppSelector } from "../../store";
import { handleSelector } from "../../features/auth/authSlice";
import LoggedOut from "../../features/user/LoggedOut";
import AccountSwitcher from "../../features/auth/AccountSwitcher";
-import { useContext, useRef } from "react";
-import { useSetActivePage } from "../../features/auth/AppContext";
+import { useContext } from "react";
import AppContent from "../../features/shared/AppContent";
import { PageContext } from "../../features/auth/PageContext";
export default function ProfilePage() {
- const pageRef = useRef();
const handle = useAppSelector(handleSelector);
+ const { page, presentLoginIfNeeded } = useContext(PageContext);
const [presentAccountSwitcher, onDismissAccountSwitcher] = useIonModal(
AccountSwitcher,
{
onDismiss: (data: string, role: string) =>
onDismissAccountSwitcher(data, role),
- page: pageRef.current,
+ page,
}
);
- const { presentLoginIfNeeded } = useContext(PageContext);
-
- useSetActivePage(pageRef.current);
return (
-
+
{handle ? (
diff --git a/src/pages/profile/UserPage.tsx b/src/pages/profile/UserPage.tsx
index 76dc30f8b5..69f4f41285 100644
--- a/src/pages/profile/UserPage.tsx
+++ b/src/pages/profile/UserPage.tsx
@@ -9,17 +9,21 @@ import {
} from "@ionic/react";
import { useParams } from "react-router";
import AsyncProfile from "../../features/user/AsyncProfile";
-import { useRef } from "react";
-import { useSetActivePage } from "../../features/auth/AppContext";
+import UserPageActions from "../../features/user/UserPageActions";
+import { useAppSelector } from "../../store";
+import {
+ handleSelector,
+ usernameSelector,
+} from "../../features/auth/authSlice";
export default function UserPage() {
const handle = useParams<{ handle: string }>().handle;
- const pageRef = useRef();
-
- useSetActivePage(pageRef.current);
+ const myUsername = useAppSelector(usernameSelector);
+ const myHandle = useAppSelector(handleSelector);
+ const isSelf = handle === myUsername || handle === myHandle;
return (
-
+
@@ -27,6 +31,12 @@ export default function UserPage() {
{handle}
+
+ {!isSelf && (
+
+
+
+ )}
diff --git a/src/pages/search/SearchPage.tsx b/src/pages/search/SearchPage.tsx
index 63b96113ae..5037aef7a3 100644
--- a/src/pages/search/SearchPage.tsx
+++ b/src/pages/search/SearchPage.tsx
@@ -1,4 +1,10 @@
-import { IonHeader, IonPage, IonSearchbar, IonToolbar } from "@ionic/react";
+import {
+ IonHeader,
+ IonPage,
+ IonSearchbar,
+ IonToolbar,
+ useIonRouter,
+} from "@ionic/react";
import AppContent from "../../features/shared/AppContent";
import { createRef, useState } from "react";
import { css } from "@emotion/react";
@@ -15,23 +21,37 @@ export const focusSearchBar = () => searchBarRef.current?.setFocus();
export default function SearchPage() {
const [search, setSearch] = useState("");
+ const router = useIonRouter();
return (
- setSearch(e.detail.value ?? "")}
- />
+
diff --git a/src/pages/settings/ApolloMigratePage.tsx b/src/pages/settings/ApolloMigratePage.tsx
deleted file mode 100644
index d3e6ee0199..0000000000
--- a/src/pages/settings/ApolloMigratePage.tsx
+++ /dev/null
@@ -1,149 +0,0 @@
-import {
- IonBackButton,
- IonButtons,
- IonHeader,
- IonItem,
- IonLabel,
- IonList,
- IonPage,
- IonTitle,
- IonToolbar,
- useIonToast,
-} from "@ionic/react";
-import AppContent from "../../features/shared/AppContent";
-import { flatten, identity, sortBy, uniq } from "lodash";
-import { useState } from "react";
-import { notEmpty } from "../../helpers/array";
-import { InsetIonItem } from "../profile/ProfileFeedItemsPage";
-import { css } from "@emotion/react";
-
-export default function ApolloMigratePage() {
- const [present] = useIonToast();
- const [subs, setSubs] = useState();
-
- function renderUpload() {
- return (
- <>
-
-
- This tool is designed for Apollo users migrating to Lemmy. Upload
- your export to easily search for your Reddit subs with a similar
- name.
-
-
- - Update Apollo to the latest version
- - Visit Settings Tab
- - Tab "Export" in header
- - Select JSON
- - Download, then come back to wefwef and upload below
-
-
-
-
-
- >
- );
- }
-
- function renderSubs() {
- return (
-
- {subs?.map((sub) => (
-
- r/{sub}
-
- ))}
-
- );
- }
-
- return (
-
-
-
-
-
-
-
- Migrate
-
-
- {!subs ? renderUpload() : renderSubs()}
-
- );
-}
-
-interface ApolloUserData {
- subscribed_subreddits: string[];
- favorites: string[];
-}
-
-async function getSubreddits(file: File): Promise {
- const dataByUser = Object.values(
- await convertFileToJson(file)
- ) as ApolloUserData[];
-
- return sortBy(
- uniq(
- flatten(
- dataByUser
- .map((user) => user.subscribed_subreddits)
- .concat(dataByUser.map((user) => user.favorites))
- )
- ).filter(notEmpty),
- identity
- );
-}
-
-function convertFileToJson(file: File): Promise {
- return new Promise((resolve, reject) => {
- const reader = new FileReader();
-
- reader.onload = (event: ProgressEvent) => {
- try {
- const json = JSON.parse(event.target?.result as string);
- resolve(json);
- } catch (error) {
- reject(error);
- }
- };
-
- reader.onerror = (event: ProgressEvent) => {
- reject(event.target?.error);
- };
-
- reader.readAsText(file);
- });
-}
diff --git a/src/pages/settings/BlocksSettingsPage.tsx b/src/pages/settings/BlocksSettingsPage.tsx
new file mode 100644
index 0000000000..d16a9c3db1
--- /dev/null
+++ b/src/pages/settings/BlocksSettingsPage.tsx
@@ -0,0 +1,61 @@
+import {
+ IonBackButton,
+ IonButtons,
+ IonContent,
+ IonHeader,
+ IonPage,
+ IonText,
+ IonTitle,
+ IonToolbar,
+} from "@ionic/react";
+import AppContent from "../../features/shared/AppContent";
+import {
+ TitleContainer,
+ UsernameIonText,
+} from "../../features/comment/reply/CommentReply";
+import { useAppSelector } from "../../store";
+import {
+ handleSelector,
+ localUserSelector,
+} from "../../features/auth/authSlice";
+import FilterNsfw from "../../features/settings/blocks/FilterNsfw";
+import BlockedCommunities from "../../features/settings/blocks/BlockedCommunities";
+import { CenteredSpinner } from "../posts/PostPage";
+import BlockedUsers from "../../features/settings/blocks/BlockedUsers";
+
+export default function BlocksSettingsPage() {
+ const userHandle = useAppSelector(handleSelector);
+ const localUser = useAppSelector(localUserSelector);
+
+ return (
+
+
+
+
+
+
+
+
+
+ Filters & Blocks
+
+ {userHandle}
+
+ {" "}
+
+
+
+ {localUser ? (
+
+
+
+
+
+ ) : (
+
+
+
+ )}
+
+ );
+}
diff --git a/src/pages/settings/CommentSortPage.tsx b/src/pages/settings/CommentSortPage.tsx
deleted file mode 100644
index 928d7c9644..0000000000
--- a/src/pages/settings/CommentSortPage.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import {
- IonBackButton,
- IonButtons,
- IonHeader,
- IonPage,
- IonTitle,
- IonToolbar,
-} from "@ionic/react";
-import AppContent from "../../features/shared/AppContent";
-import PostsViewSelection from "../../features/settings/appearance/posts/PostsViewSelection";
-import CommentDefaultSortSelection from "../../features/settings/appearance/comments/CommentDefaultSortSelection";
-
-export default function CommentSortPage() {
- return (
-
-
-
-
-
-
-
- Comments
-
-
-
-
-
-
- );
-}
diff --git a/src/pages/settings/InstallAppPage.tsx b/src/pages/settings/InstallAppPage.tsx
index 6f84f40dc9..8b8f8e82b6 100644
--- a/src/pages/settings/InstallAppPage.tsx
+++ b/src/pages/settings/InstallAppPage.tsx
@@ -155,7 +155,7 @@ export default function InstallAppPage() {
text-decoration: underline;
`}
>
- https://wefwef.app/settings/install
+ https://vger.app/settings/install
{" "}
from your phone or tablet.
@@ -182,7 +182,7 @@ export default function InstallAppPage() {
diff --git a/src/pages/settings/PostAppearancePage.tsx b/src/pages/settings/PostAppearancePage.tsx
deleted file mode 100644
index 5482124cdb..0000000000
--- a/src/pages/settings/PostAppearancePage.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import {
- IonBackButton,
- IonButtons,
- IonHeader,
- IonPage,
- IonTitle,
- IonToolbar,
-} from "@ionic/react";
-import AppContent from "../../features/shared/AppContent";
-import PostsViewSelection from "../../features/settings/appearance/posts/PostsViewSelection";
-
-export default function PostAppearancePage() {
- return (
-
-
-
-
-
-
-
- Posts
-
-
-
-
-
-
- );
-}
diff --git a/src/pages/settings/RedditDataMigratePage.tsx b/src/pages/settings/RedditDataMigratePage.tsx
new file mode 100644
index 0000000000..84756b14f4
--- /dev/null
+++ b/src/pages/settings/RedditDataMigratePage.tsx
@@ -0,0 +1,124 @@
+import {
+ IonBackButton,
+ IonButtons,
+ IonHeader,
+ IonInput,
+ IonItem,
+ IonLabel,
+ IonList,
+ IonPage,
+ IonTitle,
+ IonToolbar,
+ useIonToast,
+} from "@ionic/react";
+import AppContent from "../../features/shared/AppContent";
+import { useEffect, useState } from "react";
+import { InsetIonItem } from "../profile/ProfileFeedItemsPage";
+import { isValidUrl } from "../../helpers/url";
+
+export default function RedditMigratePage() {
+ const [present] = useIonToast();
+ const [subs, setSubs] = useState();
+ const [link, setLink] = useState("");
+
+ useEffect(() => {
+ if (!isValidUrl(link)) return;
+
+ const subs = parseSubsFromLink(link);
+
+ if (!subs.length) {
+ present({
+ message:
+ "Problem parsing link. Please make sure the link you entered is correct.",
+ duration: 3500,
+ position: "bottom",
+ color: "danger",
+ });
+ setLink("");
+ return;
+ }
+
+ setSubs(subs);
+ }, [link, present]);
+
+ function renderUpload() {
+ return (
+ <>
+
+
+ This tool is designed for Reddit users migrating to Lemmy to easily
+ search for communities similar to subscribed subreddits.
+
+
+ -
+ Visit{" "}
+
+ https://www.reddit.com/subreddits
+
+
+ - If iOS, open in Safari so you can copy links to clipboard
+ - Login
+ -
+ Copy the link for "multireddit of your subscriptions" in
+ the sidebar
+
+ - Paste below
+
+
+
+
+
+ >
+ );
+ }
+
+ function renderSubs() {
+ return (
+
+ {subs?.map((sub) => (
+
+ r/{sub}
+
+ ))}
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+ Migrate
+
+
+ {!subs ? renderUpload() : renderSubs()}
+
+ );
+}
+
+function parseSubsFromLink(multiredditUrl: string) {
+ const { pathname } = new URL(multiredditUrl);
+
+ if (!pathname.startsWith("/r/")) return [];
+
+ return pathname.slice(3).split("+");
+}
diff --git a/src/pages/settings/SettingsPage.tsx b/src/pages/settings/SettingsPage.tsx
index 0ddbbd2fe3..5115110e1d 100644
--- a/src/pages/settings/SettingsPage.tsx
+++ b/src/pages/settings/SettingsPage.tsx
@@ -17,6 +17,7 @@ import {
mailOutline,
openOutline,
reloadCircle,
+ removeCircle,
shieldCheckmarkOutline,
} from "ionicons/icons";
import { useContext, useEffect } from "react";
@@ -24,6 +25,8 @@ import { UpdateContext } from "./update/UpdateContext";
import useShouldInstall from "../../features/pwa/useShouldInstall";
import styled from "@emotion/styled";
import { css } from "@emotion/react";
+import { useAppSelector } from "../../store";
+import { handleSelector } from "../../features/auth/authSlice";
const IconBg = styled.div<{ color: string }>`
width: 30px;
@@ -46,6 +49,7 @@ const IconBg = styled.div<{ color: string }>`
export default function SettingsPage() {
const { status: updateStatus, checkForUpdates } = useContext(UpdateContext);
const shouldInstall = useShouldInstall();
+ const currentHandle = useAppSelector(handleSelector);
useEffect(() => {
checkForUpdates();
@@ -87,21 +91,31 @@ export default function SettingsPage() {
1
)}
+
+
Appearance
+ {currentHandle && (
+
+
+
+
+ Filters & Blocks
+
+ )}
-
-
+
+
- Migrate Apollo export
+ Migrate subreddits
@@ -111,7 +125,7 @@ export default function SettingsPage() {
Terms & Privacy
@@ -123,7 +137,7 @@ export default function SettingsPage() {
-
+
Contact us{" "}
diff --git a/src/pages/settings/UpdateAppPage.tsx b/src/pages/settings/UpdateAppPage.tsx
index 30e0f72f5d..a632b409ba 100644
--- a/src/pages/settings/UpdateAppPage.tsx
+++ b/src/pages/settings/UpdateAppPage.tsx
@@ -95,7 +95,7 @@ export default function UpdateAppPage() {
@@ -126,7 +126,7 @@ export default function UpdateAppPage() {
)}
{status === "current" && (
- wefwef is up to date
+ Voyager is up to date
)}
diff --git a/src/pages/shared/CommunitySidebarPage.tsx b/src/pages/shared/CommunitySidebarPage.tsx
index d1c64d544b..44129d9446 100644
--- a/src/pages/shared/CommunitySidebarPage.tsx
+++ b/src/pages/shared/CommunitySidebarPage.tsx
@@ -13,7 +13,7 @@ import { useEffect } from "react";
import { getCommunity } from "../../features/community/communitySlice";
import { useBuildGeneralBrowseLink } from "../../helpers/routes";
import { NewPostContextProvider } from "../../features/post/new/NewPostModal";
-import { CenteredSpinner } from "../../features/post/detail/PostDetail";
+import { CenteredSpinner } from "../posts/PostPage";
import Sidebar from "../../features/community/sidebar/Sidebar";
import AppContent from "../../features/shared/AppContent";
@@ -28,13 +28,13 @@ export default function CommunitySidebarPage() {
(state) => state.community.communityByHandle
);
- const communityResponse = communityByHandle[community];
+ const communityView = communityByHandle[community];
useEffect(() => {
- if (communityResponse) return;
+ if (communityView) return;
dispatch(getCommunity(community));
- }, [community, dispatch, communityResponse]);
+ }, [community, dispatch, communityView]);
return (
@@ -56,8 +56,8 @@ export default function CommunitySidebarPage() {
- {communityResponse ? (
-
+ {communityView ? (
+
) : (
)}
diff --git a/src/services/db.ts b/src/services/db.ts
index 76c2191d2f..4664e06db1 100644
--- a/src/services/db.ts
+++ b/src/services/db.ts
@@ -1,4 +1,4 @@
-import Dexie, { Table } from "dexie";
+import Dexie, { Table, Transaction } from "dexie";
export interface IPostMetadata {
post_id: number;
@@ -7,25 +7,99 @@ export interface IPostMetadata {
hidden_updated_at?: number;
}
+export const OPostAppearanceType = {
+ Compact: "compact",
+ Large: "large",
+} as const;
+
+export type PostAppearanceType =
+ (typeof OPostAppearanceType)[keyof typeof OPostAppearanceType];
+
+export const OCommentThreadCollapse = {
+ Always: "always",
+ Never: "never",
+} as const;
+
+export type CommentThreadCollapse =
+ (typeof OCommentThreadCollapse)[keyof typeof OCommentThreadCollapse];
+
+export const OCommentDefaultSort = {
+ Hot: "hot",
+ Top: "top",
+ New: "new",
+ Old: "old",
+} as const;
+
+export type CommentDefaultSort =
+ (typeof OCommentDefaultSort)[keyof typeof OCommentDefaultSort];
+
+export type SettingValueTypes = {
+ collapse_comment_threads: CommentThreadCollapse;
+ default_comment_sort: CommentDefaultSort;
+ post_appearance_type: PostAppearanceType;
+ blur_nsfw: boolean;
+ favorite_communities: string[];
+};
+
+export interface ISettingItem {
+ key: T;
+ value: SettingValueTypes[T];
+ user_handle: string;
+ community: string;
+}
+
+const defaultSettings: ISettingItem[] = [
+ {
+ key: "collapse_comment_threads",
+ value: OCommentThreadCollapse.Never,
+ user_handle: "",
+ community: "",
+ },
+ {
+ key: "default_comment_sort",
+ value: OCommentDefaultSort.Hot,
+ user_handle: "",
+ community: "",
+ },
+ {
+ key: "post_appearance_type",
+ value: OPostAppearanceType.Large,
+ user_handle: "",
+ community: "",
+ },
+ {
+ key: "blur_nsfw",
+ value: true,
+ user_handle: "",
+ community: "",
+ },
+];
+
export const CompoundKeys = {
postMetadata: {
post_id_and_user_handle: "[post_id+user_handle]",
user_handle_and_hidden: "[user_handle+hidden]",
},
+ settings: {
+ key_and_user_handle_and_community: "[key+user_handle+community]",
+ },
};
export class WefwefDB extends Dexie {
postMetadatas!: Table;
+ settings!: Table, string>;
constructor() {
super("WefwefDB");
- /* IMPORTANT: Do not alter the version. If you want to change the schema,
- create a higher version and provide migration logic.
+ /* IMPORTANT: Do not alter the version if you're changing an existing schema.
+ If you want to change the schema, create a higher version and provide migration logic.
Always assume there is a device out there with the first version of the app.
+ Also please read the Dexie documentation about versioning.
*/
- this.version(1).stores({
- postMetadatas: `
+ this.version(2)
+ .stores({
+ postMetadatas: `
++,
${CompoundKeys.postMetadata.post_id_and_user_handle},
${CompoundKeys.postMetadata.user_handle_and_hidden},
@@ -34,9 +108,31 @@ export class WefwefDB extends Dexie {
hidden,
hidden_updated_at
`,
+ settings: `
+ ++,
+ key,
+ ${CompoundKeys.settings.key_and_user_handle_and_community},
+ value,
+ user_handle,
+ community
+ `,
+ })
+ .upgrade(async (tx) => {
+ await this.populateDefaultSettings(tx);
+ await this.migrateFromLocalStorageSettings(tx);
+ });
+
+ this.on("populate", async () => {
+ this.transaction("rw", this.settings, async (tx) => {
+ await this.populateDefaultSettings(tx);
+ await this.migrateFromLocalStorageSettings(tx);
+ });
});
}
+ /*
+ * Post Metadata
+ */
async getPostMetadatas(post_id: number | number[], user_handle: string) {
const post_ids = Array.isArray(post_id) ? post_id : [post_id];
@@ -118,6 +214,117 @@ export class WefwefDB extends Dexie {
.limit(limit)
.toArray();
}
+
+ /*
+ * Settings
+ */
+
+ private async populateDefaultSettings(tx: Transaction) {
+ const settingsTable = tx.table("settings");
+ settingsTable.bulkAdd(defaultSettings);
+ }
+
+ private async migrateFromLocalStorageSettings(tx: Transaction) {
+ const localStorageMigrationKeys = {
+ collapse_comment_threads: "appearance--collapse-comment-threads",
+ post_appearance_type: "appearance--post-type",
+ };
+
+ const settingsTable = tx.table("settings");
+
+ return await Promise.all(
+ Object.entries(localStorageMigrationKeys).map(
+ ([key, localStorageKey]) => {
+ const localStorageValue = localStorage.getItem(localStorageKey);
+
+ if (!localStorageValue) {
+ return Promise.resolve();
+ }
+
+ return settingsTable
+ .where(CompoundKeys.settings.key_and_user_handle_and_community)
+ .equals([key, "", ""])
+ .modify({
+ value: JSON.parse(localStorageValue),
+ });
+ }
+ )
+ );
+ }
+
+ private findSetting(key: string, user_handle: string, community: string) {
+ return this.settings
+ .where(CompoundKeys.settings.key_and_user_handle_and_community)
+ .equals([key, user_handle, community])
+ .first();
+ }
+
+ getSetting(
+ key: T,
+ specificity?: {
+ user_handle?: string;
+ community?: string;
+ }
+ ) {
+ const { user_handle = "", community = "" } = specificity || {};
+
+ return this.transaction("r", this.settings, async () => {
+ let setting = await this.findSetting(key, user_handle, community);
+
+ if (!setting && user_handle === "" && community === "") {
+ // Already requested the global setting and it's not found, we can stop here
+ throw new Error(`Setting ${key} not found`);
+ }
+
+ if (!setting && user_handle !== "" && community !== "") {
+ // Try to find the setting with user_handle only, community only
+ setting =
+ (await this.findSetting(key, user_handle, "")) ||
+ (await this.findSetting(key, "", community));
+ }
+
+ if (!setting) {
+ // Try to find the global setting
+ setting = await this.findSetting(key, "", "");
+ }
+
+ if (!setting) {
+ throw new Error(`Setting ${key} not found`);
+ }
+
+ return setting.value as SettingValueTypes[T];
+ });
+ }
+
+ async setSetting(
+ key: T,
+ value: SettingValueTypes[T],
+ specificity?: {
+ user_handle?: string;
+ community?: string;
+ }
+ ) {
+ const { user_handle = "", community = "" } = specificity || {};
+
+ this.transaction("rw", this.settings, async () => {
+ const query = this.settings
+ .where(CompoundKeys.settings.key_and_user_handle_and_community)
+ .equals([key, user_handle, community]);
+
+ const item = await query.first();
+
+ if (item) {
+ return await query.modify({ value });
+ }
+
+ return await this.settings.add({
+ key,
+ value,
+ user_handle,
+ community,
+ });
+ });
+ }
}
export const db = new WefwefDB();
diff --git a/src/services/lemmy.ts b/src/services/lemmy.ts
index 04ea10c3c4..4e80f5520e 100644
--- a/src/services/lemmy.ts
+++ b/src/services/lemmy.ts
@@ -1,11 +1,28 @@
import { LemmyHttp } from "lemmy-js-client";
import { reduceFileSize } from "../helpers/imageCompress";
+const UNPROXIED_LEMMY_SERVERS = [
+ "lemmy.ml",
+ "beehaw.org",
+ "sh.itjust.works",
+ "lemm.ee",
+ "feddit.de",
+ "midwest.social",
+ "lemmynsfw.com",
+ "lemmy.ca",
+ "lemmy.sdf.org",
+ "lemmy.world",
+];
+
function buildBaseUrl(url: string): string {
- // if (url === "lemmy.world") {
- // return `https://lemmy.world`;
- // }
+ if (UNPROXIED_LEMMY_SERVERS.includes(url)) {
+ return `https://${url}`;
+ }
+
+ return buildProxiedBaseUrl(url);
+}
+function buildProxiedBaseUrl(url: string): string {
return `${location.origin}/api/${url}`;
}
@@ -37,8 +54,10 @@ export async function uploadImage(url: string, auth: string, image: File) {
formData.append("images[]", compressedImageIfNeeded);
+ // All requests for image upload must be proxied due to Lemmy not accepting
+ // parameterized JWT for this request (see: https://github.com/LemmyNet/lemmy/issues/3567)
const response = await fetch(
- `${buildBaseUrl(url)}${PICTRS_URL}?${new URLSearchParams({ auth })}`,
+ `${buildProxiedBaseUrl(url)}${PICTRS_URL}?${new URLSearchParams({ auth })}`,
{
method: "POST",
body: formData,
diff --git a/src/setupTests.ts b/src/setupTests.ts
index b06c239ace..6f75928da6 100644
--- a/src/setupTests.ts
+++ b/src/setupTests.ts
@@ -12,6 +12,8 @@ window.matchMedia =
matches: false,
addListener: function () {},
removeListener: function () {},
+ addEventListener: function () {},
+ removeEventListener: function () {},
};
};
diff --git a/src/store.ts b/src/store.ts
index 6f1e2b9d85..e72e8e7c8b 100644
--- a/src/store.ts
+++ b/src/store.ts
@@ -6,7 +6,9 @@ import commentSlice from "./features/comment/commentSlice";
import communitySlice from "./features/community/communitySlice";
import userSlice from "./features/user/userSlice";
import inboxSlice from "./features/inbox/inboxSlice";
-import appearanceSlice from "./features/settings/appearance/appearanceSlice";
+import appearanceSlice, {
+ fetchSettingsFromDatabase,
+} from "./features/settings/appearance/appearanceSlice";
const store = configureStore({
reducer: {
@@ -25,4 +27,7 @@ export type AppDispatch = typeof store.dispatch;
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook = useSelector;
+// Load settings from DB into the store
+store.dispatch(fetchSettingsFromDatabase());
+
export default store;
diff --git a/src/theme/variables.ts b/src/theme/variables.ts
index d180ca685e..8b11ad5d75 100644
--- a/src/theme/variables.ts
+++ b/src/theme/variables.ts
@@ -89,6 +89,11 @@ export const baseVariables = css`
--ion-color-step-100: #f3f3f3;
--unread-item-background-color: #fffcd9;
+
+ --ion-color-text-aside: rgba(0, 0, 0, 0.55);
+
+ --read-color: rgba(0, 0, 0, 0.45);
+ --read-color-medium: rgba(0, 0, 0, 0.4);
}
.ios body {
@@ -194,6 +199,11 @@ export const darkVariables = css`
--thick-separator-color: rgba(255, 255, 255, 0.08);
--unread-item-background-color: #1e1c00;
+
+ --ion-color-text-aside: rgba(255, 255, 255, 0.65);
+
+ --read-color: rgba(255, 255, 255, 0.6);
+ --read-color-medium: rgba(255, 255, 255, 0.4);
}
// iOS Dark Theme
diff --git a/yarn.lock b/yarn.lock
index 966e16ac2c..7104934ea4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5378,10 +5378,10 @@ pend@~1.2.0:
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==
-photoswipe@^5.3.7:
- version "5.3.7"
- resolved "https://registry.yarnpkg.com/photoswipe/-/photoswipe-5.3.7.tgz#c67df67aaddb5705bcf8ff265bd2086f57805756"
- integrity sha512-zsyLsTTLFrj0XR1m4/hO7qNooboFKUrDy+Zt5i2d6qjFPAtBjzaj/Xtydso4uxzcXpcqbTmyxDibb3BcSISseg==
+photoswipe@^5.3.8:
+ version "5.3.8"
+ resolved "https://registry.yarnpkg.com/photoswipe/-/photoswipe-5.3.8.tgz#bea7db18e79383833f85c005b833da2029f0daee"
+ integrity sha512-4vTzOQt8GP4Chsm0s+8j2xDtVHAEN252PxrU12A1zXauNn0zD5HRHgjALKO2GKTyBnTnOrJUOxbV8LTrFIMrYw==
picocolors@^1.0.0:
version "1.0.0"
@@ -5661,11 +5661,6 @@ react-markdown@^8.0.7:
unist-util-visit "^4.0.0"
vfile "^5.0.0"
-react-photoswipe-gallery@^2.2.7:
- version "2.2.7"
- resolved "https://registry.yarnpkg.com/react-photoswipe-gallery/-/react-photoswipe-gallery-2.2.7.tgz#27fd45e84a3b7f15846761830c600b2c719053e3"
- integrity sha512-AEYNoL4/IIRosIUonn4haaFQNn1ui4vdVgAY9LHd/imVamNCkqUcyWeT6317UILp/yJI2gohsd3lWmcJEbjCag==
-
react-redux@^8.1.1:
version "8.1.1"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.1.1.tgz#8e740f3fd864a4cd0de5ba9cdc8ad39cc9e7c81a"