about summary refs log tree commit diff stats
path: root/html
diff options
context:
space:
mode:
authorelioat <{ID}+{username}@users.noreply.github.com>2024-06-10 11:00:02 -0400
committerelioat <{ID}+{username}@users.noreply.github.com>2024-06-10 11:00:02 -0400
commit1bd51b5e9773d203c6be6020d5449973d6e25a3f (patch)
tree544e72d0fe528188e37349baa18f0eb7177071c8 /html
parentdf761f249300a4ea7f641bb698b68a144020d11d (diff)
downloadtour-1bd51b5e9773d203c6be6020d5449973d6e25a3f.tar.gz
*
Diffstat (limited to 'html')
-rw-r--r--html/yon.html208
1 files changed, 131 insertions, 77 deletions
diff --git a/html/yon.html b/html/yon.html
index 98b9ba3..ee609dd 100644
--- a/html/yon.html
+++ b/html/yon.html
@@ -1,9 +1,6 @@
 <!DOCTYPE html>
 <html lang="en">
 <!--
-
-Forked by eli_oat circa 2024
-
 MIT License
 
 Copyright (c) m15o
@@ -31,12 +28,7 @@ SOFTWARE.
     <title>yon</title>
 </head>
 <style>
-
-    html, body {
-        font-size: 1.25em;
-    }
-
-    html, body, .column, textarea, table, td {
+    html, body, #app, .column, textarea, table, td {
         height: 100%;
     }
 
@@ -93,7 +85,6 @@ SOFTWARE.
     }
 
     textarea {
-        font-size: 1.75em;
         box-sizing: border-box;
         padding: 5px;
         margin: 0;
@@ -182,32 +173,13 @@ SOFTWARE.
     #settings.visible {
         display: table-row;
     }
+
+    .dirty .save {
+        background-color: aquamarine;
+    }
 </style>
 <body>
-<datalist id="files"></datalist>
-<table>
-    <tr>
-        <td colspan="2" class="menu" style="height: 0;">
-            <span class="new">new</span><span class="log">log</span><span class="ls">ls</span><span
-                class="ref">ref</span><span class="mv">mv</span><span class="del">del</span><span class="settings">settings</span>
-        </td>
-    </tr>
-    <tr id="settings">
-        <td colspan="2" style="height: 0;">
-            <span class="export">export</span> <span class="reset">reset</span> <input type="file" id="import" value="">
-        </td>
-    </tr>
-    <tr>
-        <td class="col">
-            <div class="active column"></div>
-            <div class="resize"></div>
-        </td>
-        <td>
-            <div class="column"></div>
-        </td>
-    </tr>
-</table>
-<input list="files" spellcheck="false" id="search" autocomplete="off"/>
+<div id="app"></div>
 <script>
     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
     // Helpers
@@ -311,14 +283,56 @@ SOFTWARE.
         }
     }
 
+    function basename(path) {
+        const n = path.split('/').pop();
+        return n.split('.').shift();
+    }
+
+    function download(data, name, mime) {
+        const link = document.createElement('a');
+        link.download = name;
+        link.href = window.URL.createObjectURL(new Blob([data], {type: mime}));
+        document.body.appendChild(link);
+        link.click();
+        document.body.removeChild(link);
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // storage
+
+    let store = {};
+
+    function storeSet(k, v) {
+        if (v === storeGet(k, v)) return;
+        $('#app').classList.add('dirty');
+        store[k] = v;
+    }
+
+    function storeGet(k) {
+        return store[k] || '';
+    }
+
+    function storeDel(k) {
+        delete store[k];
+    }
+
+    function storeKeys() {
+        return Object.keys(store);
+    }
+
+    function storeClear() {
+        store = {};
+    }
+
     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
     // Core
 
     function save() {
         let name = $name().value;
         let content = $content().value;
+        $panel().dataset['name'] = name;
         if (!name || name[0] === '+') return;
-        localStorage.setItem(name, content);
+        storeSet(name, content);
         refresh();
     }
 
@@ -331,8 +345,8 @@ SOFTWARE.
         hist().forward = [];
     }
 
-    function ls() {
-        return Object.keys(localStorage).map(k => '[[' + k + ']]').join('\n');
+    function ls(prefix) {
+        return storeKeys().filter(k => !prefix || k.startsWith(prefix)).map(k => '[[' + k + ']]').join('\n');
     }
 
     function load(name, noHist) {
@@ -342,12 +356,12 @@ SOFTWARE.
             return;
         }
         !noHist && updateHistory(name)
-        if (name === '+ls') {
-            $content().value = ls();
+        if (name.startsWith('+ls')) {
+            $content().value = ls(name.split(':')[1]);
         } else if (name.startsWith('+ref')) {
             $content().value = ref(name.split(':')[1]);
         } else {
-            $content().value = localStorage.getItem(name);
+            $content().value = storeGet(name);
         }
         $name().value = name;
         $panel().dataset['name'] = name;
@@ -358,7 +372,7 @@ SOFTWARE.
     function refresh() {
         const $elt = $('#files');
         $elt.innerHTML = '';
-        Object.keys(localStorage).forEach(k => $elt.appendChild(makeElt('option', [], {}, null, k)));
+        storeKeys().forEach(k => $elt.appendChild(makeElt('option', [], {}, null, k)));
     }
 
     let paneID = 0;
@@ -385,7 +399,7 @@ SOFTWARE.
             makeElt('span', ['fold', 'action'], {}, '-'),
             makeElt('span', ['max', 'action'], {}, '+'),
             makeElt('span', ['move', 'action'], {}, '~'),
-            makeElt('span', ['close', 'action'], {}, '&times;')
+            makeElt('span', ['close', 'action'], {}, 'x')
         );
         const id = paneID++;
         const section = document.createElement('section');
@@ -402,8 +416,8 @@ SOFTWARE.
     }
 
     function ref(str) {
-        return Object.keys(localStorage).map(k => {
-            const v = localStorage.getItem(k);
+        return storeKeys().map(k => {
+            const v = storeGet(k);
             const m = v.split('\n').filter(l => l.includes('[[' + str + ']]'));
             if (!m.length) return null;
             return ['[[' + k + ']]', '----------', ...m, ''].join('\n');
@@ -411,9 +425,9 @@ SOFTWARE.
     }
 
     function mv(before, after) {
-        return Object.keys(localStorage).forEach(k => {
-            const v = localStorage.getItem(k);
-            localStorage.setItem(k, v.replaceAll('[[' + before + ']]', '[[' + after + ']]'))
+        return storeKeys().forEach(k => {
+            const v = storeGet(k);
+            storeSet(k, v.replaceAll('[[' + before + ']]', '[[' + after + ']]'))
         })
     }
 
@@ -474,8 +488,7 @@ SOFTWARE.
 
     function menuReset() {
         if (confirm('delete everything?')) {
-            localStorage.clear();
-            location.reload();
+            storeClear();
         }
     }
 
@@ -487,7 +500,7 @@ SOFTWARE.
         let prev = $panel().dataset['name'];
         mv(prev, $name().value);
         save();
-        localStorage.removeItem(prev);
+        storeDel(prev);
         $prevPanel = $panel();
         $$('section').forEach($pane => {
             setActive($pane);
@@ -497,20 +510,26 @@ SOFTWARE.
         refresh();
     }
 
+    function quine() {
+        const regex = /let store = (.*)/;
+        return ['<!DOCTYPE html>',
+            document.documentElement.outerHTML.replace(regex, "let store = " + JSON.stringify(store)
+                .replaceAll('</' + 'script', "' + '</' + 'script' + '") + ';')].join('\n');
+    }
+
+    function menuSave() {
+        $('#app').classList.remove('dirty');
+        download(quine(), basename(window.location.href) + '.html', 'text/html');
+    }
+
     function menuExport() {
-        const blob = new Blob([JSON.stringify(localStorage)], {type: 'application/json'});
-        const link = document.createElement('a');
-        link.download = 'yon-export.json';
-        link.href = window.URL.createObjectURL(blob);
-        document.body.appendChild(link);
-        link.click();
-        document.body.removeChild(link);
+        download(JSON.stringify(store), basename(window.location.href) + '.json', 'text/json');
     }
 
     function menuDel() {
         let name = $name().value;
         if (name && confirm('delete ' + name + '?')) {
-            localStorage.removeItem(name);
+            storeDel(name);
             refresh();
             deletePane($panel());
         }
@@ -568,6 +587,50 @@ SOFTWARE.
     let $prevPanel;
     let $resize;
 
+    function handleSearch(e) {
+        e.preventDefault();
+        e.stopPropagation();
+        if (isChordNavBlank(e)) {
+            navBlank(t$(e).value);
+            closeSearch();
+        } else if (isChordNav(e)) {
+            if (!$content()) createPane();
+            closeSearch();
+            load(t$(e).value);
+        } else {
+            if (!$content()) return;
+            closeSearch();
+            insert($content(), "[[" + t$(e).value + "]]");
+        }
+    }
+
+    document.getElementById('app').innerHTML = `
+ <datalist id="files"></datalist>
+<table>
+    <tr>
+        <td colspan="2" class="menu" style="height: 0;">
+            <span class="new">new</span><span class="log">log</span><span class="ls">ls</span><span
+                class="ref">ref</span><span class="mv">mv</span><span class="del">del</span><span class="save">save</span><span class="settings">import</span><span class="export">export</span><span class="reset">reset</span>
+        </td>
+    </tr>
+    <tr id="settings">
+        <td colspan="2" class="menu" style="height: 0;">
+            <input type="file" id="import" value="">
+        </td>
+    </tr>
+    <tr>
+        <td class="col">
+            <div class="active column"></div>
+            <div class="resize"></div>
+        </td>
+        <td>
+            <div class="column"></div>
+        </td>
+    </tr>
+</table>
+<input list="files" spellcheck="false" id="search" autocomplete="off"/>
+    `;
+
     document.addEventListener('mousedown', function (e) {
         if (hasCls(t$(e), 'resize')) {
             $resize = closest$(e, 'td');
@@ -596,11 +659,12 @@ SOFTWARE.
             case 'log': menuLog(); return;
             case 'ls': menuLs(); return;
             case 'reset': menuReset(); return;
-            case 'export': menuExport(); return;
+            case 'save': menuSave(); return;
             case 'ref': menuRef(); return;
             case 'mv': menuMv(); return;
             case 'del': menuDel(); return;
             case 'settings': menuSettings(); return;
+            case 'export': menuExport(); return;
             case 'close': paneClose(e); return;
             case 'fold': paneFold(e); return;
             case 'max': paneMax(e); return;
@@ -621,22 +685,12 @@ SOFTWARE.
         }
     });
 
-    function handleSearch(e) {
-        e.preventDefault();
-        e.stopPropagation();
-        if (isChordNavBlank(e)) {
-            navBlank(t$(e).value);
-            closeSearch();
-        } else if (isChordNav(e)) {
-            if (!$content()) createPane();
-            closeSearch();
-            load(t$(e).value);
-        } else {
-            if (!$content()) return;
-            closeSearch();
-            insert($content(), "[[" + t$(e).value + "]]");
+    window.addEventListener('beforeunload', e => {
+        if ($('.dirty')) {
+            e.preventDefault();
+            e.returnValue = true;
         }
-    }
+    })
 
     document.addEventListener('keydown', function (e) {
         if (e.key === 'Enter') {
@@ -660,7 +714,7 @@ SOFTWARE.
         const r = new FileReader();
         r.onload = e => {
             let data = JSON.parse('' + e.target.result);
-            Object.keys(data).forEach(k => localStorage.setItem(k, data[k]));
+            Object.keys(data).forEach(k => storeSet(k, data[k]));
         }
         r.readAsText(file);
         refresh();
@@ -668,7 +722,7 @@ SOFTWARE.
 
     refresh();
     createPane();
-    load();
+    load(location.hash && location.hash.substring(1));
 </script>
 </body>
-</html>
+</html>
\ No newline at end of file