~glacambre/firenvim

ref: 6f4d06bf58fbb7fc93f50a29988a1c5225b6d799 firenvim/src/page/functions.ts -rw-r--r-- 6.2 KiB
6f4d06bfGhjuvan Lacambre Merge pull request #135 from glacambre/replace_osx_path 2 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
128
129
130
131
132
133
134
135
136
137
138
import * as browser from "webextension-polyfill";
import { computeSelector } from "../utils/CSSUtils";

function executeInPage(code: string): Promise<any> {
    return new Promise((resolve, reject) => {
        const script = document.createElement("script");
        const eventId = (new URL(browser.runtime.getURL(""))).hostname + Math.random();
        script.innerHTML = `((evId) => {
            try {
                let result;
                result = ${code};
                window.dispatchEvent(new CustomEvent(evId, {
                    detail: {
                        success: true,
                        result,
                    }
                }));
            } catch (e) {
                window.dispatchEvent(new CustomEvent(evId, {
                    detail: { success: false, reason: e },
                }));
            }
        })(${JSON.stringify(eventId)})`;
        window.addEventListener(eventId, ({ detail }: any) => {
            script.parentNode.removeChild(script);
            if (detail.success) {
                return resolve(detail.result);
            }
            return reject(detail.reason);
        }, { once: true });
        document.head.appendChild(script);
    });
}

function _getElementContent(e: any): Promise<string> {
    if (e.className.match(/CodeMirror/gi)) {
        const selector = computeSelector(e);
        return executeInPage(`(${(selec: string) => {
            const elem = document.querySelector(selec) as any;
            return elem.CodeMirror.getValue();
        }})(${JSON.stringify(selector)})`);
    } else if (e.className.match(/ace_editor/gi)) {
        return executeInPage(`(${(selec: string) => {
            const elem = document.querySelector(selec) as any;
            return (window as any).ace.edit(elem).getValue();
        }})(${JSON.stringify(computeSelector(e))})`);
    }
    if (e.value !== undefined) {
        return Promise.resolve(e.value);
    }
    if (e.textContent !== undefined) {
        return Promise.resolve(e.textContent);
    }
    return Promise.resolve(e.innerText);
}

export function getFunctions(global: {
    lastEditorLocation: [string, string, number],
    nvimify: (evt: FocusEvent) => void,
    selectorToElems: Map<string, PageElements>,
    disabled: boolean | Promise<boolean>,
}) {
    return {
        getEditorLocation: () => {
            // global.lastEditorLocation[1] is a selector. If no selector is
            // defined, we're not the script that should answer this question
            // and thus return a Promise that will never be resolved
            if (global.lastEditorLocation[1] === "") {
                // This cast is wrong but we need it in order to be able to
                // typecheck our proxy in page/proxy.ts. Note that it's ok
                // because the promise will never return anyway.
                return new Promise(() => undefined) as Promise<typeof global.lastEditorLocation>;
            }
            // We need to reset global.lastEditorLocation in order to avoid
            // accidentally giving an already-given selector if we receive a
            // message that isn't addressed to us. Note that this is a hack, a
            // proper fix would be depending on frameIDs, but we can't do that
            // efficiently
            const result = global.lastEditorLocation;
            global.lastEditorLocation = ["", "", 0];
            return Promise.resolve(result);
        },
        getElementContent: (selector: string) => _getElementContent(global.selectorToElems.get(selector).input),
        killEditor: (selector: string) => {
            const { span, input } = global.selectorToElems.get(selector);
            span.parentNode.removeChild(span);
            global.selectorToElems.delete(selector);
            input.removeEventListener("focus", global.nvimify);
            input.focus();
            input.addEventListener("focus", global.nvimify);
        },
        resizeEditor: (selector: string, width: number, height: number) => {
            const { iframe } = global.selectorToElems.get(selector);
            iframe.style.width = `${width}px`;
            iframe.style.height = `${height}px`;
        },
        setDisabled: (disabled: boolean) => {
            global.disabled = disabled;
        },
        setElementContent: (selector: string, text: string) => {
            const { input: e } = global.selectorToElems.get(selector) as any;
            if (e.className.match(/CodeMirror/gi)) {
                return executeInPage(`(${(selec: string, str: string) => {
                    const elem = document.querySelector(selec) as any;
                    return elem.CodeMirror.setValue(str);
                }})(${JSON.stringify(selector)}, ${JSON.stringify(text)})`);
            } else if (e.className.match(/ace_editor/gi)) {
                return executeInPage(`(${(selec: string, str: string) => {
                    const elem = document.querySelector(selec) as any;
                    return (window as any).ace.edit(elem).setValue(str);
                }})(${JSON.stringify(selector)}, ${JSON.stringify(text)})`);
            }
            if (e.value !== undefined) {
                e.value = text;
            } else {
                e.textContent = text;
            }
            e.dispatchEvent(new Event("keydown",     { bubbles: true }));
            e.dispatchEvent(new Event("keyup",       { bubbles: true }));
            e.dispatchEvent(new Event("keypress",    { bubbles: true }));
            e.dispatchEvent(new Event("beforeinput", { bubbles: true }));
            e.dispatchEvent(new Event("input",       { bubbles: true }));
            e.dispatchEvent(new Event("change",      { bubbles: true }));
        },
        setElementCursor: async (selector: string, line: number, column: number) => {
            const { input } = global.selectorToElems.get(selector) as any;
            if (!input.setSelectionRange) {
                return;
            }
            const pos = (await _getElementContent(input))
                .split("\n")
                .reduce((acc: number, l: string, index: number) => acc + (index < (line - 1)
                    ? (l.length + 1)
                    : 0), column + 1);
            input.setSelectionRange(pos, pos);
        },
    };
}