@atelic-action/artifact 4.2.1 → 5.1.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 +16 -17
- package/package.json +4 -4
- package/src/documents/FirstCall.tsx +6 -6
- package/src/documents/Note.tsx +2 -2
- package/src/documents/Report.tsx +2 -2
- package/src/documents/{Read.tsx → TheRead.tsx} +251 -76
- package/src/documents/{Writeup.tsx → Topic.tsx} +202 -362
- package/src/documents/documents.css +87 -34
- package/src/documents/index.ts +28 -2
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type ArtifactLink,
|
|
3
3
|
type ArtifactParagraph,
|
|
4
|
+
GradeChip,
|
|
5
|
+
gradeLabel,
|
|
4
6
|
isEmpty,
|
|
5
7
|
newTabProps,
|
|
6
8
|
Paragraph,
|
|
@@ -12,36 +14,39 @@ import {
|
|
|
12
14
|
import { IconList, ReportCard, type Tone } from "@atelic-action/ui/components";
|
|
13
15
|
import { ArrowUpRight, Check } from "lucide-react";
|
|
14
16
|
import type { ReactNode } from "react";
|
|
15
|
-
import {
|
|
17
|
+
import { isRuns, type ReadTopic, Topic } from "./Topic";
|
|
16
18
|
|
|
17
19
|
/**
|
|
18
|
-
* The read:
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
20
|
+
* The read: the graded document a business gets from the practice, built from
|
|
21
|
+
* one snapshot. Its shape is the practice's FOOTPRINT.md, under What the
|
|
22
|
+
* Owner Gets: a title and a dated lede; the scorecard (every section under
|
|
23
|
+
* its tier, its grade, and one line, with the earlier letter beside it on a
|
|
24
|
+
* later read), with a note over it for what the read leaves out; the plan in
|
|
25
|
+
* order; then the tiers, each section the card in Topic.tsx (the trade name
|
|
26
|
+
* linked to its definition, the one liner, the grade, the why line as the
|
|
27
|
+
* summary of what we saw, the blocks, and the sources at the foot) plus what
|
|
28
|
+
* is the read's own, today and after and what we recommend; a tier may open
|
|
29
|
+
* on a table of letters per market and close on the paid facts line, which
|
|
30
|
+
* is never graded; then one closing line and the sign off. A beat with
|
|
31
|
+
* nothing in it is left out.
|
|
25
32
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* and what we recommend as a checklist; and the tools we looked through at
|
|
33
|
-
* the foot, as the sources. A beat with nothing in it is left out.
|
|
33
|
+
* The same page at two depths, and `access` says which. A public read is
|
|
34
|
+
* built from public readings alone and may be hosted open; a private one
|
|
35
|
+
* adds what the owner's own consoles show and deploys only behind the wall.
|
|
36
|
+
* The component draws whatever beats the content carries, so the depth
|
|
37
|
+
* lives in the content and `access` is the declaration the host is checked
|
|
38
|
+
* against (the content test, and `access` in site.config.ts).
|
|
34
39
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* `note` says so over the scorecard. Every prop but a finding's body and the
|
|
41
|
-
* sign off person's `role` is plain data, and both are equally happy with a
|
|
42
|
-
* string, so a routine can write a read as JSON.
|
|
40
|
+
* Every prop but a finding's body, a why line, and the sign off person's
|
|
41
|
+
* `role` is plain data, and all three are equally happy with a string, so a
|
|
42
|
+
* routine can write a read as JSON. `print` is the printed copy's own: the
|
|
43
|
+
* sender and recipient lockup where the header was, and the pointer to the
|
|
44
|
+
* hosted twin with its QR under the sign off.
|
|
43
45
|
*/
|
|
44
46
|
|
|
47
|
+
/** Whether anything on the page came from the owner's own consoles. */
|
|
48
|
+
export type ReadAccess = "public" | "private";
|
|
49
|
+
|
|
45
50
|
/** A link inside running text: the words the reader clicks and where they go. */
|
|
46
51
|
export type ReadLink = ArtifactLink;
|
|
47
52
|
|
|
@@ -55,13 +60,15 @@ export interface ReadChange {
|
|
|
55
60
|
}
|
|
56
61
|
|
|
57
62
|
/**
|
|
58
|
-
* One section of a tier: the
|
|
59
|
-
* `
|
|
60
|
-
*
|
|
63
|
+
* One section of a tier: the card (its `title`, `href`, `oneLiner`, `grade`,
|
|
64
|
+
* `why`, `blocks`, and `sources`), plus the scorecard's line, the earlier
|
|
65
|
+
* letter, today and after, and what we recommend.
|
|
61
66
|
*/
|
|
62
|
-
export interface ReadSection extends Omit<
|
|
67
|
+
export interface ReadSection extends Omit<ReadTopic, "why"> {
|
|
63
68
|
/** The one line on the scorecard. */
|
|
64
69
|
gist: ReadParagraph;
|
|
70
|
+
/** The grade in the earlier snapshot, on a later read: what moved. */
|
|
71
|
+
before?: WriteupGrade;
|
|
65
72
|
/** The summary of what we saw, in one or two sentences: the why line. A paragraph, so a read written as JSON can carry links in it. */
|
|
66
73
|
why?: ReactNode | ReadParagraph;
|
|
67
74
|
/** Today and after: what each thing is, and what it becomes. Set after the blocks. */
|
|
@@ -80,6 +87,29 @@ export interface ReadRecommendation {
|
|
|
80
87
|
tone?: Tone;
|
|
81
88
|
}
|
|
82
89
|
|
|
90
|
+
export interface ReadMarketRow {
|
|
91
|
+
market: string;
|
|
92
|
+
place?: string;
|
|
93
|
+
/** One grade per column, in column order; null draws a quiet dash for a column not read. */
|
|
94
|
+
grades: Array<WriteupGrade | null>;
|
|
95
|
+
read: ReactNode | ReadParagraph;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** The markets table: one row per market or site, one letter per column, the read beside it. */
|
|
99
|
+
export interface ReadMarkets {
|
|
100
|
+
title: string;
|
|
101
|
+
lead?: string;
|
|
102
|
+
columns: string[];
|
|
103
|
+
rows: ReadMarketRow[];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The paid facts line: what is rented, never graded (FOOTPRINT.md, Paid). */
|
|
107
|
+
export interface ReadRented {
|
|
108
|
+
/** The word before the line; defaults to the `rented` label. */
|
|
109
|
+
label?: string;
|
|
110
|
+
text: ReactNode | ReadParagraph;
|
|
111
|
+
}
|
|
112
|
+
|
|
83
113
|
/** A tier, e.g. Health: its heading, the question it answers, and its sections in order. */
|
|
84
114
|
export interface ReadTier {
|
|
85
115
|
/** The anchor a header stop points at, e.g. "health". */
|
|
@@ -90,7 +120,11 @@ export interface ReadTier {
|
|
|
90
120
|
href?: string;
|
|
91
121
|
/** The questions the tier answers, in the owner's words. */
|
|
92
122
|
lead?: string;
|
|
123
|
+
/** A table of letters per market or site, over the sections. */
|
|
124
|
+
markets?: ReadMarkets;
|
|
93
125
|
sections: ReadSection[];
|
|
126
|
+
/** The paid facts line, under the sections. Visibility's, by FOOTPRINT.md. */
|
|
127
|
+
rented?: ReadRented;
|
|
94
128
|
}
|
|
95
129
|
|
|
96
130
|
/** One step of the plan: a bold title and a sentence or two. */
|
|
@@ -117,11 +151,21 @@ export interface ReadPlan {
|
|
|
117
151
|
/** The close: one line, then the sign off. */
|
|
118
152
|
export interface ReadClose {
|
|
119
153
|
/** One sentence, e.g. what follows and when. */
|
|
120
|
-
line
|
|
154
|
+
line?: ReadParagraph;
|
|
121
155
|
signOff?: string;
|
|
122
156
|
person?: WriteupPerson;
|
|
123
157
|
}
|
|
124
158
|
|
|
159
|
+
/** The printed copy's own: the lockup where the header was, and the pointer to the hosted twin. */
|
|
160
|
+
export interface ReadPrint {
|
|
161
|
+
/** The sender's wordmark text, e.g. "atelic"; drawn with the orange run. */
|
|
162
|
+
sender: { name: string; href?: string };
|
|
163
|
+
/** The recipient's logo, lockup right of the sender. */
|
|
164
|
+
recipient: { logoSrc: string; logoAlt: string };
|
|
165
|
+
/** The pointer to the hosted page under the sign off, with its QR. */
|
|
166
|
+
twin?: { line: string; domain: string; qrSrc: string };
|
|
167
|
+
}
|
|
168
|
+
|
|
125
169
|
/** The fixed words the page sets, each overridable. */
|
|
126
170
|
export interface ReadLabels {
|
|
127
171
|
/** The dateline's words before the business, e.g. "Prepared for". */
|
|
@@ -132,6 +176,10 @@ export interface ReadLabels {
|
|
|
132
176
|
grade: string;
|
|
133
177
|
/** What a screen reader hears for a section with no grade. */
|
|
134
178
|
notGraded: string;
|
|
179
|
+
/** What a screen reader calls the earlier letter on the scorecard, e.g. "Before" or "June 2026". */
|
|
180
|
+
before: string;
|
|
181
|
+
/** The word before the paid facts line when it names none. */
|
|
182
|
+
rented: string;
|
|
135
183
|
/** The today and after table's first column. */
|
|
136
184
|
now: string;
|
|
137
185
|
/** The today and after table's second column. */
|
|
@@ -144,6 +192,8 @@ export interface ReadLabels {
|
|
|
144
192
|
sources: string;
|
|
145
193
|
/** The plan's heading when it names none. */
|
|
146
194
|
plan: string;
|
|
195
|
+
/** The note over the scorecard on a public read that sets none: what the read leaves out. */
|
|
196
|
+
publicNote: string;
|
|
147
197
|
}
|
|
148
198
|
|
|
149
199
|
export const readLabels: ReadLabels = {
|
|
@@ -151,15 +201,21 @@ export const readLabels: ReadLabels = {
|
|
|
151
201
|
gist: "The Gist",
|
|
152
202
|
grade: "Grade",
|
|
153
203
|
notGraded: "Not graded yet",
|
|
204
|
+
before: "Before",
|
|
205
|
+
rented: "Rented",
|
|
154
206
|
now: "Today",
|
|
155
207
|
after: "After",
|
|
156
208
|
recommend: "Recommendations",
|
|
157
209
|
why: "Why",
|
|
158
210
|
sources: "See it yourself",
|
|
159
211
|
plan: "The Plan",
|
|
212
|
+
publicNote:
|
|
213
|
+
"This page is read from the outside, through what anyone can open. A customer's read adds what the owner's own consoles show.",
|
|
160
214
|
};
|
|
161
215
|
|
|
162
|
-
export interface
|
|
216
|
+
export interface TheReadProps {
|
|
217
|
+
/** Whether anything on the page came from the owner's own consoles; a private read deploys only behind the wall. */
|
|
218
|
+
access: ReadAccess;
|
|
163
219
|
title: string;
|
|
164
220
|
/** The business, set in the dateline. */
|
|
165
221
|
preparedFor: string;
|
|
@@ -173,7 +229,7 @@ export interface ReadProps {
|
|
|
173
229
|
gistLead?: string;
|
|
174
230
|
/** The anchor a header stop points at for the scorecard, e.g. "scorecard": on the heading with a `gistTitle`, on the card itself without one. */
|
|
175
231
|
gistId?: string;
|
|
176
|
-
/** A callout over the scorecard, under its heading, e.g. what the read leaves out and when it follows. */
|
|
232
|
+
/** A callout over the scorecard, under its heading, e.g. what the read leaves out and when it follows. A public read that sets none gets the `publicNote` label. */
|
|
177
233
|
note?: ReadParagraph;
|
|
178
234
|
/** The key: what each letter means, in a few words each. */
|
|
179
235
|
gradeKey?: Array<{ grade: WriteupGrade; label: string }>;
|
|
@@ -182,6 +238,8 @@ export interface ReadProps {
|
|
|
182
238
|
plan: ReadPlan;
|
|
183
239
|
close: ReadClose;
|
|
184
240
|
labels?: Partial<ReadLabels>;
|
|
241
|
+
/** The printed copy's lockup and twin; the page prints without them when left out. */
|
|
242
|
+
print?: ReadPrint;
|
|
185
243
|
}
|
|
186
244
|
|
|
187
245
|
/** A paragraph with something to read. Content may arrive as JSON, so a missing one counts as empty. */
|
|
@@ -189,19 +247,6 @@ function filled(paragraph: ReadParagraph | undefined | null): paragraph is ReadP
|
|
|
189
247
|
return paragraph !== undefined && paragraph !== null && !isEmpty(paragraph);
|
|
190
248
|
}
|
|
191
249
|
|
|
192
|
-
/** A string or runs of text and links, the shape JSON content arrives in; a node of elements is anything else. */
|
|
193
|
-
function isRuns(value: unknown): value is ReadParagraph {
|
|
194
|
-
if (typeof value === "string") return true;
|
|
195
|
-
return (
|
|
196
|
-
Array.isArray(value) &&
|
|
197
|
-
value.every(
|
|
198
|
-
(run) =>
|
|
199
|
-
typeof run === "string" ||
|
|
200
|
-
(typeof run === "object" && run !== null && "text" in run && "href" in run),
|
|
201
|
-
)
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
250
|
/** Content as a node: runs are rendered, a blank is dropped, and a node passes through. */
|
|
206
251
|
function asNode(value: ReactNode | ReadParagraph | undefined | null): ReactNode {
|
|
207
252
|
if (value === undefined || value === null) return undefined;
|
|
@@ -276,8 +321,8 @@ function Gist({
|
|
|
276
321
|
id?: string;
|
|
277
322
|
title?: string;
|
|
278
323
|
lead?: string;
|
|
279
|
-
note?:
|
|
280
|
-
gradeKey?:
|
|
324
|
+
note?: ReadParagraph;
|
|
325
|
+
gradeKey?: TheReadProps["gradeKey"];
|
|
281
326
|
labels: ReadLabels;
|
|
282
327
|
}) {
|
|
283
328
|
return (
|
|
@@ -299,13 +344,14 @@ function Gist({
|
|
|
299
344
|
label={title ?? labels.gist}
|
|
300
345
|
legend={gradeKey}
|
|
301
346
|
legendAt="foot"
|
|
302
|
-
labels={{ grade: labels.grade, notGraded: labels.notGraded }}
|
|
347
|
+
labels={{ grade: labels.grade, notGraded: labels.notGraded, before: labels.before }}
|
|
303
348
|
groups={tiers.map((tier) => ({
|
|
304
349
|
title: tier.title,
|
|
305
350
|
rows: tier.sections.map((section) => ({
|
|
306
351
|
name: section.title,
|
|
307
352
|
href: `#${section.id}`,
|
|
308
353
|
grade: section.grade,
|
|
354
|
+
before: section.before,
|
|
309
355
|
line: <Runs paragraph={section.gist} />,
|
|
310
356
|
})),
|
|
311
357
|
}))}
|
|
@@ -314,7 +360,7 @@ function Gist({
|
|
|
314
360
|
);
|
|
315
361
|
}
|
|
316
362
|
|
|
317
|
-
/** A section: the
|
|
363
|
+
/** A section: the card, with today and after and the checklist handed in after its blocks. */
|
|
318
364
|
function Section({ section, labels }: { section: ReadSection; labels: ReadLabels }) {
|
|
319
365
|
const changes = (section.changes ?? []).filter(
|
|
320
366
|
(change) => filled(change?.now) || filled(change?.after),
|
|
@@ -334,16 +380,21 @@ function Section({ section, labels }: { section: ReadSection; labels: ReadLabels
|
|
|
334
380
|
: block,
|
|
335
381
|
);
|
|
336
382
|
const sources = (section.sources ?? []).filter((source) => source?.label?.trim());
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
383
|
+
const {
|
|
384
|
+
gist: _gist,
|
|
385
|
+
before: _before,
|
|
386
|
+
changes: _changes,
|
|
387
|
+
recommend: _recommend,
|
|
388
|
+
...card
|
|
389
|
+
} = section;
|
|
390
|
+
const topic: ReadTopic = {
|
|
391
|
+
...card,
|
|
340
392
|
why: asNode(section.why),
|
|
341
393
|
whyLabel: section.whyLabel ?? labels.why,
|
|
342
394
|
gradeWord: section.gradeWord ?? labels.grade,
|
|
343
395
|
blocks,
|
|
344
396
|
sources,
|
|
345
397
|
sourcesLabel: section.sourcesLabel ?? labels.sources,
|
|
346
|
-
noShot: section.noShot ?? true,
|
|
347
398
|
};
|
|
348
399
|
const extra =
|
|
349
400
|
changes.length + recommend.length > 0 ? (
|
|
@@ -355,6 +406,79 @@ function Section({ section, labels }: { section: ReadSection; labels: ReadLabels
|
|
|
355
406
|
return <Topic topic={topic} extra={extra} />;
|
|
356
407
|
}
|
|
357
408
|
|
|
409
|
+
function Markets({ markets }: { markets: ReadMarkets }) {
|
|
410
|
+
return (
|
|
411
|
+
<section className="op-topic op-markets">
|
|
412
|
+
<header className="op-topic-head">
|
|
413
|
+
<div>
|
|
414
|
+
<h3>{markets.title}</h3>
|
|
415
|
+
{markets.lead && <p className="op-def">{markets.lead}</p>}
|
|
416
|
+
</div>
|
|
417
|
+
</header>
|
|
418
|
+
<table className="op-markets-table">
|
|
419
|
+
<thead>
|
|
420
|
+
<tr>
|
|
421
|
+
<th scope="col" className="op-markets-market">
|
|
422
|
+
<span className="op-sr-only">Market</span>
|
|
423
|
+
</th>
|
|
424
|
+
{markets.columns.map((column) => (
|
|
425
|
+
<th scope="col" className="op-markets-col" key={column}>
|
|
426
|
+
{column}
|
|
427
|
+
</th>
|
|
428
|
+
))}
|
|
429
|
+
<th scope="col" className="op-markets-read">
|
|
430
|
+
<span className="op-sr-only">Read</span>
|
|
431
|
+
</th>
|
|
432
|
+
</tr>
|
|
433
|
+
</thead>
|
|
434
|
+
<tbody>
|
|
435
|
+
{markets.rows.map((row) => (
|
|
436
|
+
<tr key={row.market}>
|
|
437
|
+
<th scope="row" className="op-markets-market">
|
|
438
|
+
{row.market}
|
|
439
|
+
{row.place && <span className="op-markets-place">{row.place}</span>}
|
|
440
|
+
</th>
|
|
441
|
+
{markets.columns.map((column, i) => {
|
|
442
|
+
const grade = row.grades[i] ?? null;
|
|
443
|
+
return (
|
|
444
|
+
<td className="op-markets-grade" key={column}>
|
|
445
|
+
<span className="op-markets-label" aria-hidden="true">
|
|
446
|
+
{column}
|
|
447
|
+
</span>
|
|
448
|
+
{grade ? (
|
|
449
|
+
<>
|
|
450
|
+
<span className="op-sr-only">{`${column}, grade ${gradeLabel[grade]}`}</span>
|
|
451
|
+
<span aria-hidden="true">
|
|
452
|
+
<GradeChip grade={grade} />
|
|
453
|
+
</span>
|
|
454
|
+
</>
|
|
455
|
+
) : (
|
|
456
|
+
<>
|
|
457
|
+
<span className="op-sr-only">{`${column}, not read`}</span>
|
|
458
|
+
<span className="op-dash" aria-hidden="true" />
|
|
459
|
+
</>
|
|
460
|
+
)}
|
|
461
|
+
</td>
|
|
462
|
+
);
|
|
463
|
+
})}
|
|
464
|
+
<td className="op-markets-read">{asNode(row.read)}</td>
|
|
465
|
+
</tr>
|
|
466
|
+
))}
|
|
467
|
+
</tbody>
|
|
468
|
+
</table>
|
|
469
|
+
</section>
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function Rented({ rented, label }: { rented: ReadRented; label: string }) {
|
|
474
|
+
return (
|
|
475
|
+
<p className="op-rented">
|
|
476
|
+
<span className="op-rented-label">{rented.label ?? label}</span>
|
|
477
|
+
<span className="op-rented-text">{asNode(rented.text)}</span>
|
|
478
|
+
</p>
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
|
|
358
482
|
function Plan({ plan, labels }: { plan: ReadPlan; labels: ReadLabels }) {
|
|
359
483
|
return (
|
|
360
484
|
<section className="rd-plan" id={plan.id}>
|
|
@@ -388,7 +512,37 @@ function Plan({ plan, labels }: { plan: ReadPlan; labels: ReadLabels }) {
|
|
|
388
512
|
);
|
|
389
513
|
}
|
|
390
514
|
|
|
391
|
-
|
|
515
|
+
function Tier({ tier, labels }: { tier: ReadTier; labels: ReadLabels }) {
|
|
516
|
+
const rented =
|
|
517
|
+
tier.rented && (isRuns(tier.rented.text) ? filled(tier.rented.text) : tier.rented.text != null)
|
|
518
|
+
? tier.rented
|
|
519
|
+
: undefined;
|
|
520
|
+
return (
|
|
521
|
+
<div className="rd-tier">
|
|
522
|
+
<div className="op-part" id={tier.id}>
|
|
523
|
+
<h2>
|
|
524
|
+
{tier.href ? (
|
|
525
|
+
<a href={tier.href} {...newTabProps(tier.href, true)}>
|
|
526
|
+
{tier.title}
|
|
527
|
+
<ArrowUpRight size={14} aria-hidden="true" />
|
|
528
|
+
</a>
|
|
529
|
+
) : (
|
|
530
|
+
tier.title
|
|
531
|
+
)}
|
|
532
|
+
</h2>
|
|
533
|
+
{tier.lead && <p>{tier.lead}</p>}
|
|
534
|
+
</div>
|
|
535
|
+
{tier.markets && tier.markets.rows.length > 0 && <Markets markets={tier.markets} />}
|
|
536
|
+
{tier.sections.map((section) => (
|
|
537
|
+
<Section section={section} labels={labels} key={section.id} />
|
|
538
|
+
))}
|
|
539
|
+
{rented && <Rented rented={rented} label={labels.rented} />}
|
|
540
|
+
</div>
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
export function TheRead({
|
|
545
|
+
access,
|
|
392
546
|
title,
|
|
393
547
|
preparedFor,
|
|
394
548
|
date,
|
|
@@ -402,13 +556,36 @@ export function Read({
|
|
|
402
556
|
plan,
|
|
403
557
|
close,
|
|
404
558
|
labels: overrides,
|
|
405
|
-
|
|
559
|
+
print,
|
|
560
|
+
}: TheReadProps) {
|
|
406
561
|
const labels = { ...readLabels, ...overrides };
|
|
407
562
|
const shownTiers = tiers.filter((tier) => tier.sections.length > 0);
|
|
563
|
+
const shownNote = filled(note) ? note : access === "public" ? labels.publicNote : undefined;
|
|
564
|
+
const signOff = Boolean(close.signOff && close.person);
|
|
408
565
|
return (
|
|
409
|
-
<div className="op rd">
|
|
566
|
+
<div className="op rd" data-access={access}>
|
|
410
567
|
<article className="op-doc rd-doc">
|
|
411
568
|
<header className="op-head">
|
|
569
|
+
{print && (
|
|
570
|
+
<div className="op-top">
|
|
571
|
+
<span className="op-mark">
|
|
572
|
+
{print.sender.href ? (
|
|
573
|
+
<a href={print.sender.href}>{print.sender.name}</a>
|
|
574
|
+
) : (
|
|
575
|
+
print.sender.name
|
|
576
|
+
)}
|
|
577
|
+
<span className="op-run" aria-hidden="true" />
|
|
578
|
+
</span>
|
|
579
|
+
<span className="op-x" aria-hidden="true">
|
|
580
|
+
×
|
|
581
|
+
</span>
|
|
582
|
+
<img
|
|
583
|
+
className="op-recipient"
|
|
584
|
+
src={print.recipient.logoSrc}
|
|
585
|
+
alt={print.recipient.logoAlt}
|
|
586
|
+
/>
|
|
587
|
+
</div>
|
|
588
|
+
)}
|
|
412
589
|
<p className="op-meta">
|
|
413
590
|
{labels.preparedFor} <b>{preparedFor}</b>
|
|
414
591
|
<span className="op-meta-sep" aria-hidden="true">
|
|
@@ -425,36 +602,34 @@ export function Read({
|
|
|
425
602
|
id={gistId}
|
|
426
603
|
title={gistTitle}
|
|
427
604
|
lead={gistLead}
|
|
428
|
-
note={
|
|
605
|
+
note={shownNote}
|
|
429
606
|
gradeKey={gradeKey}
|
|
430
607
|
labels={labels}
|
|
431
608
|
/>
|
|
432
609
|
)}
|
|
433
610
|
{plan.steps.length > 0 && <Plan plan={plan} labels={labels} />}
|
|
434
611
|
{shownTiers.map((tier) => (
|
|
435
|
-
<
|
|
436
|
-
<div className="op-part" id={tier.id}>
|
|
437
|
-
<h2>
|
|
438
|
-
{tier.href ? (
|
|
439
|
-
<a href={tier.href} {...newTabProps(tier.href, true)}>
|
|
440
|
-
{tier.title}
|
|
441
|
-
<ArrowUpRight size={14} aria-hidden="true" />
|
|
442
|
-
</a>
|
|
443
|
-
) : (
|
|
444
|
-
tier.title
|
|
445
|
-
)}
|
|
446
|
-
</h2>
|
|
447
|
-
{tier.lead && <p>{tier.lead}</p>}
|
|
448
|
-
</div>
|
|
449
|
-
{tier.sections.map((section) => (
|
|
450
|
-
<Section section={section} labels={labels} key={section.id} />
|
|
451
|
-
))}
|
|
452
|
-
</div>
|
|
612
|
+
<Tier tier={tier} labels={labels} key={tier.title} />
|
|
453
613
|
))}
|
|
454
614
|
{filled(close.line) && <Paragraph paragraph={close.line} className="rd-closing" />}
|
|
455
|
-
{
|
|
615
|
+
{(signOff || print?.twin) && (
|
|
456
616
|
<footer className="op-close rd-close">
|
|
457
|
-
|
|
617
|
+
{signOff && close.signOff && close.person && (
|
|
618
|
+
<SignOff signOff={close.signOff} person={close.person} />
|
|
619
|
+
)}
|
|
620
|
+
{print?.twin && (
|
|
621
|
+
<div className="op-twin">
|
|
622
|
+
<div className="op-twin-text">
|
|
623
|
+
{print.twin.line}
|
|
624
|
+
<b>{print.twin.domain}</b>
|
|
625
|
+
</div>
|
|
626
|
+
<img
|
|
627
|
+
className="op-qr"
|
|
628
|
+
src={print.twin.qrSrc}
|
|
629
|
+
alt={`QR code for ${print.twin.domain}`}
|
|
630
|
+
/>
|
|
631
|
+
</div>
|
|
632
|
+
)}
|
|
458
633
|
</footer>
|
|
459
634
|
)}
|
|
460
635
|
</article>
|