Figma Image Upload

Figma Image Upload图片上传工具

// ==UserScript==
// @name        Figma Image Upload
// @namespace   https://github.com/gideonsenku
// @version     0.3.3
// @description Figma Image Upload图片上传工具
// @encoding    utf-8
// @author      gideonsenku
// @homepage    https://github.com/gideonsenku/figma-image-upload
// @supportURL  https://github.com/gideonsenku/figma-image-upload/issues
// @match       *://www.figma.com/file/*
// @match       http://blog.sodion.net/figma-image-upload/setting.html
// @run-at      document-end
// @icon        https://www.google.com/s2/favicons?domain=figma.com
// @license     MIT; https://github.com/gideonsenku/figma-image-upload/blob/main/LICENSE
// @grant       unsafeWindow
// @grant       GM_xmlhttpRequest
// @grant       GM_getValue
// @grant       GM_setValue
// ==/UserScript==
!function() {
    "use strict";
    function noop() {}
    function run(fn) {
        return fn();
    }
    function blank_object() {
        return Object.create(null);
    }
    function run_all(fns) {
        fns.forEach(run);
    }
    function is_function(thing) {
        return "function" == typeof thing;
    }
    function safe_not_equal(a, b) {
        return a != a ? b == b : a !== b || a && "object" == typeof a || "function" == typeof a;
    }
    function append(target, node) {
        target.appendChild(node);
    }
    function insert(target, node, anchor) {
        target.insertBefore(node, anchor || null);
    }
    function detach(node) {
        node.parentNode.removeChild(node);
    }
    function element(name) {
        return document.createElement(name);
    }
    function text(data) {
        return document.createTextNode(data);
    }
    function space() {
        return text(" ");
    }
    function listen(node, event, handler, options) {
        return node.addEventListener(event, handler, options), () => node.removeEventListener(event, handler, options);
    }
    function attr(node, attribute, value) {
        null == value ? node.removeAttribute(attribute) : node.getAttribute(attribute) !== value && node.setAttribute(attribute, value);
    }
    function set_data(text, data) {
        data = "" + data, text.wholeText !== data && (text.data = data);
    }
    function set_input_value(input, value) {
        input.value = null == value ? "" : value;
    }
    let current_component;
    function set_current_component(component) {
        current_component = component;
    }
    const dirty_components = [], binding_callbacks = [], render_callbacks = [], flush_callbacks = [], resolved_promise = Promise.resolve();
    let update_scheduled = !1;
    function add_render_callback(fn) {
        render_callbacks.push(fn);
    }
    const seen_callbacks = new Set;
    let flushidx = 0;
    function flush() {
        const saved_component = current_component;
        do {
            for (;flushidx < dirty_components.length; ) {
                const component = dirty_components[flushidx];
                flushidx++, set_current_component(component), update(component.$$);
            }
            for (set_current_component(null), dirty_components.length = 0, flushidx = 0; binding_callbacks.length; ) binding_callbacks.pop()();
            for (let i = 0; i < render_callbacks.length; i += 1) {
                const callback = render_callbacks[i];
                seen_callbacks.has(callback) || (seen_callbacks.add(callback), callback());
            }
            render_callbacks.length = 0;
        } while (dirty_components.length);
        for (;flush_callbacks.length; ) flush_callbacks.pop()();
        update_scheduled = !1, seen_callbacks.clear(), set_current_component(saved_component);
    }
    function update($$) {
        if (null !== $$.fragment) {
            $$.update(), run_all($$.before_update);
            const dirty = $$.dirty;
            $$.dirty = [ -1 ], $$.fragment && $$.fragment.p($$.ctx, dirty), $$.after_update.forEach(add_render_callback);
        }
    }
    const outroing = new Set;
    function make_dirty(component, i) {
        -1 === component.$$.dirty[0] && (dirty_components.push(component), function schedule_update() {
            update_scheduled || (update_scheduled = !0, resolved_promise.then(flush));
        }(), component.$$.dirty.fill(0)), component.$$.dirty[i / 31 | 0] |= 1 << i % 31;
    }
    function init(component, options, instance, create_fragment, not_equal, props, append_styles, dirty = [ -1 ]) {
        const parent_component = current_component;
        set_current_component(component);
        const $$ = component.$$ = {
            fragment: null,
            ctx: null,
            props: props,
            update: noop,
            not_equal: not_equal,
            bound: blank_object(),
            on_mount: [],
            on_destroy: [],
            on_disconnect: [],
            before_update: [],
            after_update: [],
            context: new Map(options.context || (parent_component ? parent_component.$$.context : [])),
            callbacks: blank_object(),
            dirty: dirty,
            skip_bound: !1,
            root: options.target || parent_component.$$.root
        };
        append_styles && append_styles($$.root);
        let ready = !1;
        if ($$.ctx = instance ? instance(component, options.props || {}, ((i, ret, ...rest) => {
            const value = rest.length ? rest[0] : ret;
            return $$.ctx && not_equal($$.ctx[i], $$.ctx[i] = value) && (!$$.skip_bound && $$.bound[i] && $$.bound[i](value), 
            ready && make_dirty(component, i)), ret;
        })) : [], $$.update(), ready = !0, run_all($$.before_update), $$.fragment = !!create_fragment && create_fragment($$.ctx), 
        options.target) {
            if (options.hydrate) {
                const nodes = function children(element) {
                    return Array.from(element.childNodes);
                }(options.target);
                $$.fragment && $$.fragment.l(nodes), nodes.forEach(detach);
            } else $$.fragment && $$.fragment.c();
            options.intro && function transition_in(block, local) {
                block && block.i && (outroing.delete(block), block.i(local));
            }(component.$$.fragment), function mount_component(component, target, anchor, customElement) {
                const {fragment: fragment, on_mount: on_mount, on_destroy: on_destroy, after_update: after_update} = component.$$;
                fragment && fragment.m(target, anchor), customElement || add_render_callback((() => {
                    const new_on_destroy = on_mount.map(run).filter(is_function);
                    on_destroy ? on_destroy.push(...new_on_destroy) : run_all(new_on_destroy), component.$$.on_mount = [];
                })), after_update.forEach(add_render_callback);
            }(component, options.target, options.anchor, options.customElement), flush();
        }
        set_current_component(parent_component);
    }
    class SvelteComponent {
        $destroy() {
            !function destroy_component(component, detaching) {
                const $$ = component.$$;
                null !== $$.fragment && (run_all($$.on_destroy), $$.fragment && $$.fragment.d(detaching), 
                $$.on_destroy = $$.fragment = null, $$.ctx = []);
            }(this, 1), this.$destroy = noop;
        }
        $on(type, callback) {
            const callbacks = this.$$.callbacks[type] || (this.$$.callbacks[type] = []);
            return callbacks.push(callback), () => {
                const index = callbacks.indexOf(callback);
                -1 !== index && callbacks.splice(index, 1);
            };
        }
        $set($$props) {
            this.$$set && !function is_empty(obj) {
                return 0 === Object.keys(obj).length;
            }($$props) && (this.$$.skip_bound = !0, this.$$set($$props), this.$$.skip_bound = !1);
        }
    }
    var UseSingleton = function(createInstance, {withKey: withKey = !1, immediate: immediate = !1} = {}) {
        const UNDEFINED_INSTANCE = {};
        let _key, _instance = UNDEFINED_INSTANCE;
        function getSingleton(key) {
            return _instance !== UNDEFINED_INSTANCE && function checkSameKey(key) {
                return !withKey || void 0 === key || key === _key;
            }(key) || (_key = key, _instance = createInstance(_key)), _instance;
        }
        return immediate && getSingleton(), getSingleton;
    };
    function styleInject(css, ref) {
        void 0 === ref && (ref = {});
        var insertAt = ref.insertAt;
        if (css && "undefined" != typeof document) {
            var head = document.head || document.getElementsByTagName("head")[0], style = document.createElement("style");
            style.type = "text/css", "top" === insertAt && head.firstChild ? head.insertBefore(style, head.firstChild) : head.appendChild(style), 
            style.styleSheet ? style.styleSheet.cssText = css : style.appendChild(document.createTextNode(css));
        }
    }
    function create_fragment$2(ctx) {
        let div, t, div_class_value;
        return {
            c() {
                div = element("div"), t = text(ctx[2]), attr(div, "class", div_class_value = "toast " + (ctx[1] ? "" : "toast--hide") + " svelte-1hd7ahf");
            },
            m(target, anchor) {
                insert(target, div, anchor), append(div, t), ctx[4](div);
            },
            p(ctx, [dirty]) {
                4 & dirty && set_data(t, ctx[2]), 2 & dirty && div_class_value !== (div_class_value = "toast " + (ctx[1] ? "" : "toast--hide") + " svelte-1hd7ahf") && attr(div, "class", div_class_value);
            },
            i: noop,
            o: noop,
            d(detaching) {
                detaching && detach(div), ctx[4](null);
            }
        };
    }
    function instance$2($$self, $$props, $$invalidate) {
        let toast, content, visiable = !1, closeTimer = null;
        return [ toast, visiable, content, function show({title: title, duration: duration = 1500}) {
            $$invalidate(2, content = title), closeTimer && clearTimeout(closeTimer), $$invalidate(1, visiable = !0), 
            closeTimer = setTimeout((() => {
                $$invalidate(1, visiable = !1), closeTimer = null;
            }), duration);
        }, function div_binding($$value) {
            binding_callbacks[$$value ? "unshift" : "push"]((() => {
                toast = $$value, $$invalidate(0, toast);
            }));
        } ];
    }
    styleInject(".toast.svelte-1hd7ahf{background-color:rgba(0,0,0,.8);border-radius:4px;color:#eee;font-size:16px;left:50%;max-width:200px;padding:12px 24px;position:fixed;top:50%;transform:translate(-50%,-50%);z-index:9999999}.toast--hide.svelte-1hd7ahf{visibility:hidden;z-index:-1}");
    class Toast extends SvelteComponent {
        constructor(options) {
            super(), init(this, options, instance$2, create_fragment$2, safe_not_equal, {
                show: 3
            });
        }
        get show() {
            return this.$$.ctx[3];
        }
    }
    const toast = UseSingleton((() => {
        const toastEl = new Toast({
            target: document.body,
            props: {
                content: ""
            }
        });
        return ({title: title, duration: duration = 1500}) => {
            toastEl.show({
                title: title,
                duration: duration
            });
        };
    }))();
    function create_fragment$1(ctx) {
        let div3, div2, div0, t1, div1, input, t2, button, mounted, dispose;
        return {
            c() {
                div3 = element("div"), div2 = element("div"), div0 = element("div"), div0.textContent = "配置url地址", 
                t1 = space(), div1 = element("div"), input = element("input"), t2 = space(), button = element("button"), 
                button.textContent = "保存", attr(div0, "class", "text-blue-800 font-medium mb-3"), 
                attr(input, "type", "text"), attr(input, "placeholder", "url"), attr(input, "class", "px-3 py-4 placeholder-blueGray-300 text-blueGray-600 relative bg-white bg-white rounded text-base border-0 shadow outline-none focus:outline-none w-full"), 
                attr(div1, "class", "mb-3 pt-0"), attr(button, "class", "bg-blue-600 text-white active:bg-blue-600 font-bold uppercase text-base px-8 py-3 rounded-full shadow hover:shadow-lg outline-none focus:outline-none mr-1 mb-1 ease-linear transition-all duration-150"), 
                attr(button, "type", "button");
            },
            m(target, anchor) {
                insert(target, div3, anchor), append(div3, div2), append(div2, div0), append(div2, t1), 
                append(div2, div1), append(div1, input), set_input_value(input, ctx[0]), append(div3, t2), 
                append(div3, button), mounted || (dispose = [ listen(input, "input", ctx[2]), listen(button, "click", ctx[1]) ], 
                mounted = !0);
            },
            p(ctx, [dirty]) {
                1 & dirty && input.value !== ctx[0] && set_input_value(input, ctx[0]);
            },
            i: noop,
            o: noop,
            d(detaching) {
                detaching && detach(div3), mounted = !1, run_all(dispose);
            }
        };
    }
    function instance$1($$self, $$props, $$invalidate) {
        let uploadUrl = GM_getValue("UPLOAD_URL", "");
        return [ uploadUrl, function save() {
            try {
                GM_setValue("UPLOAD_URL", uploadUrl), toast({
                    title: "保存成功"
                });
            } catch (e) {
                toast({
                    title: e.message
                });
            }
        }, function input_input_handler() {
            uploadUrl = this.value, $$invalidate(0, uploadUrl);
        } ];
    }
    class SettingPanel extends SvelteComponent {
        constructor(options) {
            super(), init(this, options, instance$1, create_fragment$1, safe_not_equal, {});
        }
    }
    function create_if_block(ctx) {
        let div7, div6, t5, if_block = ctx[2] && create_if_block_1(ctx);
        return {
            c() {
                div7 = element("div"), div6 = element("div"), div6.innerHTML = '<div class="sk-chase-dot svelte-8l84q3"></div> \n      <div class="sk-chase-dot svelte-8l84q3"></div> \n      <div class="sk-chase-dot svelte-8l84q3"></div> \n      <div class="sk-chase-dot svelte-8l84q3"></div> \n      <div class="sk-chase-dot svelte-8l84q3"></div> \n      <div class="sk-chase-dot svelte-8l84q3"></div>', 
                t5 = space(), if_block && if_block.c(), attr(div6, "class", "sk-chase svelte-8l84q3"), 
                attr(div7, "class", "loading-bg svelte-8l84q3");
            },
            m(target, anchor) {
                insert(target, div7, anchor), append(div7, div6), append(div7, t5), if_block && if_block.m(div7, null), 
                ctx[5](div7);
            },
            p(ctx, dirty) {
                ctx[2] ? if_block ? if_block.p(ctx, dirty) : (if_block = create_if_block_1(ctx), 
                if_block.c(), if_block.m(div7, null)) : if_block && (if_block.d(1), if_block = null);
            },
            d(detaching) {
                detaching && detach(div7), if_block && if_block.d(), ctx[5](null);
            }
        };
    }
    function create_if_block_1(ctx) {
        let div, t;
        return {
            c() {
                div = element("div"), t = text(ctx[2]), attr(div, "class", "loading-content svelte-8l84q3");
            },
            m(target, anchor) {
                insert(target, div, anchor), append(div, t);
            },
            p(ctx, dirty) {
                4 & dirty && set_data(t, ctx[2]);
            },
            d(detaching) {
                detaching && detach(div);
            }
        };
    }
    function create_fragment(ctx) {
        let if_block_anchor, if_block = ctx[1] && create_if_block(ctx);
        return {
            c() {
                if_block && if_block.c(), if_block_anchor = function empty() {
                    return text("");
                }();
            },
            m(target, anchor) {
                if_block && if_block.m(target, anchor), insert(target, if_block_anchor, anchor);
            },
            p(ctx, [dirty]) {
                ctx[1] ? if_block ? if_block.p(ctx, dirty) : (if_block = create_if_block(ctx), if_block.c(), 
                if_block.m(if_block_anchor.parentNode, if_block_anchor)) : if_block && (if_block.d(1), 
                if_block = null);
            },
            i: noop,
            o: noop,
            d(detaching) {
                if_block && if_block.d(detaching), detaching && detach(if_block_anchor);
            }
        };
    }
    function instance($$self, $$props, $$invalidate) {
        let loading, content, visiable = !1, closeTimer = null;
        return [ loading, visiable, content, function show({title: title, duration: duration = 0}) {
            $$invalidate(2, content = title), closeTimer && clearTimeout(closeTimer), $$invalidate(1, visiable = !0), 
            0 != duration && (closeTimer = setTimeout((() => {
                $$invalidate(1, visiable = !1), closeTimer = null;
            }), duration));
        }, function close() {
            closeTimer && clearTimeout(closeTimer), $$invalidate(1, visiable = !1);
        }, function div7_binding($$value) {
            binding_callbacks[$$value ? "unshift" : "push"]((() => {
                loading = $$value, $$invalidate(0, loading);
            }));
        } ];
    }
    styleInject('.loading-bg.svelte-8l84q3{align-items:center;background:rgba(0,0,0,.6);bottom:0;display:flex;flex-direction:column;justify-content:center;left:0;position:fixed;right:0;top:0;z-index:99999}.loading-content.svelte-8l84q3{color:#fff;font-size:16px;margin-top:20px}.sk-chase.svelte-8l84q3{animation:svelte-8l84q3-sk-chase 2.5s linear infinite both;height:40px;width:40px}.sk-chase-dot.svelte-8l84q3{animation:svelte-8l84q3-sk-chase-dot 2s ease-in-out infinite both;height:100%;left:0;position:absolute;top:0;width:100%}.sk-chase-dot.svelte-8l84q3:before{animation:svelte-8l84q3-sk-chase-dot-before 2s ease-in-out infinite both;background-color:#fff;border-radius:100%;content:"";display:block;height:25%;width:25%}.sk-chase-dot.svelte-8l84q3:first-child{animation-delay:-1.1s}.sk-chase-dot.svelte-8l84q3:nth-child(2){animation-delay:-1s}.sk-chase-dot.svelte-8l84q3:nth-child(3){animation-delay:-.9s}.sk-chase-dot.svelte-8l84q3:nth-child(4){animation-delay:-.8s}.sk-chase-dot.svelte-8l84q3:nth-child(5){animation-delay:-.7s}.sk-chase-dot.svelte-8l84q3:nth-child(6){animation-delay:-.6s}.sk-chase-dot.svelte-8l84q3:first-child:before{animation-delay:-1.1s}.sk-chase-dot.svelte-8l84q3:nth-child(2):before{animation-delay:-1s}.sk-chase-dot.svelte-8l84q3:nth-child(3):before{animation-delay:-.9s}.sk-chase-dot.svelte-8l84q3:nth-child(4):before{animation-delay:-.8s}.sk-chase-dot.svelte-8l84q3:nth-child(5):before{animation-delay:-.7s}.sk-chase-dot.svelte-8l84q3:nth-child(6):before{animation-delay:-.6s}@keyframes svelte-8l84q3-sk-chase{to{transform:rotate(1turn)}}@keyframes svelte-8l84q3-sk-chase-dot{80%,to{transform:rotate(1turn)}}@keyframes svelte-8l84q3-sk-chase-dot-before{50%{transform:scale(.4)}0%,to{transform:scale(1)}}');
    class Loading extends SvelteComponent {
        constructor(options) {
            super(), init(this, options, instance, create_fragment, safe_not_equal, {
                show: 3,
                close: 4
            });
        }
        get show() {
            return this.$$.ctx[3];
        }
        get close() {
            return this.$$.ctx[4];
        }
    }
    const loading = UseSingleton((() => {
        const loadingEl = new Loading({
            target: document.body,
            props: {
                content: ""
            }
        });
        return {
            show({title: title, duration: duration = 0}) {
                loadingEl.show({
                    title: title,
                    duration: duration
                });
            },
            close() {
                loadingEl.close();
            }
        };
    }))();
    const figmaImageUpload = () => {
        if (!/^https:\/\/www\.figma.com/.test(window.location.href)) return;
        const divElement = document.createElement("div");
        divElement.className = "button_row--fplButtonRow--nFW30";
        const buttonElement = document.createElement("button");
        buttonElement.className = "button-reset-module--buttonReset--AdW9- button-module--button--f8I-H utilities--bodyMedium--AOpj3 button-module--secondary--deyxA button-module--outlineStyle--eRuj0 button-module--wideSize--JMqnr", 
        buttonElement.setAttribute("data-fpl-component", ""), buttonElement.addEventListener("click", exportAndupload);
        const spanElement = document.createElement("span");
        function insertBase64Btn() {
            const targetNode = document.querySelector("div[data-trackable-name=export_panel] div");
            targetNode ? targetNode.appendChild(divElement) : console.error("Target node not found!");
        }
        spanElement.className = "button-module--buttonText--Q9pu6", spanElement.innerHTML = '<span class="end_truncated_text--truncatedText--ycps2">OSS Upload</span>', 
        buttonElement.appendChild(spanElement), divElement.appendChild(buttonElement), function addExportTabEventListener() {
            const node = document.querySelector("[data-label=export i]") || document.querySelector("[class*=draggable_list--panelTitle]");
            node ? node.addEventListener("click", (function() {
                setTimeout((() => {
                    insertBase64Btn(), function addAddBtnEventListener() {
                        document.querySelectorAll("button[aria-label=Add]")[0]?.addEventListener("click", (function() {
                            setTimeout((() => {
                                insertBase64Btn();
                            }), 100);
                        }));
                    }();
                }), 100);
            })) : setTimeout((() => {
                addExportTabEventListener();
            }), 500);
        }();
    };
    function getConstraintByScale(scale) {
        return "0.5x" === scale ? {
            type: "SCALE",
            value: .5
        } : "0.75x" === scale ? {
            type: "SCALE",
            value: .75
        } : "1x" === scale ? {
            type: "SCALE",
            value: 1
        } : "1.5x" === scale ? {
            type: "SCALE",
            value: 1.5
        } : "2x" === scale ? {
            type: "SCALE",
            value: 2
        } : "3x" === scale ? {
            type: "SCALE",
            value: 3
        } : "4x" === scale ? {
            type: "SCALE",
            value: 4
        } : "512w" === scale ? {
            type: "WIDTH",
            value: 512
        } : "512h" === scale ? {
            type: "HEIGHT",
            value: 512
        } : void 0;
    }
    async function exportAndupload() {
        console.log("exportAndupload clicked");
        const scaleInputs = Array.apply(null, document.querySelectorAll('input[spellcheck="false"][autocomplete="new-password"][class^=raw_components--textInput]'));
        let scales = Array.from(new Set(scaleInputs.map((ele => ele.value))));
        if (scales.length || (figma.notify("未选择导出尺寸, 默认导出3x"), scales = [ "3x" ], await function wait(seconds) {
            return new Promise((resolve => {
                setTimeout(resolve, 100 * seconds);
            }));
        }(3)), scales.length) {
            const {selection: selection} = figma.currentPage;
            if (!selection[0]) return void figma.notify("请选择要处理的节点");
            try {
                loading.show({
                    title: "图片上传中"
                });
                const scale = scales[0], u8Array = await selection[0].exportAsync({
                    format: "PNG",
                    constraint: getConstraintByScale(scale)
                }), blob = new Blob([ u8Array ], {
                    type: "image/png"
                }), data = new FormData;
                data.append("file", blob, (new Date).getTime() + ".png");
                const uploadUrl = GM_getValue("UPLOAD_URL", "");
                if (!uploadUrl) return void window.open("http://blog.sodion.net/figma-image-upload/setting.html");
                !function copyContent(text) {
                    if (void 0 !== navigator.clipboard) navigator.clipboard.writeText(text).then((function() {
                        parent.postMessage({
                            pluginMessage: {
                                type: "success"
                            }
                        }, "*");
                    }), (function(_err) {
                        parent.postMessage({
                            pluginMessage: {
                                type: "fail"
                            }
                        }, "*");
                    })); else {
                        const textarea = window.document.querySelector("#copy-area");
                        textarea.value = text, textarea.focus(), textarea.select(), window.document.execCommand("copy") ? parent.postMessage({
                            pluginMessage: {
                                type: "success"
                            }
                        }, "*") : parent.postMessage({
                            pluginMessage: {
                                type: "fail"
                            }
                        }, "*");
                    }
                }(await new Promise(((resolve, reject) => {
                    GM_xmlhttpRequest({
                        url: uploadUrl,
                        method: "POST",
                        data: data,
                        onload(xhr) {
                            if (200 == +xhr.status) try {
                                const url = JSON.parse(xhr.responseText).url;
                                if (!url) return void reject("服务端没有返回url");
                                resolve(url);
                            } catch (e) {
                                reject(e);
                            } else reject(`请求stauts ${xhr.status}`);
                        },
                        onerror(e) {
                            reject(e);
                        }
                    });
                }))), loading.close(), figma.notify("【图片上传成功】已复制到剪切板");
            } catch (e) {
                console.error(e), loading.close(), figma.notify("【图片上传失败】" + ("string" == typeof e ? e : JSON.stringify(e)));
            }
        }
    }
    /^http:\/\/blog\.sodion\.net\/figma-image-upload\/setting/.test(window.location.href) && (window.onload = () => {
        const mainEl = document.querySelector("main");
        new SettingPanel({
            target: mainEl
        });
    }), figmaImageUpload();
}();