fuzzyworld

tools / commit

Adding separator page.

8d5ecd8ec6e94ffcdcec07bda344d1ef7681fd78 · Pavel Safronov · 2022-01-20 13:30:57 -0800 · parents 941acdac0d72

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.