mirror of
https://github.com/linkwarden/linkwarden.git
synced 2026-03-03 03:47:02 +00:00
feat(mobile+web): add open in default browser option + cleaner code
This commit is contained in:
@@ -84,7 +84,7 @@ export default function DashboardScreen() {
|
||||
contentInsetAdjustmentBehavior="automatic"
|
||||
>
|
||||
{orderedSections.map((sectionData, i) => {
|
||||
if (!collections || !collections[0]) return <></>;
|
||||
if (!collections || !collections[0]) return null;
|
||||
|
||||
const collection = collections.find(
|
||||
(c) => c.id === sectionData.collectionId
|
||||
@@ -92,7 +92,7 @@ export default function DashboardScreen() {
|
||||
|
||||
return (
|
||||
<DashboardSection
|
||||
key={i}
|
||||
key={sectionData.id}
|
||||
sectionData={sectionData}
|
||||
collection={collection}
|
||||
collectionLinks={
|
||||
|
||||
@@ -14,9 +14,9 @@ import { useColorScheme } from "nativewind";
|
||||
import { rawTheme, ThemeName } from "@/lib/colors";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
AppWindowMac,
|
||||
Check,
|
||||
FileText,
|
||||
Globe,
|
||||
ExternalLink,
|
||||
LogOut,
|
||||
Mail,
|
||||
Moon,
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
Sun,
|
||||
} from "lucide-react-native";
|
||||
import useDataStore from "@/store/data";
|
||||
import { ArchivedFormat } from "@/types/global";
|
||||
import * as Clipboard from "expo-clipboard";
|
||||
|
||||
export default function SettingsScreen() {
|
||||
@@ -145,26 +144,24 @@ export default function SettingsScreen() {
|
||||
</View>
|
||||
|
||||
<View>
|
||||
<Text className="mb-4 mx-4 text-neutral">
|
||||
Default Behavior for Opening Links
|
||||
</Text>
|
||||
<Text className="mb-4 mx-4 text-neutral">Preferred Browser</Text>
|
||||
<View className="bg-base-200 rounded-xl flex-col">
|
||||
<TouchableOpacity
|
||||
className="flex-row gap-2 items-center justify-between py-3 px-4"
|
||||
onPress={() =>
|
||||
updateData({
|
||||
preferredFormat: null,
|
||||
preferredBrowser: "app",
|
||||
})
|
||||
}
|
||||
>
|
||||
<View className="flex-row items-center gap-2">
|
||||
<Globe
|
||||
<AppWindowMac
|
||||
size={20}
|
||||
color={rawTheme[colorScheme as ThemeName].neutral}
|
||||
/>
|
||||
<Text className="text-base-content">Open original content</Text>
|
||||
<Text className="text-base-content">In app browser</Text>
|
||||
</View>
|
||||
{data.preferredFormat === null ? (
|
||||
{data.preferredBrowser === "app" ? (
|
||||
<Check
|
||||
size={20}
|
||||
color={rawTheme[colorScheme as ThemeName].primary}
|
||||
@@ -176,18 +173,20 @@ export default function SettingsScreen() {
|
||||
className="flex-row gap-2 items-center justify-between py-3 px-4"
|
||||
onPress={() =>
|
||||
updateData({
|
||||
preferredFormat: ArchivedFormat.readability,
|
||||
preferredBrowser: "system",
|
||||
})
|
||||
}
|
||||
>
|
||||
<View className="flex-row items-center gap-2">
|
||||
<FileText
|
||||
<ExternalLink
|
||||
size={20}
|
||||
color={rawTheme[colorScheme as ThemeName].neutral}
|
||||
/>
|
||||
<Text className="text-base-content">Open reader view</Text>
|
||||
<Text className="text-base-content">
|
||||
System default browser
|
||||
</Text>
|
||||
</View>
|
||||
{data.preferredFormat === ArchivedFormat.readability ? (
|
||||
{data.preferredBrowser === "system" ? (
|
||||
<Check
|
||||
size={20}
|
||||
color={rawTheme[colorScheme as ThemeName].primary}
|
||||
@@ -230,7 +229,13 @@ export default function SettingsScreen() {
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
container: Platform.select({
|
||||
ios: {
|
||||
flex: 1,
|
||||
paddingBottom: 83,
|
||||
},
|
||||
default: {
|
||||
flex: 1,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -21,6 +21,7 @@ import { useColorScheme } from "nativewind";
|
||||
import { rawTheme, ThemeName } from "@/lib/colors";
|
||||
import { CalendarDays, Link } from "lucide-react-native";
|
||||
import useTmpStore from "@/store/tmp";
|
||||
import { ArchivedFormat } from "@/types/global";
|
||||
|
||||
const CACHE_DIR = FileSystem.documentDirectory + "archivedData/readable/";
|
||||
const htmlPath = (id: string) => `${CACHE_DIR}link_${id}.html`;
|
||||
@@ -105,16 +106,10 @@ export default function LinkScreen() {
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
// original
|
||||
else if (link?.id && !format && user && link.url) {
|
||||
setUrl(link.url);
|
||||
}
|
||||
|
||||
// other formats
|
||||
else if (link?.id && format) {
|
||||
} else if (link?.id && format) {
|
||||
setUrl(`${auth.instance}/api/v1/archives/${link.id}?format=${format}`);
|
||||
} else if (link?.id) {
|
||||
setUrl(link.url as string);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +179,10 @@ export default function LinkScreen() {
|
||||
className={isLoading ? "opacity-0" : "flex-1"}
|
||||
source={{
|
||||
uri: url,
|
||||
headers: format ? { Authorization: `Bearer ${auth.session}` } : {},
|
||||
headers:
|
||||
format || link?.type !== "url"
|
||||
? { Authorization: `Bearer ${auth.session}` }
|
||||
: {},
|
||||
}}
|
||||
onLoadEnd={() => setIsLoading(false)}
|
||||
/>
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
import { decode } from "html-entities";
|
||||
import { LinkIncludingShortenedCollectionAndTags } from "@linkwarden/types";
|
||||
import { ArchivedFormat } from "@linkwarden/types";
|
||||
import getFormatBasedOnPreference from "@linkwarden/lib/getFormatBasedOnPreference";
|
||||
import getOriginalFormat from "@linkwarden/lib/getOriginalFormat";
|
||||
import {
|
||||
atLeastOneFormatAvailable,
|
||||
formatAvailable,
|
||||
@@ -64,13 +66,27 @@ const LinkListing = ({ link, dashboard }: Props) => {
|
||||
dashboard && "rounded-xl"
|
||||
)}
|
||||
onLongPress={() => {}}
|
||||
onPress={() =>
|
||||
router.navigate(
|
||||
data.preferredFormat
|
||||
? `/links/${link.id}?format=${data.preferredFormat}`
|
||||
: `/links/${link.id}`
|
||||
)
|
||||
}
|
||||
onPress={() => {
|
||||
if (user) {
|
||||
const format = getFormatBasedOnPreference({
|
||||
link,
|
||||
preference: user.linksRouteTo,
|
||||
});
|
||||
|
||||
data.preferredBrowser === "app"
|
||||
? router.navigate(
|
||||
format !== null
|
||||
? `/links/${link.id}?format=${format}`
|
||||
: `/links/${link.id}`
|
||||
)
|
||||
: Linking.openURL(
|
||||
format !== null
|
||||
? auth.instance +
|
||||
`/preserved/${link?.id}?format=${format}`
|
||||
: (link.url as string)
|
||||
);
|
||||
}
|
||||
}}
|
||||
android_ripple={{
|
||||
color: colorScheme === "dark" ? "rgba(255,255,255,0.2)" : "#ddd",
|
||||
borderless: false,
|
||||
@@ -158,20 +174,30 @@ const LinkListing = ({ link, dashboard }: Props) => {
|
||||
|
||||
<ContextMenu.Content avoidCollisions>
|
||||
<ContextMenu.Item
|
||||
key="open-in-app"
|
||||
onSelect={() => router.navigate(`/links/${link.id}`)}
|
||||
>
|
||||
<ContextMenu.ItemTitle>Open Link</ContextMenu.ItemTitle>
|
||||
</ContextMenu.Item>
|
||||
key="open-original"
|
||||
onSelect={() => {
|
||||
if (user && link) {
|
||||
const format = getOriginalFormat(link);
|
||||
|
||||
data.preferredBrowser === "app"
|
||||
? router.navigate(
|
||||
format !== null
|
||||
? `/links/${link.id}?format=${format}`
|
||||
: `/links/${link.id}`
|
||||
)
|
||||
: Linking.openURL(
|
||||
format !== null
|
||||
? auth.instance +
|
||||
`/preserved/${link?.id}?format=${format}`
|
||||
: (link.url as string)
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ContextMenu.ItemTitle>Open Original</ContextMenu.ItemTitle>
|
||||
</ContextMenu.Item>
|
||||
{link?.url && (
|
||||
<>
|
||||
<ContextMenu.Item
|
||||
key="open-in-browser"
|
||||
onSelect={() => Linking.openURL(link.url as string)}
|
||||
>
|
||||
<ContextMenu.ItemTitle>Open in Browser</ContextMenu.ItemTitle>
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item
|
||||
key="copy-url"
|
||||
onSelect={async () => {
|
||||
|
||||
@@ -16,7 +16,7 @@ const useDataStore = create<DataStore>((set, get) => ({
|
||||
url: "",
|
||||
},
|
||||
theme: "system",
|
||||
preferredFormat: null,
|
||||
preferredBrowser: "app",
|
||||
},
|
||||
setData: async () => {
|
||||
const dataString = JSON.parse((await AsyncStorage.getItem("data")) || "{}");
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import {
|
||||
AccountSettings,
|
||||
LinkIncludingShortenedCollectionAndTags,
|
||||
} from "@linkwarden/types";
|
||||
import { generateLinkHref } from "@linkwarden/lib/generateLinkHref";
|
||||
import { LinkIncludingShortenedCollectionAndTags } from "@linkwarden/types";
|
||||
import getFormatBasedOnPreference from "@linkwarden/lib/getFormatBasedOnPreference";
|
||||
import { LinksRouteTo } from "@linkwarden/prisma/client";
|
||||
|
||||
const openLink = (
|
||||
@@ -13,7 +10,17 @@ const openLink = (
|
||||
if (user.linksRouteTo === LinksRouteTo.DETAILS) {
|
||||
openModal();
|
||||
} else {
|
||||
window.open(generateLinkHref(link, user), "_blank");
|
||||
const format = getFormatBasedOnPreference({
|
||||
link,
|
||||
preference: user.linksRouteTo,
|
||||
});
|
||||
|
||||
window.open(
|
||||
format !== null
|
||||
? `/preserved/${link?.id}?format=${format}`
|
||||
: (link.url as string),
|
||||
"_blank"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import {
|
||||
AccountSettings,
|
||||
ArchivedFormat,
|
||||
LinkIncludingShortenedCollectionAndTags,
|
||||
} from "@linkwarden/types";
|
||||
import { LinksRouteTo } from "@linkwarden/prisma/client";
|
||||
import { formatAvailable } from "@linkwarden/lib/formatStats";
|
||||
|
||||
export const generateLinkHref = (
|
||||
link: LinkIncludingShortenedCollectionAndTags,
|
||||
account: AccountSettings,
|
||||
instanceURL: string | null = "",
|
||||
apiEndpoint: boolean = false
|
||||
): string => {
|
||||
// Return the links href based on the account's preference
|
||||
// If the user's preference is not available, return the original link
|
||||
|
||||
let endpoint = "/preserved";
|
||||
if (apiEndpoint) {
|
||||
endpoint = "/api/v1/archives";
|
||||
}
|
||||
|
||||
if (account.linksRouteTo === LinksRouteTo.ORIGINAL && link.type === "url") {
|
||||
return link.url || "";
|
||||
} else if (account.linksRouteTo === LinksRouteTo.PDF || link.type === "pdf") {
|
||||
if (!formatAvailable(link, "pdf")) return link.url || "";
|
||||
|
||||
return instanceURL + `${endpoint}/${link?.id}?format=${ArchivedFormat.pdf}`;
|
||||
} else if (
|
||||
account.linksRouteTo === LinksRouteTo.READABLE &&
|
||||
link.type === "url"
|
||||
) {
|
||||
if (!formatAvailable(link, "readable")) return link.url || "";
|
||||
|
||||
return (
|
||||
instanceURL +
|
||||
`${endpoint}/${link?.id}?format=${ArchivedFormat.readability}`
|
||||
);
|
||||
} else if (
|
||||
account.linksRouteTo === LinksRouteTo.SCREENSHOT ||
|
||||
link.type === "image"
|
||||
) {
|
||||
if (!formatAvailable(link, "image")) return link.url || "";
|
||||
|
||||
return (
|
||||
instanceURL +
|
||||
`${endpoint}/${link?.id}?format=${
|
||||
link?.image?.endsWith("png") ? ArchivedFormat.png : ArchivedFormat.jpeg
|
||||
}`
|
||||
);
|
||||
} else if (account.linksRouteTo === LinksRouteTo.MONOLITH) {
|
||||
if (!formatAvailable(link, "monolith")) return link.url || "";
|
||||
|
||||
return (
|
||||
instanceURL + `${endpoint}/${link?.id}?format=${ArchivedFormat.monolith}`
|
||||
);
|
||||
} else {
|
||||
return link.url || "";
|
||||
}
|
||||
};
|
||||
43
packages/lib/getFormatBasedOnPreference.ts
Normal file
43
packages/lib/getFormatBasedOnPreference.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import {
|
||||
ArchivedFormat,
|
||||
LinkIncludingShortenedCollectionAndTags,
|
||||
} from "@linkwarden/types";
|
||||
import { LinksRouteTo } from "@linkwarden/prisma/client";
|
||||
import { formatAvailable } from "@linkwarden/lib/formatStats";
|
||||
|
||||
const getFormatBasedOnPreference = ({
|
||||
link,
|
||||
preference,
|
||||
}: {
|
||||
link: LinkIncludingShortenedCollectionAndTags;
|
||||
preference: LinksRouteTo;
|
||||
}) => {
|
||||
// Return the format based on the account's preference
|
||||
// If the user's preference is not available, return null (original url)
|
||||
|
||||
if (preference === LinksRouteTo.ORIGINAL && link.type === "url") {
|
||||
return null;
|
||||
} else if (preference === LinksRouteTo.PDF || link.type === "pdf") {
|
||||
if (!formatAvailable(link, "pdf")) return null;
|
||||
|
||||
return ArchivedFormat.pdf;
|
||||
} else if (preference === LinksRouteTo.READABLE && link.type === "url") {
|
||||
if (!formatAvailable(link, "readable")) return null;
|
||||
|
||||
return ArchivedFormat.readability;
|
||||
} else if (preference === LinksRouteTo.SCREENSHOT || link.type === "image") {
|
||||
if (!formatAvailable(link, "image")) return null;
|
||||
|
||||
return link?.image?.endsWith("png")
|
||||
? ArchivedFormat.png
|
||||
: ArchivedFormat.jpeg;
|
||||
} else if (preference === LinksRouteTo.MONOLITH) {
|
||||
if (!formatAvailable(link, "monolith")) return null;
|
||||
|
||||
return ArchivedFormat.monolith;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export default getFormatBasedOnPreference;
|
||||
18
packages/lib/getOriginalFormat.ts
Normal file
18
packages/lib/getOriginalFormat.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import {
|
||||
ArchivedFormat,
|
||||
LinkIncludingShortenedCollectionAndTags,
|
||||
} from "@linkwarden/types";
|
||||
|
||||
const getOriginalFormat = (
|
||||
link: LinkIncludingShortenedCollectionAndTags
|
||||
): ArchivedFormat | string | null => {
|
||||
if (link.url && link.type === "url") return link.url;
|
||||
else if (link.type === "pdf") return ArchivedFormat.pdf;
|
||||
else if (link.type === "image")
|
||||
return link.image?.endsWith("png")
|
||||
? ArchivedFormat.png
|
||||
: ArchivedFormat.jpeg;
|
||||
else return null;
|
||||
};
|
||||
|
||||
export default getOriginalFormat;
|
||||
@@ -186,5 +186,5 @@ export interface MobileData {
|
||||
url: string;
|
||||
};
|
||||
theme: "light" | "dark" | "system";
|
||||
preferredFormat: ArchivedFormat | null;
|
||||
preferredBrowser: "app" | "system";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user