opencode-notepad 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -69,7 +69,8 @@ OpenCode installs the package and reloads plugins when the config changes.
69
69
 
70
70
  - `bun run dev` rebuilds the GUI on every change. Each rebuild reloads the GUI in every window.
71
71
  - The server reloads when a file in `src/` changes.
72
- - `bun test` covers the note format and the file store. `bun run typecheck` runs `tsc`.
72
+ - `bun run test` covers the note format and the file store. `bun run typecheck` runs `tsc`.
73
+ - `bun run test:e2e` drives the highlight bar in Chrome with real mouse presses, including while the page streams and scrolls. Set `CHROME_PATH` if Chrome is not in `/Applications`.
73
74
 
74
75
  ## Markdown Preview
75
76
 
@@ -2,4 +2,4 @@ Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{val
2
2
  `).split(`
3
3
  `),n=[],r=0;for(;r<t.length;r++){let e=a.exec(t[r]);if(!e)break;n.push({checked:e[1]!==` `,text:e[2].trim()})}let i=t.slice(r);for(;i.length>0&&i[0].trim()===``;)i.shift();return{tasks:n,body:i.join(`
4
4
  `).trimEnd()}}function s(e){return e.replace(/^\s*(?:[-*+•]|\d+[.)])\s+(\[( |x|X)\]\s*)?/,``).replace(/\s*\n\s*/g,` `).trim()}function c(e){return e.split(/\r?\n/).map(s).filter(Boolean)}function l(e){return e.trim().split(/\r?\n/).map(e=>e.trim()?`> ${e.trimEnd()}`:`>`).join(`
5
- `)}var u={type:`string`,minLength:1},d={type:`object`,properties:{sessionID:u,doc:{type:`string`},path:{type:`string`}},required:[`sessionID`,`doc`,`path`],additionalProperties:!1},f={type:`integer`,minimum:0};function p(e){return{type:`object`,properties:{sessionID:u,...e},required:[`sessionID`,...Object.keys(e)],additionalProperties:!1}}var m=i.Rpc.define({id:`notepad`,methods:{read:{input:p({}),output:d},add:{input:p({items:{type:`array`,items:{type:`string`}}}),output:d},check:{input:p({index:f,checked:{type:`boolean`}}),output:d},remove:{input:p({index:f}),output:d},clearDone:{input:p({}),output:d},appendNote:{input:p({markdown:{type:`string`}}),output:d},setBody:{input:p({body:{type:`string`}}),output:d}},events:{updated:{schema:d}}}),h=(0,e.template)(`<div class=notepad-selection>`),g=(0,e.template)(`<span class=notepad-selection-divider>`),_=(0,e.template)(`<button type=button>`),v=`input, textarea, select, [contenteditable=''], [contenteditable='true'], .notepad, .notepad-selection`;function y(){let e=window.getSelection();if(!e||e.isCollapsed||e.rangeCount===0)return;let t=e.toString();if(!t.trim())return;let n=e.anchorNode;if((n instanceof Element?n:n?.parentElement)?.closest(v))return;let r=e.getRangeAt(0).getBoundingClientRect();if(r.width!==0||r.height!==0)return{text:t,top:r.top,left:r.left+r.width/2}}function b(n){let[r,i]=(0,t.createSignal)(),a,o=()=>{clearTimeout(a),a=setTimeout(()=>i(y()),120)},s=()=>i(void 0);(0,t.onMount)(()=>{document.addEventListener(`selectionchange`,o),window.addEventListener(`scroll`,s,!0),window.addEventListener(`resize`,s)}),(0,t.onCleanup)(()=>{clearTimeout(a),document.removeEventListener(`selectionchange`,o),window.removeEventListener(`scroll`,s,!0),window.removeEventListener(`resize`,s)});function c(e){let t=r();t&&(window.getSelection()?.removeAllRanges(),s(),e.run(t.text))}return(0,e.createComponent)(t.Show,{get when(){return r()},children:r=>(0,e.createComponent)(e.Portal,{get children(){var i=h();return i.$$mousedown=e=>e.preventDefault(),(0,e.insert)(i,(0,e.createComponent)(t.For,{get each(){return n.actions},children:(n,r)=>[(0,e.createComponent)(t.Show,{get when(){return r()>0},get children(){return g()}}),(()=>{var t=_();return t.$$click=()=>c(n),(0,e.insert)(t,()=>n.label),t})()]})),(0,e.effect)(t=>{var n=`${r().top<48?r().top+28:r().top-40}px`,a=`${Math.min(Math.max(r().left,160),window.innerWidth-160)}px`;return n!==t.e&&(0,e.setStyleProperty)(i,`top`,t.e=n),a!==t.t&&(0,e.setStyleProperty)(i,`left`,t.t=a),t},{e:void 0,t:void 0}),i}})})}(0,e.delegateEvents)([`mousedown`,`click`]);var x=`.notepad{box-sizing:border-box;flex-direction:column;gap:8px;height:100%;padding:10px 12px 16px;display:flex;overflow-y:auto}.notepad-toolbar{align-items:center;gap:4px;display:flex}.notepad-toolbar>:not(.notepad-add){flex-shrink:0}.notepad-add{flex:1;min-width:0;margin-right:2px}.notepad-add>*{width:100%}.notepad-tasks{margin:0;padding:0;list-style:none}.notepad-task{border-radius:6px;align-items:flex-start;gap:4px;padding:2px 2px 2px 4px;display:flex}.notepad-task:hover{background:color-mix(in srgb, currentColor 6%, transparent)}.notepad-task label{cursor:pointer;overflow-wrap:anywhere;flex:1;align-items:flex-start;gap:8px;min-width:0;padding:3px 0;line-height:1.4;display:flex}.notepad-task input[type=checkbox]{width:14px;height:14px;accent-color:var(--text-interactive-base,currentColor);cursor:pointer;flex-shrink:0;margin:2px 0 0}.notepad-task.is-done label span{opacity:.5}.notepad-remove{opacity:0;flex-shrink:0}.notepad-task:hover .notepad-remove,.notepad-remove:focus-visible{opacity:1}.notepad-clear{justify-content:flex-end;display:flex}.notepad-notes-header{border-top:1px solid color-mix(in srgb, currentColor 12%, transparent);opacity:.8;justify-content:space-between;align-items:center;margin-top:4px;padding-top:8px;font-size:12px;font-weight:600;display:flex}.notepad-empty{opacity:.5;margin:0;font-size:13px}.notepad-body{min-height:2em}.notepad-editor{resize:vertical;border:1px solid color-mix(in srgb, currentColor 18%, transparent);min-height:240px;color:inherit;tab-size:2;background:0 0;border-radius:6px;flex:1;padding:8px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12.5px;line-height:1.5}.notepad-editor:focus{border-color:color-mix(in srgb, currentColor 35%, transparent);outline:none}.notepad-selection{z-index:2147483000;color:#f4f4f4;background:#1f1f1f;border-radius:8px;align-items:center;padding:3px;font-size:12px;animation:.12s ease-out notepad-pop;display:flex;position:fixed;transform:translate(-50%);box-shadow:0 6px 20px #00000047,0 0 0 1px #ffffff14}.notepad-selection button{all:unset;cursor:pointer;white-space:nowrap;border-radius:5px;padding:4px 9px}.notepad-selection button:hover,.notepad-selection button:focus-visible{background:#ffffff24}.notepad-selection-divider{background:#ffffff2e;width:1px;height:14px}@keyframes notepad-pop{0%{opacity:0;transform:translate(-50%)translateY(4px)}}@media (prefers-reduced-motion:reduce){.notepad-selection{animation:none}}`,S=(0,e.template)(`<div class=notepad-clear>`),C=(0,e.template)(`<textarea class=notepad-editor autofocus>`),w=(0,e.template)(`<div class=notepad><div class=notepad-toolbar><div class=notepad-add></div></div><ul class=notepad-tasks></ul><div class=notepad-notes-header><span>`),T=(0,e.template)(`<li class=notepad-task><label><input type=checkbox><span>`),E=(0,e.template)(`<div class=notepad-body>`),D=(0,e.template)(`<p class=notepad-empty>`),O=r.Plugin.define({i18n:{en:{title:`Notepad`,"command.toggle":`Toggle Notepad`,"add.label":`Add a checklist item`,"add.placeholder":`Add an item and press Enter`,remove:`Remove item`,clearDone:`Clear done`,notes:`Notes`,"notes.empty":`No notes yet. Select Edit to write Markdown.`,edit:`Edit`,done:`Done`,openPreview:`Open in Markdown Preview`,openFile:`Open file in default app`,copyPath:`Copy file path`,pill:`{{count}} open`,"pill.title":`Notepad checklist`,"pill.open":`Open Notepad`,error:`Notepad: {{message}}`,"selection.quote":`Quote`,"selection.list":`Add to list`,"selection.notes":`Add to notes`,"command.selectionList":`Add selection to Notepad list`,"command.selectionNotes":`Add selection to Notepad notes`,"added.list":`Added {{count}} item(s) to the Notepad list`,"added.notes":`Added the selection to Notepad notes`,"quote.source":`Conversation`,"quote.unavailable":`This chat can't take a quote right now`,noChat:`Open a chat first`}},setup(i){let a=i.ui,u=`${i.id}:main`,[d,f]=(0,n.createStore)({}),p=new Set,h=e=>o(e&&d[e]?.doc||``);function g(e,t){f(e,t)}function _(e){d[e]||p.has(e)||(p.add(e),i.client.rpc(m).read({sessionID:e}).then(t=>g(e,t),v).finally(()=>p.delete(e)))}(0,r.createKeyed)(()=>i.servers.get(`sidecar`)?.client,e=>{let n=e.rpc(m);for(let e of Object.keys(d))n.read({sessionID:e}).then(t=>g(e,t),v);(0,t.onCleanup)(n.events.on(`updated`,e=>{let t=e.data;for(let[e,n]of Object.entries(d))n.sessionID===t.sessionID&&g(e,t);d[t.sessionID]||g(t.sessionID,t)}))});function v(e){i.toast.show({title:i.t(`error`,{message:e instanceof Error?e.message:String(e)}),variant:`error`})}function O(e,t,n){t(i.client.rpc(m)).then(t=>{g(e,t),n?.()},v)}function k(){let e=i.sessions.current();return e||i.toast.show({title:i.t(`noChat`)}),e}function A(e){let t=k(),n=c(e);t&&n.length!==0&&O(t.id,e=>e.add({sessionID:t.id,items:n}),()=>i.toast.show({title:i.t(`added.list`,{count:n.length}),variant:`success`,duration:2e3}))}function j(e){let t=k();t&&O(t.id,n=>n.appendNote({sessionID:t.id,markdown:l(e)}),()=>i.toast.show({title:i.t(`added.notes`),variant:`success`,duration:2e3}))}function M(e){let t=i.screen.current()?.composer;if(!t?.quotable()){i.toast.show({title:i.t(`quote.unavailable`)});return}let n=crypto.randomUUID();t.attach({type:`quote`,commentID:n,text:e.trim(),source:{label:i.t(`quote.source`),icon:`speech-bubble`,subject:`this conversation`}}),t.focus(n)}function N(){let e=i.sessions.current();e&&i.layout.toggle(u,e)}let P={id:`main`,get title(){return i.t(`title`)},closable:`compact`,fallback:!0};i.add(r.Style,x),i.add(r.Panel,{id:`main`,region:`side`,mobile:{get title(){return i.t(`title`)},order:60,kind:`menu`,icon:`checklist`},list:e=>e.open.includes(`main`)?[P]:[],enter:()=>document.querySelector(`.notepad-add input`)?.focus(),render:t=>(0,e.createComponent)(I,{get session(){return t.session}})}),i.add(r.Command,()=>({id:`toggle`,title:i.t(`command.toggle`),keywords:`notepad todo checklist notes markdown`,bind:`mod+alt+n`,enabled:!!i.sessions.current(),run:N}));let F=[{id:`quote`,get label(){return i.t(`selection.quote`)},run:M},{id:`list`,get label(){return i.t(`selection.list`)},run:A},{id:`notes`,get label(){return i.t(`selection.notes`)},run:j}];i.add(r.Slot,{at:`window.bottom`,render:()=>(0,e.createComponent)(b,{actions:F})});for(let[e,t,n]of[[`selection-list`,`mod+alt+l`,A],[`selection-notes`,`mod+alt+k`,j]])i.add(r.Command,()=>({id:e,title:i.t(e===`selection-list`?`command.selectionList`:`command.selectionNotes`),keywords:`notepad selection highlight quote`,bind:t,run:()=>{let e=y();if(!e)return!1;window.getSelection()?.removeAllRanges(),n(e.text)}}));i.add(r.MenuItem,()=>{let e=i.sessions.current();return{menu:`session.panel`,id:`open`,title:i.t(`title`),icon:`checklist`,keybind:`${i.id}.toggle`,opened:!!e&&i.layout.state(u,e)!==`closed`,run:()=>{e&&i.layout.open(u,e)}}}),(0,r.createKeyed)(()=>i.sessions.current()?.id,e=>_(e)),i.add(r.ComposerPill,{id:`open`,show:e=>{let t=h(e.session.id).tasks.filter(e=>!e.checked);if(t.length!==0)return{icon:`checklist`,label:i.t(`pill`,{count:t.length}),title:i.t(`pill.title`),rows:[...t.slice(0,8).map((e,t)=>({id:`task-${t}`,label:e.text})),{id:`open`,label:i.t(`pill.open`),icon:`checklist`,run:()=>i.layout.open(u,e.session)}]}}});function I(n){let[o,c]=(0,t.createSignal)(``),[l,u]=(0,t.createSignal)(!1),[f,p]=(0,t.createSignal)(``),m,g=()=>n.session.id,v=()=>h(g()),y=()=>d[g()]?.path??``;(0,t.createEffect)(()=>_(g())),(0,r.createKeyed)(g,e=>{(0,t.onCleanup)(()=>{l()&&k(e),u(!1)})});function b(e){if(!s(e))return;c(``);let t=g();O(t,n=>n.add({sessionID:t,items:[e]}))}function x(){p(v().body),u(!0)}function k(e=g()){clearTimeout(m),f()!==h(e).body&&O(e,t=>t.setBody({sessionID:e,body:f()}))}function A(){k(),u(!1)}return(()=>{var n=w(),r=n.firstChild,s=r.firstChild,u=r.nextSibling,d=u.nextSibling,h=d.firstChild;return(0,e.insert)(s,(0,e.createComponent)(a.TextInput,{get label(){return i.t(`add.label`)},get placeholder(){return i.t(`add.placeholder`)},get value(){return o()},onInput:c,onSubmit:b})),(0,e.insert)(r,(0,e.createComponent)(a.Tooltip,{get value(){return i.t(`openPreview`)},get children(){return(0,e.createComponent)(a.IconButton,{icon:`square-arrow-top-right`,get label(){return i.t(`openPreview`)},variant:`ghost`,size:`small`,get disabled(){return!y()},onClick:()=>i.system.openExternal(`md-preview://file${encodeURI(y())}`)})}}),null),(0,e.insert)(r,(0,e.createComponent)(a.Menu,{get label(){return i.t(`title`)},icon:`outline-dots`,placement:`bottom-end`,get items(){return[{label:i.t(`openFile`),icon:`open-file`,onSelect:()=>i.system.openExternal(`file://${encodeURI(y())}`)},{label:i.t(`copyPath`),icon:`copy`,onSelect:()=>void i.system.copy(y())}]}}),null),(0,e.insert)(u,(0,e.createComponent)(t.For,{get each(){return v().tasks},children:(t,n)=>(()=>{var r=T(),o=r.firstChild.firstChild,s=o.nextSibling;return o.addEventListener(`change`,e=>{let t=e.currentTarget.checked,r=g();O(r,e=>e.check({sessionID:r,index:n(),checked:t}))}),(0,e.insert)(s,()=>t.text),(0,e.insert)(r,(0,e.createComponent)(a.IconButton,{icon:`xmark-small`,get label(){return i.t(`remove`)},variant:`ghost-muted`,size:`small`,class:`notepad-remove`,onClick:()=>{let e=g();O(e,t=>t.remove({sessionID:e,index:n()}))}}),null),(0,e.effect)(()=>r.classList.toggle(`is-done`,!!t.checked)),(0,e.effect)(()=>o.checked=t.checked),r})()})),(0,e.insert)(n,(0,e.createComponent)(t.Show,{get when(){return v().tasks.some(e=>e.checked)},get children(){var t=S();return(0,e.insert)(t,(0,e.createComponent)(a.Button,{variant:`ghost-muted`,size:`small`,onClick:()=>{let e=g();O(e,t=>t.clearDone({sessionID:e}))},get children(){return i.t(`clearDone`)}})),t}}),d),(0,e.insert)(h,()=>i.t(`notes`)),(0,e.insert)(d,(0,e.createComponent)(a.Button,{variant:`ghost`,size:`small`,get icon(){return l()?`check`:`pencil-line`},onClick:()=>l()?A():x(),get children(){return(0,e.memo)(()=>!!l())()?i.t(`done`):i.t(`edit`)}}),null),(0,e.insert)(n,(0,e.createComponent)(t.Show,{get when(){return l()},get fallback(){return(0,e.createComponent)(t.Show,{get when(){return v().body},get fallback(){return(()=>{var t=D();return(0,e.insert)(t,()=>i.t(`notes.empty`)),t})()},get children(){var t=E();return t.$$dblclick=x,(0,e.insert)(t,(0,e.createComponent)(a.Markdown,{get text(){return v().body}})),t}})},get children(){var t=C();return t.$$keydown=e=>{(e.key===`Escape`||e.key===`Enter`&&(e.metaKey||e.ctrlKey))&&(e.preventDefault(),A())},t.addEventListener(`blur`,()=>k()),t.$$input=e=>{p(e.currentTarget.value),clearTimeout(m),m=setTimeout(k,600)},(0,e.setAttribute)(t,`spellcheck`,!0),(0,e.effect)(()=>t.value=f()),t}}),null),n})()}}});(0,e.delegateEvents)([`input`,`keydown`,`dblclick`]),exports.default=O;
5
+ `)}var u={type:`string`,minLength:1},d={type:`object`,properties:{sessionID:u,doc:{type:`string`},path:{type:`string`}},required:[`sessionID`,`doc`,`path`],additionalProperties:!1},f={type:`integer`,minimum:0};function p(e){return{type:`object`,properties:{sessionID:u,...e},required:[`sessionID`,...Object.keys(e)],additionalProperties:!1}}var m=i.Rpc.define({id:`notepad`,methods:{read:{input:p({}),output:d},add:{input:p({items:{type:`array`,items:{type:`string`}}}),output:d},check:{input:p({index:f,checked:{type:`boolean`}}),output:d},remove:{input:p({index:f}),output:d},clearDone:{input:p({}),output:d},appendNote:{input:p({markdown:{type:`string`}}),output:d},setBody:{input:p({body:{type:`string`}}),output:d}},events:{updated:{schema:d}}}),h=(0,e.template)(`<div class=notepad-selection>`),g=(0,e.template)(`<span class=notepad-selection-divider>`),_=(0,e.template)(`<button type=button>`),v=`input, textarea, select, [contenteditable=''], [contenteditable='true'], .notepad, .notepad-selection`;function y(){let e=window.getSelection();if(!e||e.isCollapsed||e.rangeCount===0)return;let t=e.toString();if(!t.trim())return;let n=e.anchorNode;if((n instanceof Element?n:n?.parentElement)?.closest(v))return;let r=e.getRangeAt(0).getBoundingClientRect();if(r.width!==0||r.height!==0)return{text:t,top:r.top,left:r.left+r.width/2}}function b(n){let[r,i]=(0,t.createSignal)(),a,o,s=()=>{clearTimeout(a),a=setTimeout(()=>i(y()),120)},c=()=>{r()&&o===void 0&&(o=requestAnimationFrame(()=>{o=void 0,i(y())}))},l=()=>i(void 0);(0,t.onMount)(()=>{document.addEventListener(`selectionchange`,s),window.addEventListener(`scroll`,c,!0),window.addEventListener(`resize`,c)}),(0,t.onCleanup)(()=>{clearTimeout(a),o!==void 0&&cancelAnimationFrame(o),document.removeEventListener(`selectionchange`,s),window.removeEventListener(`scroll`,c,!0),window.removeEventListener(`resize`,c)});function u(e){let t=r();t&&(window.getSelection()?.removeAllRanges(),l(),e.run(t.text))}return(0,e.createComponent)(t.Show,{get when(){return r()},children:r=>(0,e.createComponent)(e.Portal,{get children(){var i=h();return i.$$mousedown=e=>e.preventDefault(),(0,e.insert)(i,(0,e.createComponent)(t.For,{get each(){return n.actions},children:(n,r)=>[(0,e.createComponent)(t.Show,{get when(){return r()>0},get children(){return g()}}),(()=>{var t=_();return t.$$click=e=>{e.detail===0&&u(n)},t.$$mousedown=e=>{e.preventDefault(),e.button===0&&u(n)},(0,e.insert)(t,()=>n.label),t})()]})),(0,e.effect)(t=>{var n=`${r().top<48?r().top+28:r().top-40}px`,a=`${Math.min(Math.max(r().left,160),window.innerWidth-160)}px`;return n!==t.e&&(0,e.setStyleProperty)(i,`top`,t.e=n),a!==t.t&&(0,e.setStyleProperty)(i,`left`,t.t=a),t},{e:void 0,t:void 0}),i}})})}(0,e.delegateEvents)([`mousedown`,`click`]);var x=`.notepad{box-sizing:border-box;flex-direction:column;gap:8px;height:100%;padding:10px 12px 16px;display:flex;overflow-y:auto}.notepad-toolbar{align-items:center;gap:4px;display:flex}.notepad-toolbar>:not(.notepad-add){flex-shrink:0}.notepad-add{flex:1;min-width:0;margin-right:2px}.notepad-add>*{width:100%}.notepad-tasks{margin:0;padding:0;list-style:none}.notepad-task{border-radius:6px;align-items:flex-start;gap:4px;padding:2px 2px 2px 4px;display:flex}.notepad-task:hover{background:color-mix(in srgb, currentColor 6%, transparent)}.notepad-task label{cursor:pointer;overflow-wrap:anywhere;flex:1;align-items:flex-start;gap:8px;min-width:0;padding:3px 0;line-height:1.4;display:flex}.notepad-task input[type=checkbox]{width:14px;height:14px;accent-color:var(--text-interactive-base,currentColor);cursor:pointer;flex-shrink:0;margin:2px 0 0}.notepad-task.is-done label span{opacity:.5}.notepad-remove{opacity:0;flex-shrink:0}.notepad-task:hover .notepad-remove,.notepad-remove:focus-visible{opacity:1}.notepad-clear{justify-content:flex-end;display:flex}.notepad-notes-header{border-top:1px solid color-mix(in srgb, currentColor 12%, transparent);opacity:.8;justify-content:space-between;align-items:center;margin-top:4px;padding-top:8px;font-size:12px;font-weight:600;display:flex}.notepad-empty{opacity:.5;margin:0;font-size:13px}.notepad-body{min-height:2em}.notepad-editor{resize:vertical;border:1px solid color-mix(in srgb, currentColor 18%, transparent);min-height:240px;color:inherit;tab-size:2;background:0 0;border-radius:6px;flex:1;padding:8px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12.5px;line-height:1.5}.notepad-editor:focus{border-color:color-mix(in srgb, currentColor 35%, transparent);outline:none}.notepad-selection{z-index:2147483000;color:#f4f4f4;background:#1f1f1f;border-radius:8px;align-items:center;padding:3px;font-size:12px;animation:.12s ease-out notepad-pop;display:flex;position:fixed;transform:translate(-50%);box-shadow:0 6px 20px #00000047,0 0 0 1px #ffffff14}.notepad-selection button{all:unset;cursor:pointer;white-space:nowrap;border-radius:5px;padding:4px 9px}.notepad-selection button:hover,.notepad-selection button:focus-visible{background:#ffffff24}.notepad-selection-divider{background:#ffffff2e;width:1px;height:14px}@keyframes notepad-pop{0%{opacity:0;transform:translate(-50%)translateY(4px)}}@media (prefers-reduced-motion:reduce){.notepad-selection{animation:none}}`,S=(0,e.template)(`<div class=notepad-clear>`),C=(0,e.template)(`<textarea class=notepad-editor autofocus>`),w=(0,e.template)(`<div class=notepad><div class=notepad-toolbar><div class=notepad-add></div></div><ul class=notepad-tasks></ul><div class=notepad-notes-header><span>`),T=(0,e.template)(`<li class=notepad-task><label><input type=checkbox><span>`),E=(0,e.template)(`<div class=notepad-body>`),D=(0,e.template)(`<p class=notepad-empty>`),O=r.Plugin.define({i18n:{en:{title:`Notepad`,"command.toggle":`Toggle Notepad`,"add.label":`Add a checklist item`,"add.placeholder":`Add an item and press Enter`,remove:`Remove item`,clearDone:`Clear done`,notes:`Notes`,"notes.empty":`No notes yet. Select Edit to write Markdown.`,edit:`Edit`,done:`Done`,openPreview:`Open in Markdown Preview`,openFile:`Open file in default app`,copyPath:`Copy file path`,pill:`{{count}} open`,"pill.title":`Notepad checklist`,"pill.open":`Open Notepad`,error:`Notepad: {{message}}`,"selection.quote":`Quote`,"selection.list":`Add to list`,"selection.notes":`Add to notes`,"command.selectionList":`Add selection to Notepad list`,"command.selectionNotes":`Add selection to Notepad notes`,"added.list":`Added {{count}} item(s) to the Notepad list`,"added.notes":`Added the selection to Notepad notes`,"quote.source":`Conversation`,"quote.unavailable":`This chat can't take a quote right now`,noChat:`Open a chat first`}},setup(i){let a=i.ui,u=`${i.id}:main`,[d,f]=(0,n.createStore)({}),p=new Set,h=e=>o(e&&d[e]?.doc||``);function g(e,t){f(e,t)}function _(e){d[e]||p.has(e)||(p.add(e),i.client.rpc(m).read({sessionID:e}).then(t=>g(e,t),v).finally(()=>p.delete(e)))}(0,r.createKeyed)(()=>i.servers.get(`sidecar`)?.client,e=>{let n=e.rpc(m);for(let e of Object.keys(d))n.read({sessionID:e}).then(t=>g(e,t),v);(0,t.onCleanup)(n.events.on(`updated`,e=>{let t=e.data;for(let[e,n]of Object.entries(d))n.sessionID===t.sessionID&&g(e,t);d[t.sessionID]||g(t.sessionID,t)}))});function v(e){i.toast.show({title:i.t(`error`,{message:e instanceof Error?e.message:String(e)}),variant:`error`})}function O(e,t,n){t(i.client.rpc(m)).then(t=>{g(e,t),n?.()},v)}function k(){let e=i.sessions.current();return e||i.toast.show({title:i.t(`noChat`)}),e}function A(e){let t=k(),n=c(e);t&&n.length!==0&&O(t.id,e=>e.add({sessionID:t.id,items:n}),()=>i.toast.show({title:i.t(`added.list`,{count:n.length}),variant:`success`,duration:2e3}))}function j(e){let t=k();t&&O(t.id,n=>n.appendNote({sessionID:t.id,markdown:l(e)}),()=>i.toast.show({title:i.t(`added.notes`),variant:`success`,duration:2e3}))}function M(e){let t=i.screen.current()?.composer;if(!t?.quotable()){i.toast.show({title:i.t(`quote.unavailable`)});return}let n=crypto.randomUUID();t.attach({type:`quote`,commentID:n,text:e.trim(),source:{label:i.t(`quote.source`),icon:`speech-bubble`,subject:`this conversation`}}),t.focus(n)}function N(){let e=i.sessions.current();e&&i.layout.toggle(u,e)}let P={id:`main`,get title(){return i.t(`title`)},closable:`compact`,fallback:!0};i.add(r.Style,x),i.add(r.Panel,{id:`main`,region:`side`,mobile:{get title(){return i.t(`title`)},order:60,kind:`menu`,icon:`checklist`},list:e=>e.open.includes(`main`)?[P]:[],enter:()=>document.querySelector(`.notepad-add input`)?.focus(),render:t=>(0,e.createComponent)(I,{get session(){return t.session}})}),i.add(r.Command,()=>({id:`toggle`,title:i.t(`command.toggle`),keywords:`notepad todo checklist notes markdown`,bind:`mod+alt+n`,enabled:!!i.sessions.current(),run:N}));let F=[{id:`quote`,get label(){return i.t(`selection.quote`)},run:M},{id:`list`,get label(){return i.t(`selection.list`)},run:A},{id:`notes`,get label(){return i.t(`selection.notes`)},run:j}];i.add(r.Slot,{at:`window.bottom`,render:()=>(0,e.createComponent)(b,{actions:F})});for(let[e,t,n]of[[`selection-list`,`mod+alt+l`,A],[`selection-notes`,`mod+alt+k`,j]])i.add(r.Command,()=>({id:e,title:i.t(e===`selection-list`?`command.selectionList`:`command.selectionNotes`),keywords:`notepad selection highlight quote`,bind:t,run:()=>{let e=y();if(!e)return!1;window.getSelection()?.removeAllRanges(),n(e.text)}}));i.add(r.MenuItem,()=>{let e=i.sessions.current();return{menu:`session.panel`,id:`open`,title:i.t(`title`),icon:`checklist`,keybind:`${i.id}.toggle`,opened:!!e&&i.layout.state(u,e)!==`closed`,run:()=>{e&&i.layout.open(u,e)}}}),(0,r.createKeyed)(()=>i.sessions.current()?.id,e=>_(e)),i.add(r.ComposerPill,{id:`open`,show:e=>{let t=h(e.session.id).tasks.filter(e=>!e.checked);if(t.length!==0)return{icon:`checklist`,label:i.t(`pill`,{count:t.length}),title:i.t(`pill.title`),rows:[...t.slice(0,8).map((e,t)=>({id:`task-${t}`,label:e.text})),{id:`open`,label:i.t(`pill.open`),icon:`checklist`,run:()=>i.layout.open(u,e.session)}]}}});function I(n){let[o,c]=(0,t.createSignal)(``),[l,u]=(0,t.createSignal)(!1),[f,p]=(0,t.createSignal)(``),m,g=()=>n.session.id,v=()=>h(g()),y=()=>d[g()]?.path??``;(0,t.createEffect)(()=>_(g())),(0,r.createKeyed)(g,e=>{(0,t.onCleanup)(()=>{l()&&k(e),u(!1)})});function b(e){if(!s(e))return;c(``);let t=g();O(t,n=>n.add({sessionID:t,items:[e]}))}function x(){p(v().body),u(!0)}function k(e=g()){clearTimeout(m),f()!==h(e).body&&O(e,t=>t.setBody({sessionID:e,body:f()}))}function A(){k(),u(!1)}return(()=>{var n=w(),r=n.firstChild,s=r.firstChild,u=r.nextSibling,d=u.nextSibling,h=d.firstChild;return(0,e.insert)(s,(0,e.createComponent)(a.TextInput,{get label(){return i.t(`add.label`)},get placeholder(){return i.t(`add.placeholder`)},get value(){return o()},onInput:c,onSubmit:b})),(0,e.insert)(r,(0,e.createComponent)(a.Tooltip,{get value(){return i.t(`openPreview`)},get children(){return(0,e.createComponent)(a.IconButton,{icon:`square-arrow-top-right`,get label(){return i.t(`openPreview`)},variant:`ghost`,size:`small`,get disabled(){return!y()},onClick:()=>i.system.openExternal(`md-preview://file${encodeURI(y())}`)})}}),null),(0,e.insert)(r,(0,e.createComponent)(a.Menu,{get label(){return i.t(`title`)},icon:`outline-dots`,placement:`bottom-end`,get items(){return[{label:i.t(`openFile`),icon:`open-file`,onSelect:()=>i.system.openExternal(`file://${encodeURI(y())}`)},{label:i.t(`copyPath`),icon:`copy`,onSelect:()=>void i.system.copy(y())}]}}),null),(0,e.insert)(u,(0,e.createComponent)(t.For,{get each(){return v().tasks},children:(t,n)=>(()=>{var r=T(),o=r.firstChild.firstChild,s=o.nextSibling;return o.addEventListener(`change`,e=>{let t=e.currentTarget.checked,r=g();O(r,e=>e.check({sessionID:r,index:n(),checked:t}))}),(0,e.insert)(s,()=>t.text),(0,e.insert)(r,(0,e.createComponent)(a.IconButton,{icon:`xmark-small`,get label(){return i.t(`remove`)},variant:`ghost-muted`,size:`small`,class:`notepad-remove`,onClick:()=>{let e=g();O(e,t=>t.remove({sessionID:e,index:n()}))}}),null),(0,e.effect)(()=>r.classList.toggle(`is-done`,!!t.checked)),(0,e.effect)(()=>o.checked=t.checked),r})()})),(0,e.insert)(n,(0,e.createComponent)(t.Show,{get when(){return v().tasks.some(e=>e.checked)},get children(){var t=S();return(0,e.insert)(t,(0,e.createComponent)(a.Button,{variant:`ghost-muted`,size:`small`,onClick:()=>{let e=g();O(e,t=>t.clearDone({sessionID:e}))},get children(){return i.t(`clearDone`)}})),t}}),d),(0,e.insert)(h,()=>i.t(`notes`)),(0,e.insert)(d,(0,e.createComponent)(a.Button,{variant:`ghost`,size:`small`,get icon(){return l()?`check`:`pencil-line`},onClick:()=>l()?A():x(),get children(){return(0,e.memo)(()=>!!l())()?i.t(`done`):i.t(`edit`)}}),null),(0,e.insert)(n,(0,e.createComponent)(t.Show,{get when(){return l()},get fallback(){return(0,e.createComponent)(t.Show,{get when(){return v().body},get fallback(){return(()=>{var t=D();return(0,e.insert)(t,()=>i.t(`notes.empty`)),t})()},get children(){var t=E();return t.$$dblclick=x,(0,e.insert)(t,(0,e.createComponent)(a.Markdown,{get text(){return v().body}})),t}})},get children(){var t=C();return t.$$keydown=e=>{(e.key===`Escape`||e.key===`Enter`&&(e.metaKey||e.ctrlKey))&&(e.preventDefault(),A())},t.addEventListener(`blur`,()=>k()),t.$$input=e=>{p(e.currentTarget.value),clearTimeout(m),m=setTimeout(k,600)},(0,e.setAttribute)(t,`spellcheck`,!0),(0,e.effect)(()=>t.value=f()),t}}),null),n})()}}});(0,e.delegateEvents)([`input`,`keydown`,`dblclick`]),exports.default=O;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-notepad",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "OpenCode Desktop side-panel notepad, one per chat: Markdown notes with a checklist the coding agent can add to.",
5
5
  "keywords": [
6
6
  "opencode",
@@ -30,15 +30,17 @@
30
30
  "scripts": {
31
31
  "build": "vite build",
32
32
  "dev": "vite build --watch",
33
- "test": "bun test",
33
+ "test": "bun test ./test",
34
34
  "typecheck": "tsc --noEmit",
35
- "prepublishOnly": "bun run typecheck && bun test && bun run build"
35
+ "prepublishOnly": "bun run typecheck && bun run test && bun run test:e2e && bun run build",
36
+ "test:e2e": "vite build -c e2e/vite.config.ts && bun test ./e2e"
36
37
  },
37
38
  "dependencies": {
38
39
  "@opencode/plugin": "2.2.0"
39
40
  },
40
41
  "devDependencies": {
41
42
  "@types/bun": "^1.4.4",
43
+ "puppeteer-core": "^25.16.0",
42
44
  "solid-js": "^1.9.17",
43
45
  "typescript": "^7.0.2",
44
46
  "vite": "^8.3.5",
package/src/selection.tsx CHANGED
@@ -33,23 +33,34 @@ export function readSelection(): Picked | undefined {
33
33
  export function SelectionBar(props: { readonly actions: readonly SelectionAction[] }) {
34
34
  const [picked, setPicked] = createSignal<Picked>()
35
35
  let timer: ReturnType<typeof setTimeout> | undefined
36
+ let frame: number | undefined
36
37
 
37
38
  const refresh = () => {
38
39
  clearTimeout(timer)
39
40
  timer = setTimeout(() => setPicked(readSelection()), 120)
40
41
  }
42
+ // A streaming chat scrolls all the time. Follow the selection instead of hiding, so the bar never
43
+ // vanishes under the pointer.
44
+ const follow = () => {
45
+ if (!picked() || frame !== undefined) return
46
+ frame = requestAnimationFrame(() => {
47
+ frame = undefined
48
+ setPicked(readSelection())
49
+ })
50
+ }
41
51
  const hide = () => setPicked(undefined)
42
52
 
43
53
  onMount(() => {
44
54
  document.addEventListener("selectionchange", refresh)
45
- window.addEventListener("scroll", hide, true)
46
- window.addEventListener("resize", hide)
55
+ window.addEventListener("scroll", follow, true)
56
+ window.addEventListener("resize", follow)
47
57
  })
48
58
  onCleanup(() => {
49
59
  clearTimeout(timer)
60
+ if (frame !== undefined) cancelAnimationFrame(frame)
50
61
  document.removeEventListener("selectionchange", refresh)
51
- window.removeEventListener("scroll", hide, true)
52
- window.removeEventListener("resize", hide)
62
+ window.removeEventListener("scroll", follow, true)
63
+ window.removeEventListener("resize", follow)
53
64
  })
54
65
 
55
66
  function send(action: SelectionAction) {
@@ -79,7 +90,17 @@ export function SelectionBar(props: { readonly actions: readonly SelectionAction
79
90
  <Show when={i() > 0}>
80
91
  <span class="notepad-selection-divider" />
81
92
  </Show>
82
- <button type="button" onClick={() => send(action)}>
93
+ {/* Act on press, not release: a redraw between the two would drop the click. */}
94
+ <button
95
+ type="button"
96
+ onMouseDown={(event) => {
97
+ event.preventDefault()
98
+ if (event.button === 0) send(action)
99
+ }}
100
+ onClick={(event) => {
101
+ if (event.detail === 0) send(action)
102
+ }}
103
+ >
83
104
  {action.label}
84
105
  </button>
85
106
  </>