fewrd 0.2.0 → 0.3.0
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 +29 -5
- package/dist/playground.js +11 -10
- package/dist/types/playground.d.ts +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ few words.**
|
|
|
12
12
|

|
|
13
13
|

|
|
14
14
|

|
|
15
|
-

|
|
16
16
|
[](LICENSE)
|
|
17
17
|
|
|
18
18
|
</div>
|
|
@@ -205,8 +205,31 @@ nest. Toggle between the full and condensed view with one class:
|
|
|
205
205
|
|
|
206
206
|
## Playground
|
|
207
207
|
|
|
208
|
-
|
|
209
|
-
|
|
208
|
+
Keep a book in a folder next to your code, and open it in the playground with
|
|
209
|
+
one command. `fewrd-play` is a separate dev-only package, so none of it ends up
|
|
210
|
+
in the library your code ships:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
npm install -D fewrd-play
|
|
214
|
+
```
|
|
215
|
+
```bash
|
|
216
|
+
npx fewrd-play path/to/book --open
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
path/to/book/
|
|
221
|
+
book.json the recipes; the playground's save button writes it back
|
|
222
|
+
cases.json sample texts: [{ "name": "refund", "text": "Refund approved - SKU: abc-1234" }]
|
|
223
|
+
cases.local.json more texts, e.g. real ones you keep out of git (optional)
|
|
224
|
+
resolvers.ts export const resolvers = { upper: (p) => p.value.toUpperCase() } (optional)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
`resolvers.ts` is served with its types stripped (Node 22.13+), so it may
|
|
228
|
+
import types, `fewrd` itself and other files in the folder, but no other
|
|
229
|
+
packages. A `resolvers.js` is served as is. The server listens on localhost
|
|
230
|
+
only; `--port` picks the port (default 4747).
|
|
231
|
+
|
|
232
|
+
Or mount the playground in a page of your own:
|
|
210
233
|
|
|
211
234
|
```ts
|
|
212
235
|
import { mount } from 'fewrd/playground';
|
|
@@ -217,14 +240,15 @@ mount(document.getElementById('app')!, {
|
|
|
217
240
|
resolvers, // your named resolvers
|
|
218
241
|
cases: [{ name: 'refund', text: 'Refund approved - SKU: abc-1234 - customer notified' }],
|
|
219
242
|
fold: (m) => m.entity === 'sku', // optional: which entities start folded
|
|
243
|
+
save: (json) => fetch('/book', { method: 'POST', body: json }), // optional: a save button
|
|
220
244
|
});
|
|
221
245
|
```
|
|
222
246
|
|
|
223
247
|
Every edit recompiles the book and re-reads every case; invalid JSON keeps the
|
|
224
|
-
last good result on screen, and reset brings the
|
|
248
|
+
last good result on screen, and reset brings back the last saved book. Pass
|
|
225
249
|
`book` instead of `data`/`resolvers` for a read-only view of a compiled
|
|
226
250
|
`Book`. The playground injects its own scoped styles, so it needs no
|
|
227
|
-
stylesheet
|
|
251
|
+
stylesheet.
|
|
228
252
|
|
|
229
253
|
## Demo books
|
|
230
254
|
|
package/dist/playground.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import{read as
|
|
2
|
-
<p class="fewrd-tagged">${H(
|
|
3
|
-
<p class="fewrd-gist"><span class="fewrd-k">gist</span> ${l(
|
|
4
|
-
<details><summary>${
|
|
5
|
-
<pre>${l(JSON.stringify(
|
|
6
|
-
${
|
|
7
|
-
</details>`}catch(
|
|
1
|
+
import{read as A,compile as C,html as H,gist as F}from"./index.js";function h(e){const t=new Set;for(const r of e.recipes)t.add(r.entity);return[...t]}function J(e,t,r){if(!r)return[];const i=new Set;for(const c of t)A(c.text,e).mentions.forEach((o,u)=>{r(o,u)&&i.add(o.entity)});return h(e).filter(c=>i.has(c))}const l=e=>e.replace(/[&<>"]/g,t=>({"&":"&","<":"<",">":">",'"':"""})[t]),b=e=>e instanceof Error?e.message:String(e);function B(e){let t=0;for(let r=0;r<e.length;r++)t=t*31+e.charCodeAt(r)|0;return Math.abs(t)}function D(e){return`<table><thead><tr><th>span</th><th>leaf</th><th>mention</th><th>text</th></tr></thead><tbody>${e.leaves.map(r=>{const i=r.mention===void 0?"":`${r.mention} ${e.mentions[r.mention].entity}`;return`<tr><td>${r.start}\u2013${r.end}</td><td>${r.kind}${r.part?`:${r.part}`:""}</td><td>${i}</td><td><code>${l(JSON.stringify(e.text.slice(r.start,r.end)))}</code></td></tr>`}).join("")}</tbody></table>`}function x(e,t,r,i){try{const c=performance.now(),d=A(t,r),o=(performance.now()-c).toFixed(2);e.innerHTML=`
|
|
2
|
+
<p class="fewrd-tagged">${H(d,i)}</p>
|
|
3
|
+
<p class="fewrd-gist"><span class="fewrd-k">gist</span> ${l(F(d,i))}</p>
|
|
4
|
+
<details><summary>${d.mentions.length} mentions \xB7 ${d.leaves.length} leaves \xB7 ${o} ms</summary>
|
|
5
|
+
<pre>${l(JSON.stringify(d.mentions,null,1))}</pre>
|
|
6
|
+
${D(d)}
|
|
7
|
+
</details>`}catch(c){e.innerHTML=`<p class="fewrd-error">${l(b(c))}</p>`}}function I(e,t){const{cases:r}=t,i="data"in t?t.resolvers:void 0,c="data"in t?t.save:void 0;let d="data"in t?JSON.stringify(t.data,null,2):"",o,u=[];"book"in t?o=t.book:{book:o,errors:u}=C(t.data,{resolvers:i});const p=new Set(J(o,r,t.fold)),w=n=>p.has(n.entity),a=`fewrd-pg-${Math.random().toString(36).slice(2)}`;e.classList.add(a,"condensed");const S=document.createElement("style");document.head.appendChild(S);function k(){S.textContent=`
|
|
8
8
|
.${a}.condensed [data-fold] { display: none; }
|
|
9
9
|
.${a}:not(.condensed) [data-fold] { text-decoration: line-through; text-decoration-color: rgba(0,0,0,.35); }
|
|
10
10
|
.${a} [data-entity] { background: var(--h); border-radius: 3px; padding: 0 1px; }
|
|
@@ -20,9 +20,10 @@ import{read as E,compile as O,html as H,gist as J}from"./index.js";function m(e)
|
|
|
20
20
|
.${a} .fewrd-errors { margin: 6px 0; padding-left: 18px; font-size: 12px; }
|
|
21
21
|
.${a} .fewrd-errors:empty { display: none; }
|
|
22
22
|
.${a} .fewrd-errors, .${a} .fewrd-error { color: #b3261e; }
|
|
23
|
-
|
|
23
|
+
.${a} .fewrd-status { font-size: 12px; opacity: .7; }
|
|
24
|
+
${h(o).map(n=>`.${a} [data-entity=${JSON.stringify(n)}] { --h: hsl(${B(n)%360}, 70%, 88%); }`).join(`
|
|
24
25
|
`)}
|
|
25
|
-
`}const T="data"in
|
|
26
|
+
`}const T="data"in t?`<details class="fewrd-editor" open><summary>recipes <button type="button" class="fewrd-reset">reset</button>${c?' <button type="button" class="fewrd-save">save</button> <span class="fewrd-status"></span>':""}</summary>
|
|
26
27
|
<textarea class="fewrd-book" spellcheck="false"></textarea>
|
|
27
28
|
<ul class="fewrd-errors"></ul>
|
|
28
29
|
</details>`:"";e.innerHTML=`
|
|
@@ -35,4 +36,4 @@ import{read as E,compile as O,html as H,gist as J}from"./index.js";function m(e)
|
|
|
35
36
|
<textarea class="fewrd-custom" rows="3" spellcheck="false"></textarea>
|
|
36
37
|
<div class="fewrd-trial-out"></div>
|
|
37
38
|
</section>
|
|
38
|
-
<div class="fewrd-cases"></div>`;const
|
|
39
|
+
<div class="fewrd-cases"></div>`;const L=e.querySelector(".fewrd-condensed"),m=e.querySelector(".fewrd-folds"),$=e.querySelector(".fewrd-custom"),q=e.querySelector(".fewrd-trial-out"),E=e.querySelector(".fewrd-cases");E.innerHTML=r.map((n,s)=>`<section class="fewrd-case"><h2>${l(r[s].name)}</h2><div class="fewrd-out" data-i="${s}"></div></section>`).join("");const j=Array.from(E.querySelectorAll(".fewrd-out"));function M(){const n=h(o);for(const s of Array.from(p))n.includes(s)||p.delete(s);m.size=Math.max(n.length,1),m.innerHTML=n.map(s=>`<option value="${l(s)}"${p.has(s)?" selected":""}>${l(s)}</option>`).join("")}function y(){j.forEach(n=>x(n,r[Number(n.getAttribute("data-i"))].text,o,w)),x(q,$.value,o,w)}m.addEventListener("change",()=>{p.clear();for(const n of Array.from(m.selectedOptions))p.add(n.value);y()}),L.addEventListener("change",()=>e.classList.toggle("condensed",L.checked)),$.addEventListener("input",()=>x(q,$.value,o,w));const f=e.querySelector(".fewrd-book"),O=e.querySelector(".fewrd-errors");function g(n){O&&(O.innerHTML=n.map(s=>`<li>${l([s.path,s.entity&&`(${s.entity})`].filter(Boolean).join(" "))}${s.path?": ":""}${l(s.message)}</li>`).join(""))}function N(){if(!f)return;let n;try{n=JSON.parse(f.value)}catch(s){return g([{path:"",message:`invalid JSON, still showing the last good book: ${b(s)}`}])}({book:o,errors:u}=C(n,{resolvers:i})),g(u),k(),M(),y()}if(f){f.value=d,f.addEventListener("input",N),e.querySelector(".fewrd-reset").addEventListener("click",s=>{s.preventDefault(),f.value=d,N()});const n=e.querySelector(".fewrd-status");e.querySelector(".fewrd-save")?.addEventListener("click",async s=>{s.preventDefault();const v=f.value;try{JSON.parse(v),await c(v),d=v,n.textContent="saved"}catch(z){n.textContent=`not saved: ${b(z)}`}}),f.addEventListener("input",()=>n&&(n.textContent=""))}g(u),k(),M(),y()}export{h as entities,J as initialSelection,I as mount};
|
|
@@ -16,6 +16,8 @@ export type MountOptions = (CommonOptions & {
|
|
|
16
16
|
| (CommonOptions & {
|
|
17
17
|
data: unknown;
|
|
18
18
|
resolvers?: Readonly<Record<string, Resolver>>;
|
|
19
|
+
/** Shows a save button: called with the editor's text once it parses as JSON. Reset then returns to what was saved. */
|
|
20
|
+
save?: (json: string) => unknown;
|
|
19
21
|
});
|
|
20
22
|
/** Distinct entities across a book's recipes, in first-appearance order. */
|
|
21
23
|
export declare function entities(book: Book): string[];
|