From a6fbef9efe1950786d69da56c89ce1af1bf34dcf Mon Sep 17 00:00:00 2001 From: Nicholas Jitkoff Date: Sat, 11 Feb 2023 09:32:12 -0700 Subject: [PATCH] Add Menu --- docs/bitty-menu.js | 174 ++++++++++++++++++++++++++++++++++++++++++ docs/bitty.js | 12 ++- docs/index.css | 184 ++++++++++++++++++++++++++++++++++++++++++--- docs/index.html | 10 +-- docs/index.js | 48 ++++++++++-- 5 files changed, 401 insertions(+), 27 deletions(-) create mode 100644 docs/bitty-menu.js diff --git a/docs/bitty-menu.js b/docs/bitty-menu.js new file mode 100644 index 0000000..cdd8b31 --- /dev/null +++ b/docs/bitty-menu.js @@ -0,0 +1,174 @@ +class Menu { + /** + * @constructor + */ + constructor(button) { + this.button = button; + } + + makeTinyurl() { + console.log("url", location.href) + location.href='https://tinyurl.com/create.php?url=' + encodeURIComponent(location.href) + } + + makeText() { + console.log("url", location.href) + location.href='imessage:&body=' + encodeURIComponent(location.href) + } + + makeQR() { + + } + sendEmail() { + console.log("url", location.href) + location.href='mailto:info@example.com?body=' + encodeURIComponent(location.href) + } + + makeToot() { + return false; + } + + makeTweet() { + var url = + "https://twitter.com/intent/tweet?url=" + encodeURIComponent(location.href); + window.open(url, "_blank"); + return false; + } + + showAbout() { + var url = "https://about.bitty.site"; + window.open(url, "_blank"); + return false; + } + copyLink() { + var text = location.href; + var dummy = document.createElement("input"); + document.body.appendChild(dummy); + dummy.value = text; + dummy.select(); + document.execCommand("copy"); + document.body.removeChild(dummy); + + document.body.classList.add("copied"); + setTimeout(function() { + document.body.classList.remove("copied"); + }, 2000); + } + + + systemShare( info) { + if (!info.url) info = {title:document.title, text:document.title, url:location.href}; + + if (navigator.share) { + navigator.share(info) + .then(() => { console.log('Shared!');}) + .catch(console.error); + } else { + copyLink(info) + } + } + + icons = { + "qr": ``, + "text": ` + `, + "email": ` + `, + "mastodon": ` + `, + "twitter": ` + `, + } + + selectField(target) { + target.select(); + target.scrollLeft = 0; + } + + close() { + let menu = document.querySelector("dialog.menu"); + menu?.close(); + this.button?.classList.remove("open") + } + show(info) { + let url = location.href; + let fullMenu = !info; + this.button?.classList.add("open") + let urlField = el("input.url", {value:url, readonly:true}); + urlField.onclick = (e) => this.selectField(urlField); + let menu = el("dialog.menu", {onclick: (e) => { + if (e.target.tagName == 'DIALOG') { + e.target.close(); + this.button?.classList.remove("open") + } + }}, el("div.menu-container", {}, + urlField, + el("div.menu-icons", + el("div.menu-item", {id: "qrcode", onclick:this.makeQR, innerHTML:this.icons.qr} ), + el("div.menu-item", {id: "text", onclick:this.makeText, innerHTML:this.icons.text} ), + el("div.menu-item", {id: "email", onclick:this.sendEmail, innerHTML:this.icons.email} ), + // el("div.menu-item", {id: "mastodon", onclick:this.tootLink, innerHTML:this.icons.mastodon} ), + el("div.menu-item", {id: "twitter", onclick:this.makeTweet, innerHTML:this.icons.twitter} ), + ), + el("div.menu-item", {onclick:this.copyLink}, "copy"), + el("div.menu-item", {onclick:this.systemShare}, "share…"), + // el("div.menu-item", {onclick:this.makeTinyurl}, "shorten"), + // el("div.menu-item", {onclick:this.makeTinyurl}, "edit…"), + fullMenu ? el("hr") : null, + fullMenu ? el("div.menu-item", {onclick:this.showAbout}, "itty bitty") : null, + ) + ) + + document.body.appendChild(menu) + + if (info) { + menu.style.display = "block"; + + let x = info.offset.left; + let y = info.offset.top; + let w = menu.offsetWidth; + let h = menu.offsetHeight + x -= w / 2; + y -= h / 2; + + let overflowX = x + w - window.innerWidth + 8; + let overflowY = y + h - window.innerHeight + 8; + + + if (overflowX > 0) x -= overflowX; + if (overflowY > 0) y -= overflowY; + + x = Math.round(Math.max(8, x)); + y = Math.round(Math.max(8, y)); + + menu.style.left = x + "px"; + menu.style.top = y + "px"; + + + menu.style.removeProperty("display") + + // if (window.innerWidth - info.offset.left > 300) { + // menu.style.left = info?.offset.left + "px"; + // } else { + // menu.style.right = (window.innerWidth - info?.offset.right) + "px"; + // } + + // if (window.innerHeight - info.offset.bottom > 300) { + // menu.style.top = info?.offset.bottom + "px"; + // } else { + // menu.style.top = "auto"; + + // menu.style.bottom = (window.innerHeight - info?.offset.top) + "px"; + // } + // console.log("info", info, menu, window.innerWidth, window.innerHeight); + } + menu.showModal() + this.selectField(urlField) + + } + +} + +export { + Menu +}; diff --git a/docs/bitty.js b/docs/bitty.js index cb224bb..158db60 100644 --- a/docs/bitty.js +++ b/docs/bitty.js @@ -1,5 +1,9 @@ const padForBase64 = (s, c = " ") => s.padEnd(s.length + (3 - s.length % 3) % 3, c) -const HEAD_TAGS = () => btoa(padForBase64('\n')); +const HEAD_TAGS = (prefixes) => { + let tags = [''] + prefixes?.split(" ").forEach((p) => tags.push(p.endsWith(".css") ? `` : ``)) + return btoa(padForBase64(tags.join("\n"))); +}; const HEAD_TAGS_EXTENDED = () => btoa(padForBase64(``)); const dataUrlRE = @@ -132,8 +136,8 @@ class DataURL { } compress = async (format = GZIP_MARKER) => { - let rawData = this.encoding ? await base64ToByteArray(this.data) : stringToByteArray(this.data); - let compressedData = await compressData(rawData, format); + this.rawData = this.encoding ? await base64ToByteArray(this.data) : stringToByteArray(this.data); + let compressedData = await compressData(this.rawData, format); if (this.params.cipher && this.params._password) { let encryptedData = await encryptData(this.params.cipher, this.params._password, compressedData); @@ -262,7 +266,7 @@ async function decompressDataGzip(data) { async function compressDataGzip(data) { if (typeof CompressionStream !== 'undefined') { let blob = new Blob([data]) - const stream = blob.stream().pipeThrough(new CompressionStream("gzip")); + const stream = blob.stream().pipeThrough(new CompressionStream("deflate")); let response = await new Response (stream).arrayBuffer().catch(e => {console.error("CompressionStream error", e)}) if (response) return response } diff --git a/docs/index.css b/docs/index.css index e7ac983..70270e4 100644 --- a/docs/index.css +++ b/docs/index.css @@ -3,10 +3,23 @@ body { margin:0; } -@media(prefers-color-scheme: dark){ +:root { + --text-color: #16161d; + --background-color: white; + --shadow-color: var(--text-color); + --backdrop-color: red; + /* repeating-conic-gradient(rgba(255,255,255,1.0) 0% 25%, transparent 0% 50%) 50% / 4px 4px; */ +} +@media (prefers-color-scheme: dark) { + :root { + --text-color:white; + --background-color: #16161d; + --backdrop: repeating-conic-gradient(rgba(128,128,128,.5) 0% 25%, transparent 0% 50%) 50% / 4px 4px; + + } body{ - color:white; - background-color:#121212; + color:var(--text-color); + background-color:var(--background-color); } } @@ -18,6 +31,48 @@ body { width: 100%; height: 100%; } + + +#menu { + width: 48px; + height: 48px; + top:0; + right: 0; + position:fixed; + z-index:9; + border:none; + color: #16161d; + opacity: .8; + border-bottom-left-radius: 0; + font-size: 18px; + transition: font-size 100ms cubic-bezier(0.4, 0.0, 0.2, 1);; + text-align: right; +} + +#menu svg { + width: 1em; + height: 1em; +} + +#menu.open, +#menu:hover { + color:white; + opacity:1.0; + font-size: 48px; + /* transition: font-size 1000ms ease-in; */ +} +#menu:hover svg{ + background:#16161d; + +} + +#menu:hover svg .inner { + fill:white; +} +#menu:hover svg .outer { + fill:transparent; +} + #edit { font-family: monospace; font-weight: bold; @@ -216,21 +271,22 @@ body:not(.loading) #loader { } -dialog { +.dialog { border: 2px solid white; border-radius: 4px; box-shadow: 0 0 0 2px black, inset 0 0 0 3px black; - min-width: 320px; + min-width: 300px; padding: 1.2em; } -dialog, dialog button {font-size: 18px;font-weight: 600;font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";} +.dialog, .dialog button {font-size: 18px;font-weight: 600;font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";} dialog::backdrop { - background: repeating-conic-gradient(rgba(128,128,128,.5) 0% 25%, transparent 0% 50%) 50% / 4px 4px; + background: rgba(255,255,255,0.5); + /* repeating-conic-gradient(rgba(255,255,255,.5) 0% 25%, transparent 0% 50%) 50% / 4px 4px; */ } -dialog button { +.dialog button { border: 1px solid white; border-radius: 9px; box-shadow: inset 0 0 0 2px black; @@ -239,13 +295,121 @@ dialog button { background-color: white; margin-left:1em; padding: 0 0.8em; + color:currentColor; } -dialog button:last-child { + +.dialog button:last-child { box-shadow: 0 0 0 4px black, inset 0 0 0 2px black; } -dialog form { +.dialog form { text-align: right; margin-top: 1em; font-size: 18px; +} + +dialog.menu { + top: 48px; + right: 0; + position: fixed; + margin: 0; + left: auto; + padding:0; + bottom: 0; + max-width:16em; + font-weight:bold; + padding-bottom:.8em; +} + +.menu-container { + display:flex; + flex-direction: column; + +} + +.menu .url { + overflow:hidden; + text-overflow:ellipsis; + white-space: nowrap; + opacity: 0.5; + padding: 0.2em 0.8em; + width:auto; + border: 1.9px solid black; + flex: 1 1 auto; + margin: 2px; + font-size: 16px; +} + +.menu hr { + background-color:var(--text-color); + height:2px; + width: 100%; +} +.menu .url:focus { +} +.menu .url:focus-within { +} + +.menu .menu-icons { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0; + cursor:default; +} + +.menu .menu-icons .menu-item { + width: 24px; + height: 24px; + padding: 12px; + /* display:flex; */ + /* justify-content: center; */ +} +.menu .menu-icons .menu-item svg{ + width:2em; + height:2em; + font-size:12px; +} + +.menu .menu-item { + padding: 0.65em 1em; + cursor:default; +} + +.menu .menu-item:hover { + background-color:black; + color:white; +} + + + +.menu { + /* width: auto; */ + color: var(--text-color); + /* background-color: var(--background-color); */ + border: 2px solid currentColor; + padding: 0.5em 0; + box-shadow: 2px 2px var(--shadow-color); + margin-right: 2px; + border-radius: 2px; + margin-top: -2px; + z-index: 300; + /* top: 100px; */ + /* text-align: left; */ + /* float: right; */ + /* width: 10rem; */ + position: absolute; + /* right: 1rem; */ + background-color:var(--background-color); + } + + +.menu .menu-item { + color: var(--text-color); + text-decoration: none; + line-height: 2em; + text-align: left; + display: block; + padding: 0 1em; + cursor: default; } \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index 0bb0605..042f765 100644 --- a/docs/index.html +++ b/docs/index.html @@ -4,16 +4,14 @@ - - - - - + + + + - diff --git a/docs/index.js b/docs/index.js index 4e2a70e..066c5ab 100644 --- a/docs/index.js +++ b/docs/index.js @@ -1,4 +1,5 @@ import * as bitty from '/bitty.js'; + import * as bitty_menu from '/bitty-menu.js'; window.bitty = bitty; @@ -22,7 +23,35 @@ // if (document.getElementById("never").checked) window.localStorage.setItem('toasted', true); // document.body.classList.remove("toasting") // } - + + function getIframe() { + if (!document.iframe) { + let iframe = document.createElement('iframe'); + iframe.id = "iframe"; + iframe.sandbox = "allow-same-origin allow-scripts allow-forms allow-top-navigation allow-popups allow-modals allow-popups-to-escape-sandbox"; + if (iframe.sandbox.supports('allow-downloads')) iframe.sandbox.add('allow-downloads'); + document.body.appendChild(iframe); + document.iframe = iframe; + } + return document.iframe; + } + + async function getMenu() { + if (!document.menuButton) { + let menuButton = document.createElement('div'); + menuButton.id = "menu"; + menuButton.innerHTML = `` + menuButton.onclick = () => { + let menu = new bitty_menu.Menu(menuButton); + console.log("men", menu) + menu.show() + } + document.body.appendChild(menuButton); + document.menuButton = menuButton; + } + return document.menuButton; + } + getMenu() let autohideTimeout; function showLoader(state) { let loader = document.getElementById("loader"); @@ -79,6 +108,13 @@ function share(info) { // {title, text, url} + + let menu = new bitty_menu.Menu(); + console.log("men", menu) + menu.show(info) + return; + } + function systemShare(info) { if (!info.url) info = {title:document.title, text:document.title, url:location.href}; if (navigator.share) { @@ -155,7 +191,7 @@ window.addEventListener("message", function(e) { - console.debug("Message:", e.origin, e.data) + console.debug("Message:", e.origin, e.data, e) showLoader(false); if (e.data.loading != undefined) showLoader(e.data.loading); @@ -200,7 +236,7 @@ function showError(error) { console.warn("🛑", error) - let dialog = el("dialog", + let dialog = el("dialog.dialog", el("div", {}, error), el("form", {method: "dialog"}, @@ -244,7 +280,7 @@ // if (!window.localStorage.getItem('toasted')) document.body.classList.add("toasting"); - var iframe = document.getElementById("iframe"); + var iframe = getIframe(); var dataPrefix = undefined; var renderMode = "data"; var renderer; @@ -286,7 +322,7 @@ //if (render.script == "parse") renderer.sandbox = "none" type = "data:" + durl.mediaype; if (durl.mediatype == "text/html") { - dataPrefix = bitty.HEAD_TAGS(); + dataPrefix = bitty.HEAD_TAGS(durl.params?.prefix); } else if (durl.mediatype == "text/plain" || durl.mediatype == undefined) { dataPrefix = bitty.HEAD_TAGS_EXTENDED(); @@ -352,8 +388,6 @@ let dataContent = durl.rawData; if (!dataURL) return; - iframe.sandbox = "allow-same-origin allow-scripts allow-forms allow-top-navigation allow-popups allow-modals allow-popups-to-escape-sandbox"; - if (iframe.sandbox.supports('allow-downloads')) iframe.sandbox.add('allow-downloads'); if (isIE && renderMode == "data") renderMode = "frame"; let overwriteSelf = isWatch && !params.script.endsWith(".html");