mirror of
https://github.com/linkwarden/linkwarden.git
synced 2026-03-03 03:57:01 +00:00
Compare commits
68 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
463883383b | ||
|
|
f13ab8500a | ||
|
|
3c7bcfe3e4 | ||
|
|
476a9d78a4 | ||
|
|
fa2d439b3e | ||
|
|
0907c3caa2 | ||
|
|
a4266b1a62 | ||
|
|
05bebd8703 | ||
|
|
2c727ccd47 | ||
|
|
1205cdce1c | ||
|
|
6ae4c37d0c | ||
|
|
4148c0f5fb | ||
|
|
f7e7fda779 | ||
|
|
fbafa3df4e | ||
|
|
7d45249e8f | ||
|
|
96472243db | ||
|
|
4ef0e6bd86 | ||
|
|
daf5dc4f22 | ||
|
|
43f8da30d3 | ||
|
|
d2e5dbd521 | ||
|
|
9c05aaf2df | ||
|
|
4f2e26c31f | ||
|
|
328e031ebd | ||
|
|
93d4e58306 | ||
|
|
2255fb3a6c | ||
|
|
d23a935cae | ||
|
|
57bde730f8 | ||
|
|
01e0587012 | ||
|
|
c9f8b233d5 | ||
|
|
2685188ba6 | ||
|
|
942672ea95 | ||
|
|
ac57cc0202 | ||
|
|
69b5919a96 | ||
|
|
09823fe776 | ||
|
|
a97cb229ff | ||
|
|
cbf3756f16 | ||
|
|
900f62487b | ||
|
|
bc6f9a55e4 | ||
|
|
e7b7a7f46a | ||
|
|
f3b23dadd1 | ||
|
|
8de57d3875 | ||
|
|
bfb6b25c28 | ||
|
|
a24786a513 | ||
|
|
6b9f181585 | ||
|
|
b21e2c6ffd | ||
|
|
96dcbcfb79 | ||
|
|
9c45f933cd | ||
|
|
427c062075 | ||
|
|
2130422c2a | ||
|
|
8133900e76 | ||
|
|
9ad4a3ee87 | ||
|
|
8c6169320a | ||
|
|
ea0916d826 | ||
|
|
14550a89e6 | ||
|
|
c1c5b3e953 | ||
|
|
bebe9671cb | ||
|
|
bcf12e25a1 | ||
|
|
4d9725f66c | ||
|
|
aa045801cc | ||
|
|
8dd54a1c26 | ||
|
|
ce76eb0b74 | ||
|
|
453dfbcfb8 | ||
|
|
0ca4f72e53 | ||
|
|
645e8dc4b2 | ||
|
|
bab44a942a | ||
|
|
327826d760 | ||
|
|
2441470849 | ||
|
|
be532d5455 |
11
.env.sample
11
.env.sample
@@ -68,6 +68,10 @@ ANTHROPIC_MODEL=
|
||||
OPENROUTER_API_KEY=
|
||||
OPENROUTER_MODEL=
|
||||
|
||||
# https://ai-sdk.dev/providers/ai-sdk-providers/perplexity
|
||||
PERPLEXITY_API_KEY=
|
||||
PERPLEXITY_MODEL=
|
||||
|
||||
# MeiliSearch Settings
|
||||
MEILI_HOST=
|
||||
MEILI_MASTER_KEY=
|
||||
@@ -399,6 +403,13 @@ STRAVA_CUSTOM_NAME=
|
||||
STRAVA_CLIENT_ID=
|
||||
STRAVA_CLIENT_SECRET=
|
||||
|
||||
# Synology
|
||||
NEXT_PUBLIC_SYNOLOGY_ENABLED=
|
||||
SYNOLOGY_CUSTOM_NAME=
|
||||
SYNOLOGY_CLIENT_ID=
|
||||
SYNOLOGY_CLIENT_SECRET=
|
||||
SYNOLOGY_WELLKNOWN_URL=
|
||||
|
||||
# Todoist
|
||||
NEXT_PUBLIC_TODOIST_ENABLED=
|
||||
TODOIST_CUSTOM_NAME=
|
||||
|
||||
@@ -10,7 +10,16 @@
|
||||
"newArchEnabled": true,
|
||||
"ios": {
|
||||
"supportsTablet": true,
|
||||
"bundleIdentifier": "app.linkwarden"
|
||||
"bundleIdentifier": "app.linkwarden",
|
||||
"entitlements": {
|
||||
"com.apple.security.application-groups": ["group.app.linkwarden"]
|
||||
},
|
||||
"infoPlist": {
|
||||
"ITSAppUsesNonExemptEncryption": false,
|
||||
"NSAppTransportSecurity": {
|
||||
"NSAllowsArbitraryLoads": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
@@ -41,8 +50,26 @@
|
||||
}
|
||||
],
|
||||
"expo-secure-store",
|
||||
"expo-share-intent",
|
||||
"./plugins/with-daynight-transparent-nav"
|
||||
[
|
||||
"expo-share-intent",
|
||||
{
|
||||
"iosAppGroupIdentifier": "group.app.linkwarden"
|
||||
}
|
||||
],
|
||||
"./plugins/with-daynight-transparent-nav",
|
||||
[
|
||||
"expo-build-properties",
|
||||
{
|
||||
"android": {
|
||||
"enableProguardInReleaseBuilds": true,
|
||||
"extraProguardRules": "-keep public class com.horcrux.svg.** {*;}",
|
||||
"allowBackup": false,
|
||||
"compileSdkVersion": 35,
|
||||
"targetSdkVersion": 35,
|
||||
"buildToolsVersion": "35.0.0"
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"experiments": {
|
||||
"typedRoutes": true
|
||||
@@ -55,6 +82,15 @@
|
||||
"androidNavigationBar": {
|
||||
"backgroundColor": "#ffffff",
|
||||
"barStyle": "dark-content"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"router": {
|
||||
"origin": false
|
||||
},
|
||||
"eas": {
|
||||
"projectId": "34f82639-7a25-4ebe-81c8-2db521b612cf"
|
||||
}
|
||||
},
|
||||
"owner": "linkwarden"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@ import {
|
||||
View,
|
||||
} from "react-native";
|
||||
import { SafeAreaView } from "react-native-safe-area-context";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { useDashboardData } from "@linkwarden/router/dashboardData";
|
||||
import useAuthStore from "@/store/auth";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { DashboardSection, DashboardSectionType } from "@prisma/client";
|
||||
import { DashboardSection } from "@linkwarden/prisma/client";
|
||||
import { useUser } from "@linkwarden/router/user";
|
||||
import { useCollections } from "@linkwarden/router/collections";
|
||||
import { useTags } from "@linkwarden/router/tags";
|
||||
@@ -32,6 +32,13 @@ import {
|
||||
} from "lucide-react-native";
|
||||
import Spinner from "@/components/ui/Spinner";
|
||||
|
||||
// Don't remove this, spent a couple of days to figure out why the app crashes in production :|
|
||||
type DashboardSectionType =
|
||||
| "STATS"
|
||||
| "RECENT_LINKS"
|
||||
| "PINNED_LINKS"
|
||||
| "COLLECTION";
|
||||
|
||||
export default function DashboardScreen() {
|
||||
const { auth } = useAuthStore();
|
||||
const {
|
||||
@@ -76,6 +83,12 @@ export default function DashboardScreen() {
|
||||
});
|
||||
}, [dashboardSections]);
|
||||
|
||||
const RenderItem = React.memo(
|
||||
({ item }: { item: LinkIncludingShortenedCollectionAndTags }) => {
|
||||
return <LinkListing link={item} dashboard />;
|
||||
}
|
||||
);
|
||||
|
||||
interface SectionProps {
|
||||
sectionData: { type: DashboardSectionType };
|
||||
collection?: any;
|
||||
@@ -100,7 +113,7 @@ export default function DashboardScreen() {
|
||||
collectionLinks = [],
|
||||
}) => {
|
||||
switch (sectionData.type) {
|
||||
case DashboardSectionType.STATS:
|
||||
case "STATS":
|
||||
return (
|
||||
<View className="flex-col gap-4 max-w-full px-5">
|
||||
<View className="flex-row gap-4">
|
||||
@@ -134,7 +147,7 @@ export default function DashboardScreen() {
|
||||
</View>
|
||||
);
|
||||
|
||||
case DashboardSectionType.RECENT_LINKS:
|
||||
case "RECENT_LINKS":
|
||||
return (
|
||||
<>
|
||||
<View className="flex-row justify-between items-center px-5">
|
||||
@@ -203,7 +216,7 @@ export default function DashboardScreen() {
|
||||
</>
|
||||
);
|
||||
|
||||
case DashboardSectionType.PINNED_LINKS:
|
||||
case "PINNED_LINKS":
|
||||
return (
|
||||
<>
|
||||
<View className="flex-row justify-between items-center px-5">
|
||||
@@ -265,7 +278,7 @@ export default function DashboardScreen() {
|
||||
</>
|
||||
);
|
||||
|
||||
case DashboardSectionType.COLLECTION:
|
||||
case "COLLECTION":
|
||||
return collection?.id ? (
|
||||
<>
|
||||
<View className="flex-row justify-between items-center px-5">
|
||||
@@ -331,12 +344,6 @@ export default function DashboardScreen() {
|
||||
}
|
||||
};
|
||||
|
||||
const RenderItem = React.memo(
|
||||
({ item }: { item: LinkIncludingShortenedCollectionAndTags }) => {
|
||||
return <LinkListing link={item} dashboard />;
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
style={styles.container}
|
||||
|
||||
@@ -19,6 +19,7 @@ import useDataStore from "@/store/data";
|
||||
import useAuthStore from "@/store/auth";
|
||||
import { QueryClient } from "@tanstack/react-query";
|
||||
import * as FileSystem from "expo-file-system";
|
||||
import { KeyboardProvider } from "react-native-keyboard-controller";
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
@@ -116,85 +117,87 @@ export default function RootLayout() {
|
||||
<View
|
||||
style={[{ flex: 1 }, colorScheme === "dark" ? darkTheme : lightTheme]}
|
||||
>
|
||||
<SheetProvider>
|
||||
{!isLoading && (
|
||||
<Stack
|
||||
screenOptions={{
|
||||
navigationBarColor:
|
||||
rawTheme[colorScheme as ThemeName]["base-200"],
|
||||
headerShown: false,
|
||||
contentStyle: {
|
||||
backgroundColor:
|
||||
rawTheme[colorScheme as ThemeName]["base-100"],
|
||||
},
|
||||
...Platform.select({
|
||||
android: {
|
||||
statusBarStyle: colorScheme === "dark" ? "light" : "dark",
|
||||
statusBarBackgroundColor:
|
||||
<KeyboardProvider>
|
||||
<SheetProvider>
|
||||
{!isLoading && (
|
||||
<Stack
|
||||
screenOptions={{
|
||||
navigationBarColor:
|
||||
rawTheme[colorScheme as ThemeName]["base-200"],
|
||||
headerShown: false,
|
||||
contentStyle: {
|
||||
backgroundColor:
|
||||
rawTheme[colorScheme as ThemeName]["base-100"],
|
||||
},
|
||||
}),
|
||||
}}
|
||||
>
|
||||
{/* <Stack.Screen name="(tabs)" /> */}
|
||||
<Stack.Screen
|
||||
name="links/[id]"
|
||||
options={{
|
||||
headerShown: true,
|
||||
headerBackTitle: "Back",
|
||||
headerTitle: "",
|
||||
headerTintColor: colorScheme === "dark" ? "white" : "black",
|
||||
navigationBarColor:
|
||||
rawTheme[colorScheme as ThemeName]["base-100"],
|
||||
headerStyle: {
|
||||
backgroundColor:
|
||||
colorScheme === "dark"
|
||||
? rawTheme["dark"]["base-100"]
|
||||
: "white",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="login"
|
||||
options={{
|
||||
navigationBarColor:
|
||||
rawTheme[colorScheme as ThemeName]["base-100"],
|
||||
...Platform.select({
|
||||
android: {
|
||||
statusBarStyle:
|
||||
colorScheme === "light" ? "light" : "dark",
|
||||
statusBarStyle: colorScheme === "dark" ? "light" : "dark",
|
||||
statusBarBackgroundColor:
|
||||
rawTheme[colorScheme as ThemeName]["primary"],
|
||||
rawTheme[colorScheme as ThemeName]["base-100"],
|
||||
},
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="index"
|
||||
options={{
|
||||
navigationBarColor:
|
||||
rawTheme[colorScheme as ThemeName]["base-100"],
|
||||
...Platform.select({
|
||||
android: {
|
||||
statusBarStyle:
|
||||
colorScheme === "light" ? "light" : "dark",
|
||||
statusBarBackgroundColor:
|
||||
rawTheme[colorScheme as ThemeName]["primary"],
|
||||
>
|
||||
{/* <Stack.Screen name="(tabs)" /> */}
|
||||
<Stack.Screen
|
||||
name="links/[id]"
|
||||
options={{
|
||||
headerShown: true,
|
||||
headerBackTitle: "Back",
|
||||
headerTitle: "",
|
||||
headerTintColor: colorScheme === "dark" ? "white" : "black",
|
||||
navigationBarColor:
|
||||
rawTheme[colorScheme as ThemeName]["base-100"],
|
||||
headerStyle: {
|
||||
backgroundColor:
|
||||
colorScheme === "dark"
|
||||
? rawTheme["dark"]["base-100"]
|
||||
: "white",
|
||||
},
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="incoming"
|
||||
options={{
|
||||
navigationBarColor:
|
||||
rawTheme[colorScheme as ThemeName]["base-100"],
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen name="+not-found" />
|
||||
</Stack>
|
||||
)}
|
||||
</SheetProvider>
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="login"
|
||||
options={{
|
||||
navigationBarColor:
|
||||
rawTheme[colorScheme as ThemeName]["base-100"],
|
||||
...Platform.select({
|
||||
android: {
|
||||
statusBarStyle:
|
||||
colorScheme === "light" ? "light" : "dark",
|
||||
statusBarBackgroundColor:
|
||||
rawTheme[colorScheme as ThemeName]["primary"],
|
||||
},
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="index"
|
||||
options={{
|
||||
navigationBarColor:
|
||||
rawTheme[colorScheme as ThemeName]["base-100"],
|
||||
...Platform.select({
|
||||
android: {
|
||||
statusBarStyle:
|
||||
colorScheme === "light" ? "light" : "dark",
|
||||
statusBarBackgroundColor:
|
||||
rawTheme[colorScheme as ThemeName]["primary"],
|
||||
},
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="incoming"
|
||||
options={{
|
||||
navigationBarColor:
|
||||
rawTheme[colorScheme as ThemeName]["base-100"],
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen name="+not-found" />
|
||||
</Stack>
|
||||
)}
|
||||
</SheetProvider>
|
||||
</KeyboardProvider>
|
||||
</View>
|
||||
</PersistQueryClientProvider>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
View,
|
||||
ActivityIndicator,
|
||||
@@ -12,7 +12,6 @@ import NetInfo from "@react-native-community/netinfo";
|
||||
import useAuthStore from "@/store/auth";
|
||||
import { useLocalSearchParams, useRouter } from "expo-router";
|
||||
import { useUser } from "@linkwarden/router/user";
|
||||
import { generateLinkHref } from "@linkwarden/lib/generateLinkHref";
|
||||
import { useWindowDimensions } from "react-native";
|
||||
import RenderHtml from "@linkwarden/react-native-render-html";
|
||||
import ElementNotSupported from "@/components/ElementNotSupported";
|
||||
@@ -70,6 +69,7 @@ export default function LinkScreen() {
|
||||
}, [user, link]);
|
||||
|
||||
async function fetchLinkData() {
|
||||
// readable
|
||||
if (link?.id && format === "3") {
|
||||
const apiUrl = `${auth.instance}/api/v1/archives/${link.id}?format=${format}`;
|
||||
setUrl(apiUrl);
|
||||
@@ -87,11 +87,15 @@ export default function LinkScreen() {
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
} else if (link?.id && !format && user) {
|
||||
setUrl(
|
||||
generateLinkHref(link, { ...user, password: "" }, auth.instance, true)
|
||||
);
|
||||
} else if (link?.id && format) {
|
||||
}
|
||||
|
||||
// original
|
||||
else if (link?.id && !format && user && link.url) {
|
||||
setUrl(link.url);
|
||||
}
|
||||
|
||||
// other formats
|
||||
else if (link?.id && format) {
|
||||
setUrl(`${auth.instance}/api/v1/archives/${link.id}?format=${format}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,10 @@ import { useEffect, useState } from "react";
|
||||
import { View, Text, Dimensions, TouchableOpacity, Image } from "react-native";
|
||||
import { SheetManager } from "react-native-actions-sheet";
|
||||
import Svg, { Path } from "react-native-svg";
|
||||
import {
|
||||
KeyboardAwareScrollView,
|
||||
KeyboardToolbar,
|
||||
} from "react-native-keyboard-controller";
|
||||
|
||||
export default function HomeScreen() {
|
||||
const { auth, signIn } = useAuthStore();
|
||||
@@ -50,117 +54,134 @@ export default function HomeScreen() {
|
||||
}
|
||||
|
||||
return (
|
||||
<View className="flex-col justify-end h-full bg-primary relative">
|
||||
<View className="my-auto">
|
||||
<Image
|
||||
source={require("@/assets/images/linkwarden.png")}
|
||||
className="w-[120px] h-[120px] mx-auto"
|
||||
/>
|
||||
</View>
|
||||
<Text className="text-base-100 text-7xl font-bold ml-8">Login</Text>
|
||||
<View>
|
||||
<Text className="text-base-100 text-2xl mx-8 mt-3" numberOfLines={1}>
|
||||
Login to{" "}
|
||||
{form.instance === "https://cloud.linkwarden.app"
|
||||
? "cloud.linkwarden.app"
|
||||
: form.instance}
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
if (showInstanceField) {
|
||||
setForm({ ...form, instance: "https://cloud.linkwarden.app" });
|
||||
}
|
||||
setShowInstanceField(!showInstanceField);
|
||||
}}
|
||||
className="mx-8 mt-1 self-start"
|
||||
>
|
||||
<Text className="text-neutral-content text-sm">
|
||||
{!showInstanceField ? "Change server" : "Use official server"}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<Svg
|
||||
viewBox="0 0 1440 320"
|
||||
width={Dimensions.get("screen").width}
|
||||
height={100}
|
||||
<>
|
||||
<KeyboardAwareScrollView
|
||||
bottomOffset={62}
|
||||
contentContainerClassName="flex-col justify-end h-full bg-base-100 relative"
|
||||
>
|
||||
<Path
|
||||
fill={rawTheme[colorScheme as ThemeName]["base-100"]}
|
||||
fill-opacity="1"
|
||||
d="M0,256L48,234.7C96,213,192,171,288,176C384,181,480,235,576,266.7C672,299,768,309,864,277.3C960,245,1056,171,1152,122.7C1248,75,1344,53,1392,42.7L1440,32L1440,320L1392,320C1344,320,1248,320,1152,320C1056,320,960,320,864,320C768,320,672,320,576,320C480,320,384,320,288,320C192,320,96,320,48,320L0,320Z"
|
||||
/>
|
||||
</Svg>
|
||||
<View className="flex-col justify-end h-auto duration-100 pt-10 bg-base-100 -mt-2 pb-10 gap-4 w-full px-4">
|
||||
{showInstanceField && (
|
||||
<Input
|
||||
className="w-full text-xl p-3 leading-tight h-12"
|
||||
textAlignVertical="center"
|
||||
placeholder="Instance URL"
|
||||
selectTextOnFocus={false}
|
||||
value={form.instance}
|
||||
onChangeText={(text) => setForm({ ...form, instance: text })}
|
||||
/>
|
||||
)}
|
||||
{method === "password" ? (
|
||||
<>
|
||||
<Input
|
||||
className="w-full text-xl p-3 leading-tight h-12"
|
||||
textAlignVertical="center"
|
||||
placeholder="Email or Username"
|
||||
value={form.user}
|
||||
onChangeText={(text) => setForm({ ...form, user: text })}
|
||||
<View className="flex-col justify-end h-full bg-primary relative">
|
||||
<View className="my-auto">
|
||||
<Image
|
||||
source={require("@/assets/images/linkwarden.png")}
|
||||
className="w-[120px] h-[120px] mx-auto"
|
||||
/>
|
||||
<Input
|
||||
className="w-full text-xl p-3 leading-tight h-12"
|
||||
textAlignVertical="center"
|
||||
placeholder="Password"
|
||||
secureTextEntry
|
||||
value={form.password}
|
||||
onChangeText={(text) => setForm({ ...form, password: text })}
|
||||
</View>
|
||||
<Text className="text-base-100 text-7xl font-bold ml-8">Login</Text>
|
||||
<View>
|
||||
<Text
|
||||
className="text-base-100 text-2xl mx-8 mt-3"
|
||||
numberOfLines={1}
|
||||
>
|
||||
Login to{" "}
|
||||
{form.instance === "https://cloud.linkwarden.app"
|
||||
? "cloud.linkwarden.app"
|
||||
: form.instance}
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
if (showInstanceField) {
|
||||
setForm({
|
||||
...form,
|
||||
instance: "https://cloud.linkwarden.app",
|
||||
});
|
||||
}
|
||||
setShowInstanceField(!showInstanceField);
|
||||
}}
|
||||
className="mx-8 mt-1 self-start"
|
||||
>
|
||||
<Text className="text-neutral-content text-sm">
|
||||
{!showInstanceField ? "Change server" : "Use official server"}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<Svg
|
||||
viewBox="0 0 1440 320"
|
||||
width={Dimensions.get("screen").width}
|
||||
height={100}
|
||||
>
|
||||
<Path
|
||||
fill={rawTheme[colorScheme as ThemeName]["base-100"]}
|
||||
fill-opacity="1"
|
||||
d="M0,256L48,234.7C96,213,192,171,288,176C384,181,480,235,576,266.7C672,299,768,309,864,277.3C960,245,1056,171,1152,122.7C1248,75,1344,53,1392,42.7L1440,32L1440,320L1392,320C1344,320,1248,320,1152,320C1056,320,960,320,864,320C768,320,672,320,576,320C480,320,384,320,288,320C192,320,96,320,48,320L0,320Z"
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Input
|
||||
className="w-full text-xl p-3 leading-tight h-12"
|
||||
textAlignVertical="center"
|
||||
placeholder="Access Token"
|
||||
secureTextEntry
|
||||
value={form.token}
|
||||
onChangeText={(text) => setForm({ ...form, token: text })}
|
||||
/>
|
||||
)}
|
||||
</Svg>
|
||||
<View className="flex-col justify-end h-auto duration-100 pt-10 bg-base-100 -mt-2 pb-10 gap-4 w-full px-4">
|
||||
{showInstanceField && (
|
||||
<Input
|
||||
className="w-full text-xl p-3 leading-tight h-12"
|
||||
textAlignVertical="center"
|
||||
placeholder="Instance URL"
|
||||
selectTextOnFocus={false}
|
||||
value={form.instance}
|
||||
onChangeText={(text) => setForm({ ...form, instance: text })}
|
||||
/>
|
||||
)}
|
||||
{method === "password" ? (
|
||||
<>
|
||||
<Input
|
||||
className="w-full text-xl p-3 leading-tight h-12"
|
||||
textAlignVertical="center"
|
||||
placeholder="Email or Username"
|
||||
value={form.user}
|
||||
onChangeText={(text) => setForm({ ...form, user: text })}
|
||||
/>
|
||||
<Input
|
||||
className="w-full text-xl p-3 leading-tight h-12"
|
||||
textAlignVertical="center"
|
||||
placeholder="Password"
|
||||
secureTextEntry
|
||||
value={form.password}
|
||||
onChangeText={(text) => setForm({ ...form, password: text })}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Input
|
||||
className="w-full text-xl p-3 leading-tight h-12"
|
||||
textAlignVertical="center"
|
||||
placeholder="Access Token"
|
||||
secureTextEntry
|
||||
value={form.token}
|
||||
onChangeText={(text) => setForm({ ...form, token: text })}
|
||||
/>
|
||||
)}
|
||||
|
||||
<TouchableOpacity
|
||||
onPress={() =>
|
||||
setMethod(method === "password" ? "token" : "password")
|
||||
}
|
||||
className="w-fit mx-auto"
|
||||
>
|
||||
<Text className="text-primary w-fit text-center">
|
||||
{method === "password"
|
||||
? "Login with Access Token"
|
||||
: "Login with Username/Password"}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={() =>
|
||||
setMethod(method === "password" ? "token" : "password")
|
||||
}
|
||||
className="w-fit mx-auto"
|
||||
>
|
||||
<Text className="text-primary w-fit text-center">
|
||||
{method === "password"
|
||||
? "Login with Access Token"
|
||||
: "Login with Username/Password"}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<Button
|
||||
variant="accent"
|
||||
size="lg"
|
||||
onPress={() => {
|
||||
if (((form.user && form.password) || form.token) && form.instance) {
|
||||
signIn(form.user, form.password, form.instance, form.token);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Text className="text-white text-xl">Login</Text>
|
||||
</Button>
|
||||
<TouchableOpacity
|
||||
className="w-fit mx-auto"
|
||||
onPress={() => SheetManager.show("support-sheet")}
|
||||
>
|
||||
<Text className="text-neutral text-center w-fit">Need help?</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
<Button
|
||||
variant="accent"
|
||||
size="lg"
|
||||
onPress={() => {
|
||||
if (
|
||||
((form.user && form.password) || form.token) &&
|
||||
form.instance
|
||||
) {
|
||||
signIn(form.user, form.password, form.instance, form.token);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Text className="text-white text-xl">Login</Text>
|
||||
</Button>
|
||||
<TouchableOpacity
|
||||
className="w-fit mx-auto"
|
||||
onPress={() => SheetManager.show("support-sheet")}
|
||||
>
|
||||
<Text className="text-neutral text-center w-fit">Need help?</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</KeyboardAwareScrollView>
|
||||
<KeyboardToolbar />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
30
apps/mobile/eas.json
Normal file
30
apps/mobile/eas.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"cli": {
|
||||
"version": ">= 16.20.1",
|
||||
"appVersionSource": "remote"
|
||||
},
|
||||
"build": {
|
||||
"development": {
|
||||
"developmentClient": true,
|
||||
"distribution": "internal"
|
||||
},
|
||||
"simulator": {
|
||||
"extends": "development",
|
||||
"ios": {
|
||||
"simulator": true
|
||||
}
|
||||
},
|
||||
"production": {
|
||||
"distribution": "store",
|
||||
"autoIncrement": true,
|
||||
"channel": "production"
|
||||
}
|
||||
},
|
||||
"submit": {
|
||||
"production": {
|
||||
"ios": {
|
||||
"ascAppId": "6752550960"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
"dependencies": {
|
||||
"@expo/vector-icons": "^14.0.2",
|
||||
"@linkwarden/lib": "*",
|
||||
"@linkwarden/prisma": "*",
|
||||
"@linkwarden/react-native-render-html": "^6.3.4",
|
||||
"@linkwarden/router": "*",
|
||||
"@linkwarden/types": "*",
|
||||
@@ -30,6 +31,7 @@
|
||||
"expo": "~52.0.18",
|
||||
"expo-application": "~6.0.2",
|
||||
"expo-blur": "~14.0.1",
|
||||
"expo-build-properties": "~0.13.3",
|
||||
"expo-clipboard": "~7.0.1",
|
||||
"expo-constants": "~17.0.3",
|
||||
"expo-dev-client": "~5.0.6",
|
||||
@@ -44,6 +46,7 @@
|
||||
"expo-status-bar": "~2.0.0",
|
||||
"expo-symbols": "~0.2.0",
|
||||
"expo-system-ui": "~4.0.6",
|
||||
"expo-updates": "~0.27.4",
|
||||
"expo-web-browser": "~14.0.1",
|
||||
"html-entities": "^2.6.0",
|
||||
"lucide-react-native": "^0.536.0",
|
||||
@@ -53,8 +56,9 @@
|
||||
"react-native": "0.76.9",
|
||||
"react-native-actions-sheet": "^0.9.7",
|
||||
"react-native-gesture-handler": "~2.20.2",
|
||||
"react-native-ios-context-menu": "3.1.0",
|
||||
"react-native-ios-utilities": "5.1.2",
|
||||
"react-native-ios-context-menu": "3.1.3",
|
||||
"react-native-ios-utilities": "5.1.7",
|
||||
"react-native-keyboard-controller": "^1.19.0",
|
||||
"react-native-mmkv": "^3.2.0",
|
||||
"react-native-reanimated": "3.16.2",
|
||||
"react-native-safe-area-context": "4.12.0",
|
||||
|
||||
@@ -24,10 +24,17 @@ import {
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { useUser } from "@linkwarden/router/user";
|
||||
import Link from "next/link";
|
||||
|
||||
const STRIPE_ENABLED = process.env.NEXT_PUBLIC_STRIPE === "true";
|
||||
const TRIAL_PERIOD_DAYS =
|
||||
Number(process.env.NEXT_PUBLIC_TRIAL_PERIOD_DAYS) || 14;
|
||||
|
||||
export default function Navbar() {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const { data: user } = useUser();
|
||||
|
||||
const [sidebar, setSidebar] = useState(false);
|
||||
|
||||
@@ -50,87 +57,126 @@ export default function Navbar() {
|
||||
const [newCollectionModal, setNewCollectionModal] = useState(false);
|
||||
const [uploadFileModal, setUploadFileModal] = useState(false);
|
||||
|
||||
const [daysLeft, setDaysLeft] = useState<number>(0);
|
||||
const [isTrialing, setIsTrialing] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (user?.createdAt) {
|
||||
const trialEndTime =
|
||||
new Date(user.createdAt).getTime() +
|
||||
(1 + Number(TRIAL_PERIOD_DAYS)) * 86400000; // Add 1 to account for the current day
|
||||
|
||||
setDaysLeft(Math.floor((trialEndTime - Date.now()) / 86400000));
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
useEffect(() => {
|
||||
const isTrialing =
|
||||
user?.id &&
|
||||
!user?.subscription?.active &&
|
||||
!user.parentSubscription?.active;
|
||||
|
||||
setIsTrialing(Boolean(isTrialing));
|
||||
}, [user, daysLeft]);
|
||||
|
||||
return (
|
||||
<div className="flex justify-between gap-2 items-center pl-3 pr-4 py-2 border-solid border-b-neutral-content border-b">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="text-neutral lg:hidden sm:inline-flex"
|
||||
onClick={() => {
|
||||
setSidebar(true);
|
||||
document.body.style.overflow = "hidden";
|
||||
}}
|
||||
>
|
||||
<i className="bi-list text-xl leading-none" />
|
||||
</Button>
|
||||
<>
|
||||
{STRIPE_ENABLED && isTrialing && (
|
||||
<Link
|
||||
href="/subscribe"
|
||||
className="w-full text-sm cursor-pointer select-none bg-base-200"
|
||||
>
|
||||
<p className="w-full text-center flex items-center justify-center gap-1 underline decoration-dotted underline-offset-4 hover:opacity-70 duration-200 py-1 px-2">
|
||||
<i className="bi-clock text-primary" />
|
||||
{daysLeft === 1
|
||||
? t("trial_left_singular")
|
||||
: t("trial_left_plural", { count: daysLeft })}
|
||||
</p>
|
||||
</Link>
|
||||
)}
|
||||
<div className="flex justify-between gap-2 items-center pl-3 pr-4 py-2 border-solid border-b-neutral-content border-b">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="text-neutral lg:hidden sm:inline-flex"
|
||||
onClick={() => {
|
||||
setSidebar(true);
|
||||
document.body.style.overflow = "hidden";
|
||||
}}
|
||||
>
|
||||
<i className="bi-list text-xl leading-none" />
|
||||
</Button>
|
||||
|
||||
<SearchBar />
|
||||
<SearchBar />
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<ToggleDarkMode hideInMobile />
|
||||
<div className="flex items-center gap-2">
|
||||
<ToggleDarkMode hideInMobile />
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger className="hidden sm:inline-grid">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<Button
|
||||
variant="accent"
|
||||
size="sm"
|
||||
className="min-w-[3.4rem] h-[2rem] relative"
|
||||
>
|
||||
<span>
|
||||
<i className="bi-plus text-4xl absolute -top-[0.3rem] left-0 pointer-events-none" />
|
||||
</span>
|
||||
<span>
|
||||
<i className="bi-caret-down-fill text-xs absolute top-[0.6rem] right-[0.4rem] pointer-events-none" />
|
||||
</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{t("create_new")}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger className="hidden sm:inline-grid">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<Button
|
||||
variant="accent"
|
||||
size="sm"
|
||||
className="min-w-[3.4rem] h-[2rem] relative"
|
||||
>
|
||||
<span>
|
||||
<i className="bi-plus text-4xl absolute -top-[0.3rem] left-0 pointer-events-none" />
|
||||
</span>
|
||||
<span>
|
||||
<i className="bi-caret-down-fill text-xs absolute top-[0.6rem] right-[0.4rem] pointer-events-none" />
|
||||
</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{t("create_new")}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onSelect={() => setNewLinkModal(true)}>
|
||||
<i className="bi-link-45deg" />
|
||||
{t("new_link")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onSelect={() => setUploadFileModal(true)}>
|
||||
<i className="bi-file-earmark-arrow-up" />
|
||||
{t("upload_file")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onSelect={() => setNewCollectionModal(true)}>
|
||||
<i className="bi-folder" />
|
||||
{t("new_collection")}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onSelect={() => setNewLinkModal(true)}>
|
||||
<i className="bi-link-45deg" />
|
||||
{t("new_link")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onSelect={() => setUploadFileModal(true)}>
|
||||
<i className="bi-file-earmark-arrow-up" />
|
||||
{t("upload_file")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onSelect={() => setNewCollectionModal(true)}>
|
||||
<i className="bi-folder" />
|
||||
{t("new_collection")}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
<ProfileDropdown />
|
||||
</div>
|
||||
|
||||
<MobileNavigation />
|
||||
|
||||
{sidebar && (
|
||||
<div className="fixed top-0 bottom-0 right-0 left-0 bg-black bg-opacity-10 backdrop-blur-sm flex items-center fade-in z-40">
|
||||
<ClickAwayHandler className="h-full" onClickOutside={toggleSidebar}>
|
||||
<div className="slide-right h-full shadow-lg">
|
||||
<Sidebar />
|
||||
</div>
|
||||
</ClickAwayHandler>
|
||||
<ProfileDropdown />
|
||||
</div>
|
||||
)}
|
||||
{newLinkModal && <NewLinkModal onClose={() => setNewLinkModal(false)} />}
|
||||
{newCollectionModal && (
|
||||
<NewCollectionModal onClose={() => setNewCollectionModal(false)} />
|
||||
)}
|
||||
{uploadFileModal && (
|
||||
<UploadFileModal onClose={() => setUploadFileModal(false)} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<MobileNavigation />
|
||||
|
||||
{sidebar && (
|
||||
<div className="fixed top-0 bottom-0 right-0 left-0 bg-black bg-opacity-10 backdrop-blur-sm flex items-center fade-in z-40">
|
||||
<ClickAwayHandler className="h-full" onClickOutside={toggleSidebar}>
|
||||
<div className="slide-right h-full shadow-lg">
|
||||
<Sidebar />
|
||||
</div>
|
||||
</ClickAwayHandler>
|
||||
</div>
|
||||
)}
|
||||
{newLinkModal && (
|
||||
<NewLinkModal onClose={() => setNewLinkModal(false)} />
|
||||
)}
|
||||
{newCollectionModal && (
|
||||
<NewCollectionModal onClose={() => setNewCollectionModal(false)} />
|
||||
)}
|
||||
{uploadFileModal && (
|
||||
<UploadFileModal onClose={() => setUploadFileModal(false)} />
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { randomBytes } from "crypto";
|
||||
import { prisma } from "@linkwarden/prisma";
|
||||
import transporter from "./transporter";
|
||||
import transporter from "@linkwarden/lib/transporter";
|
||||
import Handlebars from "handlebars";
|
||||
import { readFileSync } from "fs";
|
||||
import path from "path";
|
||||
@@ -51,7 +51,7 @@ export default async function sendChangeEmailVerificationRequest(
|
||||
baseUrl: process.env.BASE_URL,
|
||||
oldEmail,
|
||||
newEmail,
|
||||
verifyUrl: `${process.env.BASE_URL}/auth/verify-email?token=${token}`,
|
||||
url: `${process.env.BASE_URL}/auth/verify-email?token=${token}`,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { readFileSync } from "fs";
|
||||
import path from "path";
|
||||
import Handlebars from "handlebars";
|
||||
import transporter from "./transporter";
|
||||
import transporter from "@linkwarden/lib/transporter";
|
||||
|
||||
type Params = {
|
||||
parentSubscriptionEmail: string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { randomBytes } from "crypto";
|
||||
import { prisma } from "@linkwarden/prisma";
|
||||
import transporter from "./transporter";
|
||||
import transporter from "@linkwarden/lib/transporter";
|
||||
import Handlebars from "handlebars";
|
||||
import { readFileSync } from "fs";
|
||||
import path from "path";
|
||||
@@ -37,7 +37,6 @@ export default async function sendPasswordResetRequest(
|
||||
subject: "Linkwarden: Reset password instructions",
|
||||
html: emailTemplate({
|
||||
user,
|
||||
baseUrl: process.env.BASE_URL,
|
||||
url: `${process.env.BASE_URL}/auth/reset-password?token=${token}`,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { readFileSync } from "fs";
|
||||
import path from "path";
|
||||
import Handlebars from "handlebars";
|
||||
import transporter from "./transporter";
|
||||
import transporter from "@linkwarden/lib/transporter";
|
||||
|
||||
type Params = {
|
||||
identifier: string;
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
import fetch from "node-fetch";
|
||||
import https from "https";
|
||||
import http from "http";
|
||||
import { HttpsProxyAgent } from "https-proxy-agent";
|
||||
import { SocksProxyAgent } from "socks-proxy-agent";
|
||||
|
||||
export default async function fetchTitleAndHeaders(url: string) {
|
||||
export default async function fetchTitleAndHeaders(
|
||||
url: string,
|
||||
content?: string
|
||||
) {
|
||||
if (!url?.startsWith("http://") && !url?.startsWith("https://"))
|
||||
return { title: "", headers: null };
|
||||
|
||||
try {
|
||||
const httpsAgent = new https.Agent({
|
||||
rejectUnauthorized:
|
||||
process.env.IGNORE_UNAUTHORIZED_CA === "true" ? false : true,
|
||||
});
|
||||
const httpsAgent = url?.startsWith("http://")
|
||||
? new http.Agent({})
|
||||
: new https.Agent({
|
||||
rejectUnauthorized:
|
||||
process.env.IGNORE_UNAUTHORIZED_CA === "true" ? false : true,
|
||||
});
|
||||
|
||||
// fetchOpts allows a proxy to be defined
|
||||
let fetchOpts = {
|
||||
@@ -45,13 +51,20 @@ export default async function fetchTitleAndHeaders(url: string) {
|
||||
const response = await Promise.race([responsePromise, timeoutPromise]);
|
||||
|
||||
if ((response as any)?.status) {
|
||||
const text = await (response as any).text();
|
||||
let text: string;
|
||||
|
||||
if (content) {
|
||||
text = content;
|
||||
} else {
|
||||
text = await (response as any).text();
|
||||
}
|
||||
|
||||
const headers = (response as Response)?.headers || null;
|
||||
|
||||
// regular expression to find the <title> tag
|
||||
let match = text.match(/<title.*>([^<]*)<\/title>/);
|
||||
|
||||
const title = match?.[1] || "";
|
||||
const headers = (response as Response)?.headers || null;
|
||||
|
||||
return { title, headers };
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@linkwarden/web",
|
||||
"version": "v2.13.0",
|
||||
"version": "v2.13.1",
|
||||
"main": "index.js",
|
||||
"repository": "https://github.com/linkwarden/linkwarden.git",
|
||||
"author": "Daniel31X13 <daniel31x13@gmail.com>",
|
||||
@@ -42,7 +42,6 @@
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/formidable": "^3.4.5",
|
||||
"@types/node": "^20.10.4",
|
||||
"@types/nodemailer": "^6.4.8",
|
||||
"@types/papaparse": "^5.3.16",
|
||||
"@types/react": "18.3.20",
|
||||
"@types/react-dom": "18.3.7",
|
||||
@@ -72,7 +71,6 @@
|
||||
"next-auth": "^4.22.1",
|
||||
"next-i18next": "^15.3.0",
|
||||
"node-fetch": "^2.7.0",
|
||||
"nodemailer": "^6.9.3",
|
||||
"papaparse": "^5.5.3",
|
||||
"playwright": "^1.55.0",
|
||||
"react": "18.3.1",
|
||||
|
||||
@@ -146,7 +146,19 @@ async function handlePost(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (!collection) {
|
||||
throw new Error("Collection not found.");
|
||||
}
|
||||
const { title = "" } = url ? await fetchTitleAndHeaders(url) : {};
|
||||
|
||||
// Generate a preview if it's an image
|
||||
const { mimetype } = files.file[0];
|
||||
const isPDF = mimetype?.includes("pdf");
|
||||
const isImage = mimetype?.includes("image");
|
||||
const isHTML = mimetype === "text/html";
|
||||
|
||||
const { title = "" } = url
|
||||
? await fetchTitleAndHeaders(
|
||||
url,
|
||||
isHTML && !isPreview ? fileBuffer.toString("utf-8") : undefined
|
||||
)
|
||||
: {};
|
||||
|
||||
const link = await prisma.link.create({
|
||||
data: {
|
||||
@@ -162,15 +174,14 @@ async function handlePost(req: NextApiRequest, res: NextApiResponse) {
|
||||
},
|
||||
},
|
||||
url,
|
||||
|
||||
// temporarily prevent archiveHandler and other processes from overwriting the file while we're uploading it
|
||||
lastPreserved: new Date(0).toISOString(),
|
||||
aiTagged: true,
|
||||
indexVersion: 1,
|
||||
},
|
||||
});
|
||||
|
||||
// Generate a preview if it's an image
|
||||
const { mimetype } = files.file[0];
|
||||
const isPDF = mimetype?.includes("pdf");
|
||||
const isImage = mimetype?.includes("image");
|
||||
const isHTML = mimetype === "text/html";
|
||||
|
||||
if (isImage) {
|
||||
const collectionId = collection.id;
|
||||
createFolder({ filePath: `archives/preview/${collectionId}` });
|
||||
@@ -203,6 +214,10 @@ async function handlePost(req: NextApiRequest, res: NextApiResponse) {
|
||||
: undefined,
|
||||
clientSide: true,
|
||||
updatedAt: new Date().toISOString(),
|
||||
|
||||
lastPreserved: null,
|
||||
aiTagged: false,
|
||||
indexVersion: null,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1068,6 +1068,35 @@ if (process.env.NEXT_PUBLIC_STRAVA_ENABLED === "true") {
|
||||
};
|
||||
}
|
||||
|
||||
// Synology
|
||||
if (process.env.NEXT_PUBLIC_SYNOLOGY_ENABLED === "true") {
|
||||
providers.push({
|
||||
id: "synology",
|
||||
name: "Synology",
|
||||
type: "oauth",
|
||||
clientId: process.env.SYNOLOGY_CLIENT_ID!,
|
||||
clientSecret: process.env.SYNOLOGY_CLIENT_SECRET!,
|
||||
wellKnown: process.env.SYNOLOGY_WELLKNOWN_URL!,
|
||||
authorization: { params: { scope: "openid email profile" } },
|
||||
idToken: true,
|
||||
checks: ["pkce", "state"],
|
||||
profile(profile) {
|
||||
return {
|
||||
id: profile.sub,
|
||||
name: profile.name,
|
||||
email: profile.email,
|
||||
username: profile.preferred_username,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const _linkAccount = adapter.linkAccount;
|
||||
adapter.linkAccount = (account) => {
|
||||
const { "not-before-policy": _, refresh_expires_in, ...data } = account;
|
||||
return _linkAccount ? _linkAccount(data) : undefined;
|
||||
};
|
||||
}
|
||||
|
||||
// Todoist
|
||||
if (process.env.NEXT_PUBLIC_TODOIST_ENABLED === "true") {
|
||||
providers.push(
|
||||
|
||||
@@ -11,7 +11,8 @@ export const getEnvData = () => {
|
||||
process.env.OPENAI_API_KEY ||
|
||||
process.env.AZURE_API_KEY ||
|
||||
process.env.ANTHROPIC_API_KEY ||
|
||||
process.env.OPENROUTER_API_KEY
|
||||
process.env.OPENROUTER_API_KEY ||
|
||||
process.env.PERPLEXITY_API_KEY
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -335,6 +335,13 @@ export function getLogins() {
|
||||
name: process.env.STRAVA_CUSTOM_NAME ?? "Strava",
|
||||
});
|
||||
}
|
||||
// Synology
|
||||
if (process.env.NEXT_PUBLIC_SYNOLOGY_ENABLED === "true") {
|
||||
buttonAuths.push({
|
||||
method: "synology",
|
||||
name: process.env.SYNOLOGY_CUSTOM_NAME ?? "Synology",
|
||||
});
|
||||
}
|
||||
// Todoist
|
||||
if (process.env.NEXT_PUBLIC_TODOIST_ENABLED === "true") {
|
||||
buttonAuths.push({
|
||||
|
||||
@@ -163,6 +163,7 @@ export default function Login({
|
||||
</p>
|
||||
|
||||
<TextInput
|
||||
name="username"
|
||||
autoFocus={true}
|
||||
placeholder="johnny"
|
||||
value={form.username}
|
||||
|
||||
@@ -9,7 +9,6 @@ import getServerSideProps from "@/lib/client/getServerSideProps";
|
||||
import { Trans, useTranslation } from "next-i18next";
|
||||
import { useUser } from "@linkwarden/router/user";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import Link from "next/link";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const TRIAL_PERIOD_DAYS =
|
||||
@@ -122,32 +121,38 @@ export default function Subscribe() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2 justify-center items-center">
|
||||
<div className="flex flex-col gap-2 justify-center items-center min-h-36">
|
||||
<p className="text-3xl">
|
||||
${plan === Plan.monthly ? "4" : "3"}
|
||||
<span className="text-base text-neutral">/mo</span>
|
||||
</p>
|
||||
|
||||
<p className="font-semibold">
|
||||
{plan === Plan.monthly ? t("billed_monthly") : t("billed_yearly")}
|
||||
</p>
|
||||
<fieldset className="w-full flex-col flex justify-evenly px-4 pb-4 pt-1 rounded-md border border-neutral-content">
|
||||
<legend className="w-fit font-extralight px-2 border border-neutral-content rounded-md text-xl">
|
||||
{t("total")}
|
||||
</legend>
|
||||
|
||||
<p className="text-sm">
|
||||
{plan === Plan.monthly
|
||||
? t("total_monthly_desc", {
|
||||
count: REQUIRE_CC ? 14 : daysLeft,
|
||||
monthlyPrice: "4",
|
||||
})
|
||||
: t("total_annual_desc", {
|
||||
count: REQUIRE_CC ? 14 : daysLeft,
|
||||
annualPrice: "36",
|
||||
})}
|
||||
</p>
|
||||
<p className="text-sm">{t("plus_tax")}</p>
|
||||
</fieldset>
|
||||
{daysLeft > 0 ? (
|
||||
<fieldset className="w-full max-h-fit flex-col flex gap-2 px-4 pb-4 pt-2 rounded-md border border-neutral-content">
|
||||
<legend className="w-fit font-extralight px-2 border border-neutral-content rounded-md text-xl">
|
||||
{t("total")}
|
||||
</legend>
|
||||
|
||||
<p className="text-sm">
|
||||
{plan === Plan.monthly
|
||||
? t("total_monthly_desc", {
|
||||
count: REQUIRE_CC ? 14 : daysLeft,
|
||||
monthlyPrice: "4",
|
||||
})
|
||||
: t("total_annual_desc", {
|
||||
count: REQUIRE_CC ? 14 : daysLeft,
|
||||
annualPrice: "36",
|
||||
})}
|
||||
</p>
|
||||
<p className="text-sm">{t("plus_tax")}</p>
|
||||
</fieldset>
|
||||
) : (
|
||||
<p className="text-xs">{t("plus_tax")}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
||||
@@ -513,5 +513,17 @@
|
||||
"rename_tag_instruction": "Please provide a name for the final tag.",
|
||||
"merging": "Merging...",
|
||||
"delete_tags": "Delete {{count}} Tags",
|
||||
"tags_deletion_confirmation_message": "Are you sure you want to delete {{count}} Tags? This will remove the tags from all links."
|
||||
"tags_deletion_confirmation_message": "Are you sure you want to delete {{count}} Tags? This will remove the tags from all links.",
|
||||
"subscribe_later": "Subscribe Later?",
|
||||
"create_your_first_tag": "Create Your First Tag!",
|
||||
"create_your_first_tag_desc": "Tags help you categorize and find your Links easily. You can create Tags based on topics, projects, or any system that works for you.",
|
||||
"create_your_first_collection": "Create Your First Collection!",
|
||||
"create_your_first_collection_desc": "Collections are like folders for your Links which can then be shared with others.",
|
||||
"this_tag_has_no_links": "This Tag Has No Links",
|
||||
"this_tag_has_no_links_desc": "Use this Tag while creating or editing Links!",
|
||||
"accept_promotional_emails": "Get notified about new features and offers via email.",
|
||||
"expand_sidebar": "Expand Sidebar",
|
||||
"shrink_sidebar": "Shrink Sidebar",
|
||||
"trial_left_plural": "Trial ends in {{count}} days. Subscribe.",
|
||||
"trial_left_singular": "Trial ends in 1 day. Subscribe."
|
||||
}
|
||||
|
||||
@@ -523,5 +523,7 @@
|
||||
"this_tag_has_no_links_desc": "Use this Tag while creating or editing Links!",
|
||||
"accept_promotional_emails": "Get notified about new features and offers via email.",
|
||||
"expand_sidebar": "Expand Sidebar",
|
||||
"shrink_sidebar": "Shrink Sidebar"
|
||||
"shrink_sidebar": "Shrink Sidebar",
|
||||
"trial_left_plural": "Trial ends in {{count}} days. Subscribe.",
|
||||
"trial_left_singular": "Trial ends in 1 day. Subscribe."
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@
|
||||
"password_successfully_updated": "Tu contraseña ha sido actualizada correctamente.",
|
||||
"user_already_member": "El usuario ya existe.",
|
||||
"you_are_already_collection_owner": "Ya eres el dueño de esta colección.",
|
||||
"link_already_in_collection": "This link is already in this collection.",
|
||||
"link_already_in_collection": "Este enlace ya se encuentra en esta colección.",
|
||||
"date_newest_first": "Fecha (Nuevos primero)",
|
||||
"date_oldest_first": "Fecha (Antiguos primero)",
|
||||
"name_az": "Nombre (A-Z)",
|
||||
@@ -491,27 +491,39 @@
|
||||
"no_notes_highlights": "No se han encontrado notas ni destacados para este enlace.",
|
||||
"save": "Guardar",
|
||||
"edit_layout": "Editar Disposición",
|
||||
"refresh_multiple_preserved_formats_confirmation_desc": "This will delete the current preserved formats and re-preserve {{count}} links.",
|
||||
"refresh_preserved_formats_confirmation_desc": "This will delete the current preserved formats and re-preserve this link.",
|
||||
"refresh_multiple_preserved_formats_confirmation_desc": "Esto eliminará los formatos conservados actuales y volverá a preservar los {{count}} enlaces.",
|
||||
"refresh_preserved_formats_confirmation_desc": "Esto eliminará los formatos conservados actuales y reconservará este enlace.",
|
||||
"tag_already_added": "Esta etiqueta ya ha sido añadida.",
|
||||
"all_tags": "All Tags",
|
||||
"new_tag": "New Tag",
|
||||
"create_new_tag": "Create New Tag",
|
||||
"tag_deletion_confirmation_message": "Are you sure you want to delete this Tag?",
|
||||
"delete_tags_by_number_of_links": "Delete Tags with <0/> Links",
|
||||
"delete_all_tags": "Delete all Tags",
|
||||
"bulk_delete_tags": "Bulk Delete Tags",
|
||||
"count_tags_deleted": "{{count}} Tags Deleted",
|
||||
"count_tag_deleted": "{{count}} Tag Deleted",
|
||||
"tag_name_placeholder": "e.g. Technology",
|
||||
"link_count_high_low": "Link count (high to low)",
|
||||
"link_count_low_high": "Link count (low to high)",
|
||||
"tags_selected": "{{count}} Tags selected",
|
||||
"tag_selected": "1 Tag selected",
|
||||
"merge_tags": "Merge Tags",
|
||||
"merge_count_tags": "Merge {{count}} Tags",
|
||||
"rename_tag_instruction": "Please provide a name for the final tag.",
|
||||
"merging": "Merging...",
|
||||
"delete_tags": "Delete {{count}} Tags",
|
||||
"tags_deletion_confirmation_message": "Are you sure you want to delete {{count}} Tags? This will remove the tags from all links."
|
||||
"all_tags": "Todas las etiquetas",
|
||||
"new_tag": "Nueva Etiqueta",
|
||||
"create_new_tag": "Crear nueva etiqueta",
|
||||
"tag_deletion_confirmation_message": "¿Está seguro que desea eliminar esta etiqueta?",
|
||||
"delete_tags_by_number_of_links": "Eliminar etiquetas con <0/> enlaces",
|
||||
"delete_all_tags": "Eliminar todas las etiquetas",
|
||||
"bulk_delete_tags": "Eliminar etiquetas en masa",
|
||||
"count_tags_deleted": "{{count}} etiquetas eliminadas",
|
||||
"count_tag_deleted": "{{count}} etiqueta eliminada",
|
||||
"tag_name_placeholder": "ej. Tecnología",
|
||||
"link_count_high_low": "Contador de enlaces (alto a bajo)",
|
||||
"link_count_low_high": "Contador de enlaces (bajo a alto)",
|
||||
"tags_selected": "{{count}} etiquetas seleccionadas",
|
||||
"tag_selected": "1 etiqueta seleccionada",
|
||||
"merge_tags": "Fusionar etiquetas",
|
||||
"merge_count_tags": "Fusionar {{count}} etiquetas",
|
||||
"rename_tag_instruction": "Por favor, proporcione un nombre para la etiqueta final.",
|
||||
"merging": "Fusionando...",
|
||||
"delete_tags": "Eliminar {{count}} etiquetas",
|
||||
"tags_deletion_confirmation_message": "¿Está seguro de que desea eliminar {{count}} etiquetas? Esto eliminará las etiquetas de todos los enlaces.",
|
||||
"subscribe_later": "¿Suscribirse más tarde?",
|
||||
"create_your_first_tag": "¡Crea tu primera etiqueta!",
|
||||
"create_your_first_tag_desc": "Las etiquetas le ayudan a clasificar y encontrar sus enlaces fácilmente. Puede crear etiquetas basadas en temas, proyectos o cualquier sistema que funcione para usted.",
|
||||
"create_your_first_collection": "¡Crea tu primera colección!",
|
||||
"create_your_first_collection_desc": "Las colecciones son como carpetas para sus enlaces que pueden ser compartidas con otros.",
|
||||
"this_tag_has_no_links": "Esta etiqueta no tiene enlaces",
|
||||
"this_tag_has_no_links_desc": "¡Usa esta etiqueta mientras crea o edita enlaces!",
|
||||
"accept_promotional_emails": "Recibe notificaciones sobre nuevas características y ofertas por correo electrónico.",
|
||||
"expand_sidebar": "Expandir barra lateral",
|
||||
"shrink_sidebar": "Contraer barra lateral",
|
||||
"trial_left_plural": "La prueba termina en {{count}} días. Suscríbete.",
|
||||
"trial_left_singular": "La prueba termina en 1 día. Suscríbete."
|
||||
}
|
||||
|
||||
@@ -513,5 +513,17 @@
|
||||
"rename_tag_instruction": "Please provide a name for the final tag.",
|
||||
"merging": "Merging...",
|
||||
"delete_tags": "Delete {{count}} Tags",
|
||||
"tags_deletion_confirmation_message": "Are you sure you want to delete {{count}} Tags? This will remove the tags from all links."
|
||||
"tags_deletion_confirmation_message": "Are you sure you want to delete {{count}} Tags? This will remove the tags from all links.",
|
||||
"subscribe_later": "Subscribe Later?",
|
||||
"create_your_first_tag": "Create Your First Tag!",
|
||||
"create_your_first_tag_desc": "Tags help you categorize and find your Links easily. You can create Tags based on topics, projects, or any system that works for you.",
|
||||
"create_your_first_collection": "Create Your First Collection!",
|
||||
"create_your_first_collection_desc": "Collections are like folders for your Links which can then be shared with others.",
|
||||
"this_tag_has_no_links": "This Tag Has No Links",
|
||||
"this_tag_has_no_links_desc": "Use this Tag while creating or editing Links!",
|
||||
"accept_promotional_emails": "Get notified about new features and offers via email.",
|
||||
"expand_sidebar": "Expand Sidebar",
|
||||
"shrink_sidebar": "Shrink Sidebar",
|
||||
"trial_left_plural": "Trial ends in {{count}} days. Subscribe.",
|
||||
"trial_left_singular": "Trial ends in 1 day. Subscribe."
|
||||
}
|
||||
|
||||
@@ -513,5 +513,17 @@
|
||||
"rename_tag_instruction": "Please provide a name for the final tag.",
|
||||
"merging": "Merging...",
|
||||
"delete_tags": "Delete {{count}} Tags",
|
||||
"tags_deletion_confirmation_message": "Are you sure you want to delete {{count}} Tags? This will remove the tags from all links."
|
||||
"tags_deletion_confirmation_message": "Are you sure you want to delete {{count}} Tags? This will remove the tags from all links.",
|
||||
"subscribe_later": "Subscribe Later?",
|
||||
"create_your_first_tag": "Create Your First Tag!",
|
||||
"create_your_first_tag_desc": "Tags help you categorize and find your Links easily. You can create Tags based on topics, projects, or any system that works for you.",
|
||||
"create_your_first_collection": "Create Your First Collection!",
|
||||
"create_your_first_collection_desc": "Collections are like folders for your Links which can then be shared with others.",
|
||||
"this_tag_has_no_links": "This Tag Has No Links",
|
||||
"this_tag_has_no_links_desc": "Use this Tag while creating or editing Links!",
|
||||
"accept_promotional_emails": "Get notified about new features and offers via email.",
|
||||
"expand_sidebar": "Expand Sidebar",
|
||||
"shrink_sidebar": "Shrink Sidebar",
|
||||
"trial_left_plural": "Trial ends in {{count}} days. Subscribe.",
|
||||
"trial_left_singular": "Trial ends in 1 day. Subscribe."
|
||||
}
|
||||
|
||||
@@ -513,5 +513,17 @@
|
||||
"rename_tag_instruction": "Please provide a name for the final tag.",
|
||||
"merging": "Merging...",
|
||||
"delete_tags": "Delete {{count}} Tags",
|
||||
"tags_deletion_confirmation_message": "Are you sure you want to delete {{count}} Tags? This will remove the tags from all links."
|
||||
"tags_deletion_confirmation_message": "Are you sure you want to delete {{count}} Tags? This will remove the tags from all links.",
|
||||
"subscribe_later": "Subscribe Later?",
|
||||
"create_your_first_tag": "Create Your First Tag!",
|
||||
"create_your_first_tag_desc": "Tags help you categorize and find your Links easily. You can create Tags based on topics, projects, or any system that works for you.",
|
||||
"create_your_first_collection": "Create Your First Collection!",
|
||||
"create_your_first_collection_desc": "Collections are like folders for your Links which can then be shared with others.",
|
||||
"this_tag_has_no_links": "This Tag Has No Links",
|
||||
"this_tag_has_no_links_desc": "Use this Tag while creating or editing Links!",
|
||||
"accept_promotional_emails": "Get notified about new features and offers via email.",
|
||||
"expand_sidebar": "Expand Sidebar",
|
||||
"shrink_sidebar": "Shrink Sidebar",
|
||||
"trial_left_plural": "Trial ends in {{count}} days. Subscribe.",
|
||||
"trial_left_singular": "Trial ends in 1 day. Subscribe."
|
||||
}
|
||||
|
||||
@@ -377,12 +377,12 @@
|
||||
"demo_desc_2": "Als u de volledige versie wilt proberen, kunt u zich aanmelden voor een gratis proefperiode op:",
|
||||
"demo_button": "Inloggen als demo gebruiker",
|
||||
"regular": "Regular",
|
||||
"thin": "Thin",
|
||||
"thin": "Dun",
|
||||
"bold": "Bold",
|
||||
"fill": "Fill",
|
||||
"duotone": "Duotone",
|
||||
"light_icon": "Light",
|
||||
"search": "Search",
|
||||
"search": "Zoek",
|
||||
"set_custom_icon": "Set Custom Icon",
|
||||
"view": "View",
|
||||
"show": "Show",
|
||||
@@ -394,7 +394,7 @@
|
||||
"untitled": "Naamloos",
|
||||
"no_tags": "No tags.",
|
||||
"no_description_provided": "No description provided.",
|
||||
"change_icon": "Change Icon",
|
||||
"change_icon": "Pas icoon aan",
|
||||
"upload_banner": "Upload Banner",
|
||||
"columns": "Kolommen",
|
||||
"default": "Default",
|
||||
@@ -419,7 +419,7 @@
|
||||
"seat_purchased": "{{count}} seat purchased",
|
||||
"date_added": "Date Added",
|
||||
"resend_invite": "Resend Invitation",
|
||||
"resend_invite_success": "Invitation Resent!",
|
||||
"resend_invite_success": "Uitnodiging opnieuw verstuurd!",
|
||||
"remove_user": "Remove User",
|
||||
"continue_to_dashboard": "Continue to Dashboard",
|
||||
"confirm_user_removal_desc": "They will need to have a subscription to access Linkwarden again.",
|
||||
@@ -513,5 +513,17 @@
|
||||
"rename_tag_instruction": "Please provide a name for the final tag.",
|
||||
"merging": "Merging...",
|
||||
"delete_tags": "Delete {{count}} Tags",
|
||||
"tags_deletion_confirmation_message": "Are you sure you want to delete {{count}} Tags? This will remove the tags from all links."
|
||||
"tags_deletion_confirmation_message": "Are you sure you want to delete {{count}} Tags? This will remove the tags from all links.",
|
||||
"subscribe_later": "Subscribe Later?",
|
||||
"create_your_first_tag": "Create Your First Tag!",
|
||||
"create_your_first_tag_desc": "Tags help you categorize and find your Links easily. You can create Tags based on topics, projects, or any system that works for you.",
|
||||
"create_your_first_collection": "Create Your First Collection!",
|
||||
"create_your_first_collection_desc": "Collections are like folders for your Links which can then be shared with others.",
|
||||
"this_tag_has_no_links": "This Tag Has No Links",
|
||||
"this_tag_has_no_links_desc": "Use this Tag while creating or editing Links!",
|
||||
"accept_promotional_emails": "Get notified about new features and offers via email.",
|
||||
"expand_sidebar": "Expand Sidebar",
|
||||
"shrink_sidebar": "Shrink Sidebar",
|
||||
"trial_left_plural": "Trial ends in {{count}} days. Subscribe.",
|
||||
"trial_left_singular": "Trial ends in 1 day. Subscribe."
|
||||
}
|
||||
|
||||
@@ -513,5 +513,17 @@
|
||||
"rename_tag_instruction": "Please provide a name for the final tag.",
|
||||
"merging": "Merging...",
|
||||
"delete_tags": "Delete {{count}} Tags",
|
||||
"tags_deletion_confirmation_message": "Are you sure you want to delete {{count}} Tags? This will remove the tags from all links."
|
||||
"tags_deletion_confirmation_message": "Are you sure you want to delete {{count}} Tags? This will remove the tags from all links.",
|
||||
"subscribe_later": "Subscribe Later?",
|
||||
"create_your_first_tag": "Create Your First Tag!",
|
||||
"create_your_first_tag_desc": "Tags help you categorize and find your Links easily. You can create Tags based on topics, projects, or any system that works for you.",
|
||||
"create_your_first_collection": "Create Your First Collection!",
|
||||
"create_your_first_collection_desc": "Collections are like folders for your Links which can then be shared with others.",
|
||||
"this_tag_has_no_links": "This Tag Has No Links",
|
||||
"this_tag_has_no_links_desc": "Use this Tag while creating or editing Links!",
|
||||
"accept_promotional_emails": "Get notified about new features and offers via email.",
|
||||
"expand_sidebar": "Expand Sidebar",
|
||||
"shrink_sidebar": "Shrink Sidebar",
|
||||
"trial_left_plural": "Trial ends in {{count}} days. Subscribe.",
|
||||
"trial_left_singular": "Trial ends in 1 day. Subscribe."
|
||||
}
|
||||
|
||||
@@ -494,24 +494,36 @@
|
||||
"refresh_multiple_preserved_formats_confirmation_desc": "Isto irá apagar os atuais formatos preservados e re-preservar os links {{count}}.",
|
||||
"refresh_preserved_formats_confirmation_desc": "Isto irá excluir os atuais formatos preservados e preservar este link novamente.",
|
||||
"tag_already_added": "Esta tag já está adicionada.",
|
||||
"all_tags": "All Tags",
|
||||
"new_tag": "New Tag",
|
||||
"create_new_tag": "Create New Tag",
|
||||
"tag_deletion_confirmation_message": "Are you sure you want to delete this Tag?",
|
||||
"delete_tags_by_number_of_links": "Delete Tags with <0/> Links",
|
||||
"delete_all_tags": "Delete all Tags",
|
||||
"bulk_delete_tags": "Bulk Delete Tags",
|
||||
"count_tags_deleted": "{{count}} Tags Deleted",
|
||||
"count_tag_deleted": "{{count}} Tag Deleted",
|
||||
"tag_name_placeholder": "e.g. Technology",
|
||||
"link_count_high_low": "Link count (high to low)",
|
||||
"link_count_low_high": "Link count (low to high)",
|
||||
"tags_selected": "{{count}} Tags selected",
|
||||
"tag_selected": "1 Tag selected",
|
||||
"merge_tags": "Merge Tags",
|
||||
"merge_count_tags": "Merge {{count}} Tags",
|
||||
"rename_tag_instruction": "Please provide a name for the final tag.",
|
||||
"merging": "Merging...",
|
||||
"delete_tags": "Delete {{count}} Tags",
|
||||
"tags_deletion_confirmation_message": "Are you sure you want to delete {{count}} Tags? This will remove the tags from all links."
|
||||
"all_tags": "Todas as Etiquetas",
|
||||
"new_tag": "Nova Etiqueta",
|
||||
"create_new_tag": "Criar etiqueta",
|
||||
"tag_deletion_confirmation_message": "Tem certeza de que deseja excluir esta etiqueta?",
|
||||
"delete_tags_by_number_of_links": "Excluir etiquetas com links <0/>",
|
||||
"delete_all_tags": "Excluir todas as etiquetas",
|
||||
"bulk_delete_tags": "Exclusão em massa de etiquetas",
|
||||
"count_tags_deleted": "{{count}} Etiquetas deletadas",
|
||||
"count_tag_deleted": "{{count}} Etiqueta deletada",
|
||||
"tag_name_placeholder": "ex. Tecnologia",
|
||||
"link_count_high_low": "Qtd. de links (do maior para o menor)",
|
||||
"link_count_low_high": "Contagem de links (do menor para o maior)",
|
||||
"tags_selected": "{{count}} Etiquetas selecionadas",
|
||||
"tag_selected": "Uma etiqueta selecionada",
|
||||
"merge_tags": "Mesclar Etiquetas",
|
||||
"merge_count_tags": "Mesclar {{count}} Etiquetas",
|
||||
"rename_tag_instruction": "Por favor, forneça um nome para a etiqueta final.",
|
||||
"merging": "Mesclando...",
|
||||
"delete_tags": "Excluir {{count}} etiquetas",
|
||||
"tags_deletion_confirmation_message": "Tem certeza que deseja excluir {{count}} etiquetas? Isto irá remover as etiquetas de todos os links.",
|
||||
"subscribe_later": "Inscrever-se depois?",
|
||||
"create_your_first_tag": "Crie sua Primeira Etiqueta!",
|
||||
"create_your_first_tag_desc": "As etiquetas ajudam você a categorizar e encontrar seus links facilmente. Você pode criar etiquetas baseadas em tópicos, projetos ou qualquer sistema que funcione para você.",
|
||||
"create_your_first_collection": "Crie Sua Primeira Coleção!",
|
||||
"create_your_first_collection_desc": "Coleções são como pastas para seus links, que podem ser compartilhadas com outras pessoas.",
|
||||
"this_tag_has_no_links": "Esta etiqueta não possui links",
|
||||
"this_tag_has_no_links_desc": "Use esta etiqueta enquanto cria ou edita Links!",
|
||||
"accept_promotional_emails": "Seja notificado sobre novos recursos e ofertas por e-mail.",
|
||||
"expand_sidebar": "Expandir barra lateral",
|
||||
"shrink_sidebar": "Encolher barra lateral",
|
||||
"trial_left_plural": "O período de teste termina em {{count}} dias. Inscreva-se.",
|
||||
"trial_left_singular": "O período de teste termina em 1 dia. Inscreva-se."
|
||||
}
|
||||
|
||||
@@ -513,5 +513,17 @@
|
||||
"rename_tag_instruction": "Please provide a name for the final tag.",
|
||||
"merging": "Merging...",
|
||||
"delete_tags": "Delete {{count}} Tags",
|
||||
"tags_deletion_confirmation_message": "Are you sure you want to delete {{count}} Tags? This will remove the tags from all links."
|
||||
"tags_deletion_confirmation_message": "Are you sure you want to delete {{count}} Tags? This will remove the tags from all links.",
|
||||
"subscribe_later": "Subscribe Later?",
|
||||
"create_your_first_tag": "Create Your First Tag!",
|
||||
"create_your_first_tag_desc": "Tags help you categorize and find your Links easily. You can create Tags based on topics, projects, or any system that works for you.",
|
||||
"create_your_first_collection": "Create Your First Collection!",
|
||||
"create_your_first_collection_desc": "Collections are like folders for your Links which can then be shared with others.",
|
||||
"this_tag_has_no_links": "This Tag Has No Links",
|
||||
"this_tag_has_no_links_desc": "Use this Tag while creating or editing Links!",
|
||||
"accept_promotional_emails": "Get notified about new features and offers via email.",
|
||||
"expand_sidebar": "Expand Sidebar",
|
||||
"shrink_sidebar": "Shrink Sidebar",
|
||||
"trial_left_plural": "Trial ends in {{count}} days. Subscribe.",
|
||||
"trial_left_singular": "Trial ends in 1 day. Subscribe."
|
||||
}
|
||||
|
||||
@@ -513,5 +513,17 @@
|
||||
"rename_tag_instruction": "Please provide a name for the final tag.",
|
||||
"merging": "Merging...",
|
||||
"delete_tags": "Delete {{count}} Tags",
|
||||
"tags_deletion_confirmation_message": "Are you sure you want to delete {{count}} Tags? This will remove the tags from all links."
|
||||
"tags_deletion_confirmation_message": "Are you sure you want to delete {{count}} Tags? This will remove the tags from all links.",
|
||||
"subscribe_later": "Subscribe Later?",
|
||||
"create_your_first_tag": "Create Your First Tag!",
|
||||
"create_your_first_tag_desc": "Tags help you categorize and find your Links easily. You can create Tags based on topics, projects, or any system that works for you.",
|
||||
"create_your_first_collection": "Create Your First Collection!",
|
||||
"create_your_first_collection_desc": "Collections are like folders for your Links which can then be shared with others.",
|
||||
"this_tag_has_no_links": "This Tag Has No Links",
|
||||
"this_tag_has_no_links_desc": "Use this Tag while creating or editing Links!",
|
||||
"accept_promotional_emails": "Get notified about new features and offers via email.",
|
||||
"expand_sidebar": "Expand Sidebar",
|
||||
"shrink_sidebar": "Shrink Sidebar",
|
||||
"trial_left_plural": "Trial ends in {{count}} days. Subscribe.",
|
||||
"trial_left_singular": "Trial ends in 1 day. Subscribe."
|
||||
}
|
||||
|
||||
@@ -513,5 +513,17 @@
|
||||
"rename_tag_instruction": "Please provide a name for the final tag.",
|
||||
"merging": "Merging...",
|
||||
"delete_tags": "Delete {{count}} Tags",
|
||||
"tags_deletion_confirmation_message": "Are you sure you want to delete {{count}} Tags? This will remove the tags from all links."
|
||||
"tags_deletion_confirmation_message": "Are you sure you want to delete {{count}} Tags? This will remove the tags from all links.",
|
||||
"subscribe_later": "Subscribe Later?",
|
||||
"create_your_first_tag": "Create Your First Tag!",
|
||||
"create_your_first_tag_desc": "Tags help you categorize and find your Links easily. You can create Tags based on topics, projects, or any system that works for you.",
|
||||
"create_your_first_collection": "Create Your First Collection!",
|
||||
"create_your_first_collection_desc": "Collections are like folders for your Links which can then be shared with others.",
|
||||
"this_tag_has_no_links": "This Tag Has No Links",
|
||||
"this_tag_has_no_links_desc": "Use this Tag while creating or editing Links!",
|
||||
"accept_promotional_emails": "Get notified about new features and offers via email.",
|
||||
"expand_sidebar": "Expand Sidebar",
|
||||
"shrink_sidebar": "Shrink Sidebar",
|
||||
"trial_left_plural": "Trial ends in {{count}} days. Subscribe.",
|
||||
"trial_left_singular": "Trial ends in 1 day. Subscribe."
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"user_administration": "Users Administration",
|
||||
"user_administration": "Управління користувачами",
|
||||
"search_users": "Пошук користувачів",
|
||||
"no_users_found": "Користувачів не знайдено.",
|
||||
"no_user_found_in_search": "За вказаним пошуковим запитом не знайдено користувачів.",
|
||||
@@ -42,8 +42,8 @@
|
||||
"from_linkwarden": "Від Linkwarden",
|
||||
"from_html": "З HTML-файлу закладок",
|
||||
"from_wallabag": "Від Wallabag (файл JSON)",
|
||||
"from_omnivore": "From Omnivore (ZIP file)",
|
||||
"from_pocket": "From Pocket (CSV file)",
|
||||
"from_omnivore": "З Omnivore (ZIP-файл)",
|
||||
"from_pocket": "З Pocket (файл CSV)",
|
||||
"pinned": "Закріплено",
|
||||
"pinned_links_desc": "Ваші закріплені посилання",
|
||||
"pin_favorite_links_here": "Закріпіть тут свої улюблені посилання!",
|
||||
@@ -208,7 +208,7 @@
|
||||
"password_successfully_updated": "Ваш пароль успішно оновлено.",
|
||||
"user_already_member": "Користувач уже існує.",
|
||||
"you_are_already_collection_owner": "Ви вже є власником колекції.",
|
||||
"link_already_in_collection": "This link is already in this collection.",
|
||||
"link_already_in_collection": "Це посилання вже є у цій колекції.",
|
||||
"date_newest_first": "Дата (спочатку найновіші)",
|
||||
"date_oldest_first": "Дата (спочатку найдавніші)",
|
||||
"name_az": "Ім'я, зростання за алфавітом (А-Я)",
|
||||
@@ -242,7 +242,7 @@
|
||||
"new_version_announcement": "Подивіться, що нового в <0>Linkwarden {{version}}</0>",
|
||||
"creating": "Створення...",
|
||||
"upload_file": "Завантажте файл",
|
||||
"content": "Content",
|
||||
"content": "Вміст",
|
||||
"file_types": "PDF, PNG, JPG (до {{size}} МБ)",
|
||||
"description": "Опис",
|
||||
"auto_generated": "Буде створено автоматично, якщо нічого не надано.",
|
||||
@@ -321,7 +321,7 @@
|
||||
"sharable_link": "Посилання для спільного використання",
|
||||
"copied": "Скопійовано!",
|
||||
"members": "Члени",
|
||||
"add_member_placeholder": "Add members by email or username",
|
||||
"add_member_placeholder": "Додати учасників за ел. поштою або ім'ям користувача",
|
||||
"owner": "Власник",
|
||||
"admin": "Адмін",
|
||||
"contributor": "Дописувач",
|
||||
@@ -343,11 +343,11 @@
|
||||
"link_deletion_confirmation_message": "Ви впевнені, що хочете видалити це посилання?",
|
||||
"warning": "Попередження",
|
||||
"irreversible_warning": "Це незворотна дія!",
|
||||
"tip": "Tip",
|
||||
"tip": "Порада",
|
||||
"shift_key_tip": "Утримуйте клавішу Shift під час натискання 'Видалити', щоб обійти це підтвердження в майбутньому.",
|
||||
"deleting_collection": "Видалення...",
|
||||
"collection_deleted": "Колекцію видалено.",
|
||||
"collection_deletion_prompt": "Are you sure you want to delete this Collection?",
|
||||
"collection_deletion_prompt": "Ви справді хочете видалити цю колекцію?",
|
||||
"type_name_placeholder": "Введіть \"{{name}}\" тут.",
|
||||
"deletion_warning": "Видалення цієї колекції призведе до остаточного видалення всього її вмісту, і вона стане недоступною для всіх, включаючи учасників з попереднім доступом.",
|
||||
"leave_prompt": "Натисніть кнопку нижче, щоб залишити поточну колекцію.",
|
||||
@@ -395,7 +395,7 @@
|
||||
"no_tags": "Без міток.",
|
||||
"no_description_provided": "Опис не надано.",
|
||||
"change_icon": "Змінити піктограму",
|
||||
"upload_banner": "Upload Banner",
|
||||
"upload_banner": "Завантажити банер",
|
||||
"columns": "Стовпці",
|
||||
"default": "За замовчуванням",
|
||||
"invalid_url_guide": "Будь ласка, введіть дійсну адресу для посилання. (Він має починатися з http/https)",
|
||||
@@ -434,84 +434,96 @@
|
||||
"lemmy": "Lemmy",
|
||||
"people_recommendation": "Рекомендація (друг, родина тощо)",
|
||||
"open_all_links": "Відкрити всі посилання",
|
||||
"ai_settings": "AI Settings",
|
||||
"generate_tags_for_existing_links": "Generate tags for existing Links",
|
||||
"ai_tagging_method": "AI Tagging Method:",
|
||||
"based_on_predefined_tags": "Based on predefined Tags",
|
||||
"based_on_predefined_tags_desc": "Auto-categorize links to predefined tags based on the content of each link.",
|
||||
"based_on_existing_tags": "Based on existing Tags",
|
||||
"based_on_existing_tags_desc": "Auto-categorize links to existing tags based on the content of each link.",
|
||||
"auto_generate_tags": "Auto-generate Tags",
|
||||
"auto_generate_tags_desc": "Auto-generate relevant tags based on the content of each link.",
|
||||
"disabled": "Disabled",
|
||||
"ai_tagging_disabled_desc": "AI tagging is disabled.",
|
||||
"tag_selection_placeholder": "Choose or add custom tags…",
|
||||
"rss_subscriptions": "RSS Subscriptions",
|
||||
"rss_subscriptions_desc": "RSS Subscriptions are a way to keep up with your favorite websites and blogs. Linkwarden will automatically fetch the latest articles every {{number}} minutes from the feeds you provide.",
|
||||
"rss_deletion_confirmation": "Are you sure you want to delete this RSS Subscription?",
|
||||
"new_rss_subscription": "New RSS Subscription",
|
||||
"rss_subscription_deleted": "RSS Subscription deleted!",
|
||||
"create_rss_subscription": "Create RSS Subscription",
|
||||
"rss_feed": "RSS Feed",
|
||||
"pinned_links": "Pinned Links",
|
||||
"recent_links": "Recent Links",
|
||||
"search_results": "Search Results",
|
||||
"linkwarden_icon": "Linkwarden Icon",
|
||||
"permanent_session": "This is a permanent session",
|
||||
"locale": "en-US",
|
||||
"not_found_404": "404 - Not Found",
|
||||
"collection_publicly_shared": "This collection is being shared publicly.",
|
||||
"search_for_links": "Search for Links",
|
||||
"search_query_invalid_symbol": "The search query should not contain '%'.",
|
||||
"open_modal_new_tab": "Open this modal in a new tab",
|
||||
"ai_settings": "Налаштування ШІ",
|
||||
"generate_tags_for_existing_links": "Створити мітки для наявних посилань",
|
||||
"ai_tagging_method": "Мітки ШІ:",
|
||||
"based_on_predefined_tags": "На основі заданих міток",
|
||||
"based_on_predefined_tags_desc": "Автоматично розподіляти посилання до заданих міток на основі вмісту кожного посилання.",
|
||||
"based_on_existing_tags": "На основі існуючих міток",
|
||||
"based_on_existing_tags_desc": "Автоматично розподіляти посилання до наявних міток на основі вмісту кожного посилання.",
|
||||
"auto_generate_tags": "Автостворення міток",
|
||||
"auto_generate_tags_desc": "Автоматично створювати відповідні мітки на основі вмісту кожного посилання.",
|
||||
"disabled": "Вимкнено",
|
||||
"ai_tagging_disabled_desc": "Мітки ШІ вимкнуто.",
|
||||
"tag_selection_placeholder": "Виберіть або додайте власні мітки…",
|
||||
"rss_subscriptions": "RSS-підписки",
|
||||
"rss_subscriptions_desc": "RSS-підписки дозволяють бути в курсі оновлень ваших улюблених сайтів та блогів. Linkwarden автоматично завантажуватиме останні статті з наданих RSS-стрічок кожні {{number}} хвилин.",
|
||||
"rss_deletion_confirmation": "Ви дійсно бажаєте видалити цю RSS-підписку?",
|
||||
"new_rss_subscription": "Нова підписка RSS",
|
||||
"rss_subscription_deleted": "RSS-підписку видалено!",
|
||||
"create_rss_subscription": "Створити RSS-підписку",
|
||||
"rss_feed": "Стрічка RSS",
|
||||
"pinned_links": "Прикріплені посилання",
|
||||
"recent_links": "Останні посилання",
|
||||
"search_results": "Результати пошуку",
|
||||
"linkwarden_icon": "Піктограма Linkwarden",
|
||||
"permanent_session": "Це постійна сесія",
|
||||
"locale": "uk-UA",
|
||||
"not_found_404": "404 — Не знайдено",
|
||||
"collection_publicly_shared": "Цю колекцію поширено публічно.",
|
||||
"search_for_links": "Шукати посилання",
|
||||
"search_query_invalid_symbol": "Пошуковий запит не може містити '%'.",
|
||||
"open_modal_new_tab": "Відкрити спливне вікно у новій вкладці",
|
||||
"file": "Файл",
|
||||
"tag_preservation_rule_label": "Preservation rules by tag (override global settings):",
|
||||
"ai_tagging": "AI Tagging",
|
||||
"worker": "Worker",
|
||||
"regenerate_broken_preservations": "Regenerate all broken or missing preservations for all users.",
|
||||
"delete_all_preservations": "Delete preservation for all webpages, applies to all users.",
|
||||
"delete_all_preservations_and_regenerate": "Delete and re-preserve all webpages, applies to all users.",
|
||||
"delete_all_preservation_warning": "This action will delete all existing preservations on the server.",
|
||||
"no_broken_preservations": "No broken preservations.",
|
||||
"links_are_being_represerved": "Links are being re-preserved...",
|
||||
"cancel": "Cancel",
|
||||
"preservation_rules": "Preservation rules:",
|
||||
"links_being_archived": "Links are being archived...",
|
||||
"display_on_dashboard": "Display on Dashboard",
|
||||
"dashboard_stats": "Dashboard Stats",
|
||||
"no_results_found": "No results found.",
|
||||
"no_link_in_collection": "No Link in this Collection",
|
||||
"no_link_in_collection_desc": "This Collection has no Links yet.",
|
||||
"theme": "Theme",
|
||||
"font_style": "Font Style",
|
||||
"font_size": "Font Size",
|
||||
"line_height": "Line Height",
|
||||
"line_width": "Line Width",
|
||||
"notes_highlights": "Notes & Highlights",
|
||||
"no_notes_highlights": "No notes or highlights found for this link.",
|
||||
"save": "Save",
|
||||
"edit_layout": "Edit Layout",
|
||||
"refresh_multiple_preserved_formats_confirmation_desc": "This will delete the current preserved formats and re-preserve {{count}} links.",
|
||||
"refresh_preserved_formats_confirmation_desc": "This will delete the current preserved formats and re-preserve this link.",
|
||||
"tag_already_added": "This tag is already added.",
|
||||
"all_tags": "All Tags",
|
||||
"new_tag": "New Tag",
|
||||
"create_new_tag": "Create New Tag",
|
||||
"tag_deletion_confirmation_message": "Are you sure you want to delete this Tag?",
|
||||
"delete_tags_by_number_of_links": "Delete Tags with <0/> Links",
|
||||
"delete_all_tags": "Delete all Tags",
|
||||
"bulk_delete_tags": "Bulk Delete Tags",
|
||||
"count_tags_deleted": "{{count}} Tags Deleted",
|
||||
"count_tag_deleted": "{{count}} Tag Deleted",
|
||||
"tag_name_placeholder": "e.g. Technology",
|
||||
"link_count_high_low": "Link count (high to low)",
|
||||
"link_count_low_high": "Link count (low to high)",
|
||||
"tags_selected": "{{count}} Tags selected",
|
||||
"tag_selected": "1 Tag selected",
|
||||
"merge_tags": "Merge Tags",
|
||||
"merge_count_tags": "Merge {{count}} Tags",
|
||||
"rename_tag_instruction": "Please provide a name for the final tag.",
|
||||
"merging": "Merging...",
|
||||
"delete_tags": "Delete {{count}} Tags",
|
||||
"tags_deletion_confirmation_message": "Are you sure you want to delete {{count}} Tags? This will remove the tags from all links."
|
||||
"tag_preservation_rule_label": "Правила збереження за міткою (пріоритетніше загальних налаштувань):",
|
||||
"ai_tagging": "Мітки ШІ",
|
||||
"worker": "Працівник",
|
||||
"regenerate_broken_preservations": "Повторно створити усі пошкоджені та відсутні збереження для усіх користувачів.",
|
||||
"delete_all_preservations": "Видалити збереження усіх вебсторінок, застосовується до усіх користувачів.",
|
||||
"delete_all_preservations_and_regenerate": "Видалити та зберегти усі вебсторінки заново, застосовується до усіх користувачів.",
|
||||
"delete_all_preservation_warning": "Ця дія видалить усі наявні збереження на сервері.",
|
||||
"no_broken_preservations": "Немає пошкоджених збережень.",
|
||||
"links_are_being_represerved": "Посилання перезберігаються...",
|
||||
"cancel": "Скасувати",
|
||||
"preservation_rules": "Правила зберігання:",
|
||||
"links_being_archived": "Посилання архівуються...",
|
||||
"display_on_dashboard": "Показати на панелі",
|
||||
"dashboard_stats": "Статистика",
|
||||
"no_results_found": "Результатів не знайдено.",
|
||||
"no_link_in_collection": "У цій колекції нема посилань",
|
||||
"no_link_in_collection_desc": "Ця колекція ще не має посилань.",
|
||||
"theme": "Тема",
|
||||
"font_style": "Стиль шрифту",
|
||||
"font_size": "Розмір шрифту",
|
||||
"line_height": "Висота рядка",
|
||||
"line_width": "Ширина рядка",
|
||||
"notes_highlights": "Нотатки та виділення",
|
||||
"no_notes_highlights": "Для цього посилання не знайдено нотаток чи виділень.",
|
||||
"save": "Зберегти",
|
||||
"edit_layout": "Редагувати макет",
|
||||
"refresh_multiple_preserved_formats_confirmation_desc": "Це видалить усі збережені формати та збереже {{count}} посилань заново.",
|
||||
"refresh_preserved_formats_confirmation_desc": "Це видалить усі збережені формати та збереже це посилання заново.",
|
||||
"tag_already_added": "Цю мітку вже додано.",
|
||||
"all_tags": "Усі мітки",
|
||||
"new_tag": "Нова мітка",
|
||||
"create_new_tag": "Створити нову мітку",
|
||||
"tag_deletion_confirmation_message": "Ви впевнені, що хочете видалити цю мітку?",
|
||||
"delete_tags_by_number_of_links": "Видалити мітки з <0/> посиланнями",
|
||||
"delete_all_tags": "Видалити усі мітки",
|
||||
"bulk_delete_tags": "Масове видалення міток",
|
||||
"count_tags_deleted": "Міток видалено: {{count}}",
|
||||
"count_tag_deleted": "Видалено міток: {{count}}",
|
||||
"tag_name_placeholder": "напр. Технології",
|
||||
"link_count_high_low": "Кількість посилань (від вищої)",
|
||||
"link_count_low_high": "Кількість посилань (від нижчої)",
|
||||
"tags_selected": "Міток видалено: {{count}}",
|
||||
"tag_selected": "Вибрано 1 мітку",
|
||||
"merge_tags": "Об'єднати мітки",
|
||||
"merge_count_tags": "Об'єднати мітки: {{count}}",
|
||||
"rename_tag_instruction": "Будь ласка, вкажіть назву для мітки.",
|
||||
"merging": "Об’єднання...",
|
||||
"delete_tags": "Видалити мітки: {{count}}",
|
||||
"tags_deletion_confirmation_message": "Ви впевнені, що хочете видалити {{count}} міток? Це видалить мітки з усіх посилань.",
|
||||
"subscribe_later": "Підписатися пізніше?",
|
||||
"create_your_first_tag": "Створіть першу мітку!",
|
||||
"create_your_first_tag_desc": "Мітки допомагають легко категоризувати та знаходити посилання. Створюйте мітки до відповідних тем, проєктів, чи будь-чого іншого.",
|
||||
"create_your_first_collection": "Створіть свою першу колекцію!",
|
||||
"create_your_first_collection_desc": "Колекції — це як теки для ваших посилань, якими можна ділитися.",
|
||||
"this_tag_has_no_links": "Ця мітка не має посилань",
|
||||
"this_tag_has_no_links_desc": "Скористайтеся цією міткою створюючи та редагуючи посилання!",
|
||||
"accept_promotional_emails": "Отримувати сповіщення про нові функції та пропозиції на ел. пошту.",
|
||||
"expand_sidebar": "Розгорнути бічну панель",
|
||||
"shrink_sidebar": "Згорнути бічну панель",
|
||||
"trial_left_plural": "Лишилося {{count}} днів пробного доступу. Підпишіться.",
|
||||
"trial_left_singular": "Лишився 1 день пробного доступу. Підпишіться."
|
||||
}
|
||||
|
||||
@@ -513,5 +513,17 @@
|
||||
"rename_tag_instruction": "Please provide a name for the final tag.",
|
||||
"merging": "Merging...",
|
||||
"delete_tags": "Delete {{count}} Tags",
|
||||
"tags_deletion_confirmation_message": "Are you sure you want to delete {{count}} Tags? This will remove the tags from all links."
|
||||
"tags_deletion_confirmation_message": "Are you sure you want to delete {{count}} Tags? This will remove the tags from all links.",
|
||||
"subscribe_later": "Subscribe Later?",
|
||||
"create_your_first_tag": "Create Your First Tag!",
|
||||
"create_your_first_tag_desc": "Tags help you categorize and find your Links easily. You can create Tags based on topics, projects, or any system that works for you.",
|
||||
"create_your_first_collection": "Create Your First Collection!",
|
||||
"create_your_first_collection_desc": "Collections are like folders for your Links which can then be shared with others.",
|
||||
"this_tag_has_no_links": "This Tag Has No Links",
|
||||
"this_tag_has_no_links_desc": "Use this Tag while creating or editing Links!",
|
||||
"accept_promotional_emails": "Get notified about new features and offers via email.",
|
||||
"expand_sidebar": "Expand Sidebar",
|
||||
"shrink_sidebar": "Shrink Sidebar",
|
||||
"trial_left_plural": "Trial ends in {{count}} days. Subscribe.",
|
||||
"trial_left_singular": "Trial ends in 1 day. Subscribe."
|
||||
}
|
||||
|
||||
@@ -379,13 +379,13 @@
|
||||
"regular": "常规",
|
||||
"thin": "细体",
|
||||
"bold": "粗体",
|
||||
"fill": "Fill",
|
||||
"duotone": "Duotone",
|
||||
"fill": "填充",
|
||||
"duotone": "双色调模式",
|
||||
"light_icon": "亮色",
|
||||
"search": "搜索",
|
||||
"set_custom_icon": "设置自定义图标",
|
||||
"view": "View",
|
||||
"show": "Show",
|
||||
"view": "查看",
|
||||
"show": "显示",
|
||||
"image": "图片",
|
||||
"icon": "图标",
|
||||
"date": "日期",
|
||||
@@ -396,7 +396,7 @@
|
||||
"no_description_provided": "没有添加描述。",
|
||||
"change_icon": "更改图标",
|
||||
"upload_banner": "上传横幅",
|
||||
"columns": "Columns",
|
||||
"columns": "列",
|
||||
"default": "默认",
|
||||
"invalid_url_guide": "请输入有效的链接地址。(应该以 http/https 开头)",
|
||||
"email_invalid": "请输入一个有效的电子邮件地址。",
|
||||
@@ -412,8 +412,8 @@
|
||||
"invitation_desc": "{{owner}} 已邀请您加入 Linkwarden。\n为继续操作,请完成您的账户设置。",
|
||||
"invitation_accepted": "邀请已接受!",
|
||||
"status": "状态",
|
||||
"pending": "Pending",
|
||||
"active": "Active",
|
||||
"pending": "待定",
|
||||
"active": "已启用",
|
||||
"manage_seats": "管理席位",
|
||||
"seats_purchased": "已购买 {{count}} 个席位",
|
||||
"seat_purchased": "已购买 {{count}} 个席位",
|
||||
@@ -428,11 +428,11 @@
|
||||
"thanks_for_feedback": "感谢您的反馈!",
|
||||
"quick_survey": "快速问卷调查",
|
||||
"how_did_you_discover_linkwarden": "你是如何得知Linkwarden的?",
|
||||
"rather_not_say": "Rather not say",
|
||||
"rather_not_say": "不愿透露",
|
||||
"search_engine": "搜索引擎(Google、Bing 等)",
|
||||
"reddit": "Reddit",
|
||||
"lemmy": "Lemmy",
|
||||
"people_recommendation": "Recommendation (Friend, Family, etc.)",
|
||||
"people_recommendation": "推荐(朋友、家人等)",
|
||||
"open_all_links": "打开所有链接",
|
||||
"ai_settings": "AI 设置",
|
||||
"generate_tags_for_existing_links": "为已有链接生成标签",
|
||||
@@ -494,24 +494,36 @@
|
||||
"refresh_multiple_preserved_formats_confirmation_desc": "此操作将删除当前已保存的格式,并重新保存 {{count}} 个链接。",
|
||||
"refresh_preserved_formats_confirmation_desc": "此操作将删除当前已保存的格式,并重新保存此链接。",
|
||||
"tag_already_added": "此标签已添加。",
|
||||
"all_tags": "All Tags",
|
||||
"new_tag": "New Tag",
|
||||
"create_new_tag": "Create New Tag",
|
||||
"tag_deletion_confirmation_message": "Are you sure you want to delete this Tag?",
|
||||
"delete_tags_by_number_of_links": "Delete Tags with <0/> Links",
|
||||
"all_tags": "所有标签",
|
||||
"new_tag": "新标签",
|
||||
"create_new_tag": "创建新标签",
|
||||
"tag_deletion_confirmation_message": "确定删除此标签?",
|
||||
"delete_tags_by_number_of_links": "删除 <0/> 链接的标签",
|
||||
"delete_all_tags": "删除所有标签",
|
||||
"bulk_delete_tags": "Bulk Delete Tags",
|
||||
"count_tags_deleted": "{{count}} Tags Deleted",
|
||||
"count_tag_deleted": "{{count}} Tag Deleted",
|
||||
"tag_name_placeholder": "e.g. Technology",
|
||||
"link_count_high_low": "Link count (high to low)",
|
||||
"link_count_low_high": "Link count (low to high)",
|
||||
"tags_selected": "{{count}} Tags selected",
|
||||
"tag_selected": "1 Tag selected",
|
||||
"merge_tags": "Merge Tags",
|
||||
"merge_count_tags": "Merge {{count}} Tags",
|
||||
"rename_tag_instruction": "Please provide a name for the final tag.",
|
||||
"merging": "Merging...",
|
||||
"delete_tags": "Delete {{count}} Tags",
|
||||
"tags_deletion_confirmation_message": "Are you sure you want to delete {{count}} Tags? This will remove the tags from all links."
|
||||
"bulk_delete_tags": "批量删除标签",
|
||||
"count_tags_deleted": "已删除 {{count}} 个标签",
|
||||
"count_tag_deleted": "已删除 {{count}} 个标签",
|
||||
"tag_name_placeholder": "例如: 技术",
|
||||
"link_count_high_low": "链接数量(从高到低)",
|
||||
"link_count_low_high": "链接数量(从低到高)",
|
||||
"tags_selected": "已选中 {{count}} 个标签",
|
||||
"tag_selected": "已选中 1 个标签",
|
||||
"merge_tags": "合并标签",
|
||||
"merge_count_tags": "合并 {{count}} 个标签",
|
||||
"rename_tag_instruction": "请为最后一个标签命名。",
|
||||
"merging": "合并中……",
|
||||
"delete_tags": "删除 {{count}} 个链接",
|
||||
"tags_deletion_confirmation_message": "确定删除 {{count}} 个标签?这些标签将从所有链接被删除。",
|
||||
"subscribe_later": "稍后订阅?",
|
||||
"create_your_first_tag": "创建您的第一个标签!",
|
||||
"create_your_first_tag_desc": "标签有助于您更容易地分类和找到您的链接。您可以根据主题、项目或为您服务的任何系统创建标签。",
|
||||
"create_your_first_collection": "创建您的第一个收藏夹!",
|
||||
"create_your_first_collection_desc": "收藏夹是您可以与他人分享的链接文件夹。",
|
||||
"this_tag_has_no_links": "此标签没有链接",
|
||||
"this_tag_has_no_links_desc": "创建或编辑链接时使用此标签!",
|
||||
"accept_promotional_emails": "通过电子邮件获得新功能和优惠通知。",
|
||||
"expand_sidebar": "展开侧边栏",
|
||||
"shrink_sidebar": "收缩侧边栏",
|
||||
"trial_left_plural": "试用期将于 {{count}} 天后结束,请订阅。",
|
||||
"trial_left_singular": "试用期将于 1 天后结束,请订阅。"
|
||||
}
|
||||
|
||||
@@ -1,53 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
<meta name="supported-color-schemes" content="light dark" />
|
||||
<title>Email</title>
|
||||
<style media="all" type="text/css">
|
||||
/* Apple Mail / iOS Mail / some webmail */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #181a1b !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
.divider {
|
||||
border-top: 1px solid #3e3f41 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* For Mobile */
|
||||
@media only screen and (max-width: 640px) {
|
||||
.main p,
|
||||
.main td,
|
||||
.main span {
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
padding: 8px !important;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 0 !important;
|
||||
padding-top: 8px !important;
|
||||
width: 100% !important;
|
||||
padding-left: 10px !important;
|
||||
padding-right: 10px !important;
|
||||
}
|
||||
|
||||
.main {
|
||||
border-left-width: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
border-right-width: 0 !important;
|
||||
}
|
||||
|
||||
.btn table {
|
||||
max-width: 100% !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.btn a {
|
||||
font-size: 16px !important;
|
||||
max-width: 100% !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* For Outlook */
|
||||
@media all {
|
||||
.ExternalClass {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ExternalClass,
|
||||
.ExternalClass p,
|
||||
.ExternalClass span,
|
||||
@@ -56,24 +45,6 @@
|
||||
.ExternalClass div {
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
.apple-link a {
|
||||
color: inherit !important;
|
||||
font-family: inherit !important;
|
||||
font-size: inherit !important;
|
||||
font-weight: inherit !important;
|
||||
line-height: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
#MessageViewBody a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
font-weight: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
@@ -81,11 +52,11 @@
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: 16px;
|
||||
line-height: 1.3;
|
||||
font-size: 15px;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
background-color: #f8f8f8;
|
||||
color: black;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
"
|
||||
@@ -95,350 +66,255 @@
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
class="body"
|
||||
style="
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
background-color: #f8f8f8;
|
||||
width: 100%;
|
||||
"
|
||||
width="100%"
|
||||
bgcolor="#f8f8f8"
|
||||
>
|
||||
<tr>
|
||||
<td
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 16px;
|
||||
vertical-align: top;
|
||||
"
|
||||
valign="top"
|
||||
>
|
||||
|
||||
</td>
|
||||
<td
|
||||
class="container"
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 16px;
|
||||
vertical-align: top;
|
||||
max-width: 600px;
|
||||
padding: 0;
|
||||
padding-top: 24px;
|
||||
width: 600px;
|
||||
margin: 0 auto;
|
||||
"
|
||||
width="600"
|
||||
valign="top"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
<td style="vertical-align: top" valign="top"> </td>
|
||||
<td class="container" width="600" valign="top" style="padding: 25px">
|
||||
<!-- Preheader (inbox preview) -->
|
||||
<span
|
||||
class="preheader"
|
||||
style="
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 600px;
|
||||
padding: 0;
|
||||
color: transparent;
|
||||
display: none;
|
||||
height: 0;
|
||||
max-height: 0;
|
||||
max-width: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
mso-hide: all;
|
||||
visibility: hidden;
|
||||
width: 0;
|
||||
"
|
||||
>
|
||||
<!-- START CENTERED WHITE CONTAINER -->
|
||||
<span
|
||||
class="preheader"
|
||||
style="
|
||||
color: transparent;
|
||||
display: none;
|
||||
height: 0;
|
||||
max-height: 0;
|
||||
max-width: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
mso-hide: all;
|
||||
visibility: hidden;
|
||||
width: 0;
|
||||
"
|
||||
>Please verify your email address by clicking the button
|
||||
below.</span
|
||||
>
|
||||
<table
|
||||
role="presentation"
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
class="main"
|
||||
style="
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
background: #ffffff;
|
||||
border: 1px solid #eaebed;
|
||||
border-radius: 16px;
|
||||
width: 100%;
|
||||
"
|
||||
width="100%"
|
||||
>
|
||||
<!-- START MAIN CONTENT AREA -->
|
||||
Please verify your email address by clicking the button below:
|
||||
</span>
|
||||
|
||||
<a href="https://linkwarden.app" target="_blank">
|
||||
<img
|
||||
style="width: 50px"
|
||||
src="https://raw.githubusercontent.com/linkwarden/linkwarden/2c727ccd478b911015b6a0d604c6fca97849c591/assets/logo_small.png"
|
||||
alt="Linkwarden"
|
||||
/>
|
||||
</a>
|
||||
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="30" style="line-height: 30px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h1 style="font-size: 24px; font-weight: bold; margin: 0">
|
||||
Dear {{identifier}},
|
||||
</h1>
|
||||
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="30" style="line-height: 30px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="font-weight: normal; margin: 0">
|
||||
You have been invited to join Linkwarden by
|
||||
{{parentSubscriptionEmail}}!
|
||||
</p>
|
||||
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="20" style="line-height: 20px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="font-weight: normal; margin: 0">
|
||||
Linkwarden helps teams and individuals collect, read, annotate, and
|
||||
fully preserve what matters, all in one place.
|
||||
</p>
|
||||
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="30" style="line-height: 30px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table
|
||||
role="presentation"
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
class="btn"
|
||||
style="box-sizing: border-box; width: 100%; min-width: 100%"
|
||||
width="100%"
|
||||
>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
class="wrapper"
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 16px;
|
||||
vertical-align: top;
|
||||
box-sizing: border-box;
|
||||
padding: 24px;
|
||||
width: fit-content;
|
||||
"
|
||||
valign="top"
|
||||
>
|
||||
<h1
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
margin-bottom: 16px;
|
||||
"
|
||||
>
|
||||
Dear
|
||||
<a
|
||||
href="mailto:{{parentSubscriptionEmail}}"
|
||||
style="color: red"
|
||||
>{{identifier}}</a
|
||||
>,
|
||||
</h1>
|
||||
|
||||
<p
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 16px;
|
||||
"
|
||||
>
|
||||
You have been invited to join Linkwarden by
|
||||
<a
|
||||
href="mailto:{{parentSubscriptionEmail}}"
|
||||
style="color: red"
|
||||
>
|
||||
{{parentSubscriptionEmail}}</a
|
||||
>!
|
||||
</p>
|
||||
|
||||
<p
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 16px;
|
||||
"
|
||||
>
|
||||
Linkwarden simplifies digital content management by allowing
|
||||
teams and individuals to easily collect, organize, and
|
||||
preserve webpages and articles.
|
||||
</p>
|
||||
|
||||
<td align="left" style="vertical-align: top" valign="top">
|
||||
<table
|
||||
role="presentation"
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
class="btn btn-primary"
|
||||
style="
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
"
|
||||
width="100%"
|
||||
>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
align="left"
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 16px;
|
||||
vertical-align: top;
|
||||
padding-bottom: 16px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
background-color: #105f9c;
|
||||
"
|
||||
valign="top"
|
||||
align="center"
|
||||
bgcolor="#00335a"
|
||||
>
|
||||
<table
|
||||
role="presentation"
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
<a
|
||||
href="{{url}}"
|
||||
target="_blank"
|
||||
style="
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
width: auto;
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
padding: 10px 18px;
|
||||
text-decoration: none;
|
||||
color: #ffffff;
|
||||
"
|
||||
>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 13px;
|
||||
vertical-align: top;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
background-color: #00335a;
|
||||
"
|
||||
valign="top"
|
||||
align="center"
|
||||
bgcolor="#0867ec"
|
||||
>
|
||||
<a
|
||||
href="{{url}}"
|
||||
target="_blank"
|
||||
style="
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
padding: 10px 18px;
|
||||
text-decoration: none;
|
||||
text-transform: capitalize;
|
||||
background-color: #00335a;
|
||||
color: #ffffff;
|
||||
"
|
||||
>
|
||||
Accept Invitation
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
Verify Email
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 16px;
|
||||
"
|
||||
>
|
||||
Please note that your Linkwarden account and billing will be
|
||||
managed by
|
||||
<a
|
||||
href="mailto:{{parentSubscriptionEmail}}"
|
||||
style="color: red"
|
||||
>
|
||||
{{parentSubscriptionEmail}}</a
|
||||
>.
|
||||
</p>
|
||||
|
||||
<hr
|
||||
style="
|
||||
border: none;
|
||||
border-top: 1px solid #eaebed;
|
||||
margin-bottom: 24px;
|
||||
width: 100%;
|
||||
"
|
||||
/>
|
||||
|
||||
<p
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 5px;
|
||||
color: #868686;
|
||||
"
|
||||
>
|
||||
If you’re having trouble clicking the button, click on the
|
||||
following link:
|
||||
</p>
|
||||
|
||||
<span
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 10px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 16px;
|
||||
word-break: break-all;
|
||||
"
|
||||
>
|
||||
{{url}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- END MAIN CONTENT AREA -->
|
||||
</table>
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="20" style="line-height: 20px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- START FOOTER -->
|
||||
<div
|
||||
class="footer"
|
||||
style="
|
||||
clear: both;
|
||||
padding-top: 24px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
"
|
||||
>
|
||||
<table
|
||||
role="presentation"
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
style="
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
width: 100%;
|
||||
"
|
||||
width="100%"
|
||||
>
|
||||
<tr>
|
||||
<td
|
||||
class="content-block"
|
||||
style="vertical-align: top; text-align: center"
|
||||
valign="top"
|
||||
align="center"
|
||||
>
|
||||
<img
|
||||
src="https://raw.githubusercontent.com/linkwarden/linkwarden/main/apps/web/public/linkwarden_light.png"
|
||||
alt="logo"
|
||||
style="width: 180px; height: auto"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<p style="font-weight: normal; margin: 0">
|
||||
Please note that your Linkwarden account and billing will be managed
|
||||
by
|
||||
<a href="mailto:{{parentSubscriptionEmail}}" style="color: red">
|
||||
{{parentSubscriptionEmail}}</a
|
||||
>.
|
||||
</p>
|
||||
|
||||
<!-- END FOOTER -->
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="20" style="line-height: 20px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- END CENTERED WHITE CONTAINER -->
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 16px;
|
||||
vertical-align: top;
|
||||
"
|
||||
valign="top"
|
||||
>
|
||||
|
||||
<hr
|
||||
class="divider"
|
||||
style="border: none; border-top: 1px solid #eaebed; width: 100%"
|
||||
/>
|
||||
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="20" style="line-height: 20px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p
|
||||
style="
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 5px;
|
||||
color: #868686;
|
||||
"
|
||||
>
|
||||
If you’re having trouble clicking the button, click on the following
|
||||
link:
|
||||
</p>
|
||||
|
||||
<a
|
||||
style="
|
||||
font-size: 10px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 16px;
|
||||
word-break: break-all;
|
||||
color: rgb(89, 89, 179);
|
||||
text-decoration: underline;
|
||||
"
|
||||
href="{{url}}"
|
||||
>
|
||||
{{url}}
|
||||
</a>
|
||||
</td>
|
||||
<td style="vertical-align: top" valign="top"> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
|
||||
@@ -1,53 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
<meta name="supported-color-schemes" content="light dark" />
|
||||
<title>Email</title>
|
||||
<style media="all" type="text/css">
|
||||
/* Apple Mail / iOS Mail / some webmail */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #181a1b !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
.divider {
|
||||
border-top: 1px solid #3e3f41 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* For Mobile */
|
||||
@media only screen and (max-width: 640px) {
|
||||
.main p,
|
||||
.main td,
|
||||
.main span {
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
padding: 8px !important;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 0 !important;
|
||||
padding-top: 8px !important;
|
||||
width: 100% !important;
|
||||
padding-left: 10px !important;
|
||||
padding-right: 10px !important;
|
||||
}
|
||||
|
||||
.main {
|
||||
border-left-width: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
border-right-width: 0 !important;
|
||||
}
|
||||
|
||||
.btn table {
|
||||
max-width: 100% !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.btn a {
|
||||
font-size: 16px !important;
|
||||
max-width: 100% !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* For Outlook */
|
||||
@media all {
|
||||
.ExternalClass {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ExternalClass,
|
||||
.ExternalClass p,
|
||||
.ExternalClass span,
|
||||
@@ -56,24 +45,6 @@
|
||||
.ExternalClass div {
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
.apple-link a {
|
||||
color: inherit !important;
|
||||
font-family: inherit !important;
|
||||
font-size: inherit !important;
|
||||
font-weight: inherit !important;
|
||||
line-height: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
#MessageViewBody a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
font-weight: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
@@ -81,11 +52,11 @@
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: 16px;
|
||||
line-height: 1.3;
|
||||
font-size: 15px;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
background-color: #f8f8f8;
|
||||
color: black;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
"
|
||||
@@ -95,293 +66,249 @@
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
class="body"
|
||||
style="
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
background-color: #f8f8f8;
|
||||
width: 100%;
|
||||
"
|
||||
width="100%"
|
||||
bgcolor="#f8f8f8"
|
||||
>
|
||||
<tr>
|
||||
<td
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 16px;
|
||||
vertical-align: top;
|
||||
"
|
||||
valign="top"
|
||||
>
|
||||
|
||||
</td>
|
||||
<td
|
||||
class="container"
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 16px;
|
||||
vertical-align: top;
|
||||
max-width: 600px;
|
||||
padding: 0;
|
||||
padding-top: 24px;
|
||||
width: 600px;
|
||||
margin: 0 auto;
|
||||
"
|
||||
width="600"
|
||||
valign="top"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
<td style="vertical-align: top" valign="top"> </td>
|
||||
<td class="container" width="600" valign="top" style="padding: 25px">
|
||||
<!-- Preheader (inbox preview) -->
|
||||
<span
|
||||
class="preheader"
|
||||
style="
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 600px;
|
||||
padding: 0;
|
||||
color: transparent;
|
||||
display: none;
|
||||
height: 0;
|
||||
max-height: 0;
|
||||
max-width: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
mso-hide: all;
|
||||
visibility: hidden;
|
||||
width: 0;
|
||||
"
|
||||
>
|
||||
<!-- START CENTERED WHITE CONTAINER -->
|
||||
<span
|
||||
class="preheader"
|
||||
style="
|
||||
color: transparent;
|
||||
display: none;
|
||||
height: 0;
|
||||
max-height: 0;
|
||||
max-width: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
mso-hide: all;
|
||||
visibility: hidden;
|
||||
width: 0;
|
||||
"
|
||||
>Reset your password?</span
|
||||
>
|
||||
<table
|
||||
role="presentation"
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
class="main"
|
||||
style="
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
background: #ffffff;
|
||||
border: 1px solid #eaebed;
|
||||
border-radius: 16px;
|
||||
width: 100%;
|
||||
"
|
||||
width="100%"
|
||||
>
|
||||
<!-- START MAIN CONTENT AREA -->
|
||||
You requested to change your password. Please click the button below
|
||||
to proceed.
|
||||
</span>
|
||||
|
||||
<a href="https://linkwarden.app" target="_blank">
|
||||
<img
|
||||
style="width: 50px"
|
||||
src="https://raw.githubusercontent.com/linkwarden/linkwarden/2c727ccd478b911015b6a0d604c6fca97849c591/assets/logo_small.png"
|
||||
alt="Linkwarden"
|
||||
/>
|
||||
</a>
|
||||
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="30" style="line-height: 30px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h1 style="font-size: 24px; font-weight: bold; margin: 0">
|
||||
Reset your password?
|
||||
</h1>
|
||||
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="30" style="line-height: 30px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="font-weight: normal; margin: 0">Hi {{user}}!</p>
|
||||
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="20" style="line-height: 20px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="font-weight: normal; margin: 0">
|
||||
Someone has requested a link to change your password.
|
||||
</p>
|
||||
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="30" style="line-height: 30px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table
|
||||
role="presentation"
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
class="btn"
|
||||
style="box-sizing: border-box; width: 100%; min-width: 100%"
|
||||
width="100%"
|
||||
>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
class="wrapper"
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 16px;
|
||||
vertical-align: top;
|
||||
box-sizing: border-box;
|
||||
padding: 24px;
|
||||
width: fit-content;
|
||||
"
|
||||
valign="top"
|
||||
>
|
||||
<h1
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
margin-bottom: 16px;
|
||||
"
|
||||
>
|
||||
Reset your password?
|
||||
</h1>
|
||||
|
||||
<p
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 16px;
|
||||
"
|
||||
>
|
||||
Hi {{user}}!
|
||||
</p>
|
||||
|
||||
<p
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 16px;
|
||||
"
|
||||
>
|
||||
Someone has requested a link to change your password.
|
||||
</p>
|
||||
|
||||
<td align="left" style="vertical-align: top" valign="top">
|
||||
<table
|
||||
role="presentation"
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
class="btn btn-primary"
|
||||
style="
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
"
|
||||
width="100%"
|
||||
>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
align="left"
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 16px;
|
||||
vertical-align: top;
|
||||
padding-bottom: 16px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
background-color: #105f9c;
|
||||
"
|
||||
valign="top"
|
||||
align="center"
|
||||
bgcolor="#00335a"
|
||||
>
|
||||
<table
|
||||
role="presentation"
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
<a
|
||||
href="{{url}}"
|
||||
target="_blank"
|
||||
style="
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
width: auto;
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
padding: 10px 18px;
|
||||
text-decoration: none;
|
||||
color: #ffffff;
|
||||
"
|
||||
>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 13px;
|
||||
vertical-align: top;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
background-color: #00335a;
|
||||
"
|
||||
valign="top"
|
||||
align="center"
|
||||
bgcolor="#0867ec"
|
||||
>
|
||||
<a
|
||||
href="{{url}}"
|
||||
target="_blank"
|
||||
style="
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
padding: 10px 18px;
|
||||
text-decoration: none;
|
||||
text-transform: capitalize;
|
||||
background-color: #00335a;
|
||||
color: #ffffff;
|
||||
"
|
||||
>Change Password</a
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
Change Password
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 16px;
|
||||
"
|
||||
>
|
||||
If you didn't request this, you can safely ignore this email
|
||||
and your password will not be changed.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- END MAIN CONTENT AREA -->
|
||||
</table>
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="20" style="line-height: 20px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- START FOOTER -->
|
||||
<div
|
||||
class="footer"
|
||||
style="
|
||||
clear: both;
|
||||
padding-top: 24px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
"
|
||||
>
|
||||
<table
|
||||
role="presentation"
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
style="
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
width: 100%;
|
||||
"
|
||||
width="100%"
|
||||
>
|
||||
<tr>
|
||||
<td
|
||||
class="content-block"
|
||||
style="vertical-align: top; text-align: center"
|
||||
valign="top"
|
||||
align="center"
|
||||
>
|
||||
<img
|
||||
src="https://raw.githubusercontent.com/linkwarden/linkwarden/main/apps/web/public/linkwarden_light.png"
|
||||
alt="logo"
|
||||
style="width: 180px; height: auto"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<p style="font-weight: normal; margin: 0">
|
||||
If you didn't request this, you can safely ignore this email and
|
||||
your password will not be changed.
|
||||
</p>
|
||||
|
||||
<!-- END FOOTER -->
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="20" style="line-height: 20px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- END CENTERED WHITE CONTAINER -->
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 16px;
|
||||
vertical-align: top;
|
||||
"
|
||||
valign="top"
|
||||
>
|
||||
|
||||
<hr
|
||||
class="divider"
|
||||
style="border: none; border-top: 1px solid #eaebed; width: 100%"
|
||||
/>
|
||||
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="20" style="line-height: 20px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p
|
||||
style="
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 5px;
|
||||
color: #868686;
|
||||
"
|
||||
>
|
||||
If you’re having trouble clicking the button, click on the following
|
||||
link:
|
||||
</p>
|
||||
|
||||
<a
|
||||
style="
|
||||
font-size: 10px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 16px;
|
||||
word-break: break-all;
|
||||
color: rgb(89, 89, 179);
|
||||
text-decoration: underline;
|
||||
"
|
||||
href="{{url}}"
|
||||
>
|
||||
{{url}}
|
||||
</a>
|
||||
</td>
|
||||
<td style="vertical-align: top" valign="top"> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
|
||||
@@ -1,53 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
<meta name="supported-color-schemes" content="light dark" />
|
||||
<title>Email</title>
|
||||
<style media="all" type="text/css">
|
||||
/* Apple Mail / iOS Mail / some webmail */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #181a1b !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
.divider {
|
||||
border-top: 1px solid #3e3f41 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* For Mobile */
|
||||
@media only screen and (max-width: 640px) {
|
||||
.main p,
|
||||
.main td,
|
||||
.main span {
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
padding: 8px !important;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 0 !important;
|
||||
padding-top: 8px !important;
|
||||
width: 100% !important;
|
||||
padding-left: 10px !important;
|
||||
padding-right: 10px !important;
|
||||
}
|
||||
|
||||
.main {
|
||||
border-left-width: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
border-right-width: 0 !important;
|
||||
}
|
||||
|
||||
.btn table {
|
||||
max-width: 100% !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.btn a {
|
||||
font-size: 16px !important;
|
||||
max-width: 100% !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* For Outlook */
|
||||
@media all {
|
||||
.ExternalClass {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ExternalClass,
|
||||
.ExternalClass p,
|
||||
.ExternalClass span,
|
||||
@@ -56,24 +45,6 @@
|
||||
.ExternalClass div {
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
.apple-link a {
|
||||
color: inherit !important;
|
||||
font-family: inherit !important;
|
||||
font-size: inherit !important;
|
||||
font-weight: inherit !important;
|
||||
line-height: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
#MessageViewBody a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
font-weight: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
@@ -81,11 +52,11 @@
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: 16px;
|
||||
line-height: 1.3;
|
||||
font-size: 15px;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
background-color: #f8f8f8;
|
||||
color: black;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
"
|
||||
@@ -95,318 +66,232 @@
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
class="body"
|
||||
style="
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
background-color: #f8f8f8;
|
||||
width: 100%;
|
||||
"
|
||||
width="100%"
|
||||
bgcolor="#f8f8f8"
|
||||
>
|
||||
<tr>
|
||||
<td
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 16px;
|
||||
vertical-align: top;
|
||||
"
|
||||
valign="top"
|
||||
>
|
||||
|
||||
</td>
|
||||
<td
|
||||
class="container"
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 16px;
|
||||
vertical-align: top;
|
||||
max-width: 600px;
|
||||
padding: 0;
|
||||
padding-top: 24px;
|
||||
width: 600px;
|
||||
margin: 0 auto;
|
||||
"
|
||||
width="600"
|
||||
valign="top"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
<td style="vertical-align: top" valign="top"> </td>
|
||||
<td class="container" width="600" valign="top" style="padding: 25px">
|
||||
<!-- Preheader (inbox preview) -->
|
||||
<span
|
||||
class="preheader"
|
||||
style="
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 600px;
|
||||
padding: 0;
|
||||
color: transparent;
|
||||
display: none;
|
||||
height: 0;
|
||||
max-height: 0;
|
||||
max-width: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
mso-hide: all;
|
||||
visibility: hidden;
|
||||
width: 0;
|
||||
"
|
||||
>
|
||||
<!-- START CENTERED WHITE CONTAINER -->
|
||||
<span
|
||||
class="preheader"
|
||||
style="
|
||||
color: transparent;
|
||||
display: none;
|
||||
height: 0;
|
||||
max-height: 0;
|
||||
max-width: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
mso-hide: all;
|
||||
visibility: hidden;
|
||||
width: 0;
|
||||
"
|
||||
>Please verify your email address by clicking the button
|
||||
below.</span
|
||||
>
|
||||
<table
|
||||
role="presentation"
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
class="main"
|
||||
style="
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
background: #ffffff;
|
||||
border: 1px solid #eaebed;
|
||||
border-radius: 16px;
|
||||
width: 100%;
|
||||
"
|
||||
width="100%"
|
||||
>
|
||||
<!-- START MAIN CONTENT AREA -->
|
||||
Please verify your email address by clicking the button below:
|
||||
</span>
|
||||
|
||||
<a href="https://linkwarden.app" target="_blank">
|
||||
<img
|
||||
style="width: 50px"
|
||||
src="https://raw.githubusercontent.com/linkwarden/linkwarden/2c727ccd478b911015b6a0d604c6fca97849c591/assets/logo_small.png"
|
||||
alt="Linkwarden"
|
||||
/>
|
||||
</a>
|
||||
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="30" style="line-height: 30px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h1 style="font-size: 24px; font-weight: bold; margin: 0">
|
||||
Verify your email address
|
||||
</h1>
|
||||
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="30" style="line-height: 30px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="font-weight: normal; margin: 0">
|
||||
Thank you for signing up with Linkwarden! 🥳
|
||||
</p>
|
||||
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="20" style="line-height: 20px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="font-weight: normal; margin: 0">
|
||||
Just one small step left. Simply verify your email address, and
|
||||
you’re all set!
|
||||
</p>
|
||||
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="30" style="line-height: 30px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table
|
||||
role="presentation"
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
class="btn"
|
||||
style="box-sizing: border-box; width: 100%; min-width: 100%"
|
||||
width="100%"
|
||||
>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
class="wrapper"
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 16px;
|
||||
vertical-align: top;
|
||||
box-sizing: border-box;
|
||||
padding: 24px;
|
||||
width: fit-content;
|
||||
"
|
||||
valign="top"
|
||||
>
|
||||
<h1
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
margin-bottom: 16px;
|
||||
"
|
||||
>
|
||||
Verify your email address
|
||||
</h1>
|
||||
|
||||
<p
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 16px;
|
||||
"
|
||||
>
|
||||
Thank you for signing up with Linkwarden! 🥳
|
||||
</p>
|
||||
|
||||
<p
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 16px;
|
||||
"
|
||||
>
|
||||
Just one small step left. Simply verify your email address,
|
||||
and you’re all set!
|
||||
</p>
|
||||
|
||||
<td align="left" style="vertical-align: top" valign="top">
|
||||
<table
|
||||
role="presentation"
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
class="btn btn-primary"
|
||||
style="
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
"
|
||||
width="100%"
|
||||
>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
align="left"
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 16px;
|
||||
vertical-align: top;
|
||||
padding-bottom: 16px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
background-color: #105f9c;
|
||||
"
|
||||
valign="top"
|
||||
align="center"
|
||||
bgcolor="#00335a"
|
||||
>
|
||||
<table
|
||||
role="presentation"
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
<a
|
||||
href="{{url}}"
|
||||
target="_blank"
|
||||
style="
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
width: auto;
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
padding: 10px 18px;
|
||||
text-decoration: none;
|
||||
color: #ffffff;
|
||||
"
|
||||
>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 13px;
|
||||
vertical-align: top;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
background-color: #00335a;
|
||||
"
|
||||
valign="top"
|
||||
align="center"
|
||||
bgcolor="#0867ec"
|
||||
>
|
||||
<a
|
||||
href="{{url}}"
|
||||
target="_blank"
|
||||
style="
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
padding: 10px 18px;
|
||||
text-decoration: none;
|
||||
text-transform: capitalize;
|
||||
background-color: #00335a;
|
||||
color: #ffffff;
|
||||
"
|
||||
>Verify Email</a
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
Verify Email
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<hr
|
||||
style="
|
||||
border: none;
|
||||
border-top: 1px solid #eaebed;
|
||||
margin-bottom: 24px;
|
||||
width: 100%;
|
||||
"
|
||||
/>
|
||||
|
||||
<p
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 5px;
|
||||
color: #868686;
|
||||
"
|
||||
>
|
||||
If you’re having trouble clicking the button, click on the
|
||||
following link:
|
||||
</p>
|
||||
|
||||
<span
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 10px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 16px;
|
||||
word-break: break-all;
|
||||
"
|
||||
>
|
||||
{{url}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- END MAIN CONTENT AREA -->
|
||||
</table>
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="20" style="line-height: 20px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- START FOOTER -->
|
||||
<div
|
||||
class="footer"
|
||||
style="
|
||||
clear: both;
|
||||
padding-top: 24px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
"
|
||||
>
|
||||
<table
|
||||
role="presentation"
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
style="
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
width: 100%;
|
||||
"
|
||||
width="100%"
|
||||
>
|
||||
<tr>
|
||||
<td
|
||||
class="content-block"
|
||||
style="vertical-align: top; text-align: center"
|
||||
valign="top"
|
||||
align="center"
|
||||
>
|
||||
<img
|
||||
src="https://raw.githubusercontent.com/linkwarden/linkwarden/main/apps/web/public/linkwarden_light.png"
|
||||
alt="logo"
|
||||
style="width: 180px; height: auto"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<hr
|
||||
class="divider"
|
||||
style="border: none; border-top: 1px solid #eaebed; width: 100%"
|
||||
/>
|
||||
|
||||
<!-- END FOOTER -->
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="20" style="line-height: 20px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- END CENTERED WHITE CONTAINER -->
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 16px;
|
||||
vertical-align: top;
|
||||
"
|
||||
valign="top"
|
||||
>
|
||||
|
||||
<p
|
||||
style="
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 5px;
|
||||
color: #868686;
|
||||
"
|
||||
>
|
||||
If you’re having trouble clicking the button, click on the following
|
||||
link:
|
||||
</p>
|
||||
|
||||
<a
|
||||
style="
|
||||
font-size: 10px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 16px;
|
||||
word-break: break-all;
|
||||
color: rgb(89, 89, 179);
|
||||
text-decoration: underline;
|
||||
"
|
||||
href="{{url}}"
|
||||
>
|
||||
{{url}}
|
||||
</a>
|
||||
</td>
|
||||
<td style="vertical-align: top" valign="top"> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
|
||||
@@ -1,53 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
<meta name="supported-color-schemes" content="light dark" />
|
||||
<title>Email</title>
|
||||
<style media="all" type="text/css">
|
||||
/* Apple Mail / iOS Mail / some webmail */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #181a1b !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
.divider {
|
||||
border-top: 1px solid #3e3f41 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* For Mobile */
|
||||
@media only screen and (max-width: 640px) {
|
||||
.main p,
|
||||
.main td,
|
||||
.main span {
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
padding: 8px !important;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 0 !important;
|
||||
padding-top: 8px !important;
|
||||
width: 100% !important;
|
||||
padding-left: 10px !important;
|
||||
padding-right: 10px !important;
|
||||
}
|
||||
|
||||
.main {
|
||||
border-left-width: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
border-right-width: 0 !important;
|
||||
}
|
||||
|
||||
.btn table {
|
||||
max-width: 100% !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.btn a {
|
||||
font-size: 16px !important;
|
||||
max-width: 100% !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* For Outlook */
|
||||
@media all {
|
||||
.ExternalClass {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ExternalClass,
|
||||
.ExternalClass p,
|
||||
.ExternalClass span,
|
||||
@@ -56,24 +45,6 @@
|
||||
.ExternalClass div {
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
.apple-link a {
|
||||
color: inherit !important;
|
||||
font-family: inherit !important;
|
||||
font-size: inherit !important;
|
||||
font-weight: inherit !important;
|
||||
line-height: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
#MessageViewBody a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
font-weight: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
@@ -81,11 +52,11 @@
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: 16px;
|
||||
line-height: 1.3;
|
||||
font-size: 15px;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
background-color: #f8f8f8;
|
||||
color: black;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
"
|
||||
@@ -95,329 +66,270 @@
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
class="body"
|
||||
style="
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
background-color: #f8f8f8;
|
||||
width: 100%;
|
||||
"
|
||||
width="100%"
|
||||
bgcolor="#f8f8f8"
|
||||
>
|
||||
<tr>
|
||||
<td
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 16px;
|
||||
vertical-align: top;
|
||||
"
|
||||
valign="top"
|
||||
>
|
||||
|
||||
</td>
|
||||
<td
|
||||
class="container"
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 16px;
|
||||
vertical-align: top;
|
||||
max-width: 600px;
|
||||
padding: 0;
|
||||
padding-top: 24px;
|
||||
width: 600px;
|
||||
margin: 0 auto;
|
||||
"
|
||||
width="600"
|
||||
valign="top"
|
||||
>
|
||||
<div
|
||||
class="content"
|
||||
<td style="vertical-align: top" valign="top"> </td>
|
||||
<td class="container" width="600" valign="top" style="padding: 25px">
|
||||
<!-- Preheader (inbox preview) -->
|
||||
<span
|
||||
class="preheader"
|
||||
style="
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 600px;
|
||||
padding: 0;
|
||||
color: transparent;
|
||||
display: none;
|
||||
height: 0;
|
||||
max-height: 0;
|
||||
max-width: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
mso-hide: all;
|
||||
visibility: hidden;
|
||||
width: 0;
|
||||
"
|
||||
>
|
||||
<!-- START CENTERED WHITE CONTAINER -->
|
||||
<span
|
||||
class="preheader"
|
||||
style="
|
||||
color: transparent;
|
||||
display: none;
|
||||
height: 0;
|
||||
max-height: 0;
|
||||
max-width: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
mso-hide: all;
|
||||
visibility: hidden;
|
||||
width: 0;
|
||||
"
|
||||
>You recently requested to change the email address associated
|
||||
with your Linkwarden account. To verify your new email address,
|
||||
please click the button below.</span
|
||||
>
|
||||
<table
|
||||
role="presentation"
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
class="main"
|
||||
style="
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
background: #ffffff;
|
||||
border: 1px solid #eaebed;
|
||||
border-radius: 16px;
|
||||
width: 100%;
|
||||
"
|
||||
width="100%"
|
||||
>
|
||||
<!-- START MAIN CONTENT AREA -->
|
||||
You requested to change your email address. Please verify your new
|
||||
email by clicking the button below.
|
||||
</span>
|
||||
|
||||
<a href="https://linkwarden.app" target="_blank">
|
||||
<img
|
||||
style="width: 50px"
|
||||
src="https://raw.githubusercontent.com/linkwarden/linkwarden/2c727ccd478b911015b6a0d604c6fca97849c591/assets/logo_small.png"
|
||||
alt="Linkwarden"
|
||||
/>
|
||||
</a>
|
||||
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="30" style="line-height: 30px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h1 style="font-size: 24px; font-weight: bold; margin: 0">
|
||||
Verify your new Linkwarden email address
|
||||
</h1>
|
||||
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="30" style="line-height: 30px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="font-weight: normal; margin: 0">Hi {{user}}!</p>
|
||||
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="20" style="line-height: 20px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="font-weight: normal; margin: 0">
|
||||
You recently requested to change the email address associated with
|
||||
your <a href="{{baseUrl}}">Linkwarden</a> account. To verify your
|
||||
new email address, please click the button below.
|
||||
</p>
|
||||
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="20" style="line-height: 20px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="font-weight: bold; margin: 0; margin-bottom: 5px">
|
||||
Old Email:
|
||||
</p>
|
||||
<p style="font-weight: normal; margin: 0">{{oldEmail}}</p>
|
||||
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="20" style="line-height: 20px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="font-weight: bold; margin: 0; margin-bottom: 5px">
|
||||
New Email:
|
||||
</p>
|
||||
<p style="font-weight: normal; margin: 0">{{newEmail}}</p>
|
||||
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="30" style="line-height: 30px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table
|
||||
role="presentation"
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
class="btn"
|
||||
style="box-sizing: border-box; width: 100%; min-width: 100%"
|
||||
width="100%"
|
||||
>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
class="wrapper"
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 16px;
|
||||
vertical-align: top;
|
||||
box-sizing: border-box;
|
||||
padding: 24px;
|
||||
"
|
||||
valign="top"
|
||||
>
|
||||
<h1
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
margin-bottom: 16px;
|
||||
"
|
||||
>
|
||||
Verify your new Linkwarden email address
|
||||
</h1>
|
||||
<p
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 16px;
|
||||
"
|
||||
>
|
||||
Hi {{user}}!
|
||||
</p>
|
||||
<p
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 16px;
|
||||
"
|
||||
>
|
||||
You recently requested to change the email address
|
||||
associated with your
|
||||
<a href="{{baseUrl}}">Linkwarden</a> account. To verify your
|
||||
new email address, please click the button below.
|
||||
</p>
|
||||
<p
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 5px;
|
||||
"
|
||||
>
|
||||
<b>Old Email:</b>
|
||||
</p>
|
||||
|
||||
<p
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 16px;
|
||||
"
|
||||
>
|
||||
{{oldEmail}}
|
||||
</p>
|
||||
|
||||
<p
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 5px;
|
||||
"
|
||||
>
|
||||
<b>New Email:</b>
|
||||
</p>
|
||||
|
||||
<p
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 16px;
|
||||
"
|
||||
>
|
||||
{{newEmail}}
|
||||
</p>
|
||||
|
||||
<td align="left" style="vertical-align: top" valign="top">
|
||||
<table
|
||||
role="presentation"
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
class="btn btn-primary"
|
||||
style="
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
"
|
||||
width="100%"
|
||||
>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
align="left"
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 16px;
|
||||
vertical-align: top;
|
||||
padding-bottom: 16px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
background-color: #105f9c;
|
||||
"
|
||||
valign="top"
|
||||
align="center"
|
||||
bgcolor="#00335a"
|
||||
>
|
||||
<table
|
||||
role="presentation"
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
<a
|
||||
href="{{url}}"
|
||||
target="_blank"
|
||||
style="
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
width: auto;
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
padding: 10px 18px;
|
||||
text-decoration: none;
|
||||
color: #ffffff;
|
||||
"
|
||||
>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 13px;
|
||||
vertical-align: top;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
background-color: #00335a;
|
||||
"
|
||||
valign="top"
|
||||
align="center"
|
||||
bgcolor="#0867ec"
|
||||
>
|
||||
<a
|
||||
href="{{verifyUrl}}"
|
||||
target="_blank"
|
||||
style="
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
padding: 10px 18px;
|
||||
text-decoration: none;
|
||||
text-transform: capitalize;
|
||||
background-color: #00335a;
|
||||
color: #ffffff;
|
||||
"
|
||||
>Verify Email</a
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
Verify New Email
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- END MAIN CONTENT AREA -->
|
||||
</table>
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="20" style="line-height: 20px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- START FOOTER -->
|
||||
<div
|
||||
class="footer"
|
||||
style="
|
||||
clear: both;
|
||||
padding-top: 24px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
"
|
||||
>
|
||||
<table
|
||||
role="presentation"
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
style="
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
width: 100%;
|
||||
"
|
||||
width="100%"
|
||||
>
|
||||
<tr>
|
||||
<td
|
||||
class="content-block"
|
||||
style="vertical-align: top; text-align: center"
|
||||
valign="top"
|
||||
align="center"
|
||||
>
|
||||
<img
|
||||
src="https://raw.githubusercontent.com/linkwarden/linkwarden/main/apps/web/public/linkwarden_light.png"
|
||||
alt="logo"
|
||||
style="width: 180px; height: auto"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<hr
|
||||
class="divider"
|
||||
style="border: none; border-top: 1px solid #eaebed; width: 100%"
|
||||
/>
|
||||
|
||||
<!-- END FOOTER -->
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="20" style="line-height: 20px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- END CENTERED WHITE CONTAINER -->
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 16px;
|
||||
vertical-align: top;
|
||||
"
|
||||
valign="top"
|
||||
>
|
||||
|
||||
<p
|
||||
style="
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 5px;
|
||||
color: #868686;
|
||||
"
|
||||
>
|
||||
If you’re having trouble clicking the button, click on the following
|
||||
link:
|
||||
</p>
|
||||
|
||||
<a
|
||||
style="
|
||||
font-size: 10px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 16px;
|
||||
word-break: break-all;
|
||||
color: rgb(89, 89, 179);
|
||||
text-decoration: underline;
|
||||
"
|
||||
href="{{url}}"
|
||||
>
|
||||
{{url}}
|
||||
</a>
|
||||
</td>
|
||||
<td style="vertical-align: top" valign="top"> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { prisma } from "@linkwarden/prisma";
|
||||
import sendToWayback from "./preservationScheme/sendToWayback";
|
||||
import { AiTaggingMethod } from "@linkwarden/prisma/client";
|
||||
import fetchHeaders from "./fetchHeaders";
|
||||
import { createFolder, removeFiles } from "@linkwarden/filesystem";
|
||||
import { createFolder, readFile, removeFiles } from "@linkwarden/filesystem";
|
||||
import handleMonolith from "./preservationScheme/handleMonolith";
|
||||
import handleReadability from "./preservationScheme/handleReadability";
|
||||
import handleArchivePreview from "./preservationScheme/handleArchivePreview";
|
||||
@@ -46,7 +46,8 @@ export default async function archiveHandler(
|
||||
process.env.OPENAI_API_KEY ||
|
||||
process.env.AZURE_API_KEY ||
|
||||
process.env.ANTHROPIC_API_KEY ||
|
||||
process.env.OPENROUTER_API_KEY)
|
||||
process.env.OPENROUTER_API_KEY ||
|
||||
process.env.PERPLEXITY_API_KEY)
|
||||
? true
|
||||
: undefined,
|
||||
},
|
||||
@@ -119,6 +120,26 @@ export default async function archiveHandler(
|
||||
} else if (link.url) {
|
||||
await page.goto(link.url, { waitUntil: "domcontentloaded" });
|
||||
|
||||
// Handle Monolith being sent in beforehand while making sure other values line up
|
||||
if (link.monolith?.endsWith(".html")) {
|
||||
// Use Monolith content instead of page
|
||||
const file = await readFile(link.monolith);
|
||||
|
||||
if (file.contentType == "text/html") {
|
||||
const fileContent = file.file;
|
||||
|
||||
if (typeof fileContent === "string") {
|
||||
await page.setContent(fileContent, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
} else {
|
||||
await page.setContent(fileContent.toString("utf-8"), {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const metaDescription = await page.evaluate(() => {
|
||||
const description = document.querySelector(
|
||||
'meta[name="description"]'
|
||||
@@ -152,7 +173,8 @@ export default async function archiveHandler(
|
||||
process.env.OPENAI_API_KEY ||
|
||||
process.env.AZURE_API_KEY ||
|
||||
process.env.ANTHROPIC_API_KEY ||
|
||||
process.env.OPENROUTER_API_KEY)
|
||||
process.env.OPENROUTER_API_KEY ||
|
||||
process.env.PERPLEXITY_API_KEY)
|
||||
) {
|
||||
await autoTagLink(user, link.id, metaDescription);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
createOpenAICompatible,
|
||||
OpenAICompatibleProviderSettings,
|
||||
} from "@ai-sdk/openai-compatible";
|
||||
import { perplexity } from "@ai-sdk/perplexity";
|
||||
import { azure } from "@ai-sdk/azure";
|
||||
import { z } from "zod";
|
||||
import { anthropic } from "@ai-sdk/anthropic";
|
||||
@@ -61,6 +62,9 @@ const getAIModel = (): LanguageModelV1 => {
|
||||
|
||||
return openrouter(process.env.OPENROUTER_MODEL) as LanguageModelV1;
|
||||
}
|
||||
if (process.env.PERPLEXITY_API_KEY) {
|
||||
return perplexity(process.env.PERPLEXITY_MODEL || "sonar-pro");
|
||||
}
|
||||
throw new Error("No AI provider configured");
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import fetch from "node-fetch";
|
||||
import https from "https";
|
||||
import http from "http";
|
||||
import { HttpsProxyAgent } from 'https-proxy-agent';
|
||||
import { SocksProxyAgent } from "socks-proxy-agent";
|
||||
|
||||
@@ -7,10 +8,12 @@ export default async function fetchHeaders(url: string) {
|
||||
if (process.env.IGNORE_URL_SIZE_LIMIT === "true") return null;
|
||||
|
||||
try {
|
||||
const httpsAgent = new https.Agent({
|
||||
rejectUnauthorized:
|
||||
process.env.IGNORE_UNAUTHORIZED_CA === "true" ? false : true,
|
||||
});
|
||||
const httpsAgent = url.startsWith("http://")
|
||||
? new http.Agent({})
|
||||
: new https.Agent({
|
||||
rejectUnauthorized:
|
||||
process.env.IGNORE_UNAUTHORIZED_CA === "true" ? false : true,
|
||||
});
|
||||
|
||||
let fetchOpts = {
|
||||
method: "HEAD",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"@ai-sdk/anthropic": "1.1.5",
|
||||
"@ai-sdk/azure": "1.1.5",
|
||||
"@ai-sdk/openai-compatible": "^0.2.13",
|
||||
"@ai-sdk/perplexity": "1.1.9",
|
||||
"@linkwarden/filesystem": "*",
|
||||
"@linkwarden/lib": "*",
|
||||
"@linkwarden/prisma": "*",
|
||||
@@ -30,6 +31,7 @@
|
||||
"rss-parser": "^3.13.0",
|
||||
"socks-proxy-agent": "^8.0.2",
|
||||
"tsx": "^4.19.3",
|
||||
"handlebars": "^4.7.8",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
334
apps/worker/templates/trialEnded.html
Normal file
334
apps/worker/templates/trialEnded.html
Normal file
@@ -0,0 +1,334 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
<meta name="supported-color-schemes" content="light dark" />
|
||||
<title>Email</title>
|
||||
<style media="all" type="text/css">
|
||||
/* Apple Mail / iOS Mail / some webmail */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #181a1b !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
.divider {
|
||||
border-top: 1px solid #3e3f41 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* For Mobile */
|
||||
@media only screen and (max-width: 640px) {
|
||||
.container {
|
||||
width: 100% !important;
|
||||
padding-left: 10px !important;
|
||||
padding-right: 10px !important;
|
||||
}
|
||||
.btn a {
|
||||
font-size: 16px !important;
|
||||
max-width: 100% !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* For Outlook */
|
||||
@media all {
|
||||
.ExternalClass {
|
||||
width: 100%;
|
||||
}
|
||||
.ExternalClass,
|
||||
.ExternalClass p,
|
||||
.ExternalClass span,
|
||||
.ExternalClass font,
|
||||
.ExternalClass td,
|
||||
.ExternalClass div {
|
||||
line-height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body
|
||||
style="
|
||||
font-family: Helvetica, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: 15px;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
background-color: #f8f8f8;
|
||||
color: black;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
"
|
||||
>
|
||||
<table
|
||||
role="presentation"
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
style="
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
width: 100%;
|
||||
"
|
||||
width="100%"
|
||||
>
|
||||
<tr>
|
||||
<td style="vertical-align: top" valign="top"> </td>
|
||||
<td class="container" width="600" valign="top" style="padding: 25px">
|
||||
<!-- Preheader (inbox preview) -->
|
||||
<span
|
||||
class="preheader"
|
||||
style="
|
||||
color: transparent;
|
||||
display: none;
|
||||
height: 0;
|
||||
max-height: 0;
|
||||
max-width: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
mso-hide: all;
|
||||
visibility: hidden;
|
||||
width: 0;
|
||||
"
|
||||
>
|
||||
Your free trial has ended. Upgrade your account to continue using
|
||||
Linkwarden.
|
||||
</span>
|
||||
|
||||
<a href="https://linkwarden.app" target="_blank">
|
||||
<img
|
||||
style="width: 50px"
|
||||
src="https://raw.githubusercontent.com/linkwarden/linkwarden/2c727ccd478b911015b6a0d604c6fca97849c591/assets/logo_small.png"
|
||||
alt="Linkwarden"
|
||||
/>
|
||||
</a>
|
||||
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="30" style="line-height: 30px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h1 style="font-size: 24px; font-weight: bold; margin: 0">
|
||||
Your Linkwarden free trial has ended
|
||||
</h1>
|
||||
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="30" style="line-height: 30px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="font-weight: normal; margin: 0">Hi {{name}},</p>
|
||||
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="20" style="line-height: 20px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="font-weight: normal; margin: 0">
|
||||
Thanks for trying Linkwarden! Your free trial has now ended, and
|
||||
some features are no longer available.
|
||||
</p>
|
||||
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="20" style="line-height: 20px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="font-weight: normal; margin: 0">
|
||||
To continue saving, organizing, and accessing your links and
|
||||
preserved data, please upgrade your account.
|
||||
</p>
|
||||
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="30" style="line-height: 30px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table
|
||||
role="presentation"
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
class="btn"
|
||||
style="box-sizing: border-box; width: 100%; min-width: 100%"
|
||||
width="100%"
|
||||
>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="vertical-align: top" valign="top">
|
||||
<table
|
||||
role="presentation"
|
||||
border="0"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
style="
|
||||
vertical-align: top;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
background-color: #105f9c;
|
||||
"
|
||||
valign="top"
|
||||
align="center"
|
||||
bgcolor="#00335a"
|
||||
>
|
||||
<a
|
||||
href="{{url}}"
|
||||
target="_blank"
|
||||
style="
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
padding: 10px 18px;
|
||||
text-decoration: none;
|
||||
color: #ffffff;
|
||||
"
|
||||
>
|
||||
Upgrade Your Account
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="20" style="line-height: 20px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="font-weight: normal; margin: 0">
|
||||
Have questions? Just reply, we’re happy to help!
|
||||
</p>
|
||||
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="20" style="line-height: 20px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<hr
|
||||
class="divider"
|
||||
style="border: none; border-top: 1px solid #eaebed; width: 100%"
|
||||
/>
|
||||
|
||||
<!-- SPACER -->
|
||||
<table
|
||||
role="presentation"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
>
|
||||
<tr>
|
||||
<td height="20" style="line-height: 20px; font-size: 0">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p
|
||||
style="
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 5px;
|
||||
color: #868686;
|
||||
"
|
||||
>
|
||||
If you’re having trouble clicking the button, click on the following
|
||||
link:
|
||||
</p>
|
||||
|
||||
<a
|
||||
style="
|
||||
font-size: 10px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 16px;
|
||||
word-break: break-all;
|
||||
color: rgb(89, 89, 179);
|
||||
text-decoration: underline;
|
||||
"
|
||||
href="{{url}}"
|
||||
>
|
||||
{{url}}
|
||||
</a>
|
||||
</td>
|
||||
<td style="vertical-align: top" valign="top"> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,6 +1,7 @@
|
||||
import { startIndexing } from "./workers/linkIndexing";
|
||||
import { linkProcessing } from "./workers/linkProcessing";
|
||||
import { startRSSPolling } from "./workers/rssPolling";
|
||||
import { trialEndEmailWorker } from "./workers/trialEndEmailWorker";
|
||||
|
||||
const workerIntervalInSeconds =
|
||||
Number(process.env.ARCHIVE_SCRIPT_INTERVAL) || 10;
|
||||
@@ -10,6 +11,7 @@ async function init() {
|
||||
startRSSPolling();
|
||||
linkProcessing(workerIntervalInSeconds);
|
||||
startIndexing(workerIntervalInSeconds);
|
||||
trialEndEmailWorker();
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { prisma } from "@linkwarden/prisma";
|
||||
import archiveHandler from "../lib/archiveHandler";
|
||||
import { LinkWithCollectionOwnerAndTags } from "@linkwarden/types";
|
||||
import { delay } from "@linkwarden/lib";
|
||||
|
||||
125
apps/worker/workers/trialEndEmailWorker.ts
Normal file
125
apps/worker/workers/trialEndEmailWorker.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import { prisma } from "@linkwarden/prisma";
|
||||
import transporter from "@linkwarden/lib/transporter";
|
||||
import Handlebars from "handlebars";
|
||||
import { readFileSync } from "fs";
|
||||
import path from "path";
|
||||
|
||||
const sleep = (ms: number) => new Promise((res) => setTimeout(res, ms));
|
||||
|
||||
const batchSize = 10;
|
||||
const pauseMs = 30000;
|
||||
|
||||
/**
|
||||
* Runs the "trial ended" notifier in batches.
|
||||
* - Only runs if NEXT_PUBLIC_TRIAL_PERIOD_DAYS > 1
|
||||
* - Picks users whose createdAt is older than that trial window AND trialEndEmailSent=false
|
||||
* - Skips users with an active own or parent subscription
|
||||
* - Sends email via the provided transporter
|
||||
* - Marks trialEndEmailSent=true for processed users (including those with active subs)
|
||||
* - Waits for a minute between batches
|
||||
*/
|
||||
export async function trialEndEmailWorker() {
|
||||
const trialDays = Number(process.env.NEXT_PUBLIC_TRIAL_PERIOD_DAYS);
|
||||
if (
|
||||
!Number.isFinite(trialDays) ||
|
||||
trialDays <= 1 ||
|
||||
!process.env.STRIPE_SECRET_KEY ||
|
||||
process.env.NEXT_PUBLIC_REQUIRE_CC === "true"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("\x1b[34m%s\x1b[0m", `Starting trial-ended email worker...`);
|
||||
|
||||
const cutoff = new Date(Date.now() - trialDays * 24 * 60 * 60 * 1000);
|
||||
const from = {
|
||||
name: "Linkwarden",
|
||||
address: String(process.env.EMAIL_FROM),
|
||||
};
|
||||
|
||||
while (true) {
|
||||
// 1) Pick a batch of candidates
|
||||
const candidates = await prisma.user.findMany({
|
||||
where: {
|
||||
trialEndEmailSent: false,
|
||||
emailVerified: {
|
||||
not: null,
|
||||
},
|
||||
createdAt: { lte: cutoff, gte: new Date("2025-09-25") }, // safety upper bound to avoid processing old users
|
||||
},
|
||||
orderBy: { createdAt: "asc" },
|
||||
take: batchSize,
|
||||
include: {
|
||||
subscriptions: { select: { active: true } }, // own subscription
|
||||
parentSubscription: { select: { active: true } }, // family/parent plan
|
||||
},
|
||||
});
|
||||
|
||||
if (candidates.length === 0) {
|
||||
await sleep(pauseMs);
|
||||
continue;
|
||||
}
|
||||
|
||||
const processedIds: number[] = [];
|
||||
let emailsSent = 0;
|
||||
|
||||
// 2) Process the batch (send emails if no active sub)
|
||||
for (const user of candidates) {
|
||||
const hasActive =
|
||||
Boolean(user.subscriptions?.active) ||
|
||||
Boolean(user.parentSubscription?.active);
|
||||
|
||||
// We’ll mark users as processed at the end of this loop iteration.
|
||||
// If sending fails, we skip marking so we retry next pass.
|
||||
if (!hasActive && user.email) {
|
||||
emailsSent++;
|
||||
try {
|
||||
const emailsDir = path.resolve(process.cwd(), "templates");
|
||||
|
||||
const templateFile = readFileSync(
|
||||
path.join(emailsDir, "trialEnded.html"),
|
||||
"utf8"
|
||||
);
|
||||
|
||||
const emailTemplate = Handlebars.compile(templateFile);
|
||||
|
||||
await transporter.sendMail({
|
||||
from,
|
||||
to: user.email,
|
||||
subject: "Your Linkwarden trial has ended",
|
||||
html: emailTemplate({
|
||||
name: user.name?.trim() ? user.name.trim() : "there",
|
||||
url: process.env.BASE_URL,
|
||||
}),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(
|
||||
`[trial-worker] Failed to send trial-ended email to user ${user.id}`,
|
||||
err
|
||||
);
|
||||
// Do not mark as processed so it can be retried on the next batch run
|
||||
await sleep(pauseMs);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Whether we emailed or skipped (active sub, or missing email), mark processed
|
||||
processedIds.push(user.id);
|
||||
}
|
||||
|
||||
// 3) Mark processed users so we don't pick them again
|
||||
if (processedIds.length) {
|
||||
await prisma.user.updateMany({
|
||||
where: { id: { in: processedIds } },
|
||||
data: { trialEndEmailSent: true },
|
||||
});
|
||||
console.log(
|
||||
"\x1b[34m%s\x1b[0m",
|
||||
`Marked off ${processedIds.length} users' trialEndEmailSent to true. Emails sent: ${emailsSent}`
|
||||
);
|
||||
}
|
||||
|
||||
// 4) Pause before the next batch
|
||||
await sleep(pauseMs);
|
||||
}
|
||||
}
|
||||
BIN
assets/logo_small.png
Normal file
BIN
assets/logo_small.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
@@ -11,6 +11,8 @@
|
||||
"meilisearch": "^0.48.2",
|
||||
"rss-parser": "^3.13.0",
|
||||
"clsx": "^2.1.1",
|
||||
"tailwind-merge": "^3.3.0"
|
||||
"tailwind-merge": "^3.3.0",
|
||||
"nodemailer": "^6.9.3",
|
||||
"@types/nodemailer": "^6.4.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "User" ADD COLUMN "trialEndEmailSent" BOOLEAN NOT NULL DEFAULT false;
|
||||
@@ -69,6 +69,7 @@ model User {
|
||||
dashboardSections DashboardSection[]
|
||||
lastPickedAt DateTime?
|
||||
acceptPromotionalEmails Boolean @default(false)
|
||||
trialEndEmailSent Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
}
|
||||
|
||||
94
yarn.lock
94
yarn.lock
@@ -45,6 +45,14 @@
|
||||
"@ai-sdk/provider" "1.0.6"
|
||||
"@ai-sdk/provider-utils" "2.1.5"
|
||||
|
||||
"@ai-sdk/perplexity@1.1.9":
|
||||
version "1.1.9"
|
||||
resolved "https://registry.yarnpkg.com/@ai-sdk/perplexity/-/perplexity-1.1.9.tgz#99542573a4ac240e513c3b7de99359a77a7797a7"
|
||||
integrity sha512-Ytolh/v2XupXbTvjE18EFBrHLoNMH0Ueji3lfSPhCoRUfkwrgZ2D9jlNxvCNCCRiGJG5kfinSHvzrH5vGDklYA==
|
||||
dependencies:
|
||||
"@ai-sdk/provider" "1.1.3"
|
||||
"@ai-sdk/provider-utils" "2.2.8"
|
||||
|
||||
"@ai-sdk/provider-utils@2.1.10":
|
||||
version "2.1.10"
|
||||
resolved "https://registry.yarnpkg.com/@ai-sdk/provider-utils/-/provider-utils-2.1.10.tgz#dfd671ccda12e321b58f347b6cbbdd982d139359"
|
||||
@@ -74,6 +82,15 @@
|
||||
nanoid "^3.3.8"
|
||||
secure-json-parse "^2.7.0"
|
||||
|
||||
"@ai-sdk/provider-utils@2.2.8":
|
||||
version "2.2.8"
|
||||
resolved "https://registry.yarnpkg.com/@ai-sdk/provider-utils/-/provider-utils-2.2.8.tgz#ad11b92d5a1763ab34ba7b5fc42494bfe08b76d1"
|
||||
integrity sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==
|
||||
dependencies:
|
||||
"@ai-sdk/provider" "1.1.3"
|
||||
nanoid "^3.3.8"
|
||||
secure-json-parse "^2.7.0"
|
||||
|
||||
"@ai-sdk/provider@1.0.6":
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@ai-sdk/provider/-/provider-1.0.6.tgz#ab7e53a48a2cefe7bd593590a611b2ce12465bc3"
|
||||
@@ -2047,7 +2064,7 @@
|
||||
wrap-ansi "^7.0.0"
|
||||
ws "^8.12.1"
|
||||
|
||||
"@expo/code-signing-certificates@^0.0.5":
|
||||
"@expo/code-signing-certificates@0.0.5", "@expo/code-signing-certificates@^0.0.5":
|
||||
version "0.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@expo/code-signing-certificates/-/code-signing-certificates-0.0.5.tgz#a693ff684fb20c4725dade4b88a6a9f96b02496c"
|
||||
integrity sha512-BNhXkY1bblxKZpltzAx98G2Egj9g1Q+JRcvR7E99DOj862FTCX+ZPsAUtPTr7aHxwtrL7+fL3r0JSmM9kBm+Bw==
|
||||
@@ -5131,7 +5148,7 @@ ajv@^6.12.4:
|
||||
json-schema-traverse "^0.4.1"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
ajv@^8.0.0, ajv@^8.9.0:
|
||||
ajv@^8.0.0, ajv@^8.11.0, ajv@^8.9.0:
|
||||
version "8.17.1"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6"
|
||||
integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==
|
||||
@@ -7379,6 +7396,14 @@ expo-blur@~14.0.1:
|
||||
resolved "https://registry.yarnpkg.com/expo-blur/-/expo-blur-14.0.3.tgz#656d6b2442bfbbfb2a6608c6bc1151b29bce6698"
|
||||
integrity sha512-BL3xnqBJbYm3Hg9t/HjNjdeY7N/q8eK5tsLYxswWG1yElISWZmMvrXYekl7XaVCPfyFyz8vQeaxd7q74ZY3Wrw==
|
||||
|
||||
expo-build-properties@~0.13.3:
|
||||
version "0.13.3"
|
||||
resolved "https://registry.yarnpkg.com/expo-build-properties/-/expo-build-properties-0.13.3.tgz#6b96d0486148fca6e74e62c7c502c0a9990931aa"
|
||||
integrity sha512-gw7AYP+YF50Gr912BedelRDTfR4GnUEn9p5s25g4nv0hTJGWpBZdCYR5/Oi2rmCHJXxBqhPjxzV7JRh72fntLg==
|
||||
dependencies:
|
||||
ajv "^8.11.0"
|
||||
semver "^7.6.0"
|
||||
|
||||
expo-clipboard@~7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/expo-clipboard/-/expo-clipboard-7.0.1.tgz#31d61270e77a37d2a6b7ae9abf79e060497ef43b"
|
||||
@@ -7433,6 +7458,11 @@ expo-dev-menu@6.0.25:
|
||||
dependencies:
|
||||
expo-dev-menu-interface "1.9.3"
|
||||
|
||||
expo-eas-client@~0.13.3:
|
||||
version "0.13.3"
|
||||
resolved "https://registry.yarnpkg.com/expo-eas-client/-/expo-eas-client-0.13.3.tgz#1535a99a224e360581c6253b0a1ea767e19815b8"
|
||||
integrity sha512-t+1F1tiDocSot8iSnrn/CjTUMvVvPV2DpafSVcticpbSzMGybEN7wcamO1t18fK7WxGXpZE9gxtd80qwv/LLqQ==
|
||||
|
||||
expo-file-system@~18.0.12:
|
||||
version "18.0.12"
|
||||
resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-18.0.12.tgz#6ceeeb0725f6c5faaf58112f18c073c2acfb3027"
|
||||
@@ -7470,7 +7500,7 @@ expo-linking@~7.0.2, expo-linking@~7.0.5:
|
||||
expo-constants "~17.0.5"
|
||||
invariant "^2.2.4"
|
||||
|
||||
expo-manifests@~0.15.8:
|
||||
expo-manifests@~0.15.7, expo-manifests@~0.15.8:
|
||||
version "0.15.8"
|
||||
resolved "https://registry.yarnpkg.com/expo-manifests/-/expo-manifests-0.15.8.tgz#15e7b7b99d764b40ca3e3f859a126c856e2d6206"
|
||||
integrity sha512-VuIyaMfRfLZeETNsRohqhy1l7iZ7I+HKMPfZXVL2Yn17TT0WkOhZoq1DzYwPbOHPgp1Uk6phNa86EyaHrD2DLw==
|
||||
@@ -7544,6 +7574,11 @@ expo-status-bar@~2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/expo-status-bar/-/expo-status-bar-2.0.1.tgz#fc07726346dc30fbb68aadb0d7890b34fba42eee"
|
||||
integrity sha512-AkIPX7jWHRPp83UBZ1iXtVvyr0g+DgBVvIXTtlmPtmUsm8Vq9Bb5IGj86PW8osuFlgoTVAg7HI/+Ok7yEYwiRg==
|
||||
|
||||
expo-structured-headers@~4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/expo-structured-headers/-/expo-structured-headers-4.0.0.tgz#85537ae6daec61ebfb214ede4107c8841c6e16d0"
|
||||
integrity sha512-uPiwZjWq3AdFGgY52+I2nGPrNa6izxAglymPXHUZLekZW290GqIUOk7MBNDD4sg4JwUbSi3gdxEurpEvuq+FSg==
|
||||
|
||||
expo-symbols@~0.2.0:
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/expo-symbols/-/expo-symbols-0.2.2.tgz#753e55ab1fb8b8f3bdb66c0cfd954c390fa7f62b"
|
||||
@@ -7564,6 +7599,26 @@ expo-updates-interface@~1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/expo-updates-interface/-/expo-updates-interface-1.0.0.tgz#b98c66b800d29561c62409556948b2af3d5316e5"
|
||||
integrity sha512-93oWtvULJOj+Pp+N/lpTcFfuREX1wNeHtp7Lwn8EbzYYmdn37MvZU3TPW2tYYCZuhzmKEXnUblYcruYoDu7IrQ==
|
||||
|
||||
expo-updates@~0.27.4:
|
||||
version "0.27.4"
|
||||
resolved "https://registry.yarnpkg.com/expo-updates/-/expo-updates-0.27.4.tgz#e1c017b285ae5eee1a82b38b10d05a19eef81aa4"
|
||||
integrity sha512-0rg4L2fFPEjTR/qnZ9Te4Q4irVC8uvNcTZW1pWnWbadG1SLv2PKjS1MYX5BboKzC3ao0H7m++5TP3hWhNg9org==
|
||||
dependencies:
|
||||
"@expo/code-signing-certificates" "0.0.5"
|
||||
"@expo/config" "~10.0.11"
|
||||
"@expo/config-plugins" "~9.0.17"
|
||||
"@expo/spawn-async" "^1.7.2"
|
||||
arg "4.1.0"
|
||||
chalk "^4.1.2"
|
||||
expo-eas-client "~0.13.3"
|
||||
expo-manifests "~0.15.7"
|
||||
expo-structured-headers "~4.0.0"
|
||||
expo-updates-interface "~1.0.0"
|
||||
fast-glob "^3.3.2"
|
||||
fbemitter "^3.0.0"
|
||||
ignore "^5.3.1"
|
||||
resolve-from "^5.0.0"
|
||||
|
||||
expo-web-browser@~14.0.1:
|
||||
version "14.0.2"
|
||||
resolved "https://registry.yarnpkg.com/expo-web-browser/-/expo-web-browser-14.0.2.tgz#52d53947c42fdfb225e8c230418ffe508bcf98a7"
|
||||
@@ -8511,6 +8566,11 @@ ignore@^5.2.0:
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
|
||||
integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==
|
||||
|
||||
ignore@^5.3.1:
|
||||
version "5.3.2"
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5"
|
||||
integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==
|
||||
|
||||
image-q@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/image-q/-/image-q-4.0.0.tgz#31e075be7bae3c1f42a85c469b4732c358981776"
|
||||
@@ -11660,23 +11720,35 @@ react-native-helmet-async@2.0.4:
|
||||
react-fast-compare "^3.2.2"
|
||||
shallowequal "^1.1.0"
|
||||
|
||||
react-native-ios-context-menu@3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/react-native-ios-context-menu/-/react-native-ios-context-menu-3.1.0.tgz#8e5b7054959e6d4629d9aa18e5fe51fc18ccc597"
|
||||
integrity sha512-qdPSXMKUp5lDgmZeUPdv5sgBFhkFrIqma+zsnqJQYOvekb6Qs17yJy1Rqhrj0bJrwuduHzZX0aYbaA8whxqpDw==
|
||||
react-native-ios-context-menu@3.1.3:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/react-native-ios-context-menu/-/react-native-ios-context-menu-3.1.3.tgz#ac340cb4052ba7866bca19b365f8425801889139"
|
||||
integrity sha512-p65JTOxL0D8TOgTgq3A7nVhr/hQuRTtlmsH/aQ7vaOgxY4Na/QVcEF9s4wHc7y+Rcmv84bi6V6DhqxGkFFLPmA==
|
||||
dependencies:
|
||||
"@dominicstop/ts-event-emitter" "^1.1.0"
|
||||
|
||||
react-native-ios-utilities@5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/react-native-ios-utilities/-/react-native-ios-utilities-5.1.2.tgz#78ac76bc07d3464fe746194146c3dea6cff1080a"
|
||||
integrity sha512-H566MdgC1x0vW6D8EKweno1yRj/gQHeWiK0cRmyRexnUSk7oIecy7l2uyxXLYWFs+yvB/YIdLm+s2c19lKGfaw==
|
||||
react-native-ios-utilities@5.1.7:
|
||||
version "5.1.7"
|
||||
resolved "https://registry.yarnpkg.com/react-native-ios-utilities/-/react-native-ios-utilities-5.1.7.tgz#d9dfbeef45c8598a9fe32f0e2a2e3ff72deae8ec"
|
||||
integrity sha512-tvJFBfjYHcYFN3PhJOPxU71J+4fBKNWt65FlqxHApVTPcX2xxnrqvSHj7i1qHkjNG7bLDSsTotr+d1gIrXBzQg==
|
||||
|
||||
react-native-is-edge-to-edge@^1.1.6:
|
||||
version "1.1.7"
|
||||
resolved "https://registry.yarnpkg.com/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.1.7.tgz#28947688f9fafd584e73a4f935ea9603bd9b1939"
|
||||
integrity sha512-EH6i7E8epJGIcu7KpfXYXiV2JFIYITtq+rVS8uEb+92naMRBdxhTuS8Wn2Q7j9sqyO0B+Xbaaf9VdipIAmGW4w==
|
||||
|
||||
react-native-is-edge-to-edge@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.2.1.tgz#64e10851abd9d176cbf2b40562f751622bde3358"
|
||||
integrity sha512-FLbPWl/MyYQWz+KwqOZsSyj2JmLKglHatd3xLZWskXOpRaio4LfEDEz8E/A6uD8QoTHW6Aobw1jbEwK7KMgR7Q==
|
||||
|
||||
react-native-keyboard-controller@^1.19.0:
|
||||
version "1.19.0"
|
||||
resolved "https://registry.yarnpkg.com/react-native-keyboard-controller/-/react-native-keyboard-controller-1.19.0.tgz#c74e8fe2ccc98e5ba15c1dcb659ed5fdb8d3190f"
|
||||
integrity sha512-G/SPwip7XWObjHLLpMB83pQoki8wQh0dPL4c+uEqxmaDUznnHvkiGuSTz0FWY0UUCeClw/iJ31DirMp6jBC2Jw==
|
||||
dependencies:
|
||||
react-native-is-edge-to-edge "^1.2.1"
|
||||
|
||||
react-native-mmkv@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-native-mmkv/-/react-native-mmkv-3.2.0.tgz#460723eb23b9cc92c65b0416eae9874a6ddf5b82"
|
||||
|
||||
Reference in New Issue
Block a user