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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ and this project adheres to
- 🥅(frontend) intercept 401 error on GET threads #1754
- 🦺(frontend) check content type pdf on PdfBlock #1756
- ✈️(frontend) pause Posthog when offline #1755
- 📱(frontend) toolbar to the bottom when mobile #1774

### Fixed

Expand Down
9 changes: 6 additions & 3 deletions src/frontend/apps/impress/src/core/AppProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,17 @@ export function AppProvider({ children }: { children: React.ReactNode }) {
const { theme } = useCunninghamTheme();
const { replace } = useRouter();

const initializeResizeListener = useResponsiveStore(
(state) => state.initializeResizeListener,
);
const { initializeResizeListener, initializeInputDetection } =
useResponsiveStore();

useEffect(() => {
return initializeResizeListener();
}, [initializeResizeListener]);

useEffect(() => {
return initializeInputDetection();
}, [initializeInputDetection]);

/**
* Update the global router replace function
* This allows us to use the router replace function globally
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
import { FormattingToolbarExtension } from '@blocknote/core/extensions';
import {
ExperimentalMobileFormattingToolbarController,
FormattingToolbar,
FormattingToolbarController,
blockTypeSelectItems,
getFormattingToolbarItems,
useBlockNoteEditor,
useDictionary,
useExtensionState,
} from '@blocknote/react';
import React, { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';

import { Box } from '@/components';
import { useConfig } from '@/core/config/api';
import { useResponsiveStore } from '@/stores';

import {
DocsBlockSchema,
DocsInlineContentSchema,
DocsStyleSchema,
} from '../../types';
import { CommentToolbarButton } from '../comments/CommentToolbarButton';
import { getCalloutFormattingToolbarItems } from '../custom-blocks';

Expand All @@ -24,6 +35,7 @@ export const BlockNoteToolbar = () => {
const [onConfirm, setOnConfirm] = useState<() => void | Promise<void>>();
const { t } = useTranslation();
const { data: conf } = useConfig();
const { isTablet, isInputTouch } = useResponsiveStore();

const toolbarItems = useMemo(() => {
let toolbarItems = getFormattingToolbarItems([
Expand Down Expand Up @@ -84,7 +96,13 @@ export const BlockNoteToolbar = () => {

return (
<>
<FormattingToolbarController formattingToolbar={formattingToolbar} />
{isInputTouch && isTablet ? (
<MobileFormattingToolbarController
formattingToolbar={formattingToolbar}
/>
) : (
<FormattingToolbarController formattingToolbar={formattingToolbar} />
)}
{confirmOpen && (
<ModalConfirmDownloadUnsafe
onClose={() => setIsConfirmOpen(false)}
Expand All @@ -94,3 +112,38 @@ export const BlockNoteToolbar = () => {
</>
);
};

const MobileFormattingToolbarController = ({
formattingToolbar,
}: {
formattingToolbar: () => React.ReactNode;
}) => {
const editor = useBlockNoteEditor<
DocsBlockSchema,
DocsInlineContentSchema,
DocsStyleSchema
>();
const show = useExtensionState(FormattingToolbarExtension, {
editor,
});

if (!show) {
return null;
}

return (
<Box
$position="absolute"
$css={`
& > div {
left: 50%;
transform: translate(0px, 0px) scale(1) translateX(-50%)!important;
}
`}
>
<ExperimentalMobileFormattingToolbarController
formattingToolbar={formattingToolbar}
/>
</Box>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ export function MarkdownButton() {
};

const show = useMemo(() => {
return !!selectedBlocks.find((block) => block.content !== undefined);
return (
selectedBlocks.filter((block) => block.content !== undefined).length !== 0
);
}, [selectedBlocks]);

if (!show || !editor.isEditable || !Components) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,7 @@ interface PdfBlockComponentProps {
>;
}

const PdfBlockComponent = ({
editor,
block,
contentRef,
}: PdfBlockComponentProps) => {
const PdfBlockComponent = ({ editor, block }: PdfBlockComponentProps) => {
const pdfUrl = block.props.url;
const { i18n, t } = useTranslation();
const lang = i18n.resolvedLanguage;
Expand Down Expand Up @@ -114,27 +110,34 @@ const PdfBlockComponent = ({
void validatePDFContent();
}, [pdfUrl]);

if (isPDFContentLoading) {
return <Loading />;
}

if (!isPDFContentLoading && isPDFContent !== null && !isPDFContent) {
return (
<Box
$align="center"
$justify="center"
$color="#666"
$background="#f5f5f5"
$border="1px solid #ddd"
$height="300px"
$css={css`
text-align: center;
`}
$width="100%"
contentEditable={false}
onClick={() => editor.setTextCursorPosition(block)}
>
{t('Invalid or missing PDF file.')}
</Box>
);
}

return (
<Box ref={contentRef} className="bn-file-block-content-wrapper">
<>
<PDFBlockStyle />
{isPDFContentLoading && <Loading />}
{!isPDFContentLoading && isPDFContent !== null && !isPDFContent && (
<Box
$align="center"
$justify="center"
$color="#666"
$background="#f5f5f5"
$border="1px solid #ddd"
$height="300px"
$css={css`
text-align: center;
`}
contentEditable={false}
onClick={() => editor.setTextCursorPosition(block)}
>
{t('Invalid or missing PDF file.')}
</Box>
)}
<ResizableFileBlockWrapper
buttonIcon={
<Icon iconName="upload" $size="24px" $css="line-height: normal;" />
Expand All @@ -158,7 +161,7 @@ const PdfBlockComponent = ({
/>
)}
</ResizableFileBlockWrapper>
</Box>
</>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,9 @@ export const cssEditor = css`
& .bn-editor {
padding-right: 36px;
}
& .bn-toolbar {
max-width: 100vw;
}
}

@media screen and (width <= 560px) {
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/apps/impress/src/layouts/MainLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ const MainContent = ({
$css={css`
overflow-y: auto;
overflow-x: clip;
&:focus {
&:focus-visible {
outline: 3px solid ${colorsTokens['brand-400']};
outline-offset: -3px;
}
Expand Down
39 changes: 39 additions & 0 deletions src/frontend/apps/impress/src/stores/useResponsiveStore.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { create } from 'zustand';

export type ScreenSize = 'small-mobile' | 'mobile' | 'tablet' | 'desktop';
export type InputMethod = 'touch' | 'mouse' | 'unknown';

export interface UseResponsiveStore {
isMobile: boolean;
Expand All @@ -10,7 +11,11 @@ export interface UseResponsiveStore {
screenWidth: number;
setScreenSize: (size: ScreenSize) => void;
isDesktop: boolean;
isTouchCapable: boolean;
isInputTouch: boolean;
inputMethod: InputMethod;
initializeResizeListener: () => () => void;
initializeInputDetection: () => () => void;
}

const initialState = {
Expand All @@ -20,6 +25,9 @@ const initialState = {
isDesktop: false,
screenSize: 'desktop' as ScreenSize,
screenWidth: 0,
isTouchCapable: false,
isInputTouch: false,
inputMethod: 'unknown' as InputMethod,
};

export const useResponsiveStore = create<UseResponsiveStore>((set) => ({
Expand All @@ -29,6 +37,9 @@ export const useResponsiveStore = create<UseResponsiveStore>((set) => ({
isTablet: initialState.isTablet,
screenSize: initialState.screenSize,
screenWidth: initialState.screenWidth,
isTouchCapable: initialState.isTouchCapable,
isInputTouch: initialState.isInputTouch,
inputMethod: initialState.inputMethod,
setScreenSize: (size: ScreenSize) => set(() => ({ screenSize: size })),
initializeResizeListener: () => {
const resizeHandler = () => {
Expand Down Expand Up @@ -84,4 +95,32 @@ export const useResponsiveStore = create<UseResponsiveStore>((set) => ({
window.removeEventListener('resize', debouncedResizeHandler);
};
},
initializeInputDetection: () => {
// Detect if device has touch capability
const isTouchCapable =
'ontouchstart' in window ||
navigator.maxTouchPoints > 0 ||
// @ts-ignore - for older browsers
navigator.msMaxTouchPoints > 0;

set({ isTouchCapable });

// Track actual input method being used
const handleTouchStart = () => {
set({ inputMethod: 'touch', isInputTouch: true });
};

const handleMouseMove = () => {
set({ inputMethod: 'mouse', isInputTouch: false });
};

// Listen for first interaction to determine input method
window.addEventListener('touchstart', handleTouchStart, { once: false });
window.addEventListener('mousemove', handleMouseMove, { once: false });

return () => {
window.removeEventListener('touchstart', handleTouchStart);
window.removeEventListener('mousemove', handleMouseMove);
};
},
}));
Loading