Skip to content
Merged
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
4 changes: 4 additions & 0 deletions packages/pluggableWidgets/rich-text-web/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

### Added

- We added support to change the form orientation of the link, image and video modals

## [4.8.1] - 2025-07-29

### Fixed
Expand Down
8 changes: 8 additions & 0 deletions packages/pluggableWidgets/rich-text-web/src/RichText.xml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@
<enumerationValue key="readPanel">Read panel</enumerationValue>
</enumerationValues>
</property>
<property key="formOrientation" type="enumeration" defaultValue="horizontal" required="true">
<caption>Form orientation</caption>
<description>The form orientation for modals (Insert link, Insert image, Insert video).</description>
<enumerationValues>
<enumerationValue key="horizontal">Horizontal</enumerationValue>
<enumerationValue key="vertical">Vertical</enumerationValue>
</enumerationValues>
</property>
</propertyGroup>
<propertyGroup caption="Visibility">
<systemProperty key="Visibility" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ describe("Rich Text", () => {
minHeight: 75,
OverflowY: "auto",
customFonts: [],
enableDefaultUpload: true
enableDefaultUpload: true,
formOrientation: "vertical"
};
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { type VideoFormType } from "../ModalDialog/VideoDialog";
import { Delta } from "quill/core";
import { IMG_MIME_TYPES } from "./constants";
import Emitter from "quill/core/emitter";
import { RichTextContainerProps } from "typings/RichTextProps";

type ModalReturnType = {
showDialog: boolean;
Expand All @@ -23,7 +24,13 @@ type ModalReturnType = {
customImageUploadHandler(value: any): void;
};

export function useEmbedModal(ref: MutableRefObject<Quill | null>): ModalReturnType {
export function useEmbedModal(
ref: MutableRefObject<Quill | null>,
props: Pick<
RichTextContainerProps,
"imageSource" | "imageSourceContent" | "enableDefaultUpload" | "formOrientation"
>
): ModalReturnType {
const [showDialog, setShowDialog] = useState<boolean>(false);
const [dialogConfig, setDialogConfig] = useState<ChildDialogProps>({});
const openDialog = (): void => {
Expand Down Expand Up @@ -51,7 +58,8 @@ export function useEmbedModal(ref: MutableRefObject<Quill | null>): ModalReturnT
closeDialog();
},
onClose: closeDialog,
defaultValue: { ...value, text }
defaultValue: { ...value, text },
formOrientation: props.formOrientation
}
});
openDialog();
Expand Down Expand Up @@ -113,7 +121,8 @@ export function useEmbedModal(ref: MutableRefObject<Quill | null>): ModalReturnT
},
onClose: closeDialog,
selection: ref.current?.getSelection(),
defaultValue: { ...value }
defaultValue: { ...value },
formOrientation: props.formOrientation
}
});
openDialog();
Expand All @@ -136,7 +145,8 @@ export function useEmbedModal(ref: MutableRefObject<Quill | null>): ModalReturnT
}
closeDialog();
},
onClose: closeDialog
onClose: closeDialog,
formOrientation: props.formOrientation
}
});
openDialog();
Expand Down Expand Up @@ -186,7 +196,11 @@ export function useEmbedModal(ref: MutableRefObject<Quill | null>): ModalReturnT
closeDialog();
},
onClose: closeDialog,
defaultValue: { ...value }
defaultValue: { ...value },
formOrientation: props.formOrientation,
imageSource: props.imageSource,
imageSourceContent: props.imageSourceContent,
enableDefaultUpload: props.enableDefaultUpload
}
});
openDialog();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export interface EditorProps
defaultValue?: string;
onTextChange?: (...args: [delta: Delta, oldContent: Delta, source: EmitterSource]) => void;
onSelectionChange?: (...args: [range: Range, oldRange: Range, source: EmitterSource]) => void;
formOrientation: "horizontal" | "vertical";
theme: string;
style?: CSSProperties;
className?: string;
Expand Down Expand Up @@ -65,7 +66,7 @@ const Editor = forwardRef((props: EditorProps, ref: MutableRefObject<Quill | nul
customVideoHandler,
customViewCodeHandler,
customImageUploadHandler
} = useEmbedModal(ref);
} = useEmbedModal(ref, props);
const customIndentHandler = getIndentHandler(ref);

// quill instance is not changing, thus, the function reference has to stays.
Expand Down Expand Up @@ -213,9 +214,6 @@ const Editor = forwardRef((props: EditorProps, ref: MutableRefObject<Quill | nul
isOpen={showDialog}
onOpenChange={open => setShowDialog(open)}
parentNode={modalRef.current?.ownerDocument.body}
imageSource={props.imageSource}
imageSourceContent={props.imageSourceContent}
enableDefaultUpload={props.enableDefaultUpload}
{...dialogConfig}
></Dialog>
</Fragment>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ function EditorWrapperInner(props: EditorWrapperProps): ReactElement {
tabIndex,
imageSource,
imageSourceContent,
enableDefaultUpload
enableDefaultUpload,
formOrientation
} = props;

const globalState = useContext(EditorContext);
Expand Down Expand Up @@ -215,6 +216,7 @@ function EditorWrapperInner(props: EditorWrapperProps): ReactElement {
imageSource={imageSource}
imageSourceContent={imageSourceContent}
enableDefaultUpload={enableDefaultUpload}
formOrientation={formOrientation}
/>
</div>
{enableStatusBar && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import {
useRole
} from "@floating-ui/react";
import { If } from "@mendix/widget-plugin-component-kit/If";
import classNames from "classnames";
import { createElement, Fragment, ReactElement } from "react";
import LinkDialog, { LinkDialogProps } from "./LinkDialog";
import VideoDialog, { VideoDialogProps } from "./VideoDialog";
import ViewCodeDialog, { ViewCodeDialogProps } from "./ViewCodeDialog";
import ImageDialog, { ImageDialogProps } from "./ImageDialog";
import "./Dialog.scss";
import { RichTextContainerProps } from "../../../typings/RichTextProps";

interface BaseDialogProps {
isOpen: boolean;
Expand Down Expand Up @@ -49,15 +49,13 @@ export type ChildDialogProps =
| ViewCodeDialogBaseProps
| ImageDialogBaseProps;

export type DialogProps = BaseDialogProps &
ChildDialogProps &
Pick<RichTextContainerProps, "imageSource" | "imageSourceContent" | "enableDefaultUpload">;
export type DialogProps = BaseDialogProps & ChildDialogProps;

/**
* Dialog components that will be shown on toolbar's button
*/
export default function Dialog(props: DialogProps): ReactElement {
const { isOpen, onOpenChange, dialogType, config, imageSource, imageSourceContent, enableDefaultUpload } = props;
const { isOpen, onOpenChange, dialogType, config } = props;
const { refs, context } = useFloating({
open: isOpen,
onOpenChange
Expand All @@ -81,7 +79,9 @@ export default function Dialog(props: DialogProps): ReactElement {
></FloatingOverlay>
<FloatingFocusManager context={context}>
<div
className="Dialog mx-layoutgrid widget-rich-text"
className={classNames("Dialog mx-layoutgrid widget-rich-text", {
"form-vertical": config?.formOrientation === "vertical"
})}
ref={refs.setFloating}
aria-labelledby={dialogType}
aria-describedby={dialogType}
Expand All @@ -97,12 +97,7 @@ export default function Dialog(props: DialogProps): ReactElement {
<ViewCodeDialog {...(config as ViewCodeDialogProps)}></ViewCodeDialog>
</If>
<If condition={dialogType === "image"}>
<ImageDialog
imageSource={imageSource}
imageSourceContent={imageSourceContent}
enableDefaultUpload={enableDefaultUpload}
{...(config as ImageDialogProps)}
></ImageDialog>
<ImageDialog {...(config as ImageDialogProps)}></ImageDialog>
</If>
</div>
</FloatingFocusManager>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,21 @@ interface PropsWithChildrenWithClass extends PropsWithChildren {
className?: string;
}

export function DialogContent(props: PropsWithChildrenWithClass): ReactElement {
const { children, className } = props;
export interface DialogContentProps extends PropsWithChildrenWithClass {
formOrientation: "horizontal" | "vertical";
}

export function DialogContent(props: DialogContentProps): ReactElement {
const { children, className, formOrientation } = props;

return (
<div 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",
{ "form-vertical": formOrientation === "vertical" },
className
)}
>
<div className="modal-content mx-window-content">{children}</div>
</div>
);
Expand All @@ -36,24 +46,55 @@ export function DialogHeader(props: DialogHeaderProps): ReactElement {
);
}

export function DialogBody(props: PropsWithChildrenWithClass): ReactElement {
const { children, className } = props;
export interface DialogBodyProps extends PropsWithChildrenWithClass {
formOrientation: "horizontal" | "vertical";
}

export function DialogBody(props: DialogBodyProps): ReactElement {
const { children, className, formOrientation } = props;

return <div className={classNames("widget-rich-text-modal-content form-horizontal", className)}>{children}</div>;
return (
<div
className={classNames(
"widget-rich-text-modal-content",
{
"form-vertical": formOrientation === "vertical",
"form-horizontal": formOrientation !== "vertical"
},
className
)}
>
{children}
</div>
);
}

export interface FormControlProps extends PropsWithChildrenWithClass {
label?: string;
formOrientation: "horizontal" | "vertical";
inputId?: string;
}

export function FormControl(props: FormControlProps): ReactElement {
const { children, className, label } = props;
const { children, className, label, formOrientation, inputId } = props;

return (
<If condition={children !== undefined && children !== null}>
<div className={classNames("form-group", className)}>
{label && <label className="control-label col-sm-3">{label}</label>}
<div className={`col-sm-${label ? "9" : "12"}`}> {children}</div>
<div className={classNames("form-group", { "no-columns": formOrientation === "vertical" }, className)}>
{label && (
<label
htmlFor={inputId}
id={`${inputId}-label`}
className={classNames("control-label", { "col-sm-3": formOrientation !== "vertical" })}
>
{label}
</label>
)}
{formOrientation === "vertical" ? (
children
) : (
<div className={`col-sm-${label ? "9" : "12"}`}>{children}</div>
)}
</div>
</If>
);
Expand Down
Loading
Loading