@sheetpane/review 0.0.0-stage → 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.
- package/LICENSE +21 -0
- package/README.md +125 -2
- package/dist/fills.d.ts +64 -0
- package/dist/fills.d.ts.map +1 -0
- package/dist/fills.js +233 -0
- package/dist/fills.js.map +1 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +87 -0
- package/dist/index.js.map +1 -0
- package/dist/rows.d.ts +49 -0
- package/dist/rows.d.ts.map +1 -0
- package/dist/rows.js +79 -0
- package/dist/rows.js.map +1 -0
- package/dist/sheet.d.ts +74 -0
- package/dist/sheet.d.ts.map +1 -0
- package/dist/sheet.js +181 -0
- package/dist/sheet.js.map +1 -0
- package/dist/write.d.ts +53 -0
- package/dist/write.d.ts.map +1 -0
- package/dist/write.js +99 -0
- package/dist/write.js.map +1 -0
- package/dist/xml.d.ts +23 -0
- package/dist/xml.d.ts.map +1 -0
- package/dist/xml.js +41 -0
- package/dist/xml.js.map +1 -0
- package/package.json +45 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Arpit Arya
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,126 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @sheetpane/review
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**The review written into the download as a sheet of its own. Every error is listed on a *Review* sheet with a link to its cell. Optionally, each flagged cell is filled by severity, so a reviewer can work the list in Excel, fix the file and upload it again.**
|
|
4
|
+
|
|
5
|
+
A plugin for `@sheetpane/grid`. It uses one hook, `exportPatch`, on the
|
|
6
|
+
in-memory workbook, so it works for `<sheetpane-excel>` and for a store with
|
|
7
|
+
no page. `@sheetpane/grid` is a peer and `@sheetpane/export` is a dependency.
|
|
8
|
+
Versioned in lockstep with them. There is no `/ui` entry: the plugin adds two
|
|
9
|
+
items to the Download menu and nothing else. Everything is written into the
|
|
10
|
+
bytes the browser saves. Nothing is sent anywhere (SR-LAW-11).
|
|
11
|
+
|
|
12
|
+
## Use
|
|
13
|
+
|
|
14
|
+
```html
|
|
15
|
+
<script type="module">
|
|
16
|
+
import '@sheetpane/grid';
|
|
17
|
+
import '@sheetpane/review'; // registers "review"
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<sheetpane-excel pane="q3" plugins="review" src="/files/holdings.xlsx" mode="edit" author="Arpit"></sheetpane-excel>
|
|
21
|
+
<sheetpane-errors pane="q3"></sheetpane-errors>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The Download menu gains **Add a Review sheet** (on by default) and **Colour
|
|
25
|
+
flagged cells** (off by default). Each one applies to the next download.
|
|
26
|
+
|
|
27
|
+
Without a page:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { createWorkbook } from '@sheetpane/grid';
|
|
31
|
+
import { review } from '@sheetpane/review';
|
|
32
|
+
|
|
33
|
+
const plugin = review({ fill: true }); // colour flagged cells by default
|
|
34
|
+
const store = createWorkbook({ plugins: [plugin] });
|
|
35
|
+
await store.load(bytes, 'holdings.xlsx');
|
|
36
|
+
const { blob } = await store.exportFile(); // the file, plus a Review sheet
|
|
37
|
+
plugin.lastExport; // { sheet, listed, counts, filled, skipped }
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Options
|
|
41
|
+
|
|
42
|
+
| option | default | what |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `sheet` | `true` | Add the Review sheet. `export({ review: false })` leaves it out of one export. |
|
|
45
|
+
| `fill` | `false` | Colour each flagged cell. `export({ reviewFills: true })` colours one export. |
|
|
46
|
+
| `sheetName` | `'Review'` | The sheet's name. Characters Excel forbids are dropped, and the name is cut to 31 characters. |
|
|
47
|
+
| `severities` | `['error', 'warning', 'note']` | The severities listed and coloured. Results such as `pass` and `skipped` are written only when you name them. `export({ reviewSeverities })` overrides it for one export. |
|
|
48
|
+
| `layers` | every layer but `compare` | The layers listed and coloured. An error with no `layer` belongs to `review`. `@sheetpane/compare`'s `compare` layer is a comparison, not the review: it is listed only when named, as `@sheetpane/comments` writes it. `export({ reviewLayers })` overrides it for one export. |
|
|
49
|
+
| `fills` | pale red, yellow, blue | A fill per severity, as `#RRGGBB`, `RRGGBB` or ARGB. `null` turns a severity's fill off. A value that is not a colour throws SP-5005. |
|
|
50
|
+
|
|
51
|
+
## The Review sheet
|
|
52
|
+
|
|
53
|
+
One row per error, below a header row that stays frozen. The workbook's own
|
|
54
|
+
errors come first. Then, for each sheet in the file's order, its whole-sheet
|
|
55
|
+
errors, then its cells from top to bottom and left to right.
|
|
56
|
+
|
|
57
|
+
| column | value |
|
|
58
|
+
|---|---|
|
|
59
|
+
| ID | `ERR-0003` |
|
|
60
|
+
| Sheet | the sheet's name; empty for an error about the workbook |
|
|
61
|
+
| Cell | `I6` or `A14:J14`, as a link that opens the cell in Excel. A whole-sheet error reads *Whole sheet* and links to its A1. A workbook error reads *Whole workbook* and has no link. |
|
|
62
|
+
| Severity · Type | `Error` · `Wrong value` |
|
|
63
|
+
| Found · Expected · Message · Author | as in the review |
|
|
64
|
+
| Status | `Open`, `Resolved` or `Accepted` |
|
|
65
|
+
| Accepted · Accepted by · Accepted at | for an accepted error: *Accepted: \<reason\>*, who accepted it and when (UTC) |
|
|
66
|
+
|
|
67
|
+
- **A user's own sheet is never overwritten.** If the file already has a
|
|
68
|
+
sheet called *Review* (in any case), the new one is *Review (2)*.
|
|
69
|
+
- **The file's own comments are not listed.** These are errors with `author:
|
|
70
|
+
'file'`, which came from the file in the first place.
|
|
71
|
+
- **Every cell is an inline string.** `sharedStrings.xml` is never touched,
|
|
72
|
+
and the links are internal hyperlinks, which need no relationship.
|
|
73
|
+
- **No font is written** (SR-LAW-5). The header row is marked with a grey
|
|
74
|
+
fill instead of bold text.
|
|
75
|
+
|
|
76
|
+
## Uploading an annotated file again
|
|
77
|
+
|
|
78
|
+
A hidden, workbook-scoped defined name, `_sheetpane_review`, marks the sheet.
|
|
79
|
+
`reviewSheetOf(book)` reads that name, so a host can leave the sheet out:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { read } from '@sheetpane/import';
|
|
83
|
+
import { reviewSheetOf } from '@sheetpane/review';
|
|
84
|
+
|
|
85
|
+
const book = await read(bytes, { name: 'holdings.xlsx' });
|
|
86
|
+
const skip = reviewSheetOf(book); // 'Review', or undefined
|
|
87
|
+
const data = book.sheets.filter((s) => s.name !== skip);
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
When a file that already has a Review sheet is exported again, the plugin
|
|
91
|
+
rewrites that sheet in place rather than adding a second one. There is one
|
|
92
|
+
exception: if someone gave the sheet parts of its own in Excel, such as
|
|
93
|
+
comments or a drawing, it is left alone and a new sheet is added.
|
|
94
|
+
|
|
95
|
+
## Colouring flagged cells
|
|
96
|
+
|
|
97
|
+
Each **open** error at a cell has its cells filled by severity. **Accepted**
|
|
98
|
+
and **resolved** errors are not problems, so they are never filled.
|
|
99
|
+
Whole-sheet and workbook errors are listed but not coloured. They appear in
|
|
100
|
+
`lastExport.skipped` as `no-cell`.
|
|
101
|
+
|
|
102
|
+
- **The cell's own format stays.** Its `xf` is cloned with a solid fill and
|
|
103
|
+
the cell points at the clone, so the number format, font, borders and
|
|
104
|
+
alignment are kept. One clone is made per format and colour. Existing
|
|
105
|
+
formats and fills are never edited, only added to.
|
|
106
|
+
- **Nothing else changes.** Only the `s` of a flagged cell is written. Every
|
|
107
|
+
other row and cell keeps its exact bytes. A flagged cell the file does not
|
|
108
|
+
have is added empty, styled from its row or column.
|
|
109
|
+
- **Two errors on one cell:** the worse severity's fill wins.
|
|
110
|
+
- **A range over 10,000 cells** colours only the cells the file already has
|
|
111
|
+
in it (`range-capped`, SP-5003).
|
|
112
|
+
- **No styles part:** nothing is coloured (`no-styles`, SP-5004), and the
|
|
113
|
+
sheet is still written.
|
|
114
|
+
|
|
115
|
+
## What it does not do
|
|
116
|
+
|
|
117
|
+
- It writes into an `.xlsx` download of an `.xlsx`. A CSV, or a CSV converted
|
|
118
|
+
to `.xlsx`, carries no plugin parts.
|
|
119
|
+
- An empty review writes nothing.
|
|
120
|
+
- Whether Excel opens the file without a repair prompt and follows each link
|
|
121
|
+
to its cell is checked by the decider on real Excel (W-30). These tests
|
|
122
|
+
check the parts sheetpane writes and reads, not Excel itself.
|
|
123
|
+
|
|
124
|
+
Spec: [SR-REVIEW](../../records/0123_REVIEW.md). MIT.
|
|
125
|
+
|
|
126
|
+
**Using an AI coding agent?** `npx sheetpane-skills install` puts sheetpane's skills for Claude Code, Kiro and Codex into your repository.
|
package/dist/fills.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type RangePos } from '@sheetpane/export';
|
|
2
|
+
/** The fill each problem severity gets by default (ARGB): Excel's *Bad*, *Neutral* and a pale blue. */
|
|
3
|
+
export declare const DEFAULT_FILLS: Readonly<Record<string, string>>;
|
|
4
|
+
/** The Review sheet's header fill (ARGB): a light grey. No font is written (SR-LAW-5). */
|
|
5
|
+
export declare const HEADER_FILL = "FFD9D9D9";
|
|
6
|
+
/** An error's range larger than this many cells colours only the cells the file already has in it (SP-5003). */
|
|
7
|
+
export declare const MAX_FILL_CELLS = 10000;
|
|
8
|
+
/**
|
|
9
|
+
* A colour as ARGB: `#C00000`, `C00000` or `FFC00000` → `FFC00000`.
|
|
10
|
+
*
|
|
11
|
+
* @param c - The colour.
|
|
12
|
+
* @returns The ARGB form, or undefined when it is not a hex colour.
|
|
13
|
+
*/
|
|
14
|
+
export declare function argb(c: string): string | undefined;
|
|
15
|
+
/** The workbook's style tables, extended in memory: fills and cell formats are only ever appended. */
|
|
16
|
+
export declare class StyleBook {
|
|
17
|
+
private readonly xml;
|
|
18
|
+
private readonly P;
|
|
19
|
+
private readonly xfs;
|
|
20
|
+
private readonly fillCount;
|
|
21
|
+
private readonly newFills;
|
|
22
|
+
private readonly newXfs;
|
|
23
|
+
private readonly fillOf;
|
|
24
|
+
private readonly xfOf;
|
|
25
|
+
constructor(xml: string);
|
|
26
|
+
/** Whether anything was added. */
|
|
27
|
+
get changed(): boolean;
|
|
28
|
+
/** The id of a solid fill in this colour, added once. */
|
|
29
|
+
fill(color: string): number;
|
|
30
|
+
/**
|
|
31
|
+
* A cell format that is format `s` with a solid fill — cloned once per format and colour.
|
|
32
|
+
*
|
|
33
|
+
* @param s - The cell's style index (a missing or unknown one is format 0).
|
|
34
|
+
* @param color - ARGB.
|
|
35
|
+
* @returns The new format's index.
|
|
36
|
+
*/
|
|
37
|
+
clone(s: number, color: string): number;
|
|
38
|
+
/** The styles part with the new fills and formats appended and the counts set. */
|
|
39
|
+
toXml(): string;
|
|
40
|
+
}
|
|
41
|
+
/** What to colour on one sheet: whole cells by zero-based row and column, and ranges too large to fill whole. */
|
|
42
|
+
export interface FillPlan {
|
|
43
|
+
/** row → column → severity. */
|
|
44
|
+
cells: Map<number, Map<number, string>>;
|
|
45
|
+
/** Over {@link MAX_FILL_CELLS}: only the cells the file has inside are coloured. */
|
|
46
|
+
wide: {
|
|
47
|
+
range: RangePos;
|
|
48
|
+
severity: string;
|
|
49
|
+
}[];
|
|
50
|
+
}
|
|
51
|
+
/** Adds a range to a plan; the worse severity wins on a cell. Returns false when the range was too large to fill whole. */
|
|
52
|
+
export declare function planRange(plan: FillPlan, r: RangePos, severity: string, rank: (s: string) => number): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Colours one sheet's flagged cells. Rows and cells not flagged are copied unchanged; a flagged cell keeps everything
|
|
55
|
+
* but its `s`; a flagged cell the file lacks is added empty, styled from its row (when `customFormat`) or column.
|
|
56
|
+
*
|
|
57
|
+
* @param xml - The worksheet part.
|
|
58
|
+
* @param plan - What to colour.
|
|
59
|
+
* @param rank - A severity's rank: lower is worse, and wins on a cell.
|
|
60
|
+
* @param styleFor - The style index for a cell of style `s` coloured for `severity`.
|
|
61
|
+
* @returns The worksheet part, coloured.
|
|
62
|
+
*/
|
|
63
|
+
export declare function fillSheet(xml: string, plan: FillPlan, rank: (s: string) => number, styleFor: (s: number, severity: string) => number): string;
|
|
64
|
+
//# sourceMappingURL=fills.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fills.d.ts","sourceRoot":"","sources":["../src/fills.ts"],"names":[],"mappings":"AAKA,OAAO,EAAkD,KAAK,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAGlG,uGAAuG;AACvG,eAAO,MAAM,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAgE,CAAC;AAE5H,0FAA0F;AAC1F,eAAO,MAAM,WAAW,aAAa,CAAC;AAEtC,gHAAgH;AAChH,eAAO,MAAM,cAAc,QAAS,CAAC;AAErC;;;;;GAKG;AACH,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGlD;AAID,sGAAsG;AACtG,qBAAa,SAAS;IASR,OAAO,CAAC,QAAQ,CAAC,GAAG;IARhC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAW;IAC/B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;IACzC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6B;IACpD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA6B;IAElD,YAA6B,GAAG,EAAE,MAAM,EAOvC;IAED,kCAAkC;IAClC,IAAI,OAAO,IAAI,OAAO,CAAmC;IAEzD,yDAAyD;IACzD,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAS1B;IAED;;;;;;OAMG;IACH,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAWtC;IAED,kFAAkF;IAClF,KAAK,IAAI,MAAM,CAmBd;CACF;AAED,iHAAiH;AACjH,MAAM,WAAW,QAAQ;IACvB,+BAA+B;IAC/B,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACxC,oFAAoF;IACpF,IAAI,EAAE;QAAE,KAAK,EAAE,QAAQ,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC/C;AAED,2HAA2H;AAC3H,wBAAgB,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAO7G;AAYD;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAiF7I"}
|
package/dist/fills.js
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
// Colouring flagged cells (W-59 DoD 3). Each flagged cell's own style (`xf`) is
|
|
2
|
+
// cloned with a solid fill and the cell points at the clone, so its number
|
|
3
|
+
// format, font, borders and alignment stay. Only the `s` of a flagged <c> is
|
|
4
|
+
// changed (or a styled empty <c> added where the file has none); every other row
|
|
5
|
+
// and cell is copied as the file has it, byte for byte.
|
|
6
|
+
import { cellRef, getAttr, parseRange, setAttr, spError } from '@sheetpane/export';
|
|
7
|
+
import { elementRe, openTag, prefixOf } from './xml.js';
|
|
8
|
+
/** The fill each problem severity gets by default (ARGB): Excel's *Bad*, *Neutral* and a pale blue. */
|
|
9
|
+
export const DEFAULT_FILLS = { error: 'FFFFC7CE', warning: 'FFFFEB9C', note: 'FFDDEBF7' };
|
|
10
|
+
/** The Review sheet's header fill (ARGB): a light grey. No font is written (SR-LAW-5). */
|
|
11
|
+
export const HEADER_FILL = 'FFD9D9D9';
|
|
12
|
+
/** An error's range larger than this many cells colours only the cells the file already has in it (SP-5003). */
|
|
13
|
+
export const MAX_FILL_CELLS = 10_000;
|
|
14
|
+
/**
|
|
15
|
+
* A colour as ARGB: `#C00000`, `C00000` or `FFC00000` → `FFC00000`.
|
|
16
|
+
*
|
|
17
|
+
* @param c - The colour.
|
|
18
|
+
* @returns The ARGB form, or undefined when it is not a hex colour.
|
|
19
|
+
*/
|
|
20
|
+
export function argb(c) {
|
|
21
|
+
const h = c.trim().replace(/^#/, '').toUpperCase();
|
|
22
|
+
return /^[0-9A-F]{6}$/.test(h) ? `FF${h}` : /^[0-9A-F]{8}$/.test(h) ? h : undefined;
|
|
23
|
+
}
|
|
24
|
+
const block = (name) => new RegExp(`<(\\w+:)?${name}\\b([^>]*?)(?:/>|>([\\s\\S]*?)</\\1${name}>)`);
|
|
25
|
+
/** The workbook's style tables, extended in memory: fills and cell formats are only ever appended. */
|
|
26
|
+
export class StyleBook {
|
|
27
|
+
xml;
|
|
28
|
+
P;
|
|
29
|
+
xfs;
|
|
30
|
+
fillCount;
|
|
31
|
+
newFills = [];
|
|
32
|
+
newXfs = [];
|
|
33
|
+
fillOf = new Map();
|
|
34
|
+
xfOf = new Map();
|
|
35
|
+
constructor(xml) {
|
|
36
|
+
this.xml = xml;
|
|
37
|
+
this.P = prefixOf(xml, 'styleSheet');
|
|
38
|
+
const xfs = block('cellXfs').exec(xml);
|
|
39
|
+
if (!/<(?:\w+:)?styleSheet\b/.test(xml) || !xfs)
|
|
40
|
+
throw spError('SP-5002', 'sheetpane review: the styles part has no <cellXfs>');
|
|
41
|
+
this.xfs = [...(xfs[3] ?? '').matchAll(elementRe('xf'))].map((m) => m[0]);
|
|
42
|
+
const fills = block('fills').exec(xml);
|
|
43
|
+
this.fillCount = fills ? [...(fills[3] ?? '').matchAll(elementRe('fill'))].length : 2; // a missing <fills> is written with Excel's two required fills
|
|
44
|
+
}
|
|
45
|
+
/** Whether anything was added. */
|
|
46
|
+
get changed() { return this.newXfs.length > 0; }
|
|
47
|
+
/** The id of a solid fill in this colour, added once. */
|
|
48
|
+
fill(color) {
|
|
49
|
+
let id = this.fillOf.get(color);
|
|
50
|
+
if (id === undefined) {
|
|
51
|
+
const P = this.P;
|
|
52
|
+
id = this.fillCount + this.newFills.length;
|
|
53
|
+
this.newFills.push(`<${P}fill><${P}patternFill patternType="solid"><${P}fgColor rgb="${color}"/><${P}bgColor indexed="64"/></${P}patternFill></${P}fill>`);
|
|
54
|
+
this.fillOf.set(color, id);
|
|
55
|
+
}
|
|
56
|
+
return id;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A cell format that is format `s` with a solid fill — cloned once per format and colour.
|
|
60
|
+
*
|
|
61
|
+
* @param s - The cell's style index (a missing or unknown one is format 0).
|
|
62
|
+
* @param color - ARGB.
|
|
63
|
+
* @returns The new format's index.
|
|
64
|
+
*/
|
|
65
|
+
clone(s, color) {
|
|
66
|
+
const k = `${s}|${color}`;
|
|
67
|
+
let id = this.xfOf.get(k);
|
|
68
|
+
if (id === undefined) {
|
|
69
|
+
const base = this.xfs[s] ?? this.xfs[0] ?? `<${this.P}xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>`;
|
|
70
|
+
const tag = openTag(base);
|
|
71
|
+
id = this.xfs.length + this.newXfs.length;
|
|
72
|
+
this.newXfs.push(base.replace(tag, setAttr(setAttr(tag, 'fillId', String(this.fill(color))), 'applyFill', '1')));
|
|
73
|
+
this.xfOf.set(k, id);
|
|
74
|
+
}
|
|
75
|
+
return id;
|
|
76
|
+
}
|
|
77
|
+
/** The styles part with the new fills and formats appended and the counts set. */
|
|
78
|
+
toXml() {
|
|
79
|
+
const P = this.P;
|
|
80
|
+
let out = this.xml;
|
|
81
|
+
const grow = (name, items, count, missing) => {
|
|
82
|
+
const m = block(name).exec(out);
|
|
83
|
+
if (!m) {
|
|
84
|
+
out = missing();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const open = setAttr(openTag(m[0]).replace(/\s*\/>$/, '>'), 'count', String(count));
|
|
88
|
+
out = out.replace(m[0], `${open}${m[3] ?? ''}${items}</${m[1] ?? ''}${name}>`);
|
|
89
|
+
};
|
|
90
|
+
grow('cellXfs', this.newXfs.join(''), this.xfs.length + this.newXfs.length, () => out);
|
|
91
|
+
if (this.newFills.length) {
|
|
92
|
+
grow('fills', this.newFills.join(''), this.fillCount + this.newFills.length, () => {
|
|
93
|
+
// CT_Stylesheet: numFmts, fonts, fills, borders, cellStyleXfs, cellXfs …
|
|
94
|
+
const at = out.search(/<(?:\w+:)?(?:borders|cellStyleXfs|cellXfs)\b/);
|
|
95
|
+
const fills = `<${P}fills count="${this.fillCount + this.newFills.length}"><${P}fill><${P}patternFill patternType="none"/></${P}fill><${P}fill><${P}patternFill patternType="gray125"/></${P}fill>${this.newFills.join('')}</${P}fills>`;
|
|
96
|
+
return out.slice(0, at) + fills + out.slice(at);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/** Adds a range to a plan; the worse severity wins on a cell. Returns false when the range was too large to fill whole. */
|
|
103
|
+
export function planRange(plan, r, severity, rank) {
|
|
104
|
+
if ((r.bottom - r.top + 1) * (r.right - r.left + 1) > MAX_FILL_CELLS) {
|
|
105
|
+
plan.wide.push({ range: r, severity });
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
for (let row = r.top; row <= r.bottom; row++) {
|
|
109
|
+
const cols = plan.cells.get(row) ?? plan.cells.set(row, new Map()).get(row);
|
|
110
|
+
for (let col = r.left; col <= r.right; col++) {
|
|
111
|
+
const was = cols.get(col);
|
|
112
|
+
if (was === undefined || rank(severity) < rank(was))
|
|
113
|
+
cols.set(col, severity);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
/** `<col min max style>` → the style a column gives a cell the file does not have. */
|
|
119
|
+
function colStyles(xml) {
|
|
120
|
+
const cols = block('cols').exec(xml)?.[3] ?? '';
|
|
121
|
+
const spans = [...cols.matchAll(/<(?:\w+:)?col\b[^>]*>/g)].map((m) => ({ min: Number(getAttr(m[0], 'min')), max: Number(getAttr(m[0], 'max')), s: getAttr(m[0], 'style') }));
|
|
122
|
+
return (col) => { const c = spans.find((x) => x.min <= col + 1 && col + 1 <= x.max && x.s !== undefined); return c ? Number(c.s) : undefined; };
|
|
123
|
+
}
|
|
124
|
+
const ROW = /<(\w+:)?row\b[^>]*?(?:\/>|>[\s\S]*?<\/\1row>)/g;
|
|
125
|
+
const CELL = /<(\w+:)?c\b[^>]*?(?:\/>|>[\s\S]*?<\/\1c>)/g;
|
|
126
|
+
/**
|
|
127
|
+
* Colours one sheet's flagged cells. Rows and cells not flagged are copied unchanged; a flagged cell keeps everything
|
|
128
|
+
* but its `s`; a flagged cell the file lacks is added empty, styled from its row (when `customFormat`) or column.
|
|
129
|
+
*
|
|
130
|
+
* @param xml - The worksheet part.
|
|
131
|
+
* @param plan - What to colour.
|
|
132
|
+
* @param rank - A severity's rank: lower is worse, and wins on a cell.
|
|
133
|
+
* @param styleFor - The style index for a cell of style `s` coloured for `severity`.
|
|
134
|
+
* @returns The worksheet part, coloured.
|
|
135
|
+
*/
|
|
136
|
+
export function fillSheet(xml, plan, rank, styleFor) {
|
|
137
|
+
const sd = /<(\w+:)?sheetData\b[^>]*?(\/?)>/.exec(xml);
|
|
138
|
+
if (!sd)
|
|
139
|
+
throw spError('SP-5002', 'sheetpane review: the worksheet has no <sheetData>');
|
|
140
|
+
const P = sd[1] ?? '';
|
|
141
|
+
const start = sd.index + sd[0].length;
|
|
142
|
+
const end = sd[2] === '/' ? start : xml.indexOf(`</${P}sheetData>`, start);
|
|
143
|
+
if (end < 0)
|
|
144
|
+
throw spError('SP-5002', 'sheetpane review: <sheetData> is not closed');
|
|
145
|
+
const inner = xml.slice(start, end);
|
|
146
|
+
const colStyle = colStyles(xml);
|
|
147
|
+
let maxRow = -1, maxCol = -1;
|
|
148
|
+
const made = (row, col) => { maxRow = Math.max(maxRow, row); maxCol = Math.max(maxCol, col); };
|
|
149
|
+
const emptyCell = (row, col, base, sev) => { made(row, col); return `<${P}c r="${cellRef(row, col)}" s="${styleFor(base, sev)}"/>`; };
|
|
150
|
+
const newRow = (row) => {
|
|
151
|
+
const cols = [...plan.cells.get(row)].sort(([a], [b]) => a - b);
|
|
152
|
+
return `<${P}row r="${row + 1}">${cols.map(([col, sev]) => emptyCell(row, col, colStyle(col) ?? 0, sev)).join('')}</${P}row>`;
|
|
153
|
+
};
|
|
154
|
+
const wideSev = (row, col, sev) => {
|
|
155
|
+
for (const w of plan.wide)
|
|
156
|
+
if (w.range.top <= row && row <= w.range.bottom && w.range.left <= col && col <= w.range.right && (sev === undefined || rank(w.severity) < rank(sev)))
|
|
157
|
+
sev = w.severity;
|
|
158
|
+
return sev;
|
|
159
|
+
};
|
|
160
|
+
const pending = [...plan.cells.keys()].sort((a, b) => a - b);
|
|
161
|
+
let pi = 0, last = 0, prev = 0;
|
|
162
|
+
const out = [];
|
|
163
|
+
for (const m of inner.matchAll(ROW)) {
|
|
164
|
+
const tag = openTag(m[0]);
|
|
165
|
+
const r = Number(getAttr(tag, 'r') ?? prev + 1);
|
|
166
|
+
prev = r;
|
|
167
|
+
const row = r - 1;
|
|
168
|
+
out.push(inner.slice(last, m.index));
|
|
169
|
+
while (pi < pending.length && pending[pi] < row)
|
|
170
|
+
out.push(newRow(pending[pi++]));
|
|
171
|
+
const targets = pending[pi] === row ? plan.cells.get(pending[pi++]) : undefined;
|
|
172
|
+
const wide = plan.wide.some((w) => w.range.top <= row && row <= w.range.bottom);
|
|
173
|
+
out.push(targets || wide ? fillRow(m[0], tag, row, targets) : m[0]);
|
|
174
|
+
last = m.index + m[0].length;
|
|
175
|
+
}
|
|
176
|
+
while (pi < pending.length)
|
|
177
|
+
out.push(newRow(pending[pi++]));
|
|
178
|
+
out.push(inner.slice(last));
|
|
179
|
+
function fillRow(rowXml, tag, row, targets) {
|
|
180
|
+
const self = /\/>$/.test(tag);
|
|
181
|
+
const body = self ? '' : rowXml.slice(tag.length, rowXml.length - `</${P}row>`.length);
|
|
182
|
+
const custom = getAttr(tag, 'customFormat');
|
|
183
|
+
const rowStyle = custom === '1' || custom === 'true' ? Number(getAttr(tag, 's') ?? 0) : undefined;
|
|
184
|
+
const todo = new Map(targets ?? []);
|
|
185
|
+
const parts = [];
|
|
186
|
+
let at = 0, prevCol = -1, minCol = Infinity, maxNew = -1;
|
|
187
|
+
const addMissing = (before) => {
|
|
188
|
+
for (const col of [...todo.keys()].filter((c) => c < before).sort((a, b) => a - b)) {
|
|
189
|
+
parts.push(emptyCell(row, col, rowStyle ?? colStyle(col) ?? 0, todo.get(col)));
|
|
190
|
+
todo.delete(col);
|
|
191
|
+
minCol = Math.min(minCol, col);
|
|
192
|
+
maxNew = Math.max(maxNew, col);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
for (const c of body.matchAll(CELL)) {
|
|
196
|
+
const ctag = openTag(c[0]);
|
|
197
|
+
const ref = getAttr(ctag, 'r');
|
|
198
|
+
const col = ref ? (parseRange(ref)?.left ?? prevCol + 1) : prevCol + 1;
|
|
199
|
+
prevCol = col;
|
|
200
|
+
parts.push(body.slice(at, c.index));
|
|
201
|
+
addMissing(col);
|
|
202
|
+
const sev = wideSev(row, col, todo.get(col));
|
|
203
|
+
todo.delete(col);
|
|
204
|
+
parts.push(sev === undefined ? c[0] : c[0].replace(ctag, setAttr(ctag, 's', String(styleFor(Number(getAttr(ctag, 's') ?? 0), sev)))));
|
|
205
|
+
at = c.index + c[0].length;
|
|
206
|
+
}
|
|
207
|
+
parts.push(body.slice(at));
|
|
208
|
+
addMissing(Infinity);
|
|
209
|
+
let open = self ? tag.replace(/\s*\/>$/, '>') : tag;
|
|
210
|
+
const spans = getAttr(open, 'spans');
|
|
211
|
+
if (spans && maxNew >= 0) {
|
|
212
|
+
const [a, b] = spans.split(':').map(Number);
|
|
213
|
+
if (Number.isFinite(a) && Number.isFinite(b))
|
|
214
|
+
open = setAttr(open, 'spans', `${Math.min(a, minCol + 1)}:${Math.max(b, maxNew + 1)}`);
|
|
215
|
+
}
|
|
216
|
+
return `${open}${parts.join('')}</${P}row>`;
|
|
217
|
+
}
|
|
218
|
+
const data = sd[2] === '/' ? `${sd[0].replace(/\s*\/>$/, '>')}${out.join('')}</${P}sheetData>` : `${sd[0]}${out.join('')}`;
|
|
219
|
+
let result = xml.slice(0, sd.index) + data + xml.slice(end);
|
|
220
|
+
if (maxRow >= 0)
|
|
221
|
+
result = widenDimension(result, maxRow, maxCol);
|
|
222
|
+
return result;
|
|
223
|
+
}
|
|
224
|
+
/** `<dimension ref>` grown to take in a cell the fills added. */
|
|
225
|
+
function widenDimension(xml, row, col) {
|
|
226
|
+
const d = /<(?:\w+:)?dimension\b[^>]*>/.exec(xml);
|
|
227
|
+
const ref = d && getAttr(d[0], 'ref');
|
|
228
|
+
const r = ref ? parseRange(ref) : undefined;
|
|
229
|
+
if (!d || !r || (row <= r.bottom && col <= r.right))
|
|
230
|
+
return xml;
|
|
231
|
+
return xml.replace(d[0], setAttr(d[0], 'ref', `${cellRef(r.top, r.left)}:${cellRef(Math.max(r.bottom, row), Math.max(r.right, col))}`));
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=fills.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fills.js","sourceRoot":"","sources":["../src/fills.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { type Plugin, type Severity } from '@sheetpane/grid';
|
|
2
|
+
import { type ReviewReport } from './write.js';
|
|
3
|
+
/** Options for {@link review}. Each switch is the default for the Download menu's item; a host flips one per export with `export({ … })`. */
|
|
4
|
+
export interface ReviewOptions {
|
|
5
|
+
/** Add the Review sheet. Default `true`; `export({ review: false })` leaves it out of one export. */
|
|
6
|
+
sheet?: boolean;
|
|
7
|
+
/** Colour each flagged cell by severity. Default `false`; `export({ reviewFills: true })` colours one export. */
|
|
8
|
+
fill?: boolean;
|
|
9
|
+
/** The Review sheet's name. Default `Review`; beside a sheet of that name it becomes `Review (2)`. */
|
|
10
|
+
sheetName?: string;
|
|
11
|
+
/**
|
|
12
|
+
* The severities listed and coloured. Default the problems — `error`, `warning`, `note`; results such as `pass`
|
|
13
|
+
* and `skipped` (W-50) only when listed. `export({ reviewSeverities: […] })` overrides it for one export.
|
|
14
|
+
*/
|
|
15
|
+
severities?: readonly Severity[];
|
|
16
|
+
/**
|
|
17
|
+
* The layers listed and coloured (W-51); an error with no `layer` is `review`. Default every layer but `compare`
|
|
18
|
+
* (W-63: a comparison is not the review), which is listed only when named.
|
|
19
|
+
* `export({ reviewLayers: […] })` overrides it for one export.
|
|
20
|
+
*/
|
|
21
|
+
layers?: readonly string[];
|
|
22
|
+
/** A fill per severity — `#RRGGBB`, `RRGGBB` or ARGB — over the defaults (`error` pale red, `warning` pale yellow, `note` pale blue); `null` colours none. */
|
|
23
|
+
fills?: Readonly<Record<string, string | null>>;
|
|
24
|
+
}
|
|
25
|
+
/** What one export wrote: the sheet's name, the ids listed, the counts by status, the ids coloured and what was skipped. */
|
|
26
|
+
export type { ReviewReport, SkipReason } from './write.js';
|
|
27
|
+
/** The review plugin, plus the report of its most recent export. */
|
|
28
|
+
export interface ReviewPlugin extends Plugin {
|
|
29
|
+
/** What the most recent export wrote and skipped; undefined until one runs (or when there was nothing to write). */
|
|
30
|
+
readonly lastExport: ReviewReport | undefined;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The review plugin. On export (`exportPatch`), it adds a *Review* sheet listing every error — id, sheet, cell (a link
|
|
34
|
+
* to it), severity, type, found, expected, message, author, status, and for an accepted error *Accepted: <reason>*
|
|
35
|
+
* with who and when — and, when asked, fills each open error's cells by severity, cloning each cell's format so its
|
|
36
|
+
* own formatting stays. Whole-sheet and workbook errors are listed, never coloured. A defined name marks the sheet
|
|
37
|
+
* ({@link reviewSheetOf}). An `.xlsx` export only. Importing this module registers it as `review`.
|
|
38
|
+
*
|
|
39
|
+
* @param options - Defaults for the Download menu's *Add a Review sheet* and *Colour flagged cells*, the sheet's
|
|
40
|
+
* name, the severities and layers written, and the fills.
|
|
41
|
+
* @returns The plugin (`id: 'review'`) with `lastExport`.
|
|
42
|
+
* @throws SP-5005 when a `fills` colour is not a hex colour.
|
|
43
|
+
* @see https://github.com/arpitarya/sheetpane/blob/main/records/0123_REVIEW.md
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* import { createWorkbook } from '@sheetpane/grid';
|
|
47
|
+
* import { review } from '@sheetpane/review';
|
|
48
|
+
*
|
|
49
|
+
* const plugin = review({ fill: true });
|
|
50
|
+
* const book = createWorkbook({ plugins: [plugin] });
|
|
51
|
+
* await book.load(bytes, 'holdings.xlsx');
|
|
52
|
+
* const { blob } = await book.exportFile();
|
|
53
|
+
* plugin.lastExport?.counts; // { open, accepted, resolved }
|
|
54
|
+
* ```
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* import '@sheetpane/review'; // registers `review`
|
|
58
|
+
* excel.setAttribute('plugins', 'review');
|
|
59
|
+
* const annotated = await excel.export({ reviewFills: true });
|
|
60
|
+
* const listOnly = await excel.export({ reviewSeverities: ['error'] });
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export declare function review(options?: ReviewOptions): ReviewPlugin;
|
|
64
|
+
export { reviewSheetOf, REVIEW_NAME } from './sheet.js';
|
|
65
|
+
export { COLUMNS, PROBLEM_SEVERITIES } from './rows.js';
|
|
66
|
+
export { DEFAULT_FILLS, MAX_FILL_CELLS } from './fills.js';
|
|
67
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAqC,KAAK,MAAM,EAAE,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAGhG,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAE5D,6IAA6I;AAC7I,MAAM,WAAW,aAAa;IAC5B,qGAAqG;IACrG,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,iHAAiH;IACjH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,sGAAsG;IACtG,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,UAAU,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAC;IACjC;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,8JAA8J;IAC9J,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC;CACjD;AAED,4HAA4H;AAC5H,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE3D,oEAAoE;AACpE,MAAM,WAAW,YAAa,SAAQ,MAAM;IAC1C,oHAAoH;IACpH,QAAQ,CAAC,UAAU,EAAE,YAAY,GAAG,SAAS,CAAC;CAC/C;AAOD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,MAAM,CAAC,OAAO,GAAE,aAAkB,GAAG,YAAY,CA8BhE;AAID,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// @sheetpane/review — the review written into the download as a sheet of its
|
|
2
|
+
// own, and optionally each flagged cell filled by severity (W-59). Export-only:
|
|
3
|
+
// one `exportPatch` hook on the existing context, no seventh hook, no controls
|
|
4
|
+
// beyond the Download menu's two switches. String XML only, so it runs in Node.
|
|
5
|
+
// Nothing leaves the page (SR-LAW-11): the parts are written into the bytes the
|
|
6
|
+
// browser saves.
|
|
7
|
+
import { SEVERITY, registerPlugin, spError } from '@sheetpane/grid';
|
|
8
|
+
import { DEFAULT_FILLS, argb } from './fills.js';
|
|
9
|
+
import { PROBLEM_SEVERITIES, selectErrors } from './rows.js';
|
|
10
|
+
import { writeReview } from './write.js';
|
|
11
|
+
const strings = (v) => (Array.isArray(v) && v.every((x) => typeof x === 'string') ? v : undefined);
|
|
12
|
+
/** `export({ reviewSeverities })` as the grid's severities: a list of names, each one SEVERITY knows (W-50). */
|
|
13
|
+
const severitiesOf = (v) => (strings(v)?.every((s) => Object.hasOwn(SEVERITY, s)) ? v : undefined);
|
|
14
|
+
const flag = (v, fallback) => (typeof v === 'boolean' ? v : fallback);
|
|
15
|
+
/**
|
|
16
|
+
* The review plugin. On export (`exportPatch`), it adds a *Review* sheet listing every error — id, sheet, cell (a link
|
|
17
|
+
* to it), severity, type, found, expected, message, author, status, and for an accepted error *Accepted: <reason>*
|
|
18
|
+
* with who and when — and, when asked, fills each open error's cells by severity, cloning each cell's format so its
|
|
19
|
+
* own formatting stays. Whole-sheet and workbook errors are listed, never coloured. A defined name marks the sheet
|
|
20
|
+
* ({@link reviewSheetOf}). An `.xlsx` export only. Importing this module registers it as `review`.
|
|
21
|
+
*
|
|
22
|
+
* @param options - Defaults for the Download menu's *Add a Review sheet* and *Colour flagged cells*, the sheet's
|
|
23
|
+
* name, the severities and layers written, and the fills.
|
|
24
|
+
* @returns The plugin (`id: 'review'`) with `lastExport`.
|
|
25
|
+
* @throws SP-5005 when a `fills` colour is not a hex colour.
|
|
26
|
+
* @see https://github.com/arpitarya/sheetpane/blob/main/records/0123_REVIEW.md
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* import { createWorkbook } from '@sheetpane/grid';
|
|
30
|
+
* import { review } from '@sheetpane/review';
|
|
31
|
+
*
|
|
32
|
+
* const plugin = review({ fill: true });
|
|
33
|
+
* const book = createWorkbook({ plugins: [plugin] });
|
|
34
|
+
* await book.load(bytes, 'holdings.xlsx');
|
|
35
|
+
* const { blob } = await book.exportFile();
|
|
36
|
+
* plugin.lastExport?.counts; // { open, accepted, resolved }
|
|
37
|
+
* ```
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* import '@sheetpane/review'; // registers `review`
|
|
41
|
+
* excel.setAttribute('plugins', 'review');
|
|
42
|
+
* const annotated = await excel.export({ reviewFills: true });
|
|
43
|
+
* const listOnly = await excel.export({ reviewSeverities: ['error'] });
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export function review(options = {}) {
|
|
47
|
+
const fills = { ...DEFAULT_FILLS };
|
|
48
|
+
for (const [sev, c] of Object.entries(options.fills ?? {})) {
|
|
49
|
+
if (c === null) {
|
|
50
|
+
delete fills[sev];
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const v = argb(c);
|
|
54
|
+
if (!v)
|
|
55
|
+
throw spError('SP-5005', `sheetpane review: fill for "${sev}" is not a colour: ${JSON.stringify(c)}`, TypeError);
|
|
56
|
+
fills[sev] = v;
|
|
57
|
+
}
|
|
58
|
+
let last;
|
|
59
|
+
return {
|
|
60
|
+
id: 'review',
|
|
61
|
+
provides: ['exportPatch'],
|
|
62
|
+
// the Download menu's switches for this export (W-34)
|
|
63
|
+
exportOptions: [
|
|
64
|
+
{ key: 'review', label: 'Add a Review sheet', default: options.sheet !== false },
|
|
65
|
+
{ key: 'reviewFills', label: 'Colour flagged cells', default: options.fill === true },
|
|
66
|
+
],
|
|
67
|
+
get lastExport() { return last; },
|
|
68
|
+
async exportPatch(patch, ctx) {
|
|
69
|
+
last = undefined;
|
|
70
|
+
const x = ctx.exportOptions ?? {};
|
|
71
|
+
const sheet = flag(x.review, options.sheet !== false), fill = flag(x.reviewFills, options.fill === true);
|
|
72
|
+
if (!sheet && !fill)
|
|
73
|
+
return;
|
|
74
|
+
const severities = severitiesOf(x.reviewSeverities) ?? options.severities ?? PROBLEM_SEVERITIES;
|
|
75
|
+
const layers = strings(x.reviewLayers) ?? options.layers;
|
|
76
|
+
const errors = selectErrors(ctx.snapshot().errors, { severities, layers }, patch.sheetNames());
|
|
77
|
+
if (!errors.length)
|
|
78
|
+
return;
|
|
79
|
+
last = await writeReview(patch, errors, { sheet, fill, sheetName: options.sheetName ?? 'Review', fills, order: [...PROBLEM_SEVERITIES, ...severities] });
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
registerPlugin('review', () => review());
|
|
84
|
+
export { reviewSheetOf, REVIEW_NAME } from './sheet.js';
|
|
85
|
+
export { COLUMNS, PROBLEM_SEVERITIES } from './rows.js';
|
|
86
|
+
export { DEFAULT_FILLS, MAX_FILL_CELLS } from './fills.js';
|
|
87
|
+
//# sourceMappingURL=index.js.map
|