@deskwork/studio 0.12.1 → 0.13.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/dist/components/scrapbook-item.d.ts +9 -1
- package/dist/components/scrapbook-item.d.ts.map +1 -1
- package/dist/components/scrapbook-item.js +11 -2
- package/dist/components/scrapbook-item.js.map +1 -1
- package/dist/data/glossary.json +62 -0
- package/dist/lib/glossary-helper.d.ts +16 -0
- package/dist/lib/glossary-helper.d.ts.map +1 -0
- package/dist/lib/glossary-helper.js +26 -0
- package/dist/lib/glossary-helper.js.map +1 -0
- package/dist/pages/chrome.d.ts +24 -13
- package/dist/pages/chrome.d.ts.map +1 -1
- package/dist/pages/chrome.js +25 -24
- package/dist/pages/chrome.js.map +1 -1
- package/dist/pages/chrome.ts +38 -27
- package/dist/pages/content-detail.js +1 -1
- package/dist/pages/content-detail.js.map +1 -1
- package/dist/pages/content-detail.ts +1 -1
- package/dist/pages/entry-review.js +2 -2
- package/dist/pages/entry-review.js.map +1 -1
- package/dist/pages/entry-review.ts +2 -2
- package/dist/pages/html.d.ts +2 -0
- package/dist/pages/html.d.ts.map +1 -1
- package/dist/pages/html.js +2 -0
- package/dist/pages/html.js.map +1 -1
- package/dist/pages/html.ts +4 -0
- package/dist/pages/layout.d.ts.map +1 -1
- package/dist/pages/layout.js +6 -0
- package/dist/pages/layout.js.map +1 -1
- package/dist/pages/layout.ts +7 -0
- package/dist/pages/review-scrapbook-drawer.d.ts +7 -0
- package/dist/pages/review-scrapbook-drawer.d.ts.map +1 -1
- package/dist/pages/review-scrapbook-drawer.js +45 -6
- package/dist/pages/review-scrapbook-drawer.js.map +1 -1
- package/dist/pages/review-scrapbook-drawer.ts +50 -6
- package/dist/pages/review.d.ts.map +1 -1
- package/dist/pages/review.js +168 -41
- package/dist/pages/review.js.map +1 -1
- package/dist/pages/review.ts +192 -41
- package/dist/pages/scrapbook.d.ts +7 -14
- package/dist/pages/scrapbook.d.ts.map +1 -1
- package/dist/pages/scrapbook.js +352 -193
- package/dist/pages/scrapbook.js.map +1 -1
- package/dist/pages/scrapbook.ts +390 -222
- package/dist/pages/shortform.js +1 -1
- package/dist/pages/shortform.js.map +1 -1
- package/dist/pages/shortform.ts +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +10 -13
- package/dist/server.js.map +1 -1
- package/package.json +4 -4
package/dist/pages/layout.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { escapeHtml } from "./html.js";
|
|
6
6
|
import { clientScriptTag, viteClientTag } from "../lib/client-script.js";
|
|
7
|
+
import glossary from '../data/glossary.json' with { type: 'json' };
|
|
7
8
|
export function layout(options) {
|
|
8
9
|
const { title, cssHrefs, bodyHtml, bodyAttrs, embeddedJson, scriptModules, } = options;
|
|
9
10
|
const cssTags = cssHrefs
|
|
@@ -16,10 +17,14 @@ export function layout(options) {
|
|
|
16
17
|
return ` <script type="application/json"${idPart}${attrPart}>${escapeForScriptTag(JSON.stringify(j.data))}</script>`;
|
|
17
18
|
})
|
|
18
19
|
.join('\n');
|
|
20
|
+
// Inline glossary data into every page so the tooltip client can access it.
|
|
21
|
+
const glossaryInline = `<script>window.__GLOSSARY__ = ${JSON.stringify(glossary).replace(/</g, '\\u003c')};</script>`;
|
|
19
22
|
const hmrTag = viteClientTag();
|
|
23
|
+
const glossaryClientTag = clientScriptTag('glossary-tooltip');
|
|
20
24
|
const scriptTags = [
|
|
21
25
|
...(hmrTag ? [` ${hmrTag}`] : []),
|
|
22
26
|
...scriptModules.map((name) => ` ${clientScriptTag(name)}`),
|
|
27
|
+
` ${glossaryClientTag}`,
|
|
23
28
|
].join('\n');
|
|
24
29
|
const bodyOpen = bodyAttrs ? `<body ${bodyAttrs}>` : '<body>';
|
|
25
30
|
return `<!DOCTYPE html>
|
|
@@ -30,6 +35,7 @@ export function layout(options) {
|
|
|
30
35
|
<meta name="robots" content="noindex">
|
|
31
36
|
<title>${escapeHtml(title)}</title>
|
|
32
37
|
${cssTags}
|
|
38
|
+
${glossaryInline}
|
|
33
39
|
</head>
|
|
34
40
|
${bodyOpen}
|
|
35
41
|
${bodyHtml}
|
package/dist/pages/layout.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layout.js","sourceRoot":"","sources":["../../src/pages/layout.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"layout.js","sourceRoot":"","sources":["../../src/pages/layout.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACzE,OAAO,QAAQ,MAAM,uBAAuB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AAoCnE,MAAM,UAAU,MAAM,CAAC,OAAsB;IAC3C,MAAM,EACJ,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,aAAa,GACd,GAAG,OAAO,CAAC;IAEZ,MAAM,OAAO,GAAG,QAAQ;SACrB,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,oCAAoC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;SACvE,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,MAAM,QAAQ,GAAG,CAAC,YAAY,IAAI,EAAE,CAAC;SAClC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,MAAM,QAAQ,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5C,MAAM,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QACvD,OAAO,sCAAsC,MAAM,GAAG,QAAQ,IAAI,kBAAkB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,WAAW,CAAC;IAC1H,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,4EAA4E;IAC5E,MAAM,cAAc,GAAG,iCAAiC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,YAAY,CAAC;IAEtH,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;IAC/B,MAAM,iBAAiB,GAAG,eAAe,CAAC,kBAAkB,CAAC,CAAC;IAC9D,MAAM,UAAU,GAAG;QACjB,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACpC,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9D,OAAO,iBAAiB,EAAE;KAC3B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEb,MAAM,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAC,SAAS,SAAS,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC;IAE9D,OAAO;;;;;;aAMI,UAAU,CAAC,KAAK,CAAC;EAC5B,OAAO;MACH,cAAc;;IAEhB,QAAQ;EACV,QAAQ;EACR,QAAQ;EACR,UAAU;;;CAGX,CAAC;AACF,CAAC;AAED,SAAS,UAAU,CAAC,CAAS;IAC3B,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC;AACvB,CAAC;AAED;;;;GAIG;AACH,SAAS,kBAAkB,CAAC,IAAY;IACtC,OAAO,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,QAAQ,CAAC,CAAC;AACrD,CAAC"}
|
package/dist/pages/layout.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { escapeHtml } from './html.ts';
|
|
7
7
|
import { clientScriptTag, viteClientTag } from '../lib/client-script.ts';
|
|
8
|
+
import glossary from '../data/glossary.json' with { type: 'json' };
|
|
8
9
|
|
|
9
10
|
export interface EmbeddedJson {
|
|
10
11
|
/** `id` attribute of the `<script type="application/json">` tag. */
|
|
@@ -62,10 +63,15 @@ export function layout(options: LayoutOptions): string {
|
|
|
62
63
|
})
|
|
63
64
|
.join('\n');
|
|
64
65
|
|
|
66
|
+
// Inline glossary data into every page so the tooltip client can access it.
|
|
67
|
+
const glossaryInline = `<script>window.__GLOSSARY__ = ${JSON.stringify(glossary).replace(/</g, '\\u003c')};</script>`;
|
|
68
|
+
|
|
65
69
|
const hmrTag = viteClientTag();
|
|
70
|
+
const glossaryClientTag = clientScriptTag('glossary-tooltip');
|
|
66
71
|
const scriptTags = [
|
|
67
72
|
...(hmrTag ? [` ${hmrTag}`] : []),
|
|
68
73
|
...scriptModules.map((name) => ` ${clientScriptTag(name)}`),
|
|
74
|
+
` ${glossaryClientTag}`,
|
|
69
75
|
].join('\n');
|
|
70
76
|
|
|
71
77
|
const bodyOpen = bodyAttrs ? `<body ${bodyAttrs}>` : '<body>';
|
|
@@ -78,6 +84,7 @@ export function layout(options: LayoutOptions): string {
|
|
|
78
84
|
<meta name="robots" content="noindex">
|
|
79
85
|
<title>${escapeHtml(title)}</title>
|
|
80
86
|
${cssTags}
|
|
87
|
+
${glossaryInline}
|
|
81
88
|
</head>
|
|
82
89
|
${bodyOpen}
|
|
83
90
|
${bodyHtml}
|
|
@@ -11,6 +11,13 @@
|
|
|
11
11
|
* directory via the index. This makes writingcontrol-shape entries
|
|
12
12
|
* (slug != fs path) list their items at the actual file location.
|
|
13
13
|
* Falls back to slug-template addressing for unbound / legacy entries.
|
|
14
|
+
*
|
|
15
|
+
* Issue #154 Dispatch D: this drawer is now a real bottom-anchored
|
|
16
|
+
* expandable drawer. The handle (header) is a clickable role=button
|
|
17
|
+
* that toggles `body[data-drawer]`; the drawer height transitions
|
|
18
|
+
* 4rem (collapsed) → 22rem (expanded). The standalone-viewer link is
|
|
19
|
+
* demoted to a small inline affordance — the primary action is now
|
|
20
|
+
* "expand the drawer" rather than "navigate away."
|
|
14
21
|
*/
|
|
15
22
|
import type { ContentIndex } from '@deskwork/core/content-index';
|
|
16
23
|
import type { CalendarEntry } from '@deskwork/core/types';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"review-scrapbook-drawer.d.ts","sourceRoot":"","sources":["../../src/pages/review-scrapbook-drawer.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"review-scrapbook-drawer.d.ts","sourceRoot":"","sources":["../../src/pages/review-scrapbook-drawer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAYH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAOtD,OAAO,EAA4B,KAAK,OAAO,EAAE,MAAM,WAAW,CAAC;AAqFnE,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,aAAa,GAAG,IAAI,EAC3B,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,YAAY,GACnB,OAAO,CA6DT"}
|
|
@@ -11,13 +11,20 @@
|
|
|
11
11
|
* directory via the index. This makes writingcontrol-shape entries
|
|
12
12
|
* (slug != fs path) list their items at the actual file location.
|
|
13
13
|
* Falls back to slug-template addressing for unbound / legacy entries.
|
|
14
|
+
*
|
|
15
|
+
* Issue #154 Dispatch D: this drawer is now a real bottom-anchored
|
|
16
|
+
* expandable drawer. The handle (header) is a clickable role=button
|
|
17
|
+
* that toggles `body[data-drawer]`; the drawer height transitions
|
|
18
|
+
* 4rem (collapsed) → 22rem (expanded). The standalone-viewer link is
|
|
19
|
+
* demoted to a small inline affordance — the primary action is now
|
|
20
|
+
* "expand the drawer" rather than "navigate away."
|
|
14
21
|
*/
|
|
15
22
|
import { readFileSync } from 'node:fs';
|
|
16
23
|
import { join } from 'node:path';
|
|
17
24
|
import { listScrapbook, listScrapbookAtDir, scrapbookDirForEntry, } from '@deskwork/core/scrapbook';
|
|
18
25
|
import { resolveContentDir } from '@deskwork/core/paths';
|
|
19
26
|
import { renderEmptyScrapbookRow, renderReadOnlyScrapbookRow, scrapbookViewerUrl, } from "../components/scrapbook-item.js";
|
|
20
|
-
import { html, unsafe } from "./html.js";
|
|
27
|
+
import { html, unsafe, escapeHtml } from "./html.js";
|
|
21
28
|
/**
|
|
22
29
|
* Build an inline-text loader for the shared scrapbook-item renderer.
|
|
23
30
|
* Reads at most `maxBytes` from a file inside the scrapbook directory
|
|
@@ -48,11 +55,32 @@ function makeInlineTextLoader(ctx, site, entry, slug, index) {
|
|
|
48
55
|
}
|
|
49
56
|
function renderScrapbookDrawerItems(site, slug, items, loader) {
|
|
50
57
|
if (items.length === 0) {
|
|
51
|
-
return renderEmptyScrapbookRow();
|
|
58
|
+
return renderEmptyScrapbookRow({ site, path: slug });
|
|
52
59
|
}
|
|
53
60
|
const rows = items.map((item) => renderReadOnlyScrapbookRow({ site, path: slug }, item, { inlinePreviewLoader: loader }));
|
|
54
61
|
return unsafe(rows.map((r) => r.__raw).join(''));
|
|
55
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Build the "peek" line shown in the collapsed handle — up to 3 item
|
|
65
|
+
* filenames separated by `·`, plus a `+ N more` suffix when there are
|
|
66
|
+
* additional items. Empty scrapbook renders an inline empty-state hint
|
|
67
|
+
* so the operator still sees the surface.
|
|
68
|
+
*/
|
|
69
|
+
function renderPeek(items, secretItems) {
|
|
70
|
+
const all = [...items, ...secretItems];
|
|
71
|
+
if (all.length === 0) {
|
|
72
|
+
return '<span class="er-scrapbook-drawer-peek-empty">(empty — drop research here)</span>';
|
|
73
|
+
}
|
|
74
|
+
const shown = all
|
|
75
|
+
.slice(0, 3)
|
|
76
|
+
.map((i) => `<span>${escapeHtml(i.name)}</span>`)
|
|
77
|
+
.join('<span class="sep">·</span>');
|
|
78
|
+
const remaining = all.length - 3;
|
|
79
|
+
const suffix = remaining > 0
|
|
80
|
+
? `<span class="sep">·</span><span>+ ${remaining} more</span>`
|
|
81
|
+
: '';
|
|
82
|
+
return shown + suffix;
|
|
83
|
+
}
|
|
56
84
|
export function renderScrapbookDrawer(ctx, site, entry, slug, index) {
|
|
57
85
|
const summary = (() => {
|
|
58
86
|
try {
|
|
@@ -75,13 +103,24 @@ export function renderScrapbookDrawer(ctx, site, entry, slug, index) {
|
|
|
75
103
|
const loader = makeInlineTextLoader(ctx, site, entry, slug, index);
|
|
76
104
|
return unsafe(html `
|
|
77
105
|
<aside class="er-scrapbook-drawer" data-scrapbook-drawer aria-label="Scrapbook for this entry">
|
|
78
|
-
<header class="er-scrapbook-drawer-
|
|
79
|
-
|
|
106
|
+
<header class="er-scrapbook-drawer-handle" data-drawer-toggle role="button" tabindex="0"
|
|
107
|
+
aria-expanded="false" aria-controls="er-scrapbook-drawer-body">
|
|
108
|
+
<span class="er-scrapbook-drawer-kicker"><em>§</em> Scrapbook</span>
|
|
80
109
|
<span class="er-scrapbook-drawer-count">${total} ${total === 1 ? 'item' : 'items'}</span>
|
|
110
|
+
<span class="er-scrapbook-drawer-peek" aria-hidden="true">
|
|
111
|
+
${unsafe(renderPeek(items, secretItems))}
|
|
112
|
+
</span>
|
|
81
113
|
<a class="er-scrapbook-drawer-open" href="${scrapbookViewerUrl({ site, path: slug })}"
|
|
82
|
-
title="Open the standalone scrapbook viewer"
|
|
114
|
+
title="Open the standalone scrapbook viewer"
|
|
115
|
+
onclick="event.stopPropagation()">open viewer ↗</a>
|
|
116
|
+
<button class="er-scrapbook-drawer-toggle" type="button" data-drawer-toggle
|
|
117
|
+
aria-controls="er-scrapbook-drawer-body" tabindex="-1">
|
|
118
|
+
<span data-toggle-label>Expand</span>
|
|
119
|
+
<span class="chev" aria-hidden="true">▾</span>
|
|
120
|
+
</button>
|
|
83
121
|
</header>
|
|
84
|
-
<div class="er-scrapbook-drawer-body"
|
|
122
|
+
<div class="er-scrapbook-drawer-body" id="er-scrapbook-drawer-body"
|
|
123
|
+
role="region" aria-label="scrapbook items">
|
|
85
124
|
${renderScrapbookDrawerItems(site, slug, items, loader)}
|
|
86
125
|
${secretItems.length > 0
|
|
87
126
|
? unsafe(html `
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"review-scrapbook-drawer.js","sourceRoot":"","sources":["../../src/pages/review-scrapbook-drawer.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"review-scrapbook-drawer.js","sourceRoot":"","sources":["../../src/pages/review-scrapbook-drawer.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,6 +11,13 @@
|
|
|
11
11
|
* directory via the index. This makes writingcontrol-shape entries
|
|
12
12
|
* (slug != fs path) list their items at the actual file location.
|
|
13
13
|
* Falls back to slug-template addressing for unbound / legacy entries.
|
|
14
|
+
*
|
|
15
|
+
* Issue #154 Dispatch D: this drawer is now a real bottom-anchored
|
|
16
|
+
* expandable drawer. The handle (header) is a clickable role=button
|
|
17
|
+
* that toggles `body[data-drawer]`; the drawer height transitions
|
|
18
|
+
* 4rem (collapsed) → 22rem (expanded). The standalone-viewer link is
|
|
19
|
+
* demoted to a small inline affordance — the primary action is now
|
|
20
|
+
* "expand the drawer" rather than "navigate away."
|
|
14
21
|
*/
|
|
15
22
|
|
|
16
23
|
import { readFileSync } from 'node:fs';
|
|
@@ -32,7 +39,7 @@ import {
|
|
|
32
39
|
scrapbookViewerUrl,
|
|
33
40
|
type InlineTextLoader,
|
|
34
41
|
} from '../components/scrapbook-item.ts';
|
|
35
|
-
import { html, unsafe, type RawHtml } from './html.ts';
|
|
42
|
+
import { html, unsafe, escapeHtml, type RawHtml } from './html.ts';
|
|
36
43
|
|
|
37
44
|
/**
|
|
38
45
|
* Build an inline-text loader for the shared scrapbook-item renderer.
|
|
@@ -79,7 +86,7 @@ function renderScrapbookDrawerItems(
|
|
|
79
86
|
loader: InlineTextLoader,
|
|
80
87
|
): RawHtml {
|
|
81
88
|
if (items.length === 0) {
|
|
82
|
-
return renderEmptyScrapbookRow();
|
|
89
|
+
return renderEmptyScrapbookRow({ site, path: slug });
|
|
83
90
|
}
|
|
84
91
|
const rows = items.map((item) =>
|
|
85
92
|
renderReadOnlyScrapbookRow(
|
|
@@ -91,6 +98,32 @@ function renderScrapbookDrawerItems(
|
|
|
91
98
|
return unsafe(rows.map((r) => r.__raw).join(''));
|
|
92
99
|
}
|
|
93
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Build the "peek" line shown in the collapsed handle — up to 3 item
|
|
103
|
+
* filenames separated by `·`, plus a `+ N more` suffix when there are
|
|
104
|
+
* additional items. Empty scrapbook renders an inline empty-state hint
|
|
105
|
+
* so the operator still sees the surface.
|
|
106
|
+
*/
|
|
107
|
+
function renderPeek(
|
|
108
|
+
items: readonly ScrapbookItem[],
|
|
109
|
+
secretItems: readonly ScrapbookItem[],
|
|
110
|
+
): string {
|
|
111
|
+
const all = [...items, ...secretItems];
|
|
112
|
+
if (all.length === 0) {
|
|
113
|
+
return '<span class="er-scrapbook-drawer-peek-empty">(empty — drop research here)</span>';
|
|
114
|
+
}
|
|
115
|
+
const shown = all
|
|
116
|
+
.slice(0, 3)
|
|
117
|
+
.map((i) => `<span>${escapeHtml(i.name)}</span>`)
|
|
118
|
+
.join('<span class="sep">·</span>');
|
|
119
|
+
const remaining = all.length - 3;
|
|
120
|
+
const suffix =
|
|
121
|
+
remaining > 0
|
|
122
|
+
? `<span class="sep">·</span><span>+ ${remaining} more</span>`
|
|
123
|
+
: '';
|
|
124
|
+
return shown + suffix;
|
|
125
|
+
}
|
|
126
|
+
|
|
94
127
|
export function renderScrapbookDrawer(
|
|
95
128
|
ctx: StudioContext,
|
|
96
129
|
site: string,
|
|
@@ -126,13 +159,24 @@ export function renderScrapbookDrawer(
|
|
|
126
159
|
|
|
127
160
|
return unsafe(html`
|
|
128
161
|
<aside class="er-scrapbook-drawer" data-scrapbook-drawer aria-label="Scrapbook for this entry">
|
|
129
|
-
<header class="er-scrapbook-drawer-
|
|
130
|
-
|
|
162
|
+
<header class="er-scrapbook-drawer-handle" data-drawer-toggle role="button" tabindex="0"
|
|
163
|
+
aria-expanded="false" aria-controls="er-scrapbook-drawer-body">
|
|
164
|
+
<span class="er-scrapbook-drawer-kicker"><em>§</em> Scrapbook</span>
|
|
131
165
|
<span class="er-scrapbook-drawer-count">${total} ${total === 1 ? 'item' : 'items'}</span>
|
|
166
|
+
<span class="er-scrapbook-drawer-peek" aria-hidden="true">
|
|
167
|
+
${unsafe(renderPeek(items, secretItems))}
|
|
168
|
+
</span>
|
|
132
169
|
<a class="er-scrapbook-drawer-open" href="${scrapbookViewerUrl({ site, path: slug })}"
|
|
133
|
-
title="Open the standalone scrapbook viewer"
|
|
170
|
+
title="Open the standalone scrapbook viewer"
|
|
171
|
+
onclick="event.stopPropagation()">open viewer ↗</a>
|
|
172
|
+
<button class="er-scrapbook-drawer-toggle" type="button" data-drawer-toggle
|
|
173
|
+
aria-controls="er-scrapbook-drawer-body" tabindex="-1">
|
|
174
|
+
<span data-toggle-label>Expand</span>
|
|
175
|
+
<span class="chev" aria-hidden="true">▾</span>
|
|
176
|
+
</button>
|
|
134
177
|
</header>
|
|
135
|
-
<div class="er-scrapbook-drawer-body"
|
|
178
|
+
<div class="er-scrapbook-drawer-body" id="er-scrapbook-drawer-body"
|
|
179
|
+
role="region" aria-label="scrapbook items">
|
|
136
180
|
${renderScrapbookDrawerItems(site, slug, items, loader)}
|
|
137
181
|
${
|
|
138
182
|
secretItems.length > 0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"review.d.ts","sourceRoot":"","sources":["../../src/pages/review.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAcH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAEjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAStD;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,YAAY,CAAC;AAE/D,UAAU,WAAW;IACnB,oEAAoE;IACpE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,wDAAwD;IACxD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,2DAA2D;IAC3D,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"review.d.ts","sourceRoot":"","sources":["../../src/pages/review.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAcH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAEjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAStD;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,YAAY,CAAC;AAE/D,UAAU,WAAW;IACnB,oEAAoE;IACpE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,wDAAwD;IACxD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,2DAA2D;IAC3D,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AA8a7C,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,aAAa,EAClB,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,WAAW,EAClB,QAAQ,CAAC,EAAE,iBAAiB,GAC3B,OAAO,CAAC,MAAM,CAAC,CAiMjB"}
|
package/dist/pages/review.js
CHANGED
|
@@ -28,7 +28,7 @@ import { splitOutline } from '@deskwork/core/outline-split';
|
|
|
28
28
|
import { html, unsafe } from "./html.js";
|
|
29
29
|
import { layout } from "./layout.js";
|
|
30
30
|
import { renderEditorialFolio } from "./chrome.js";
|
|
31
|
-
import { escapeHtml } from "./html.js";
|
|
31
|
+
import { escapeHtml, gloss } from "./html.js";
|
|
32
32
|
import { renderScrapbookDrawer } from "./review-scrapbook-drawer.js";
|
|
33
33
|
import { existsSync } from 'node:fs';
|
|
34
34
|
import { resolveCalendarPath } from '@deskwork/core/paths';
|
|
@@ -123,23 +123,46 @@ function pendingSkillCmd(workflow) {
|
|
|
123
123
|
}
|
|
124
124
|
return '';
|
|
125
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Wrap an action button in a `.er-shortcut-chip-wrap` span carrying a
|
|
128
|
+
* small chord chip beneath the button. The chord style mirrors the
|
|
129
|
+
* shortcuts modal's verbatim two-tap rendering (e.g. `<kbd>a</kbd>
|
|
130
|
+
* <kbd>a</kbd>` for approve) — the destructive-shortcut UX, post-#108,
|
|
131
|
+
* is bare-letter double-tap (no Cmd/Ctrl modifier; verified in the
|
|
132
|
+
* keybinding handler at editorial-review-client.ts).
|
|
133
|
+
*
|
|
134
|
+
* The chip is hidden on narrow viewports via the cross-surface CSS
|
|
135
|
+
* media query — the wrap stays in the markup at every breakpoint so
|
|
136
|
+
* the column flex it triggers (`.er-strip-right > *:has(.er-shortcut-chip)`)
|
|
137
|
+
* is consistent with the chip's visibility state.
|
|
138
|
+
*
|
|
139
|
+
* Issue 5 — keyboard-shortcut chips on action buttons.
|
|
140
|
+
*/
|
|
141
|
+
function shortcutChipWrap(buttonHtml, letter) {
|
|
142
|
+
return html `<span class="er-shortcut-chip-wrap">${unsafe(buttonHtml)}<small class="er-shortcut-chip"><kbd>${letter}</kbd><kbd>${letter}</kbd></small></span>`;
|
|
143
|
+
}
|
|
126
144
|
function renderControlsRight(workflow) {
|
|
127
145
|
const isActive = workflow.state === 'open' || workflow.state === 'in-review';
|
|
128
146
|
const isApproved = workflow.state === 'approved';
|
|
129
147
|
const isIterating = workflow.state === 'iterating';
|
|
130
148
|
const isTerminal = workflow.state === 'applied' || workflow.state === 'cancelled';
|
|
131
149
|
const buttons = [];
|
|
132
|
-
|
|
150
|
+
// Issue 7 — emit the edit-mode disclosure label next to the Edit
|
|
151
|
+
// button. The client (editorial-review-client.ts) flips both the
|
|
152
|
+
// `data-mode` attribute AND inner text on each toggle. Initial state
|
|
153
|
+
// matches the surface's initial mode (preview).
|
|
154
|
+
buttons.push(html `<button class="er-btn er-btn-small" data-action="toggle-edit" type="button">Edit</button><span class="er-edit-mode-label" data-mode="preview">preview</span>`);
|
|
133
155
|
if (isActive) {
|
|
134
|
-
|
|
135
|
-
buttons.push(html `<button class="er-btn er-btn-small" data-action="
|
|
136
|
-
buttons.push(html `<button class="er-btn er-btn-small
|
|
156
|
+
// Issue 5 — wrap each destructive action button with its chord chip.
|
|
157
|
+
buttons.push(shortcutChipWrap(html `<button class="er-btn er-btn-small er-btn-approve" data-action="approve" type="button">Approve</button>`, 'a'));
|
|
158
|
+
buttons.push(shortcutChipWrap(html `<button class="er-btn er-btn-small" data-action="iterate" type="button">Iterate</button>`, 'i'));
|
|
159
|
+
buttons.push(shortcutChipWrap(html `<button class="er-btn er-btn-small er-btn-reject" data-action="reject" type="button">Reject</button>`, 'r'));
|
|
137
160
|
}
|
|
138
161
|
if (isApproved) {
|
|
139
162
|
const applyCmd = pendingSkillCmd(workflow);
|
|
140
163
|
buttons.push(html `<span class="er-pending-state">awaiting apply…</span>`);
|
|
141
164
|
buttons.push(html `<button class="er-btn er-btn-small" data-action="copy-cmd" data-cmd="${applyCmd}" title="Copy ${applyCmd} to clipboard" type="button">copy <code>/deskwork:approve</code></button>`);
|
|
142
|
-
buttons.push(html `<button class="er-btn er-btn-small er-btn-reject" data-action="reject" type="button">Reject</button
|
|
165
|
+
buttons.push(shortcutChipWrap(html `<button class="er-btn er-btn-small er-btn-reject" data-action="reject" type="button">Reject</button>`, 'r'));
|
|
143
166
|
}
|
|
144
167
|
if (isIterating) {
|
|
145
168
|
const iterateCmd = pendingSkillCmd(workflow);
|
|
@@ -160,7 +183,7 @@ function renderError(slug, site, contentKind, message) {
|
|
|
160
183
|
: `/deskwork:review-start --site ${site} ${slug}`;
|
|
161
184
|
const body = html `
|
|
162
185
|
<div data-review-ui="longform">
|
|
163
|
-
${renderEditorialFolio('
|
|
186
|
+
${renderEditorialFolio('longform', `longform · ${slug}`)}
|
|
164
187
|
<div class="er-error">
|
|
165
188
|
<h1>No galley to review.</h1>
|
|
166
189
|
<p><strong>Slug:</strong> <code>${slug}</code></p>
|
|
@@ -194,6 +217,8 @@ function renderShortcutsOverlay() {
|
|
|
194
217
|
<dt><kbd>i</kbd> <kbd>i</kbd></dt><dd>iterate <em>— press twice within 500ms</em></dd>
|
|
195
218
|
<dt><kbd>r</kbd> <kbd>r</kbd></dt><dd>reject <em>— press twice within 500ms</em></dd>
|
|
196
219
|
<dt><kbd>j</kbd> / <kbd>k</kbd></dt><dd>next / previous margin note</dd>
|
|
220
|
+
<dt><kbd>shift</kbd><kbd>F</kbd></dt><dd>focus mode <em>(edit mode only)</em></dd>
|
|
221
|
+
<dt><kbd>shift</kbd><kbd>M</kbd></dt><dd>show / hide margin notes column <em>— or click the chevron in the head when visible, or the pull tab on the right edge when stowed</em></dd>
|
|
197
222
|
<dt><kbd>?</kbd></dt><dd>this panel</dd>
|
|
198
223
|
<dt><kbd>esc</kbd></dt><dd>close / cancel composer</dd>
|
|
199
224
|
</dl>
|
|
@@ -201,11 +226,44 @@ function renderShortcutsOverlay() {
|
|
|
201
226
|
</div>
|
|
202
227
|
</div>`);
|
|
203
228
|
}
|
|
229
|
+
/* Issue #159 — marginalia stow affordance.
|
|
230
|
+
*
|
|
231
|
+
* The toggle for "show / hide the margin-notes column" lives ON the
|
|
232
|
+
* marginalia component, not in a generic toolbar. Two paired
|
|
233
|
+
* affordances drive the same state:
|
|
234
|
+
*
|
|
235
|
+
* - `.er-marginalia-stow` — chevron button INSIDE the marginalia
|
|
236
|
+
* head (next to "Margin notes" label). Clicking it stows the
|
|
237
|
+
* column. Visible only when marginalia is visible (the head is
|
|
238
|
+
* inside `.er-marginalia`, which is `display: none` when stowed).
|
|
239
|
+
*
|
|
240
|
+
* - `.er-marginalia-tab` — pull tab on the right edge of the
|
|
241
|
+
* viewport, mirroring `.er-outline-tab` on the left edge. Visible
|
|
242
|
+
* ONLY when marginalia is stowed (CSS rule `body[data-marginalia=
|
|
243
|
+
* "hidden"] .er-marginalia-tab { display: block }`). Clicking it
|
|
244
|
+
* unstows.
|
|
245
|
+
*
|
|
246
|
+
* Both affordances + Shift+M dispatch through the same client-side
|
|
247
|
+
* toggleMarginalia handler. Mirrors the outline-drawer's pull-tab
|
|
248
|
+
* pattern so the project's affordance vocabulary stays consistent.
|
|
249
|
+
*/
|
|
250
|
+
function renderMarginaliaTab() {
|
|
251
|
+
return unsafe(html `
|
|
252
|
+
<button class="er-marginalia-tab" data-action="toggle-marginalia" type="button" aria-pressed="true" aria-label="Show margin notes (Shift+M)" title="Show margin notes (Shift+M)">
|
|
253
|
+
<span class="er-marginalia-tab-glyph" aria-hidden="true">‹</span>
|
|
254
|
+
<span class="er-marginalia-tab-label">Notes</span>
|
|
255
|
+
</button>`);
|
|
256
|
+
}
|
|
204
257
|
function renderMarginalia() {
|
|
205
258
|
return unsafe(html `
|
|
206
259
|
<aside class="er-marginalia" data-comments-sidebar aria-label="Margin notes">
|
|
207
|
-
<p class="er-marginalia-head">
|
|
208
|
-
|
|
260
|
+
<p class="er-marginalia-head">
|
|
261
|
+
<button class="er-marginalia-stow" data-action="toggle-marginalia" type="button" aria-pressed="false" aria-label="Hide margin notes (Shift+M)" title="Hide margin notes (Shift+M)">
|
|
262
|
+
<span aria-hidden="true">›</span>
|
|
263
|
+
</button>
|
|
264
|
+
<span class="er-marginalia-head-label">Margin notes</span>
|
|
265
|
+
</p>
|
|
266
|
+
<p class="er-marginalia-empty" data-sidebar-empty>Select text in the draft to leave a <em>margin note</em>.</p>
|
|
209
267
|
<section class="er-marginalia-composer" data-comment-composer hidden aria-label="New margin note">
|
|
210
268
|
<p class="er-marginalia-composer-head">New mark</p>
|
|
211
269
|
<div class="er-marginalia-composer-quote" data-composer-quote></div>
|
|
@@ -230,24 +288,52 @@ function renderMarginalia() {
|
|
|
230
288
|
<ol class="er-marginalia-list" data-sidebar-list></ol>
|
|
231
289
|
</aside>`);
|
|
232
290
|
}
|
|
233
|
-
|
|
291
|
+
/**
|
|
292
|
+
* Issue #154 Dispatch C — the edit-mode chrome was previously a single
|
|
293
|
+
* `.er-edit-mode` block rendered inside `.er-draft-frame` (below
|
|
294
|
+
* `#draft-body`). With the page-grid in place, the natural layout is:
|
|
295
|
+
*
|
|
296
|
+
* - the toolbar (Source/Split/Preview tabs + Outline/Focus/Save/
|
|
297
|
+
* Cancel actions) sticks above `.er-page`, replacing the strip's
|
|
298
|
+
* right-side action buttons;
|
|
299
|
+
* - the source/preview panes take over the article column where
|
|
300
|
+
* `#draft-body` was.
|
|
301
|
+
*
|
|
302
|
+
* `renderEditToolbar` emits the bar that lives ABOVE `.er-page`; the
|
|
303
|
+
* client toggles its `[hidden]` attribute on enter/exit. Keeps
|
|
304
|
+
* `data-edit-toolbar` on the wrapper so `editorial-review-client.ts`'s
|
|
305
|
+
* existing `q('[data-edit-toolbar]')` lookup keeps working.
|
|
306
|
+
*/
|
|
307
|
+
function renderEditToolbar(outlineHasContent) {
|
|
234
308
|
const outlineBtnAttrs = outlineHasContent ? '' : ' hidden';
|
|
235
309
|
return unsafe(html `
|
|
236
|
-
<div class="er-edit-
|
|
237
|
-
<div class="er-edit-
|
|
238
|
-
<
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
<
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
<span class="er-edit-hint" data-edit-hint></span>
|
|
249
|
-
</div>
|
|
310
|
+
<div class="er-edit-toolbar" data-edit-toolbar hidden>
|
|
311
|
+
<div class="er-edit-modes" role="tablist" aria-label="Editor mode">
|
|
312
|
+
<button class="er-edit-mode-btn" data-edit-view="source" type="button" aria-pressed="true">Source</button>
|
|
313
|
+
<button class="er-edit-mode-btn" data-edit-view="split" type="button" aria-pressed="false">Split</button>
|
|
314
|
+
<button class="er-edit-mode-btn" data-edit-view="preview" type="button" aria-pressed="false">Preview</button>
|
|
315
|
+
</div>
|
|
316
|
+
<div class="er-edit-actions">
|
|
317
|
+
<button class="er-btn er-btn-small" data-action="outline-drawer" type="button" title="Show the outline for reference (O)" aria-pressed="false"${unsafe(outlineBtnAttrs)}>Outline ↗</button>
|
|
318
|
+
<button class="er-btn er-btn-small" data-action="focus-mode" type="button" title="Distraction-free mode (Shift+F)" aria-pressed="false">Focus ⛶</button>
|
|
319
|
+
<button class="er-btn er-btn-primary" data-action="save-version" type="button">Save as new version</button>
|
|
320
|
+
<button class="er-btn" data-action="cancel-edit" type="button">Cancel</button>
|
|
321
|
+
<span class="er-edit-hint" data-edit-hint></span>
|
|
250
322
|
</div>
|
|
323
|
+
</div>`);
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Issue #154 Dispatch C — the source/preview panes (and supporting
|
|
327
|
+
* focus-mode affordances + backing textarea) live inside the article
|
|
328
|
+
* column, replacing `#draft-body`. The wrapper keeps the
|
|
329
|
+
* `er-edit-mode` class so existing CSS (panes-host paper-2 background,
|
|
330
|
+
* focus-mode full-viewport canvas) cascades unchanged. Adds
|
|
331
|
+
* `data-edit-panes-host` so the client can flip `[hidden]` on the
|
|
332
|
+
* panes wrapper independently of the toolbar.
|
|
333
|
+
*/
|
|
334
|
+
function renderEditPanes() {
|
|
335
|
+
return unsafe(html `
|
|
336
|
+
<div class="er-edit-mode" data-edit-panes-host hidden>
|
|
251
337
|
<div class="er-edit-panes" data-edit-panes data-view="source">
|
|
252
338
|
<div class="er-edit-source" data-edit-source aria-label="Markdown source"></div>
|
|
253
339
|
<div class="er-edit-preview" data-edit-preview aria-label="Rendered preview"></div>
|
|
@@ -385,29 +471,70 @@ export async function renderReviewPage(ctx, lookup, query, getIndex) {
|
|
|
385
471
|
const folioSpine = isShortform
|
|
386
472
|
? `shortform · ${workflow.platform ?? '?'}${workflow.channel ? ` · ${workflow.channel}` : ''} · ${slug}`
|
|
387
473
|
: `longform · ${slug}`;
|
|
474
|
+
// Issue 4 — shortform reviews highlight the "Shortform" nav item;
|
|
475
|
+
// longform reviews don't match any nav-item (no longform desk
|
|
476
|
+
// exists). Pre-Issue-4, longform mistakenly highlighted shortform
|
|
477
|
+
// because the chrome treated all review surfaces as 'reviews'.
|
|
478
|
+
const folioActive = isShortform
|
|
479
|
+
? 'shortform'
|
|
480
|
+
: 'longform';
|
|
481
|
+
// Issue #154 Dispatch A — `.er-page` wraps the draft frame +
|
|
482
|
+
// marginalia inside a CSS Grid composition so marginalia sits next
|
|
483
|
+
// to the prose it annotates rather than pinned to the viewport.
|
|
484
|
+
// Shortform reviews skip the marginalia column (no margin-note
|
|
485
|
+
// workflow on shortform), so the page collapses to the draft frame
|
|
486
|
+
// alone for that surface — keeping the same `.er-page` shell
|
|
487
|
+
// preserves the desk metaphor across longform/shortform.
|
|
488
|
+
// Issue #154 Dispatch C — edit-mode panes-host lives inside the
|
|
489
|
+
// article column (in place of #draft-body when editing); the
|
|
490
|
+
// toolbar that drives it lives ABOVE `.er-page` (rendered below,
|
|
491
|
+
// outside the grid). Shortform never enters edit mode on this
|
|
492
|
+
// surface, so the panes-host is rendered but stays hidden — keeps
|
|
493
|
+
// the JS hooks present for forward compatibility without flipping
|
|
494
|
+
// any visible chrome.
|
|
495
|
+
const pageGrid = isShortform
|
|
496
|
+
? html `
|
|
497
|
+
<div class="er-page-grid">
|
|
498
|
+
<div class="er-draft-frame">
|
|
499
|
+
<div id="draft-body" data-draft-body
|
|
500
|
+
title="Double-click to edit · select text to leave a margin note">${unsafe(bodyHtml)}</div>
|
|
501
|
+
${renderEditPanes()}
|
|
502
|
+
</div>
|
|
503
|
+
</div>`
|
|
504
|
+
: html `
|
|
505
|
+
<div class="er-page-grid">
|
|
506
|
+
<div class="er-draft-frame">
|
|
507
|
+
<div id="draft-body" data-draft-body
|
|
508
|
+
title="Double-click to edit · select text to leave a margin note">${unsafe(bodyHtml)}</div>
|
|
509
|
+
${renderEditPanes()}
|
|
510
|
+
</div>
|
|
511
|
+
<div class="er-page-gutter" aria-hidden="true"></div>
|
|
512
|
+
${renderMarginalia()}
|
|
513
|
+
</div>`;
|
|
388
514
|
const body = html `
|
|
389
515
|
<div data-review-ui="${reviewUiAttr}" class="er-review-shell">
|
|
390
|
-
${renderEditorialFolio(
|
|
516
|
+
${renderEditorialFolio(folioActive, folioSpine)}
|
|
391
517
|
${shortformMeta}
|
|
392
|
-
<div class="er-draft-frame">
|
|
393
|
-
<div id="draft-body" data-draft-body
|
|
394
|
-
title="Double-click to edit · select text to leave a margin note">${unsafe(bodyHtml)}</div>
|
|
395
|
-
${renderEditMode(outlineHtml.length > 0)}
|
|
396
|
-
</div>
|
|
397
518
|
<div class="er-strip">
|
|
398
|
-
<
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
<span class="er-
|
|
404
|
-
|
|
519
|
+
<div class="er-strip-inner">
|
|
520
|
+
<a class="er-strip-back" href="/dev/editorial-studio" title="Back to the editorial studio">← studio</a>
|
|
521
|
+
<span class="er-strip-galley">${gloss('galley')} <em>№ ${currentVersion.version}</em></span>
|
|
522
|
+
<span class="er-strip-slug">${workflow.site} / ${workflow.slug}</span>
|
|
523
|
+
${renderVersionsStrip(versions, resolvedSite, contentKind, currentVersion)}
|
|
524
|
+
<span class="er-strip-center">
|
|
525
|
+
<span class="er-stamp er-stamp-big er-stamp-${workflow.state}" data-state-label>
|
|
526
|
+
${stateLabel(workflow.state)}
|
|
527
|
+
</span>
|
|
528
|
+
<span class="er-strip-hint">select text to <span class="er-gloss" data-term="marginalia" tabindex="0" role="button" aria-describedby="glossary-marginalia">mark</span> · double-click to edit · <kbd>?</kbd> for shortcuts</span>
|
|
405
529
|
</span>
|
|
406
|
-
|
|
407
|
-
</
|
|
408
|
-
${renderControlsRight(workflow)}
|
|
530
|
+
${renderControlsRight(workflow)}
|
|
531
|
+
</div>
|
|
409
532
|
</div>
|
|
410
|
-
${
|
|
533
|
+
${renderEditToolbar(outlineHtml.length > 0)}
|
|
534
|
+
<article class="er-page">
|
|
535
|
+
${unsafe(pageGrid)}
|
|
536
|
+
</article>
|
|
537
|
+
${isShortform ? unsafe('') : renderMarginaliaTab()}
|
|
411
538
|
<button class="er-pencil-btn" data-add-comment-btn hidden type="button">Mark</button>
|
|
412
539
|
${isShortform ? unsafe('') : renderOutlineDrawer(outlineHtml)}
|
|
413
540
|
${isShortform
|