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

Chromadb on fly.io adding some auth

2024-05-30

Next

Adapting to new mediums

2024-06-09

howto

Previously

Basic LLM Chat UI the base of a few experiments

2024-04-26

Next

How to use pako with vite javascript string encoding is simply insane

2024-06-14