aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2021-03-18 21:17:12 +0800
committercrupest <crupest@outlook.com>2021-03-18 21:17:12 +0800
commitac9f1a2d563e3797d7164a442338997ff6ac47d5 (patch)
tree53d24d18e4cd0e034945273e19db47d8d0cf9915
parent2157ccdb22266781de271b302ddd1538498fcdc8 (diff)
downloadtimeline-ac9f1a2d563e3797d7164a442338997ff6ac47d5.tar.gz
timeline-ac9f1a2d563e3797d7164a442338997ff6ac47d5.tar.bz2
timeline-ac9f1a2d563e3797d7164a442338997ff6ac47d5.zip
feat: Prevent leave.
-rw-r--r--FrontEnd/src/app/locales/en/translation.json1
-rw-r--r--FrontEnd/src/app/locales/zh/translation.json1
-rw-r--r--FrontEnd/src/app/services/TimelinePostBuilder.ts4
-rw-r--r--FrontEnd/src/app/views/timeline-common/MarkdownPostEdit.tsx149
4 files changed, 90 insertions, 65 deletions
diff --git a/FrontEnd/src/app/locales/en/translation.json b/FrontEnd/src/app/locales/en/translation.json
index 5613668e..8fcd5bcd 100644
--- a/FrontEnd/src/app/locales/en/translation.json
+++ b/FrontEnd/src/app/locales/en/translation.json
@@ -67,6 +67,7 @@
"send": "Send",
"deletePostFailed": "Failed to delete post.",
"sendPostFailed": "Failed to send post.",
+ "confirmLeave": "Are you sure to leave? All content you typed would be lost.",
"visibility": {
"public": "public to everyone",
"register": "only registed people can see",
diff --git a/FrontEnd/src/app/locales/zh/translation.json b/FrontEnd/src/app/locales/zh/translation.json
index c73f2876..f03c4c03 100644
--- a/FrontEnd/src/app/locales/zh/translation.json
+++ b/FrontEnd/src/app/locales/zh/translation.json
@@ -67,6 +67,7 @@
"send": "发送",
"deletePostFailed": "删除消息失败。",
"sendPostFailed": "发送消息失败。",
+ "confirmLeave":"确定要离开吗?所有输入的内容将会丢失。",
"visibility": {
"public": "对所有人公开",
"register": "仅注册可见",
diff --git a/FrontEnd/src/app/services/TimelinePostBuilder.ts b/FrontEnd/src/app/services/TimelinePostBuilder.ts
index 92d8484b..40279eca 100644
--- a/FrontEnd/src/app/services/TimelinePostBuilder.ts
+++ b/FrontEnd/src/app/services/TimelinePostBuilder.ts
@@ -82,6 +82,10 @@ export default class TimelinePostBuilder {
return this._images;
}
+ get isEmpty(): boolean {
+ return this._text.length === 0 && this._images.length === 0;
+ }
+
renderHtml(): string {
return this._md.render(this._text);
}
diff --git a/FrontEnd/src/app/views/timeline-common/MarkdownPostEdit.tsx b/FrontEnd/src/app/views/timeline-common/MarkdownPostEdit.tsx
index d43077b4..ab6aafea 100644
--- a/FrontEnd/src/app/views/timeline-common/MarkdownPostEdit.tsx
+++ b/FrontEnd/src/app/views/timeline-common/MarkdownPostEdit.tsx
@@ -1,6 +1,7 @@
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";
@@ -26,6 +27,8 @@ const MarkdownPostEdit: React.FC<MarkdownPostEditProps> = ({
}) => {
const { t } = useTranslation();
+ const [canLeave, setCanLeave] = React.useState<boolean>(true);
+
const [process, setProcess] = React.useState<boolean>(false);
const [text, _setText] = React.useState<string>("");
@@ -39,6 +42,7 @@ const MarkdownPostEdit: React.FC<MarkdownPostEditProps> = ({
const getBuilder = (): TimelinePostBuilder => {
if (_builder.current == null) {
const builder = new TimelinePostBuilder(() => {
+ setCanLeave(builder.isEmpty);
_setText(builder.text);
_setImages(builder.images);
_setPreviewHtml(builder.renderHtml());
@@ -54,6 +58,18 @@ const MarkdownPostEdit: React.FC<MarkdownPostEditProps> = ({
};
}, []);
+ React.useEffect(() => {
+ window.onbeforeunload = () => {
+ if (!canLeave) {
+ return t("timeline.confirmLeave");
+ }
+ };
+
+ return () => {
+ window.onbeforeunload = null;
+ };
+ }, [canLeave, t]);
+
const send = async (): Promise<void> => {
setProcess(true);
try {
@@ -70,73 +86,76 @@ const MarkdownPostEdit: React.FC<MarkdownPostEditProps> = ({
};
return (
- <TabPages
- className={className}
- style={style}
- pageContainerClassName="py-2"
- actions={
- <>
- <div className="flat-button text-danger mr-2" onClick={onClose}>
- {t("operationDialog.cancel")}
- </div>
- <div className="flat-button text-primary" onClick={send}>
- {t("timeline.send")}
- </div>
- </>
- }
- 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) => (
- <img
- key={image.url}
- src={image.url}
- className="timeline-markdown-post-edit-image"
- />
- ))}
- <Form.File
- label={t("chooseImage")}
- accept="image/*"
- onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
- const { files } = event.currentTarget;
- if (files != null && files.length !== 0) {
- getBuilder().appendImage(files[0]);
- }
- }}
+ <>
+ <Prompt when={!canLeave} message={t("timeline.confirmLeave")} />
+ <TabPages
+ className={className}
+ style={style}
+ pageContainerClassName="py-2"
+ actions={
+ <>
+ <div className="flat-button text-danger mr-2" onClick={onClose}>
+ {t("operationDialog.cancel")}
+ </div>
+ <div className="flat-button text-primary" onClick={send}>
+ {t("timeline.send")}
+ </div>
+ </>
+ }
+ pages={[
+ {
+ id: "text",
+ tabText: "edit",
+ page: (
+ <Form.Control
+ as="textarea"
+ value={text}
disabled={process}
+ onChange={(event) => {
+ getBuilder().setMarkdownText(event.currentTarget.value);
+ }}
/>
- </div>
- ),
- },
- {
- id: "preview",
- tabText: "preview",
- page: (
- <div
- className="markdown-container timeline-markdown-post-edit-page"
- dangerouslySetInnerHTML={{ __html: previewHtml }}
- />
- ),
- },
- ]}
- />
+ ),
+ },
+ {
+ id: "images",
+ tabText: "image",
+ page: (
+ <div className="timeline-markdown-post-edit-page">
+ {images.map((image) => (
+ <img
+ key={image.url}
+ src={image.url}
+ className="timeline-markdown-post-edit-image"
+ />
+ ))}
+ <Form.File
+ label={t("chooseImage")}
+ accept="image/*"
+ 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 }}
+ />
+ ),
+ },
+ ]}
+ />
+ </>
);
};