Will Schenk

howto

Recreating notepadtab

how small can we go?

I found this cool little project called notepadtab.com that lets you enter text into a box, and it will encode it in the url, which you can then bookmark, or share around, or whatever. Put it through a url shortener or what have you and the next is available to see anywhere.

I wanted to see how it worked by implementing it myself. This code is much smaller than what is at revolter/notepadtab.com, and once I figured out how to add pako compression, I figured why not also add a QR code?

Important code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
  // main.js
  import '@unocss/reset/tailwind.css';
  import './main.css';
  import '@shoelace-style/shoelace/dist/themes/light.css';
  import '@shoelace-style/shoelace/dist/components/copy-button/copy-button.js';
  import '@shoelace-style/shoelace/dist/components/textarea/textarea.js';
  import '@shoelace-style/shoelace/dist/components/qr-code/qr-code.js';
  import pako from 'pako';

  function deflateToBase64( inputData ) {
      const compressed = pako.deflate( inputData );
      const base64 = window.btoa( String.fromCharCode.apply(null, compressed ));

      return base64;
  }

  function inflateFromBase64( base64 ) {
      const reverseBase64 = atob(base64);

      const reverseBase64Array = new Uint8Array(reverseBase64.split("").map(function(c) {
          return c.charCodeAt(0); }));

      const inflatedRaw = pako.inflate( reverseBase64Array );
      const decompressed = String.fromCharCode.apply( null, inflatedRaw );

      return decompressed;
  }

  window.addEventListener("load", (event) => {
      const box = document.getElementById( "note-area" )
      const copy = document.getElementById( "copy" );
      copy.value = window.location;
      
      const hash = window.location.hash
      if( hash != '' ) {
          // Restore the value
          const smaller = inflateFromBase64( hash.substring( 1 ) )
          box.value = smaller;
      }

      qrcode.value = window.location.href
      qrcode.size = 256;
      
      box.addEventListener( "sl-input", (event) => {
          // Update the url on change
          const hash = deflateToBase64( box.value )
          window.location.hash = '#' + hash
          copy.value = window.location
          qrcode.value = window.location.href
      });
  });

index.html:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
      <html>
        <head>
          <title>Notepad</title>
          <script src="main.js" type="module"></script>
          <meta name="viewport" content="width=device-width, initial-scale=1" />
        </head>
        <body px-2>
          <div max-w-prose mx-auto prose>
            <h1 font-header text-2xl font-bold>All your note data in the url</h1>

            <sl-copy-button float-right id="copy"></sl-copy-button>

            <sl-textarea
              id="note-area"
              label="Secret note"
              resize="auto"
              help-text="Nothing stored on the server, just a big url">
            </sl-textarea>

            <sl-qr-code
              id="qrcode"
              >
            </sl-qr-code>

            <p>
              Reimplementing <a href="https://notepadtab.com/">notepadtab</a> for fun.  This code is at
              <a href="https://github.com/wschenk/nodepadjs/">https://github.com/wschenk/nodepadjs/</a>.
            </p>
          </div>
        </body>
      </html>

Boilerplate junk

package.json:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
  {
      "type": "module",
      "scripts": {
          "dev": "unocss \"**/*.html\" -o main.css --watch & vite",
          "build": "unocss \"**/*.html\" -o main.css && vite build"
      },
      "dependencies": {
          "@shoelace-style/shoelace": "^2.15.1",
          "pako": "^2.1.0",
          "unocss": "^0.60.4",
          "vite": "^5.2.12",
          "vite-plugin-static-copy": "^1.0.5"
      }
  }
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
  // uno.config.ts
  import {
      defineConfig,
      presetAttributify,
      presetTypography,
      presetUno
  } from 'unocss'

  export default defineConfig({
    presets: [
        presetAttributify(), // required when using attributify mode
        presetUno(), // required
        presetTypography(),
    ],
  })

Update the base if you are deploying it somewhere else, I'm using github pages without a domain so it's in a subdirectory.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
  // vite.config.js
  import { defineConfig } from 'vite';
  import { viteStaticCopy } from 'vite-plugin-static-copy';

  const iconsPath = 'node_modules/@shoelace-style/shoelace/dist/assets/icons';

  // https://vitejs.dev/config/
  export default defineConfig({
      base: '/nodepadjs/', // Or / if you arent using github pages
      resolve: {
          alias: [
              {
                  find: /\/assets\/icons\/(.+)/,
                  replacement: `${iconsPath}/$1`,
              },
          ],
      },
      build: {
          rollupOptions: {
              // external: /^lit/,
              plugins: [],
          },
      },
      plugins: [
          viteStaticCopy({
              targets: [
                  {
                      src: iconsPath,
                      dest: 'assets',
                  },
              ],
          }),
      ],
  });

Deploy

1
  mkdir -p .github/workflows
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
  name: Deploy

  on:
    push:
      branches:
        - main

  jobs:
    build:
      name: Build
      runs-on: ubuntu-latest

      steps:
        - name: Checkout repo
          uses: actions/checkout@v3

        - name: Setup Node
          uses: actions/setup-node@v3

        - name: Install dependencies
          uses: bahmutov/npm-install@v1

        - name: Build project
          run: npm run build

        - name: Upload production-ready build files
          uses: actions/upload-artifact@v3
          with:
            name: production-files
            path: ./dist

    deploy:
      name: Deploy
      needs: build
      runs-on: ubuntu-latest
      if: github.ref == 'refs/heads/main'

      steps:
        - name: Download artifact
          uses: actions/download-artifact@v3
          with:
            name: production-files
            path: ./dist

        - name: Deploy to GitHub Pages
          uses: peaceiris/actions-gh-pages@v3
          with:
            github_token: ${{ secrets.GITHUB_TOKEN }}
            publish_dir: ./dist

Check it out

Deployed here. It takes forever because the vite build includes all of the icons for shoelace…

Previously

labnotes

Chromadb on fly.io adding some auth

Next

fragments

Adapting to new mediums