aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd/src/app/views/timeline-common/TimelinePostEdit.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'FrontEnd/src/app/views/timeline-common/TimelinePostEdit.tsx')
-rw-r--r--FrontEnd/src/app/views/timeline-common/TimelinePostEdit.tsx156
1 files changed, 118 insertions, 38 deletions
diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePostEdit.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePostEdit.tsx
index 5bc5b166..6c428b74 100644
--- a/FrontEnd/src/app/views/timeline-common/TimelinePostEdit.tsx
+++ b/FrontEnd/src/app/views/timeline-common/TimelinePostEdit.tsx
@@ -17,13 +17,40 @@ import { base64 } from "@/http/common";
import BlobImage from "../common/BlobImage";
import LoadingButton from "../common/LoadingButton";
+import { PopupMenu } from "../common/Menu";
+
+interface TimelinePostEditTextProps {
+ text: string;
+ disabled: boolean;
+ onChange: (text: string) => void;
+ className?: string;
+ style?: React.CSSProperties;
+}
+
+const TimelinePostEditText: React.FC<TimelinePostEditTextProps> = (props) => {
+ const { text, disabled, onChange, className, style } = props;
+
+ return (
+ <Form.Control
+ as="textarea"
+ value={text}
+ disabled={disabled}
+ onChange={(event) => {
+ onChange(event.target.value);
+ }}
+ className={className}
+ style={style}
+ />
+ );
+};
interface TimelinePostEditImageProps {
onSelect: (file: File | null) => void;
+ disabled: boolean;
}
const TimelinePostEditImage: React.FC<TimelinePostEditImageProps> = (props) => {
- const { onSelect } = props;
+ const { onSelect, disabled } = props;
const { t } = useTranslation();
@@ -41,12 +68,19 @@ const TimelinePostEditImage: React.FC<TimelinePostEditImageProps> = (props) => {
}
};
+ React.useEffect(() => {
+ return () => {
+ onSelect(null);
+ };
+ }, [onSelect]);
+
return (
<>
<Form.File
label={t("chooseImage")}
onChange={onInputChange}
accept="image/*"
+ disabled={disabled}
className="mx-3 my-1 d-inline-block"
/>
{file != null && !error && (
@@ -65,6 +99,14 @@ const TimelinePostEditImage: React.FC<TimelinePostEditImageProps> = (props) => {
);
};
+type PostKind = "text" | "markdown" | "image";
+
+const postKindIconClassNameMap: Record<PostKind, string> = {
+ text: "bi-fonts",
+ markdown: "bi-markdown",
+ image: "bi-image",
+};
+
export interface TimelinePostEditProps {
className?: string;
timeline: HttpTimelineInfo;
@@ -78,19 +120,27 @@ const TimelinePostEdit: React.FC<TimelinePostEditProps> = (props) => {
const { t } = useTranslation();
const [process, setProcess] = React.useState<boolean>(false);
- const [kind, setKind] = React.useState<"text" | "image">("text");
+
+ const [kind, setKind] = React.useState<PostKind>("text");
+
const [text, setText] = React.useState<string>("");
+ const [markdown, setMarkdown] = React.useState<string>("");
const [image, setImage] = React.useState<File | null>(null);
- const draftLocalStorageKey = `timeline.${timeline.name}.postDraft`;
+ const draftTextLocalStorageKey = `timeline.${timeline.name}.postDraft.text`;
+ const draftMarkdownLocalStorageKey = `timeline.${timeline.name}.postDraft.markdown`;
React.useEffect(() => {
- setText(window.localStorage.getItem(draftLocalStorageKey) ?? "");
- }, [draftLocalStorageKey]);
+ setText(window.localStorage.getItem(draftTextLocalStorageKey) ?? "");
+ setMarkdown(
+ window.localStorage.getItem(draftMarkdownLocalStorageKey) ?? ""
+ );
+ }, [draftTextLocalStorageKey, draftMarkdownLocalStorageKey]);
const canSend =
(kind === "text" && text.length !== 0) ||
- (kind === "image" && image != null);
+ (kind === "image" && image != null) ||
+ (kind === "markdown" && markdown.length !== 0);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const containerRef = React.useRef<HTMLDivElement>(null!);
@@ -102,9 +152,7 @@ const TimelinePostEdit: React.FC<TimelinePostEditProps> = (props) => {
};
React.useEffect(() => {
- if (onHeightChange) {
- onHeightChange(containerRef.current.clientHeight);
- }
+ notifyHeightChange();
return () => {
if (onHeightChange) {
onHeightChange(0);
@@ -112,11 +160,6 @@ const TimelinePostEdit: React.FC<TimelinePostEditProps> = (props) => {
};
});
- const toggleKind = React.useCallback(() => {
- setKind((oldKind) => (oldKind === "text" ? "image" : "text"));
- setImage(null);
- }, []);
-
const onSend = async (): Promise<void> => {
setProcess(true);
@@ -128,6 +171,12 @@ const TimelinePostEdit: React.FC<TimelinePostEditProps> = (props) => {
data: await base64(new Blob([text])),
};
break;
+ case "markdown":
+ requestData = {
+ contentType: "text/markdown",
+ data: await base64(new Blob([markdown])),
+ };
+ break;
case "image":
if (image == null) {
throw new UiLogicError(
@@ -151,7 +200,10 @@ const TimelinePostEdit: React.FC<TimelinePostEditProps> = (props) => {
(data) => {
if (kind === "text") {
setText("");
- window.localStorage.removeItem(draftLocalStorageKey);
+ window.localStorage.removeItem(draftTextLocalStorageKey);
+ } else if (kind === "markdown") {
+ setMarkdown("");
+ window.localStorage.removeItem(draftMarkdownLocalStorageKey);
}
setProcess(false);
setKind("text");
@@ -174,32 +226,60 @@ const TimelinePostEdit: React.FC<TimelinePostEditProps> = (props) => {
>
<Row>
<Col className="px-1 py-1">
- {kind === "text" ? (
- <Form.Control
- as="textarea"
- className="w-100 h-100 timeline-post-edit"
- value={text}
- disabled={process}
- onChange={(event: React.ChangeEvent<HTMLTextAreaElement>) => {
- const value = event.currentTarget.value;
- setText(value);
- window.localStorage.setItem(draftLocalStorageKey, value);
- }}
- />
- ) : (
- <TimelinePostEditImage onSelect={setImage} />
- )}
+ {(() => {
+ if (kind === "text") {
+ return (
+ <TimelinePostEditText
+ className="w-100 h-100 timeline-post-edit"
+ text={text}
+ disabled={process}
+ onChange={(t) => {
+ setText(t);
+ window.localStorage.setItem(draftTextLocalStorageKey, t);
+ }}
+ />
+ );
+ } else if (kind === "image") {
+ return (
+ <TimelinePostEditImage onSelect={setImage} disabled={process} />
+ );
+ } else if (kind === "markdown") {
+ return (
+ <TimelinePostEditText
+ className="w-100 h-100 timeline-post-edit"
+ text={markdown}
+ disabled={process}
+ onChange={(t) => {
+ setMarkdown(t);
+ window.localStorage.setItem(
+ draftMarkdownLocalStorageKey,
+ t
+ );
+ }}
+ />
+ );
+ }
+ })()}
</Col>
<Col xs="auto" className="align-self-end m-1">
<div className="d-block text-center mt-1 mb-2">
- <i
- onLoad={notifyHeightChange}
- className={clsx(
- kind === "text" ? "bi-image" : "bi-card-text",
- "icon-button"
- )}
- onClick={process ? undefined : toggleKind}
- />
+ <PopupMenu
+ items={(["text", "image", "markdown"] as const).map((kind) => ({
+ type: "button",
+ text: `timeline.post.type.${kind}`,
+ iconClassName: postKindIconClassNameMap[kind],
+ onClick: () => {
+ setKind(kind);
+ },
+ }))}
+ >
+ <i
+ className={clsx(
+ postKindIconClassNameMap[kind],
+ "icon-button large"
+ )}
+ />
+ </PopupMenu>
</div>
<LoadingButton
variant="primary"