aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/FormattedTextBoxComment.tsx
blob: 255000936ca0a2ef470a87609621dae715223794 (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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
import { Plugin, EditorState } from "prosemirror-state";
import './FormattedTextBoxComment.scss';
import { ResolvedPos, Mark } from "prosemirror-model";
import { EditorView } from "prosemirror-view";
import { Doc } from "../../../new_fields/Doc";
import { schema } from "../../util/RichTextSchema";
import { DocServer } from "../../DocServer";
import { Utils } from "../../../Utils";
import { StrCast } from "../../../new_fields/Types";

export let formattedTextBoxCommentPlugin = new Plugin({
    view(editorView) { return new FormattedTextBoxComment(editorView); }
});
export function findOtherUserMark(marks: Mark[]): Mark | undefined {
    return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail);
}
export function findUserMark(marks: Mark[]): Mark | undefined {
    return marks.find(m => m.attrs.userid);
}
export function findLinkMark(marks: Mark[]): Mark | undefined {
    return marks.find(m => m.type === schema.marks.link);
}
export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) {
    let before = 0;
    let nbef = rpos.nodeBefore;
    while (nbef && finder(nbef.marks)) {
        before += nbef.nodeSize;
        rpos = view.state.doc.resolve(rpos.pos - nbef.nodeSize);
        rpos && (nbef = rpos.nodeBefore);
    }
    return before;
}
export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) {
    let after = 0;
    let naft = rpos.nodeAfter;
    while (naft && finder(naft.marks)) {
        after += naft.nodeSize;
        rpos = view.state.doc.resolve(rpos.pos + naft.nodeSize);
        rpos && (naft = rpos.nodeAfter);
    }
    return after;
}


export class FormattedTextBoxComment {
    static tooltip: HTMLElement;
    static tooltipText: HTMLElement;
    static start: number;
    static end: number;
    static mark: Mark;
    static opened: boolean;
    static textBox: any;
    constructor(view: any) {
        if (!FormattedTextBoxComment.tooltip) {
            const root = document.getElementById("root");
            let input = document.createElement("input");
            input.type = "checkbox";
            FormattedTextBoxComment.tooltip = document.createElement("div");
            FormattedTextBoxComment.tooltipText = document.createElement("div");
            FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText);
            FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip";
            FormattedTextBoxComment.tooltip.style.pointerEvents = "all";
            FormattedTextBoxComment.tooltip.appendChild(input);
            FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => {
                let keep = e.target && (e.target as any).type === "checkbox";
                FormattedTextBoxComment.opened = keep || !FormattedTextBoxComment.opened;
                FormattedTextBoxComment.textBox && FormattedTextBoxComment.textBox.setAnnotation(
                    FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark,
                    FormattedTextBoxComment.opened, keep);
            };
            root && root.appendChild(FormattedTextBoxComment.tooltip);
        }
        this.update(view, undefined);
    }

    public static Hide() {
        FormattedTextBoxComment.textBox = undefined;
        FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none");
    }
    public static SetState(textBox: any, opened: boolean, start: number, end: number, mark: Mark) {
        FormattedTextBoxComment.textBox = textBox;
        FormattedTextBoxComment.start = start;
        FormattedTextBoxComment.end = end;
        FormattedTextBoxComment.mark = mark;
        FormattedTextBoxComment.opened = opened;
        FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "");
    }

    update(view: EditorView, lastState?: EditorState) {
        let state = view.state;
        // Don't do anything if the document/selection didn't change
        if (lastState && lastState.doc.eq(state.doc) &&
            lastState.selection.eq(state.selection)) return;

        let set = "none"
        if (state.selection.$from) {
            let nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
            let naft = findEndOfMark(state.selection.$from, view, findOtherUserMark);
            const spos = state.selection.$from.pos - nbef;
            const epos = state.selection.$from.pos + naft;
            let child = state.selection.$from.nodeBefore;
            let mark = child && findOtherUserMark(child.marks);
            let noselection = view.state.selection.$from === view.state.selection.$to;
            if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) {
                FormattedTextBoxComment.SetState(this, mark.attrs.opened, spos, epos, mark);
            }
            if (mark && child && nbef && naft) {
                FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " " + mark.attrs.modified;
                // These are in screen coordinates
                // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to);
                let start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
                // The box in which the tooltip is positioned, to use as base
                let box = (document.getElementById("main-div") as any).getBoundingClientRect();
                // Find a center-ish x position from the selection endpoints (when
                // crossing lines, end may be more to the left)
                let left = Math.max((start.left + end.left) / 2, start.left + 3);
                FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
                FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
                set = "";
            }
        }
        if (set === "none" && state.selection.$from) {
            FormattedTextBoxComment.textBox = undefined;
            let nbef = findStartOfMark(state.selection.$from, view, findLinkMark);
            let naft = findEndOfMark(state.selection.$from, view, findLinkMark);
            let child = state.selection.$from.nodeBefore;
            let mark = child && findLinkMark(child.marks);
            if (mark && child && nbef && naft) {
                FormattedTextBoxComment.tooltipText.textContent = "link : " + (mark.attrs.title || mark.attrs.href);
                if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) {
                    let docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0];
                    docTarget && DocServer.GetRefField(docTarget).then(linkDoc =>
                        (linkDoc as Doc) && (FormattedTextBoxComment.tooltipText.textContent = "link :" + StrCast((linkDoc as Doc)!.title)));
                }
                // These are in screen coordinates
                // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to);
                let start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
                // The box in which the tooltip is positioned, to use as base
                let box = (document.getElementById("main-div") as any).getBoundingClientRect();
                // Find a center-ish x position from the selection endpoints (when
                // crossing lines, end may be more to the left)
                let left = Math.max((start.left + end.left) / 2, start.left + 3);
                FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
                FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
                set = "";
            }
        }
        FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set);
    }

    destroy() { FormattedTextBoxComment.tooltip.style.display = "none"; }
}