您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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(); }();