~sirn/fanboi2

ref: a68873a108d20a6a49a1b6caf374bfb2cf6a94fe fanboi2/assets/app/javascripts/components/topic_quick_reply.ts -rw-r--r-- 4.2 KiB
a68873a1Kridsada Thanabulpong Coding style cleanups and setup pre-commit hooks (#42) 3 years ago
                                                                                
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
126
127
import { VNode, create, diff, patch } from "virtual-dom";

import { DelegationComponent } from "./base";
import { TopicInlineReply } from "./topic_inline_reply";
import { TopicStateTracker } from "./topic_state_tracker";

import { PopoverView } from "../views/popover_view";
import { PostForm } from "../views/post_form";
import { dispatchCustomEvent } from "../utils/elements";

export class TopicQuickReply extends DelegationComponent {
    public targetSelector = "[data-topic-quick-reply]";

    protected bindGlobal(): void {
        document.body.addEventListener("click", (e: Event): void => {
            let $target = e.target;

            if ($target instanceof Element && $target.matches(this.targetSelector)) {
                let anchor = $target.getAttribute("data-topic-quick-reply");
                let $topic = $target.closest("[data-topic]");
                let $input: Element;

                e.preventDefault();

                if (!anchor) {
                    throw new Error("Reply anchor is empty when it should not.");
                }

                if ($topic) {
                    $input = $topic.querySelector("[data-topic-quick-reply-input]");

                    if ($input instanceof HTMLTextAreaElement) {
                        this.insertTextAtCursor($input, anchor);
                    } else {
                        this.attachForm($target, $topic, anchor);
                    }
                }
            }
        });
    }

    private insertTextAtCursor($input: HTMLTextAreaElement, anchor: string): void {
        let anchorText = `>>${anchor} `;
        let startPos = $input.selectionStart;
        let endPos = $input.selectionEnd;
        let currentValue = $input.value;

        $input.value =
            currentValue.substring(0, startPos) +
            anchorText +
            currentValue.substring(endPos, currentValue.length);

        $input.focus();
        $input.dispatchEvent(new Event("change"));
        $input.selectionStart = startPos + anchorText.length;
    }

    private attachForm($target: Element, $topic: Element, anchor: string): void {
        let $parent = $target.closest(".js-popover");
        let $popover: Element;
        let $textarea: Element;
        let popoverView: PopoverView;
        let popoverNode: VNode;
        let throttleTimer: number;

        if (!$parent) {
            $parent = $topic;
        }

        let _removePopover = () => {
            $topic.removeEventListener("postCreated", _removePopover);
            document.body.removeEventListener("click", _clickRemovePopover);
            window.removeEventListener("resize", _repositionPopover);

            if ($parent) {
                $parent.removeChild($popover);
            }
        };

        popoverView = new PopoverView(
            new PostForm().render(),
            "Quick Reply",
            _removePopover,
        );

        let _repositionPopover = () => {
            clearTimeout(throttleTimer);
            throttleTimer = setTimeout(() => {
                if ($popover) {
                    let newPopoverNode = popoverView.render($target);
                    let patches = diff(popoverNode, newPopoverNode);

                    $popover = patch($popover, patches);
                    popoverNode = newPopoverNode;
                }
            }, 100);
        };

        let _clickRemovePopover = (e: Event) => {
            let _n = e.target;

            while (_n instanceof Node && _n != $popover) {
                _n = _n.parentNode;
            }

            if (_n != $popover) {
                _removePopover();
            }
        };

        popoverNode = popoverView.render($target);
        $popover = create(popoverNode);
        $parent.appendChild($popover);

        new TopicInlineReply($popover);
        new TopicStateTracker($popover);

        $textarea = $popover.querySelector("textarea");
        if ($textarea instanceof HTMLTextAreaElement) {
            this.insertTextAtCursor($textarea, anchor);
        }

        document.body.addEventListener("click", _clickRemovePopover);
        $topic.addEventListener("postCreated", _removePopover);
        window.addEventListener("resize", _repositionPopover);
    }
}