Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

markdown editor v6 #9708

Merged
merged 9 commits into from
Nov 12, 2024
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { MarkdownEditorModeContext } from 'views/components/MarkdownEditor/Markd
import { NullComponent } from 'views/components/MarkdownEditor/NullComponent';
import { useDeviceProfile } from 'views/components/MarkdownEditor/useDeviceProfile';
import { MarkdownEditorMethods } from 'views/components/MarkdownEditor/useMarkdownEditorMethods';
import { useMobileKeyboardResizeHandler } from 'views/components/MarkdownEditor/useMobileKeyboardResizeHandler';
import { DragIndicator } from './indicators/DragIndicator';
import { UploadIndicator } from './indicators/UploadIndicator';
import './MarkdownEditor.scss';
Expand Down Expand Up @@ -119,6 +120,7 @@ export const MarkdownEditor = memo(function MarkdownEditor(
const mdxEditorRef: MutableRefObject<MDXEditorMethods | null> = useRef(null);

const imageUploadHandlerDelegate = useImageUploadHandler(imageHandler);
useMobileKeyboardResizeHandler(mode);

/**
* When we've stopped dragging, we also need to decrement the drag counter.
Expand Down Expand Up @@ -321,6 +323,14 @@ export const MarkdownEditor = memo(function MarkdownEditor(
[disabled],
);

const handleFocus = useCallback(() => {
setActive(true);
}, []);

const handleBlur = useCallback(() => {
setActive(false);
}, []);

return (
<MarkdownEditorModeContext.Provider value={mode}>
<MarkdownEditorContext.Provider value={mdxEditorMethods}>
Expand All @@ -340,8 +350,8 @@ export const MarkdownEditor = memo(function MarkdownEditor(
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onPaste={(event) => handlePaste(event)}
onFocus={() => setActive(true)}
onBlur={() => setActive(false)}
onFocus={handleFocus}
onBlur={handleBlur}
onMouseEnter={() => setHovering(true)}
onMouseLeave={() => setHovering(false)}
autoFocus={autoFocus}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
}
}

.CustomLinkDialogForDesktopPopover .CustomLinkEdit {
gap: 8px;
}

.CustomLinkDialogForDesktopPopover input {
min-width: 25em;
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const CustomLinkEdit = () => {
return (
<div className="CustomLinkEdit">
<input
type="text"
type="url"
value={link}
style={{ flexGrow: 1 }}
autoFocus={true}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@
.CustomLinkDialogForDesktopPopover .MainLink {
margin-right: 16px;
}

.CustomLinkDialogForDesktopPopover .CustomLinkPreview {
gap: 8px;
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
.BlockSelectorButton {
display: flex;
gap: 2px;
button {
outline: none;
border: none;
cursor: pointer;
}
.DropdownIndicator {
display: flex;
gap: 2px;
}
}

.FormattingPopover {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,11 @@ export const BlockSelectorButton = (props: BlockSelectorButtonProps) => {
return (
<ClickAwayListener onClickAway={handleClickAway}>
<div className="BlockSelectorButton">
<button onClick={handleClick}>
<div className="DropdownIndicator">
{iconName && <CWIcon iconName={iconName} />}
{!iconName && <PlaceholderIcon />}
<CWIcon iconName="caretDown" iconSize="xs" />
</div>
<button onClick={handleClick} style={{ display: 'flex' }}>
burtonator marked this conversation as resolved.
Show resolved Hide resolved
{iconName && <CWIcon iconName={iconName} />}
{!iconName && <PlaceholderIcon />}

<CWIcon iconName="caretDown" iconSize="xs" />
</button>

<CWPopover
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { AnchorType } from 'views/components/component_kit/new_designs/CWPopover
import { CWTooltip } from 'views/components/component_kit/new_designs/CWTooltip';
import { TooltipProps } from 'views/components/component_kit/new_designs/CWTooltip/CWTooltip';

function nullRenderTrigger() {
// noop render trigger that does nothing on mobile.
}

/**
* Functions just like CWTooltip but only activates tooltip when the editor
* is in desktop mode. Otherwise, tooltips can popup on mobile and that is
Expand All @@ -13,6 +17,12 @@ export const EditorTooltip = (props: TooltipProps) => {
const { renderTrigger } = props;
const mode = useMarkdownEditorMode();

if (mode === 'mobile') {
// don't use the CWTooltip on mobile to avoid issues with rendering on
// Safari mobile
return renderTrigger(nullRenderTrigger);
}

return (
<CWTooltip
{...props}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
cursor: pointer;
border: none;
color: $neutral-500;
padding: 0;
}

.IconAndText {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
@import '../../../../../styles/shared';

.FormatButton {
}
burtonator marked this conversation as resolved.
Show resolved Hide resolved

.FormatButtonActive {
font-weight: bold;
background-color: $neutral-100;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const FormatButton = (props: HeadingButtonProps) => {
content={`Change to ${formatName}`}
renderTrigger={(handleInteraction) => (
<CWIconButton
className={clsx({ FormatButtonActive: active })}
className={clsx({ FormatButton: true, FormatButtonActive: active })}
burtonator marked this conversation as resolved.
Show resolved Hide resolved
buttonSize="lg"
iconName={formatToIconName(format)}
onMouseEnter={handleInteraction}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ export const HeadingButton = (props: HeadingButtonProps) => {
case 'quote':
convertSelectionToNode(() => $createQuoteNode());
break;
case 'p':
convertSelectionToNode(() => $createParagraphNode());
break;
}
} else {
convertSelectionToNode(() => $createParagraphNode());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const IMAGE_ACCEPT =
'.jpg, .jpeg, .png, .gif, .webp, .svg, .apng, .avif';

type ImageButtonProps = Readonly<{
onImage?: (file: File) => void;
onImage: (file: File) => void;
text?: string;
}>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
outline: none;
border: none;
cursor: pointer;
padding: 0;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { TableButton } from 'views/components/MarkdownEditor/toolbars/TableButto
import './ToolbarForDesktop.scss';

type ToolbarForDesktopProps = Readonly<{
onImage?: (file: File) => void;
onImage: (file: File) => void;
focus: () => void;
}>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
.ToolbarForMobile {
display: flex;
flex-grow: 1;
column-gap: 4px;

* {
margin-top: auto;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
Separator,
useCellValues,
} from 'commonwealth-mdxeditor';
import React, { ReactNode, useCallback, useEffect } from 'react';
import React, { ReactNode, useCallback } from 'react';

import { CustomLinkDialogForMobile } from 'views/components/MarkdownEditor/customLinkDialog/CustomLinkDialogForMobile';
import { BlockSelectorButton } from 'views/components/MarkdownEditor/toolbars/BlockSelectorButton';
Expand All @@ -21,45 +21,20 @@ type ToolbarForMobileProps = Readonly<{
SubmitButton?: () => ReactNode;
focus: () => void;

onImage?: (file: File) => void;
onImage: (file: File) => void;
}>;

export const ToolbarForMobile = (props: ToolbarForMobileProps) => {
const { SubmitButton, focus, onImage } = props;

const [linkDialogState] = useCellValues(linkDialogState$);

const adjustForKeyboard = useCallback(() => {
if (!window.visualViewport) {
return;
}

const height = Math.floor(window.visualViewport.height);

const root = document.getElementById('root');

if (root) {
root.style.maxHeight = `${height}px`;
}
}, []);

useEffect(() => {
adjustForKeyboard();

// Adjust whenever the window resizes (e.g., when the keyboard appears)
window.addEventListener('resize', adjustForKeyboard);

return () => {
window.removeEventListener('resize', adjustForKeyboard);
};
}, [adjustForKeyboard]);

const preventKeyboardDeactivation = useCallback(
(event: React.MouseEvent) => {
event.stopPropagation();

if (focus) {
focus?.();
focus();
} else {
console.warn('No focus');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ export type DeviceProfile = 'mobile' | 'desktop';
export type DeviceOrientation = 'vertical' | 'horizontal';

function useDeviceOrientation(): DeviceOrientation {
return [0, 180].includes(window.screen.orientation.angle)
? 'vertical'
: 'horizontal';
if (window.innerHeight > window.innerWidth) return 'vertical';
else return 'horizontal';
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { useCallback, useEffect, useRef } from 'react';
import { MarkdownEditorMode } from 'views/components/MarkdownEditor/MarkdownEditor';

/**
* Do the actual keyboard resize now.
*/
export function resizeRootElementForKeyboard() {
burtonator marked this conversation as resolved.
Show resolved Hide resolved
if (!window.visualViewport) {
console.warn('No visual viewport.');
return;
}

const height = Math.floor(window.visualViewport.height);

const elementToResize = document.getElementById('root');

if (elementToResize) {
elementToResize.style.maxHeight = `${height}px`;
}
}

/**
* Because Safari and iOS have different keyboard handling mechanisms, we have
* to listen for window resize (the keyboard becoming active) using
* requestAnimationFrame.
*
* - Safari does NOT call the resize event when the keyboard is made active.
* - Safari resizes the keyboard AFTER onMouseUp, so we can't listen for it
* that way either.
*
* Animation frames run 60x per second, and only when the UI repaints.
*
* This means that there will be no excessive power usage.
*
* We also, correctly , handle the component being shut down so there won't be
* errant background animation frames.
*/
export const useMobileKeyboardResizeHandler = (mode: MarkdownEditorMode) => {
const animationTaskRef = useRef<number | undefined>(undefined);

const listenForResize = useCallback(() => {
resizeRootElementForKeyboard();
animationTaskRef.current = requestAnimationFrame(listenForResize);
}, []);

useEffect(() => {
if (mode !== 'mobile') {
// do nothing whatsoever on mobile.
return;
}

// start the first resize, then continually call itself using
// requestAnimationFrame
listenForResize();

return () => {
if (animationTaskRef.current !== undefined) {
// if there's an animation frame scheduled, we have to cancel it now
// otherwise, it will just keep running forever.
cancelAnimationFrame(animationTaskRef.current);
}
};
}, [listenForResize, mode]);
};
Loading