aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd/src/services/TimelinePostBuilder.ts
blob: 83d63abeda268a902999f2f5856848aaea86d82e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import { marked } from "marked";

import { UiLogicError } from "@/common";

import base64 from "@/utilities/base64";

import { HttpTimelinePostPostRequest } from "@/http/timeline";

class TimelinePostMarkedRenderer extends marked.Renderer {
  constructor(private _images: { file: File; url: string }[]) {
    super();
  }

  image(href: string | null, title: string | null, text: string): string {
    if (href != null) {
      const i = parseInt(href);
      if (!isNaN(i) && i > 0 && i <= this._images.length) {
        href = this._images[i - 1].url;
      }
    }
    return this.image(href, title, text);
  }
}

export default class TimelinePostBuilder {
  private _onChange: () => void;
  private _text = "";
  private _images: { file: File; url: string }[] = [];
  private _markedOptions: marked.MarkedOptions;

  constructor(onChange: () => void) {
    this._onChange = onChange;
    this._markedOptions = {
      renderer: new TimelinePostMarkedRenderer(this._images),
    };
  }

  setMarkdownText(text: string): void {
    this._text = text;
    this._onChange();
  }

  appendImage(file: File): void {
    this._images = this._images.slice();
    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;
    }

    this._images = this._images.slice();

    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.");
    }

    this._images = this._images.slice();

    URL.revokeObjectURL(this._images[index].url);
    this._images.splice(index, 1);

    this._onChange();
  }

  get text(): string {
    return this._text;
  }

  get images(): { file: File; url: string }[] {
    return this._images;
  }

  get isEmpty(): boolean {
    return this._text.length === 0 && this._images.length === 0;
  }

  renderHtml(): string {
    return marked.parse(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,
          })),
        ),
      )),
    ];
  }
}