From 549ef07b8ff71b2750c62eaaf280015cc7b75578 Mon Sep 17 00:00:00 2001 From: jrmann100 Date: Sat, 22 Jun 2024 01:38:08 +0000 Subject: [PATCH] =?UTF-8?q?Deploying=20to=20gh-pages=20from=20@=20jrmann10?= =?UTF-8?q?0/jrmann100.github.io@c0b2a0161db9affb7d73a0aaa9e082cf5ec741f4?= =?UTF-8?q?=20=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fragments/utilities/clipboard.html | 80 ++++++++------- index.html | 5 +- resume.html | 5 +- ripple.html | 5 +- static/js/clipboard.js | 159 +++++++++++++++++++++++++++++ utilities/clipboard.html | 85 +++++++-------- utilities/dashboard.html | 5 +- utilities/godango.html | 5 +- 8 files changed, 265 insertions(+), 84 deletions(-) create mode 100644 static/js/clipboard.js diff --git a/fragments/utilities/clipboard.html b/fragments/utilities/clipboard.html index 6dff09d..1e188f7 100644 --- a/fragments/utilities/clipboard.html +++ b/fragments/utilities/clipboard.html @@ -1,9 +1,43 @@ ~jordan/utilities/clipboard @@ -15,42 +49,10 @@ >This is a live-updating dashboard; as such, you need to enable JavaScript for it to work. - +
- diff --git a/index.html b/index.html index 52076f4..f9114ed 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ - + @@ -144,6 +144,9 @@

  • dashboard - various info widgets
  • +
  • + clipboard - inspect what you copy +
  • diff --git a/resume.html b/resume.html index 36bc7f8..c923b6f 100644 --- a/resume.html +++ b/resume.html @@ -2,7 +2,7 @@ - + @@ -98,6 +98,9 @@

  • dashboard - various info widgets
  • +
  • + clipboard - inspect what you copy +
  • diff --git a/ripple.html b/ripple.html index 914ac7a..806ca20 100644 --- a/ripple.html +++ b/ripple.html @@ -2,7 +2,7 @@ - + @@ -86,6 +86,9 @@

  • dashboard - various info widgets
  • +
  • + clipboard - inspect what you copy +
  • diff --git a/static/js/clipboard.js b/static/js/clipboard.js new file mode 100644 index 0000000..3ac5fa7 --- /dev/null +++ b/static/js/clipboard.js @@ -0,0 +1,159 @@ +/** + * @file Clipboard helper. Parses and renders data of various formats from the clipboard. + * @author Jordan Mann + */ + +/** + * Add a listener to render data from the clipboard on paste. + */ +export default function main() { + document.body.addEventListener('paste', (ev) => { + ev.preventDefault(); + /** + * @type {HTMLElement | null} + */ + const outputs = document.querySelector('#outputs'); + if (outputs === null) { + throw new Error('Could not found output container.'); + } + + let items = ev.clipboardData?.items; + + if (items === undefined) { + outputs.innerHTML = + 'Error: received no clipboard data.'; + return; + } + + outputs.replaceChildren(); + + for (const item of items) { + const figure = document.createElement('figure'); + const caption = document.createElement('figcaption'); + caption.textContent = item.kind + ': ' + item.type; + figure.append(caption); + if (item.kind === 'string') { + /** + * @type {HTMLIFrameElement | undefined} + */ + let frame; + const type = item.type; + if (type === 'text/html') { + frame = document.createElement('iframe'); + figure.append(frame); + } + const ta = document.createElement('textarea'); + + /** + * Display useful controls for a set of links. + * @param {Set} links to render controls for. + * @param {boolean} copy whether to render a button to copy the links. + * If we're just pasting a list of links, we don't need to copy them again. + */ + const handleLinks = (links, copy = true) => { + if (links.size > 1) { + const openLinksButton = document.createElement('button'); + openLinksButton.textContent = `open ${links.size} links`; + openLinksButton.addEventListener('click', () => + links.forEach((link) => window.open(link, '_blank')) + ); + + figure.append(openLinksButton); + + if (copy) { + const copyLinksButton = document.createElement('button'); + copyLinksButton.textContent = `copy ${links.size} links`; + copyLinksButton.addEventListener('click', () => { + navigator.clipboard.writeText([...links].join('\n')); + }); + figure.append(copyLinksButton); + } + } + }; + + // trying to make the callback async was causing issues + // with the DataTransferItemList/DataTransferItems being freed + // possibly related to https://stackoverflow.com/a/13443728/9068081 + new Promise((res) => item.getAsString(res)).then((string) => { + // TODO: ignore anchor links? + if (type === 'text/plain') { + const lines = [...string.matchAll(/\n/g)].length; + if (lines > 1) { + caption.textContent += ` (${lines + 1} lines)`; + } + + // split by whitespace and try to parse URLs; check for host otherwise "protocol:" will match + // todo: should we handle commas and other separators? + const links = new Set( + string.split(/\s+/).filter((/** @type {string} */ line) => { + try { + return new URL(line).host; + } catch { + return false; + } + }) + ); + handleLinks(links, false); + } + + if (frame instanceof HTMLIFrameElement) { + frame.srcdoc = string; + frame.addEventListener('load', () => { + const contentDocument = frame?.contentDocument ?? undefined; + if (contentDocument === undefined) { + return; + } + // prefer to open links in the parent window if possible without overriding another + if (contentDocument.querySelector('base') === null) { + const base = document.createElement('base'); + base.target = '_parent'; + contentDocument.head.append(base); + } + /** + * Set of valid URLs linked in the document. + * @type {Set} + */ + const links = new Set( + [...contentDocument.querySelectorAll('a')] + .map((link) => link.href) + .filter((href) => { + // guard against anchor links and other non-URL hrefs + try { + new URL(href); + return true; + } catch { + return false; + } + }) + ); + handleLinks(links); + }); + } + try { + // try to parse as JSON and pretty-print it + const json = JSON.parse(string); + ta.style.fontFamily = 'monospace'; + ta.style.whiteSpace = 'pre-wrap'; + ta.textContent = JSON.stringify(json, null, 2); + } catch { + ta.textContent = string; + } + + // scale to fit content up to 10em + ta.style.height = `${ta.scrollHeight}px`; + ta.style.maxHeight = '10em'; + }); + figure.append(ta); + } else if (item.kind === 'file' && item.type.startsWith('image/')) { + const file = item.getAsFile(); + if (file !== null) { + const img = document.createElement('img'); + img.alt = 'Image pasted from clipboard'; + img.src = URL.createObjectURL(file); + figure.append(img); + } + } + outputs.append(figure); + } + }); +} diff --git a/utilities/clipboard.html b/utilities/clipboard.html index 891bfb6..8441283 100644 --- a/utilities/clipboard.html +++ b/utilities/clipboard.html @@ -2,7 +2,7 @@ - + @@ -23,9 +23,43 @@ ~jordan/utilities/clipboard @@ -86,6 +120,9 @@

  • dashboard - various info widgets
  • +
  • + clipboard - inspect what you copy +
  • @@ -122,43 +159,11 @@

    >This is a live-updating dashboard; as such, you need to enable JavaScript for it to work. - +
    - diff --git a/utilities/dashboard.html b/utilities/dashboard.html index c4a2966..a4dbb0b 100644 --- a/utilities/dashboard.html +++ b/utilities/dashboard.html @@ -2,7 +2,7 @@ - + @@ -139,6 +139,9 @@

  • dashboard - various info widgets
  • +
  • + clipboard - inspect what you copy +
  • diff --git a/utilities/godango.html b/utilities/godango.html index 4258e45..ecb15db 100644 --- a/utilities/godango.html +++ b/utilities/godango.html @@ -2,7 +2,7 @@ - + @@ -128,6 +128,9 @@

  • dashboard - various info widgets
  • +
  • + clipboard - inspect what you copy +