APL bookmarklet for modern Jupyter

Has anyone managed to get the APL bookmarklet working for modern Jupyter?

ChatGPT came up with this, but it only works for clicking (poorly):

// APL toolbar for modern Jupyter / CM6
(function () {
  if (window.__apl_toolbar_installed) { document.querySelector('.apl_toolbar').toggleAttribute('hidden'); return; }
  window.__apl_toolbar_installed = true;

  // --- helpers
  const he = s => s.replace(/[<&'"]/g, c => ({'<':'&lt;','&':'&amp;',"'":'&apos;','"':'&quot;'}[c]));
  const insertIntoTextarea = (t, text) => {
    const i = t.selectionStart, j = t.selectionEnd;
    t.value = t.value.slice(0, i) + text + t.value.slice(j);
    t.selectionStart = t.selectionEnd = i + text.length;
    t.focus();
    t.dispatchEvent(new Event('input', { bubbles: true }));
  };
  const insertIntoContentEditable = (el, text) => {
    const sel = window.getSelection();
    if (!sel.rangeCount) {
      // focus and place caret at end
      el.focus();
      const range = document.createRange();
      range.selectNodeContents(el);
      range.collapse(false);
      sel.removeAllRanges();
      sel.addRange(range);
    }
    const range = sel.getRangeAt(0);
    range.deleteContents();
    // insert text node(s)
    const tn = document.createTextNode(text);
    range.insertNode(tn);
    // move caret after inserted node
    range.setStartAfter(tn);
    range.collapse(true);
    sel.removeAllRanges();
    sel.addRange(range);
    // notify CM6 by dispatching input events on nearest editable
    let node = el;
    el.dispatchEvent(new InputEvent('input', { bubbles: true }));
    // try to find ancestor CodeMirror textarea/input to trigger internal handlers
    const ta = el.closest('.jp-Notebook, .jp-FileEditor, .jp-Editor')?.querySelector('textarea');
    if (ta) { ta.dispatchEvent(new Event('input', { bubbles: true })); }
    el.focus();
  };
  const insertSymbol = s => {
    const ae = document.activeElement;
    if (!ae) return;
    if ((ae.tagName === 'TEXTAREA') || (ae.tagName === 'INPUT' && (ae.type === 'text' || ae.type === 'search'))) {
      insertIntoTextarea(ae, s);
      return;
    }
    // try to find nearest contenteditable cm element
    let ce = ae;
    if (!ce || !ce.isContentEditable) {
      // sometimes CodeMirror's actual editable is the .cm-content inside the editor
      ce = document.querySelector('.cm-content[contenteditable="true"]:focus') || document.querySelector('.cm-content[contenteditable="true"]');
    }
    if (ce && ce.isContentEditable) {
      insertIntoContentEditable(ce, s);
      return;
    }
    // last resort: insert into body at caret
    const sel = window.getSelection();
    if (sel.rangeCount) {
      const r = sel.getRangeAt(0);
      r.deleteContents();
      r.insertNode(document.createTextNode(s));
      r.collapse(false);
      sel.removeAllRanges();
    }
  };

  // --- symbol sets (kept close to original)
  const lbs = [
    '←',' ', '⍟','○','*','!','?',' ',
    '⌹','○','!!','??',' ','|','⌈','⌊',
    '⊥','⊤','⊣','⊢',' ','==','≠','≤',
    '<','>','≥','≡','≢',' ','∨','∧',
    '⍲','⍱',' ','↑','↓','⊂','⊃','⊆',
    '⌷','⍋','⍒',' ','⍳','⍸','∊','⍷',
    '∪','∩','~',' ','/','\\','⌿','⍀',
    ',','⍪','⍴','⌽','⊖','⍉',' ','¨','⍨',
    '⍣','..','∘','⍛','⍤','⍥','@@',' ',
    '⍞','⎕','⍠','⌸','⌺','⌶','⍎','⍕','⋄','⍝','→','⍵','⍺','∇','&&',' ',
    '¯','⍬','∆','⍙'
  ];
  // optional small labels for tooltip (kept short)
  const tt = {
    '←':'assign','⌹':'matrx_inv','⍟':'log','⍳':'indices','∊':'member','⍴':'shape','∘':'compose','⍎':'execute','⍕':'format'
  };

  // --- build bar
  const bar = document.createElement('div');
  bar.className = 'apl_toolbar';
  bar.style.cssText = `
    position:fixed;left:0;right:0;top:0;z-index:2147483647;
    font-family: "DejaVu Sans Mono", monospace; background:#f3f3f3;
    color:#111;border-bottom:1px solid #bbb;padding:3px 6px;display:flex;flex-wrap:wrap;gap:4px;
  `;
  // close button
  const close = document.createElement('button');
  close.innerText = '✖';
  close.title = 'Close';
  close.style.cssText = 'margin-right:8px';
  close.onclick = () => { bar.hidden = true; };
  bar.appendChild(close);

  // build buttons
  lbs.forEach(sym => {
    const b = document.createElement('button');
    b.type = 'button';
    b.className = 'apl_sym';
    b.title = tt[sym] || sym;
    b.textContent = sym;
    b.style.cssText = `
      padding:2px 6px;border:1px solid #ccc;border-radius:4px;background:white;
      font-size:14px;line-height:1;cursor:pointer;min-width:26px;text-align:center;
    `;
    b.onclick = e => {
      insertSymbol(sym);
      e.preventDefault(); e.stopPropagation();
    };
    bar.appendChild(b);
  });

  // toggle backquote-mode helper info (for plaintext only)
  const info = document.createElement('span');
  info.style.cssText = 'margin-left:8px;font-size:12px;color:#444';
  info.innerHTML = 'Click symbols to insert. For plain textareas: press ` then key for composed APL characters.';
  bar.appendChild(info);

  document.body.appendChild(bar);
  // push page content down so it doesn't overlap editor top
  const _updateMargin = () => { document.body.style.marginTop = bar.clientHeight + 'px'; };
  _updateMargin();
  window.addEventListener('resize', _updateMargin);

  // --- backquote mappings (for plaintext use) copied and simplified from original
  const bqk = " =1234567890-qwertyuiop\\asdfghjk∙l;\'zxcvbnm,./`[]+!@#$%^&*()_QWERTYUIOP|ASDFGHJKL:\"ZXCVBNM<>?~{}".replace(/\∙/g,'');
  const bqv = "`÷¨¯<≤=≥>≠∨∧×?⍵∊⍴~↑↓⍳○*⊢∙⍺⌈⌊_∇∆∘'⎕⍎⍕∙⊂⊃∩∪⊥⊤|⍝⍀⌿⋄←→⌹⌶⍫⍒⍋⌽⍉⊖⍟⍱⍲!⍰W⍷R⍨YU⍸⍥⍣⊣ASD⍛⍢H⍤⌸⌷≡≢⊆⊇CVB¤∥⍪⍙⍠⌺⍞⍬".replace(/\∙/g,'');
  const bqc = {};
  for (let i=0;i<bqk.length;i++) bqc[bqk[i]] = bqv[i];

  // simple backquote mode for textareas/inputs (press ` then next key to insert mapped char)
  let backquoteMode = false;
  const handleKeydown = e => {
    const ae = document.activeElement;
    if (!ae) return;
    if (!((ae.tagName === 'TEXTAREA') || (ae.tagName === 'INPUT' && (ae.type === 'text' || ae.type === 'search')))) return;
    if (!e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey) {
      if (e.key === '`') {
        backquoteMode = true;
        document.body.classList.add('apl_bq');
        e.preventDefault();
        return;
      }
      if (backquoteMode) {
        backquoteMode = false;
        document.body.classList.remove('apl_bq');
        const mapped = bqc[e.key] || bqc[e.key.toLowerCase()];
        if (mapped) {
          insertIntoTextarea(ae, mapped);
          e.preventDefault();
          return;
        }
      }
      // handle tab-based short completions: check previous 2 chars
      if (e.key === 'Tab') {
        const t = ae;
        const i = t.selectionStart;
        if (i >= 2) {
          const two = t.value.slice(i-2,i);
          // naive set: some common pairs -> symbol mapping
          const tabMap = {'<-':'←','<-':'←','/\\':'⍀','..':'∘.'};
          const sub = tabMap[two];
          if (sub) {
            t.value = t.value.slice(0,i-2) + sub + t.value.slice(t.selectionEnd);
            t.selectionStart = t.selectionEnd = i-2 + sub.length;
            e.preventDefault();
            t.dispatchEvent(new Event('input',{bubbles:true}));
          }
        }
      }
    }
  };
  document.addEventListener('keydown', handleKeydown, true);

  // small style for backquote mode state
  const css = document.createElement('style');
  css.innerHTML = `
    .apl_toolbar .apl_sym:hover{background:#eee}
    body.apl_bq{outline: 2px dashed rgba(0,0,0,0.08);}
  `;
  document.head.appendChild(css);

  // expose a small API to remove toolbar if needed
  window.__APLToolbar = {
    remove: () => {
      bar.remove();
      document.body.style.marginTop = '';
      document.removeEventListener('keydown', handleKeydown, true);
      window.__apl_toolbar_installed = false;
      if (css.parentNode) css.remove();
    }
  };
})();

It seems to have been broken a couple of years ago, judging from this issue