aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2021-03-12 17:14:57 +0800
committercrupest <crupest@outlook.com>2021-03-12 17:14:57 +0800
commit6d35c11754b3840a1debf36ce1ab8df3ec4fc491 (patch)
treec39cf5b1ce36c7ce18e9f9cb50ed7035adb298cd
parent5742cdaf5a0285725b630a769626c7f72cf635d8 (diff)
downloadtimeline-6d35c11754b3840a1debf36ce1ab8df3ec4fc491.tar.gz
timeline-6d35c11754b3840a1debf36ce1ab8df3ec4fc491.tar.bz2
timeline-6d35c11754b3840a1debf36ce1ab8df3ec4fc491.zip
feat: Add TimelinePostBuilder.
-rw-r--r--FrontEnd/src/app/services/TimelinePostBuilder.ts120
1 files changed, 120 insertions, 0 deletions
diff --git a/FrontEnd/src/app/services/TimelinePostBuilder.ts b/FrontEnd/src/app/services/TimelinePostBuilder.ts
new file mode 100644
index 00000000..8594fa49
--- /dev/null
+++ b/FrontEnd/src/app/services/TimelinePostBuilder.ts
@@ -0,0 +1,120 @@
+import {
+ escapeHtml,
+ replaceEntities,
+ unescapeMd,
+} from "remarkable/lib/common/utils";
+import { Remarkable } from "remarkable";
+
+import { UiLogicError } from "@/common";
+
+import { base64 } from "@/http/common";
+import { HttpTimelinePostPostRequest } from "@/http/timeline";
+
+export class TimelinePostBuilder {
+ private _onChange: () => void;
+ private _text = "";
+ private _images: { file: File; url: string }[] = [];
+ private _md: Remarkable = new Remarkable();
+
+ constructor(onChange: () => void) {
+ this._onChange = onChange;
+ this._md.renderer.rules.image = ((
+ _t: TimelinePostBuilder
+ ): Remarkable.Rule<Remarkable.ImageToken, string> =>
+ function (tokens, idx, options /*, env */) {
+ const i = parseInt(tokens[idx].src);
+ const src =
+ ' src="' +
+ (isNaN(i) && i > 0 && i <= _t._images.length
+ ? escapeHtml(tokens[idx].src)
+ : _t._images[i - 1].url) +
+ '"';
+ const title = tokens[idx].title
+ ? ' title="' + escapeHtml(replaceEntities(tokens[idx].title)) + '"'
+ : "";
+ const alt =
+ ' alt="' +
+ (tokens[idx].alt
+ ? escapeHtml(replaceEntities(unescapeMd(tokens[idx].alt)))
+ : "") +
+ '"';
+ const suffix = options?.xhtmlOut ? " /" : "";
+ return "<img" + src + alt + title + suffix + ">";
+ })(this);
+ }
+
+ setMarkdownText(text: string): void {
+ this._text = text;
+ this._onChange();
+ }
+
+ appendImage(file: File): void {
+ this._images.push({
+ file,
+ url: URL.createObjectURL(file),
+ });
+ this._onChange();
+ }
+
+ moveImage(oldIndex: number, newIndex: number): void {
+ if (oldIndex < 0 || oldIndex >= this._images.length) {
+ throw new UiLogicError("Old index out of range.");
+ }
+
+ if (newIndex < 0) {
+ newIndex = 0;
+ }
+
+ if (newIndex >= this._images.length) {
+ newIndex = this._images.length - 1;
+ }
+
+ const [old] = this._images.splice(oldIndex, 1);
+ this._images.splice(newIndex, 0, old);
+
+ this._onChange();
+ }
+
+ deleteImage(index: number): void {
+ if (index < 0 || index >= this._images.length) {
+ throw new UiLogicError("Old index out of range.");
+ }
+
+ URL.revokeObjectURL(this._images[index].url);
+ this._images.splice(index, 1);
+
+ this._onChange();
+ }
+
+ get images(): { file: File; url: string }[] {
+ return this._images;
+ }
+
+ renderHtml(): string {
+ return this._md.render(this._text);
+ }
+
+ dispose(): void {
+ for (const image of this._images) {
+ URL.revokeObjectURL(image.url);
+ }
+ this._images = [];
+ }
+
+ async build(): Promise<HttpTimelinePostPostRequest["dataList"]> {
+ return [
+ {
+ contentType: "text/markdown",
+ data: await base64(this._text),
+ },
+ ...(await Promise.all(
+ this._images.map((image) =>
+ base64(image.file).then((data) => ({
+ contentType: image.file.type,
+ data,
+ }))
+ )
+ )),
+ ];
+ }
+}