1/** 2 * SuperEdit SDK v2.0.0 (protocol 2) 3 * Embed SuperEdit canvas editor in any web application. 4 * 5 * Usage: 6 * <script src="https://superedit.ai/sdk/v1/superedit.js"></script> 7 * <script> 8 * const editor = SuperEdit.open({ 9 * mode: "tab", // "iframe" | "popup" | "tab" 10 * designUrl: "https://example.com/my-image.png", 11 * appName: "My App", 12 * apiKey: "se_live_...", 13 * onSave: (result) => { 14 * console.log(result.imageDataUrl); // base64 PNG 15 * console.log(result.r2Url); // server-side backup URL (may be null) 16 * console.log(result.canvasJson); // fabric.js JSON 17 * console.log(result.transparent); // boolean â was BG transparent? 18 * }, 19 * onError: (err) => console.warn(err.code, err.message), 20 * onClose: () => console.log("Editor closed"), 21 * }); 22 * </script> 23 * 24 * Methods: 25 * editor.close() - Close the editor 26 * editor.loadImage(url) - Load a new image into the editor 27 * editor.loadJson(json) - Load a fabric.js JSON state 28 * 29 * Events (via postMessage, all carry protocol: 2): 30 * superedit:ready - Editor is loaded and ready 31 * superedit:save - User clicked "Save & Return" (imageDataUrl, r2Url, canvasJson, width, height, transparent) 32 * superedit:close - Editor requests close (after save) â SDK closes the overlay automatically 33 * superedit:error - Something failed in the editor ({ code, message }) â onError 34 * 35 * Error codes: ready_timeout, design_load_failed, embed_blocked 36 * 37 * Modes: 38 * iframe (default) - Opens inside a fullscreen overlay iframe on the current page 39 * popup - Opens in a popup window (centered, configurable size) 40 * tab - Opens in a new browser tab (recommended for 3rd-party integrations) 41 */ 42 43(function (global) { 44 "use strict"; 45 46 var SUPEREDIT_HOST = "https://superedit.ai"; 47 48 // Allow override for development 49 if (global.__SUPEREDIT_HOST__) { 50 SUPEREDIT_HOST = global.__SUPEREDIT_HOST__; 51 } 52 53 /** 54 * @param {Object} options 55 * @param {string} [options.designUrl] - URL of the image to edit 56 * @param {string} [options.appName] - Your app name (shown in "Save & Return to {appName}") 57 * @param {string} [options.returnUrl] - URL to redirect after save (optional, for popup mode) 58 * @param {string} [options.apiKey] - Your SuperEdit API key (for analytics & access control) 59 * @param {string} [options.container] - CSS selector for iframe container (default: fullscreen overlay) 60 * @param {string} [options.mode] - "iframe" | "popup" | "tab" (default: "iframe") 61 * @param {number} [options.width] - Popup width (default: 1200) 62 * @param {number} [options.height] - Popup height (default: 800) 63 * @param {Function} [options.onReady] - Called when editor is ready 64 * @param {Function} [options.onSave] - Called with { imageDataUrl, canvasJson, width, height } 65 * @param {Function} [options.onClose] - Called when editor is closed 66 */ 67 function open(options) { 68 options = options || {}; 69 var mode = options.mode || "iframe"; 70 71 // Build embed URL â large data: URIs are uploaded via POST first 72 var params = new URLSearchParams(); 73 var deferredDesignUrl = null; 74 75 if (options.designUrl) { 76 if (options.designUrl.indexOf("data:") === 0) { 77 // Large base64 data â upload via POST, open with token 78 deferredDesignUrl = options.designUrl; 79 } else { 80 params.set("designUrl", options.designUrl); 81 } 82 } 83 if (options.appName) params.set("appName", options.appName); 84 if (options.returnUrl) params.set("returnUrl", options.returnUrl); 85 if (options.apiKey) params.set("apiKey", options.apiKey); 86 params.set("origin", global.location.origin); 87 88 // For large data URIs: open editor immediately (short URL), then send design 89 // via postMessage after editor signals ready. This avoids URL length limits 90 // and cross-origin sync XHR issues. 91 var url = SUPEREDIT_HOST + "/embed?" + params.toString(); 92 93 var iframe = null; 94 var popup = null;
95 var overlay = null; 96 var closed = false; 97 var readyReceived = false; 98 var readyTimer = null; 99 100 function emitError(code, message) { 101 if (options.onError) { 102 try { options.onError({ code: code, message: message }); } catch (e) {} 103 } 104 } 105 106 // âââ postMessage handler âââââââââââââââââââââââââââââ 107 function onMessage(ev) { 108 // In dev, allow localhost origins 109 if (ev.origin !== SUPEREDIT_HOST && ev.origin !== global.location.origin) return; 110 var msg = ev.data; 111 if (!msg || typeof msg !== "object" || msg.source !== "superedit") return; 112 // msg.protocol: 2 on current editors; absent on older ones â both accepted 113 114 if (msg.type === "superedit:ready") { 115 readyReceived = true; 116 if (readyTimer) { clearTimeout(readyTimer); readyTimer = null; } 117 // If we have a deferred large designUrl, send it now via postMessage 118 if (deferredDesignUrl) { 119 var target = iframe ? iframe.contentWindow : popup; 120 if (target) { 121 target.postMessage({ 122 source: "superedit", 123 protocol: 2, 124 type: "superedit:load-design", 125 designUrl: deferredDesignUrl, 126 }, SUPEREDIT_HOST); 127 } 128 deferredDesignUrl = null; // only send once 129 } 130 if (options.onReady) options.onReady(); 131 } 132 133 if (msg.type === "superedit:save" && options.onSave) { 134 options.onSave({ 135 imageDataUrl: msg.imageDataUrl, 136 canvasJson: msg.canvasJson, 137 width: msg.width, 138 height: msg.height, 139 transparent: !!msg.transparent, 140 r2Url: msg.r2Url || null, 141 }); 142 } 143 144 // Editor asks us to close (e.g. after save in iframe mode where 145 // window.close() is a no-op) 146 if (msg.type === "superedit:close") { 147 instance.close(); 148 } 149 150 if (msg.type === "superedit:error") { 151 emitError(msg.code || "unknown", msg.message || "SuperEdit error"); 152 } 153 } 154 155 global.addEventListener("message", onMessage); 156 157 // Ready-handshake watchdog: if the editor never signals ready, surface 158 // it instead of failing silently. 159 var readyTimeoutMs = options.readyTimeoutMs || 20000; 160 readyTimer = setTimeout(function () { 161 if (!readyReceived && !closed) { 162 emitError("ready_timeout", "SuperEdit editor did not become ready within " + readyTimeoutMs + "ms"); 163 } 164 }, readyTimeoutMs); 165 166 // âââ iframe mode âââââââââââââââââââââââââââââââââââââ 167 if (mode === "iframe") { 168 // Create fullscreen overlay or attach to container 169 if (options.container) { 170 var container = document.querySelector(options.container); 171 if (!container) throw new Error("SuperEdit: container not found: " + options.container); 172 iframe = document.createElement("iframe"); 173 iframe.src = url; 174 iframe.style.cssText = "width:100%;height:100%;border:none;"; 175 iframe.setAttribute("allow", "clipboard-write"); 176 container.innerHTML = ""; 177 container.appendChild(iframe); 178 } else { 179 // Fullscreen overlay 180 overlay = document.createElement("div"); 181 overlay.style.cssText = 182 "position:fixed;top:0;left:0;right:0;bottom:0;z-index:999999;" + 183 "background:rgba(0,0,0,0.6);display:flex;align-items:center;justify-content:center;" + 184 "backdrop-filter:blur(4px);animation:se-fade-in 0.2s ease-out;"; 185 overlay.innerHTML = 186 '<style>@keyframes se-fade-in{from{opacity:0}to{opacity:1}}@keyframes se-slide-up{from{transform:translateY(20px);opacity:0}to{transform:translateY(0);opacity:1}}</style>'; 187 188 var frame = document.createElement("div"); 189 frame.style.cssText = 190 "width:95vw;height:92vh;max-width:1400px;border-radius:16px;overflow:hidden;" + 191 "box-shadow:0 25px 60px rgba(0,0,0,0.3);animation:se-slide-up 0.25s ease-out;position:relative;"; 192 193 // Close button on overlay 194 var closeBtn = document.createElement("button"); 195 closeBtn.innerHTML = "✕"; 196 closeBtn.style.cssText = 197 "position:absolute;top:12px;right:12px;z-index:10;width:32px;height:32px;" + 198 "border-radius:50%;border:none;background:rgba(0,0,0,0.5);color:#fff;" + 199 "font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center;" + 200 "transition:background 0.2s;"; 201 closeBtn.onmouseover = function () { closeBtn.style.background = "rgba(0,0,0,0.7)"; }; 202 closeBtn.onmouseout = function () { closeBtn.style.background = "rgba(0,0,0,0.5)"; }; 203 closeBtn.onclick = function () { instance.close(); }; 204 205 iframe = document.createElement("iframe"); 206 iframe.src = url; 207 iframe.style.cssText = "width:100%;height:100%;border:none;"; 208 iframe.setAttribute("allow", "clipboard-write"); 209 210 frame.appendChild(closeBtn); 211 frame.appendChild(iframe); 212 overlay.appendChild(frame); 213 document.body.appendChild(overlay); 214 215 // Close on backdrop click 216 overlay.addEventListener("click", function (e) { 217 if (e.target === overlay) instance.close(); 218 }); 219 220 // Close on Escape 221 var onEsc = function (e) { 222 if (e.key === "Escape") instance.close(); 223 }; 224 global.addEventListener("keydown", onEsc); 225 } 226 } 227 228 // âââ popup mode ââââââââââââââââââââââââââââââââââââââ 229 if (mode === "popup") { 230 var w = options.width || 1200; 231 var h = options.height || 800; 232 var left = (screen.width - w) / 2; 233 var top = (screen.height - h) / 2;
234 popup = global.open(url, "superedit", "width=" + w + ",height=" + h + ",left=" + left + ",top=" + top + ",resizable=yes,scrollbars=no"); 235 236 // Poll for popup close 237 var pollTimer = setInterval(function () { 238 if (popup && popup.closed) { 239 clearInterval(pollTimer); 240 if (!closed && options.onClose) options.onClose(); 241 closed = true; 242 global.removeEventListener("message", onMessage); 243 } 244 }, 500); 245 } 246 247 // âââ tab mode (new browser tab) âââââââââââââââââââââ 248 if (mode === "tab") { 249 popup = global.open(url, "_blank"); 250 251 // Poll for tab close 252 var tabPollTimer = setInterval(function () { 253 if (popup && popup.closed) { 254 clearInterval(tabPollTimer); 255 if (!closed && options.onClose) options.onClose(); 256 closed = true; 257 global.removeEventListener("message", onMessage); 258 } 259 }, 500); 260 } 261 262 // âââ instance API ââââââââââââââââââââââââââââââââââââ 263 var instance = { 264 close: function () { 265 if (closed) return; 266 closed = true; 267 if (readyTimer) { clearTimeout(readyTimer); readyTimer = null; } 268 global.removeEventListener("message", onMessage); 269 if (onEsc) global.removeEventListener("keydown", onEsc); 270 if (overlay && overlay.parentNode) overlay.parentNode.removeChild(overlay); 271 if (popup && !popup.closed) popup.close(); 272 if (options.onClose) options.onClose(); 273 }, 274 275 loadImage: function (imageUrl) { 276 var target = iframe ? iframe.contentWindow : popup; 277 if (!target) return; 278 target.postMessage({ source: "superedit", protocol: 2, type: "superedit:load", imageUrl: imageUrl }, SUPEREDIT_HOST); 279 }, 280 281 loadJson: function (json) { 282 var target = iframe ? iframe.contentWindow : popup; 283 if (!target) return; 284 target.postMessage({ source: "superedit", protocol: 2, type: "superedit:loadJson", json: json }, SUPEREDIT_HOST); 285 }, 286 287 getIframe: function () { return iframe; }, 288 getPopup: function () { return popup; }, 289 }; 290 291 return instance; 292 } 293 294 // âââ Expose global ââââââââââââââââââââââââââââââââââââ 295 global.SuperEdit = { 296 open: open, 297 version: "2.0.0", 298 protocol: 2, 299 }; 300 301})(typeof window !== "undefined" ? window : this);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.