@drghaliasri/butex 5.6.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -264,6 +264,47 @@ Figures and tables always export as centered `figure` / `table` floats. Optional
264
264
 
265
265
  V2 exports Arabic TeX by default for equations saved from the embedded editor. Imported raw-only math is preserved as raw source and marked non-editable until a structured equation object is attached.
266
266
 
267
+ ### Headless document commands
268
+
269
+ Canonical `Document2Json` exports include a stable `id` on every block. Legacy input without IDs remains accepted and receives IDs on its next import/export normalization. Use the pure command and outline helpers for server-side session transforms:
270
+
271
+ ```ts
272
+ import {
273
+ applyDocument2Command,
274
+ document2Outline,
275
+ fromDocumentJson2,
276
+ toDocumentJson2,
277
+ } from '@drghaliasri/butex/document2';
278
+
279
+ const canonical = toDocumentJson2(fromDocumentJson2(legacyDocument));
280
+ const outline = document2Outline(canonical);
281
+ const updated = applyDocument2Command(canonical, {
282
+ op: 'insert_text_block',
283
+ kind: 'paragraph',
284
+ text: 'New session text',
285
+ anchor: { end: true },
286
+ });
287
+ ```
288
+
289
+ Commands address top-level blocks only. Insertions require either `{ end: true }` or `{ after_block_id }`; missing anchors fail explicitly. Whole-block text replacement rejects formatted text, citations, references, and math so an agent cannot silently discard structured inline content.
290
+
291
+ ### Document worker CLI
292
+
293
+ Node 20+ can run the same transforms through the isolated `butex-document2` executable. One-shot mode reads one request from stdin and writes one response to stdout:
294
+
295
+ ```bash
296
+ printf '%s' '{"action":"normalize","document":{"node_type":"DocumentObject","blocks":[]}}' | butex-document2
297
+ ```
298
+
299
+ Railway HTTP mode is stateless and requires a service token:
300
+
301
+ ```bash
302
+ BUTEX_WORKER_TOKEN=replace-me PORT=3000 \
303
+ butex-document2 --serve --host 0.0.0.0
304
+ ```
305
+
306
+ It exposes `GET /health` plus authenticated `POST /v1/document2/normalize`, `/v1/document2/outline`, and `/v1/document2/commands`. The worker accepts document JSON and returns transformed JSON only. FastAPI remains responsible for user authentication, article/session lookup, revisions, idempotency, S3 persistence, and MCP policy; browsers and agents must never call the worker directly.
307
+
267
308
  ### Document JSON contract (`value` + `math_objects`)
268
309
 
269
310
  Both document layers import the same shape. A text field holds `value` (the full string, **with math delimiters kept in place**) and an optional parallel `math_objects` array that supplies the structured equation AST for each math span found in that string.
@@ -393,6 +434,49 @@ onDocumentChange={(node) => {
393
434
 
394
435
  Do not call `JSON.stringify(documentNode)` directly. JSON serialization removes `CharNode`, `CommandNode`, and other equation prototypes: the saved TeX source can still render in preview, but the structured equation cannot reopen in the editor. Also do not feed every change callback back into `initialDocument`; that prop is for initial or externally loaded data, and changing it resets the editor state and undo history. Keep the current save snapshot in a ref or separate state that is not passed back as `initialDocument`. Change callbacks run during editing, so debounce network autosaves or save explicitly.
395
436
 
437
+ ##### Host-driven figure insert
438
+
439
+ After an external upload (for example S3), insert a figure on the **live** editor without remounting or rewriting `initialDocument`:
440
+
441
+ ```tsx
442
+ 'use client';
443
+
444
+ import { useRef } from 'react';
445
+ import {
446
+ ButexDocumentEditor2,
447
+ type ImageAssetRef,
448
+ type ButexDocumentEditor2Ref,
449
+ } from '@drghaliasri/butex/react-document2';
450
+
451
+ export function ArticleEditorWithUpload() {
452
+ const editorRef = useRef<ButexDocumentEditor2Ref>(null);
453
+
454
+ async function onFileUploaded(uuid: string) {
455
+ // Sets asset_id and \\includegraphics{assets/<uuid>.jpg} after the focused block.
456
+ // Do not call addDocument2ImageBlock outside the component or remount with key++.
457
+ editorRef.current?.insertImageBlock({ assetId: `assets/${uuid}.jpg` } satisfies ImageAssetRef);
458
+ }
459
+
460
+ return (
461
+ <ButexDocumentEditor2
462
+ ref={editorRef}
463
+ resolveImageUrl={({ assetId, value }) =>
464
+ assetId ? `https://cdn.example/${assetId}` : value
465
+ }
466
+ onRequestImagePick={async ({ current }) => {
467
+ /* open host asset picker; return { assetId, value?, label?, thumbUrl? } or null */
468
+ return current;
469
+ }}
470
+ onDocumentJsonChange={(json) => {
471
+ /* persist json */
472
+ }}
473
+ />
474
+ );
475
+ }
476
+ ```
477
+
478
+ `insertImageBlock` matches the toolbar figure button (focus-aware insert + undo snapshot). Pass `{ assetId, value?, label?, thumbUrl? }` to set both live `assetId` and wire `asset_id`; passing a non-empty string remains supported and treats that string as both `value` and `asset_id`. Use `updateImageBlockAsset(blockId, ref)` to set a host asset on an existing image block, `updateImageBlockValue(blockId, value)` to change only the path value while preserving any existing asset identity, and `getDocumentJson()` to read the canonical wire JSON without host-side AST mutation. BuTeX never uploads image bytes; hosts provide `resolveImageUrl`, optionally `onRequestImagePick`, `listImageAssets`, or `renderImageBlockEditor` for picker/inventory UI.
479
+
396
480
  Host apps still register BuTeX with MathJax before preview rendering. `uiLocale` selects Arabic (`"ar"`, the default) or English (`"en"`) document-editor chrome and is passed to the embedded equation editor. `documentDirection` independently controls prose inputs and preview flow without creating a second document tree. `equationSide` independently controls whether structured equations open, render, and save from the `"english"` or `"arabic"` side. Set `editableEquations={false}` to show math islands without equation insertion, deletion, or editor access. Set `previewOnly={true}` to render only the read-only document preview with no toolbar, editor panel, or equation drawer. Optional `\includegraphics` `asset_id` is preserved on import; pass `resolveImageUrl={({ assetId, value }) => …})` so preview can load S3/CDN/local assets while plain `value` URLs keep working without a resolver. Document JSON may include root `meta` (`title`, `authors`, structured Hijri `date` `{ day, month, year }` with Arabic month names such as `"محرم"`, `abstract`) plus a `references` catalog (`key`, `authors`, `title`, `year`, `url`, `venue`) and `\cite{key1,key2}` spans in text values. Pass `documentMeta` to seed missing meta fields from the host; JSON keys win when present. The editor always centers a fixed basmala line before the title block and a closing ḥamdala after the body (preview + LaTeX). Hijri date uses day/month/year dropdowns (no calendar conversion; default year 1448). The editor shows numeric cite chips and a bibliography block. RTL preview/chips render reversed labels such as `[٣،٢،١]` (Arabic comma); LTR stays `[1, 2, 3]`. Pass `digitForm` (`western` / `arabicIndic` / `persianIndic`, defaulting from `documentDirection`) or use the toolbar digit control. LaTeX export keeps logical `\cite{…}` key order for XeLaTeX; digit shaping and bidi display in PDF belong in the host preamble (`bidi`/polyglossia + font `Mapping`, see `references/commands.py`). Range compression (`[3–7]`) is not implemented yet. Raw-only equations keep their original source because the browser does not parse raw LaTeX into equation ASTs. Document editor v2 defaults to **`tex-svg.js`**; use `tex-chtml.js` only if you pass `mathOutput="chtml"`.
397
481
 
398
482
  ### Styling/theming contract