123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331 |
- <!doctype html><!-- This is a valid HTML5 document. -->
- <!-- Screen readers, SEO, extensions and so on. -->
- <html lang="fr">
- <!-- Has to be within the first 1024 bytes, hence before the `title` element
- See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
- <meta charset="utf-8">
- <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
- <!-- The viewport meta is quite crowded and we are responsible for that.
- See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
- <meta name="viewport" content="width=device-width,initial-scale=1">
- <!-- Required to make a valid HTML5 document. -->
- <title>World smallest office suite (archive) — David Larlet</title>
- <meta name="description" content="Publication mise en cache pour en conserver une trace.">
- <!-- That good ol' feed, subscribe :). -->
- <link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/">
- <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
- <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png">
- <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png">
- <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png">
- <link rel="manifest" href="/static/david/icons2/site.webmanifest">
- <link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c">
- <link rel="shortcut icon" href="/static/david/icons2/favicon.ico">
- <meta name="msapplication-TileColor" content="#f7f7f7">
- <meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml">
- <meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)">
- <meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)">
- <!-- Documented, feel free to shoot an email. -->
- <link rel="stylesheet" href="/static/david/css/style_2021-01-20.css">
- <!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
- <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
- <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
- <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
- <link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
- <link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
- <link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
- <script>
- function toggleTheme(themeName) {
- document.documentElement.classList.toggle(
- 'forced-dark',
- themeName === 'dark'
- )
- document.documentElement.classList.toggle(
- 'forced-light',
- themeName === 'light'
- )
- }
- const selectedTheme = localStorage.getItem('theme')
- if (selectedTheme !== 'undefined') {
- toggleTheme(selectedTheme)
- }
- </script>
-
- <meta name="robots" content="noindex, nofollow">
- <meta content="origin-when-cross-origin" name="referrer">
- <!-- Canonical URL for SEO purposes -->
- <link rel="canonical" href="https://zserge.com/posts/awfice/">
-
- <body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all">
-
-
- <article>
- <header>
- <h1>World smallest office suite</h1>
- </header>
- <nav>
- <p class="center">
- <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
- <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
- </svg> Accueil</a> •
- <a href="https://zserge.com/posts/awfice/" title="Lien vers le contenu original">Source originale</a>
- </p>
- </nav>
- <hr>
- <p>We are all familiar with a traditional office suite - a word processor, a spreadsheet, a presentation program, maybe a diagramming or note-taking app. We have seen it all in Microsoft Office and Google Docs. Those are really powerful and large. But what would be the most minimal amount of code required to build an office suite?</p>
- <h2 id="platform">platform</h2>
- <p>Obviously, our office suite won’t be a desktop GUI app - those require plenty of code and efforts to build one. Same applies to native mobile apps. We might consider building a console (terminal-based) app, and in fact there are already absurdly small <a href="https://github.com/antirez/kilo">text editors</a> or <a href="https://github.com/c00kiemon5ter/ioccc-obfuscated-c-contest/blob/master/2000/jarijyrki.c">spreadsheets</a>, but it would be much easier if we targeted a browser.</p>
- <p>Browsers already come with a decent rich-text editor (contenteditable) and are really good (although, unsafe) <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval">evaluator</a> for math expressions.</p>
- <p>Now, how small can we get?</p>
- <h2 id="text-editor">text editor</h2>
- <p>This is in fact the “app” I have been using for years:</p>
- <p>Yes, that’s it. Moreover, one can turn it into a self-contained URL and that’s how I use it when I need a scratchpad for quick notes:</p>
- <div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html">data:text/html,<span class="p"><</span><span class="nt">html</span> <span class="na">contenteditable</span><span class="p">></span>
- </code></pre></div>
- <p>Try pasting this into your URL bar. If your browser is nice with you, you should be able to use <code>Ctrl+B</code> or <code>Ctrl+I</code> to make text look bold or italic.</p>
- <p>We can enhance it a bit more by adding some style (yes, I believe that <a href="http://bettermotherfuckingwebsite.com/">some small typographic improvements</a> matter):</p>
- <div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html">data:text/html,<span class="p"><</span><span class="nt">body</span> <span class="na">contenteditable</span> <span class="na">style</span><span class="o">=</span><span class="s">"line-height:1.5;font-size:20px;"</span><span class="p">></span>
- </code></pre></div>
- <p>I have added this to bookmarks and now my zero-weight text editor is one keypress away from me. You might also use it as a temporary clipboard to paste text or even pictures.</p>
- <p>Of course, this can be further extended to support various heading styles, lists or indentation.</p>
- <p>You can save your text by saving the whole HTML as a file, or by printing it on paper.</p>
- <h2 id="presentations">presentations</h2>
- <p>Some time ago I already made a <a href="https://github.com/trikita/slide-html">simple presentation tool</a>, which is a self-contained HTML file that one can edit (like markdown text) and it will render in a colorful <a href="https://en.wikipedia.org/wiki/Takahashi_method">Takahashi-style</a> presentation.</p>
- <p>This time, as we continue talking about <code>contenteditable</code>, we will make a WYSYWIG slide editor instead. First of all, let’s create several empty slides that are editable:</p>
- <div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html"><span class="p"><</span><span class="nt">body</span><span class="p">><</span><span class="nt">script</span><span class="p">></span>
- <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span> <span class="nx">i</span><span class="o"><</span><span class="mi">50</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
- <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">+=</span> <span class="sb">`
- </span><span class="sb"> <div style="position:relative;width:90%;padding-top:60%;margin:5%;border:1px solid silver;page-break-after:always;">
- </span><span class="sb"> <div contenteditable style="outline:none;position:absolute;right:10%;bottom:10%;left:10%;top:10%;font-size:5vmin;">
- </span><span class="sb"> </div>
- </span><span class="sb"> </div>`</span><span class="p">;</span>
- <span class="p">}</span>
- <span class="p"></</span><span class="nt">script</span><span class="p">></span>
- </code></pre></div>
- <p>The number of 50 is arbitrary, but I don’t remember ever using more slides than this. Each outer div is a slide with a thin outline. The trick with width and padding-top is to keep the slide aspect ratio. Try changing the values to see how that affects the layout. Each inner div is a basic rich text editor, with a fairly large font to be readable from the projector screen.</p>
- <p>Good enough. But we want to have headers and lists on our slides, don’t we?</p>
- <p>Let’s add some hotkeys:</p>
- <div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="nb">document</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="s2">"div>div"</span><span class="p">).</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">el</span> <span class="p">=></span> <span class="p">{</span>
- <span class="nx">el</span><span class="p">.</span><span class="nx">onkeydown</span> <span class="o">=</span> <span class="nx">e</span><span class="p">=></span> <span class="p">{</span>
- <span class="c1">// `code` will be false if Ctrl or Alt are not pressed
- </span><span class="c1"></span> <span class="c1">// `code` will be 0..8 for numeric keys 1..9
- </span><span class="c1"></span> <span class="c1">// `code` will be some other numeric value if another key is pressed
- </span><span class="c1"></span> <span class="c1">// with Ctrl+Alt hold.
- </span><span class="c1"></span> <span class="kr">const</span> <span class="nx">code</span> <span class="o">=</span> <span class="nx">e</span><span class="p">.</span><span class="nx">ctrlKey</span> <span class="o">&&</span> <span class="nx">e</span><span class="p">.</span><span class="nx">altKey</span> <span class="o">&&</span> <span class="nx">e</span><span class="p">.</span><span class="nx">keyCode</span><span class="o">-</span><span class="mi">49</span><span class="p">;</span>
- <span class="c1">// Find the suitable rich text command, or undefined if the key
- </span><span class="c1"></span> <span class="c1">// is out of range
- </span><span class="c1"></span> <span class="nx">x</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"formatBlock"</span><span class="p">,</span> <span class="s2">"formatBlock"</span><span class="p">,</span> <span class="s2">"justifyLeft"</span><span class="p">,</span> <span class="s2">"justifyCenter"</span><span class="p">,</span>
- <span class="s2">"justifyRight"</span><span class="p">,</span> <span class="s2">"outdent"</span><span class="p">,</span> <span class="s2">"indent"</span><span class="p">,</span> <span class="s2">"insertUnorderedList"</span><span class="p">][</span><span class="nx">n</span><span class="p">];</span>
- <span class="c1">// Find command parameter (only for 1 and 2)
- </span><span class="c1"></span> <span class="nx">y</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"<h1>"</span><span class="p">,</span> <span class="s2">"<div>"</span><span class="p">][</span><span class="nx">n</span><span class="p">];</span>
- <span class="c1">// Send the command and the parameter (if any) to the editor
- </span><span class="c1"></span> <span class="k">if</span> <span class="p">(</span><span class="nx">x</span><span class="p">)</span> <span class="p">{</span>
- <span class="nb">document</span><span class="p">.</span><span class="nx">execCommand</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="kc">false</span><span class="p">,</span> <span class="nx">y</span><span class="p">);</span>
- <span class="p">}</span>
- <span class="p">};</span>
- <span class="p">});</span>
- </code></pre></div>
- <p>Now if we press <code>Ctrl+Alt+1</code> inside the slide - we make the selected text a header. Or if we press <code>Ctrl+Alt+2</code> we turn it back to normal. <code>Ctrl+Alt+3</code>..<code>Ctrl+Alt+5</code> change the alignment, and <code>6</code> and <code>7</code> change the indentation. <code>8</code> starts a list. <code>9</code> is left for your own needs, feel free to customize. The full list of <code>contenteditable</code> operations can be found on <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand">MDN</a>.</p>
- <p>Squeezing the code above a little bit and turning it into a data URL would result in the following ~600 bytes long rich slide editor:</p>
- <div class="highlight"><pre class="chroma"><code class="language-html" data-lang="html">data:text/html,<span class="p"><</span><span class="nt">style</span><span class="p">>@</span><span class="k">page</span><span class="p">{</span><span class="nt">size</span><span class="o">:</span> <span class="nt">6in</span> <span class="nt">8in</span> <span class="nt">landscape</span><span class="o">;</span><span class="p">}</</span><span class="nt">style</span><span class="p">><</span><span class="nt">body</span><span class="p">><</span><span class="nt">script</span><span class="p">></span><span class="nx">d</span><span class="o">=</span><span class="nb">document</span><span class="p">;</span><span class="k">for</span><span class="p">(</span><span class="nx">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="nx">i</span><span class="o"><</span><span class="mi">50</span><span class="p">;</span><span class="nx">i</span><span class="o">++</span><span class="p">)</span><span class="nx">d</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">innerHTML</span><span class="o">+=</span><span class="s1">'<div style="position:relative;width:90%;padding-top:60%;margin:5%;border:1px solid silver;page-break-after:always;"><div contenteditable style="outline:none;position:absolute;right:10%;bottom:10%;left:10%;top:10%;font-size:5vmin;"></div></div>'</span><span class="p">;</span><span class="nx">d</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="s2">"div>div"</span><span class="p">).</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">e</span><span class="p">=></span><span class="nx">e</span><span class="p">.</span><span class="nx">onkeydown</span><span class="o">=</span><span class="nx">e</span><span class="p">=>{</span><span class="nx">n</span><span class="o">=</span><span class="nx">e</span><span class="p">.</span><span class="nx">ctrlKey</span><span class="o">&&</span><span class="nx">e</span><span class="p">.</span><span class="nx">altKey</span><span class="o">&&</span><span class="nx">e</span><span class="p">.</span><span class="nx">keyCode</span><span class="o">-</span><span class="mi">49</span><span class="p">,</span><span class="nx">x</span><span class="o">=</span><span class="p">[</span><span class="s2">"formatBlock"</span><span class="p">,</span><span class="s2">"formatBlock"</span><span class="p">,</span><span class="s2">"justifyLeft"</span><span class="p">,</span><span class="s2">"justifyCenter"</span><span class="p">,</span><span class="s2">"justifyRight"</span><span class="p">,</span><span class="s2">"outdent"</span><span class="p">,</span><span class="s2">"indent"</span><span class="p">,</span><span class="s2">"insertUnorderedList"</span><span class="p">][</span><span class="nx">n</span><span class="p">],</span><span class="nx">y</span><span class="o">=</span><span class="p">[</span><span class="s2">"<h1>"</span><span class="p">,</span><span class="s2">"<div>"</span><span class="p">][</span><span class="nx">n</span><span class="p">],</span><span class="nx">x</span><span class="o">&&</span><span class="nb">document</span><span class="p">.</span><span class="nx">execCommand</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span><span class="o">!</span><span class="mi">1</span><span class="p">,</span><span class="nx">y</span><span class="p">)})</</span><span class="nt">script</span><span class="p">></span>
- </code></pre></div>
- <p>The slides can be exported to PDF by printing into the file, and from there could be shown on any computer.</p>
- <h2 id="quick-drawing">quick drawing</h2>
- <p>A while ago I’ve built <a href="https://onthesamepage.online">https://onthesamepage.online</a> to quickly sketch ideas in collaboration with other people, but despite its simplicity it’s still larger than what we do here.</p>
- <p>As a bare minimum, we can only draw lines on canvas. We need a <code><canvas></code> elements, a few mouse/touch handlers and a flag to indicate that mouse movement is actually drawing when the mouse is pressed.</p>
- <p>Here worth mentioning that elements with an id can be accessed as window[id] or window.id. The thing that wasn’t standardized for a long time and has been a hack from IE, now has become a <a href="https://html.spec.whatwg.org/multipage/window-object.html#named-access-on-the-window-object">standard</a>.</p>
- <p>Also, I moved cursor position handling to separate short functions to reuse them in mousedown and mousemove handlers. Finally, I reset the margins of the body elements to make our canvas full screen.</p>
- <p>The minified code is roughly 400 bytes and allows you to draw with your mouse, nothing more, nothing less:</p>
- <div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="o"><</span><span class="nx">canvas</span> <span class="nx">id</span><span class="o">=</span><span class="s2">"v"</span><span class="o">></span>
- <span class="o"><</span><span class="nx">script</span><span class="o">></span>
- <span class="nx">d</span><span class="o">=</span><span class="nb">document</span><span class="p">,</span> <span class="c1">// shortcut for document
- </span><span class="c1"></span><span class="nx">d</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">margin</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="c1">// reset style
- </span><span class="c1"></span><span class="nx">f</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="c1">// mouse-down flag
- </span><span class="c1"></span><span class="nx">c</span><span class="o">=</span><span class="nx">v</span><span class="p">.</span><span class="nx">getContext</span><span class="p">(</span><span class="s2">"2d"</span><span class="p">),</span> <span class="c1">// canvas context
- </span><span class="c1"></span><span class="nx">v</span><span class="p">.</span><span class="nx">width</span><span class="o">=</span><span class="nx">innerWidth</span><span class="p">,</span> <span class="c1">// make canvas element fullscreen
- </span><span class="c1"></span><span class="nx">v</span><span class="p">.</span><span class="nx">height</span><span class="o">=</span><span class="nx">innerHeight</span><span class="p">,</span>
- <span class="nx">c</span><span class="p">.</span><span class="nx">lineWidth</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="c1">// make lines a bit wider
- </span><span class="c1"></span><span class="nx">x</span><span class="o">=</span><span class="nx">e</span><span class="p">=></span><span class="nx">e</span><span class="p">.</span><span class="nx">clientX</span><span class="o">||</span><span class="nx">e</span><span class="p">.</span><span class="nx">touches</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">clientX</span><span class="p">,</span> <span class="c1">// get X position from mouse/touch
- </span><span class="c1"></span><span class="nx">y</span><span class="o">=</span><span class="nx">e</span><span class="p">=></span><span class="nx">e</span><span class="p">.</span><span class="nx">clientY</span><span class="o">||</span><span class="nx">e</span><span class="p">.</span><span class="nx">touches</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">clientY</span><span class="p">,</span> <span class="c1">// get Y position from mouse/touch
- </span><span class="c1"></span><span class="nx">d</span><span class="p">.</span><span class="nx">onmousedown</span><span class="o">=</span><span class="nx">d</span><span class="p">.</span><span class="nx">ontouchstart</span><span class="o">=</span><span class="nx">e</span><span class="p">=>{</span><span class="nx">f</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span><span class="nx">e</span><span class="p">.</span><span class="nx">preventDefault</span><span class="p">(),</span><span class="nx">c</span><span class="p">.</span><span class="nx">moveTo</span><span class="p">(</span><span class="nx">x</span><span class="p">(</span><span class="nx">e</span><span class="p">),</span><span class="nx">y</span><span class="p">(</span><span class="nx">e</span><span class="p">)),</span><span class="nx">c</span><span class="p">.</span><span class="nx">beginPath</span><span class="p">()},</span>
- <span class="nx">d</span><span class="p">.</span><span class="nx">onmousemove</span><span class="o">=</span><span class="nx">d</span><span class="p">.</span><span class="nx">ontouchmove</span><span class="o">=</span><span class="nx">e</span><span class="p">=>{</span><span class="nx">f</span><span class="o">&&</span><span class="p">(</span><span class="nx">c</span><span class="p">.</span><span class="nx">lineTo</span><span class="p">(</span><span class="nx">x</span><span class="p">(</span><span class="nx">e</span><span class="p">),</span><span class="nx">y</span><span class="p">(</span><span class="nx">e</span><span class="p">)),</span><span class="nx">c</span><span class="p">.</span><span class="nx">stroke</span><span class="p">())},</span>
- <span class="nx">d</span><span class="p">.</span><span class="nx">onmouseup</span><span class="o">=</span><span class="nx">d</span><span class="p">.</span><span class="nx">ontouchend</span><span class="o">=</span><span class="nx">e</span><span class="p">=></span><span class="nx">f</span><span class="o">=</span><span class="mi">0</span>
- <span class="o"><</span><span class="err">/script></span>
- </code></pre></div>
- <p>Or, as a short one-liner bookmark:</p>
- <div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="nx">data</span><span class="o">:</span><span class="nx">text</span><span class="o">/</span><span class="nx">html</span><span class="p">,</span><span class="o"><</span><span class="nx">canvas</span> <span class="nx">id</span><span class="o">=</span><span class="s2">"v"</span><span class="o">><</span><span class="nx">script</span><span class="o">></span><span class="nx">d</span><span class="o">=</span><span class="nb">document</span><span class="p">,</span><span class="nx">d</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">margin</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span><span class="nx">f</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span><span class="nx">c</span><span class="o">=</span><span class="nx">v</span><span class="p">.</span><span class="nx">getContext</span><span class="p">(</span><span class="s2">"2d"</span><span class="p">),</span><span class="nx">v</span><span class="p">.</span><span class="nx">width</span><span class="o">=</span><span class="nx">innerWidth</span><span class="p">,</span><span class="nx">v</span><span class="p">.</span><span class="nx">height</span><span class="o">=</span><span class="nx">innerHeight</span><span class="p">,</span><span class="nx">c</span><span class="p">.</span><span class="nx">lineWidth</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span><span class="nx">x</span><span class="o">=</span><span class="nx">e</span><span class="p">=></span><span class="nx">e</span><span class="p">.</span><span class="nx">clientX</span><span class="o">||</span><span class="nx">e</span><span class="p">.</span><span class="nx">touches</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">clientX</span><span class="p">,</span><span class="nx">y</span><span class="o">=</span><span class="nx">e</span><span class="p">=></span><span class="nx">e</span><span class="p">.</span><span class="nx">clientY</span><span class="o">||</span><span class="nx">e</span><span class="p">.</span><span class="nx">touches</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">clientY</span><span class="p">,</span><span class="nx">d</span><span class="p">.</span><span class="nx">onmousedown</span><span class="o">=</span><span class="nx">d</span><span class="p">.</span><span class="nx">ontouchstart</span><span class="o">=</span><span class="nx">e</span><span class="p">=>{</span><span class="nx">f</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span><span class="nx">e</span><span class="p">.</span><span class="nx">preventDefault</span><span class="p">(),</span><span class="nx">c</span><span class="p">.</span><span class="nx">moveTo</span><span class="p">(</span><span class="nx">x</span><span class="p">(</span><span class="nx">e</span><span class="p">),</span><span class="nx">y</span><span class="p">(</span><span class="nx">e</span><span class="p">)),</span><span class="nx">c</span><span class="p">.</span><span class="nx">beginPath</span><span class="p">()},</span><span class="nx">d</span><span class="p">.</span><span class="nx">onmousemove</span><span class="o">=</span><span class="nx">d</span><span class="p">.</span><span class="nx">ontouchmove</span><span class="o">=</span><span class="nx">e</span><span class="p">=>{</span><span class="nx">f</span><span class="o">&&</span><span class="p">(</span><span class="nx">c</span><span class="p">.</span><span class="nx">lineTo</span><span class="p">(</span><span class="nx">x</span><span class="p">(</span><span class="nx">e</span><span class="p">),</span><span class="nx">y</span><span class="p">(</span><span class="nx">e</span><span class="p">)),</span><span class="nx">c</span><span class="p">.</span><span class="nx">stroke</span><span class="p">())},</span><span class="nx">d</span><span class="p">.</span><span class="nx">onmouseup</span><span class="o">=</span><span class="nx">d</span><span class="p">.</span><span class="nx">ontouchend</span><span class="o">=</span><span class="nx">e</span><span class="p">=></span><span class="nx">f</span><span class="o">=</span><span class="mi">0</span><span class="o"><</span><span class="err">/script></span>
- </code></pre></div>
- <h2 id="spreadsheet">spreadsheet</h2>
- <p>This would probably be the most complex one and the largest one, but we will try to stay below the limit of 1KB per app.</p>
- <p>The layout would be simple. HTML comes with tables, so why don’t we use them. As the spreadsheet cells are normally addressable by “letter” + “number”, let’s restrict our table to 26x100 cells. It makes sense to create rows and cells dynamically in a loop. Some basic styling would make our spreadsheet look nicer:</p>
- <div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="o"><</span><span class="nx">table</span> <span class="nx">id</span><span class="o">=</span><span class="s2">"t"</span><span class="o">></span>
-
- <span class="nx">t</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">borderCollapse</span><span class="o">=</span><span class="s2">"collapse"</span><span class="p">;</span> <span class="c1">// remove gaps between cells
- </span><span class="c1"></span><span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="mi">101</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
- <span class="kr">const</span> <span class="nx">row</span> <span class="o">=</span> <span class="nx">t</span><span class="p">.</span><span class="nx">insertRow</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">);</span>
- <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">j</span> <span class="o"><</span> <span class="mi">27</span><span class="p">;</span> <span class="nx">j</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
- <span class="c1">// convert column index j to a letter (char code of "A" is 65)
- </span><span class="c1"></span> <span class="kr">const</span> <span class="nx">letter</span> <span class="o">=</span> <span class="nb">String</span><span class="p">.</span><span class="nx">fromCharCode</span><span class="p">(</span><span class="mi">65</span><span class="o">+</span><span class="nx">j</span><span class="o">-</span><span class="mi">1</span><span class="p">);</span> <span class="c1">// 1=A, 2=B, 3=C etc
- </span><span class="c1"></span> <span class="kr">const</span> <span class="nx">cell</span> <span class="o">=</span> <span class="nx">row</span><span class="p">.</span><span class="nx">insertCell</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">);</span>
- <span class="nx">cell</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">border</span> <span class="o">=</span> <span class="s2">"1px solid silver"</span><span class="p">;</span> <span class="c1">// make thin grey border
- </span><span class="c1"></span> <span class="nx">cell</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">textAlign</span> <span class="o">=</span> <span class="s2">"right"</span><span class="p">;</span> <span class="c1">// right-align, like excel
- </span><span class="c1"></span> <span class="k">if</span> <span class="p">(</span><span class="nx">i</span> <span class="o">></span> <span class="mi">0</span> <span class="o">&&</span> <span class="nx">j</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
- <span class="c1">// add identifiable input field, this is where formula is entered
- </span><span class="c1"></span> <span class="kr">const</span> <span class="nx">field</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s1">'input'</span><span class="p">);</span>
- <span class="nx">field</span><span class="p">.</span><span class="nx">id</span> <span class="o">=</span> <span class="nx">letter</span> <span class="o">+</span> <span class="nx">i</span><span class="p">;</span> <span class="c1">// i.e, "B3"
- </span><span class="c1"></span> <span class="nx">cell</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">field</span><span class="p">);</span>
- <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">i</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
- <span class="c1">// Row numbers
- </span><span class="c1"></span> <span class="nx">cell</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="nx">i</span><span class="p">;</span>
- <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">j</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
- <span class="c1">// Column letters
- </span><span class="c1"></span> <span class="nx">cell</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="nx">letter</span><span class="p">;</span>
- <span class="p">}</span>
- <span class="p">}</span>
- <span class="p">}</span>
- </code></pre></div>
- <p>Now we have a large grid of cells, with rows and columns. Time to add an expression evaluator. We can achieve a hacky, but mostly working evaluator with 3 arrays - an array of all input fields (to get their actual entered values, number or formulas), an array that has a smart getter, calling eval() if a variable is requested and it is linked to an input field with the formula, and a cache of the last entered values for each field:</p>
- <div class="highlight"><pre class="chroma"><code class="language-fallback" data-lang="fallback">inputs = []; // assume that we did inputs.push(field) for each field in the loop above
- data = {}; // smart data accessing object
- cache = {}; // cache
-
- // Re-calculate all fields
- const calc = () => {
- inputs.map(field => {
- try {
- field.value = D[field.id];
- } catch (e) { /* ignore */}
- });
- };
-
- // We also need to customize our field initialization code:
- field.onfocus = () => {
- // When element is focused - replace its calculated value with its formula
- field.value = cache[field.id] || "";
- };
- field.onblur = () => {
- // When element loses focus - put formula in cache, and re-calculate everything
- cache[field.id] = field.value;
- calc();
- };
- // Smart getter for a field, evaluates formula if needed
- const get = () => {
- let value = cache[field.id] || "";
- if(value.chatAt(0) == "=") {
- // evaluate the formula using "with" hack:
- with(data) return eval(value.substring(1));
- } else {
- // return value as it is, convert to number if possible:
- return isNaN(parseFloat(value)) ? value : parseFloat(value);
- }
- };
- // Add smart getter to the data array for both upper and lower case variants:
- Object.defineProperty(data, field.id, {get}),
- Object.defineProperty(data, field.id.toLowerCase(), {get})
- </code></pre></div>
- <p>Now the spreadsheet should work, if you put, for example, “42” into A1 and “=A1+3” into A2 - you should see “45” when you move the focus away from A2.</p>
- <p>After carefully minizing the code above, we get the following ~800 byte working spreadsheet:</p>
- <div class="highlight"><pre class="chroma"><code class="language-js" data-lang="js"><span class="nx">data</span><span class="o">:</span><span class="nx">text</span><span class="o">/</span><span class="nx">html</span><span class="p">,</span><span class="o"><</span><span class="nx">table</span> <span class="nx">id</span><span class="o">=</span><span class="s2">"t"</span><span class="o">><</span><span class="nx">script</span><span class="o">></span><span class="k">for</span><span class="p">(</span><span class="nx">I</span><span class="o">=</span><span class="p">[],</span><span class="nx">D</span><span class="o">=</span><span class="p">{},</span><span class="nx">C</span><span class="o">=</span><span class="p">{},</span><span class="nx">calc</span><span class="o">=</span><span class="p">()=></span><span class="nx">I</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">e</span><span class="p">=>{</span><span class="k">try</span><span class="p">{</span><span class="nx">e</span><span class="p">.</span><span class="nx">value</span><span class="o">=</span><span class="nx">D</span><span class="p">[</span><span class="nx">e</span><span class="p">.</span><span class="nx">id</span><span class="p">]}</span><span class="k">catch</span><span class="p">(</span><span class="nx">e</span><span class="p">){}}),</span><span class="nx">t</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">borderCollapse</span><span class="o">=</span><span class="s2">"collapse"</span><span class="p">,</span><span class="nx">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="nx">i</span><span class="o"><</span><span class="mi">101</span><span class="p">;</span><span class="nx">i</span><span class="o">++</span><span class="p">)</span><span class="k">for</span><span class="p">(</span><span class="nx">r</span><span class="o">=</span><span class="nx">t</span><span class="p">.</span><span class="nx">insertRow</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">),</span><span class="nx">j</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="nx">j</span><span class="o"><</span><span class="mi">27</span><span class="p">;</span><span class="nx">j</span><span class="o">++</span><span class="p">)</span><span class="nx">c</span><span class="o">=</span><span class="nb">String</span><span class="p">.</span><span class="nx">fromCharCode</span><span class="p">(</span><span class="mi">65</span><span class="o">+</span><span class="nx">j</span><span class="o">-</span><span class="mi">1</span><span class="p">),</span><span class="nx">d</span><span class="o">=</span><span class="nx">r</span><span class="p">.</span><span class="nx">insertCell</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">),</span><span class="nx">d</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">border</span><span class="o">=</span><span class="s2">"1px solid gray"</span><span class="p">,</span><span class="nx">d</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">textAlign</span><span class="o">=</span><span class="s2">"right"</span><span class="p">,</span><span class="nx">d</span><span class="p">.</span><span class="nx">innerHTML</span><span class="o">=</span><span class="nx">i</span><span class="o">?</span><span class="nx">j</span><span class="o">?</span><span class="s2">""</span><span class="o">:</span><span class="nx">i</span><span class="o">:</span><span class="nx">c</span><span class="p">,</span><span class="nx">i</span><span class="o">*</span><span class="nx">j</span><span class="o">&&</span><span class="nx">I</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">d</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">((</span><span class="nx">f</span><span class="p">=>(</span><span class="nx">f</span><span class="p">.</span><span class="nx">id</span><span class="o">=</span><span class="nx">c</span><span class="o">+</span><span class="nx">i</span><span class="p">,</span><span class="nx">f</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">border</span><span class="o">=</span><span class="s2">"none"</span><span class="p">,</span><span class="nx">f</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">width</span><span class="o">=</span><span class="s2">"4rem"</span><span class="p">,</span><span class="nx">f</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">textAlign</span><span class="o">=</span><span class="s2">"right"</span><span class="p">,</span><span class="nx">f</span><span class="p">.</span><span class="nx">onfocus</span><span class="o">=</span><span class="nx">e</span><span class="p">=></span><span class="nx">f</span><span class="p">.</span><span class="nx">value</span><span class="o">=</span><span class="nx">C</span><span class="p">[</span><span class="nx">f</span><span class="p">.</span><span class="nx">id</span><span class="p">]</span><span class="o">||</span><span class="s2">""</span><span class="p">,</span><span class="nx">f</span><span class="p">.</span><span class="nx">onblur</span><span class="o">=</span><span class="nx">e</span><span class="p">=>{</span><span class="nx">C</span><span class="p">[</span><span class="nx">f</span><span class="p">.</span><span class="nx">id</span><span class="p">]</span><span class="o">=</span><span class="nx">f</span><span class="p">.</span><span class="nx">value</span><span class="p">,</span><span class="nx">calc</span><span class="p">()},</span><span class="nx">get</span><span class="o">=</span><span class="p">()=>{</span><span class="kd">let</span> <span class="nx">v</span><span class="o">=</span><span class="nx">C</span><span class="p">[</span><span class="nx">f</span><span class="p">.</span><span class="nx">id</span><span class="p">]</span><span class="o">||</span><span class="s2">""</span><span class="p">;</span><span class="k">if</span><span class="p">(</span><span class="s2">"="</span><span class="o">!=</span><span class="nx">v</span><span class="p">.</span><span class="nx">charAt</span><span class="p">(</span><span class="mi">0</span><span class="p">))</span><span class="k">return</span> <span class="nb">isNaN</span><span class="p">(</span><span class="nb">parseFloat</span><span class="p">(</span><span class="nx">v</span><span class="p">))</span><span class="o">?</span><span class="nx">v</span><span class="o">:</span><span class="nb">parseFloat</span><span class="p">(</span><span class="nx">v</span><span class="p">);</span><span class="kd">with</span><span class="p">(</span><span class="nx">D</span><span class="p">)</span><span class="k">return</span> <span class="nb">eval</span><span class="p">(</span><span class="nx">v</span><span class="p">.</span><span class="nx">substring</span><span class="p">(</span><span class="mi">1</span><span class="p">))},</span><span class="nb">Object</span><span class="p">.</span><span class="nx">defineProperty</span><span class="p">(</span><span class="nx">D</span><span class="p">,</span><span class="nx">f</span><span class="p">.</span><span class="nx">id</span><span class="p">,{</span><span class="nx">get</span><span class="o">:</span><span class="nx">get</span><span class="p">}),</span><span class="nb">Object</span><span class="p">.</span><span class="nx">defineProperty</span><span class="p">(</span><span class="nx">D</span><span class="p">,</span><span class="nx">f</span><span class="p">.</span><span class="nx">id</span><span class="p">.</span><span class="nx">toLowerCase</span><span class="p">(),{</span><span class="nx">get</span><span class="o">:</span><span class="nx">get</span><span class="p">}),</span><span class="nx">f</span><span class="p">))(</span><span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s2">"input"</span><span class="p">))))</span><span class="o"><</span><span class="err">/script></span>
- </code></pre></div>
- <h2 id="are-you-serious">are you serious?</h2>
- <p>Well, in no way it’s a replacement for a proper office suite. But it’s a good demonstration of minimalism and tiny code. All these apps are ephemeral, they lose their state if you refresh the page and it looks like there is no way for data URLs to keep their state. But they might be helpful as quick bookmarks if you need to calculate a few bits or draft some quick note without opening heavy “real” office apps. As a bonus, all these tiny apps are ultimately respectful to your privacy and do no share your data (do not store it either).</p>
- <p>So, yes, it is more of a joke than a serious application, but still, I created a repo for these tiny apps in case anyone would like to use them or customize further for their own needs: <a href="http://github.com/zserge/awfice">http://github.com/zserge/awfice</a>. PRs and further improvements are welcome!</p>
- <p>I hope you’ve enjoyed this article. You can follow – and contribute to – on <a href="https://github.com/zserge">Github</a>, <a href="https://twitter.com/zsergo">Twitter</a> or subscribe via <a href="https://zserge.com/rss.xml">rss</a>.</p>
- <p class="date"><em>Oct 11, 2020</em></p>
- </article>
-
-
- <hr>
-
- <footer>
- <p>
- <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
- <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
- </svg> Accueil</a> •
- <a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2">
- <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-rss2"></use>
- </svg> Suivre</a> •
- <a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie">
- <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-user-tie"></use>
- </svg> Pro</a> •
- <a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail">
- <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-mail"></use>
- </svg> Email</a> •
- <abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2">
- <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-hammer2"></use>
- </svg> Légal</abbr>
- </p>
- <template id="theme-selector">
- <form>
- <fieldset>
- <legend><svg class="icon icon-brightness-contrast">
- <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-brightness-contrast"></use>
- </svg> Thème</legend>
- <label>
- <input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
- </label>
- <label>
- <input type="radio" value="dark" name="chosen-color-scheme"> Foncé
- </label>
- <label>
- <input type="radio" value="light" name="chosen-color-scheme"> Clair
- </label>
- </fieldset>
- </form>
- </template>
- </footer>
- <script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script>
- <script>
- function loadThemeForm(templateName) {
- const themeSelectorTemplate = document.querySelector(templateName)
- const form = themeSelectorTemplate.content.firstElementChild
- themeSelectorTemplate.replaceWith(form)
-
- form.addEventListener('change', (e) => {
- const chosenColorScheme = e.target.value
- localStorage.setItem('theme', chosenColorScheme)
- toggleTheme(chosenColorScheme)
- })
-
- const selectedTheme = localStorage.getItem('theme')
- if (selectedTheme && selectedTheme !== 'undefined') {
- form.querySelector(`[value="${selectedTheme}"]`).checked = true
- }
- }
-
- const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
- window.addEventListener('load', () => {
- let hasDarkRules = false
- for (const styleSheet of Array.from(document.styleSheets)) {
- let mediaRules = []
- for (const cssRule of styleSheet.cssRules) {
- if (cssRule.type !== CSSRule.MEDIA_RULE) {
- continue
- }
- // WARNING: Safari does not have/supports `conditionText`.
- if (cssRule.conditionText) {
- if (cssRule.conditionText !== prefersColorSchemeDark) {
- continue
- }
- } else {
- if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
- continue
- }
- }
- mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
- }
-
- // WARNING: do not try to insert a Rule to a styleSheet you are
- // currently iterating on, otherwise the browser will be stuck
- // in a infinite loop…
- for (const mediaRule of mediaRules) {
- styleSheet.insertRule(mediaRule.cssText)
- hasDarkRules = true
- }
- }
- if (hasDarkRules) {
- loadThemeForm('#theme-selector')
- }
- })
- </script>
- </body>
- </html>
|