-
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathdocument.ts
More file actions
220 lines (215 loc) · 8.18 KB
/
Copy pathdocument.ts
File metadata and controls
220 lines (215 loc) · 8.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
/**
* Document HTML Template (Pure Function)
*
* Generates the documentation webview body and its template-specific styles.
* The shared skeleton (<!DOCTYPE>…</head>, base stylesheet, outer body tag)
* lives in `./shell.ts`; this file owns only what is unique to the document
* view — the nav bar, main/footer layout, and the link-intercept script.
*
* Split from the legacy monolithic htmlTemplates.ts.
*
* @author Null;Variant
* @license MIT
*/
import { escapeHtmlEntities } from '../documentationInternal';
import {
GITHUB_README_URL,
getFocusVisibleForcedColorsRule,
getFocusVisibleRule,
} from './baseStyles';
import { buildLinkInterceptScript } from './linkIntercept';
import { buildHtmlShell } from './shell';
import { type SanitizedHtml } from './types';
/**
* Generate the document HTML with content
*
* @param cspSource - Webview CSP source
* @param content - Pre-sanitized HTML content, typed as `SanitizedHtml` so
* the compiler forbids passing a raw `string`. The brand can only originate
* at the sanitizer boundary (`renderMarkdown`), making the trust boundary
* structurally impossible to bypass. This function injects `content`
* verbatim; CSP blocks `<script>` execution but does NOT stop `<img
* onerror>` or `javascript:` URLs outside the linkInterceptScript path,
* which is exactly why we want the type-level guarantee on top of CSP.
* @param locale - Current locale
* @param currentPath - Current document path (for relative link resolution)
* @param nonce - CSP nonce for inline styles and scripts
* @param canGoBack - Whether back navigation is available
* @returns Complete HTML document
*/
export function buildDocumentHtml(
cspSource: string,
content: SanitizedHtml,
locale: string,
currentPath: string,
nonce: string,
canGoBack: boolean
): string {
const linkInterceptScript = buildLinkInterceptScript();
const extraStyles = ` /* External link indicator.
The "/ <alt-text>" syntax (CSS Generated Content L3) provides the
screen-reader announcement for the decorative arrow glyph. We
announce "(opens externally)" rather than silencing it so AT
users receive the same "external link" signal as sighted users.
Note: VS Code webview is Electron/Chromium so this syntax is
supported; it is not portable to Firefox. */
a[href^="http"]:not([href*="assets.nullvariant.com"])::after {
content: " ↗" / " (opens externally)";
font-size: var(--gis-font-xs);
}
h1, h2, h3 {
color: var(--vscode-foreground);
margin-top: var(--gis-space-lg);
margin-bottom: var(--gis-space-sm);
}
h1 { border-bottom: var(--gis-border-subtle); padding-bottom: var(--gis-space-xs); }
code {
background-color: var(--vscode-textCodeBlock-background);
padding: var(--gis-pad-code);
border-radius: var(--gis-radius-sm);
font-family: var(--vscode-editor-font-family);
}
pre {
background-color: var(--vscode-textCodeBlock-background);
padding: var(--gis-space-md);
border-radius: var(--gis-radius-md);
overflow-x: auto;
}
pre code {
background: none;
padding: 0;
}
ul {
padding-left: var(--gis-space-lg);
}
li {
margin-bottom: var(--gis-space-sm);
}
table {
border-collapse: collapse;
width: 100%;
margin: var(--gis-space-md) 0;
}
th, td {
border: var(--gis-border-subtle);
padding: var(--gis-space-sm) var(--gis-space-md);
text-align: left;
/* Prevent long tokens (URLs, hashes) from pushing the table
wider than its container. overflow-wrap: anywhere is sufficient;
we deliberately do NOT use table-layout: fixed (which would
force equal column widths regardless of content). */
overflow-wrap: anywhere;
}
th {
background-color: var(--vscode-textCodeBlock-background);
font-weight: bold;
}
tr:nth-child(even) {
background-color: var(--vscode-textCodeBlock-background);
}
blockquote {
border-left: var(--gis-border-emphasis);
margin: var(--gis-space-md) 0;
padding: var(--gis-space-sm) var(--gis-space-md);
background-color: var(--vscode-textCodeBlock-background);
font-style: italic;
}
hr {
border: none;
border-top: var(--gis-border-subtle);
margin: var(--gis-space-xl) 0;
}
.nav-bar {
margin-bottom: var(--gis-space-md);
padding-bottom: var(--gis-space-sm);
border-bottom: var(--gis-border-subtle);
}
.nav-bar button {
background: var(--vscode-button-secondaryBackground);
color: var(--vscode-button-secondaryForeground);
border: none;
padding: var(--gis-size-btn);
border-radius: var(--gis-radius-sm);
cursor: pointer;
font-family: var(--vscode-font-family);
font-size: var(--vscode-font-size);
}
.nav-bar button:hover {
background: var(--vscode-button-secondaryHoverBackground);
}
/* aria-disabled is used instead of the [disabled] attribute so
Safari/VoiceOver keep the button in the focus order.
Uses a theme-aware foreground color instead of opacity so the
focus-visible outline retains full contrast (WCAG 1.4.11) when
the button is both focused and disabled. */
.nav-bar button[aria-disabled="true"] {
color: var(--vscode-disabledForeground, var(--vscode-descriptionForeground));
cursor: not-allowed;
}
.nav-bar .current-path {
margin-left: var(--gis-space-md);
color: var(--vscode-descriptionForeground);
font-size: var(--gis-font-sm);
}
${getFocusVisibleRule('a:focus-visible, button:focus-visible')}
/* Forced-colors (Windows High Contrast) overrides.
- The nth-child(even) zebra uses a theme background that collapses to
Canvas in forced-colors, flattening rows. Explicitly reset to
Canvas so rows keep the system background rather than an opaque
panel color that would fight cell borders.
- Focus outlines must use the system Highlight color per WCAG 1.4.3. */
@media (forced-colors: active) {
tr:nth-child(even),
th {
background-color: Canvas;
}
${getFocusVisibleForcedColorsRule('a:focus-visible, button:focus-visible')}
}
.footer {
margin-top: var(--gis-size-body-lg);
padding-top: var(--gis-size-body);
border-top: var(--gis-border-subtle);
font-size: var(--gis-font-sm);
}
.footer a {
margin-right: var(--gis-space-md);
}
/* Template-specific body layout — scoped by body.gis-doc class to
raise specificity above the base body rule, making cascade order
irrelevant. */
body.gis-doc {
padding: var(--gis-size-body);
line-height: var(--gis-line-height-doc);
max-width: var(--gis-width-readable);
margin: 0 auto;
}`;
// aria-disabled (not [disabled]) preserves tab focus under Safari/VoiceOver.
// The ← glyph lives inside an aria-hidden span so AT announces only the
// button's aria-label ("Go back") instead of reading the arrow as a
// standalone token before the word "Back".
// Brand the body as SanitizedHtml: every interpolated value is either a
// static literal, an already-escaped attribute (escapeHtmlEntities), or
// the pre-branded `content` parameter. A stray raw string from a future
// refactor fails at this cast site instead of at the webview.
const body = ` <nav class="nav-bar" aria-label="Document navigation">
<button id="back-btn" type="button" aria-label="Go back" aria-disabled="${canGoBack ? 'false' : 'true'}"><span aria-hidden="true">← </span>Back</button>
<span class="current-path" aria-current="page">${escapeHtmlEntities(currentPath)}</span>
</nav>
<main>
${content}
</main>
<footer class="footer">
<a href="${GITHUB_README_URL}" aria-label="View Git ID Switcher on GitHub">View on GitHub</a>
<a href="https://marketplace.visualstudio.com/items?itemName=nullvariant.git-id-switcher">VS Code Marketplace</a>
</footer>
<script nonce="${nonce}">${linkInterceptScript}</script>` as SanitizedHtml;
return buildHtmlShell({
cspSource,
nonce,
lang: locale,
title: 'Git ID Switcher Documentation',
extraStyles,
bodyClass: 'gis-doc',
body,
});
}