composer.html (5223B) - raw


      1 <!DOCTYPE html>
      2 <!--
      3   Composer by Oscar Benedito <oscar@oscarbenedito.com>
      4   License: Affero General Public License version 3 or later
      5 
      6   Minimalistic interface to write without distractions. It is a standalone HTML
      7   file. It will save your progress as long as you don't delete browser data.
      8 -->
      9 <html lang="en">
     10   <head>
     11     <meta charset="utf-8">
     12     <meta name="viewport" content="width=device-width, initial-scale=1">
     13     <meta name="author" content="Oscar Benedito">
     14     <title>Composer</title>
     15     <style>
     16 :root {
     17   --font-family: 'serif';
     18   --nav-height: 3em;
     19   --text-color: #000;
     20   --bg-color: #fff;
     21   --nav-color: hsl(0, 0%, 46%);
     22 }
     23 .toggled {
     24   --text-color: hsl(0, 0%, 93%);
     25   --bg-color: hsl(0, 0%, 13%);
     26   --nav-color: hsl(0, 0%, 56%);
     27 }
     28 @media (prefers-color-scheme: dark) {
     29   :root {
     30     --text-color: hsl(0, 0%, 93%);
     31     --bg-color: hsl(0, 0%, 13%);
     32     --nav-color: hsl(0, 0%, 56%);
     33   }
     34 }
     35 @media (prefers-color-scheme: dark) {
     36   .toggled {
     37     --text-color: #000;
     38     --bg-color: #fff;
     39     --nav-color: hsl(0, 0%, 46%);
     40   }
     41 }
     42 .mono {
     43   --font-family: 'mono';
     44 }
     45 html {
     46   background-color: var(--bg-color);
     47 }
     48 textarea {
     49   line-height: 1.4em;
     50   font-family: var(--font-family);
     51   padding: 1em calc((100% - 720px)/2);
     52   background-color: var(--bg-color);
     53   color: var(--text-color);
     54   margin: 0;
     55   height: calc(100% - var(--nav-height));
     56   font-size: 1.2em;
     57   box-sizing: border-box;
     58   resize: none;
     59   right: 0;
     60   top: var(--nav-height);
     61   bottom: 0;
     62   left: 0;
     63   width: 100%;
     64   position: fixed;
     65   border: 0;
     66   outline: 0;
     67 }
     68 nav {
     69   text-align: center;
     70   height: var(--nav-height);
     71   line-height: var(--nav-height);
     72   font-size: 1.2em;
     73   opacity: 0;
     74   color: var(--nav-color);
     75   background-color: var(--bg-color);
     76   position: fixed;
     77   top: 0;
     78   right: 0;
     79   left: 0;
     80   z-index: 1;
     81 }
     82 nav a {
     83   color: var(--nav-color);
     84   text-decoration: none;
     85   cursor: pointer;
     86 }
     87 nav:hover {
     88   opacity: 1;
     89 }
     90     </style>
     91   </head>
     92   <body>
     93     <nav>
     94       <span id="word-count">0 words &middot; 0 minutes</span> &middot; <a onclick="toggleTheme()">Toggle theme</a> &middot; <a onclick="toggleFont()">Toggle font</a>
     95     </nav>
     96     <textarea id="composer" placeholder="Start writing..." autofocus></textarea>
     97     <script type="text/javascript">
     98 // @license magnet:?xt=urn:btih:90dc5c0be029de84e523b9b3922520e79e0e6f08&dn=cc0.txt CC0-1.0
     99 var $composer = document.getElementById('composer');
    100 var $wordcount = document.getElementById('word-count');
    101 var key;
    102 var typingTimer;
    103 var typingInterval = 200;
    104 function updateWordCount() {
    105   var words = 0;
    106   var content = $composer.value.trim();
    107   if (content != '') {
    108     words = content.replace(/\s+/gi, ' ').split(' ').length;
    109   }
    110   minutes = Math.floor(words/140);
    111   $wordcount.textContent = words + ' word' + (words != 1 ? 's' : '') + ' ยท ' + minutes + ' minute' + (minutes != 1 ? 's' : '');
    112 }
    113 function loadContents() {
    114   var content = localStorage.getItem(key);
    115   if (content != null) {
    116     $composer.value = content;
    117   }
    118 }
    119 var updateContents = function() {
    120   if ($composer.value == '') {
    121     localStorage.removeItem(key);
    122   }
    123   else {
    124     localStorage.setItem(key, $composer.value);
    125   }
    126   updateWordCount();
    127 }
    128 var resetTimer = function() {
    129   clearTimeout(typingTimer);
    130   typingTimer = setTimeout(updateContents, typingInterval);
    131 }
    132 function downloadData(filename, text) {
    133   var text = localStorage.getItem(key);
    134   if (text != null && text != '') {
    135     var tmpElement = document.createElement('a');
    136     tmpElement.setAttribute('href', 'data:text/markdown;charset=utf-8,' + encodeURIComponent(text));
    137     tmpElement.setAttribute('download', key + '.md');
    138     tmpElement.style.display = 'none';
    139 
    140     document.body.appendChild(tmpElement);
    141     tmpElement.click();
    142     document.body.removeChild(tmpElement);
    143   }
    144 }
    145 var saveEvent = function(event) {
    146   if (event.keyCode == 83 && (event.metaKey || event.ctrlKey)) {
    147     clearTimeout(typingTimer);
    148     event.preventDefault();
    149     updateContents();
    150     if (event.shiftKey)
    151       downloadData();
    152   }
    153 }
    154 function toggleTheme() {
    155   if (localStorage && localStorage.getItem('theme') == 'toggled') {
    156     localStorage.removeItem('theme');
    157   } else if (localStorage) {
    158     localStorage.setItem('theme', 'toggled');
    159   }
    160   document.documentElement.classList.toggle('toggled');
    161 }
    162 function toggleFont() {
    163   if (localStorage && localStorage.getItem('font') == 'mono') {
    164     localStorage.removeItem('font');
    165   } else if (localStorage) {
    166     localStorage.setItem('font', 'mono');
    167   }
    168   document.documentElement.classList.toggle('mono');
    169 }
    170 key = (new URLSearchParams(window.location.search)).get('key');
    171 if (key == '' || key == null) {
    172   key = 'null';
    173 }
    174 key = 'k-' + key;
    175 if (localStorage && localStorage.getItem('theme') == 'toggled') {
    176   document.documentElement.classList.toggle('toggled');
    177 }
    178 if (localStorage && localStorage.getItem('font') == 'mono') {
    179   document.documentElement.classList.toggle('mono');
    180 }
    181 $composer.addEventListener('keyup input', resetTimer);
    182 $composer.addEventListener('keydown', resetTimer);
    183 $composer.addEventListener('input', resetTimer);
    184 window.addEventListener('beforeunload', updateContents);
    185 window.addEventListener('keydown', saveEvent);
    186 loadContents();
    187 updateWordCount();
    188 // @license-end
    189     </script>
    190   </body>
    191 </html>