@startsimpli/ui 0.4.161 → 0.4.163
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/package.json +4 -4
- package/src/components/history/RecordActivityPanel.tsx +4 -1
- package/src/components/history/RevisionFields.tsx +3 -1
- package/src/components/history/__tests__/record-activity-panel.test.tsx +44 -1
- package/src/components/history/index.ts +1 -0
- package/src/components/history/revision-history.ts +30 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@startsimpli/ui",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.163",
|
|
4
4
|
"description": "Shared UI components package for StartSimpli applications",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"@codemirror/state": "^6.0.0",
|
|
57
57
|
"@codemirror/view": "^6.0.0",
|
|
58
58
|
"@startsimpli/api": "^0.5.68",
|
|
59
|
-
"@startsimpli/auth": "^0.4.
|
|
59
|
+
"@startsimpli/auth": "^0.4.44",
|
|
60
60
|
"@startsimpli/i18n": "^0.1.3"
|
|
61
61
|
},
|
|
62
62
|
"peerDependenciesMeta": {
|
|
@@ -152,8 +152,8 @@
|
|
|
152
152
|
"tailwindcss": "^3.4.19",
|
|
153
153
|
"tsup": "^8.5.1",
|
|
154
154
|
"typescript": "^6.0.3",
|
|
155
|
-
"@startsimpli/
|
|
156
|
-
"@startsimpli/
|
|
155
|
+
"@startsimpli/realtime": "0.0.2",
|
|
156
|
+
"@startsimpli/i18n": "0.1.3"
|
|
157
157
|
},
|
|
158
158
|
"keywords": [
|
|
159
159
|
"ui",
|
|
@@ -289,9 +289,12 @@ export function RecordActivityPanel({
|
|
|
289
289
|
<button
|
|
290
290
|
type="button"
|
|
291
291
|
className="text-xs text-muted-foreground underline underline-offset-2 hover:text-foreground"
|
|
292
|
+
title={`Only ${actorText}'s edits`}
|
|
292
293
|
onClick={() => onFiltersChange(prune({ ...applied, actor: sitting.actorSub }))}
|
|
293
294
|
>
|
|
294
|
-
|
|
295
|
+
{/* The actor's name is printed on the line below; saying it
|
|
296
|
+
again here doubled every card (bd startsim-spywp). */}
|
|
297
|
+
Only their edits
|
|
295
298
|
</button>
|
|
296
299
|
)}
|
|
297
300
|
</div>
|
|
@@ -33,6 +33,7 @@ import { DiffViewer } from '../diff';
|
|
|
33
33
|
import {
|
|
34
34
|
canDiffFieldChange,
|
|
35
35
|
classifyFieldChange,
|
|
36
|
+
sittingNameRow,
|
|
36
37
|
type FoldedFieldChange,
|
|
37
38
|
type RevisionSitting,
|
|
38
39
|
} from './revision-history';
|
|
@@ -180,13 +181,14 @@ export function RevisionFields({
|
|
|
180
181
|
onNarrowToField,
|
|
181
182
|
}: RevisionFieldsProps) {
|
|
182
183
|
const [open, setOpen] = useState<string | null>(null);
|
|
184
|
+
const nameRow = sittingNameRow(sitting);
|
|
183
185
|
const entries: Array<[string, FoldedFieldChange]> = [
|
|
184
186
|
...Object.keys(sitting.changed)
|
|
185
187
|
.sort()
|
|
186
188
|
.map((field) => [field, sitting.changed[field]!] as [string, FoldedFieldChange]),
|
|
187
189
|
// The name COLUMN, kept namespaced away from the data keys because a tenant
|
|
188
190
|
// may declare a data key literally called "name".
|
|
189
|
-
...(
|
|
191
|
+
...(nameRow ? ([['name (title)', nameRow]] as Array<[string, FoldedFieldChange]>) : []),
|
|
190
192
|
];
|
|
191
193
|
|
|
192
194
|
return (
|
|
@@ -167,7 +167,7 @@ describe('RecordActivityPanel', () => {
|
|
|
167
167
|
const onFiltersChange = jest.fn()
|
|
168
168
|
const list = jest.fn().mockResolvedValue({ next: null, results: [row('1', draftA)] })
|
|
169
169
|
mount(<RecordActivityPanel client={{ list }} filters={{}} onFiltersChange={onFiltersChange} />)
|
|
170
|
-
fireEvent.click(await screen.findByRole('button', { name:
|
|
170
|
+
fireEvent.click(await screen.findByRole('button', { name: 'Only their edits' }))
|
|
171
171
|
expect(onFiltersChange).toHaveBeenCalledWith({ actor: 'u-jurga' })
|
|
172
172
|
})
|
|
173
173
|
|
|
@@ -177,3 +177,46 @@ describe('RecordActivityPanel', () => {
|
|
|
177
177
|
expect(await screen.findByText(/no edits match/i)).toBeInTheDocument()
|
|
178
178
|
})
|
|
179
179
|
})
|
|
180
|
+
|
|
181
|
+
describe('an Activity card says each thing once (bd startsim-8udmw, startsim-spywp)', () => {
|
|
182
|
+
const scopeRec = { id: 's-1', type: 'scope', name: 'OGMC' }
|
|
183
|
+
|
|
184
|
+
it('a create counts the rows it lists, and the name row reads "set"', async () => {
|
|
185
|
+
const created = row('1', draftA, {
|
|
186
|
+
version: 1,
|
|
187
|
+
action: 'created',
|
|
188
|
+
metadata: {
|
|
189
|
+
changed: { blog: { after: 'x' }, linkedin: { after: 'y' } },
|
|
190
|
+
name: { before: '', after: 'Gulf logistics brief' },
|
|
191
|
+
},
|
|
192
|
+
})
|
|
193
|
+
const list = jest.fn().mockResolvedValue({ next: null, results: [created] })
|
|
194
|
+
mount(<RecordActivityPanel client={{ list }} filters={{}} onFiltersChange={jest.fn()} />)
|
|
195
|
+
expect(await screen.findByText('Created the record with 3 fields')).toBeInTheDocument()
|
|
196
|
+
const nameRow = screen.getByRole('button', { name: /name \(title\)/ })
|
|
197
|
+
expect(nameRow).toHaveTextContent('set')
|
|
198
|
+
expect(nameRow).not.toHaveTextContent('changed')
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
it('a type whose own `name` key mirrors the record name shows one name row', async () => {
|
|
202
|
+
const created = row('1', scopeRec, {
|
|
203
|
+
version: 1,
|
|
204
|
+
action: 'created',
|
|
205
|
+
metadata: {
|
|
206
|
+
changed: { name: { after: 'OGMC' }, path: { after: '/ogmc' } },
|
|
207
|
+
name: { before: null, after: 'OGMC' },
|
|
208
|
+
},
|
|
209
|
+
})
|
|
210
|
+
const list = jest.fn().mockResolvedValue({ next: null, results: [created] })
|
|
211
|
+
mount(<RecordActivityPanel client={{ list }} filters={{}} onFiltersChange={jest.fn()} />)
|
|
212
|
+
expect(await screen.findByText('Created the record with 2 fields')).toBeInTheDocument()
|
|
213
|
+
expect(screen.queryByRole('button', { name: /name \(title\)/ })).toBeNull()
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
it('the narrow-to-person link does not repeat the address', async () => {
|
|
217
|
+
const list = jest.fn().mockResolvedValue({ next: null, results: [row('1', draftA)] })
|
|
218
|
+
mount(<RecordActivityPanel client={{ list }} filters={{}} onFiltersChange={jest.fn()} />)
|
|
219
|
+
expect(await screen.findByRole('button', { name: 'Only their edits' })).toBeInTheDocument()
|
|
220
|
+
expect(screen.queryByText(/Only jurga@ogmc\.example's edits/)).toBeNull()
|
|
221
|
+
})
|
|
222
|
+
})
|
|
@@ -457,6 +457,33 @@ export function sittingFields(sitting: RevisionSitting): string[] {
|
|
|
457
457
|
return Object.keys(sitting.changed).sort();
|
|
458
458
|
}
|
|
459
459
|
|
|
460
|
+
/**
|
|
461
|
+
* The record-name row a sitting shows, if any (bd startsim-8udmw).
|
|
462
|
+
*
|
|
463
|
+
* Two corrections over printing `sitting.name` as is:
|
|
464
|
+
*
|
|
465
|
+
* - A type may declare its own data key `name` that mirrors the record name
|
|
466
|
+
* (scope, client). When that key changed to the same value in the same
|
|
467
|
+
* sitting, the name row says it a second time, under a different verb — so
|
|
468
|
+
* it is dropped and the data row stands for both.
|
|
469
|
+
* - On a CREATE there was no name before it. The wire may still carry a
|
|
470
|
+
* `before` (null or blank), which classified the row "changed" on a record
|
|
471
|
+
* that had only just come into being; on a create it reads "set".
|
|
472
|
+
*/
|
|
473
|
+
export function sittingNameRow(sitting: RevisionSitting): FoldedFieldChange | undefined {
|
|
474
|
+
const name = sitting.name;
|
|
475
|
+
if (!name) return undefined;
|
|
476
|
+
const dataName = sitting.changed.name;
|
|
477
|
+
if (dataName && dataName.hasAfter && name.hasAfter && String(dataName.after) === String(name.after)) {
|
|
478
|
+
return undefined;
|
|
479
|
+
}
|
|
480
|
+
if (sitting.action === 'created' && name.hasBefore) {
|
|
481
|
+
const { before: _before, ...rest } = name;
|
|
482
|
+
return { ...rest, hasBefore: false };
|
|
483
|
+
}
|
|
484
|
+
return name;
|
|
485
|
+
}
|
|
486
|
+
|
|
460
487
|
/** "a", "a and b", "a, b and c" — an Oxford-free list, because these are field
|
|
461
488
|
* names in a sentence and not prose. */
|
|
462
489
|
export function joinNames(names: string[]): string {
|
|
@@ -495,7 +522,9 @@ export function sittingHeadline(
|
|
|
495
522
|
: `Restored ${fields.length} fields from ${from}`;
|
|
496
523
|
}
|
|
497
524
|
if (sitting.action === 'created') {
|
|
498
|
-
|
|
525
|
+
// Counts the rows the reader is shown under it, name row included.
|
|
526
|
+
const count = fields.length + (sittingNameRow(sitting) ? 1 : 0);
|
|
527
|
+
return count ? `Created the record with ${count} ${count === 1 ? 'field' : 'fields'}` : 'Created the record';
|
|
499
528
|
}
|
|
500
529
|
if (!fields.length) return renamed ? 'Renamed the record' : 'Saved the record';
|
|
501
530
|
const named =
|