Add a guest name above and the links appear here.
'; return; } list.forEach(function(g){ var row = document.createElement("div"); row.className = "row"; var who = document.createElement("span"); who.className = "who"; who.textContent = g.guest; var url = document.createElement("span"); url.className = "url"; url.textContent = linkFor(g); var b1 = document.createElement("button"); b1.className = "mini"; b1.type = "button"; b1.textContent = "Copy link"; var b2 = document.createElement("button"); b2.className = "mini"; b2.type = "button"; b2.textContent = "Copy message"; b1.addEventListener("click", function(){ copy(linkFor(g), "Link copied"); }); b2.addEventListener("click", function(){ copy(g.guest + ", your invitation from " + (data.society || "us") + ":\n" + linkFor(g), "Message copied"); }); row.appendChild(who); row.appendChild(url); row.appendChild(b1); row.appendChild(b2); box.appendChild(row); }); } function copy(text, msg){ if(navigator.clipboard && navigator.clipboard.writeText){ navigator.clipboard.writeText(text).then(function(){ toast(msg); }, function(){ fallbackCopy(text, msg); }); } else fallbackCopy(text, msg); } function fallbackCopy(text, msg){ var t = document.createElement("textarea"); t.value = text; t.style.position = "fixed"; t.style.opacity = "0"; document.body.appendChild(t); t.select(); try{ document.execCommand("copy"); toast(msg); }catch(e){ toast("Copy it by hand"); } t.remove(); } /* a plain, stored zip — no library needed */ var CRC = (function(){ var t = new Uint32Array(256); for(var n = 0; n < 256; n++){ var c = n; for(var k = 0; k < 8; k++) c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1); t[n] = c >>> 0; } return t; })(); function crc32(u8){ var c = 0xFFFFFFFF; for(var i = 0; i < u8.length; i++) c = CRC[(c ^ u8[i]) & 0xFF] ^ (c >>> 8); return (c ^ 0xFFFFFFFF) >>> 0; } function utf8(str){ if(window.TextEncoder) return new TextEncoder().encode(str); var s = unescape(encodeURIComponent(str)), a = new Uint8Array(s.length); for(var i = 0; i < s.length; i++) a[i] = s.charCodeAt(i); return a; } function unb64(b64){ var bin = atob(b64), a = new Uint8Array(bin.length); for(var i = 0; i < bin.length; i++) a[i] = bin.charCodeAt(i); return a; } function zip(files){ var parts = [], central = [], off = 0; files.forEach(function(f){ var name = utf8(f.name), crc = crc32(f.data), len = f.data.length; var lh = new Uint8Array(30 + name.length), lv = new DataView(lh.buffer); lv.setUint32(0, 0x04034b50, true); lv.setUint16(4, 20, true); lv.setUint16(6, 0x0800, true); lv.setUint16(8, 0, true); lv.setUint16(10, 0, true); lv.setUint16(12, 0x2821, true); lv.setUint32(14, crc, true); lv.setUint32(18, len, true); lv.setUint32(22, len, true); lv.setUint16(26, name.length, true); lv.setUint16(28, 0, true); lh.set(name, 30); var ch = new Uint8Array(46 + name.length), cv = new DataView(ch.buffer); cv.setUint32(0, 0x02014b50, true); cv.setUint16(4, 20, true); cv.setUint16(6, 20, true); cv.setUint16(8, 0x0800, true); cv.setUint16(10, 0, true); cv.setUint16(12, 0, true); cv.setUint16(14, 0x2821, true); cv.setUint32(16, crc, true); cv.setUint32(20, len, true); cv.setUint32(24, len, true); cv.setUint16(28, name.length, true); cv.setUint16(30, 0, true); cv.setUint16(32, 0, true); cv.setUint16(34, 0, true); cv.setUint16(36, 0, true); cv.setUint32(38, 0, true); cv.setUint32(42, off, true); ch.set(name, 46); parts.push(lh, f.data); central.push(ch); off += lh.length + len; }); var cdSize = central.reduce(function(n, c){ return n + c.length; }, 0); var end = new Uint8Array(22), ev = new DataView(end.buffer); ev.setUint32(0, 0x06054b50, true); ev.setUint16(4, 0, true); ev.setUint16(6, 0, true); ev.setUint16(8, files.length, true); ev.setUint16(10, files.length, true); ev.setUint32(12, cdSize, true); ev.setUint32(16, off, true); ev.setUint16(20, 0, true); return new Blob(parts.concat(central, [end]), { type: "application/zip" }); } function buildSite(){ var list = guestList(); if(!list.length){ toast("Add a guest name first"); return null; } var files = [ { name: "assets/invite.css", data: utf8(WEB.css) }, { name: "assets/invite.js", data: utf8(WEB.js) } ]; Object.keys(WEB.bin).forEach(function(k){ files.push({ name: k, data: unb64(WEB.bin[k]) }); }); files.push({ name: "index.html", data: utf8( WEB.index.split("__SOCIETY__").join(esc(data.society || "Invitation")) .split("__FOOT__").join(esc(data.foot || "By invitation only"))) }); /* one page that takes the name from the address: upload it once and a new guest never needs another upload, only another link */ var shared = JSON.parse(JSON.stringify(data)); shared.guest = ""; shared.guestTitle = ""; files.push({ name: "i.html", data: utf8( WEB.page.replace("__TITLE__", function(){ return esc(data.society || "Invitation"); }) .replace("/*__DATA__*/{}", function(){ return JSON.stringify(shared); })) }); list.forEach(function(g){ var one = JSON.parse(JSON.stringify(g)); delete one._slug; var title = esc((one.guest ? one.guest + " — " : "") + (one.society || "Invitation")); var json = JSON.stringify(one); var page = WEB.page.replace("__TITLE__", function(){ return title; }) .replace("/*__DATA__*/{}", function(){ return json; }); files.push({ name: g._slug + ".html", data: utf8(page) }); }); return { blob: zip(files), count: list.length }; } $("site").addEventListener("input", function(){ site = this.value; drawLinks(); }); $("bulk").addEventListener("input", drawLinks); $("onePage").addEventListener("change", drawLinks); $("bSite").addEventListener("click", function(){ var out = buildSite(); if(!out) return; var url = URL.createObjectURL(out.blob); var a = document.createElement("a"); a.href = url; a.download = "invitation-website.zip"; a.rel = "noopener"; document.body.appendChild(a); a.click(); a.remove(); setTimeout(function(){ URL.revokeObjectURL(url); }, 6000); toast("Website saved — " + out.count + " guest" + (out.count > 1 ? "s" : "")); }); $("bSave").addEventListener("click", function(){ var out = JSON.parse(JSON.stringify(data)); out._site = site; out._guests = $("bulk").value; /* the list matters most: losing it breaks links */ save("invitation-setup.json", JSON.stringify(out, null, 2), "application/json"); toast("Setup saved"); }); drawLinks(); $("bLoad").addEventListener("click", function(){ $("fileIn").click(); }); $("fileIn").addEventListener("change", function(){ var f = this.files && this.files[0]; if(!f) return; var r = new FileReader(); r.onload = function(){ try{ var o = JSON.parse(r.result); if(o._site != null){ site = String(o._site); $("site").value = site; } if(o._guests != null) $("bulk").value = String(o._guests); setAll(o); drawLinks(); toast("Setup loaded"); } catch(e){ toast("That file could not be read"); } }; r.readAsText(f); this.value = ""; }); })();