Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fbcb291b9a | ||
| b7ec975fb0 | |||
|
|
3f5d5dec98 | ||
| c7b6e8f5f0 | |||
|
|
f709998c50 | ||
| ddcda65c1f | |||
| 6e32bf302a |
@@ -1,15 +1,23 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Outlet, useLocation } from 'react-router-dom';
|
import { Outlet, useLocation } from 'react-router-dom';
|
||||||
import { useCompactNav } from '../hooks/useCompactNav';
|
import { useCompactNav, useIsCompactListRoute } from '../hooks/useCompactNav';
|
||||||
import { MobileSwipeGestureHost } from './mobile/MobileSwipeGestureHost';
|
import { MobileSwipeGestureHost } from './mobile/MobileSwipeGestureHost';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper for Outlet that adds route-based animation
|
* Outlet wrapper for route remounts + compact swipe-back chrome.
|
||||||
* Forces remount on route change by using location as key
|
*
|
||||||
|
* On compact list routes the parent PageRoot already omits children. If this
|
||||||
|
* outlet is still mounted (stale stack / empty SpaceIndexRedirect), render
|
||||||
|
* nothing so an empty swipe shell cannot sit above the channel list and eat taps.
|
||||||
*/
|
*/
|
||||||
export function AnimatedOutlet() {
|
export function AnimatedOutlet() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const compact = useCompactNav();
|
const compact = useCompactNav();
|
||||||
|
const isListRoute = useIsCompactListRoute();
|
||||||
|
|
||||||
|
if (compact && isListRoute) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MobileSwipeGestureHost>
|
<MobileSwipeGestureHost>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { ReactNode, useCallback, useEffect, useRef } from 'react';
|
import React, { ReactNode, useCallback, useEffect, useRef } from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
import { useCompactNav } from '../../hooks/useCompactNav';
|
import { useCompactNav } from '../../hooks/useCompactNav';
|
||||||
import { useBackRoute } from '../../hooks/useBackRoute';
|
import { useBackRoute } from '../../hooks/useBackRoute';
|
||||||
import { startReplyToEvent } from '../../features/room/replyToMessage';
|
import { startReplyToEvent } from '../../features/room/replyToMessage';
|
||||||
@@ -93,6 +94,7 @@ const resetElementTransform = (el: HTMLElement | null) => {
|
|||||||
|
|
||||||
export function MobileSwipeGestureHost({ children }: MobileSwipeGestureHostProps) {
|
export function MobileSwipeGestureHost({ children }: MobileSwipeGestureHostProps) {
|
||||||
const compact = useCompactNav();
|
const compact = useCompactNav();
|
||||||
|
const { pathname } = useLocation();
|
||||||
const { canGoBack, goBack } = useBackRoute();
|
const { canGoBack, goBack } = useBackRoute();
|
||||||
const backEnabled = compact && canGoBack;
|
const backEnabled = compact && canGoBack;
|
||||||
const replyEnabled = compact;
|
const replyEnabled = compact;
|
||||||
@@ -350,7 +352,7 @@ export function MobileSwipeGestureHost({ children }: MobileSwipeGestureHostProps
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
resetGesture();
|
resetGesture();
|
||||||
}, [canGoBack, resetGesture]);
|
}, [canGoBack, pathname, resetGesture]);
|
||||||
|
|
||||||
if (!compact) {
|
if (!compact) {
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { style } from '@vanilla-extract/css';
|
import { globalStyle, style } from '@vanilla-extract/css';
|
||||||
import { color, config, toRem } from 'folds';
|
import { color, config, toRem } from 'folds';
|
||||||
|
|
||||||
export const SwipeBackRoot = style({
|
export const SwipeBackRoot = style({
|
||||||
@@ -36,7 +36,10 @@ export const CompactNavUnderlay = style({
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
zIndex: 0,
|
zIndex: 0,
|
||||||
pointerEvents: 'none',
|
// Keep underlay tappable if the detail layer is empty/transparent
|
||||||
|
// (e.g. space index after leaving DMs). Opaque detail content still
|
||||||
|
// sits above and receives taps first.
|
||||||
|
pointerEvents: 'auto',
|
||||||
});
|
});
|
||||||
|
|
||||||
export const CompactDetailLayer = style({
|
export const CompactDetailLayer = style({
|
||||||
@@ -45,6 +48,19 @@ export const CompactDetailLayer = style({
|
|||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
|
// Pass through taps when outlet content is null/empty; real pages
|
||||||
|
// re-enable pointer events on their root via the child rule below.
|
||||||
|
pointerEvents: 'none',
|
||||||
|
});
|
||||||
|
|
||||||
|
globalStyle(`${CompactDetailLayer} > *`, {
|
||||||
|
pointerEvents: 'auto',
|
||||||
|
flex: 1,
|
||||||
|
minHeight: 0,
|
||||||
|
minWidth: 0,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
backgroundColor: color.Background.Container,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const SwipeToReplyLayer = style({
|
export const SwipeToReplyLayer = style({
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ export function PageRoot({ nav, children }: PageRootProps) {
|
|||||||
const showCompactMaster = useShowCompactMasterView();
|
const showCompactMaster = useShowCompactMasterView();
|
||||||
const compact = screenSize === ScreenSize.Mobile;
|
const compact = screenSize === ScreenSize.Mobile;
|
||||||
|
|
||||||
|
// Master list routes: only the channel/space list is interactive.
|
||||||
|
// Never mount the detail/underlay stack here — an empty detail layer would
|
||||||
|
// sit above the list with pointer-events and freeze taps (DM → space bug).
|
||||||
if (compact && showCompactMaster) {
|
if (compact && showCompactMaster) {
|
||||||
return (
|
return (
|
||||||
<Box grow="Yes" className={ContainerColor({ variant: 'Background' })}>
|
<Box grow="Yes" className={ContainerColor({ variant: 'Background' })}>
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import { useRoomCreators } from '../../hooks/useRoomCreators';
|
|||||||
import { activeThreadIdAtomFamily } from '../../state/activeThread';
|
import { activeThreadIdAtomFamily } from '../../state/activeThread';
|
||||||
import { ThreadView } from './ThreadView';
|
import { ThreadView } from './ThreadView';
|
||||||
import { MobileSwipeToReplyLayer } from '../../components/mobile/MobileSwipeToReplyLayer';
|
import { MobileSwipeToReplyLayer } from '../../components/mobile/MobileSwipeToReplyLayer';
|
||||||
|
import { useMobileKeyboardLayout } from '../../hooks/useMobileKeyboardLayout';
|
||||||
|
|
||||||
const FN_KEYS_REGEX = /^F\d+$/;
|
const FN_KEYS_REGEX = /^F\d+$/;
|
||||||
const shouldFocusMessageField = (evt: KeyboardEvent): boolean => {
|
const shouldFocusMessageField = (evt: KeyboardEvent): boolean => {
|
||||||
@@ -63,6 +64,7 @@ export function RoomView({ room, eventId }: { room: Room; eventId?: string }) {
|
|||||||
const roomViewRef = useRef<HTMLDivElement>(null);
|
const roomViewRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
|
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
|
||||||
|
const { isLandscape, keyboardOpen } = useMobileKeyboardLayout();
|
||||||
|
|
||||||
const { roomId } = room;
|
const { roomId } = room;
|
||||||
const editor = useEditor();
|
const editor = useEditor();
|
||||||
@@ -101,9 +103,12 @@ export function RoomView({ room, eventId }: { room: Room; eventId?: string }) {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const composerPadding = isLandscape ? '0' : `0 ${config.space.S400}`;
|
||||||
|
const showHeader = !keyboardOpen;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page ref={roomViewRef}>
|
<Page ref={roomViewRef}>
|
||||||
<RoomViewHeader />
|
{showHeader && <RoomViewHeader />}
|
||||||
{activeThreadId ? (
|
{activeThreadId ? (
|
||||||
<ThreadView room={room} threadRootId={activeThreadId} />
|
<ThreadView room={room} threadRootId={activeThreadId} />
|
||||||
) : (
|
) : (
|
||||||
@@ -121,7 +126,7 @@ export function RoomView({ room, eventId }: { room: Room; eventId?: string }) {
|
|||||||
</Box>
|
</Box>
|
||||||
</MobileSwipeToReplyLayer>
|
</MobileSwipeToReplyLayer>
|
||||||
<Box shrink="No" direction="Column" data-disable-swipe-back="true">
|
<Box shrink="No" direction="Column" data-disable-swipe-back="true">
|
||||||
<div style={{ padding: `0 ${config.space.S400}` }}>
|
<div style={{ padding: composerPadding }}>
|
||||||
{tombstoneEvent ? (
|
{tombstoneEvent ? (
|
||||||
<RoomTombstone
|
<RoomTombstone
|
||||||
roomId={roomId}
|
roomId={roomId}
|
||||||
|
|||||||
30
overlay/src/app/hooks/useCompactNav.ts
Normal file
30
overlay/src/app/hooks/useCompactNav.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { matchPath, useLocation } from 'react-router-dom';
|
||||||
|
import { DIRECT_PATH, EXPLORE_PATH, HOME_PATH, INBOX_PATH, SPACE_PATH } from '../pages/paths';
|
||||||
|
import { ScreenSize, useScreenSizeContext } from './useScreenSize';
|
||||||
|
|
||||||
|
/** Skinny window layout (≤750px): master-detail instead of 3-column desktop. */
|
||||||
|
export const useCompactNav = (): boolean => {
|
||||||
|
const screenSize = useScreenSizeContext();
|
||||||
|
return screenSize === ScreenSize.Mobile;
|
||||||
|
};
|
||||||
|
|
||||||
|
const LIST_ROUTE_PATHS = [HOME_PATH, DIRECT_PATH, SPACE_PATH, EXPLORE_PATH, INBOX_PATH] as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Routes where sidebar + channel list are shown together (no room/content pane).
|
||||||
|
* Uses matchPath on the current pathname so trailing-slash / encoding quirks
|
||||||
|
* after DM → space navigations still count as master list routes.
|
||||||
|
*/
|
||||||
|
export const useIsCompactListRoute = (): boolean => {
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
|
return LIST_ROUTE_PATHS.some(
|
||||||
|
(path) => matchPath({ path, caseSensitive: true, end: true }, pathname) != null
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useShowCompactMasterView = (): boolean => {
|
||||||
|
const compact = useCompactNav();
|
||||||
|
const isListRoute = useIsCompactListRoute();
|
||||||
|
return compact && isListRoute;
|
||||||
|
};
|
||||||
67
overlay/src/app/hooks/useMobileKeyboardLayout.ts
Normal file
67
overlay/src/app/hooks/useMobileKeyboardLayout.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
const KEYBOARD_HEIGHT_THRESHOLD_PX = 120;
|
||||||
|
|
||||||
|
const getIsLandscape = (): boolean => {
|
||||||
|
if (typeof window === 'undefined') return false;
|
||||||
|
if (window.matchMedia?.('(orientation: landscape)').matches) return true;
|
||||||
|
return window.innerWidth > window.innerHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getKeyboardOpen = (): boolean => {
|
||||||
|
if (typeof window === 'undefined') return false;
|
||||||
|
|
||||||
|
const viewport = window.visualViewport;
|
||||||
|
if (viewport) {
|
||||||
|
const obscured = window.innerHeight - viewport.height - viewport.offsetTop;
|
||||||
|
if (obscured > KEYBOARD_HEIGHT_THRESHOLD_PX) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const active = document.activeElement;
|
||||||
|
if (!(active instanceof HTMLElement)) return false;
|
||||||
|
if (active.isContentEditable) return true;
|
||||||
|
const tag = active.tagName;
|
||||||
|
return tag === 'INPUT' || tag === 'TEXTAREA';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MobileKeyboardLayout = {
|
||||||
|
isLandscape: boolean;
|
||||||
|
keyboardOpen: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Landscape + soft-keyboard layout signals for compact room chrome.
|
||||||
|
* Uses visualViewport when available (Capacitor/Android WebView).
|
||||||
|
*/
|
||||||
|
export const useMobileKeyboardLayout = (): MobileKeyboardLayout => {
|
||||||
|
const [isLandscape, setIsLandscape] = useState(getIsLandscape);
|
||||||
|
const [keyboardOpen, setKeyboardOpen] = useState(getKeyboardOpen);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const sync = () => {
|
||||||
|
setIsLandscape(getIsLandscape());
|
||||||
|
setKeyboardOpen(getKeyboardOpen());
|
||||||
|
};
|
||||||
|
|
||||||
|
sync();
|
||||||
|
|
||||||
|
const orientationQuery = window.matchMedia?.('(orientation: landscape)');
|
||||||
|
orientationQuery?.addEventListener?.('change', sync);
|
||||||
|
window.addEventListener('resize', sync);
|
||||||
|
window.visualViewport?.addEventListener('resize', sync);
|
||||||
|
window.visualViewport?.addEventListener('scroll', sync);
|
||||||
|
window.addEventListener('focusin', sync);
|
||||||
|
window.addEventListener('focusout', sync);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
orientationQuery?.removeEventListener?.('change', sync);
|
||||||
|
window.removeEventListener('resize', sync);
|
||||||
|
window.visualViewport?.removeEventListener('resize', sync);
|
||||||
|
window.visualViewport?.removeEventListener('scroll', sync);
|
||||||
|
window.removeEventListener('focusin', sync);
|
||||||
|
window.removeEventListener('focusout', sync);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { isLandscape, keyboardOpen };
|
||||||
|
};
|
||||||
57
overlay/src/app/hooks/useViewTransitions.ts
Normal file
57
overlay/src/app/hooks/useViewTransitions.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { useCallback, useEffect } from 'react';
|
||||||
|
import { useLocation, useNavigate, NavigateOptions } from 'react-router-dom';
|
||||||
|
import { supportsViewTransitions, withViewTransition } from '../utils/viewTransitions';
|
||||||
|
|
||||||
|
export const useViewTransitions = () => {
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!supportsViewTransitions()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.documentElement.classList.add('view-transitions-enabled');
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.documentElement.classList.remove('view-transitions-enabled');
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Route changed
|
||||||
|
}, [location]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
supportsViewTransitions: supportsViewTransitions(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate helper. On Capacitor / coarse-pointer, View Transitions are disabled
|
||||||
|
* so DM ↔ space switches cannot leave a stuck hit-blocking overlay.
|
||||||
|
*/
|
||||||
|
export const useNavigateWithTransition = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
(to: string | number, options?: NavigateOptions) => {
|
||||||
|
if (!supportsViewTransitions()) {
|
||||||
|
if (typeof to === 'number') {
|
||||||
|
navigate(to);
|
||||||
|
} else {
|
||||||
|
navigate(to, options);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
withViewTransition(() => {
|
||||||
|
if (typeof to === 'number') {
|
||||||
|
navigate(to);
|
||||||
|
} else {
|
||||||
|
navigate(to, options);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[navigate]
|
||||||
|
);
|
||||||
|
};
|
||||||
21
overlay/src/app/pages/client/ClientLayout.tsx
Normal file
21
overlay/src/app/pages/client/ClientLayout.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import React, { ReactNode } from 'react';
|
||||||
|
import { Box } from 'folds';
|
||||||
|
import { DockedCallPanel } from '../../features/call/DockedCallPanel';
|
||||||
|
import { useMobileKeyboardLayout } from '../../hooks/useMobileKeyboardLayout';
|
||||||
|
|
||||||
|
type ClientLayoutProps = {
|
||||||
|
nav: ReactNode;
|
||||||
|
children: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ClientLayout({ nav, children }: ClientLayoutProps) {
|
||||||
|
const { keyboardOpen } = useMobileKeyboardLayout();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box grow="Yes">
|
||||||
|
{!keyboardOpen && <Box shrink="No">{nav}</Box>}
|
||||||
|
<Box grow="Yes">{children}</Box>
|
||||||
|
<DockedCallPanel />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
81
overlay/src/app/utils/viewTransitions.ts
Normal file
81
overlay/src/app/utils/viewTransitions.ts
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import { isCapacitorNative } from './tauri';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View Transitions can leave an invisible hit-blocking overlay on Android
|
||||||
|
* WebView after DM ↔ space navigations. Keep them off for native/touch shells.
|
||||||
|
*/
|
||||||
|
const shouldDisableViewTransitions = (): boolean => {
|
||||||
|
if (typeof window === 'undefined') return true;
|
||||||
|
if (isCapacitorNative()) return true;
|
||||||
|
if (window.matchMedia?.('(pointer: coarse)').matches) return true;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const supportsViewTransitions = (): boolean => {
|
||||||
|
if (shouldDisableViewTransitions()) return false;
|
||||||
|
return 'startViewTransition' in document;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const withViewTransition = async (callback: () => void | Promise<void>): Promise<void> => {
|
||||||
|
if (!supportsViewTransitions()) {
|
||||||
|
await callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const doc = document as Document & {
|
||||||
|
startViewTransition: (callback: () => void | Promise<void>) => {
|
||||||
|
finished: Promise<void>;
|
||||||
|
updateCallbackDone: Promise<void>;
|
||||||
|
ready: Promise<void>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const transition = doc.startViewTransition(async () => {
|
||||||
|
await callback();
|
||||||
|
});
|
||||||
|
|
||||||
|
await transition.finished;
|
||||||
|
} catch {
|
||||||
|
await callback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Global link interception for View Transitions. No-op on Capacitor / touch.
|
||||||
|
* Also fights hash-router navigations — leave disabled on mobile.
|
||||||
|
*/
|
||||||
|
export const enableViewTransitionsForNavigation = (): void => {
|
||||||
|
if (!supportsViewTransitions()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleLinkClick = (event: MouseEvent) => {
|
||||||
|
const target = (event.target as HTMLElement).closest('a');
|
||||||
|
|
||||||
|
if (!target) return;
|
||||||
|
if (target.target === '_blank') return;
|
||||||
|
if (target.origin !== window.location.origin) return;
|
||||||
|
if (event.metaKey || event.ctrlKey || event.shiftKey) return;
|
||||||
|
|
||||||
|
const url = new URL(target.href);
|
||||||
|
|
||||||
|
if (url.origin === window.location.origin) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
withViewTransition(() => {
|
||||||
|
window.history.pushState({}, '', target.href);
|
||||||
|
window.dispatchEvent(new PopStateEvent('popstate'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePopState = () => {
|
||||||
|
withViewTransition(() => {
|
||||||
|
// URL already changed
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('click', handleLinkClick);
|
||||||
|
window.addEventListener('popstate', handlePopState);
|
||||||
|
};
|
||||||
@@ -521,3 +521,53 @@ html.android-capacitor body {
|
|||||||
word-spacing: normal;
|
word-spacing: normal;
|
||||||
font-variant-ligatures: normal;
|
font-variant-ligatures: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Mobile: hide all scrollbars (touch scrolling still works) */
|
||||||
|
@media (max-width: 750px) {
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar,
|
||||||
|
*::-webkit-scrollbar-thumb,
|
||||||
|
*::-webkit-scrollbar-track,
|
||||||
|
*::-webkit-scrollbar-corner {
|
||||||
|
display: none;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twilight-theme ::-webkit-scrollbar,
|
||||||
|
.twilight-theme ::-webkit-scrollbar-thumb,
|
||||||
|
.twilight-theme ::-webkit-scrollbar-track,
|
||||||
|
.mocha-theme ::-webkit-scrollbar,
|
||||||
|
.mocha-theme ::-webkit-scrollbar-thumb,
|
||||||
|
.mocha-theme ::-webkit-scrollbar-track {
|
||||||
|
display: none;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html.android-capacitor,
|
||||||
|
html.android-capacitor *,
|
||||||
|
html.android-capacitor *::before,
|
||||||
|
html.android-capacitor *::after {
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.android-capacitor *::-webkit-scrollbar,
|
||||||
|
html.android-capacitor *::-webkit-scrollbar-thumb,
|
||||||
|
html.android-capacitor *::-webkit-scrollbar-track,
|
||||||
|
html.android-capacitor *::-webkit-scrollbar-corner {
|
||||||
|
display: none;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "paarrot",
|
"name": "paarrot",
|
||||||
"version": "4.11.117",
|
"version": "4.11.120",
|
||||||
"description": "Paarrot - A Matrix client based on Cinny",
|
"description": "Paarrot - A Matrix client based on Cinny",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user