wysiwygido 0.3.0 → 1.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/CHANGELOG.md CHANGED
@@ -1,6 +1,26 @@
1
1
  # Changelog
2
2
 
3
- All notable changes to this project are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses [Semantic Versioning](https://semver.org/). Until 1.0, minor versions may contain breaking changes; they are listed under "Changed" or "Removed".
3
+ All notable changes to this project are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses [Semantic Versioning](https://semver.org/). Before 1.0.0 minor versions could contain breaking changes. From 1.0.0 on, only a major version may break the public API (see "Stability and versioning" in the README); changes are listed under "Changed" or "Removed".
4
+
5
+ ## 1.0.0
6
+
7
+ The first stable release. The public API (the exported names, the plugin contract, the stored document formats and the `--wy-*` colour variables) now follows semantic versioning, and tests guard it. New since 0.4.0: the data table and its stand-alone version.
8
+
9
+ - Added: **data table** (`DataTable` plugin): a block of records with typed columns (text, number, currency, date, yes/no, choice), search, column filters, multi-column sort, paging, cell editing, tick boxes, footer totals, custom toolbar buttons and row actions, right-click and heading menus, keyboard navigation and undo. Export to CSV, Excel (.xlsx, written without a library) and PDF; import from CSV and Excel; convert to and from a regular table; written to Word and Markdown as a table.
10
+ - Added: the data table on its own, with no editor: `createDataTable` and `<wysiwyg-data-table>` from the new entry point `wysiwygido/data-table` (it imports no package, checked by the build), with its own undo, themes, change events and right-click menu.
11
+ - Added: the data table's stored format is versioned (`v`). A table saved by a newer version is shown but not edited, and is kept exactly as it was.
12
+ - Added: rows of a data table may be objects by column key; `createFetchDataSource` and a `GET /api/orders` route in the example server.
13
+ - Added: tests that guard the public API (`tests/api-surface.json`) and the stored formats (`tests/formats.test.ts`); a "Stability and versioning" section in the README.
14
+ - Changed (data table names, before they were released): types are `DataTable*` (`DataTableOptions` is the plugin's options, `DataTableSettings` the table's own), `formatDataTableCell`, `queryDataTable`; `DataTableContext.editor` is `null` when the table stands alone.
15
+ - Added: data table extras: formula columns (own safe parser), highlight rules and data bars (also written to .xlsx and PDF), saved views, openable row details (`DataTable({ detail })`), column resizing by mouse or keyboard, row reordering by drag or Alt+Up/Down, and charts from the rows on screen.
16
+ - Fixed: a data table's heading did not stay in view while the table scrolled.
17
+ - Added: data table at scale: group by up to three columns with folding and per-group totals, virtual scrolling for long tables without paging, rows loaded page by page from a data source (`DataTable({ sources, onEdit })`, `options.source`), and import of Excel (.xlsx) files read without a library (`dataTableFromFile`).
18
+ - Added: data table (`DataTable` plugin): a block of records with typed columns, search, column filters, multi-column sort, paging, cell editing, selection, footer totals, custom toolbar buttons and row actions, right-click and heading menus, and keyboard navigation. Export to CSV, Excel (.xlsx, written without a library) and PDF; import from CSV; convert to and from a regular table. Written to Word and Markdown as a table.
19
+
20
+ ## 0.4.0
21
+
22
+ - Added: colour themes (`Themes` plugin): light, dark, sepia, ocean, forest, rose, solarized, midnight, nord and high-contrast, a View → Themes menu, `colorTheme` / `cycleColorTheme` / `clearColorTheme` commands, your own theme from a handful of colours, and `<wysiwyg-editor theme="sepia">`.
23
+ - Added: the editor emits a `theme` event when Dark Mode or Switch Background changes.
4
24
 
5
25
  ## 0.3.0
6
26
 
package/README.md CHANGED
@@ -11,7 +11,7 @@ Everything is opt-in plugins; the core is small. Install from npm (the first rel
11
11
 
12
12
  ```sh
13
13
  pnpm install
14
- pnpm dev # the demo gallery at http://localhost:5173 (39 examples)
14
+ pnpm dev # the demo gallery at http://localhost:5173 (49 examples)
15
15
  pnpm test # 196 tests (unit, accessibility with axe-core, server integration)
16
16
  pnpm build # library in dist/
17
17
  pnpm server # reference backend on :8787
@@ -332,6 +332,17 @@ A provider is any `(req) => Promise<string> | AsyncIterable<string>`; stream chu
332
332
  Model output is parsed as Markdown with raw HTML disabled, so it cannot inject markup. What the endpoint must return is described in
333
333
  [Endpoints your app provides](#endpoints-your-app-provides).
334
334
 
335
+ ## Stability and versioning
336
+
337
+ The package follows [semantic versioning](https://semver.org/). Since 1.0.0 only a major release may break the public API (before it, minor releases could). What counts as the public API:
338
+
339
+ - **Everything exported** from `wysiwygido` and its entry points (`/data-table`, `/docx`, `/pdf`, `/epub`, `/collab`, `/react`, `/vue`, `/svelte`, `/adapter`). The list of names is a test (`tests/api-surface.json`): removing or renaming one fails the build.
340
+ - **The plugin contract**: the `EditorPlugin` hooks, `editor.registerCommand` / `execute` / `on`, command names, and the events the editor emits.
341
+ - **What is stored in documents**: the HTML attributes (`data-data-table`, `data-chart`, `data-cover-page`…) and the JSON they hold. Each stored format is read by every later version; the data table's format carries a version number (`v`), and a table saved by a *newer* version than the one reading it is shown, kept untouched and not editable, so an older copy can never lose data. Golden files for these formats are tests (`tests/formats.test.ts`).
342
+ - **The CSS custom properties** (`--wy-*`) used for theming.
343
+
344
+ Not part of it: the `wy-*` class names and the exact DOM the editor builds, internal modules not exported from an entry point, and the exact wording of interface text and error messages. `defaultPlugins` may gain plugins in a minor release. Anything deprecated keeps working for at least one minor release and is listed in the changelog. Supported browsers are the current versions of Chrome, Edge, Firefox and Safari (all four are tested); the pure functions (`dataTableToCSV`, `dataTableToXlsx`, `queryDataTable`…) also run on Node 18+.
345
+
335
346
  ## Endpoints your app provides
336
347
 
337
348
  The editor calls your code at a few points. Each contract below is exercised by `server/examples/api.ts` (`pnpm server:examples`, port 8788),
@@ -347,6 +358,7 @@ Try it with the AI demo: `…/demo/ai.html?endpoint=http://127.0.0.1:8788/api/ai
347
358
  | `SpellCheck({ provider: createLanguageToolProvider(url) })` | `POST` form `text=…&language=…` | LanguageTool's JSON: `{ matches: [{ offset, length, message, replacements: [{ value }], rule: { id, issueType } }] }` |
348
359
  | `SpellCheck({ provider })` (your own) | `{ text, lang, signal }` | `[{ offset, length, message, replacements?: string[], rule?, kind?: 'spelling' \| 'grammar' \| 'style' }]`, offsets in characters of the text sent |
349
360
  | `Mentions({ search })` | the typed text | `[{ id, label }]` (may be a Promise) |
361
+ | `DataTable({ sources: { orders: createFetchDataSource(url) } })` | `GET url?page&pageSize&search&sort&filters` (`sort` and `filters` are JSON) | JSON `{ rows, total, totals? }`: one page, the number of matching rows, and footer totals over all of them. Try it with the data table demo: `…/demo/data-table.html?endpoint=http://127.0.0.1:8788/api/orders` |
350
362
  | `PdfEpub`, `Embeds`, `Offline` | nothing to your server | they run in the browser |
351
363
 
352
364
  **What an AI endpoint returns per `action`** (the full instruction is in `instruction`, pass it to the model as the system prompt and `text` as the user message). The reply is the text itself: no "Here is…", no quotes, no JSON wrapper.
@@ -469,6 +481,121 @@ tOGGLE cASE (`Ctrl/⌘+Alt+G`), snake_case (`Ctrl/⌘+Alt+S`), camelCase (`Ctrl/
469
481
  in the right-click menu. Upper/lower/toggle/title/sentence keep every bold, link and colour in place; snake/camel/kebab join the words into one
470
482
  identifier that takes the formatting of its first letter. From code: `editor.execute('changeCase', 'snake')` or the pure `convertCase(text, 'camel')`.
471
483
 
484
+ ## Data table
485
+
486
+ A table of records that people can search, sort, filter, page through, edit and export, in the middle of a document. It is a separate block from the ordinary table: the rows are stored as data, so sorting or filtering changes only what is shown, never the document.
487
+
488
+ ```ts
489
+ import { createEditor, defaultPlugins, DataTable } from 'wysiwygido';
490
+
491
+ const editor = createEditor({
492
+ element,
493
+ plugins: [...defaultPlugins, DataTable({
494
+ buttons: [{ id: 'ship', label: 'Ship', needsSelection: true, onClick: ({ selected }) => ship(selected) }],
495
+ rowActions: [{ label: 'View', onClick: (row, index) => open(row.id) }],
496
+ })],
497
+ });
498
+
499
+ editor.execute('insertDataTable', {
500
+ columns: [{ key: 'name', title: 'Product', type: 'text' }, { key: 'price', title: 'Price', type: 'currency', currency: 'USD', total: 'sum' }],
501
+ rows: [['Tea', 4.5], ['Cake', 6]],
502
+ options: { title: 'Menu', pageSize: 25, filters: true, selectable: true },
503
+ });
504
+ ```
505
+
506
+ - **Columns** are `text`, `number`, `currency`, `date`, `boolean` or `select`, each with its own editor, format, alignment and an optional footer total (`sum`, `avg`, `count`, `min`, `max`). Add, edit, move, hide and delete them from the heading menu.
507
+ - **Search and filter:** a search box, and an optional filter row. Text filters match "contains"; number and date filters take `>5`, `>=5`, `<5`, `5..10`, `=5`, `!=5`.
508
+ - **Sort** by clicking a heading (Shift+click for a second column). Empty values always come last.
509
+ - **Paging** with 10, 25, 50, 100 or all rows per page. Nothing is drawn beyond the page. With paging off, a table of more than about 200 rows draws only the rows in view and scrolls (20,000 rows stay smooth).
510
+ - **Group by** one to three columns from the heading menu (or `options.groupBy`). Each group has a heading with its row count and the footer totals of its columns, and can be folded. A page that starts inside a group repeats the heading.
511
+ - **Editing:** double-click or press Enter on a cell, Enter or Tab to save and move, Esc to cancel. Arrow keys move between cells. Every edit is one undo step.
512
+ - **Buttons:** your own toolbar buttons (`needsSelection` keeps one disabled until rows are ticked) and a button on every row.
513
+ - **Export:** CSV, Excel (.xlsx) and PDF from the Export menu, using what is shown (filtered, sorted, visible columns), or only the ticked rows. The .xlsx file is written by the editor itself, with no library: a bold frozen header, filter buttons, number, currency and date formats. CSV text that starts with `=`, `+`, `-` or `@` is quoted so a spreadsheet does not run it. PDF uses the print dialog (choose "Save as PDF"), so every script and font works.
514
+ - **Convert both ways:** `tableToDataTable` turns the table at the cursor into a data table (the first row names the columns, types are guessed from the values), and `dataTableToTable` turns the selected data table back into an ordinary table. Also in the right-click menu.
515
+ - **Look:** follows the colour theme. Options: striped rows, cell borders, row height, a fixed height with a sticky heading. The block is exported to Word as a real table, to Markdown as a table, and to HTML with a plain table inside.
516
+ - **Import:** `editor.execute('dataTableFromFile')` opens a file picker for CSV, TSV or Excel (.xlsx); `dataTableFromCSV(text)` and `dataTableFromExcel(file)` take the content directly. The first row names the columns and each column's type is guessed (dates stay dates). The .xlsx reader is also written here, with no library, and reads the first visible sheet.
517
+ - **Formula columns:** give a column a formula over the other columns of the same row: `[Price] * [Qty]`, `IF([Stock] < 5, "Reorder", "OK")`, `ROUND([Total] / [Count], 2)`. The results are calculated, never stored, and can be searched, filtered, sorted, totalled and exported like any column. Operators `+ - * / % ^ &` and comparisons; functions `IF AND OR NOT ROUND FLOOR CEIL ABS SQRT POWER MOD MIN MAX SUM AVERAGE COUNT LEN UPPER LOWER TRIM LEFT RIGHT CONCAT CONTAINS ISEMPTY TODAY YEAR MONTH DAY DAYS`. Errors show as `#DIV/0!`, `#REF!`, `#CYCLE!` and so on. The formula language is its own small parser (no `eval`): it can only calculate from its row.
518
+ - **Highlight rules and bars:** Options → Highlight rules colours cells (or whole rows) that match a condition written like a filter box (`>100`, `5..10`, `Overdue`, `empty`) in red, green, amber, blue, gray or bold, using the theme's colours. The colours also go into .xlsx and PDF files. A column can draw a bar in each cell, in proportion to the largest value.
519
+ - **Saved views:** Views → Save current view keeps the search, filters, sort, grouping, hidden columns and rows per page under a name; readers can apply a view too (they get the search, filters and sort).
520
+ - **Details:** `options.detail` adds an open button to each row. By default it lists every field, including hidden columns; `DataTable({ detail: (row) => element })` shows what you build (it may be async).
521
+ - **Resize and reorder:** drag a heading's right edge (or focus it and use the arrow keys; double-click resets). With `options.reorder`, drag the ⠿ handle or press Alt+Up / Alt+Down to move a row, while no search, sort or filter makes the order on screen different from the stored one.
522
+ - **Charts:** Options → Chart of these rows makes a bar, line or pie chart (the Charts plugin) from the rows on screen, right after the table.
523
+ - **Rows from a server:** set `options.source` to a name and give the plugin a function for it. The table then asks for one page at a time and stores no rows in the document.
524
+
525
+ ```ts
526
+ DataTable({
527
+ sources: {
528
+ orders: createFetchDataSource('/api/orders'), // GET /api/orders?page=0&pageSize=25&search=…&sort=[…]&filters={…} -> { rows, total, totals }
529
+ // or write your own: async ({ page, pageSize, search, filters, sort }) => ({ rows, total, totals: { amount } }) (rows as arrays or { columnKey: value })
530
+ },
531
+ onEdit: async ({ key, value, row }) => api.patch(row.id, { [key]: value }), // optional: without it the table is read-only
532
+ });
533
+ editor.execute('insertDataTable', { columns: [...], rows: [], options: { source: 'orders', pageSize: 25, filters: true } });
534
+ ```
535
+
536
+ Search is sent after a short pause, only the newest answer is shown, a failure shows its message with a "Try again" button, and Export asks the source for every matching row. Ticked rows are those on the current page. Groups show the rows of the current page.
537
+
538
+ The pure parts (`dataTableToCSV`, `dataTableToXlsx`, `dataTableToPrintHTML`, `queryDataTable`, `cleanDataTable`, `dataTableWithFormulas`, `parseXlsx`) are exported too, so a server can build the same files.
539
+
540
+ ### Without the editor
541
+
542
+ The same table works on its own: no editor, no ProseMirror. Import `wysiwygido/data-table` (a small entry point that pulls in nothing else; the build checks that) and the stylesheet.
543
+
544
+ ```ts
545
+ import { createDataTable } from 'wysiwygido/data-table';
546
+ import 'wysiwygido/style.css';
547
+
548
+ const app = createDataTable({
549
+ element: document.querySelector('#grid')!,
550
+ table: { columns: [{ key: 'name', title: 'Name' }, { key: 'qty', title: 'Qty', type: 'number', total: 'sum' }], rows: [{ name: 'Tea', qty: 3 }], options: { pageSize: 25, filters: true } },
551
+ theme: 'nord', // 'light' | 'dark' | 'auto' | a theme name | a theme object
552
+ onChange: (table) => save(table), // after every edit, new row, resized column…
553
+ buttons: [{ id: 'ship', label: 'Ship', needsSelection: true, onClick: ({ selected }) => ship(selected) }],
554
+ sources: { orders: createFetchDataSource('/api/orders') }, // for tables with options.source
555
+ });
556
+
557
+ app.getRows(); app.visibleRows(); app.selectedRows();
558
+ app.undo(); app.redo(); // its own history (Ctrl/⌘+Z and Ctrl+Y work too)
559
+ app.setTheme('dark'); app.setReadOnly(true); app.setTable(newTable);
560
+ app.export('xlsx'); app.download('csv');
561
+ app.destroy();
562
+ ```
563
+
564
+ Rows can be arrays in column order or objects by column key (objects are easier when some columns are calculated). Everything above (search, filters, sort, paging, groups, formulas, highlight rules, saved views, details, resizing, reordering, export, loading from a server) works the same way, and the table has its own right-click menu. As a custom element:
565
+
566
+ ```html
567
+ <script type="module">import { defineDataTableElement } from 'wysiwygido/data-table'; defineDataTableElement();</script>
568
+ <wysiwyg-data-table theme="nord" readonly>
569
+ <script type="application/json">{ "columns": [{ "title": "Language" }, { "title": "Year", "type": "number" }], "rows": [["C", 1972], ["TypeScript", 2012]] }</script>
570
+ </wysiwyg-data-table>
571
+ ```
572
+
573
+ Events `table-ready` and `table-change` (`detail.table`); the element's `table` property sets or reads the table.
574
+
575
+ ## Themes
576
+
577
+ Ten colour themes ship with the editor: `light`, `dark`, `sepia`, `ocean`, `forest`, `rose`, `solarized`, `midnight`, `nord` and `high-contrast`. A theme restyles the toolbar, ribbon, panels and the paper, and only inside the editor.
578
+
579
+ ```ts
580
+ import { createEditor, defaultPlugins, Themes } from 'wysiwygido';
581
+
582
+ createEditor({ element, plugins: [...defaultPlugins, Themes({ initial: 'sepia' })] });
583
+ editor.execute('colorTheme', 'midnight');
584
+ editor.execute('cycleColorTheme');
585
+ editor.execute('clearColorTheme'); // back to the stock look
586
+ ```
587
+
588
+ Pick one from **View → Themes** in the ribbon, or use `<wysiwyg-editor theme="nord">`. Dark Mode on its own returns to the stock look.
589
+
590
+ Your own theme is a handful of colours; hover, borders, selection and the rest are derived from them:
591
+
592
+ ```ts
593
+ Themes({ extra: [{ name: 'brand', label: 'Brand', palette: { mode: 'light', bg: '#fff8f0', surface: '#ffeede', fg: '#2a1a10',
594
+ muted: '#7a5a44', border: '#f0d6bf', accent: '#e8590c', workspace: '#f0d2b8', pageBg: '#fff', pageFg: '#2a1a10' } }] });
595
+ ```
596
+
597
+ `editor.extensions.themes` gives `list()`, `current()`, `register(theme)`, `apply(name)` and `clear()`. A theme can also set any `--wy-*` variable through `vars`.
598
+
472
599
  ## Cover page
473
600
 
474
601
  `Cover` (in `defaultPlugins`) adds a cover page: one picture that fills the whole first page, edge to edge, with the text starting on page 2. Use
package/dist/adapter.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as createEditor } from "./src-CW6adV6q.js";
1
+ import { t as createEditor } from "./src-ByAIGOkX.js";
2
2
  //#region src/adapter.ts
3
3
  /**
4
4
  * Framework-neutral binding for anything that needs a "value in, value out" control: Angular's `ControlValueAccessor`, Svelte, Solid,
package/dist/collab.js CHANGED
@@ -1,4 +1,4 @@
1
- import { n as avatar } from "./dialog-Dl0WPFKl.js";
1
+ import { n as avatar } from "./dialog-DVjlHRqv.js";
2
2
  import { keymap } from "prosemirror-keymap";
3
3
  import { Plugin } from "prosemirror-state";
4
4
  import { redoCommand, relativePositionToAbsolutePosition, undoCommand, yCursorPlugin, ySyncPlugin, ySyncPluginKey, yUndoPlugin, yUndoPluginKey } from "y-prosemirror";