Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: remember previously used links for migration #933

Merged
merged 33 commits into from
Feb 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
a2efff7
feat: remember previously used links for migration
sharunkumar Nov 14, 2023
dd22d3a
remove unused imports
sharunkumar Nov 14, 2023
302998c
add dependency in useEffect
sharunkumar Nov 14, 2023
76ef7bf
add inset
sharunkumar Nov 14, 2023
8b01d69
add clear button
sharunkumar Nov 14, 2023
e237d55
update clear button style
sharunkumar Nov 14, 2023
af43ad8
added a back button to go from the subs list to the migration page
sharunkumar Nov 14, 2023
b5c69da
lint fix
sharunkumar Nov 14, 2023
30f3211
Merge remote-tracking branch 'upstream/main' into migrate-remember
sharunkumar Nov 16, 2023
95f5274
Merge Release 1.24.2
sharunkumar Nov 18, 2023
644fd20
Merge Release 1.25.0
sharunkumar Nov 23, 2023
132252e
Merge Release 1.26.0 + conflicts resolved
sharunkumar Nov 27, 2023
ee78177
Merge Release 1.27.0
sharunkumar Dec 1, 2023
c13a590
Merge Release 1.28.0
sharunkumar Dec 4, 2023
e6ad500
Merge remote-tracking branch 'upstream/main' into migrate-remember + …
sharunkumar Dec 5, 2023
522addc
Merge Release 1.29.0
sharunkumar Dec 6, 2023
5438be7
Merge Release 1.30.3
sharunkumar Dec 11, 2023
e29301b
not sure how this import got removed
sharunkumar Dec 12, 2023
b8d9627
Merge Release 1.32.2
sharunkumar Dec 18, 2023
c949b73
update reducers
sharunkumar Dec 18, 2023
03c8877
remove unused param
sharunkumar Dec 18, 2023
fc7d8eb
lint fix
sharunkumar Dec 18, 2023
46abca6
Merge Release 1.32.6
sharunkumar Dec 30, 2023
7b3e6ac
Merge Release 1.32.7
sharunkumar Jan 4, 2024
bec24dc
Merge Release 1.33.0
sharunkumar Jan 7, 2024
fa0c240
Merge Release 1.34.1
sharunkumar Jan 15, 2024
83e83d0
Merge Release 1.36.0
sharunkumar Jan 20, 2024
0c7031a
Merge Release 1.37.2
sharunkumar Jan 25, 2024
632cb49
Merge Release 1.37.4
sharunkumar Jan 27, 2024
7426317
Merge Release 1.38.0
sharunkumar Feb 3, 2024
06a9338
Merge Release 1.39.0
sharunkumar Feb 10, 2024
d31e8cc
Merge branch 'main' into migrate-remember
aeharding Feb 16, 2024
093178c
Add separate pages for navigation, refactor add UI, swipe to forget
aeharding Feb 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions src/features/migrate/MigrateList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import {
IonItemOption,
IonItemOptions,
IonItemSliding,
IonLabel,
IonList,
useIonAlert,
} from "@ionic/react";
import { InsetIonItem } from "../user/Profile";
import { useAppDispatch, useAppSelector } from "../../store";
import { useEffect } from "react";
import {
addMigrationLink,
getMigrationLinks,
removeMigrationLink,
} from "./migrationSlice";
import { getPathname, isValidUrl } from "../../helpers/url";
import { parseSubsFromLink } from "./MigrateSubsList";
import useAppToast from "../../helpers/useAppToast";
import { migrateParseError } from "../../helpers/toastMessages";

export default function MigrateList() {
const dispatch = useAppDispatch();
const links = useAppSelector((state) => state.migration.links);
const presentToast = useAppToast();
const [presentAlert] = useIonAlert();

useEffect(() => {
dispatch(getMigrationLinks());
});

function remove(link: string) {
dispatch(removeMigrationLink(link));
}

function add() {
presentAlert({
message: "Paste Multireddit Link",
buttons: [
{
text: "OK",
handler: ({ link }) => {
if (
!isValidUrl(link, { checkProtocol: true, allowRelative: false })
) {
presentToast(migrateParseError);
return;
}

const subs = parseSubsFromLink(link);

if (!subs.length) {
presentToast(migrateParseError);
return;
}

dispatch(addMigrationLink(link));
},
},
"Cancel",
],
inputs: [
{
placeholder: "Multireddit link",
name: "link",
},
],
});
}

return (
<>
<div className="ion-padding">
<p>
This tool is designed for Reddit users migrating to Lemmy to easily
search for communities similar to subscribed subreddits.
</p>
<ul>
<li>
Visit{" "}
<a
href="https://www.reddit.com/subreddits/"
target="_blank"
rel="noopener noreferrer"
>
https://www.reddit.com/subreddits
</a>
</li>
<li>If iOS, open in Safari so you can copy links to clipboard</li>
<li>Login</li>
<li>
Copy the link for &quot;multireddit of your subscriptions&quot; in
the sidebar
</li>
<li>Paste below</li>
</ul>
</div>

<IonList inset>
{links.map((link) => (
<IonItemSliding key={link}>
<IonItemOptions side="end" onIonSwipe={() => remove(link)}>
<IonItemOption
color="danger"
expandable
onClick={() => remove(link)}
>
Forget
</IonItemOption>
</IonItemOptions>
<InsetIonItem
routerLink={`/settings/reddit-migrate/${encodeURIComponent(link)}`}
>
<IonLabel class="ion-text-nowrap">{getPathname(link)}</IonLabel>
</InsetIonItem>
</IonItemSliding>
))}
<IonItemSliding>
<InsetIonItem onClick={() => add()}>
<IonLabel color="primary">Add multireddit link</IonLabel>
</InsetIonItem>
</IonItemSliding>
</IonList>
</>
);
}
40 changes: 40 additions & 0 deletions src/features/migrate/MigrateSubsList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { IonItem, IonList } from "@ionic/react";
import { memo, useMemo } from "react";

interface MigrateSubsListProps {
link: string;
}

function MigrateSubsList({ link }: MigrateSubsListProps) {
const subs = useMemo(() => {
try {
return parseSubsFromLink(decodeURIComponent(link));
} catch (error) {
console.error("Failed to parse link", error);
return [];
}
}, [link]);

return (
<IonList>
{subs?.map((sub) => (
<IonItem
key={sub}
routerLink={`/settings/reddit-migrate/${link}/${sub}`}
>
r/{sub}
</IonItem>
))}
</IonList>
);
}

export default memo(MigrateSubsList);

export function parseSubsFromLink(multiredditUrl: string) {
const { pathname } = new URL(multiredditUrl);

if (!pathname.startsWith("/r/")) return [];

return pathname.slice(3).split("+");
}
41 changes: 41 additions & 0 deletions src/features/migrate/migrationSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
import { AppDispatch } from "../../store";
import { db } from "../../services/db";
import { uniq, without } from "lodash";

interface MigrationSlice {
links: Array<string>;
}

const initialState: MigrationSlice = {
links: [],
};

export const migrationSlice = createSlice({
name: "migration",
initialState,
reducers: {
setMigrationLinks: (state, action: PayloadAction<string[]>) => {
state.links = action.payload;
},
addMigrationLink: (state, action: PayloadAction<string>) => {
state.links = uniq([action.payload, ...state.links]);
db.setSetting("migration_links", state.links);
},
removeMigrationLink: (state, action: PayloadAction<string>) => {
state.links = without(state.links, action.payload);
db.setSetting("migration_links", state.links);
},
},
});

export default migrationSlice.reducer;

export const { setMigrationLinks, addMigrationLink, removeMigrationLink } =
migrationSlice.actions;

export const getMigrationLinks = () => async (dispatch: AppDispatch) => {
const links = await db.getSetting("migration_links");

dispatch(setMigrationLinks(links || []));
};
9 changes: 7 additions & 2 deletions src/helpers/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,13 @@ export function useBuildGeneralBrowseLink() {
const tabName = useContext(TabNameContext);

const buildGeneralBrowseLink = useCallback(
(path: string) =>
`/${tabName || tabRef?.current}/${connectedInstance}${path}`,
(path: string) => {
const tab = tabName || tabRef?.current;
// /settings/lemmy.world is invalid. Posts tab is special case
if (tab !== "posts" && (!path || path === "/")) return `/${tab}`;

return `/${tab}/${connectedInstance}${path}`;
},
// tab should never dynamically change for a rendered buildGeneralBrowseLink tab. So don't re-render
// eslint-disable-next-line react-hooks/exhaustive-deps
[connectedInstance],
Expand Down
6 changes: 6 additions & 0 deletions src/helpers/toastMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,9 @@ export const loginSuccess: AppToastOptions = {
centerText: true,
icon: checkmark,
};

export const migrateParseError: AppToastOptions = {
message:
"Problem parsing link. Please make sure the link you entered is correct.",
color: "warning",
};
2 changes: 1 addition & 1 deletion src/helpers/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function isValidUrl(
return url.protocol === "http:" || url.protocol === "https:";
}

function getPathname(url: string): string | undefined {
export function getPathname(url: string): string | undefined {
try {
return new URL(url).pathname;
} catch {
Expand Down
100 changes: 5 additions & 95 deletions src/routes/pages/settings/RedditDataMigratePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,104 +2,20 @@ import {
IonBackButton,
IonButtons,
IonHeader,
IonInput,
IonItem,
IonList,
IonPage,
IonTitle,
IonToolbar,
} from "@ionic/react";
import AppContent from "../../../features/shared/AppContent";
import { useEffect, useRef, useState } from "react";
import { InsetIonItem } from "../profile/ProfileFeedItemsPage";
import { isValidUrl } from "../../../helpers/url";
import useAppToast from "../../../helpers/useAppToast";
import { useRef } from "react";
import { useSetActivePage } from "../../../features/auth/AppContext";
import MigrateList from "../../../features/migrate/MigrateList";

export default function RedditMigratePage() {
const pageRef = useRef<HTMLElement>(null);

const presentToast = useAppToast();
const [subs, setSubs] = useState<string[] | undefined>();
const [link, setLink] = useState("");

useSetActivePage(pageRef);

useEffect(() => {
if (!isValidUrl(link, { checkProtocol: true, allowRelative: false }))
return;

const subs = parseSubsFromLink(link);

if (!subs.length) {
presentToast({
message:
"Problem parsing link. Please make sure the link you entered is correct.",
color: "warning",
});
setLink("");
return;
}

setSubs(subs);
}, [link, presentToast]);

function renderUpload() {
return (
<>
<div className="ion-padding">
<p>
This tool is designed for Reddit users migrating to Lemmy to easily
search for communities similar to subscribed subreddits.
</p>
<ul>
<li>
Visit{" "}
<a
href="https://www.reddit.com/subreddits/"
target="_blank"
rel="noopener noreferrer"
>
https://www.reddit.com/subreddits
</a>
</li>
<li>If iOS, open in Safari so you can copy links to clipboard</li>
<li>Login</li>
<li>
Copy the link for &quot;multireddit of your subscriptions&quot; in
the sidebar
</li>
<li>Paste below</li>
</ul>
</div>
<IonList inset>
<label htmlFor="upload-csv">
<InsetIonItem>
<IonInput
label="Multireddit link"
type="text"
value={link}
onIonInput={(e) => setLink(e.target.value as string)}
/>
</InsetIonItem>
</label>
</IonList>
</>
);
}

function renderSubs() {
return (
<IonList>
{subs?.map((sub) => (
<IonItem key={sub} routerLink={`/settings/reddit-migrate/${sub}`}>
r/{sub}
</IonItem>
))}
</IonList>
);
}

return (
<IonPage ref={pageRef} className="grey-bg">
<IonHeader>
Expand All @@ -111,15 +27,9 @@ export default function RedditMigratePage() {
<IonTitle>Migrate</IonTitle>
</IonToolbar>
</IonHeader>
<AppContent scrollY>{!subs ? renderUpload() : renderSubs()}</AppContent>
<AppContent scrollY>
<MigrateList />
</AppContent>
</IonPage>
);
}

function parseSubsFromLink(multiredditUrl: string) {
const { pathname } = new URL(multiredditUrl);

if (!pathname.startsWith("/r/")) return [];

return pathname.slice(3).split("+");
}
Loading
Loading