Skip to content

Commit

Permalink
fix: design mode and embed video
Browse files Browse the repository at this point in the history
  • Loading branch information
gjulivan committed Sep 19, 2024
1 parent c706686 commit 9ff4687
Show file tree
Hide file tree
Showing 10 changed files with 288 additions and 378 deletions.
2 changes: 1 addition & 1 deletion packages/pluggableWidgets/rich-text-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
"rollup-preserve-directives": "^1.1.1"
},
"dependencies": {
"@radix-ui/react-dialog": "^1.1.1",
"@floating-ui/react": "^0.26.23",
"classnames": "^2.2.6",
"dompurify": "^2.5.0",
"linkifyjs": "^4.1.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { MutableRefObject } from "react";
import { Range } from "quill/core/selection";
import Keyboard, { Context } from "quill/modules/keyboard";
import { Scope } from "parchment";
import { Delta } from "quill/core";
import { AttributeMap, Delta } from "quill/core";
import CustomVideo from "../../utils/formats/video";
/**
* give custom indent handler to use our custom "indent-left" and "indent-right" formats (formats/indent.ts)
*/
Expand Down Expand Up @@ -66,3 +67,71 @@ export function enterKeyKeyboardHandler(this: Keyboard, range: Range, context: C
this.quill.format(name, context.format[name], Quill.sources.USER);
});
}

/**
* copied with modification from "handleBackspace" function in :
* https://github.com/slab/quill/blob/main/packages/quill/src/modules/keyboard.ts
*/
export function backspaceKeyboardHandler(this: Keyboard, range: Range, context: Context): any {
// Check for astral symbols
const length = /[\uD800-\uDBFF][\uDC00-\uDFFF]$/.test(context.prefix) ? 2 : 1;
if (range.index === 0 || this.quill.getLength() <= 1) {
const [line] = this.quill.getLine(range.index);
if (!(line instanceof CustomVideo)) {
return;
}
}
let formats = {};
const [line] = this.quill.getLine(range.index);
let delta = new Delta().retain(range.index - length).delete(length);
if (context.offset === 0) {
// Always deleting newline here, length always 1
const [prev] = this.quill.getLine(range.index - 1);
if (prev) {
const isPrevLineEmpty = prev.statics.blotName === "block" && prev.length() <= 1;
if (!isPrevLineEmpty) {
const curFormats = line.formats();
const prevFormats = this.quill.getFormat(range.index - 1, 1);
formats = AttributeMap.diff(curFormats, prevFormats) || {};
if (Object.keys(formats).length > 0) {
// line.length() - 1 targets \n in line, another -1 for newline being deleted
const formatDelta = new Delta().retain(range.index + line.length() - 2).retain(1, formats);
delta = delta.compose(formatDelta);
}
}
}
}
this.quill.updateContents(delta, Quill.sources.USER);
this.quill.focus();
}

/**
* copied with modification from "handleDelete" function in :
* https://github.com/slab/quill/blob/main/packages/quill/src/modules/keyboard.ts
*/
export function deleteKeyboardHandler(this: Keyboard, range: Range, context: Context): any {
// Check for astral symbols
const length = /^[\uD800-\uDBFF][\uDC00-\uDFFF]/.test(context.suffix) ? 2 : 1;
if (range.index >= this.quill.getLength() - length) {
const [line] = this.quill.getLine(range.index);
if (!(line instanceof CustomVideo)) {
return;
}
}
let formats = {};
const [line] = this.quill.getLine(range.index);
let delta = new Delta().retain(range.index).delete(length);
if (context.offset >= line.length() - 1) {
const [next] = this.quill.getLine(range.index + 1);
if (next) {
const curFormats = line.formats();
const nextFormats = this.quill.getFormat(range.index, 1);
formats = AttributeMap.diff(curFormats, nextFormats) || {};
if (Object.keys(formats).length > 0) {
delta = delta.retain(next.length() - 1).retain(1, formats);
}
}
}
this.quill.updateContents(delta, Quill.sources.USER);
this.quill.focus();
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ import Delta from "quill-delta";
import Dialog from "./ModalDialog/Dialog";
import "../utils/customPluginRegisters";
import { useEmbedModal } from "./CustomToolbars/useEmbedModal";
import { getIndentHandler, enterKeyKeyboardHandler } from "./CustomToolbars/toolbarHandlers";
import {
getIndentHandler,
enterKeyKeyboardHandler,
backspaceKeyboardHandler,
deleteKeyboardHandler
} from "./CustomToolbars/toolbarHandlers";
import MxQuill from "../utils/MxQuill";

export interface EditorProps {
Expand Down Expand Up @@ -75,6 +80,14 @@ const Editor = forwardRef((props: EditorProps, ref: MutableRefObject<Quill | nul
enter: {
key: "Enter",
handler: enterKeyKeyboardHandler
},
backspace: {
key: "Backspace",
handler: backspaceKeyboardHandler
},
delete: {
key: "Delete",
handler: deleteKeyboardHandler
}
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
justify-content: center;
align-items: center;
display: flex;
z-index: 105;
}

&-body {
&.modal-dialog {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 105;
}

&.view-code-dialog {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { If } from "@mendix/widget-plugin-component-kit/If";
import { createElement, ReactElement } from "react";
import * as RadixDialog from "@radix-ui/react-dialog";
import {
useFloating,
useDismiss,
useRole,
useClick,
useInteractions,
FloatingFocusManager,
FloatingOverlay,
FloatingPortal
} from "@floating-ui/react";
import "./Dialog.scss";
import LinkDialog, { LinkDialogProps } from "./LinkDialog";
import VideoDialog, { VideoDialogProps } from "./VideoDialog";
Expand Down Expand Up @@ -36,20 +45,44 @@ export type DialogProps = BaseDialogProps & ChildDialogProps;
*/
export default function Dialog(props: DialogProps): ReactElement {
const { isOpen, onOpenChange, dialogType, config } = props;
const { refs, context } = useFloating({
open: isOpen,
onOpenChange
});

const click = useClick(context);
const dismiss = useDismiss(context, {
outsidePressEvent: "mousedown"
});
const role = useRole(context);

const { getFloatingProps } = useInteractions([click, dismiss, role]);

return (
<RadixDialog.Root open={isOpen} onOpenChange={onOpenChange}>
<RadixDialog.Portal>
<RadixDialog.Overlay className="widget-rich-text-modal-overlay" />
<If condition={dialogType === "link"}>
<LinkDialog {...(config as LinkDialogProps)}></LinkDialog>
</If>
<If condition={dialogType === "video"}>
<VideoDialog {...(config as VideoDialogProps)}></VideoDialog>
</If>
<If condition={dialogType === "view-code"}>
<ViewCodeDialog {...(config as ViewCodeDialogProps)}></ViewCodeDialog>
</If>
</RadixDialog.Portal>
</RadixDialog.Root>
<FloatingPortal>
{isOpen && (
<FloatingOverlay lockScroll className="widget-rich-text-modal-overlay">
<FloatingFocusManager context={context}>
<div
className="Dialog"
ref={refs.setFloating}
aria-labelledby={dialogType}
aria-describedby={dialogType}
{...getFloatingProps()}
>
<If condition={dialogType === "link"}>
<LinkDialog {...(config as LinkDialogProps)}></LinkDialog>
</If>
<If condition={dialogType === "video"}>
<VideoDialog {...(config as VideoDialogProps)}></VideoDialog>
</If>
<If condition={dialogType === "view-code"}>
<ViewCodeDialog {...(config as ViewCodeDialogProps)}></ViewCodeDialog>
</If>
</div>
</FloatingFocusManager>
</FloatingOverlay>
)}
</FloatingPortal>
);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import classNames from "classnames";
import * as RadixDialog from "@radix-ui/react-dialog";
import { createElement, PropsWithChildren, ReactElement, Fragment } from "react";

interface PropsWithChildrenWithClass extends PropsWithChildren {
Expand All @@ -10,11 +9,9 @@ export function DialogContent(props: PropsWithChildrenWithClass): ReactElement {
const { children, className } = props;

return (
<RadixDialog.Content
className={classNames("widget-rich-text-modal-body modal-dialog mx-window mx-window-active", className)}
>
<div className={classNames("widget-rich-text-modal-body modal-dialog mx-window mx-window-active", className)}>
<div className="modal-content mx-window-content">{children}</div>
</RadixDialog.Content>
</div>
);
}

Expand All @@ -27,13 +24,13 @@ export function DialogHeader(props: DialogHeaderProps): ReactElement {

return (
<Fragment>
<RadixDialog.Title className={classNames("widget-rich-text-modal-header modal-header", className)}>
<div className={classNames("widget-rich-text-modal-header modal-header", className)}>
<button type="button" className="close" title="close" aria-label="close" onClick={onClose}>
×
</button>
<h4>{children}</h4>
</RadixDialog.Title>
<RadixDialog.Description className="hide">{children}</RadixDialog.Description>
</div>
<div className="hide">{children}</div>
</Fragment>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { If } from "@mendix/widget-plugin-component-kit/If";
import { DialogContent, DialogHeader, DialogBody, FormControl, DialogFooter } from "./DialogContent";
import classNames from "classnames";
import { type videoConfigType, type videoEmbedConfigType } from "../../utils/formats";
import { getPatternMatch } from "../../utils/videoUrlPattern";

export type VideoFormType = videoConfigType | videoEmbedConfigType;

Expand All @@ -26,7 +27,19 @@ function GeneralVideoDialog(props: VideoDialogProps): ReactElement {
});

const onInputChange = (e: ChangeEvent<HTMLInputElement | HTMLSelectElement>): void => {
setFormState({ ...formState, [e.target.name]: e.target.value });
if (e.target.name === "src") {
const pattern = getPatternMatch(e.target.value);
setFormState({
...formState,
...{
src: e.target.value,
width: pattern?.w || 560,
height: pattern?.h || 314
}
});
} else {
setFormState({ ...formState, [e.target.name]: e.target.value });
}
};

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Video from "quill/formats/video";
import { videoConfigType } from "../formats";
import { matchPattern } from "../videoUrlPattern";

/**
* custom video link handler, allowing width and height config
Expand All @@ -12,7 +13,8 @@ export default class CustomVideo extends Video {
static create(value: unknown): Element {
if ((value as videoConfigType)?.src !== undefined) {
const videoConfig = value as videoConfigType;
const node = super.create(videoConfig.src) as Element;
const urlPatterns = matchPattern(videoConfig.src);
const node = super.create(urlPatterns?.url || videoConfig.src) as Element;

if (videoConfig.width && videoConfig.width > 0) {
node.setAttribute("width", videoConfig.width.toString());
Expand Down
Loading

0 comments on commit 9ff4687

Please sign in to comment.