pageship 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +18 -108
  2. package/dist/cli.mjs +1889 -481
  3. package/dist/skills/LICENSE +21 -0
  4. package/dist/skills/pageship/SKILL.md +150 -0
  5. package/dist/skills/pageship/references/download.md +9 -0
  6. package/dist/skills/pageship/references/list-and-history.md +15 -0
  7. package/dist/skills/pageship/references/restore.md +9 -0
  8. package/dist/skills/pageship/references/visibility.md +12 -0
  9. package/dist/skills/pageship-chart/SKILL.md +110 -0
  10. package/dist/skills/pageship-code-block/SKILL.md +113 -0
  11. package/dist/skills/pageship-code-block/references/highlight-lock.json +542 -0
  12. package/dist/skills/pageship-code-block/references/highlight.mjs +138 -0
  13. package/dist/skills/pageship-decision-record/SKILL.md +43 -0
  14. package/dist/skills/pageship-diagram/SKILL.md +92 -0
  15. package/dist/skills/pageship-diagram/references/check-diagram.js +301 -0
  16. package/dist/skills/pageship-diagram/references/computed-layout.md +49 -0
  17. package/dist/skills/pageship-diagram/references/layout.mjs +310 -0
  18. package/dist/skills/pageship-diagram/references/mermaid.md +89 -0
  19. package/dist/skills/pageship-diagram/references/svg.md +205 -0
  20. package/dist/skills/pageship-docs/NOTICES.md +35 -0
  21. package/dist/skills/pageship-docs/SKILL.md +67 -0
  22. package/dist/skills/pageship-docs/references/ask.md +18 -0
  23. package/dist/skills/pageship-docs/references/check.md +39 -0
  24. package/dist/skills/pageship-docs/references/content.md +29 -0
  25. package/dist/skills/pageship-docs/references/design.md +60 -0
  26. package/dist/skills/pageship-docs/references/file.md +52 -0
  27. package/dist/skills/pageship-docs/references/plain-default.md +331 -0
  28. package/dist/skills/pageship-explainer/SKILL.md +92 -0
  29. package/dist/skills/pageship-interaction/SKILL.md +115 -0
  30. package/dist/skills/pageship-interaction/references/pointer.md +271 -0
  31. package/dist/skills/pageship-interaction/references/step-through.md +146 -0
  32. package/dist/skills/pageship-key-figures/SKILL.md +127 -0
  33. package/dist/skills/pageship-media/SKILL.md +122 -0
  34. package/dist/skills/pageship-motion/SKILL.md +58 -0
  35. package/dist/skills/pageship-plan/SKILL.md +55 -0
  36. package/dist/skills/pageship-report/SKILL.md +29 -0
  37. package/dist/skills/pageship-review-report/SKILL.md +90 -0
  38. package/dist/skills/pageship-slide-deck/SKILL.md +53 -0
  39. package/dist/skills/pageship-slide-deck/references/engine.md +362 -0
  40. package/dist/skills/pageship-spec/SKILL.md +58 -0
  41. package/dist/skills/pageship-table/SKILL.md +243 -0
  42. package/dist/skills/pageship-table/references/sort-and-filter.md +235 -0
  43. package/package.json +1 -1
@@ -0,0 +1,235 @@
1
+ # Sort and filter
2
+
3
+ Use this markup, stylesheet and script as written. Change only the ids, the labels, the filter values and the column numbers. The script works on every `<figure class="table-figure" data-table>` in the file, so one copy serves all tables.
4
+
5
+ ## Markup
6
+
7
+ The controls sit in the figure, between the caption and the table box. Leave out the parts the table doesn't need: a filter, the search box or the sort buttons.
8
+
9
+ ```html
10
+ <figure class="table-figure" data-table>
11
+ <figcaption id="queues">Tickets by queue, Q3 2026, from the help desk export.</figcaption>
12
+ <div class="table-controls">
13
+ <span id="queues-region">Region</span>
14
+ <div role="group" aria-labelledby="queues-region" data-filter-by="group">
15
+ <button type="button" data-filter="" aria-pressed="true">All</button>
16
+ <button type="button" data-filter="EMEA" aria-pressed="false">EMEA</button>
17
+ <button type="button" data-filter="APAC" aria-pressed="false">APAC</button>
18
+ </div>
19
+ <input type="search" data-search aria-label="Find a queue" placeholder="Find a queue" />
20
+ <button type="button" data-reset>Reset</button>
21
+ <p data-count aria-live="polite"></p>
22
+ </div>
23
+ <div class="table-scroll" role="region" tabindex="0" aria-labelledby="queues">
24
+ <table aria-labelledby="queues">
25
+ <thead>
26
+ <tr>
27
+ <th scope="col"><button type="button" data-sort="text">Queue</button></th>
28
+ <th scope="col" class="num"><button type="button" data-sort="number">Tickets</button></th>
29
+ <th scope="col" class="num">
30
+ <button type="button" data-sort="number">Median first response</button>
31
+ </th>
32
+ </tr>
33
+ </thead>
34
+ <tbody data-group="EMEA">
35
+ <tr>
36
+ <th scope="rowgroup" colspan="3">EMEA</th>
37
+ </tr>
38
+ <tr>
39
+ <th scope="row">Billing</th>
40
+ <td class="num" data-value="1140">1,140</td>
41
+ <td class="num" data-value="220">3 h 40 min</td>
42
+ </tr>
43
+ </tbody>
44
+ <tbody data-group="APAC">
45
+ <tr>
46
+ <th scope="rowgroup" colspan="3">APAC</th>
47
+ </tr>
48
+ <tr>
49
+ <th scope="row">Billing</th>
50
+ <td class="num" data-value="265">265</td>
51
+ <td class="num">Not given</td>
52
+ </tr>
53
+ </tbody>
54
+ </table>
55
+ </div>
56
+ <p class="table-empty" hidden>No rows match. Reset to show every row.</p>
57
+ </figure>
58
+ ```
59
+
60
+ - **Sort buttons** go inside the header cells of the columns a reader ranks by. `data-sort="number"` sorts largest first and `data-sort="text"` from A; a second press reverses. Rows sort within their group.
61
+ - **`data-value`** holds a plain number on every cell of a number column whose text is not one, such as `1,140`, `18%` or `3 h 40 min` (in one unit down the column, here minutes). Leave it off a "Not given" cell; those rows sort last.
62
+ - **A filter** is a `role="group"` of buttons. With `data-filter-by="group"`, each button's `data-filter` names a `<tbody>` by its `data-group`. With `data-filter-by` set to a column number, counted from 0 with the row label as column 0, each button keeps the rows whose cell in that column reads exactly its `data-filter`. The `All` button's `data-filter` is empty.
63
+ - **The search box** keeps the rows whose text contains what the reader types. Keep its `aria-label`, and keep it outside any `form`.
64
+ - Every body row is a plain row of cells, apart from the one group heading row that opens each `<tbody>`.
65
+
66
+ ## Stylesheet
67
+
68
+ Add this after the table stylesheet from the skill.
69
+
70
+ ```css
71
+ .table-controls {
72
+ display: flex;
73
+ flex-wrap: wrap;
74
+ gap: 0.5rem 1rem;
75
+ align-items: center;
76
+ margin: 0.75rem 0;
77
+ }
78
+ .table-controls [role="group"] {
79
+ display: flex;
80
+ flex-wrap: wrap;
81
+ gap: 0.25rem;
82
+ }
83
+ .table-controls button,
84
+ .table-controls input {
85
+ font: inherit;
86
+ font-size: 0.875rem;
87
+ color: var(--fg);
88
+ background: var(--bg);
89
+ border: 1px solid var(--muted);
90
+ border-radius: 4px;
91
+ padding: 0.25rem 0.625rem;
92
+ }
93
+ .table-controls button[aria-pressed="true"] {
94
+ background: var(--accent);
95
+ border-color: var(--accent);
96
+ color: var(--bg);
97
+ font-weight: 600;
98
+ }
99
+ .table-scroll th button[data-sort] {
100
+ font: inherit;
101
+ color: inherit;
102
+ background: none;
103
+ border: 0;
104
+ padding: 0;
105
+ cursor: pointer;
106
+ text-align: inherit;
107
+ }
108
+ .table-scroll th button[data-sort]::after {
109
+ content: " \2195";
110
+ color: var(--muted);
111
+ }
112
+ .table-scroll th[aria-sort="ascending"] button::after {
113
+ content: " \2191";
114
+ color: inherit;
115
+ }
116
+ .table-scroll th[aria-sort="descending"] button::after {
117
+ content: " \2193";
118
+ color: inherit;
119
+ }
120
+ @media print {
121
+ .table-scroll th button[data-sort] {
122
+ display: inline !important;
123
+ }
124
+ .table-scroll th button[data-sort]::after {
125
+ content: none;
126
+ }
127
+ }
128
+ ```
129
+
130
+ ## Script
131
+
132
+ Put it in one `<script>` element at the end of `<body>`.
133
+
134
+ ```js
135
+ for (const figure of document.querySelectorAll("figure[data-table]")) {
136
+ const groups = [...figure.querySelectorAll("tbody")].map((body) => {
137
+ const heading = body.rows[0]?.querySelector('th[scope="rowgroup"]') ? body.rows[0] : null;
138
+ return { body, heading, rows: [...body.rows].filter((row) => row !== heading) };
139
+ });
140
+ const total = groups.reduce((sum, group) => sum + group.rows.length, 0);
141
+ const sortButtons = [...figure.querySelectorAll("th button[data-sort]")];
142
+ const filters = [...figure.querySelectorAll("[data-filter-by]")];
143
+ const search = figure.querySelector("input[data-search]");
144
+ const count = figure.querySelector("[data-count]");
145
+ const empty = figure.querySelector(".table-empty");
146
+ let sort = null;
147
+
148
+ const cellText = (row, column) => row.cells[column]?.textContent.trim() ?? "";
149
+ const sortKey = (row, column, type) => {
150
+ const raw = row.cells[column]?.dataset.value ?? cellText(row, column);
151
+ if (type !== "number") return raw === "" ? null : raw;
152
+ return raw === "" || Number.isNaN(Number(raw)) ? null : Number(raw);
153
+ };
154
+ const keeps = (filter, group, row) => {
155
+ const value = filter.querySelector('[aria-pressed="true"]')?.dataset.filter ?? "";
156
+ if (value === "") return true;
157
+ const by = filter.dataset.filterBy;
158
+ return by === "group"
159
+ ? group.body.dataset.group === value
160
+ : cellText(row, Number(by)) === value;
161
+ };
162
+
163
+ function update() {
164
+ const query = search?.value.trim().toLowerCase() ?? "";
165
+ let shown = 0;
166
+ for (const group of groups) {
167
+ const rows = [...group.rows];
168
+ if (sort) {
169
+ const { column, type, direction } = sort;
170
+ rows.sort((a, b) => {
171
+ const x = sortKey(a, column, type);
172
+ const y = sortKey(b, column, type);
173
+ if (x === null || y === null) return (x === null) - (y === null);
174
+ const order =
175
+ type === "number" ? x - y : x.localeCompare(y, undefined, { numeric: true });
176
+ return direction === "ascending" ? order : -order;
177
+ });
178
+ }
179
+ group.body.append(...rows);
180
+ let groupShown = 0;
181
+ for (const row of rows) {
182
+ const match =
183
+ filters.every((filter) => keeps(filter, group, row)) &&
184
+ (query === "" || row.textContent.toLowerCase().includes(query));
185
+ row.hidden = !match;
186
+ if (match) groupShown += 1;
187
+ }
188
+ if (group.heading) group.heading.hidden = groupShown === 0;
189
+ shown += groupShown;
190
+ }
191
+ for (const button of sortButtons) {
192
+ const th = button.closest("th");
193
+ if (sort?.column === th.cellIndex) th.setAttribute("aria-sort", sort.direction);
194
+ else th.removeAttribute("aria-sort");
195
+ }
196
+ if (count) count.textContent = `Showing ${shown} of ${total} rows`;
197
+ if (empty) empty.hidden = shown > 0;
198
+ }
199
+
200
+ for (const button of sortButtons) {
201
+ button.addEventListener("click", () => {
202
+ const column = button.closest("th").cellIndex;
203
+ const type = button.dataset.sort;
204
+ // Numbers start largest first and text starts at A; a second press reverses.
205
+ const first = type === "number" ? "descending" : "ascending";
206
+ const other = first === "ascending" ? "descending" : "ascending";
207
+ const direction = sort?.column === column && sort.direction === first ? other : first;
208
+ sort = { column, type, direction };
209
+ update();
210
+ });
211
+ }
212
+ for (const filter of filters) {
213
+ for (const button of filter.querySelectorAll("button[data-filter]")) {
214
+ button.addEventListener("click", () => {
215
+ for (const other of filter.querySelectorAll("button[data-filter]")) {
216
+ other.setAttribute("aria-pressed", String(other === button));
217
+ }
218
+ update();
219
+ });
220
+ }
221
+ }
222
+ search?.addEventListener("input", update);
223
+ figure.querySelector("button[data-reset]")?.addEventListener("click", () => {
224
+ sort = null;
225
+ for (const filter of filters) {
226
+ for (const button of filter.querySelectorAll("button[data-filter]")) {
227
+ button.setAttribute("aria-pressed", String(button.dataset.filter === ""));
228
+ }
229
+ }
230
+ if (search) search.value = "";
231
+ update();
232
+ });
233
+ update();
234
+ }
235
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pageship",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "Pageship CLI for checking server API compatibility.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {