prosebook 0.2.0 → 0.4.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.
Files changed (115) hide show
  1. package/README.md +345 -29
  2. package/dist/bun.d.ts +34 -0
  3. package/dist/bun.d.ts.map +1 -0
  4. package/dist/bun.js +98 -0
  5. package/dist/bun.js.map +1 -0
  6. package/dist/bundle-CpUIWb9j.d.ts +6 -0
  7. package/dist/bundle-CpUIWb9j.d.ts.map +1 -0
  8. package/dist/bundle-DboItAre.js +111 -0
  9. package/dist/bundle-DboItAre.js.map +1 -0
  10. package/dist/cli.js +51 -56
  11. package/dist/cli.js.map +1 -1
  12. package/dist/compile-ClK9htKr.d.ts +16 -0
  13. package/dist/compile-ClK9htKr.d.ts.map +1 -0
  14. package/dist/{config-_4HfnJQu.d.ts → config-CzaJJQ60.d.ts} +75 -2
  15. package/dist/config-CzaJJQ60.d.ts.map +1 -0
  16. package/dist/{config-D--RITk2.js → config-nWZf9QmF.js} +42 -212
  17. package/dist/config-nWZf9QmF.js.map +1 -0
  18. package/dist/config.d.ts +2 -2
  19. package/dist/config.js +1 -1
  20. package/dist/{copy-DXnzXsJa.d.ts → copy-B3V_QRkT.d.ts} +20 -7
  21. package/dist/copy-B3V_QRkT.d.ts.map +1 -0
  22. package/dist/copy-Bo_FIoLy.js +154 -0
  23. package/dist/copy-Bo_FIoLy.js.map +1 -0
  24. package/dist/endpoints-BgG_5Vpm.js +96 -0
  25. package/dist/endpoints-BgG_5Vpm.js.map +1 -0
  26. package/dist/generate-BW3N8EGb.js +5488 -0
  27. package/dist/generate-BW3N8EGb.js.map +1 -0
  28. package/dist/helpers-BWGcD0kS.js +96 -0
  29. package/dist/helpers-BWGcD0kS.js.map +1 -0
  30. package/dist/icu-Dej0mNsF.js +218 -0
  31. package/dist/icu-Dej0mNsF.js.map +1 -0
  32. package/dist/index.d.ts +12 -2
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +4 -188
  35. package/dist/inspector.d.ts +4 -1
  36. package/dist/inspector.d.ts.map +1 -1
  37. package/dist/inspector.js +7 -2
  38. package/dist/inspector.js.map +1 -1
  39. package/dist/locale-DpQA1pqH.js +70 -0
  40. package/dist/locale-DpQA1pqH.js.map +1 -0
  41. package/dist/next-loader.d.ts +11 -0
  42. package/dist/next-loader.d.ts.map +1 -0
  43. package/dist/next-loader.js +17 -0
  44. package/dist/next-loader.js.map +1 -0
  45. package/dist/next.d.ts +30 -0
  46. package/dist/next.d.ts.map +1 -0
  47. package/dist/next.js +160 -0
  48. package/dist/next.js.map +1 -0
  49. package/dist/react/index.d.ts +3 -3
  50. package/dist/react/index.d.ts.map +1 -1
  51. package/dist/react/index.js.map +1 -1
  52. package/dist/react/server.d.ts +20 -0
  53. package/dist/react/server.d.ts.map +1 -0
  54. package/dist/react/server.js +34 -0
  55. package/dist/react/server.js.map +1 -0
  56. package/dist/{sarif-CI8HOokm.js → sarif-B5nrMqrT.js} +103 -87
  57. package/dist/sarif-B5nrMqrT.js.map +1 -0
  58. package/dist/scan-Bx0_yDSi.js +387 -0
  59. package/dist/scan-Bx0_yDSi.js.map +1 -0
  60. package/dist/tools.d.ts +83 -90
  61. package/dist/tools.d.ts.map +1 -1
  62. package/dist/tools.js +5 -4
  63. package/dist/vite.d.ts +3 -2
  64. package/dist/vite.d.ts.map +1 -1
  65. package/dist/vite.js +29 -109
  66. package/dist/vite.js.map +1 -1
  67. package/package.json +37 -4
  68. package/src/bun.ts +132 -0
  69. package/src/cli.ts +60 -58
  70. package/src/index.ts +1 -1
  71. package/src/inspector.ts +8 -3
  72. package/src/next-loader.ts +16 -0
  73. package/src/next.ts +188 -0
  74. package/src/react/index.tsx +6 -6
  75. package/src/react/server.ts +37 -0
  76. package/src/runtime/copy.ts +22 -3
  77. package/src/runtime/helpers.ts +54 -18
  78. package/src/runtime/locale.ts +19 -0
  79. package/src/runtime/types.ts +21 -6
  80. package/src/tools/bundle.ts +131 -0
  81. package/src/tools/catalog.ts +2 -2
  82. package/src/tools/compile.ts +60 -23
  83. package/src/tools/config.ts +57 -0
  84. package/src/tools/endpoints.ts +121 -0
  85. package/src/tools/entry.ts +77 -0
  86. package/src/tools/generate.ts +2 -0
  87. package/src/tools/icu.ts +63 -7
  88. package/src/tools/index.ts +6 -3
  89. package/src/tools/lock.ts +7 -0
  90. package/src/tools/mf1.ts +21 -7
  91. package/src/tools/mf2.ts +249 -57
  92. package/src/tools/migrate.ts +3 -2
  93. package/src/tools/native/android.ts +201 -0
  94. package/src/tools/native/apple.ts +139 -0
  95. package/src/tools/native/emit.ts +87 -0
  96. package/src/tools/native/index.ts +48 -0
  97. package/src/tools/native/kotlin.ts +269 -0
  98. package/src/tools/native/model.ts +292 -0
  99. package/src/tools/native/number.ts +210 -0
  100. package/src/tools/native/plurals.ts +103 -0
  101. package/src/tools/native/printf.ts +95 -0
  102. package/src/tools/native/rust.ts +367 -0
  103. package/src/tools/native/swift.ts +326 -0
  104. package/src/tools/syntax.ts +3 -3
  105. package/src/tools/todo.ts +118 -0
  106. package/src/vite.ts +28 -114
  107. package/dist/config-D--RITk2.js.map +0 -1
  108. package/dist/config-_4HfnJQu.d.ts.map +0 -1
  109. package/dist/copy-DXnzXsJa.d.ts.map +0 -1
  110. package/dist/helpers-BPuPGjQP.js +0 -61
  111. package/dist/helpers-BPuPGjQP.js.map +0 -1
  112. package/dist/index.js.map +0 -1
  113. package/dist/sarif-CI8HOokm.js.map +0 -1
  114. package/dist/scan-Ch2BfIUD.js +0 -2561
  115. package/dist/scan-Ch2BfIUD.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"inspector.js","names":[],"sources":["../src/inspector-dialog.ts","../src/inspector.ts"],"sourcesContent":["/**\n * The inspector's dialog: Alt-right-click a string to edit its entry on the page, in every locale, as\n * fields or as the YAML written in its files. The server checks each edit as the whole catalog before\n * writing it (`tools/entry.ts`); once written, the page updates in place like after any catalog edit.\n * While typing, it asks the server to check the edit unsaved, and shows what lint says of each locale\n * under its text: a finding selects its words, and one that has a fix makes it.\n *\n * A reviewer who would rather ask than edit leaves a comment, or marks the entry for removal: both are\n * written in the catalog, where whoever edits next, often an agent, finds them among the warnings.\n *\n * It renders in a shadow root, so the app's styles don't reach it and its own don't leak out.\n */\nimport type { Problem } from '#src/tools/catalog.ts';\nimport type { EntryEdit, EntryFields, EntryFinding, EntryView } from '#src/tools/entry.ts';\n\nexport interface DialogOptions {\n base: string;\n key: string;\n /** The locale the string was found in, whose translation comes first, or nothing to start at the source. */\n locale?: string;\n /** Where the pointer was, so the dialog opens beside it. */\n x: number;\n y: number;\n /** Opens the entry in the editor instead. */\n openFile: () => void;\n}\n\ntype Reply = { view?: EntryView; problems?: Problem[] };\n\nconst STYLE = `\n:host { all: initial; }\n.panel {\n --bg: #fff; --fg: #1d1b17; --muted: #6b665c; --line: #e2ded4; --field: #faf9f6; --err: #b91c1c; --warn: #b45309; --ok: #15803d;\n position: fixed; z-index: 2147483647; display: flex; flex-direction: column;\n width: min(460px, calc(100vw - 24px)); max-height: min(80vh, 680px);\n background: var(--bg); color: var(--fg); border: 1px solid var(--line); border-radius: 10px;\n box-shadow: 0 16px 48px rgb(0 0 0 / 0.22); font: 13px/1.45 system-ui, -apple-system, 'Segoe UI', sans-serif;\n color-scheme: light;\n}\n@media (prefers-color-scheme: dark) {\n .panel { --bg: #1d1c17; --fg: #ece8dd; --muted: #a29c8d; --line: #34322a; --field: #15140f; --err: #f87171; --warn: #fbbf24; --ok: #4ade80; color-scheme: dark; }\n}\nheader, footer { display: flex; align-items: center; gap: 8px; padding: 10px 12px; }\nheader { border-bottom: 1px solid var(--line); }\nfooter { border-top: 1px solid var(--line); justify-content: space-between; flex-wrap: wrap; }\n.key { font: 600 12px/1.5 ui-monospace, monospace; color: #fff; background: #7c3aed; padding: 1px 6px; border-radius: 4px; }\n.locale { font: 12px/1 ui-monospace, monospace; color: var(--muted); }\n.grow { flex: 1; }\n.tabs { display: inline-flex; border: 1px solid var(--line); border-radius: 6px; overflow: hidden; }\n.tabs button { border: 0; border-radius: 0; padding: 4px 10px; background: transparent; }\n.tabs button[aria-selected='true'] { background: #7c3aed; color: #fff; }\n.body { padding: 12px; overflow: auto; display: grid; gap: 12px; }\nlabel { display: grid; gap: 4px; font-size: 12px; font-weight: 500; color: var(--muted); }\nlabel small { font-weight: 400; font-family: ui-monospace, monospace; }\ntextarea, input, select {\n font: 13px/1.45 ui-monospace, 'SF Mono', Consolas, monospace; color: var(--fg); background: var(--field);\n border: 1px solid var(--line); border-radius: 6px; padding: 6px 8px; width: 100%; box-sizing: border-box;\n}\ntextarea { resize: vertical; }\n.prose { font-family: inherit; }\ntextarea:focus, input:focus, select:focus { outline: 2px solid #7c3aed; outline-offset: -1px; }\n.row { display: grid; grid-template-columns: 1fr 1fr auto; gap: 8px; align-items: end; }\n.check { display: flex; align-items: center; gap: 6px; padding-bottom: 7px; color: var(--fg); font-weight: 400; }\n.check input { width: auto; }\nfieldset { border: 0; padding: 0; margin: 0; display: grid; gap: 8px; }\nlegend { font-size: 12px; font-weight: 500; color: var(--muted); padding: 0 0 4px; }\n.problems { margin: 0; padding: 0 12px 0 28px; color: var(--err); }\n.problems:empty { display: none; }\n.problems li { margin: 2px 0; }\n.findings { list-style: none; margin: 0; padding: 0; display: grid; gap: 3px; font-weight: 400; color: var(--fg); }\n.findings:empty { display: none; }\n.findings.all { padding: 0 12px 8px; }\n.findings li { display: flex; align-items: baseline; gap: 6px; }\n.findings li::before { content: ''; flex: none; width: 6px; height: 6px; border-radius: 50%; background: var(--warn); }\n.findings li.error::before { background: var(--err); }\n.findings .say { flex: 1; border: 0; padding: 0; background: transparent; font: inherit; text-align: left; color: inherit; }\n.findings button.say:hover { text-decoration: underline; }\n.findings .rule { font: 11px/1.4 ui-monospace, monospace; color: var(--muted); }\n.findings button.fix { padding: 2px 8px; font-size: 12px; }\n.status { color: var(--muted); }\n.status.ok { color: var(--ok); }\nbutton {\n font: 500 13px/1 system-ui, -apple-system, 'Segoe UI', sans-serif; color: var(--fg); background: var(--bg);\n border: 1px solid var(--line); border-radius: 6px; padding: 7px 12px; cursor: pointer;\n}\nbutton.primary { background: #7c3aed; border-color: #7c3aed; color: #fff; }\nbutton.link { border: 0; padding: 7px 0; color: var(--muted); text-decoration: underline; background: transparent; }\nbutton.close { border: 0; padding: 2px 6px; font-size: 18px; color: var(--muted); background: transparent; }\nbutton.remove[aria-pressed='true'] { color: var(--err); border-color: currentColor; }\n.removing textarea[name='message'], .removing textarea[name^='translation.'] { text-decoration: line-through; color: var(--muted); }\n`;\n\nlet closeOpen: (() => void) | undefined;\n\n/** An element with properties set (`className`, `value`, `checked`, ...) and children appended. */\nfunction el<K extends keyof HTMLElementTagNameMap>(tag: K, props: object = {}, ...children: (Node | string)[]): HTMLElementTagNameMap[K] {\n const node = document.createElement(tag);\n Object.assign(node, props);\n node.append(...children);\n return node;\n}\n\nconst rowsFor = (text: string, least: number) => Math.min(12, Math.max(least, text.split('\\n').length + (text.length > 60 ? 1 : 0)));\n\n/** Opens the dialog for an entry, closing any already open. */\nexport async function openDialog(options: DialogOptions): Promise<void> {\n closeOpen?.();\n const query = new URLSearchParams({ key: options.key });\n const host = el('div');\n const root = host.attachShadow({ mode: 'open' });\n const panel = el('div', { className: 'panel' });\n // Hidden until it has its place beside the pointer, which needs its size, which needs the entry.\n panel.style.visibility = 'hidden';\n panel.setAttribute('role', 'dialog');\n panel.setAttribute('aria-label', `Edit ${options.key}`);\n root.append(el('style', { textContent: STYLE }), panel);\n\n const close = () => {\n clearTimeout(timer);\n host.remove();\n if (closeOpen === close) closeOpen = undefined;\n };\n closeOpen = close;\n\n const problems = el('ul', { className: 'problems' });\n const status = el('span', { className: 'status' });\n const body = el('div', { className: 'body' });\n const tabs = {\n fields: el('button', { type: 'button', textContent: 'Fields' }),\n yaml: el('button', { type: 'button', textContent: 'YAML' }),\n };\n let tab: 'fields' | 'yaml' = 'fields';\n let view: EntryView | undefined;\n // Marked for removal: written with the fields, so it is the YAML tab's to change while that is open.\n let remove = false;\n const removeButton = el('button', {\n type: 'button',\n className: 'remove',\n title: 'Leave the entry for whoever edits next to delete, with the code that reads it',\n });\n const showRemove = () => {\n removeButton.hidden = tab === 'yaml' || view === undefined;\n removeButton.setAttribute('aria-pressed', String(remove));\n removeButton.textContent = remove ? 'Marked for removal' : 'Mark for removal';\n panel.classList.toggle('removing', remove && tab === 'fields');\n };\n removeButton.addEventListener('click', () => {\n remove = !remove;\n showRemove();\n });\n // What lint said, and each locale's text it said it of: a finding's place and fix are in that text.\n let findings: EntryFinding[] = [];\n let checked = new Map<string, string>();\n const heard = (next: EntryView) => {\n findings = next.findings;\n checked = new Map([[next.source, next.fields.message], ...Object.entries(next.fields.translations)]);\n };\n const all = el('ul', { className: 'findings all' });\n\n const showProblems = (list: readonly Problem[]) =>\n problems.replaceChildren(\n ...list.map((p) =>\n el('li', { textContent: p.file === undefined ? p.message : `${[p.file, p.key].filter(Boolean).join(' ')}: ${p.message}` }),\n ),\n );\n\n const area = (name: string, label: string, value: string, prose = false, least = 2) =>\n el(\n 'label',\n {},\n label,\n // YAML doesn't wrap: a wrapped line would hide its indentation.\n el('textarea', {\n name,\n value,\n rows: rowsFor(value, least),\n spellcheck: prose,\n wrap: prose ? 'soft' : 'off',\n className: prose ? 'prose' : '',\n }),\n );\n\n // The locale the string was read in comes first; the others follow, in the catalog's order.\n const focus = () => (options.locale !== undefined && view?.locales.includes(options.locale) === true ? options.locale : undefined);\n const ordered = () => (view === undefined ? [] : [...new Set([focus(), ...view.locales].filter((l) => l !== undefined))]);\n const placeholder = (label: HTMLLabelElement, text: string) => {\n label.querySelector('textarea')!.placeholder = text;\n return label;\n };\n\n const fieldOf = (locale: string) =>\n root.querySelector<HTMLTextAreaElement>(`[name=\"${locale === view?.source ? 'message' : `translation.${locale}`}\"]`);\n\n const item = (f: EntryFinding, prefix: string) => {\n const field = fieldOf(f.locale);\n // Typed over since the check: the next one is on its way, so the old place and fix are left alone.\n const current = () => (tab === 'fields' && field !== null && field.value === checked.get(f.locale) ? field : undefined);\n const text = `${prefix}${f.message}`;\n const at = tab === 'fields' ? f.at : undefined;\n const say =\n at === undefined\n ? el('span', { className: 'say', textContent: text })\n : el('button', { type: 'button', className: 'say', textContent: text });\n say.addEventListener('click', () => {\n const target = current();\n if (at === undefined || target === undefined) return;\n target.focus();\n target.setSelectionRange(at.start, at.end);\n });\n const li = el('li', { className: f.severity }, say, el('span', { className: 'rule', textContent: f.rule }));\n const fixed = f.fixed;\n if (fixed !== undefined && tab === 'fields') {\n const fix = el('button', { type: 'button', className: 'fix', textContent: 'Fix' });\n fix.addEventListener('click', () => {\n const target = current();\n if (target === undefined) return;\n target.value = fixed;\n target.dispatchEvent(new Event('input', { bubbles: true }));\n });\n li.append(fix);\n }\n return li;\n };\n\n const showFindings = () => {\n if (tab === 'yaml') {\n all.replaceChildren(...findings.map((f) => item(f, `${f.locale}: `)));\n return;\n }\n all.replaceChildren();\n for (const list of root.querySelectorAll<HTMLUListElement>('.body .findings'))\n list.replaceChildren(...findings.filter((f) => f.locale === list.dataset['locale']).map((f) => item(f, '')));\n };\n\n /** A text field with what lint says of its locale under it. */\n const withFindings = (label: HTMLLabelElement, locale: string) => {\n const list = el('ul', { className: 'findings' });\n list.dataset['locale'] = locale;\n label.append(list);\n return label;\n };\n\n const render = () => {\n if (view === undefined) return;\n tabs.fields.setAttribute('aria-selected', String(tab === 'fields'));\n tabs.yaml.setAttribute('aria-selected', String(tab === 'yaml'));\n remove = view.fields.remove;\n showRemove();\n if (tab === 'yaml') {\n const v = view;\n body.replaceChildren(\n area('yaml.catalog', v.files.catalog, v.yaml.catalog, false, 3),\n ...ordered().map((l) =>\n placeholder(area(`yaml.${l}`, v.files.translations[l]!, v.yaml.translations[l] ?? '', false, 1), `${v.key}: …`),\n ),\n );\n showFindings();\n return;\n }\n const f = view.fields;\n const fallsBack = `No translation: the ${view.source} text shows`;\n const translation = (l: string) =>\n withFindings(\n placeholder(\n area(`translation.${l}`, `Translation · ${l}${f.translations[l] === '' ? ' · none yet' : ''}`, f.translations[l] ?? '', true),\n fallsBack,\n ),\n l,\n );\n const others = ordered().filter((l) => l !== focus());\n const select = (name: string, value: string, choices: string[]) =>\n el(\n 'select',\n { name },\n el('option', { value: '', textContent: '—' }),\n ...choices.map((c) => el('option', { value: c, textContent: c, selected: c === value })),\n );\n const argNames = [...new Set([...view.argNames, ...Object.keys(f.args)])];\n body.replaceChildren(\n ...(focus() === undefined ? [] : [translation(focus()!)]),\n withFindings(area('message', `${focus() === undefined ? 'Message' : 'Source'} · ${view.source}`, f.message, true), view.source),\n ...(others.length === 0\n ? []\n : [\n el(\n 'fieldset',\n {},\n el('legend', { textContent: focus() === undefined ? 'Translations' : 'Other translations' }),\n ...others.map(translation),\n ),\n ]),\n placeholder(area('comment', 'Comment · for whoever edits next', f.comment, true, 1), 'Ask for a change instead of making it'),\n area('context', 'Context', f.context, true, 1),\n el(\n 'div',\n { className: 'row' },\n el('label', {}, 'Kind', select('kind', f.kind, view.kinds)),\n el('label', {}, 'Surface', select('surface', f.surface, view.surfaces)),\n el('label', { className: 'check' }, el('input', { type: 'checkbox', name: 'fragment', checked: f.fragment }), 'Fragment'),\n ),\n ...(argNames.length === 0\n ? []\n : [\n el(\n 'fieldset',\n {},\n el('legend', { textContent: 'What each placeholder holds' }),\n ...argNames.map((n) =>\n el('label', {}, el('small', { textContent: `{${n}}` }), el('input', { name: `arg.${n}`, value: f.args[n] ?? '' })),\n ),\n ),\n ]),\n );\n showFindings();\n };\n\n const value = (name: string) => root.querySelector<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>(`[name=\"${name}\"]`)?.value;\n\n const current = (): EntryEdit => {\n const byLocale = (prefix: string) => Object.fromEntries(ordered().map((l) => [l, value(`${prefix}${l}`) ?? '']));\n if (tab === 'yaml') return { yaml: { catalog: value('yaml.catalog') ?? '', translations: byLocale('yaml.') } };\n const args: Record<string, string> = {};\n for (const input of root.querySelectorAll<HTMLInputElement>('input[name^=\"arg.\"]')) args[input.name.slice(4)] = input.value;\n const fields: EntryFields = {\n message: value('message') ?? '',\n translations: byLocale('translation.'),\n context: value('context') ?? '',\n kind: value('kind') ?? '',\n surface: value('surface') ?? '',\n fragment: root.querySelector<HTMLInputElement>('input[name=\"fragment\"]')?.checked === true,\n args,\n comment: value('comment') ?? '',\n remove,\n };\n return { fields };\n };\n\n const send = async (write: boolean): Promise<EntryView | undefined> => {\n const res = await fetch(`${options.base}__prosebook/entry`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ key: options.key, edit: current(), write }),\n });\n const reply = (await res.json()) as Reply;\n showProblems(reply.problems ?? []);\n return reply.view;\n };\n\n // Checked a moment after typing stops; a reply to an older check than the last is dropped.\n let timer: ReturnType<typeof setTimeout> | undefined;\n let asked = 0;\n const check = () => {\n clearTimeout(timer);\n timer = setTimeout(() => {\n const mine = ++asked;\n void send(false).then((checkedView) => {\n if (mine !== asked) return;\n if (checkedView === undefined) findings = [];\n else heard(checkedView);\n showFindings();\n });\n }, 350);\n };\n body.addEventListener('input', check);\n body.addEventListener('change', check);\n\n const save = async () => {\n status.className = 'status';\n status.textContent = 'Checking…';\n const saved = await send(true);\n status.textContent = '';\n if (saved === undefined) return;\n status.className = 'status ok';\n status.textContent = 'Saved';\n setTimeout(close, 600);\n };\n\n const switchTo = async (next: 'fields' | 'yaml') => {\n if (next === tab) return;\n // The other tab shows this one's edits, so they are turned into it on the server, checked.\n const converted = await send(false);\n if (converted === undefined) return;\n view = converted;\n heard(converted);\n tab = next;\n render();\n };\n\n tabs.fields.addEventListener('click', () => void switchTo('fields'));\n tabs.yaml.addEventListener('click', () => void switchTo('yaml'));\n\n const saveButton = el('button', { type: 'button', className: 'primary', textContent: 'Save' });\n saveButton.addEventListener('click', () => void save());\n const cancel = el('button', { type: 'button', textContent: 'Cancel' });\n cancel.addEventListener('click', close);\n const closeButton = el('button', { type: 'button', className: 'close', textContent: '×', title: 'Close' });\n closeButton.addEventListener('click', close);\n const openFile = el('button', { type: 'button', className: 'link', textContent: 'Open in editor' });\n openFile.addEventListener('click', () => {\n options.openFile();\n close();\n });\n\n panel.append(\n el(\n 'header',\n {},\n el('span', { className: 'key', textContent: options.key }),\n el('span', { className: 'locale', textContent: options.locale ?? '' }),\n el('span', { className: 'grow' }),\n el('span', { className: 'tabs' }, tabs.fields, tabs.yaml),\n closeButton,\n ),\n body,\n all,\n problems,\n el('footer', {}, openFile, el('span', { className: 'grow' }), status, removeButton, cancel, saveButton),\n );\n // Keys typed here are the dialog's: the app's shortcuts don't see them.\n for (const type of ['keydown', 'keyup', 'keypress'] as const) host.addEventListener(type, (e) => e.stopPropagation());\n panel.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') close();\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) void save();\n });\n\n document.body.append(host);\n const res = await fetch(`${options.base}__prosebook/entry?${query}`);\n const reply = (await res.json()) as EntryView | { problems: Problem[] };\n if ('problems' in reply) {\n showProblems(reply.problems);\n } else {\n view = reply;\n heard(reply);\n }\n render();\n\n // Beside the pointer, inside the window.\n const box = panel.getBoundingClientRect();\n const left = Math.min(Math.max(12, options.x - 24), innerWidth - box.width - 12);\n const below = options.y + 16;\n const top = below + box.height <= innerHeight - 12 ? below : Math.max(12, options.y - box.height - 16);\n Object.assign(panel.style, { left: `${left}px`, top: `${top}px`, visibility: '' });\n // Growing later, with problems or the other tab, moves it up rather than past the bottom.\n new ResizeObserver(() => {\n const now = panel.getBoundingClientRect();\n const fitted = Math.max(12, Math.min(now.top, innerHeight - now.height - 12));\n if (fitted !== now.top) panel.style.top = `${fitted}px`;\n }).observe(panel);\n (root.querySelector<HTMLElement>(`[name=\"translation.${focus()}\"]`) ?? root.querySelector<HTMLElement>('textarea'))?.focus();\n}\n","/**\n * The dev inspector the Vite plugin loads into the page: hold Alt and point at a string to see its\n * key, Alt-click to open the entry it came from, Alt-Shift-click to open the catalog entry instead of\n * the translation. Alt-right-click edits the entry on the page instead (`inspector-dialog.ts`).\n *\n * Text on screen carries no key. `<Text>` renders a fragment, so marking it would take a wrapper\n * element that exists only in dev and changes what a stylesheet sees; `t()` returns a bare string. So\n * the runtime reports every read (`ReadHook`) and the text under the pointer is matched back to them.\n */\nimport type { DevHooks } from '#src/runtime/types.ts';\n\nimport { openDialog } from './inspector-dialog.ts';\n\nexport interface Read {\n key: string;\n /** The locale the message was found in. */\n locale: string;\n}\n\n/** Recent reads by the text they came to, least recent text first; each text's keys most recent first. */\nexport type Reads = Map<string, Read[]>;\n\n/** The character under the pointer: a text node and its index there. */\nexport interface Caret {\n node: Node;\n offset: number;\n}\n\nexport interface Hit {\n text: string;\n read: Read;\n /** How many other keys came to the same text. */\n others: number;\n /** The innermost element holding all of the text. */\n element: Element;\n}\n\n// Enough for every string on a busy screen and the ones it showed a moment ago.\nconst LIMIT = 2000;\nconst ATTRIBUTES = ['placeholder', 'title', 'aria-label', 'alt', 'value'];\n\nexport function record(reads: Reads, key: string, text: string, locale: string): void {\n if (text.trim() === '') return;\n const seen = reads.get(text) ?? [];\n reads.delete(text);\n reads.set(text, seen[0]?.key === key && seen[0].locale === locale ? seen : [{ key, locale }, ...seen.filter((r) => r.key !== key)]);\n if (reads.size > LIMIT) reads.delete(reads.keys().next().value!);\n}\n\n/**\n * The read under the caret: the longest text that covers it, in the innermost element holding all of\n * it. A rich message runs across its elements, so it wins over a shorter text inside one of them.\n * Without a caret (over an input, or padding) an element's whole text, or the target's readable\n * attribute, must equal a read.\n */\nexport function keyAt(target: Element, caret: Caret | undefined, reads: Reads): Hit | undefined {\n let best: Hit | undefined;\n for (let el: Element | null = target; el !== null; el = el.parentElement) {\n const text = el.textContent ?? '';\n const at = caret === undefined ? undefined : offsetIn(el, caret);\n for (const [said, seen] of reads) {\n if (said.length <= (best?.text.length ?? 0)) continue;\n if (at === undefined ? text.trim() === said.trim() : covers(text, said, at))\n best = { text: said, read: seen[0]!, others: seen.length - 1, element: el };\n }\n if (best !== undefined || el !== target) continue;\n for (const name of ATTRIBUTES) {\n const value = el.getAttribute(name);\n const seen = value === null ? undefined : reads.get(value);\n if (value !== null && seen !== undefined) return { text: value, read: seen[0]!, others: seen.length - 1, element: el };\n }\n }\n return best;\n}\n\n/** Whether an occurrence of `said` in `text` holds the character at `at`. */\nfunction covers(text: string, said: string, at: number): boolean {\n const start = text.indexOf(said, Math.max(0, at - said.length + 1));\n return start !== -1 && start <= at;\n}\n\n/** The caret's index in the element's text, or nothing when the caret is outside it. */\nfunction offsetIn(el: Element, caret: Caret): number | undefined {\n if (caret.node.nodeType !== Node.TEXT_NODE || !el.contains(caret.node)) return undefined;\n const walker = el.ownerDocument.createTreeWalker(el, NodeFilter.SHOW_TEXT);\n let offset = 0;\n for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) {\n if (node === caret.node) return offset + caret.offset;\n offset += node.textContent?.length ?? 0;\n }\n return undefined;\n}\n\n/** The character under a point on screen, where the browser can say. */\nexport function caretAt(x: number, y: number): Caret | undefined {\n if (typeof document.caretPositionFromPoint !== 'function') return undefined;\n const position = document.caretPositionFromPoint(x, y);\n if (position === null) return undefined;\n const { offsetNode: node, offset } = position;\n // A caret position sits between two characters, and where one string ends the next may begin: take\n // the character the pointer is over, or the one before (past the end of a line, in padding).\n if (node.nodeType !== Node.TEXT_NODE || offset === 0) return { node, offset };\n const range = document.createRange();\n range.setStart(node, offset);\n range.setEnd(node, Math.min(offset + 1, node.textContent?.length ?? 0));\n const r = range.getBoundingClientRect();\n const over = !range.collapsed && x >= r.left && x <= r.right && y >= r.top && y <= r.bottom;\n return { node, offset: over ? offset : offset - 1 };\n}\n\nexport interface InspectorOptions {\n /** The dev server's base URL, ending in `/`. */\n base: string;\n}\n\n/** Starts listening for reads and for Alt on the page. Called by the module the Vite plugin serves. */\nexport function install({ base }: InspectorOptions): void {\n const reads: Reads = new Map();\n (globalThis as DevHooks).__PROSEBOOK_READ__ = (key, text, locale) => record(reads, key, text, locale);\n\n const box = document.createElement('div');\n const label = document.createElement('div');\n box.append(label);\n Object.assign(box.style, {\n position: 'fixed',\n display: 'none',\n zIndex: '2147483647',\n pointerEvents: 'none',\n boxSizing: 'border-box',\n outline: '2px solid #7c3aed',\n background: 'rgb(124 58 237 / 0.08)',\n });\n Object.assign(label.style, {\n position: 'absolute',\n left: '-2px',\n padding: '1px 6px',\n font: '12px/1.5 ui-monospace, monospace',\n whiteSpace: 'nowrap',\n color: '#fff',\n background: '#7c3aed',\n });\n\n let pointer: { x: number; y: number } | undefined;\n let frame = 0;\n\n const hitAt = (x: number, y: number) => {\n const target = document.elementFromPoint(x, y);\n return target === null ? undefined : keyAt(target, caretAt(x, y), reads);\n };\n const hide = () => {\n box.style.display = 'none';\n };\n const show = () => {\n frame = 0;\n const hit = pointer === undefined ? undefined : hitAt(pointer.x, pointer.y);\n if (hit === undefined) return hide();\n const r = hit.element.getBoundingClientRect();\n Object.assign(box.style, { display: 'block', left: `${r.left}px`, top: `${r.top}px`, width: `${r.width}px`, height: `${r.height}px` });\n // Below the box when there is no room above it.\n Object.assign(label.style, r.top < 24 ? { top: '100%', bottom: '' } : { top: '', bottom: '100%' });\n label.textContent = `${hit.read.key} · ${hit.read.locale}${hit.others > 0 ? ` (+${hit.others} with this text)` : ''}`;\n if (!box.isConnected) document.body.append(box);\n };\n const update = (e: MouseEvent | KeyboardEvent) => {\n if (!e.altKey) return hide();\n if (frame === 0) frame = requestAnimationFrame(show);\n };\n\n window.addEventListener(\n 'mousemove',\n (e) => {\n pointer = { x: e.clientX, y: e.clientY };\n update(e);\n },\n { capture: true, passive: true },\n );\n window.addEventListener('keydown', update, true);\n window.addEventListener('keyup', update, true);\n window.addEventListener('blur', hide);\n // Alt-click on a link would otherwise download it, and on a field focus it.\n window.addEventListener(\n 'mousedown',\n (e) => {\n if (e.altKey && hitAt(e.clientX, e.clientY) !== undefined) e.preventDefault();\n },\n true,\n );\n window.addEventListener(\n 'click',\n (e) => {\n if (!e.altKey) return;\n const hit = hitAt(e.clientX, e.clientY);\n if (hit === undefined) return;\n e.preventDefault();\n e.stopPropagation();\n void open(base, hit.read, e.shiftKey);\n },\n true,\n );\n window.addEventListener(\n 'contextmenu',\n (e) => {\n if (!e.altKey) return;\n const hit = hitAt(e.clientX, e.clientY);\n if (hit === undefined) return;\n e.preventDefault();\n e.stopPropagation();\n hide();\n const catalog = e.shiftKey;\n void openDialog({\n base,\n key: hit.read.key,\n locale: catalog ? undefined : hit.read.locale,\n x: e.clientX,\n y: e.clientY,\n openFile: () => void open(base, hit.read, catalog),\n });\n },\n true,\n );\n}\n\nasync function open(base: string, read: Read, catalog: boolean): Promise<void> {\n const query = new URLSearchParams({ key: read.key, locale: read.locale });\n if (catalog) query.set('catalog', '');\n const res = await fetch(`${base}__prosebook/open?${query}`);\n if (!res.ok) console.warn(`prosebook: ${await res.text()}`);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"inspector.js","names":[],"sources":["../src/inspector-dialog.ts","../src/inspector.ts"],"sourcesContent":["/**\n * The inspector's dialog: Alt-right-click a string to edit its entry on the page, in every locale, as\n * fields or as the YAML written in its files. The server checks each edit as the whole catalog before\n * writing it (`tools/entry.ts`); once written, the page updates in place like after any catalog edit.\n * While typing, it asks the server to check the edit unsaved, and shows what lint says of each locale\n * under its text: a finding selects its words, and one that has a fix makes it.\n *\n * A reviewer who would rather ask than edit leaves a comment, or marks the entry for removal: both are\n * written in the catalog, where whoever edits next, often an agent, finds them among the warnings.\n *\n * It renders in a shadow root, so the app's styles don't reach it and its own don't leak out.\n */\nimport type { Problem } from '#src/tools/catalog.ts';\nimport type { EntryEdit, EntryFields, EntryFinding, EntryView } from '#src/tools/entry.ts';\n\nexport interface DialogOptions {\n base: string;\n key: string;\n /** The locale the string was found in, whose translation comes first, or nothing to start at the source. */\n locale?: string;\n /** Where the pointer was, so the dialog opens beside it. */\n x: number;\n y: number;\n /** Opens the entry in the editor instead. */\n openFile: () => void;\n}\n\ntype Reply = { view?: EntryView; problems?: Problem[] };\n\nconst STYLE = `\n:host { all: initial; }\n.panel {\n --bg: #fff; --fg: #1d1b17; --muted: #6b665c; --line: #e2ded4; --field: #faf9f6; --err: #b91c1c; --warn: #b45309; --ok: #15803d;\n position: fixed; z-index: 2147483647; display: flex; flex-direction: column;\n width: min(460px, calc(100vw - 24px)); max-height: min(80vh, 680px);\n background: var(--bg); color: var(--fg); border: 1px solid var(--line); border-radius: 10px;\n box-shadow: 0 16px 48px rgb(0 0 0 / 0.22); font: 13px/1.45 system-ui, -apple-system, 'Segoe UI', sans-serif;\n color-scheme: light;\n}\n@media (prefers-color-scheme: dark) {\n .panel { --bg: #1d1c17; --fg: #ece8dd; --muted: #a29c8d; --line: #34322a; --field: #15140f; --err: #f87171; --warn: #fbbf24; --ok: #4ade80; color-scheme: dark; }\n}\nheader, footer { display: flex; align-items: center; gap: 8px; padding: 10px 12px; }\nheader { border-bottom: 1px solid var(--line); }\nfooter { border-top: 1px solid var(--line); justify-content: space-between; flex-wrap: wrap; }\n.key { font: 600 12px/1.5 ui-monospace, monospace; color: #fff; background: #7c3aed; padding: 1px 6px; border-radius: 4px; }\n.locale { font: 12px/1 ui-monospace, monospace; color: var(--muted); }\n.grow { flex: 1; }\n.tabs { display: inline-flex; border: 1px solid var(--line); border-radius: 6px; overflow: hidden; }\n.tabs button { border: 0; border-radius: 0; padding: 4px 10px; background: transparent; }\n.tabs button[aria-selected='true'] { background: #7c3aed; color: #fff; }\n.body { padding: 12px; overflow: auto; display: grid; gap: 12px; }\nlabel { display: grid; gap: 4px; font-size: 12px; font-weight: 500; color: var(--muted); }\nlabel small { font-weight: 400; font-family: ui-monospace, monospace; }\ntextarea, input, select {\n font: 13px/1.45 ui-monospace, 'SF Mono', Consolas, monospace; color: var(--fg); background: var(--field);\n border: 1px solid var(--line); border-radius: 6px; padding: 6px 8px; width: 100%; box-sizing: border-box;\n}\ntextarea { resize: vertical; }\n.prose { font-family: inherit; }\ntextarea:focus, input:focus, select:focus { outline: 2px solid #7c3aed; outline-offset: -1px; }\n.row { display: grid; grid-template-columns: 1fr 1fr auto; gap: 8px; align-items: end; }\n.check { display: flex; align-items: center; gap: 6px; padding-bottom: 7px; color: var(--fg); font-weight: 400; }\n.check input { width: auto; }\nfieldset { border: 0; padding: 0; margin: 0; display: grid; gap: 8px; }\nlegend { font-size: 12px; font-weight: 500; color: var(--muted); padding: 0 0 4px; }\n.problems { margin: 0; padding: 0 12px 0 28px; color: var(--err); }\n.problems:empty { display: none; }\n.problems li { margin: 2px 0; }\n.findings { list-style: none; margin: 0; padding: 0; display: grid; gap: 3px; font-weight: 400; color: var(--fg); }\n.findings:empty { display: none; }\n.findings.all { padding: 0 12px 8px; }\n.findings li { display: flex; align-items: baseline; gap: 6px; }\n.findings li::before { content: ''; flex: none; width: 6px; height: 6px; border-radius: 50%; background: var(--warn); }\n.findings li.error::before { background: var(--err); }\n.findings .say { flex: 1; border: 0; padding: 0; background: transparent; font: inherit; text-align: left; color: inherit; }\n.findings button.say:hover { text-decoration: underline; }\n.findings .rule { font: 11px/1.4 ui-monospace, monospace; color: var(--muted); }\n.findings button.fix { padding: 2px 8px; font-size: 12px; }\n.status { color: var(--muted); }\n.status.ok { color: var(--ok); }\nbutton {\n font: 500 13px/1 system-ui, -apple-system, 'Segoe UI', sans-serif; color: var(--fg); background: var(--bg);\n border: 1px solid var(--line); border-radius: 6px; padding: 7px 12px; cursor: pointer;\n}\nbutton.primary { background: #7c3aed; border-color: #7c3aed; color: #fff; }\nbutton.link { border: 0; padding: 7px 0; color: var(--muted); text-decoration: underline; background: transparent; }\nbutton.close { border: 0; padding: 2px 6px; font-size: 18px; color: var(--muted); background: transparent; }\nbutton.remove[aria-pressed='true'] { color: var(--err); border-color: currentColor; }\n.removing textarea[name='message'], .removing textarea[name^='translation.'] { text-decoration: line-through; color: var(--muted); }\n`;\n\nlet closeOpen: (() => void) | undefined;\n\n/** An element with properties set (`className`, `value`, `checked`, ...) and children appended. */\nfunction el<K extends keyof HTMLElementTagNameMap>(tag: K, props: object = {}, ...children: (Node | string)[]): HTMLElementTagNameMap[K] {\n const node = document.createElement(tag);\n Object.assign(node, props);\n node.append(...children);\n return node;\n}\n\nconst rowsFor = (text: string, least: number) => Math.min(12, Math.max(least, text.split('\\n').length + (text.length > 60 ? 1 : 0)));\n\n/** Opens the dialog for an entry, closing any already open. */\nexport async function openDialog(options: DialogOptions): Promise<void> {\n closeOpen?.();\n const query = new URLSearchParams({ key: options.key });\n const host = el('div');\n const root = host.attachShadow({ mode: 'open' });\n const panel = el('div', { className: 'panel' });\n // Hidden until it has its place beside the pointer, which needs its size, which needs the entry.\n panel.style.visibility = 'hidden';\n panel.setAttribute('role', 'dialog');\n panel.setAttribute('aria-label', `Edit ${options.key}`);\n root.append(el('style', { textContent: STYLE }), panel);\n\n const close = () => {\n clearTimeout(timer);\n host.remove();\n if (closeOpen === close) closeOpen = undefined;\n };\n closeOpen = close;\n\n const problems = el('ul', { className: 'problems' });\n const status = el('span', { className: 'status' });\n const body = el('div', { className: 'body' });\n const tabs = {\n fields: el('button', { type: 'button', textContent: 'Fields' }),\n yaml: el('button', { type: 'button', textContent: 'YAML' }),\n };\n let tab: 'fields' | 'yaml' = 'fields';\n let view: EntryView | undefined;\n // Marked for removal: written with the fields, so it is the YAML tab's to change while that is open.\n let remove = false;\n const removeButton = el('button', {\n type: 'button',\n className: 'remove',\n title: 'Leave the entry for whoever edits next to delete, with the code that reads it',\n });\n const showRemove = () => {\n removeButton.hidden = tab === 'yaml' || view === undefined;\n removeButton.setAttribute('aria-pressed', String(remove));\n removeButton.textContent = remove ? 'Marked for removal' : 'Mark for removal';\n panel.classList.toggle('removing', remove && tab === 'fields');\n };\n removeButton.addEventListener('click', () => {\n remove = !remove;\n showRemove();\n });\n // What lint said, and each locale's text it said it of: a finding's place and fix are in that text.\n let findings: EntryFinding[] = [];\n let checked = new Map<string, string>();\n const heard = (next: EntryView) => {\n findings = next.findings;\n checked = new Map([[next.source, next.fields.message], ...Object.entries(next.fields.translations)]);\n };\n const all = el('ul', { className: 'findings all' });\n\n const showProblems = (list: readonly Problem[]) =>\n problems.replaceChildren(\n ...list.map((p) =>\n el('li', { textContent: p.file === undefined ? p.message : `${[p.file, p.key].filter(Boolean).join(' ')}: ${p.message}` }),\n ),\n );\n\n const area = (name: string, label: string, value: string, prose = false, least = 2) =>\n el(\n 'label',\n {},\n label,\n // YAML doesn't wrap: a wrapped line would hide its indentation.\n el('textarea', {\n name,\n value,\n rows: rowsFor(value, least),\n spellcheck: prose,\n wrap: prose ? 'soft' : 'off',\n className: prose ? 'prose' : '',\n }),\n );\n\n // The locale the string was read in comes first; the others follow, in the catalog's order.\n const focus = () => (options.locale !== undefined && view?.locales.includes(options.locale) === true ? options.locale : undefined);\n const ordered = () => (view === undefined ? [] : [...new Set([focus(), ...view.locales].filter((l) => l !== undefined))]);\n const placeholder = (label: HTMLLabelElement, text: string) => {\n label.querySelector('textarea')!.placeholder = text;\n return label;\n };\n\n const fieldOf = (locale: string) =>\n root.querySelector<HTMLTextAreaElement>(`[name=\"${locale === view?.source ? 'message' : `translation.${locale}`}\"]`);\n\n const item = (f: EntryFinding, prefix: string) => {\n const field = fieldOf(f.locale);\n // Typed over since the check: the next one is on its way, so the old place and fix are left alone.\n const current = () => (tab === 'fields' && field !== null && field.value === checked.get(f.locale) ? field : undefined);\n const text = `${prefix}${f.message}`;\n const at = tab === 'fields' ? f.at : undefined;\n const say =\n at === undefined\n ? el('span', { className: 'say', textContent: text })\n : el('button', { type: 'button', className: 'say', textContent: text });\n say.addEventListener('click', () => {\n const target = current();\n if (at === undefined || target === undefined) return;\n target.focus();\n target.setSelectionRange(at.start, at.end);\n });\n const li = el('li', { className: f.severity }, say, el('span', { className: 'rule', textContent: f.rule }));\n const fixed = f.fixed;\n if (fixed !== undefined && tab === 'fields') {\n const fix = el('button', { type: 'button', className: 'fix', textContent: 'Fix' });\n fix.addEventListener('click', () => {\n const target = current();\n if (target === undefined) return;\n target.value = fixed;\n target.dispatchEvent(new Event('input', { bubbles: true }));\n });\n li.append(fix);\n }\n return li;\n };\n\n const showFindings = () => {\n if (tab === 'yaml') {\n all.replaceChildren(...findings.map((f) => item(f, `${f.locale}: `)));\n return;\n }\n all.replaceChildren();\n for (const list of root.querySelectorAll<HTMLUListElement>('.body .findings'))\n list.replaceChildren(...findings.filter((f) => f.locale === list.dataset['locale']).map((f) => item(f, '')));\n };\n\n /** A text field with what lint says of its locale under it. */\n const withFindings = (label: HTMLLabelElement, locale: string) => {\n const list = el('ul', { className: 'findings' });\n list.dataset['locale'] = locale;\n label.append(list);\n return label;\n };\n\n const render = () => {\n if (view === undefined) return;\n tabs.fields.setAttribute('aria-selected', String(tab === 'fields'));\n tabs.yaml.setAttribute('aria-selected', String(tab === 'yaml'));\n remove = view.fields.remove;\n showRemove();\n if (tab === 'yaml') {\n const v = view;\n body.replaceChildren(\n area('yaml.catalog', v.files.catalog, v.yaml.catalog, false, 3),\n ...ordered().map((l) =>\n placeholder(area(`yaml.${l}`, v.files.translations[l]!, v.yaml.translations[l] ?? '', false, 1), `${v.key}: …`),\n ),\n );\n showFindings();\n return;\n }\n const f = view.fields;\n const fallsBack = `No translation: the ${view.source} text shows`;\n const translation = (l: string) =>\n withFindings(\n placeholder(\n area(`translation.${l}`, `Translation · ${l}${f.translations[l] === '' ? ' · none yet' : ''}`, f.translations[l] ?? '', true),\n fallsBack,\n ),\n l,\n );\n const others = ordered().filter((l) => l !== focus());\n const select = (name: string, value: string, choices: string[]) =>\n el(\n 'select',\n { name },\n el('option', { value: '', textContent: '—' }),\n ...choices.map((c) => el('option', { value: c, textContent: c, selected: c === value })),\n );\n const argNames = [...new Set([...view.argNames, ...Object.keys(f.args)])];\n body.replaceChildren(\n ...(focus() === undefined ? [] : [translation(focus()!)]),\n withFindings(area('message', `${focus() === undefined ? 'Message' : 'Source'} · ${view.source}`, f.message, true), view.source),\n ...(others.length === 0\n ? []\n : [\n el(\n 'fieldset',\n {},\n el('legend', { textContent: focus() === undefined ? 'Translations' : 'Other translations' }),\n ...others.map(translation),\n ),\n ]),\n placeholder(area('comment', 'Comment · for whoever edits next', f.comment, true, 1), 'Ask for a change instead of making it'),\n area('context', 'Context', f.context, true, 1),\n el(\n 'div',\n { className: 'row' },\n el('label', {}, 'Kind', select('kind', f.kind, view.kinds)),\n el('label', {}, 'Surface', select('surface', f.surface, view.surfaces)),\n el('label', { className: 'check' }, el('input', { type: 'checkbox', name: 'fragment', checked: f.fragment }), 'Fragment'),\n ),\n ...(argNames.length === 0\n ? []\n : [\n el(\n 'fieldset',\n {},\n el('legend', { textContent: 'What each placeholder holds' }),\n ...argNames.map((n) =>\n el('label', {}, el('small', { textContent: `{${n}}` }), el('input', { name: `arg.${n}`, value: f.args[n] ?? '' })),\n ),\n ),\n ]),\n );\n showFindings();\n };\n\n const value = (name: string) => root.querySelector<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>(`[name=\"${name}\"]`)?.value;\n\n const current = (): EntryEdit => {\n const byLocale = (prefix: string) => Object.fromEntries(ordered().map((l) => [l, value(`${prefix}${l}`) ?? '']));\n if (tab === 'yaml') return { yaml: { catalog: value('yaml.catalog') ?? '', translations: byLocale('yaml.') } };\n const args: Record<string, string> = {};\n for (const input of root.querySelectorAll<HTMLInputElement>('input[name^=\"arg.\"]')) args[input.name.slice(4)] = input.value;\n const fields: EntryFields = {\n message: value('message') ?? '',\n translations: byLocale('translation.'),\n context: value('context') ?? '',\n kind: value('kind') ?? '',\n surface: value('surface') ?? '',\n fragment: root.querySelector<HTMLInputElement>('input[name=\"fragment\"]')?.checked === true,\n args,\n comment: value('comment') ?? '',\n remove,\n };\n return { fields };\n };\n\n const send = async (write: boolean): Promise<EntryView | undefined> => {\n const res = await fetch(`${options.base}__prosebook/entry`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ key: options.key, edit: current(), write }),\n });\n const reply = (await res.json()) as Reply;\n showProblems(reply.problems ?? []);\n return reply.view;\n };\n\n // Checked a moment after typing stops; a reply to an older check than the last is dropped.\n let timer: ReturnType<typeof setTimeout> | undefined;\n let asked = 0;\n const check = () => {\n clearTimeout(timer);\n timer = setTimeout(() => {\n const mine = ++asked;\n void send(false).then((checkedView) => {\n if (mine !== asked) return;\n if (checkedView === undefined) findings = [];\n else heard(checkedView);\n showFindings();\n });\n }, 350);\n };\n body.addEventListener('input', check);\n body.addEventListener('change', check);\n\n const save = async () => {\n status.className = 'status';\n status.textContent = 'Checking…';\n const saved = await send(true);\n status.textContent = '';\n if (saved === undefined) return;\n status.className = 'status ok';\n status.textContent = 'Saved';\n setTimeout(close, 600);\n };\n\n const switchTo = async (next: 'fields' | 'yaml') => {\n if (next === tab) return;\n // The other tab shows this one's edits, so they are turned into it on the server, checked.\n const converted = await send(false);\n if (converted === undefined) return;\n view = converted;\n heard(converted);\n tab = next;\n render();\n };\n\n tabs.fields.addEventListener('click', () => void switchTo('fields'));\n tabs.yaml.addEventListener('click', () => void switchTo('yaml'));\n\n const saveButton = el('button', { type: 'button', className: 'primary', textContent: 'Save' });\n saveButton.addEventListener('click', () => void save());\n const cancel = el('button', { type: 'button', textContent: 'Cancel' });\n cancel.addEventListener('click', close);\n const closeButton = el('button', { type: 'button', className: 'close', textContent: '×', title: 'Close' });\n closeButton.addEventListener('click', close);\n const openFile = el('button', { type: 'button', className: 'link', textContent: 'Open in editor' });\n openFile.addEventListener('click', () => {\n options.openFile();\n close();\n });\n\n panel.append(\n el(\n 'header',\n {},\n el('span', { className: 'key', textContent: options.key }),\n el('span', { className: 'locale', textContent: options.locale ?? '' }),\n el('span', { className: 'grow' }),\n el('span', { className: 'tabs' }, tabs.fields, tabs.yaml),\n closeButton,\n ),\n body,\n all,\n problems,\n el('footer', {}, openFile, el('span', { className: 'grow' }), status, removeButton, cancel, saveButton),\n );\n // Keys typed here are the dialog's: the app's shortcuts don't see them.\n for (const type of ['keydown', 'keyup', 'keypress'] as const) host.addEventListener(type, (e) => e.stopPropagation());\n panel.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') close();\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) void save();\n });\n\n document.body.append(host);\n const res = await fetch(`${options.base}__prosebook/entry?${query}`);\n const reply = (await res.json()) as EntryView | { problems: Problem[] };\n if ('problems' in reply) {\n showProblems(reply.problems);\n } else {\n view = reply;\n heard(reply);\n }\n render();\n\n // Beside the pointer, inside the window.\n const box = panel.getBoundingClientRect();\n const left = Math.min(Math.max(12, options.x - 24), innerWidth - box.width - 12);\n const below = options.y + 16;\n const top = below + box.height <= innerHeight - 12 ? below : Math.max(12, options.y - box.height - 16);\n Object.assign(panel.style, { left: `${left}px`, top: `${top}px`, visibility: '' });\n // Growing later, with problems or the other tab, moves it up rather than past the bottom.\n new ResizeObserver(() => {\n const now = panel.getBoundingClientRect();\n const fitted = Math.max(12, Math.min(now.top, innerHeight - now.height - 12));\n if (fitted !== now.top) panel.style.top = `${fitted}px`;\n }).observe(panel);\n (root.querySelector<HTMLElement>(`[name=\"translation.${focus()}\"]`) ?? root.querySelector<HTMLElement>('textarea'))?.focus();\n}\n","/**\n * The dev inspector each integration loads into the page: hold Alt and point at a string to see its\n * key, Alt-click to open the entry it came from, Alt-Shift-click to open the catalog entry instead of\n * the translation. Alt-right-click edits the entry on the page instead (`inspector-dialog.ts`).\n *\n * Text on screen carries no key. `<Text>` renders a fragment, so marking it would take a wrapper\n * element that exists only in dev and changes what a stylesheet sees; `t()` returns a bare string. So\n * the runtime reports every read (`ReadHook`) and the text under the pointer is matched back to them.\n */\nimport type { DevHooks } from '#src/runtime/types.ts';\n\nimport { openDialog } from './inspector-dialog.ts';\n\nexport interface Read {\n key: string;\n /** The locale the message was found in. */\n locale: string;\n}\n\n/** Recent reads by the text they came to, least recent text first; each text's keys most recent first. */\nexport type Reads = Map<string, Read[]>;\n\n/** The character under the pointer: a text node and its index there. */\nexport interface Caret {\n node: Node;\n offset: number;\n}\n\nexport interface Hit {\n text: string;\n read: Read;\n /** How many other keys came to the same text. */\n others: number;\n /** The innermost element holding all of the text. */\n element: Element;\n}\n\n// Enough for every string on a busy screen and the ones it showed a moment ago.\nconst LIMIT = 2000;\nconst ATTRIBUTES = ['placeholder', 'title', 'aria-label', 'alt', 'value'];\n\nexport function record(reads: Reads, key: string, text: string, locale: string): void {\n if (text.trim() === '') return;\n const seen = reads.get(text) ?? [];\n reads.delete(text);\n reads.set(text, seen[0]?.key === key && seen[0].locale === locale ? seen : [{ key, locale }, ...seen.filter((r) => r.key !== key)]);\n if (reads.size > LIMIT) reads.delete(reads.keys().next().value!);\n}\n\n/**\n * The read under the caret: the longest text that covers it, in the innermost element holding all of\n * it. A rich message runs across its elements, so it wins over a shorter text inside one of them.\n * Without a caret (over an input, or padding) an element's whole text, or the target's readable\n * attribute, must equal a read.\n */\nexport function keyAt(target: Element, caret: Caret | undefined, reads: Reads): Hit | undefined {\n let best: Hit | undefined;\n for (let el: Element | null = target; el !== null; el = el.parentElement) {\n const text = el.textContent ?? '';\n const at = caret === undefined ? undefined : offsetIn(el, caret);\n for (const [said, seen] of reads) {\n if (said.length <= (best?.text.length ?? 0)) continue;\n if (at === undefined ? text.trim() === said.trim() : covers(text, said, at))\n best = { text: said, read: seen[0]!, others: seen.length - 1, element: el };\n }\n if (best !== undefined || el !== target) continue;\n for (const name of ATTRIBUTES) {\n const value = el.getAttribute(name);\n const seen = value === null ? undefined : reads.get(value);\n if (value !== null && seen !== undefined) return { text: value, read: seen[0]!, others: seen.length - 1, element: el };\n }\n }\n return best;\n}\n\n/** Whether an occurrence of `said` in `text` holds the character at `at`. */\nfunction covers(text: string, said: string, at: number): boolean {\n const start = text.indexOf(said, Math.max(0, at - said.length + 1));\n return start !== -1 && start <= at;\n}\n\n/** The caret's index in the element's text, or nothing when the caret is outside it. */\nfunction offsetIn(el: Element, caret: Caret): number | undefined {\n if (caret.node.nodeType !== Node.TEXT_NODE || !el.contains(caret.node)) return undefined;\n const walker = el.ownerDocument.createTreeWalker(el, NodeFilter.SHOW_TEXT);\n let offset = 0;\n for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) {\n if (node === caret.node) return offset + caret.offset;\n offset += node.textContent?.length ?? 0;\n }\n return undefined;\n}\n\n/** The character under a point on screen, where the browser can say. */\nexport function caretAt(x: number, y: number): Caret | undefined {\n if (typeof document.caretPositionFromPoint !== 'function') return undefined;\n const position = document.caretPositionFromPoint(x, y);\n if (position === null) return undefined;\n const { offsetNode: node, offset } = position;\n // A caret position sits between two characters, and where one string ends the next may begin: take\n // the character the pointer is over, or the one before (past the end of a line, in padding).\n if (node.nodeType !== Node.TEXT_NODE || offset === 0) return { node, offset };\n const range = document.createRange();\n range.setStart(node, offset);\n range.setEnd(node, Math.min(offset + 1, node.textContent?.length ?? 0));\n const r = range.getBoundingClientRect();\n const over = !range.collapsed && x >= r.left && x <= r.right && y >= r.top && y <= r.bottom;\n return { node, offset: over ? offset : offset - 1 };\n}\n\nexport interface InspectorOptions {\n /** The dev server's base URL, ending in `/`. */\n base: string;\n}\n\n/**\n * Starts listening for reads and for Alt on the page, once: Bun and Next call it from the generated\n * index, which runs again whenever it changes.\n */\nexport function install({ base }: InspectorOptions): void {\n const hooks = globalThis as DevHooks;\n if (hooks.__PROSEBOOK_READ__ !== undefined) return;\n const reads: Reads = new Map();\n hooks.__PROSEBOOK_READ__ = (key, text, locale) => record(reads, key, text, locale);\n\n const box = document.createElement('div');\n const label = document.createElement('div');\n box.append(label);\n Object.assign(box.style, {\n position: 'fixed',\n display: 'none',\n zIndex: '2147483647',\n pointerEvents: 'none',\n boxSizing: 'border-box',\n outline: '2px solid #7c3aed',\n background: 'rgb(124 58 237 / 0.08)',\n });\n Object.assign(label.style, {\n position: 'absolute',\n left: '-2px',\n padding: '1px 6px',\n font: '12px/1.5 ui-monospace, monospace',\n whiteSpace: 'nowrap',\n color: '#fff',\n background: '#7c3aed',\n });\n\n let pointer: { x: number; y: number } | undefined;\n let frame = 0;\n\n const hitAt = (x: number, y: number) => {\n const target = document.elementFromPoint(x, y);\n return target === null ? undefined : keyAt(target, caretAt(x, y), reads);\n };\n const hide = () => {\n box.style.display = 'none';\n };\n const show = () => {\n frame = 0;\n const hit = pointer === undefined ? undefined : hitAt(pointer.x, pointer.y);\n if (hit === undefined) return hide();\n const r = hit.element.getBoundingClientRect();\n Object.assign(box.style, { display: 'block', left: `${r.left}px`, top: `${r.top}px`, width: `${r.width}px`, height: `${r.height}px` });\n // Below the box when there is no room above it.\n Object.assign(label.style, r.top < 24 ? { top: '100%', bottom: '' } : { top: '', bottom: '100%' });\n label.textContent = `${hit.read.key} · ${hit.read.locale}${hit.others > 0 ? ` (+${hit.others} with this text)` : ''}`;\n if (!box.isConnected) document.body.append(box);\n };\n const update = (e: MouseEvent | KeyboardEvent) => {\n if (!e.altKey) return hide();\n if (frame === 0) frame = requestAnimationFrame(show);\n };\n\n window.addEventListener(\n 'mousemove',\n (e) => {\n pointer = { x: e.clientX, y: e.clientY };\n update(e);\n },\n { capture: true, passive: true },\n );\n window.addEventListener('keydown', update, true);\n window.addEventListener('keyup', update, true);\n window.addEventListener('blur', hide);\n // Alt-click on a link would otherwise download it, and on a field focus it.\n window.addEventListener(\n 'mousedown',\n (e) => {\n if (e.altKey && hitAt(e.clientX, e.clientY) !== undefined) e.preventDefault();\n },\n true,\n );\n window.addEventListener(\n 'click',\n (e) => {\n if (!e.altKey) return;\n const hit = hitAt(e.clientX, e.clientY);\n if (hit === undefined) return;\n e.preventDefault();\n e.stopPropagation();\n void open(base, hit.read, e.shiftKey);\n },\n true,\n );\n window.addEventListener(\n 'contextmenu',\n (e) => {\n if (!e.altKey) return;\n const hit = hitAt(e.clientX, e.clientY);\n if (hit === undefined) return;\n e.preventDefault();\n e.stopPropagation();\n hide();\n const catalog = e.shiftKey;\n void openDialog({\n base,\n key: hit.read.key,\n locale: catalog ? undefined : hit.read.locale,\n x: e.clientX,\n y: e.clientY,\n openFile: () => void open(base, hit.read, catalog),\n });\n },\n true,\n );\n}\n\nasync function open(base: string, read: Read, catalog: boolean): Promise<void> {\n const query = new URLSearchParams({ key: read.key, locale: read.locale });\n if (catalog) query.set('catalog', '');\n const res = await fetch(`${base}__prosebook/open?${query}`);\n if (!res.ok) console.warn(`prosebook: ${await res.text()}`);\n}\n"],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ // src/runtime/locale.ts
2
+ /**
3
+ * Picks the best available locale for what was asked, in the order it was asked.
4
+ *
5
+ * Each requested tag tries an exact match, then its parents (`pt-BR` finds `pt`), then any locale
6
+ * of the same language (`pt` finds `pt-BR`). Matching ignores case, as BCP 47 does.
7
+ */
8
+ function negotiate(requested, available, fallback) {
9
+ const find = (tag) => available.find((a) => a.toLowerCase() === tag.toLowerCase());
10
+ for (const want of typeof requested === "string" ? [requested] : requested) {
11
+ let tag = want;
12
+ for (;;) {
13
+ const found = find(tag);
14
+ if (found !== void 0) return found;
15
+ const cut = tag.lastIndexOf("-");
16
+ if (cut < 0) break;
17
+ tag = tag.slice(0, cut);
18
+ }
19
+ const language = languageOf(want);
20
+ const sibling = available.find((a) => languageOf(a) === language);
21
+ if (sibling !== void 0) return sibling;
22
+ }
23
+ return fallback;
24
+ }
25
+ /** The locales a message is looked up in, in order: the locale, its available parents, then the source. */
26
+ function chainOf(locale, available, source) {
27
+ const chain = [locale];
28
+ let tag = locale;
29
+ while (tag.includes("-")) {
30
+ tag = tag.slice(0, tag.lastIndexOf("-"));
31
+ const parent = available.find((a) => a.toLowerCase() === tag.toLowerCase());
32
+ if (parent !== void 0 && !chain.includes(parent)) chain.push(parent);
33
+ }
34
+ if (!chain.includes(source)) chain.push(source);
35
+ return chain;
36
+ }
37
+ /** The locales the browser or the environment asks for, most preferred first. */
38
+ function preferredLocales() {
39
+ const nav = globalThis.navigator;
40
+ if (nav?.languages && nav.languages.length > 0) return [...nav.languages];
41
+ if (nav?.language) return [nav.language];
42
+ const resolved = Intl.DateTimeFormat().resolvedOptions().locale;
43
+ return resolved ? [resolved] : [];
44
+ }
45
+ /**
46
+ * The locales an `Accept-Language` header asks for, most preferred first: ordered by `q`, ties in
47
+ * the header's order. `q=0` means "not this one", and `*` asks for nothing `negotiate`'s fallback
48
+ * doesn't already give, so both are left out, as is a part whose `q` isn't a number.
49
+ */
50
+ function acceptedLocales(header) {
51
+ const weighed = [];
52
+ for (const part of (header ?? "").split(",")) {
53
+ const [tag = "", ...params] = part.split(";").map((s) => s.trim());
54
+ if (tag === "" || tag === "*") continue;
55
+ const weight = params.find((p) => /^q\s*=/i.test(p));
56
+ const q = weight === void 0 ? 1 : Number(weight.slice(weight.indexOf("=") + 1).trim());
57
+ if (Number.isNaN(q) || q <= 0) continue;
58
+ weighed.push({
59
+ tag,
60
+ q
61
+ });
62
+ }
63
+ return weighed.sort((a, b) => b.q - a.q).map((w) => w.tag);
64
+ }
65
+ function languageOf(tag) {
66
+ return tag.split("-", 1)[0].toLowerCase();
67
+ }
68
+ export { preferredLocales as i, chainOf as n, negotiate as r, acceptedLocales as t };
69
+
70
+ //# sourceMappingURL=locale-DpQA1pqH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locale-DpQA1pqH.js","names":[],"sources":["../src/runtime/locale.ts"],"sourcesContent":["/**\n * Picks the best available locale for what was asked, in the order it was asked.\n *\n * Each requested tag tries an exact match, then its parents (`pt-BR` finds `pt`), then any locale\n * of the same language (`pt` finds `pt-BR`). Matching ignores case, as BCP 47 does.\n */\nexport function negotiate<L extends string>(requested: string | readonly string[], available: readonly L[], fallback: L): L {\n const find = (tag: string) => available.find((a) => a.toLowerCase() === tag.toLowerCase());\n for (const want of typeof requested === 'string' ? [requested] : requested) {\n let tag = want;\n for (;;) {\n const found = find(tag);\n if (found !== undefined) return found;\n const cut = tag.lastIndexOf('-');\n if (cut < 0) break;\n tag = tag.slice(0, cut);\n }\n const language = languageOf(want);\n const sibling = available.find((a) => languageOf(a) === language);\n if (sibling !== undefined) return sibling;\n }\n return fallback;\n}\n\n/** The locales a message is looked up in, in order: the locale, its available parents, then the source. */\nexport function chainOf<L extends string>(locale: L, available: readonly L[], source: L): L[] {\n const chain: L[] = [locale];\n let tag: string = locale;\n while (tag.includes('-')) {\n tag = tag.slice(0, tag.lastIndexOf('-'));\n const parent = available.find((a) => a.toLowerCase() === tag.toLowerCase());\n if (parent !== undefined && !chain.includes(parent)) chain.push(parent);\n }\n if (!chain.includes(source)) chain.push(source);\n return chain;\n}\n\n/** The locales the browser or the environment asks for, most preferred first. */\nexport function preferredLocales(): string[] {\n const nav = (globalThis as { navigator?: { languages?: readonly string[]; language?: string } }).navigator;\n if (nav?.languages && nav.languages.length > 0) return [...nav.languages];\n if (nav?.language) return [nav.language];\n const resolved = Intl.DateTimeFormat().resolvedOptions().locale;\n return resolved ? [resolved] : [];\n}\n\n/**\n * The locales an `Accept-Language` header asks for, most preferred first: ordered by `q`, ties in\n * the header's order. `q=0` means \"not this one\", and `*` asks for nothing `negotiate`'s fallback\n * doesn't already give, so both are left out, as is a part whose `q` isn't a number.\n */\nexport function acceptedLocales(header: string | null | undefined): string[] {\n const weighed: { tag: string; q: number }[] = [];\n for (const part of (header ?? '').split(',')) {\n const [tag = '', ...params] = part.split(';').map((s) => s.trim());\n if (tag === '' || tag === '*') continue;\n const weight = params.find((p) => /^q\\s*=/i.test(p));\n const q = weight === undefined ? 1 : Number(weight.slice(weight.indexOf('=') + 1).trim());\n if (Number.isNaN(q) || q <= 0) continue;\n weighed.push({ tag, q });\n }\n // Array.prototype.sort is stable, so equal weights keep the header's order.\n return weighed.sort((a, b) => b.q - a.q).map((w) => w.tag);\n}\n\nfunction languageOf(tag: string): string {\n return tag.split('-', 1)[0]!.toLowerCase();\n}\n"],"mappings":";;;;;;;AAMA,SAAgB,UAA4B,WAAuC,WAAyB,UAAgB;CAC1H,MAAM,QAAQ,QAAgB,UAAU,MAAM,MAAM,EAAE,YAAY,MAAM,IAAI,YAAY,CAAC;CACzF,KAAK,MAAM,QAAQ,OAAO,cAAc,WAAW,CAAC,SAAS,IAAI,WAAW;EAC1E,IAAI,MAAM;EACV,SAAS;GACP,MAAM,QAAQ,KAAK,GAAG;GACtB,IAAI,UAAU,KAAA,GAAW,OAAO;GAChC,MAAM,MAAM,IAAI,YAAY,GAAG;GAC/B,IAAI,MAAM,GAAG;GACb,MAAM,IAAI,MAAM,GAAG,GAAG;EACxB;EACA,MAAM,WAAW,WAAW,IAAI;EAChC,MAAM,UAAU,UAAU,MAAM,MAAM,WAAW,CAAC,MAAM,QAAQ;EAChE,IAAI,YAAY,KAAA,GAAW,OAAO;CACpC;CACA,OAAO;AACT;;AAGA,SAAgB,QAA0B,QAAW,WAAyB,QAAgB;CAC5F,MAAM,QAAa,CAAC,MAAM;CAC1B,IAAI,MAAc;CAClB,OAAO,IAAI,SAAS,GAAG,GAAG;EACxB,MAAM,IAAI,MAAM,GAAG,IAAI,YAAY,GAAG,CAAC;EACvC,MAAM,SAAS,UAAU,MAAM,MAAM,EAAE,YAAY,MAAM,IAAI,YAAY,CAAC;EAC1E,IAAI,WAAW,KAAA,KAAa,CAAC,MAAM,SAAS,MAAM,GAAG,MAAM,KAAK,MAAM;CACxE;CACA,IAAI,CAAC,MAAM,SAAS,MAAM,GAAG,MAAM,KAAK,MAAM;CAC9C,OAAO;AACT;;AAGA,SAAgB,mBAA6B;CAC3C,MAAM,MAAO,WAAoF;CACjG,IAAI,KAAK,aAAa,IAAI,UAAU,SAAS,GAAG,OAAO,CAAC,GAAG,IAAI,SAAS;CACxE,IAAI,KAAK,UAAU,OAAO,CAAC,IAAI,QAAQ;CACvC,MAAM,WAAW,KAAK,eAAe,CAAC,CAAC,gBAAgB,CAAC,CAAC;CACzD,OAAO,WAAW,CAAC,QAAQ,IAAI,CAAC;AAClC;;;;;;AAOA,SAAgB,gBAAgB,QAA6C;CAC3E,MAAM,UAAwC,CAAC;CAC/C,KAAK,MAAM,SAAS,UAAU,GAAA,CAAI,MAAM,GAAG,GAAG;EAC5C,MAAM,CAAC,MAAM,IAAI,GAAG,UAAU,KAAK,MAAM,GAAG,CAAC,CAAC,KAAK,MAAM,EAAE,KAAK,CAAC;EACjE,IAAI,QAAQ,MAAM,QAAQ,KAAK;EAC/B,MAAM,SAAS,OAAO,MAAM,MAAM,UAAU,KAAK,CAAC,CAAC;EACnD,MAAM,IAAI,WAAW,KAAA,IAAY,IAAI,OAAO,OAAO,MAAM,OAAO,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;EACxF,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,GAAG;EAC/B,QAAQ,KAAK;GAAE;GAAK;EAAE,CAAC;CACzB;CAEA,OAAO,QAAQ,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE,GAAG;AAC3D;AAEA,SAAS,WAAW,KAAqB;CACvC,OAAO,IAAI,MAAM,KAAK,CAAC,CAAC,CAAC,EAAE,CAAE,YAAY;AAC3C"}
@@ -0,0 +1,11 @@
1
+ export type LoaderOptions = {
2
+ code: string;
3
+ } | {
4
+ prepend: string;
5
+ } | {
6
+ locale: string;
7
+ };
8
+ export default function loader(this: {
9
+ getOptions(): LoaderOptions;
10
+ }, source: string): string;
11
+ //# sourceMappingURL=next-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"next-loader.d.ts","names":[],"sources":["../src/next-loader.ts"],"mappings":"A,YASY;EAAkB;;EAAmB;;EAAsB;;wBAE/C,OAAO;EAAQ,cAAc;GAAiB"}
@@ -0,0 +1,17 @@
1
+ import { s as notifyingLocale } from "./bundle-DboItAre.js";
2
+ // src/next-loader.ts
3
+ /**
4
+ * The Turbopack loader `prosebook/next` sets on the generated modules. In a build it hands over a
5
+ * locale module `withProsebook` has already pruned, since that reads the source once for every
6
+ * locale. In dev, in the browser, it puts what `withProsebook` hands it before the index (the hot
7
+ * hooks, and the inspector), and makes each locale module update the copies holding it whenever Fast
8
+ * Refresh runs it again.
9
+ */
10
+ function loader(source) {
11
+ const options = this.getOptions();
12
+ if ("code" in options) return options.code;
13
+ return "prepend" in options ? options.prepend + source : notifyingLocale(source, options.locale);
14
+ }
15
+ export { loader as default };
16
+
17
+ //# sourceMappingURL=next-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"next-loader.js","names":[],"sources":["../src/next-loader.ts"],"sourcesContent":["/**\n * The Turbopack loader `prosebook/next` sets on the generated modules. In a build it hands over a\n * locale module `withProsebook` has already pruned, since that reads the source once for every\n * locale. In dev, in the browser, it puts what `withProsebook` hands it before the index (the hot\n * hooks, and the inspector), and makes each locale module update the copies holding it whenever Fast\n * Refresh runs it again.\n */\nimport { notifyingLocale } from './tools/bundle.ts';\n\nexport type LoaderOptions = { code: string } | { prepend: string } | { locale: string };\n\nexport default function loader(this: { getOptions(): LoaderOptions }, source: string): string {\n const options = this.getOptions();\n if ('code' in options) return options.code;\n return 'prepend' in options ? options.prepend + source : notifyingLocale(source, options.locale);\n}\n"],"mappings":";;;;;;;;;AAWA,SAAwB,OAA8C,QAAwB;CAC5F,MAAM,UAAU,KAAK,WAAW;CAChC,IAAI,UAAU,SAAS,OAAO,QAAQ;CACtC,OAAO,aAAa,UAAU,QAAQ,UAAU,SAAS,gBAAgB,QAAQ,QAAQ,MAAM;AACjG"}
package/dist/next.d.ts ADDED
@@ -0,0 +1,30 @@
1
+ import { t as Prune } from "./bundle-CpUIWb9j.js";
2
+ import { i as ProsebookConfig } from "./config-CzaJJQ60.js";
3
+ export interface ProsebookNextOptions {
4
+ /** The config, inline. Without it, `prosebook.config.*` at `root` is read. */
5
+ config?: ProsebookConfig;
6
+ /** The folder the config's paths resolve from, and where its file is found: the Next app's when unset. */
7
+ root?: string;
8
+ /**
9
+ * Ship only the messages the code names. `strict`, the default, also fails the build on a read
10
+ * whose key it can't see, such as `t(item.key)`. `loose` prunes without that check; `false` ships
11
+ * every message.
12
+ */
13
+ prune?: Prune;
14
+ /** Keys a build always keeps, exactly or as `prefix.*`: for keys built at runtime from data. */
15
+ keep?: string[];
16
+ /** In dev, Alt-click a string on the page to open its entry in the editor. On by default. */
17
+ inspect?: boolean;
18
+ }
19
+ type ConfigFunction<C> = (phase: string, context: {
20
+ defaultConfig: C;
21
+ }) => C | Promise<C>;
22
+ /**
23
+ * Wraps the Next config: `export default withProsebook({ ... })`. Takes the config as an object or as
24
+ * the function Next calls with the phase, and returns the function.
25
+ */
26
+ export declare function withProsebook<C extends object>(next?: C | ConfigFunction<C>, options?: ProsebookNextOptions): (phase: string, context: {
27
+ defaultConfig: C;
28
+ }) => Promise<C>;
29
+ export { withProsebook as default };
30
+ //# sourceMappingURL=next.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"next.d.ts","names":[],"sources":["../src/next.ts"],"mappings":";;iBAuCiB;;EAEf,SAAS;;EAET;;;;;;EAMA,QAAQ;;EAER;;EAEA;;KAMG,eAAe,MAAM,eAAe;EAAW,eAAe;MAAQ,IAAI,QAAQ;;;;;wBAoBvE,cAAc,kBAC5B,OAAM,IAAI,eAAe,IACzB,UAAS,wBACP,eAAe;EAAW,eAAe;MAAQ,QAAQ"}
package/dist/next.js ADDED
@@ -0,0 +1,160 @@
1
+ import { a as installInspector, c as prunedModule, f as watchCatalog, l as scanSources, n as HOT_SET, o as keeper, r as beside, u as unseenMessage } from "./bundle-DboItAre.js";
2
+ import { n as generate, x as CatalogError } from "./generate-BW3N8EGb.js";
3
+ import { r as loadConfig } from "./config-nWZf9QmF.js";
4
+ import { n as nodeMiddleware, r as redirect, t as inspectorEndpoints } from "./endpoints-BgG_5Vpm.js";
5
+ import { join, relative, resolve } from "node:path";
6
+ import { createServer } from "node:http";
7
+ // src/next.ts
8
+ /**
9
+ * `prosebook/next`: the catalog as part of a Next.js app.
10
+ *
11
+ * Generates when `next dev` or `next build` loads the config, and in dev again whenever a catalog or
12
+ * translation file changes, so Fast Refresh picks up the new modules and a broken entry shows in the
13
+ * terminal. Server components read the new text on their next render; in the browser, each locale
14
+ * module hands its new messages to the copies holding it, which re-render in the locale and state
15
+ * they were in. `next build` ships only the messages the code names: each locale module is handed to a
16
+ * Turbopack loader (`next-loader.ts`) that returns it pruned. Turbopack loads a module before it has
17
+ * seen the code that reads it, so the keys come from the source under `scan.dirs` (`tools/bundle.ts`),
18
+ * read here once, and each pruned module travels to the loader as its options.
19
+ *
20
+ * In dev the browser's index also starts the inspector (`inspector.ts`). Its endpoints can't be a
21
+ * route of the app without a file in it, so a server of their own answers them on a local port, and a
22
+ * rewrite sends `/__prosebook/*` there; an entry opens through Next's own `/__nextjs_launch-editor`.
23
+ *
24
+ * Turbopack only: Next 16 fails a Turbopack build whose config has a `webpack` key, so none is added.
25
+ */
26
+ const DEV = "phase-development-server";
27
+ const BUILD = "phase-production-build";
28
+ const loaderPath = beside(import.meta.url, "next-loader");
29
+ const inspectorPath = beside(import.meta.url, "inspector");
30
+ const watching = /* @__PURE__ */ new Set();
31
+ const serving = /* @__PURE__ */ new Map();
32
+ const describe = (e) => e instanceof CatalogError ? e.message : e instanceof Error ? e.stack ?? e.message : String(e);
33
+ const escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
34
+ /**
35
+ * Wraps the Next config: `export default withProsebook({ ... })`. Takes the config as an object or as
36
+ * the function Next calls with the phase, and returns the function.
37
+ */
38
+ function withProsebook(next = {}, options = {}) {
39
+ return async (phase, context) => {
40
+ const resolved = typeof next === "function" ? await next(phase, context) : next;
41
+ if (phase !== DEV && phase !== BUILD) return resolved;
42
+ const config = await loadConfig(resolve(options.root ?? process.cwd()), options.config);
43
+ if (config.language === "ts" && config.importExtension === ".js") throw new Error("prosebook: Turbopack doesn't resolve `./en.js` to `en.ts`: set `importExtension: ''` in the prosebook config");
44
+ const generated = generate(config);
45
+ if (phase === DEV) {
46
+ watch(config);
47
+ if (options.inspect === false) return withRules(resolved, hotRules(config, generated, HOT_SET));
48
+ const base = `${resolved.basePath ?? ""}/`;
49
+ const path = relative(config.out, inspectorPath).replaceAll("\\", "/");
50
+ return withRewrite(withRules(resolved, hotRules(config, generated, HOT_SET + installInspector(path.startsWith(".") ? path : `./${path}`, base))), await serveInspector(config, base));
51
+ }
52
+ const prune = options.prune ?? "strict";
53
+ if (prune === false) return resolved;
54
+ return withRules(resolved, prunedRules(config, generated, prune, options.keep));
55
+ };
56
+ }
57
+ function withRules(resolved, rules) {
58
+ const turbopack = resolved.turbopack;
59
+ return {
60
+ ...resolved,
61
+ turbopack: {
62
+ ...turbopack,
63
+ rules: {
64
+ ...turbopack?.rules,
65
+ ...rules
66
+ }
67
+ }
68
+ };
69
+ }
70
+ /** Sends `/__prosebook/*` to the inspector's server, before any of the app's own rewrites. */
71
+ function withRewrite(resolved, port) {
72
+ const theirs = resolved.rewrites;
73
+ const ours = [{
74
+ source: "/__prosebook/:path*",
75
+ destination: `http://127.0.0.1:${port}/__prosebook/:path*`
76
+ }];
77
+ const rewrites = async () => {
78
+ const r = await theirs?.() ?? [];
79
+ return Array.isArray(r) ? {
80
+ beforeFiles: ours,
81
+ afterFiles: r
82
+ } : {
83
+ ...r,
84
+ beforeFiles: [...ours, ...r.beforeFiles ?? []]
85
+ };
86
+ };
87
+ return {
88
+ ...resolved,
89
+ rewrites
90
+ };
91
+ }
92
+ /**
93
+ * Serves the inspector's endpoints on a free local port, for as long as the process lives. Next's
94
+ * rewrite forwards the page's host, so an edit is still checked against the page's origin.
95
+ */
96
+ function serveInspector(config, base) {
97
+ let port = serving.get(config.catalog);
98
+ if (port !== void 0) return port;
99
+ const handle = inspectorEndpoints(config, { open: ({ file, line, column }) => redirect(`${base}__nextjs_launch-editor?${new URLSearchParams({
100
+ file,
101
+ line1: String(line),
102
+ column1: String(column)
103
+ })}`) });
104
+ const middleware = nodeMiddleware(handle);
105
+ const server = createServer((req, res) => middleware(req, res, () => {
106
+ res.statusCode = 404;
107
+ res.end();
108
+ }));
109
+ port = new Promise((done, fail) => {
110
+ server.once("error", fail);
111
+ server.listen(0, "127.0.0.1", () => done(server.address().port));
112
+ });
113
+ server.unref();
114
+ serving.set(config.catalog, port);
115
+ return port;
116
+ }
117
+ function watch(config) {
118
+ if (watching.has(config.catalog)) return;
119
+ watching.add(config.catalog);
120
+ watchCatalog(config, () => {
121
+ try {
122
+ generate(config);
123
+ console.info(`prosebook: regenerated ${relative(config.root, config.out).replaceAll("\\", "/")}`);
124
+ } catch (e) {
125
+ console.error(describe(e));
126
+ }
127
+ });
128
+ }
129
+ /**
130
+ * A rule per generated module: matched by its file name, then by its path, which Turbopack gives from
131
+ * its own root, so only the end is known.
132
+ */
133
+ function rule(config, name, options, browser = false) {
134
+ const file = `${name}.${config.language === "ts" ? "ts" : "js"}`;
135
+ const path = { path: new RegExp(`(?:^|/)${escape(join(relative(config.root, config.out), file).replaceAll("\\", "/"))}$`) };
136
+ return [file, {
137
+ condition: browser ? { all: ["browser", path] } : path,
138
+ loaders: [{
139
+ loader: loaderPath,
140
+ options
141
+ }]
142
+ }];
143
+ }
144
+ /**
145
+ * In the browser: the index starts with `prepend`, the hot hooks and the inspector, there before any
146
+ * copy exists, and each locale updates the copies.
147
+ */
148
+ function hotRules(config, generated, prepend) {
149
+ const locales = [...generated.entries.keys()].map((locale) => rule(config, locale, { locale }, true));
150
+ return Object.fromEntries([rule(config, "index", { prepend }, true), ...locales]);
151
+ }
152
+ function prunedRules(config, generated, prune, keep) {
153
+ const { refs, unseen } = scanSources(config, prune === "strict");
154
+ if (unseen.length > 0) throw new Error(unseenMessage(unseen, config.root));
155
+ const kept = keeper(refs, keep);
156
+ return Object.fromEntries([...generated.entries].map(([locale, entries]) => rule(config, locale, { code: prunedModule(entries, kept) })));
157
+ }
158
+ export { withProsebook as default, withProsebook };
159
+
160
+ //# sourceMappingURL=next.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"next.js","names":[],"sources":["../src/next.ts"],"sourcesContent":["/**\n * `prosebook/next`: the catalog as part of a Next.js app.\n *\n * Generates when `next dev` or `next build` loads the config, and in dev again whenever a catalog or\n * translation file changes, so Fast Refresh picks up the new modules and a broken entry shows in the\n * terminal. Server components read the new text on their next render; in the browser, each locale\n * module hands its new messages to the copies holding it, which re-render in the locale and state\n * they were in. `next build` ships only the messages the code names: each locale module is handed to a\n * Turbopack loader (`next-loader.ts`) that returns it pruned. Turbopack loads a module before it has\n * seen the code that reads it, so the keys come from the source under `scan.dirs` (`tools/bundle.ts`),\n * read here once, and each pruned module travels to the loader as its options.\n *\n * In dev the browser's index also starts the inspector (`inspector.ts`). Its endpoints can't be a\n * route of the app without a file in it, so a server of their own answers them on a local port, and a\n * rewrite sends `/__prosebook/*` there; an entry opens through Next's own `/__nextjs_launch-editor`.\n *\n * Turbopack only: Next 16 fails a Turbopack build whose config has a `webpack` key, so none is added.\n */\nimport { createServer } from 'node:http';\nimport type { AddressInfo } from 'node:net';\nimport { join, relative, resolve } from 'node:path';\n\nimport type { LoaderOptions } from './next-loader.ts';\nimport {\n beside,\n HOT_SET,\n installInspector,\n keeper,\n prunedModule,\n scanSources,\n unseenMessage,\n watchCatalog,\n type Prune,\n} from './tools/bundle.ts';\nimport { CatalogError } from './tools/catalog.ts';\nimport { loadConfig, type ProsebookConfig, type ResolvedConfig } from './tools/config.ts';\nimport { inspectorEndpoints, nodeMiddleware, redirect } from './tools/endpoints.ts';\nimport { generate, type Generated } from './tools/generate.ts';\n\nexport interface ProsebookNextOptions {\n /** The config, inline. Without it, `prosebook.config.*` at `root` is read. */\n config?: ProsebookConfig;\n /** The folder the config's paths resolve from, and where its file is found: the Next app's when unset. */\n root?: string;\n /**\n * Ship only the messages the code names. `strict`, the default, also fails the build on a read\n * whose key it can't see, such as `t(item.key)`. `loose` prunes without that check; `false` ships\n * every message.\n */\n prune?: Prune;\n /** Keys a build always keeps, exactly or as `prefix.*`: for keys built at runtime from data. */\n keep?: string[];\n /** In dev, Alt-click a string on the page to open its entry in the editor. On by default. */\n inspect?: boolean;\n}\n\ntype Rules = { [glob: string]: unknown };\ntype Rewrite = { source: string; destination: string };\ntype Rewrites = Rewrite[] | { beforeFiles?: Rewrite[]; afterFiles?: Rewrite[]; fallback?: Rewrite[] };\ntype ConfigFunction<C> = (phase: string, context: { defaultConfig: C }) => C | Promise<C>;\n\nconst DEV = 'phase-development-server';\nconst BUILD = 'phase-production-build';\n\nconst loaderPath = beside(import.meta.url, 'next-loader');\nconst inspectorPath = beside(import.meta.url, 'inspector');\n\n// Next loads its config more than once in a process; one watcher and one inspector server per catalog\n// is enough.\nconst watching = new Set<string>();\nconst serving = new Map<string, Promise<number>>();\n\nconst describe = (e: unknown) => (e instanceof CatalogError ? e.message : e instanceof Error ? (e.stack ?? e.message) : String(e));\nconst escape = (s: string) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n/**\n * Wraps the Next config: `export default withProsebook({ ... })`. Takes the config as an object or as\n * the function Next calls with the phase, and returns the function.\n */\nexport function withProsebook<C extends object>(\n next: C | ConfigFunction<C> = {} as C,\n options: ProsebookNextOptions = {},\n): (phase: string, context: { defaultConfig: C }) => Promise<C> {\n return async (phase, context) => {\n const resolved = typeof next === 'function' ? await next(phase, context) : next;\n if (phase !== DEV && phase !== BUILD) return resolved;\n const config = await loadConfig(resolve(options.root ?? process.cwd()), options.config);\n if (config.language === 'ts' && config.importExtension === '.js')\n throw new Error(\"prosebook: Turbopack doesn't resolve `./en.js` to `en.ts`: set `importExtension: ''` in the prosebook config\");\n const generated = generate(config);\n if (phase === DEV) {\n watch(config);\n if (options.inspect === false) return withRules(resolved, hotRules(config, generated, HOT_SET));\n const base = `${(resolved as { basePath?: string }).basePath ?? ''}/`;\n // A relative import, which Turbopack resolves as it would the app's own.\n const path = relative(config.out, inspectorPath).replaceAll('\\\\', '/');\n const prepend = HOT_SET + installInspector(path.startsWith('.') ? path : `./${path}`, base);\n return withRewrite(withRules(resolved, hotRules(config, generated, prepend)), await serveInspector(config, base));\n }\n const prune = options.prune ?? 'strict';\n if (prune === false) return resolved;\n return withRules(resolved, prunedRules(config, generated, prune, options.keep));\n };\n}\n\nfunction withRules<C extends object>(resolved: C, rules: Rules): C {\n const turbopack = (resolved as { turbopack?: { rules?: Rules } }).turbopack;\n return { ...resolved, turbopack: { ...turbopack, rules: { ...turbopack?.rules, ...rules } } };\n}\n\n/** Sends `/__prosebook/*` to the inspector's server, before any of the app's own rewrites. */\nfunction withRewrite<C extends object>(resolved: C, port: number): C {\n const theirs = (resolved as { rewrites?: () => Promise<Rewrites> }).rewrites;\n const ours = [{ source: '/__prosebook/:path*', destination: `http://127.0.0.1:${port}/__prosebook/:path*` }];\n const rewrites = async (): Promise<Rewrites> => {\n const r = (await theirs?.()) ?? [];\n return Array.isArray(r) ? { beforeFiles: ours, afterFiles: r } : { ...r, beforeFiles: [...ours, ...(r.beforeFiles ?? [])] };\n };\n return { ...resolved, rewrites };\n}\n\n/**\n * Serves the inspector's endpoints on a free local port, for as long as the process lives. Next's\n * rewrite forwards the page's host, so an edit is still checked against the page's origin.\n */\nfunction serveInspector(config: ResolvedConfig, base: string): Promise<number> {\n let port = serving.get(config.catalog);\n if (port !== undefined) return port;\n const handle = inspectorEndpoints(config, {\n open: ({ file, line, column }) =>\n redirect(`${base}__nextjs_launch-editor?${new URLSearchParams({ file, line1: String(line), column1: String(column) })}`),\n });\n const middleware = nodeMiddleware(handle);\n const server = createServer((req, res) =>\n middleware(req, res, () => {\n res.statusCode = 404;\n res.end();\n }),\n );\n port = new Promise<number>((done, fail) => {\n server.once('error', fail);\n server.listen(0, '127.0.0.1', () => done((server.address() as AddressInfo).port));\n });\n server.unref();\n serving.set(config.catalog, port);\n return port;\n}\n\nfunction watch(config: ResolvedConfig): void {\n if (watching.has(config.catalog)) return;\n watching.add(config.catalog);\n watchCatalog(config, () => {\n try {\n generate(config);\n console.info(`prosebook: regenerated ${relative(config.root, config.out).replaceAll('\\\\', '/')}`);\n } catch (e) {\n console.error(describe(e));\n }\n });\n}\n\n/**\n * A rule per generated module: matched by its file name, then by its path, which Turbopack gives from\n * its own root, so only the end is known.\n */\nfunction rule(config: ResolvedConfig, name: string, options: LoaderOptions, browser = false): [string, unknown] {\n const file = `${name}.${config.language === 'ts' ? 'ts' : 'js'}`;\n const path = { path: new RegExp(`(?:^|/)${escape(join(relative(config.root, config.out), file).replaceAll('\\\\', '/'))}$`) };\n return [file, { condition: browser ? { all: ['browser', path] } : path, loaders: [{ loader: loaderPath, options }] }];\n}\n\n/**\n * In the browser: the index starts with `prepend`, the hot hooks and the inspector, there before any\n * copy exists, and each locale updates the copies.\n */\nfunction hotRules(config: ResolvedConfig, generated: Generated, prepend: string): Rules {\n const locales = [...generated.entries.keys()].map((locale) => rule(config, locale, { locale }, true));\n return Object.fromEntries([rule(config, 'index', { prepend }, true), ...locales]);\n}\n\nfunction prunedRules(config: ResolvedConfig, generated: Generated, prune: 'strict' | 'loose', keep?: string[]): Rules {\n const { refs, unseen } = scanSources(config, prune === 'strict');\n if (unseen.length > 0) throw new Error(unseenMessage(unseen, config.root));\n const kept = keeper(refs, keep);\n return Object.fromEntries([...generated.entries].map(([locale, entries]) => rule(config, locale, { code: prunedModule(entries, kept) })));\n}\n\nexport default withProsebook;\n"],"mappings":"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"}
@@ -1,16 +1,16 @@
1
- import { S as RichParam, b as RegisteredMessages, h as MessageShape, n as Copy, w as TagFn, x as RichKey, y as RegisteredLocale } from "../copy-DXnzXsJa.js";
1
+ import { S as RichParam, b as RegisteredMessages, h as MessageShape, n as Copy, w as TagFn, x as RichKey, y as RegisteredLocale } from "../copy-B3V_QRkT.js";
2
2
  import { ReactNode } from "react";
3
3
  type M = RegisteredMessages;
4
4
  type L = RegisteredLocale;
5
5
  /** `Copy`, with `rich` returning a `ReactNode`. */
6
- export interface ReactCopy extends Omit<Copy<M, L>, 'rich'> {
6
+ export interface ReactCopy extends Omit<Copy, 'rich'> {
7
7
  rich<K extends RichKey<M>>(this: void, key: K, ...values: RichParam<M[K], ReactNode>): ReactNode;
8
8
  }
9
9
  type Tags = {
10
10
  readonly [tag: string]: TagFn<ReactNode>;
11
11
  };
12
12
  export interface CopyProviderProps {
13
- copy: Copy<M, L> | Copy;
13
+ copy: Copy;
14
14
  /** Tag renderers every rich read under this provider gets, beside the copy's own. */
15
15
  tags?: Tags;
16
16
  children?: ReactNode;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/react/index.tsx"],"mappings":";;KAYK,IAAI;KACJ,IAAI;;iBAGQ,kBAAkB,KAAK,KAAK,GAAG;EAC9C,KAAK,UAAU,QAAQ,IAAI,YAAY,KAAK,MAAM,QAAQ,UAAU,EAAE,IAAI,aAAa;;KAKpF;YAAmB,cAAc,MAAM;;iBAE3B;EACf,MAAM,KAAK,GAAG,KAAK;;EAEnB,OAAO;EACP,WAAW;;wBAGG,eAAe,MAAM,MAAM,YAAY,oCAAiB,IAAA;;wBAMxD,WAAW;;wBAaX,aAAa;KA+CxB,WAAW,UAAU,2BAA2B,eAAe,KAAK;EACnE,SAAS,eAAe,KAAK,aAAa,MAAM;;EAChD,QAAQ,UAAU,GAAG;;YAEf,UAAU,UAAU,cAAc;EAAgB,IAAI;IAAM,WAAW,EAAE;;;;;wBAMrE,KAAK,UAAU,cAAc,YAAY,OAAO,UAAU,qBAAE,IAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/react/index.tsx"],"mappings":";;KAYK,IAAI;KACJ,IAAI;;iBAGQ,kBAAkB,KAAK;EACtC,KAAK,UAAU,QAAQ,IAAI,YAAY,KAAK,MAAM,QAAQ,UAAU,EAAE,IAAI,aAAa;;KAKpF;YAAmB,cAAc,MAAM;;iBAE3B;EACf,MAAM;;EAEN,OAAO;EACP,WAAW;;wBAGG,eAAe,MAAM,MAAM,YAAY,oCAAiB,IAAA;;wBAMxD,WAAW;;wBAaX,aAAa;KA+CxB,WAAW,UAAU,2BAA2B,eAAe,KAAK;EACnE,SAAS,eAAe,KAAK,aAAa,MAAM;;EAChD,QAAQ,UAAU,GAAG;;YAEf,UAAU,UAAU,cAAc;EAAgB,IAAI;IAAM,WAAW,EAAE;;;;;wBAMrE,KAAK,UAAU,cAAc,YAAY,OAAO,UAAU,qBAAE,IAAA"}