mirror of
https://github.com/karakeep-app/karakeep.git
synced 2025-12-12 20:35:52 +01:00
feat(mobile): Add user setting for default bookmark view mode (#1723)
* feat(mobile): add user setting for default bookmark view mode * regen db migration script * clean up implementation * Update docs/docs/07-Development/01-setup.md * Update GEMINI.md * use local setting instead of storing value in db * improve start-dev.sh to also handle for db migration * rename mobileBookmarkClickDefaultViewMode to defaultBookmarkView for consistency
This commit is contained in:
9
.claude/settings.json
Normal file
9
.claude/settings.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(pnpm typecheck:*)",
|
||||
"Bash(pnpm lint:*)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
}
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -61,3 +61,4 @@ data
|
||||
# VS-Code
|
||||
.vscode
|
||||
auth_failures.log
|
||||
.claude/settings.local.json
|
||||
|
||||
@@ -62,6 +62,7 @@ The project is organized into `apps` and `packages`:
|
||||
- `pnpm format`: Format the codebase.
|
||||
- `pnpm format:fix`: Fix formatting issues.
|
||||
- `pnpm test`: Run tests.
|
||||
- `pnpm db:generate --name description_of_schema_change`: db migration after making schema changes
|
||||
|
||||
Starting services:
|
||||
- `pnpm web`: Start the web application (this doesn't return, unless you kill it).
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect } from "react";
|
||||
import { Pressable, Text, View } from "react-native";
|
||||
import { ActivityIndicator, Pressable, Text, View } from "react-native";
|
||||
import { Slider } from "react-native-awesome-slider";
|
||||
import { useSharedValue } from "react-native-reanimated";
|
||||
import { Link } from "expo-router";
|
||||
@@ -67,6 +67,31 @@ export default function Dashboard() {
|
||||
</Pressable>
|
||||
</Link>
|
||||
</View>
|
||||
<View className="flex w-full flex-row items-center justify-between gap-8 rounded-lg bg-white px-4 py-2 dark:bg-accent">
|
||||
<Link
|
||||
asChild
|
||||
href="/dashboard/settings/bookmark-default-view"
|
||||
className="flex-1"
|
||||
>
|
||||
<Pressable className="flex flex-row justify-between">
|
||||
<Text className="text-lg text-accent-foreground">
|
||||
Default Bookmark View
|
||||
</Text>
|
||||
<View className="flex flex-row items-center gap-2">
|
||||
{isSettingsLoading ? (
|
||||
<ActivityIndicator size="small" />
|
||||
) : (
|
||||
<Text className="text-lg text-muted-foreground">
|
||||
{settings.defaultBookmarkView === "reader"
|
||||
? "Reader"
|
||||
: "Browser"}
|
||||
</Text>
|
||||
)}
|
||||
<ChevronRight color="rgb(0, 122, 255)" />
|
||||
</View>
|
||||
</Pressable>
|
||||
</Link>
|
||||
</View>
|
||||
<Text className="w-full p-1 text-2xl font-bold text-foreground">
|
||||
Upload Settings
|
||||
</Text>
|
||||
|
||||
@@ -136,6 +136,14 @@ export default function Dashboard() {
|
||||
headerBackTitle: "Back",
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="settings/bookmark-default-view"
|
||||
options={{
|
||||
title: "Bookmark View Mode",
|
||||
headerTitle: "Bookmark View Mode",
|
||||
headerBackTitle: "Back",
|
||||
}}
|
||||
/>
|
||||
</StyledStack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Alert,
|
||||
Keyboard,
|
||||
@@ -27,6 +27,7 @@ import FullPageSpinner from "@/components/ui/FullPageSpinner";
|
||||
import { Input } from "@/components/ui/Input";
|
||||
import { useToast } from "@/components/ui/Toast";
|
||||
import { useAssetUrl } from "@/lib/hooks";
|
||||
import useAppSettings from "@/lib/settings";
|
||||
import { api } from "@/lib/trpc";
|
||||
import { MenuView } from "@react-native-menu/menu";
|
||||
import {
|
||||
@@ -378,9 +379,11 @@ export default function ListView() {
|
||||
const { slug } = useLocalSearchParams();
|
||||
const { colorScheme } = useColorScheme();
|
||||
const isDark = colorScheme === "dark";
|
||||
const { settings } = useAppSettings();
|
||||
|
||||
const [bookmarkLinkType, setBookmarkLinkType] =
|
||||
useState<BookmarkLinkType>("browser");
|
||||
const [bookmarkLinkType, setBookmarkLinkType] = useState<BookmarkLinkType>(
|
||||
settings.defaultBookmarkView,
|
||||
);
|
||||
|
||||
if (typeof slug !== "string") {
|
||||
throw new Error("Unexpected param type");
|
||||
|
||||
68
apps/mobile/app/dashboard/settings/bookmark-default-view.tsx
Normal file
68
apps/mobile/app/dashboard/settings/bookmark-default-view.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import { Pressable, Text, View } from "react-native";
|
||||
import { useRouter } from "expo-router";
|
||||
import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView";
|
||||
import { Divider } from "@/components/ui/Divider";
|
||||
import { useToast } from "@/components/ui/Toast";
|
||||
import useAppSettings from "@/lib/settings";
|
||||
import { Check } from "lucide-react-native";
|
||||
|
||||
export default function BookmarkDefaultViewSettings() {
|
||||
const router = useRouter();
|
||||
const { toast } = useToast();
|
||||
const { settings, setSettings } = useAppSettings();
|
||||
|
||||
const handleUpdate = async (mode: "reader" | "browser") => {
|
||||
try {
|
||||
await setSettings({
|
||||
...settings,
|
||||
defaultBookmarkView: mode,
|
||||
});
|
||||
toast({
|
||||
message: "Default Bookmark View updated!",
|
||||
showProgress: false,
|
||||
});
|
||||
router.back();
|
||||
} catch {
|
||||
toast({
|
||||
message: "Something went wrong",
|
||||
variant: "destructive",
|
||||
showProgress: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const options = (["reader", "browser"] as const)
|
||||
.map((mode) => {
|
||||
const currentMode = settings.defaultBookmarkView;
|
||||
const isChecked = currentMode === mode;
|
||||
return [
|
||||
<Pressable
|
||||
onPress={() => handleUpdate(mode)}
|
||||
className="flex flex-row justify-between"
|
||||
key={mode}
|
||||
>
|
||||
<Text className="text-lg text-accent-foreground">
|
||||
{{ browser: "Browser", reader: "Reader" }[mode]}
|
||||
</Text>
|
||||
{isChecked && <Check color="rgb(0, 122, 255)" />}
|
||||
</Pressable>,
|
||||
<Divider
|
||||
key={mode + "-divider"}
|
||||
orientation="horizontal"
|
||||
className="my-3 h-0.5 w-full"
|
||||
/>,
|
||||
];
|
||||
})
|
||||
.flat();
|
||||
options.pop();
|
||||
|
||||
return (
|
||||
<CustomSafeAreaView>
|
||||
<View className="flex h-full w-full items-center px-4 py-2">
|
||||
<View className="w-full rounded-lg bg-white px-4 py-2 dark:bg-accent">
|
||||
{options}
|
||||
</View>
|
||||
</View>
|
||||
</CustomSafeAreaView>
|
||||
);
|
||||
}
|
||||
@@ -10,6 +10,10 @@ const zSettingsSchema = z.object({
|
||||
address: z.string(),
|
||||
imageQuality: z.number().optional().default(0.2),
|
||||
theme: z.enum(["light", "dark", "system"]).optional().default("system"),
|
||||
defaultBookmarkView: z
|
||||
.enum(["reader", "browser"])
|
||||
.optional()
|
||||
.default("reader"),
|
||||
});
|
||||
|
||||
export type Settings = z.infer<typeof zSettingsSchema>;
|
||||
@@ -23,7 +27,12 @@ interface AppSettingsState {
|
||||
const useSettings = create<AppSettingsState>((set, get) => ({
|
||||
settings: {
|
||||
isLoading: true,
|
||||
settings: { address: "", imageQuality: 0.2, theme: "system" },
|
||||
settings: {
|
||||
address: "",
|
||||
imageQuality: 0.2,
|
||||
theme: "system",
|
||||
defaultBookmarkView: "reader",
|
||||
},
|
||||
},
|
||||
setSettings: async (settings) => {
|
||||
await SecureStore.setItemAsync(SETTING_NAME, JSON.stringify(settings));
|
||||
|
||||
@@ -1,5 +1,31 @@
|
||||
# Setup
|
||||
|
||||
## Quick Start
|
||||
|
||||
For the fastest way to get started with development, use the one-command setup script:
|
||||
|
||||
```bash
|
||||
./start-dev.sh
|
||||
```
|
||||
|
||||
This script will automatically:
|
||||
- Start Meilisearch in Docker (on port 7700)
|
||||
- Start headless Chrome in Docker (on port 9222)
|
||||
- Install dependencies with `pnpm install` if needed
|
||||
- Start both the web app and workers in parallel
|
||||
- Provide cleanup when you stop with Ctrl+C
|
||||
|
||||
**Prerequisites:**
|
||||
- Docker installed and running
|
||||
- pnpm installed (see manual setup below for installation instructions)
|
||||
|
||||
The script will output the running services:
|
||||
- Web app: http://localhost:3000
|
||||
- Meilisearch: http://localhost:7700
|
||||
- Chrome debugger: http://localhost:9222
|
||||
|
||||
Press Ctrl+C to stop all services and clean up Docker containers.
|
||||
|
||||
## Manual Setup
|
||||
|
||||
Karakeep uses `node` version 22. To install it, you can use `nvm` [^1]
|
||||
@@ -88,14 +114,44 @@ The worker app will automatically start headless chrome on startup for crawling
|
||||
|
||||
- Run `pnpm workers` in the root of the repo.
|
||||
|
||||
### iOS Mobile App
|
||||
### Mobile App (iOS & Android)
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
To build and run the mobile app locally, you'll need:
|
||||
|
||||
- **For iOS development**:
|
||||
- macOS computer
|
||||
- Xcode installed from the App Store
|
||||
- iOS Simulator (comes with Xcode)
|
||||
|
||||
- **For Android development**:
|
||||
- Android Studio installed
|
||||
- Android SDK configured
|
||||
- Android Emulator or physical device
|
||||
|
||||
For detailed setup instructions, refer to the [Expo documentation](https://docs.expo.dev/guides/local-app-development/).
|
||||
|
||||
#### Running the app
|
||||
|
||||
- `cd apps/mobile`
|
||||
- `pnpm exec expo prebuild --no-install` to build the app.
|
||||
- Start the ios simulator.
|
||||
|
||||
**For iOS:**
|
||||
- `pnpm exec expo run:ios`
|
||||
- The app will be installed and started in the simulator.
|
||||
|
||||
**Troubleshooting iOS Setup:**
|
||||
If you encounter an error like `xcrun: error: SDK "iphoneos" cannot be located`, you may need to set the correct Xcode developer directory:
|
||||
```bash
|
||||
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
|
||||
```
|
||||
|
||||
**For Android:**
|
||||
- Start the Android emulator or connect a physical device.
|
||||
- `pnpm exec expo run:android`
|
||||
- The app will be installed and started on the emulator/device.
|
||||
|
||||
Changing the code will hot reload the app. However, installing new packages requires restarting the expo server.
|
||||
|
||||
### Browser Extension
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
- The database schema lives in `packages/db/schema.ts`.
|
||||
- Changing the schema, requires a migration.
|
||||
- You can generate the migration by running `pnpm run db:generate` in the root dir.
|
||||
- You can generate the migration by running `pnpm run db:generate --name description_of_schema_change` in the root dir.
|
||||
- You can then apply the migration by running `pnpm run db:migrate`.
|
||||
|
||||
## Drizzle Studio
|
||||
|
||||
28
start-dev.sh
28
start-dev.sh
@@ -50,9 +50,33 @@ if [ ! -d "node_modules" ]; then
|
||||
pnpm install
|
||||
fi
|
||||
|
||||
# Start the web app and workers in parallel
|
||||
echo "Starting web app and workers..."
|
||||
# Get DATA_DIR from environment or .env file
|
||||
if [ -z "$DATA_DIR" ] && [ -f ".env" ]; then
|
||||
DATA_DIR=$(grep "^DATA_DIR=" .env | cut -d'=' -f2)
|
||||
fi
|
||||
|
||||
# Create DATA_DIR if it doesn't exist
|
||||
if [ -n "$DATA_DIR" ] && [ ! -d "$DATA_DIR" ]; then
|
||||
echo "Creating DATA_DIR at $DATA_DIR..."
|
||||
mkdir -p "$DATA_DIR"
|
||||
fi
|
||||
|
||||
# Start the web app
|
||||
echo "Starting web app..."
|
||||
pnpm web & WEB_PID=$!
|
||||
|
||||
# Wait for web app to be ready
|
||||
echo "Waiting for web app to start..."
|
||||
until curl -s http://localhost:3000 > /dev/null 2>&1; do
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Run database migrations
|
||||
echo "Running database migrations..."
|
||||
pnpm run db:migrate
|
||||
|
||||
# Start workers
|
||||
echo "Starting workers..."
|
||||
pnpm workers & WORKERS_PID=$!
|
||||
|
||||
# Function to handle script termination
|
||||
|
||||
Reference in New Issue
Block a user