tools / commit
Adding separator page.
separator-page/index.html | 287 ++++++++++++++++++++++++++++++++++++++++++++++ separator-page/readme.md | 11 ++ 2 files changed, 298 insertions(+) diff --git a/separator-page/index.html b/separator-page/index.html new file mode 100644 index 0000000..136145b --- /dev/null +++ b/separator-page/index.html @@ -0,0 +1,287 @@ +<!DOCTYPE html> +<html> + + <head> + <meta charset="utf-8" /> + <title>Separator</title> + <link rel="icon" + href="data:image/x-icon;base64,AAABAAEAEBAQAAEABAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAgAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//wAA//8AAP//AAD//wAA/n8AAP5/AAD+fwAA/n8AAOAHAADgBwAA/n8AAP5/AAD+fwAA/n8AAP//AAD//wAA" /> + <script language="javascript" + src="https://pieroxy.net/lzstring/lz-string-1.4.4.js"></script> + + <!-- https://github.com/showdownjs/showdown --> + <script language="javascript" + src="https://rawgit.com/showdownjs/showdown/develop/dist/showdown.min.js"></script> + <script> + /** @typedef {Object} State + * @property {string} name + * @property {string} description + */ + + /** @type {State} */ + const defaultState = { + name: "Separator", + description: `# Info + +This is a separator page! + +## TODO + +1. Read this page +1. Understand this page +1. The page name and page content are both editable +1. To edit an element, **double-click on it** +1. Page content (this block) is not editable by default +1. Replace the contents on this page to whatever you want a new separator page to contain +1. Markdown contents will be rendered as HTML +1. Content links will always open in a new tab +1. Click "Save as Default" button to save current page as default template +1. If you messed up this page, click on "Hard Reset" to go back to this default message +1. The name and the description are stored zipped content in the url, so copy-pasting the url in a new tab will exactly reproduce this page +1. Your data is stored in the hash fragment of the url, and [this data is not sent to the server](https://stackoverflow.com/questions/14462218/is-the-url-fragment-identifier-sent-to-the-server), so your notes are never sent anywhere. +1. But your notes **do** end up in the browser's history. So don't keep secrets here.` + }; + const maxDataLength = 64 * 1024; + let currentState = defaultState; + + /** @type {HTMLElement} */ + let nameSpan; + /** @type {HTMLInputElement} */ + let nameInput; + /** @type {HTMLElement} */ + let descriptionSpan; + /** @type {HTMLElement} */ + let descriptionShowSpan; + + const lsKey = "pavel-safronov-separator-page"; + const storedDefaultJson = localStorage.getItem(lsKey); + if (isNullOrEmpty(storedDefaultJson)) { + localStorage.setItem(lsKey, JSON.stringify(currentState)); + } else { + /** @type {State} */ + let newState; + try { + newState = JSON.parse(storedDefaultJson); + } catch { + console.error({ message: "Could not parse stored default", storedDefaultJson }); + } + if (newState) { + currentState = newState; + } + } + + // MD converter + var shodownConverter = new showdown.Converter(); + + /** + * @param {string} text + */ + function isNullOrEmpty(text) { + return (!text || text.length === 0); + } + + /** + * @param {State} partialState + */ + function update(partialState) { + const path = window.location.pathname; + + if (partialState) { + const newState = { + ...currentState, + ...partialState + }; + currentState = newState; + } + const stateJson = JSON.stringify(currentState); + const stateCompressed = LZString144.compressToBase64(stateJson); + const newPath = `${path}#${stateCompressed}`; + if (newPath.length >= maxDataLength) { + alert(`Url is too long (${newPath.length} characters), need to rethink storing data in the hash. Or not using the Separator Page as a OneNote replacement.`); + } + + try { + history.replaceState(currentState, "", newPath); + } catch (ex) { + alert(`Not able to save contents: ${ex}`); + } + + let { name, description } = currentState; + // set name + document.title = isNullOrEmpty(name) ? "---" : name; + if (nameSpan.innerHTML !== name) { + nameSpan.innerHTML = name; + } + if (nameInput.value !== name) { + nameInput.value = name; + } + + // set description + const descriptionToUse = description !== null && description.length > 0 ? description : "**No content specified, double-click to edit**"; + descriptionShowSpan.innerHTML = shodownConverter.makeHtml(descriptionToUse); + descriptionSpan.value = isNullOrEmpty(description) ? "" : description; + + // update all href links to open in new tabs + const links = descriptionShowSpan.getElementsByTagName("a"); + for (let i = 0; i < links.length; i++) { + const link = links.item(i); + link.target = "_blank"; + } + } + + /** + * @param {HTMLElement} a + * @param {HTMLElement} b + */ + function flipHideShow(a, b) { + const isAHidden = a.className === "hidden"; + a.className = isAHidden ? "" : "hidden"; + b.className = isAHidden ? "hidden" : ""; + } + + window.onpopstate = (state) => { + update(state); + }; + window.onload = () => { + /** @type {State} */ + let state = history.state; + + const search = window.location.search; + if (search) { + const urlParams = new URLSearchParams(search); + const name = urlParams.get("name"); + if (name) { + state = { name }; + } + } + + const hash = window.location.hash; + if (hash) { + const compressed = hash.substring(1); + var json = LZString144.decompressFromBase64(compressed); + state = JSON.parse(json); + } + + // name input + nameInput = document.getElementById("name-input"); + nameInput.onkeyup = () => update({ name: nameInput.value }); + + // name field + nameSpan = document.getElementById("name"); + + // description field + descriptionSpan = document.getElementById("description"); + descriptionSpan.onkeyup = () => update({ description: descriptionSpan.value }); + + // description html field + descriptionShowSpan = document.getElementById("description-show"); + + // hook up double-click switching + descriptionShowSpan.addEventListener("dblclick", () => flipHideShow(descriptionShowSpan, descriptionSpan)); + descriptionSpan.addEventListener("dblclick", () => flipHideShow(descriptionShowSpan, descriptionSpan)); + nameInput.addEventListener("dblclick", () => flipHideShow(nameInput, nameSpan)); + nameSpan.addEventListener("dblclick", () => flipHideShow(nameInput, nameSpan)); + + const saveButton = document.getElementById("save"); + saveButton.onclick = () => { + if (confirm("Save current title and description as default starting values for separator page?")) { + localStorage.setItem(lsKey, JSON.stringify(currentState)); + } + }; + + const resetButton = document.getElementById("reset"); + resetButton.onclick = () => { + if (confirm("Set default title and description values to original values?")) { + localStorage.setItem(lsKey, JSON.stringify(defaultState)); + update(defaultState); + } + }; + + // render everything for the first time + update(state); + }; + + </script> + <style> + #description { + color: gray; + font-size: small; + width: 100%; + height: 100%; + box-sizing: border-box; + } + + #name { + width: 100%; + font-size: xx-large; + } + + .container { + height: 100%; + } + + .info { + color: gray; + font-size: small; + } + + .left { + float: left; + } + + .right { + float: right; + } + + .hidden { + display: none; + } + + </style> + </head> + + <body> + + <div class="container"> + + <div class="container"> + + <input autofocus + id="name-input" + title="Name" + class="hidden" + size="100" /> + + <span id="name" + title="Tab title - double-click to edit"> + Separator + </span> + + <span class="info"> + Tab title + </span> + </div> + + <div class="container"> + + <textarea rows="30" + id="description" + title="content" + class="hidden"></textarea> + + <span id="description-show" + title="Description - double-click to edit"></span> + </div> + </div> + + <div class="container"> + <button class="left" + type="button" + id="save">Save as Default</button> + <button class="right" + type="button" + id="reset">Hard Reset</button> + </div> + </body> + +</html> diff --git a/separator-page/readme.md b/separator-page/readme.md new file mode 100644 index 0000000..3d183b0 --- /dev/null +++ b/separator-page/readme.md @@ -0,0 +1,11 @@ +# Separator page + +This page can be used as a separator. Simply open the page, enter a title (and optional Markdown description), and you've got an arbitrarily-named tab in your browser now. + +## Screenshots + +TODO + +## Installation + +Navigate to [this page](https://gitlab.com/fuzzy-rp/tools/-/raw/main/separator-page/index.html), bookmark it, then start using the bookmark as a separator.