@gjsify/site 0.64.10 → 0.64.12-next.279
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 +85 -19
- package/lib/cli.js +4 -14
- package/lib/client/site.css +283 -3
- package/lib/index.d.ts +5 -0
- package/lib/index.js +5 -0
- package/lib/render/blog.d.ts +62 -0
- package/lib/render/blog.js +240 -0
- package/lib/render/developer.d.ts +30 -0
- package/lib/render/developer.js +285 -0
- package/lib/render/feed.d.ts +25 -0
- package/lib/render/feed.js +97 -0
- package/lib/render/i18n.d.ts +2 -2
- package/lib/render/i18n.js +2 -0
- package/lib/render/icons.d.ts +1 -1
- package/lib/render/icons.js +3 -0
- package/lib/render/image-backend.d.ts +2 -1
- package/lib/render/image-backend.js +2 -2
- package/lib/render/images.d.ts +2 -0
- package/lib/render/images.js +3 -2
- package/lib/render/layout.d.ts +94 -0
- package/lib/render/layout.js +216 -0
- package/lib/render/markdown.d.ts +15 -1
- package/lib/render/markdown.js +245 -20
- package/lib/render/pages.d.ts +21 -8
- package/lib/render/pages.js +99 -263
- package/lib/render/seo.d.ts +34 -1
- package/lib/render/seo.js +50 -4
- package/lib/site-data/blog.d.ts +30 -0
- package/lib/site-data/blog.js +226 -0
- package/lib/site-data/config.d.ts +7 -2
- package/lib/site-data/config.js +118 -23
- package/lib/site-data/developer.d.ts +16 -0
- package/lib/site-data/developer.js +12 -0
- package/lib/site-data/frontmatter.d.ts +7 -0
- package/lib/site-data/frontmatter.js +242 -0
- package/lib/site-data/index.d.ts +4 -0
- package/lib/site-data/index.js +4 -0
- package/lib/site-data/related.d.ts +4 -2
- package/lib/site-data/related.js +8 -3
- package/lib/site-data/resume.d.ts +64 -0
- package/lib/site-data/resume.js +277 -0
- package/lib/site-data/types.d.ts +57 -15
- package/lib/site.d.ts +2 -26
- package/lib/site.js +211 -78
- package/package.json +13 -12
- package/po/de.po +88 -25
- package/po/site.pot +86 -23
- package/lib/render/overview.d.ts +0 -19
- package/lib/render/overview.js +0 -53
package/lib/render/markdown.js
CHANGED
|
@@ -20,24 +20,84 @@ function isTableStart(lines, index) {
|
|
|
20
20
|
}
|
|
21
21
|
function isBlockStart(lines, index) {
|
|
22
22
|
const line = lines[index];
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
const trimmed = line.trim();
|
|
24
|
+
if (trimmed === '') {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
if (HEADING.test(line)) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
if (HR.test(trimmed)) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
if (ORDERED.test(line) || UNORDERED.test(line)) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
if (isTableStart(lines, index)) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
if (trimmed.startsWith('```') || trimmed.startsWith('~~~')) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
if (trimmed.startsWith('>')) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
// Image paragraph check - handled separately, but treat as block start
|
|
46
|
+
if (/^!\[([^\]]*)\]\([^)]+\)$/.test(trimmed) ||
|
|
47
|
+
/^!\[([^\]]*)\]\([^)]+\s+"[^"]*"\)$/.test(trimmed) ||
|
|
48
|
+
/^!\[([^\]]*)\]\([^)]+\s+'[^']*'\)$/.test(trimmed)) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
29
52
|
}
|
|
30
|
-
function parseBlocks(source) {
|
|
53
|
+
function parseBlocks(source, options = {}) {
|
|
31
54
|
const lines = source.replace(/\r\n/g, '\n').split('\n');
|
|
32
55
|
const blocks = [];
|
|
33
56
|
let i = 0;
|
|
57
|
+
const resolveUrl = options.resolveUrl;
|
|
34
58
|
while (i < lines.length) {
|
|
35
59
|
const line = lines[i];
|
|
36
60
|
if (line.trim() === '') {
|
|
37
61
|
i++;
|
|
38
62
|
continue;
|
|
39
63
|
}
|
|
40
|
-
|
|
64
|
+
const trimmed = line.trim();
|
|
65
|
+
// Fenced code blocks
|
|
66
|
+
const fenceMatch = trimmed.match(/^(`{3,}|~{3,})(.*)$/);
|
|
67
|
+
if (fenceMatch) {
|
|
68
|
+
const fence = fenceMatch[1];
|
|
69
|
+
const fenceChar = fence[0];
|
|
70
|
+
const infoString = (fenceMatch[2] || '').trim();
|
|
71
|
+
i++;
|
|
72
|
+
const codeLines = [];
|
|
73
|
+
while (i < lines.length) {
|
|
74
|
+
const currentLine = lines[i];
|
|
75
|
+
const currentTrimmed = currentLine.trim();
|
|
76
|
+
// Check if it's the closing fence
|
|
77
|
+
if (fenceChar === '`') {
|
|
78
|
+
if (/^`{3,}\s*$/.test(currentTrimmed) && currentTrimmed.length >= fence.length) {
|
|
79
|
+
i++;
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else if (fenceChar === '~') {
|
|
84
|
+
if (/^~{3,}\s*$/.test(currentTrimmed) && currentTrimmed.length >= fence.length) {
|
|
85
|
+
i++;
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
codeLines.push(currentLine);
|
|
90
|
+
i++;
|
|
91
|
+
}
|
|
92
|
+
// Remove trailing newline inside code? "no trailing newline inside code"
|
|
93
|
+
let content = codeLines.join('\n');
|
|
94
|
+
if (content.endsWith('\n')) {
|
|
95
|
+
content = content.slice(0, -1);
|
|
96
|
+
}
|
|
97
|
+
blocks.push({ kind: 'code', lang: infoString || undefined, content });
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (HR.test(trimmed)) {
|
|
41
101
|
blocks.push({ kind: 'hr' });
|
|
42
102
|
i++;
|
|
43
103
|
continue;
|
|
@@ -64,12 +124,50 @@ function parseBlocks(source) {
|
|
|
64
124
|
const pattern = ordered ? ORDERED : UNORDERED;
|
|
65
125
|
const items = [];
|
|
66
126
|
while (i < lines.length && pattern.test(lines[i])) {
|
|
67
|
-
|
|
127
|
+
let item = pattern.exec(lines[i])[1].trim();
|
|
68
128
|
i++;
|
|
129
|
+
// An indented line under an item is that item, wrapped.
|
|
130
|
+
while (i < lines.length && /^\s+\S/.test(lines[i]) && !pattern.test(lines[i])) {
|
|
131
|
+
item += ` ${lines[i].trim()}`;
|
|
132
|
+
i++;
|
|
133
|
+
}
|
|
134
|
+
items.push(item);
|
|
69
135
|
}
|
|
70
136
|
blocks.push({ kind: 'list', ordered, items });
|
|
71
137
|
continue;
|
|
72
138
|
}
|
|
139
|
+
// Blockquote
|
|
140
|
+
if (trimmed.startsWith('>')) {
|
|
141
|
+
const quoteLines = [];
|
|
142
|
+
while (i < lines.length) {
|
|
143
|
+
const qline = lines[i];
|
|
144
|
+
const qtrimmed = qline.trim();
|
|
145
|
+
if (qtrimmed.startsWith('>')) {
|
|
146
|
+
// Remove > and optional space
|
|
147
|
+
const content = qline.replace(/^\s*>\s?/, '');
|
|
148
|
+
quoteLines.push(content);
|
|
149
|
+
i++;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
blocks.push({ kind: 'blockquote', content: quoteLines.join('\n') });
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
// Image
|
|
158
|
+
const imgMatch = trimmed.match(/^!\[([^\]]*)\]\(([^)]+?)(?:\s+"([^"]*)")?\)$/) ||
|
|
159
|
+
trimmed.match(/^!\[([^\]]*)\]\(([^)]+?)(?:\s+'([^']*)')?\)$/);
|
|
160
|
+
if (imgMatch) {
|
|
161
|
+
let src = imgMatch[2].trim();
|
|
162
|
+
if (resolveUrl) {
|
|
163
|
+
src = resolveUrl(src);
|
|
164
|
+
}
|
|
165
|
+
const alt = imgMatch[1] || '';
|
|
166
|
+
const title = imgMatch[3];
|
|
167
|
+
blocks.push({ kind: 'image', src, alt, title, isFigure: true });
|
|
168
|
+
i++;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
73
171
|
const paragraph = [];
|
|
74
172
|
while (i < lines.length && !isBlockStart(lines, i)) {
|
|
75
173
|
paragraph.push(lines[i].trim());
|
|
@@ -80,34 +178,161 @@ function parseBlocks(source) {
|
|
|
80
178
|
return blocks;
|
|
81
179
|
}
|
|
82
180
|
/** Escapes first, then marks up the already-escaped text — a link's URL and label are both safe. */
|
|
83
|
-
function renderInline(text) {
|
|
181
|
+
function renderInline(text, options = {}) {
|
|
182
|
+
const resolveUrl = options.resolveUrl;
|
|
84
183
|
let html = esc(text);
|
|
85
|
-
|
|
184
|
+
// Images
|
|
185
|
+
html = html.replace(/!\[([^\]]*)\]\(([^)\s]+?)(?:\s+"([^"]*)")?\)/g, (_match, alt, src, title) => {
|
|
186
|
+
// Groups are from escaped text
|
|
187
|
+
let resolvedSrc = src
|
|
188
|
+
.replace(/&/g, '&')
|
|
189
|
+
.replace(/</g, '<')
|
|
190
|
+
.replace(/>/g, '>')
|
|
191
|
+
.replace(/"/g, '"')
|
|
192
|
+
.replace(/'/g, "'");
|
|
193
|
+
if (resolveUrl) {
|
|
194
|
+
resolvedSrc = resolveUrl(resolvedSrc);
|
|
195
|
+
}
|
|
196
|
+
let altUnesc = alt
|
|
197
|
+
.replace(/&/g, '&')
|
|
198
|
+
.replace(/</g, '<')
|
|
199
|
+
.replace(/>/g, '>')
|
|
200
|
+
.replace(/"/g, '"')
|
|
201
|
+
.replace(/'/g, "'");
|
|
202
|
+
const titleAttr = title ? ` title="${esc(title.replace(/"/g, '"'))}"` : '';
|
|
203
|
+
return `<img src="${esc(resolvedSrc)}" alt="${esc(altUnesc)}" loading="lazy"${titleAttr}>`;
|
|
204
|
+
});
|
|
205
|
+
html = html.replace(/!\[([^\]]*)\]\(([^)\s]+?)(?:\s+'([^']*)')?\)/g, (_match, alt, src, title) => {
|
|
206
|
+
let resolvedSrc = src
|
|
207
|
+
.replace(/&/g, '&')
|
|
208
|
+
.replace(/</g, '<')
|
|
209
|
+
.replace(/>/g, '>')
|
|
210
|
+
.replace(/"/g, '"')
|
|
211
|
+
.replace(/'/g, "'");
|
|
212
|
+
if (resolveUrl) {
|
|
213
|
+
resolvedSrc = resolveUrl(resolvedSrc);
|
|
214
|
+
}
|
|
215
|
+
let altUnesc = alt
|
|
216
|
+
.replace(/&/g, '&')
|
|
217
|
+
.replace(/</g, '<')
|
|
218
|
+
.replace(/>/g, '>')
|
|
219
|
+
.replace(/"/g, '"')
|
|
220
|
+
.replace(/'/g, "'");
|
|
221
|
+
const titleAttr = title ? ` title="${esc(title.replace(/'/g, "'"))}"` : '';
|
|
222
|
+
return `<img src="${esc(resolvedSrc)}" alt="${esc(altUnesc)}" loading="lazy"${titleAttr}>`;
|
|
223
|
+
});
|
|
224
|
+
html = html.replace(/\[([^\]]+)\]\(([^)\s]+?)(?:\s+"([^"]*)")?\)/g, (_match, label, url) => {
|
|
225
|
+
let resolvedUrl = url
|
|
226
|
+
.replace(/&/g, '&')
|
|
227
|
+
.replace(/</g, '<')
|
|
228
|
+
.replace(/>/g, '>')
|
|
229
|
+
.replace(/"/g, '"')
|
|
230
|
+
.replace(/'/g, "'");
|
|
231
|
+
if (resolveUrl) {
|
|
232
|
+
resolvedUrl = resolveUrl(resolvedUrl);
|
|
233
|
+
}
|
|
234
|
+
return `<a href="${esc(resolvedUrl)}">${label}</a>`;
|
|
235
|
+
});
|
|
236
|
+
html = html.replace(/\[([^\]]+)\]\(([^)\s]+?)(?:\s+'([^']*)')?\)/g, (_match, label, url) => {
|
|
237
|
+
let resolvedUrl = url
|
|
238
|
+
.replace(/&/g, '&')
|
|
239
|
+
.replace(/</g, '<')
|
|
240
|
+
.replace(/>/g, '>')
|
|
241
|
+
.replace(/"/g, '"')
|
|
242
|
+
.replace(/'/g, "'");
|
|
243
|
+
if (resolveUrl) {
|
|
244
|
+
resolvedUrl = resolveUrl(resolvedUrl);
|
|
245
|
+
}
|
|
246
|
+
return `<a href="${esc(resolvedUrl)}">${label}</a>`;
|
|
247
|
+
});
|
|
248
|
+
// Also handle links without title
|
|
249
|
+
html = html.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, (_match, label, url) => {
|
|
250
|
+
let resolvedUrl = url
|
|
251
|
+
.replace(/&/g, '&')
|
|
252
|
+
.replace(/</g, '<')
|
|
253
|
+
.replace(/>/g, '>')
|
|
254
|
+
.replace(/"/g, '"')
|
|
255
|
+
.replace(/'/g, "'");
|
|
256
|
+
if (resolveUrl) {
|
|
257
|
+
resolvedUrl = resolveUrl(resolvedUrl);
|
|
258
|
+
}
|
|
259
|
+
return `<a href="${esc(resolvedUrl)}">${label}</a>`;
|
|
260
|
+
});
|
|
86
261
|
html = html.replace(/`([^`]+)`/g, (_match, code) => `<code>${code}</code>`);
|
|
87
|
-
|
|
262
|
+
// Bold first (** takes precedence) - be careful not to break
|
|
263
|
+
html = html.replace(/\*\*([^*]+?)\*\*/g, (_match, bold) => `<strong>${bold}</strong>`);
|
|
264
|
+
// Emphasis with * - single asterisks not part of **
|
|
265
|
+
html = html.replace(/(?<!\*)\*([^*\s][^*]*?|[^*\s])\*(?!\*)/g, (_match, content) => {
|
|
266
|
+
return `<em>${content}</em>`;
|
|
267
|
+
});
|
|
268
|
+
// Emphasis with _ - must not break snake_case (no word chars immediately adjacent on both sides)
|
|
269
|
+
// Use word boundary checks
|
|
270
|
+
html = html.replace(/(^|[^a-zA-Z0-9])_([^_\s][^_]*?|[^_\s])_([^a-zA-Z0-9]|$)/g, (_match, p1, content, p3) => {
|
|
271
|
+
return p1 + `<em>${content}</em>` + p3;
|
|
272
|
+
});
|
|
88
273
|
return html;
|
|
89
274
|
}
|
|
90
|
-
function renderBlock(block) {
|
|
275
|
+
function renderBlock(block, options = {}) {
|
|
91
276
|
switch (block.kind) {
|
|
92
277
|
case 'heading':
|
|
93
|
-
return `<h${block.level}>${renderInline(block.text)}</h${block.level}>`;
|
|
278
|
+
return `<h${block.level}>${renderInline(block.text, options)}</h${block.level}>`;
|
|
94
279
|
case 'paragraph':
|
|
95
|
-
|
|
280
|
+
// Check if paragraph contains only an image
|
|
281
|
+
const imgOnlyMatch = block.text.match(/^!\[([^\]]*)\]\(([^)]+?)(?:\s+"([^"]*)")?\)$/) ||
|
|
282
|
+
block.text.match(/^!\[([^\]]*)\]\(([^)]+?)(?:\s+'([^']*)')?\)$/);
|
|
283
|
+
if (imgOnlyMatch) {
|
|
284
|
+
let src = imgOnlyMatch[2].trim();
|
|
285
|
+
if (options.resolveUrl) {
|
|
286
|
+
src = options.resolveUrl(src);
|
|
287
|
+
}
|
|
288
|
+
const alt = imgOnlyMatch[1] || '';
|
|
289
|
+
const title = imgOnlyMatch[3];
|
|
290
|
+
const titleAttr = title ? ` title="${esc(title)}"` : '';
|
|
291
|
+
if (title) {
|
|
292
|
+
return `<figure><img src="${esc(src)}" alt="${esc(alt)}" loading="lazy"${titleAttr}><figcaption>${esc(title)}</figcaption></figure>`;
|
|
293
|
+
}
|
|
294
|
+
return `<figure><img src="${esc(src)}" alt="${esc(alt)}" loading="lazy"${titleAttr}></figure>`;
|
|
295
|
+
}
|
|
296
|
+
return `<p>${renderInline(block.text, options)}</p>`;
|
|
96
297
|
case 'list': {
|
|
97
298
|
const tag = block.ordered ? 'ol' : 'ul';
|
|
98
|
-
return `<${tag}>${block.items.map((item) => `<li>${renderInline(item)}</li>`).join('')}</${tag}>`;
|
|
299
|
+
return `<${tag}>${block.items.map((item) => `<li>${renderInline(item, options)}</li>`).join('')}</${tag}>`;
|
|
99
300
|
}
|
|
100
301
|
case 'table': {
|
|
101
|
-
const head = `<thead><tr>${block.header.map((cell) => `<th>${renderInline(cell)}</th>`).join('')}</tr></thead>`;
|
|
302
|
+
const head = `<thead><tr>${block.header.map((cell) => `<th>${renderInline(cell, options)}</th>`).join('')}</tr></thead>`;
|
|
102
303
|
const body = `<tbody>${block.rows
|
|
103
|
-
.map((row) => `<tr>${row.map((cell) => `<td>${renderInline(cell)}</td>`).join('')}</tr>`)
|
|
304
|
+
.map((row) => `<tr>${row.map((cell) => `<td>${renderInline(cell, options)}</td>`).join('')}</tr>`)
|
|
104
305
|
.join('')}</tbody>`;
|
|
105
306
|
return `<table>${head}${body}</table>`;
|
|
106
307
|
}
|
|
107
308
|
case 'hr':
|
|
108
309
|
return '<hr>';
|
|
310
|
+
case 'code': {
|
|
311
|
+
if (block.lang) {
|
|
312
|
+
return `<pre><code class="language-${esc(block.lang)}">${esc(block.content)}</code></pre>`;
|
|
313
|
+
}
|
|
314
|
+
return `<pre><code>${esc(block.content)}</code></pre>`;
|
|
315
|
+
}
|
|
316
|
+
case 'blockquote': {
|
|
317
|
+
const inner = parseBlocks(block.content, options)
|
|
318
|
+
.map((b) => renderBlock(b, options))
|
|
319
|
+
.join('\n');
|
|
320
|
+
return `<blockquote>${inner}</blockquote>`;
|
|
321
|
+
}
|
|
322
|
+
case 'image': {
|
|
323
|
+
const titleAttr = block.title ? ` title="${esc(block.title)}"` : '';
|
|
324
|
+
if (block.title && block.isFigure) {
|
|
325
|
+
return `<figure><img src="${esc(block.src)}" alt="${esc(block.alt)}" loading="lazy"${titleAttr}><figcaption>${esc(block.title)}</figcaption></figure>`;
|
|
326
|
+
}
|
|
327
|
+
if (block.isFigure) {
|
|
328
|
+
return `<figure><img src="${esc(block.src)}" alt="${esc(block.alt)}" loading="lazy"${titleAttr}></figure>`;
|
|
329
|
+
}
|
|
330
|
+
return `<img src="${esc(block.src)}" alt="${esc(block.alt)}" loading="lazy"${titleAttr}>`;
|
|
331
|
+
}
|
|
109
332
|
}
|
|
110
333
|
}
|
|
111
|
-
export function renderMarkdown(source) {
|
|
112
|
-
return parseBlocks(source)
|
|
334
|
+
export function renderMarkdown(source, options = {}) {
|
|
335
|
+
return parseBlocks(source, options)
|
|
336
|
+
.map((b) => renderBlock(b, options))
|
|
337
|
+
.join('\n');
|
|
113
338
|
}
|
package/lib/render/pages.d.ts
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import { type DownloadTarget, type Localized, type Screenshot, type SiteData, type SiteSupportSepa } from '../site-data/index.js';
|
|
1
|
+
import { type DownloadTarget, type Localized, type RelatedApp, type Screenshot, type SiteData, type SiteSupportEntry, type SiteSupportSepa } from '../site-data/index.js';
|
|
2
2
|
import { type IconName } from './icons.js';
|
|
3
|
+
import { type ClientAssets, type Shell, type Translate } from './layout.js';
|
|
3
4
|
import type { ProcessedImage } from './images.js';
|
|
4
|
-
export interface ClientAssets {
|
|
5
|
-
js: string;
|
|
6
|
-
css: string[];
|
|
7
|
-
}
|
|
8
5
|
export interface RenderContext {
|
|
9
6
|
data: SiteData;
|
|
10
7
|
lang: string;
|
|
@@ -18,8 +15,11 @@ export interface RenderContext {
|
|
|
18
15
|
view?: 'app' | 'support' | 'privacy' | 'legal-notice';
|
|
19
16
|
/** Root-relative URL of the link-preview card for this language. */
|
|
20
17
|
ogImage?: string;
|
|
18
|
+
/** The blog's latest posts in this language, rendered; only the landing page shows them. */
|
|
19
|
+
latestPosts?: string;
|
|
20
|
+
/** Root-relative URL of the published `theme.displayFont`. */
|
|
21
|
+
displayFontUrl?: string;
|
|
21
22
|
}
|
|
22
|
-
type Translate = (msgid: string) => string;
|
|
23
23
|
/** What the download dialog reads: the targets, the catalogue and the locale it formats sizes in. */
|
|
24
24
|
export type DownloadContext = Pick<RenderContext, 'data' | 't' | 'lang'>;
|
|
25
25
|
export declare function localized(text: Localized, lang: string): string;
|
|
@@ -35,7 +35,6 @@ export declare function privacyUrl(data: SiteData, lang: string): string;
|
|
|
35
35
|
export declare function legalNoticeUrl(data: SiteData, lang: string): string;
|
|
36
36
|
/** The transfer reference when the config sets none: one per app, so the bank statement says what it was for. */
|
|
37
37
|
export declare function supportReference(data: SiteData): string;
|
|
38
|
-
export declare function languageName(code: string): string;
|
|
39
38
|
/**
|
|
40
39
|
* What the primary button says. Installing from a store, opening the web build and downloading a
|
|
41
40
|
* file are three different promises, so each gets its own wording.
|
|
@@ -58,6 +57,8 @@ export declare function downloadWidget(ctx: DownloadContext): string;
|
|
|
58
57
|
*/
|
|
59
58
|
export declare function badges(ctx: Pick<RenderContext, 'data' | 't' | 'lang'>): string;
|
|
60
59
|
export declare function linkCard(href: string, title: string, glyph: IconName): string;
|
|
60
|
+
/** One app as a card: icon, name, summary. Also the app grid of a developer site. */
|
|
61
|
+
export declare function relatedCard(app: RelatedApp, lang: string): string;
|
|
61
62
|
/** Other apps of the same developer, resolved at build time so the page needs no script. */
|
|
62
63
|
export declare function relatedSection(ctx: Pick<RenderContext, 'data' | 'lang' | 't'>): string;
|
|
63
64
|
/** A bank transfer as a card: the details to read or copy, and an EPC QR code that fills them into a banking app. */
|
|
@@ -68,9 +69,22 @@ export declare function pageMeta(ctx: Pick<RenderContext, 'data' | 'lang' | 't'>
|
|
|
68
69
|
title: string;
|
|
69
70
|
description: string;
|
|
70
71
|
};
|
|
72
|
+
/** The frame of every page of an app site in one language. */
|
|
73
|
+
export declare function appShell(ctx: Pick<RenderContext, 'data' | 'lang' | 't' | 'assets' | 'iconUrl' | 'displayFontUrl'> & {
|
|
74
|
+
current?: 'blog';
|
|
75
|
+
}): Shell;
|
|
71
76
|
export declare function renderPage(ctx: RenderContext): string;
|
|
77
|
+
/** The support entries as cards; shared with the developer site, whose reference is the person's. */
|
|
78
|
+
export declare function supportList(entries: readonly SiteSupportEntry[], t: Translate, reference: string): string;
|
|
72
79
|
/** `/<lang>/support/`: the ways to support the app, as plain markup that needs no script. */
|
|
73
80
|
export declare function renderSupportPage(page: RenderContext): string;
|
|
81
|
+
/**
|
|
82
|
+
* The paragraph every privacy page opens with, regardless of what the site is about. It is about
|
|
83
|
+
* visiting THIS static page — this package adds no cookie, no analytics and no third-party resource
|
|
84
|
+
* of its own, so that much is true for every consumer. `hostingPolicyUrl` is the one fact that
|
|
85
|
+
* varies by deployment and is not guessed; left out, nothing about hosting is said.
|
|
86
|
+
*/
|
|
87
|
+
export declare function hostingNotice(config: Pick<SiteData['config'], 'privacy'>, t: Translate): string;
|
|
74
88
|
/**
|
|
75
89
|
* `/<lang>/privacy/`: the hosting paragraph above, then the app's own statement of what it does
|
|
76
90
|
* with data — read verbatim from the file `gjsify.site.privacy.statements[lang]` names, rendered
|
|
@@ -87,4 +101,3 @@ export declare function renderPrivacyPage(page: RenderContext, statement: string
|
|
|
87
101
|
export declare function renderLegalNoticePage(page: RenderContext, statement: string): string;
|
|
88
102
|
/** The root page: a script picks the visitor's language, the link list is what everyone else (and every crawler) sees. */
|
|
89
103
|
export declare function renderRoot(data: SiteData, assets: ClientAssets, iconUrl: string | undefined): string;
|
|
90
|
-
export {};
|