Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import LottieSplashScreen from 'react-native-lottie-splash-screen';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { Provider as PaperProvider } from 'react-native-paper';
import * as Notifications from 'expo-notifications';
import { KeyboardProvider } from 'react-native-keyboard-controller';

import AppErrorBoundary, {
ErrorFallback,
Expand All @@ -30,7 +31,6 @@ Notifications.setNotificationHandler({
},
});


const App = () => {
const state = useInitDatabase();

Expand All @@ -48,14 +48,16 @@ const App = () => {
<Suspense fallback={null}>
<GestureHandlerRootView style={styles.flex}>
<AppErrorBoundary>
<SafeAreaProvider>
<PaperProvider>
<BottomSheetModalProvider>
<StatusBar translucent={true} backgroundColor="transparent" />
<Main />
</BottomSheetModalProvider>
</PaperProvider>
</SafeAreaProvider>
<KeyboardProvider>
<SafeAreaProvider>
<PaperProvider>
<BottomSheetModalProvider>
<StatusBar translucent={true} backgroundColor="transparent" />
<Main />
</BottomSheetModalProvider>
</PaperProvider>
</SafeAreaProvider>
</KeyboardProvider>
</AppErrorBoundary>
</GestureHandlerRootView>
</Suspense>
Expand Down
181 changes: 181 additions & 0 deletions android/app/src/main/assets/js/textRemover.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// Text selection functionality
window.textRemover = new (function () {
let selectionUI = null;
let isUIActive = false;
this.hidden = van.state(true);

function createSelectionUI() {
if (selectionUI) return selectionUI;

const { div, button } = van.tags;
selectionUI = div(
{
id: 'text-selection-ui',
style:
'position: fixed; background: var(--theme-surface); border: 1px solid var(--theme-outline); border-radius: 8px; padding: 8px; z-index: 100000; display: none; box-shadow: 0 4px 12px rgba(0,0,0,0.25); pointer-events: auto;',
},
button(
{
style:
'background: var(--theme-secondary); color: var(--theme-onSecondary); border: none; padding: 6px 12px; margin: 2px; border-radius: 4px; font-size: 12px; cursor: pointer;',
onclick: () => removeSelectedText(),
},
'Remove',
),
button(
{
style:
'background: var(--theme-primary); color: var(--theme-onPrimary); border: none; padding: 6px 12px; margin: 2px; border-radius: 4px; font-size: 12px; cursor: pointer;',
onclick: () => replaceSelectedText(),
},
'Replace',
),
);

document.body.appendChild(selectionUI);
return selectionUI;
}

function showSelectionUI() {
const ui = createSelectionUI();

// Get selection bounds
const selection = window.getSelection();
if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
const rect = range.getBoundingClientRect();

// Get UI element heights from CSS variables (with fallbacks)
const statusBarHeight =
parseInt(
getComputedStyle(document.documentElement).getPropertyValue(
'--StatusBar-currentHeight',
),
) || 24;
const readerPadding =
parseInt(
getComputedStyle(document.documentElement).getPropertyValue(
'--readerSettings-padding',
),
) || 16;
const uiHeight = 50; // Approximate height of our UI

// Calculate available space
const viewportHeight = window.innerHeight;
const selectionCenterY = rect.top + rect.height / 2;
const topSafeArea = statusBarHeight + readerPadding + uiHeight + 10;
const bottomSafeArea = readerPadding + uiHeight + 10;

// Position UI based on selection location
let topPosition;
if (selectionCenterY < viewportHeight / 2) {
// Selection is in top half, position UI at bottom
//TODO: make this dynamic
const avoidUI = !reader.hidden.val ? 58 : 0;
const avoidScrollbar = reader.generalSettings.val.verticalSeekbar
? 0
: 20;
topPosition =
viewportHeight - bottomSafeArea - avoidUI - avoidScrollbar;
ui.style.top = topPosition + 'px';
ui.style.bottom = 'auto';
} else {
// Selection is in bottom half, position UI at top (accounting for status bar)
topPosition = Math.max(
statusBarHeight + readerPadding + 10,
statusBarHeight + 20,
);
const avoidUI = !reader.hidden.val ? 32 : 0;
ui.style.top = topPosition + avoidUI + 'px';
ui.style.bottom = 'auto';
}

// Center horizontally
ui.style.left = '50%';
ui.style.transform = 'translateX(-50%)';
} else {
// Fallback: position at top if no selection rect available
ui.style.top = '20px';
ui.style.left = '50%';
ui.style.transform = 'translateX(-50%)';
ui.style.bottom = 'auto';
}

ui.style.display = 'block';
isUIActive = true;
}

function hideSelectionUI() {
if (selectionUI) {
selectionUI.style.display = 'none';
}
isUIActive = false;
}

function getSelectedText() {
const selection = window.getSelection();
if (selection.rangeCount > 0) {
return selection.toString().trim();
}
return '';
}

function removeSelectedText() {
const selectedText = getSelectedText();
if (selectedText) {
reader.post({
type: 'text-action',
data: { remove: selectedText },
});
}
hideSelectionUI();
window.getSelection().removeAllRanges();
}

function replaceSelectedText() {
const selectedText = getSelectedText();
if (selectedText) {
// For replace, we need user input, so send a different message
reader.post({
type: 'text-action',
data: { replace: selectedText },
});
}
hideSelectionUI();
window.getSelection().removeAllRanges();
}

// Handle text selection
document.addEventListener('selectionchange', function () {
const selectedText = getSelectedText();
if (selectedText) {
showSelectionUI();
} else if (!isUIActive) {
hideSelectionUI();
}
});

// Hide UI when clicking/tapping elsewhere
document.addEventListener('touchstart', function (e) {
if (isUIActive && selectionUI && !selectionUI.contains(e.target)) {
const selectedText = getSelectedText();
if (!selectedText) {
hideSelectionUI();
}
}
});

document.addEventListener('click', function (e) {
if (isUIActive && selectionUI && !selectionUI.contains(e.target)) {
const selectedText = getSelectedText();
if (!selectedText) {
hideSelectionUI();
}
}
});

// Hide UI on scroll
window.addEventListener('scroll', function () {
hideSelectionUI();
});
})();
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
"react-native-error-boundary": "^2.0.0",
"react-native-file-access": "^3.2.0",
"react-native-gesture-handler": "^2.30.0",
"react-native-keyboard-controller": "^1.20.7",
"react-native-lottie-splash-screen": "^1.1.2",
"react-native-mmkv": "^3.3.3",
"react-native-pager-view": "^6.9.1",
Expand Down
17 changes: 17 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 18 additions & 1 deletion src/components/Common.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,27 @@ import { View, StyleSheet } from 'react-native';
const Row = ({
children,
style = {},
horizontalSpacing,
verticalSpacing,
}: {
children?: React.ReactNode;
style?: any;
}) => <View style={[styles.row, style]}>{children}</View>;
horizontalSpacing?: number | `${number}%`;
verticalSpacing?: number | `${number}%`;
}) => (
<View
style={[
styles.row,
style,
{
paddingHorizontal: horizontalSpacing,
paddingVertical: verticalSpacing,
},
]}
>
{children}
</View>
);

export { Row };

Expand Down
3 changes: 3 additions & 0 deletions src/components/Common/ToggleButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ interface ToggleButtonProps {
theme: ThemeColors;
color?: string;
onPress: () => void;
disabled?: boolean;
}

export const ToggleButton: React.FC<ToggleButtonProps> = ({
Expand All @@ -36,6 +37,7 @@ export const ToggleButton: React.FC<ToggleButtonProps> = ({
theme,
color,
onPress,
disabled,
}) => (
<View style={styles.toggleButtonContainer}>
<Pressable
Expand All @@ -45,6 +47,7 @@ export const ToggleButton: React.FC<ToggleButtonProps> = ({
getToggleButtonPressableStyle(selected, theme),
]}
onPress={onPress}
disabled={disabled}
>
<MaterialCommunityIcons
name={icon}
Expand Down
Loading
Loading