composer.html (5163B) - raw


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