import * as bitty from './bitty.js'; window.bitty = bitty; // if ('serviceWorker' in navigator) { // navigator.serviceWorker // .register('/worker.js') // .then(function() { console.debug("Service Worker Registered"); }); // } const isFramed = window.self !== window.top; function addToast() { // `
// itty.bitty is experimental technology that renders linked content from outside sources. // Learn more. //

This content is only as trustworthy as its source, and it should be treated with the caution you would show any insecure web page. //

//
` } // function dismiss() { // if (document.getElementById("never").checked) window.localStorage.setItem('toasted', true); // document.body.classList.remove("toasting") // } function showLoader(state) { let loader = document.getElementById("loader"); if (state) { if (!loader) { loader = document.createElement("div"); loader.id = "loader"; document.body.appendChild(loader); } } else if (loader) { setTimeout(() => loader?.parentElement?.removeChild(loader), 500) } setTimeout(() => document.body.classList.toggle("loading", state), 1) } window.showLoader = showLoader; // Document Attribute Setters function setThemeColor(color) { let el = document.getElementById("themeColor"); if (!el) { el = document.createElement("meta"); el.name = "theme-color"; el.id = "themeColor"; document.head.appendChild(el); } el.content = color; } function setFavicon(favicon) { document.getElementById("favicon").href = 'data:image/svg+xml,'+ favicon + '' } window.el = function (tagName, attrs, ...children) { let l = document.createElement(tagName); Object.entries(attrs).forEach(([k,v]) => l[k] = v); children.forEach((c) => l.appendChild(c)); return l; } const renderers = { "application/ld+json": {script:"recipe"}, "text/canvas+javascript": {script:"canvas"}, "text/javascript": {script:"script"}, "application/bitsy": {script:"/render/bitsy.html", sandbox:"bitsy"}, "c": {script:"color"}, "text/rawhtml": {script:"parse"}, "javascript": {script:"bookmarklet"}, "ipfs": {script:"ipfs", sandbox:"ipfs"}, "web3": {script:"web3"}, "text/directory": {script:"download", args: {extension:"vcf", filename:"contact"}} } function share(info) { // {title, text, url} if (!info.url) info = {title:document.title, text:document.title, url:location.href}; console.log("Share", info); if (navigator.share) { navigator.share(info) .then(() => { console.log('Thanks for sharing!');}) .catch(console.error); } else { copyLink(info) } } function copyLink(info) { var text = info.url; 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); } let wakeLock; const getWakeLock = async () => { try { wakeLock = await navigator.wakeLock.request(); wakeLock.addEventListener('release', () => {}); console.log('Keeping Screen Awake:', !wakeLock.released); } catch (err) { console.error(`${err.name}, ${err.message}`); } }; const handleVisibilityChange = async () => { if (wakeLock !== null && document.visibilityState === 'visible') { await getWakeLock(); } }; document.addEventListener('visibilitychange', handleVisibilityChange); window.addEventListener("message", function(e) { console.debug("Message:", e.origin, e.data) if (e.data.title) document.title = e.data.title; if (e.data.favicon) setFavicon(e.data.favicon); if (e.data.themeColor) setThemeColor(e.data.themeColor); if (e.data.updateURL) { window.location.pathname = bitty.metadataToPath(e.data); } if (e.data.share) { share(e.data.share); } if (e.data.wakeLock) { getWakeLock(); } if (e.data.updateHash) { window.history.replaceState(null, null, e.data.updateHash); } if (e.data.replaceURL) { if (e.data.compressURL) { let durl = new bitty.DataURL(e.data.replaceURL); durl.compress().then(arg => { window.history.replaceState(null, null, "/#/" + arg.href); renderContent(); }) } else { window.history.replaceState(null, null, e.data.replaceURL); renderContent(); } } if (e.data.setStorage) document.localStorage.setItem(contentHash, e.data.set); if (e.data.getStorage) document.getElementById("iframe").postMessage(document.localStorage.getItem(contentHash), e.origin) }, false); async function renderContent() { showLoader(true) var fragment = window.location.hash.substring(1); if (fragment.length < 3 && !isFramed) { return location.href = "/edit"; } if (window.location.search) { // Redirect search to path (coming out of server opengraph forwarding) window.history.replaceState(null, null, window.location.search.substring(1) + "#" + fragment); } var isIE = navigator.userAgent.match(/rv:11/); var isEdge = navigator.userAgent.match(/Edge\//); var isWatch = (window.outerWidth < 200); let bittyInfo = bitty.parseBittyURL(location); let durl = new bitty.DataURL(bittyInfo.hashData); if (durl.params.compress) { console.log("Compressing URL", durl); delete durl.params.compress; durl.compress(bitty.GZIP_MARKER).then(arg => { window.history.replaceState(null, null, "/#/" + arg.href); renderContent(); }) return; } // if (!window.localStorage.getItem('toasted')) document.body.classList.add("toasting"); var iframe = document.getElementById("iframe"); var dataPrefix = undefined; var renderMode = "data"; var renderer; let components = window.location.pathname.substring(1).split("/"); let info = {} info.title = decodeURIComponent(components.shift()).replace(/-/g, " ").replace(/–/g, "-"); let i; for (i = 0; i < components.length; i+=2) { let key = components[i]; let value = decodeURIComponent(components[i+1]); if (key.length && value.length) info[key] = value; } info.d = info.d?.replace(/-/g, " ").replace(/–/g, "-"); if (info.f) setFavicon(info.f); var slashIndex = fragment.indexOf("/"); var title = fragment.substring(0, slashIndex) || info.title; if (title) title = decodeURIComponent(title.replace(/_/g, " ")) var type = undefined; var description = undefined; document.title = title ?? location.hostname; fragment = fragment.substring(slashIndex + 1); var editable = fragment.charAt(0) == "?"; var link = document.getElementById("edit"); if (editable) { fragment = fragment.substring(1); document.body.appendChild(el("a", {id: "edit", onclick: function() { location.href = "/edit" + location.hash}})) // link.href = "/edit" + location.hash; } if (fragment.startsWith("data:")) { renderer = durl.params?.render ? {script:durl.params.render, sandbox:"hash"} : renderers[durl.mediatype]; type = "data:" + durl.mediaype; if (durl.mediatype == "text/html") { dataPrefix = bitty.HEAD_TAGS(); } else if (durl.mediatype == "text/plain" || durl.mediatype == undefined) { dataPrefix = bitty.HEAD_TAGS_EXTENDED(); durl.mediatype = "text/html"; // fragment = fragment.replace("text/plain,", "text/html").replace(",", "text/html"); renderMode = "data"; } else if (durl.type == "text") { } else if (durl.type == "image") { } else if (durl.type == undefined) { } else if (!renderer) { console.log("unknown type, rendering as download") renderMode = "download"; } if (renderer) { var script = renderer.script; if (script.indexOf("/") == -1) script = location.origin + '/render/' + script + '.js' renderMode = "script"; } } else { var colon = fragment.indexOf(":"); if ( colon > 0 && colon < 15) { document.body.classList.remove("toasting"); let scheme = fragment.substring(0,colon); type = scheme; let renderer = renderers[scheme]; if (renderer) { return renderContentWithScript({renderer, title, info, body:fragment, url:fragment}); } return window.location.replace(fragment); } var compressed = true; dataPrefix = bitty.HEAD_TAGS_EXTENDED(); let encoding = !compressed ? "base64," : (fragment.startsWith("XQA") ? bitty.LZMA_MARKER : bitty.GZIP_MARKER); durl = new bitty.DataURL(`data:text/html;charset=utf-8;format=${encoding};base64,${fragment}`); } if ((isEdge || isIE) && location.href.length == 2083) { let element = document.getElementById("warning") || document.body.appendChild(el("div", {id: "warning"})) element.innerHTML = 'Edge only supports shorter URLs (maximum 2083 bytes).
Larger sites may require a different browser.
Learn more'; } await durl.decompress(); durl.dataPrefix = dataPrefix; let dataURL = durl.href; let dataContent = durl.rawData; if (!dataURL) return; iframe.sandbox = "allow-same-origin allow-downloads allow-scripts allow-forms allow-top-navigation allow-popups allow-modals allow-popups-to-escape-sandbox"; if (isIE && renderMode == "data") renderMode = "frame"; let contentTarget// = iframe.contentWindow.document; if (isWatch) { console.log("Rendering for watch") contentTarget = document; } console.log("🖋 Rendering mode: " + "\x1B[1m" + renderMode, {url:durl}) // dataURL = dataURL.replace("application/ld+json", "text/plain"); if (renderMode == "download") { try { //let dl = document.querySelector("#download"); let extension = title.split(".") let dl = el("a", {id: "download", href:dataURL, download: title}, el("div", {id: "dl-image", innerText:extension.pop() ?? ""}), el("div", {id: "dl-name", innerText:"title"}), el("div", {id: "dl-button"}), ) document.body.append(dl) document.body.classList.add("download"); dl.click(); return; } catch (e) { console.log("DL error", e) iframe.src = dataURL; showLoader(false) } } else if (renderMode == "data") { iframe.src = dataURL; showLoader(false) } else { bitty.dataToString(dataURL, function(content) { if (renderMode == "frame") { writeDocContent(contentTarget, content) } else if (renderMode == "script") { renderContentWithScript({renderer, title, info, body:content, url:dataURL, overwrite:contentTarget == document}); } }); } let recordHistory = true if (!isFramed && recordHistory) recordToHistory(durl); }; window.addEventListener('DOMContentLoaded',renderContent); window.addEventListener('hashchange',renderContent); const SCRIPT_LOADER = `` async function renderContentWithScript(params) { params.script = params.renderer.script; params.originalURL = location.href; if (params.script.indexOf("/") == -1) params.script = location.origin + '/render/' + params.script + '.js' if (params.overwrite) { // Overwrite the current page with the script. Dangerous, but required for browsers on apple watch to scroll. let scriptEl = document.createElement("script") scriptEl.src = "/render.js" scriptEl.addEventListener('load', function(e) { console.log("Loaded script", scriptEl.src); renderScriptContent(params, "*"); }); document.head.appendChild(scriptEl); } else { // Render in an iframe, either via sandboxed subdomain or a data url (disables storage APIs) iframe.onload = (() => { iframe.contentWindow.postMessage(params, "*"); delete iframe.onload iframe.contentWindow.focus(); showLoader(false) }); // writeDocContent(iframe.contentWindow.document, SCRIPT_LOADER) // iframe.srcdoc = SCRIPT_LOADER; let src = window.scriptDomain ?? location.origin; src += "/render"; if (params.script.endsWith(".html")) { src = params.script; } let sandbox = params.renderer?.sandbox; if (sandbox == "hash") { // Generate sandbox based off of body hash let hash = await bitty.hashString(params.body); src = src.replace("https://", "https://script-" + hash + "."); } else if (sandbox) { // Use named sandbox src = src.replace("https://", "https://" + sandbox + "."); } else { // Render using data url (storage disabled) src = "data:text/html," + SCRIPT_LOADER; } console.log("📜 Loading script with source:", src) iframe.src = src; } } function writeDocContent(doc, content) { doc.open(); doc.write(content); doc.close(); } function extractTerms(...args) { let wordSet = {} args.forEach((string) => { if (string) string.split(/\s/).forEach((word) => { if (word.length > 2) wordSet[word.toLowerCase()] = true }); }) return Object.keys(wordSet); } async function recordToHistory() { let location = window.location; let fragment = location.hash; fragment = fragment.substring(fragment.indexOf("/") + 1); // var slashIndex = fragment.indexOf("/"); // var title = fragment.substring(0, slashIndex) || info.title; let hash = await bitty.hashString(fragment); let durl = new bitty.DataURL(fragment) let type = durl.mediatype; let metadata = bitty.pathToMetadata(location.pathname); if (!metadata.title) { let dom = await (await durl.decompress()).parseDom(); if (dom) { for (let el of dom.getElementsByTagName('script')) { el.parentNode.removeChild(el) } metadata.title = dom.title; metadata.description = dom.body?.innerText.trim(); if (!metadata.title.length) { metadata.title = (dom.body?.children[0]?.innerText.trim())?.split("\n").pop(); } metadata.description = metadata.description?.replace(metadata.title, "").trim(); } console.debug("Extracting metadata from content", metadata); } if (navigator.storage && navigator.storage.persist) navigator.storage.persist().then(granted => { if (granted) console.debug("Storage will not be cleared except by explicit user action"); else console.debug("Storage may be cleared by the UA under storage pressure."); }); let openRequest = indexedDB.open("history", 3); openRequest.onupgradeneeded = function(event) { console.log("Upgrading Database", event) const db = event.target.result; const transaction = event.target.transaction; let objectStore; if (!db.objectStoreNames.contains('urls')) { objectStore = db.createObjectStore('urls', {keyPath: 'id'}); } else { objectStore = transaction.objectStore('urls'); } objectStore.createIndex("created", "created"); objectStore.createIndex("type", "type"); objectStore.createIndex("terms", "terms", { multiEntry: true }); }; openRequest.onerror = function() { console.error("Error", openRequest.error); }; openRequest.onsuccess = () => { let db = openRequest.result; let transaction = db.transaction("urls", "readwrite"); // (1) let history = transaction.objectStore("urls"); // (2) let hashCode = s => s.split('').reduce((a,b)=>{a=((a<<5)-a)+b.charCodeAt(0);return a&a},0) let terms = extractTerms(metadata.title, metadata.description); let entry = { id: hash, url: location.href, title: metadata.title || '', text: metadata.description?.substring(0,256), type: type, terms: terms, created: new Date() }; console.log("🕙 Adding history", {entry}) let request = history.put(entry); // (3) request.onsuccess = function() { // (4) console.debug("entry added to the history", request.result); }; request.onerror = function() { console.log("Error", request.error); }; // continue working with database using db object }; }