diff options
author | crupest <crupest@outlook.com> | 2021-03-18 22:06:18 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-03-18 22:06:18 +0800 |
commit | 9e749b05ad4c20eb69ec4ac6fe8ae83f94ad871a (patch) | |
tree | 19024c5fa955ba8f58504d7961e9964a317d0b49 /FrontEnd/src/app/views/timeline-common/MarkdownPostEdit.tsx | |
parent | 5d41c3aa6d90b2f3892dfe76fd6a0072b76fc4d5 (diff) | |
parent | e46f97d5b1a7573909c88b87e7cf2298e1c5dfa0 (diff) | |
download | timeline-9e749b05ad4c20eb69ec4ac6fe8ae83f94ad871a.tar.gz timeline-9e749b05ad4c20eb69ec4ac6fe8ae83f94ad871a.tar.bz2 timeline-9e749b05ad4c20eb69ec4ac6fe8ae83f94ad871a.zip |
Merge pull request #387 from crupest/markdown-edit
Markdown edit enhancement!
Diffstat (limited to 'FrontEnd/src/app/views/timeline-common/MarkdownPostEdit.tsx')
-rw-r--r-- | FrontEnd/src/app/views/timeline-common/MarkdownPostEdit.tsx | 197 |
1 files changed, 197 insertions, 0 deletions
diff --git a/FrontEnd/src/app/views/timeline-common/MarkdownPostEdit.tsx b/FrontEnd/src/app/views/timeline-common/MarkdownPostEdit.tsx new file mode 100644 index 00000000..fb38d2f7 --- /dev/null +++ b/FrontEnd/src/app/views/timeline-common/MarkdownPostEdit.tsx @@ -0,0 +1,197 @@ +import React from "react"; +import { Form } from "react-bootstrap"; +import { useTranslation } from "react-i18next"; +import { Prompt } from "react-router"; + +import { getHttpTimelineClient, HttpTimelinePostInfo } from "@/http/timeline"; + +import FlatButton from "../common/FlatButton"; +import TabPages from "../common/TabPages"; +import TimelinePostBuilder from "@/services/TimelinePostBuilder"; +import ConfirmDialog from "../common/ConfirmDialog"; + +export interface MarkdownPostEditProps { + timeline: string; + onPosted: (post: HttpTimelinePostInfo) => void; + onPostError: () => void; + onClose: () => void; + className?: string; + style?: React.CSSProperties; +} + +const MarkdownPostEdit: React.FC<MarkdownPostEditProps> = ({ + timeline: timelineName, + onPosted, + onClose, + onPostError, + className, + style, +}) => { + const { t } = useTranslation(); + + const [canLeave, setCanLeave] = React.useState<boolean>(true); + + const [process, setProcess] = React.useState<boolean>(false); + + const [ + showLeaveConfirmDialog, + setShowLeaveConfirmDialog, + ] = React.useState<boolean>(false); + + const [text, _setText] = React.useState<string>(""); + const [images, _setImages] = React.useState<{ file: File; url: string }[]>( + [] + ); + const [previewHtml, _setPreviewHtml] = React.useState<string>(""); + + const _builder = React.useRef<TimelinePostBuilder | null>(null); + + const getBuilder = (): TimelinePostBuilder => { + if (_builder.current == null) { + const builder = new TimelinePostBuilder(() => { + setCanLeave(builder.isEmpty); + _setText(builder.text); + _setImages(builder.images); + _setPreviewHtml(builder.renderHtml()); + }); + _builder.current = builder; + } + return _builder.current; + }; + + React.useEffect(() => { + return () => { + getBuilder().dispose(); + }; + }, []); + + React.useEffect(() => { + window.onbeforeunload = () => { + if (!canLeave) { + return t("timeline.confirmLeave"); + } + }; + + return () => { + window.onbeforeunload = null; + }; + }, [canLeave, t]); + + const send = async (): Promise<void> => { + setProcess(true); + try { + const dataList = await getBuilder().build(); + const post = await getHttpTimelineClient().postPost(timelineName, { + dataList, + }); + onPosted(post); + onClose(); + } catch (e) { + setProcess(false); + onPostError(); + } + }; + + return ( + <> + <Prompt when={!canLeave} message={t("timeline.confirmLeave")} /> + <TabPages + className={className} + style={style} + pageContainerClassName="py-2" + actions={ + <> + <FlatButton + className="mr-2" + variant="danger" + onClick={() => { + if (canLeave) { + onClose(); + } else { + setShowLeaveConfirmDialog(true); + } + }} + > + {t("operationDialog.cancel")} + </FlatButton> + <FlatButton onClick={send} disabled={canLeave}> + {t("timeline.send")} + </FlatButton> + </> + } + pages={[ + { + id: "text", + tabText: "edit", + page: ( + <Form.Control + as="textarea" + value={text} + disabled={process} + onChange={(event) => { + getBuilder().setMarkdownText(event.currentTarget.value); + }} + /> + ), + }, + { + id: "images", + tabText: "image", + page: ( + <div className="timeline-markdown-post-edit-page"> + {images.map((image, index) => ( + <div + key={image.url} + className="timeline-markdown-post-edit-image-container" + > + <img + src={image.url} + className="timeline-markdown-post-edit-image" + /> + <i + className="bi-trash text-danger icon-button timeline-markdown-post-edit-image-delete-button" + onClick={() => { + getBuilder().deleteImage(index); + }} + /> + </div> + ))} + <Form.File + label={t("chooseImage")} + accept="image/jpeg,image/jpg,image/png,image/gif,image/webp" + onChange={(event: React.ChangeEvent<HTMLInputElement>) => { + const { files } = event.currentTarget; + if (files != null && files.length !== 0) { + getBuilder().appendImage(files[0]); + } + }} + disabled={process} + /> + </div> + ), + }, + { + id: "preview", + tabText: "preview", + page: ( + <div + className="markdown-container timeline-markdown-post-edit-page" + dangerouslySetInnerHTML={{ __html: previewHtml }} + /> + ), + }, + ]} + /> + {showLeaveConfirmDialog && ( + <ConfirmDialog + onClose={() => setShowLeaveConfirmDialog(false)} + onConfirm={onClose} + title="timeline.dropDraft" + body="timeline.confirmLeave" + /> + )} + </> + ); +}; + +export default MarkdownPostEdit; |