@k2b/ui 0.12.0 → 0.14.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 +1 -1
- package/dist/browser/content/MarkdownView.js +13 -5
- package/dist/browser/content/MarkdownView.js.map +1 -1
- package/dist/browser/content/PdfPreview.js +5 -2
- package/dist/browser/content/PdfPreview.js.map +1 -1
- package/dist/browser/content/index.js +1 -0
- package/dist/browser/content/index.js.map +1 -1
- package/dist/browser/content/markdown-info-blocks.js +363 -0
- package/dist/browser/content/markdown-info-blocks.js.map +1 -0
- package/dist/browser/feedback/InstallGuide.js +267 -0
- package/dist/browser/feedback/InstallGuide.js.map +1 -0
- package/dist/browser/feedback/announce.js +50 -0
- package/dist/browser/feedback/announce.js.map +1 -0
- package/dist/browser/feedback/dialog-core.js +88 -2
- package/dist/browser/feedback/dialog-core.js.map +1 -1
- package/dist/browser/feedback/index.js +4 -0
- package/dist/browser/feedback/index.js.map +1 -1
- package/dist/browser/feedback/install.js +99 -0
- package/dist/browser/feedback/install.js.map +1 -0
- package/dist/browser/feedback/prompts.js +11 -1
- package/dist/browser/feedback/prompts.js.map +1 -1
- package/dist/browser/feedback/theme-color.js +15 -0
- package/dist/browser/feedback/theme-color.js.map +1 -0
- package/dist/browser/feedback/toast.js +10 -44
- package/dist/browser/feedback/toast.js.map +1 -1
- package/dist/browser/inputs/QrScanner.js +164 -0
- package/dist/browser/inputs/QrScanner.js.map +1 -0
- package/dist/browser/inputs/index.js +1 -0
- package/dist/browser/inputs/index.js.map +1 -1
- package/dist/browser/intl/messages.js +96 -2
- package/dist/browser/intl/messages.js.map +1 -1
- package/dist/browser/layout/Discussion.js +2 -1
- package/dist/browser/layout/Discussion.js.map +1 -1
- package/dist/browser/layout/MobileShell.js +284 -0
- package/dist/browser/layout/MobileShell.js.map +1 -0
- package/dist/browser/layout/TabBar.js +69 -0
- package/dist/browser/layout/TabBar.js.map +1 -0
- package/dist/browser/layout/index.js +2 -0
- package/dist/browser/layout/index.js.map +1 -1
- package/dist/browser/surfaces/NoticeCard.js +55 -60
- package/dist/browser/surfaces/NoticeCard.js.map +1 -1
- package/dist/browser/surfaces/index.js +1 -1
- package/dist/browser/surfaces/index.js.map +1 -1
- package/dist/ssr/content/MarkdownView.js +13 -5
- package/dist/ssr/content/MarkdownView.js.map +1 -1
- package/dist/ssr/content/PdfPreview.js +4 -2
- package/dist/ssr/content/PdfPreview.js.map +1 -1
- package/dist/ssr/content/index.js +1 -0
- package/dist/ssr/content/index.js.map +1 -1
- package/dist/ssr/content/markdown-info-blocks.js +363 -0
- package/dist/ssr/content/markdown-info-blocks.js.map +1 -0
- package/dist/ssr/feedback/InstallGuide.js +201 -0
- package/dist/ssr/feedback/InstallGuide.js.map +1 -0
- package/dist/ssr/feedback/announce.js +50 -0
- package/dist/ssr/feedback/announce.js.map +1 -0
- package/dist/ssr/feedback/dialog-core.js +88 -2
- package/dist/ssr/feedback/dialog-core.js.map +1 -1
- package/dist/ssr/feedback/index.js +4 -0
- package/dist/ssr/feedback/index.js.map +1 -1
- package/dist/ssr/feedback/install.js +99 -0
- package/dist/ssr/feedback/install.js.map +1 -0
- package/dist/ssr/feedback/prompts.js +11 -1
- package/dist/ssr/feedback/prompts.js.map +1 -1
- package/dist/ssr/feedback/theme-color.js +15 -0
- package/dist/ssr/feedback/theme-color.js.map +1 -0
- package/dist/ssr/feedback/toast.js +10 -44
- package/dist/ssr/feedback/toast.js.map +1 -1
- package/dist/ssr/inputs/QrScanner.js +128 -0
- package/dist/ssr/inputs/QrScanner.js.map +1 -0
- package/dist/ssr/inputs/index.js +1 -0
- package/dist/ssr/inputs/index.js.map +1 -1
- package/dist/ssr/intl/messages.js +96 -2
- package/dist/ssr/intl/messages.js.map +1 -1
- package/dist/ssr/layout/Discussion.js +2 -1
- package/dist/ssr/layout/Discussion.js.map +1 -1
- package/dist/ssr/layout/MobileShell.js +253 -0
- package/dist/ssr/layout/MobileShell.js.map +1 -0
- package/dist/ssr/layout/TabBar.js +25 -0
- package/dist/ssr/layout/TabBar.js.map +1 -0
- package/dist/ssr/layout/index.js +2 -0
- package/dist/ssr/layout/index.js.map +1 -1
- package/dist/ssr/surfaces/NoticeCard.js +21 -30
- package/dist/ssr/surfaces/NoticeCard.js.map +1 -1
- package/dist/ssr/surfaces/index.js +1 -1
- package/dist/ssr/surfaces/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/tabler.css +1 -1
- package/dist/types/content/MarkdownView.d.ts +6 -2
- package/dist/types/content/index.d.ts +1 -0
- package/dist/types/content/markdown-info-blocks.d.ts +67 -0
- package/dist/types/feedback/InstallGuide.d.ts +26 -0
- package/dist/types/feedback/announce.d.ts +19 -0
- package/dist/types/feedback/dialog-core.d.ts +5 -0
- package/dist/types/feedback/index.d.ts +7 -0
- package/dist/types/feedback/install.d.ts +32 -0
- package/dist/types/feedback/prompts.d.ts +2 -0
- package/dist/types/feedback/theme-color.d.ts +6 -0
- package/dist/types/inputs/QrScanner.d.ts +20 -0
- package/dist/types/inputs/index.d.ts +2 -0
- package/dist/types/intl/messages.d.ts +47 -0
- package/dist/types/layout/MobileShell.d.ts +40 -0
- package/dist/types/layout/TabBar.d.ts +26 -0
- package/dist/types/layout/index.d.ts +4 -0
- package/dist/types/surfaces/NoticeCard.d.ts +4 -4
- package/dist/types/surfaces/index.d.ts +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import { Lexer, Tokenizer } from "marked";
|
|
2
|
+
import { resolveUiMessages } from "../intl/messages";
|
|
3
|
+
import { NOTICE_CARD_CLASSES } from "../surfaces/NoticeCard";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Markdown info blocks: one grammar and one look for every Markdown surface.
|
|
7
|
+
*
|
|
8
|
+
* ```markdown
|
|
9
|
+
* :::warning Before deleting
|
|
10
|
+
* Deleting an item cannot be undone.
|
|
11
|
+
* :::
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* The opening line holds `:::`, a type in any letter case, and an optional
|
|
15
|
+
* plain-text title. The block ends at a line holding only `:::`, indented no
|
|
16
|
+
* deeper than the opener; code, lists, quotes, and HTML inside the body cannot
|
|
17
|
+
* end it. The body is ordinary Markdown. A block that is never closed stays
|
|
18
|
+
* ordinary text.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const TONES = {
|
|
22
|
+
note: "neutral",
|
|
23
|
+
info: "info",
|
|
24
|
+
success: "success",
|
|
25
|
+
warning: "warning",
|
|
26
|
+
danger: "danger"
|
|
27
|
+
};
|
|
28
|
+
const OPENER = /^( {0,3}):::(note|info|success|warning|danger)(?:[ \t]+(.*?))?[ \t]*$/i;
|
|
29
|
+
const START = /^ {0,3}:::(?:note|info|success|warning|danger)(?=[ \t\n]|$)/i;
|
|
30
|
+
const OPENERS = /(?<=^|\n) {0,3}:::(?:note|info|success|warning|danger)(?=[ \t\n]|$)/gi;
|
|
31
|
+
const CLOSING = /^( {0,3}):::[ \t]*$/;
|
|
32
|
+
const FENCE = /^( *)(`{3,}|~{3,})(.*)$/;
|
|
33
|
+
const LABELS = {
|
|
34
|
+
note: t => t.infoBlockNote,
|
|
35
|
+
info: t => t.infoBlockInfo,
|
|
36
|
+
success: t => t.infoBlockSuccess,
|
|
37
|
+
warning: t => t.infoBlockWarning,
|
|
38
|
+
danger: t => t.infoBlockDanger
|
|
39
|
+
};
|
|
40
|
+
const escapeHtml = value => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
41
|
+
const lineEnd = (source, from) => {
|
|
42
|
+
const end = source.indexOf("\n", from);
|
|
43
|
+
return end < 0 ? source.length : end;
|
|
44
|
+
};
|
|
45
|
+
const closes = (line, indent) => {
|
|
46
|
+
const closing = CLOSING.exec(line);
|
|
47
|
+
return !!closing && closing[1].length <= indent;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/** Column where a list item's text starts, as CommonMark counts it. */
|
|
51
|
+
const itemTextIndent = raw => {
|
|
52
|
+
const [, bullet = "", gap = ""] = /^( {0,3}(?:[-+*]|\d{1,9}[.)]))( *)/.exec(raw) ?? [];
|
|
53
|
+
return bullet.length + (gap.length >= 1 && gap.length <= 4 && raw[bullet.length + gap.length] !== "\n" ? gap.length : 1);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Offset of the block's closing line inside a list or quote, or -1. Lazy
|
|
58
|
+
* paragraph lines may hold it; a list item's own fenced code cannot.
|
|
59
|
+
*/
|
|
60
|
+
const closingLineIn = (token, indent) => {
|
|
61
|
+
if (token.type !== "list" && token.type !== "blockquote") return -1;
|
|
62
|
+
const items = token.type === "list" ? token.items : [];
|
|
63
|
+
let item = -1;
|
|
64
|
+
let itemStart = 0;
|
|
65
|
+
let itemEnd = 0;
|
|
66
|
+
let text = 0;
|
|
67
|
+
let fence = "";
|
|
68
|
+
for (let from = 0; from < token.raw.length;) {
|
|
69
|
+
while (item + 1 < items.length && from >= itemEnd) {
|
|
70
|
+
item++;
|
|
71
|
+
itemStart = itemEnd;
|
|
72
|
+
itemEnd += items[item].raw.length;
|
|
73
|
+
text = itemTextIndent(items[item].raw);
|
|
74
|
+
fence = "";
|
|
75
|
+
}
|
|
76
|
+
const end = lineEnd(token.raw, from);
|
|
77
|
+
const line = token.raw.slice(from, end);
|
|
78
|
+
// A line indented less than the item's text has left the item and its fence.
|
|
79
|
+
if (fence && line.trim() && line.length - line.trimStart().length < text) fence = "";
|
|
80
|
+
// The first line of an item carries its bullet where the text would be indented.
|
|
81
|
+
const marker = items.length ? FENCE.exec(from === itemStart ? " ".repeat(text) + line.slice(text) : line) : null;
|
|
82
|
+
if (fence) {
|
|
83
|
+
if (marker && marker[2][0] === fence[0] && marker[2].length >= fence.length && !marker[3].trim()) fence = "";
|
|
84
|
+
} else if (marker && marker[1].length >= text && marker[1].length <= text + 3 && !(marker[2][0] === "`" && marker[3].includes("`"))) {
|
|
85
|
+
fence = marker[2];
|
|
86
|
+
} else if (closes(line, indent)) {
|
|
87
|
+
return from;
|
|
88
|
+
}
|
|
89
|
+
from = end + 1;
|
|
90
|
+
}
|
|
91
|
+
return -1;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/** Whether `line` starts a block of its own after a paragraph line instead of continuing the paragraph, as marked decides. */
|
|
95
|
+
const interruptsParagraph = line => Lexer.rules.block.gfm.paragraph.exec(`x\n${line}\n`)?.[0] === "x";
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Reads info blocks in one `\n`-separated source. The opening line reads like
|
|
99
|
+
* a paragraph line, so the body continues it: only a block that may interrupt
|
|
100
|
+
* a paragraph, such as a fence, a list, a quote, or block HTML, hides its
|
|
101
|
+
* lines from the closing line. A scan's only state is its line and whether a
|
|
102
|
+
* paragraph is open there, so a later scan that reaches a line an earlier one
|
|
103
|
+
* passed ends the same way; `outcomes` remembers that, which keeps reading
|
|
104
|
+
* many openers of one document linear.
|
|
105
|
+
*/
|
|
106
|
+
const infoBlockReader = source => {
|
|
107
|
+
const outcomes = new Map();
|
|
108
|
+
let tokenizer;
|
|
109
|
+
|
|
110
|
+
/** Code, lists, quotes, and HTML whose lines belong to the body even when one of them reads `:::`. */
|
|
111
|
+
const container = (offset, line, paragraph) => {
|
|
112
|
+
const kind = /^(?: {4}| {0,3}\t)/.test(line) ? "code" : /^ {0,3}(?:`{3,}|~{3,})/.test(line) ? "fences" : /^ {0,3}(?:[-+*][ \t]|\d{1,9}[.)][ \t])/.test(line) ? "list" : /^ {0,3}>/.test(line) ? "blockquote" : /^ {0,3}</.test(line) ? "html" : undefined;
|
|
113
|
+
if (!kind || paragraph && !interruptsParagraph(line)) return undefined;
|
|
114
|
+
if (!tokenizer) {
|
|
115
|
+
// One tokenizer per document: its lexer queues inline work that nothing drains.
|
|
116
|
+
tokenizer = new Tokenizer({
|
|
117
|
+
gfm: true
|
|
118
|
+
});
|
|
119
|
+
new Lexer({
|
|
120
|
+
gfm: true,
|
|
121
|
+
tokenizer
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return tokenizer[kind](source.slice(offset));
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/** Offset of the closing line for a body that starts at `start`, or -1 when the block never closes. */
|
|
128
|
+
const closingLine = (start, indent) => {
|
|
129
|
+
const passed = [];
|
|
130
|
+
let offset = start;
|
|
131
|
+
let paragraph = true;
|
|
132
|
+
let result = -1;
|
|
133
|
+
while (offset < source.length) {
|
|
134
|
+
const key = offset * 8 + indent * 2 + (paragraph ? 1 : 0);
|
|
135
|
+
const known = outcomes.get(key);
|
|
136
|
+
if (known !== undefined) {
|
|
137
|
+
result = known;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
passed.push(key);
|
|
141
|
+
const end = lineEnd(source, offset);
|
|
142
|
+
const line = source.slice(offset, end);
|
|
143
|
+
if (closes(line, indent)) {
|
|
144
|
+
result = offset;
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
const token = container(offset, line, paragraph);
|
|
148
|
+
if (token) {
|
|
149
|
+
const closing = closingLineIn(token, indent);
|
|
150
|
+
const length = closing < 0 ? token.raw.length : closing;
|
|
151
|
+
if (length > 0) {
|
|
152
|
+
offset += length;
|
|
153
|
+
paragraph = false;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// A blank line ends a paragraph; any other line continues or starts one.
|
|
158
|
+
paragraph = line.trim() !== "";
|
|
159
|
+
offset = Math.min(end + 1, source.length);
|
|
160
|
+
}
|
|
161
|
+
for (const key of passed) outcomes.set(key, result);
|
|
162
|
+
return result;
|
|
163
|
+
};
|
|
164
|
+
return offset => {
|
|
165
|
+
const firstEnd = lineEnd(source, offset);
|
|
166
|
+
const opener = OPENER.exec(source.slice(offset, firstEnd));
|
|
167
|
+
if (!opener) return null;
|
|
168
|
+
const indent = opener[1].length;
|
|
169
|
+
const type = opener[2].toLowerCase();
|
|
170
|
+
const title = opener[3]?.trim() || undefined;
|
|
171
|
+
const bodyStart = Math.min(firstEnd + 1, source.length);
|
|
172
|
+
const closing = closingLine(bodyStart, indent);
|
|
173
|
+
if (closing < 0) return {
|
|
174
|
+
type,
|
|
175
|
+
title,
|
|
176
|
+
body: source.slice(bodyStart),
|
|
177
|
+
length: source.length - offset,
|
|
178
|
+
closed: false
|
|
179
|
+
};
|
|
180
|
+
const body = source.slice(bodyStart, closing).replace(/\n$/, "");
|
|
181
|
+
return {
|
|
182
|
+
type,
|
|
183
|
+
title,
|
|
184
|
+
body,
|
|
185
|
+
length: Math.min(lineEnd(source, closing) + 1, source.length) - offset,
|
|
186
|
+
closed: true
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Reads the info block that starts `source`. Returns `null` when the first
|
|
193
|
+
* line is not an info-block opener. Applications with their own Markdown
|
|
194
|
+
* pipeline use this to recognize exactly the blocks `MarkdownView` renders.
|
|
195
|
+
* Any line endings are accepted; `length` counts `source` as given.
|
|
196
|
+
*/
|
|
197
|
+
export const scanMarkdownInfoBlock = source => {
|
|
198
|
+
const normalized = source.replace(/\r\n?/g, "\n");
|
|
199
|
+
const block = infoBlockReader(normalized)(0);
|
|
200
|
+
if (!block || normalized.length === source.length) return block;
|
|
201
|
+
// Only `\r\n` shortens the source; count each one twice up to the block's end.
|
|
202
|
+
let length = 0;
|
|
203
|
+
for (let index = 0; index < block.length; index++) length += source[length] === "\r" && source[length + 1] === "\n" ? 2 : 1;
|
|
204
|
+
return {
|
|
205
|
+
...block,
|
|
206
|
+
length
|
|
207
|
+
};
|
|
208
|
+
};
|
|
209
|
+
const renderBlock = (input, messages) => {
|
|
210
|
+
const name = input.title ? `<p class="${NOTICE_CARD_CLASSES.title}">${escapeHtml(input.title)}</p>` : `<span class="k2b-sr-only">${escapeHtml(LABELS[input.type](messages))}: </span>`;
|
|
211
|
+
return `<aside class="${NOTICE_CARD_CLASSES.root}" data-tone="${TONES[input.type]}" role="note">${name}<div class="${NOTICE_CARD_CLASSES.body}">${input.bodyHtml}</div></aside>`;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Renders an info block as the calm `NoticeCard`: the tone tints the surface,
|
|
216
|
+
* the text stays neutral, and there is no icon. A title is the visible heading
|
|
217
|
+
* and names the block; without one, the localized type name is announced to
|
|
218
|
+
* screen readers only.
|
|
219
|
+
*/
|
|
220
|
+
export const renderMarkdownInfoBlock = input => renderBlock(input, resolveUiMessages(input.locale));
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* What one `marked` lexer run knows about its document: the opening lines, the
|
|
224
|
+
* blocks read so far, the length of the last top-level source, and the extent
|
|
225
|
+
* of the last top-level paragraph `start` measured.
|
|
226
|
+
*/
|
|
227
|
+
|
|
228
|
+
const documents = new WeakMap();
|
|
229
|
+
|
|
230
|
+
/** The closed block that starts at `offset`, read once per document. */
|
|
231
|
+
const closedBlockAt = (document, offset) => {
|
|
232
|
+
let block = document.blocks.get(offset);
|
|
233
|
+
if (block === undefined) {
|
|
234
|
+
const read = document.read(offset);
|
|
235
|
+
block = read?.closed ? read : null;
|
|
236
|
+
document.blocks.set(offset, block);
|
|
237
|
+
}
|
|
238
|
+
return block;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
/** Index of the first of the ascending `offsets` at or after `from`. */
|
|
242
|
+
const firstAtOrAfter = (offsets, from) => {
|
|
243
|
+
let low = 0;
|
|
244
|
+
let high = offsets.length;
|
|
245
|
+
while (low < high) {
|
|
246
|
+
const middle = low + high >> 1;
|
|
247
|
+
if (offsets[middle] < from) low = middle + 1;else high = middle;
|
|
248
|
+
}
|
|
249
|
+
return low;
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* The first closed block after the top-level `position` inside the paragraph
|
|
254
|
+
* that would start there. Openers further away cannot end that paragraph, and
|
|
255
|
+
* a paragraph that starts inside the last measured one ends where it ends.
|
|
256
|
+
*/
|
|
257
|
+
const nextBlockStart = (document, position, rules) => {
|
|
258
|
+
if (position < document.paragraph.start || position >= document.paragraph.end) {
|
|
259
|
+
const extent = rules.exec(document.source.slice(position))?.[0].length ?? 0;
|
|
260
|
+
document.paragraph = {
|
|
261
|
+
start: position,
|
|
262
|
+
end: position + extent
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
for (let index = firstAtOrAfter(document.openers, position + 1); index < document.openers.length; index++) {
|
|
266
|
+
const offset = document.openers[index];
|
|
267
|
+
if (offset >= document.paragraph.end) return undefined;
|
|
268
|
+
if (closedBlockAt(document, offset)) return offset;
|
|
269
|
+
}
|
|
270
|
+
return undefined;
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
/** The paragraph rule of the lexer's own flavour. */
|
|
274
|
+
const paragraphRule = lexer => Lexer.rules.block[lexer.options.pedantic ? "pedantic" : lexer.options.gfm ? "gfm" : "normal"].paragraph;
|
|
275
|
+
const createExtension = messages => {
|
|
276
|
+
const extension = {
|
|
277
|
+
name: "markdownInfoBlock",
|
|
278
|
+
level: "block",
|
|
279
|
+
// marked ends a top-level paragraph where `start` points. Only a block
|
|
280
|
+
// that closes renders, so `start` points at the first one inside the
|
|
281
|
+
// paragraph; a split anywhere else would only make marked lex the rest of
|
|
282
|
+
// the document again.
|
|
283
|
+
start(source) {
|
|
284
|
+
const document = documents.get(this.lexer);
|
|
285
|
+
// marked asks right after the tokenizer below ran at the same top-level position.
|
|
286
|
+
if (!document || source.length + 1 !== document.top) return undefined;
|
|
287
|
+
const position = document.source.length - source.length - 1;
|
|
288
|
+
const next = nextBlockStart(document, position, paragraphRule(this.lexer));
|
|
289
|
+
return next === undefined ? undefined : next - position - 1;
|
|
290
|
+
},
|
|
291
|
+
tokenizer(source, tokens) {
|
|
292
|
+
let document = documents.get(this.lexer);
|
|
293
|
+
if (tokens !== this.lexer.tokens) {
|
|
294
|
+
// Nested sources are rewritten text, not the rest of the document.
|
|
295
|
+
if (document) document.top = -1;
|
|
296
|
+
return undefined;
|
|
297
|
+
}
|
|
298
|
+
// The first top-level call sees the whole document; later ones see what is left of it.
|
|
299
|
+
if (!document) {
|
|
300
|
+
document = {
|
|
301
|
+
source,
|
|
302
|
+
read: infoBlockReader(source),
|
|
303
|
+
openers: Array.from(source.matchAll(OPENERS), opener => opener.index),
|
|
304
|
+
blocks: new Map(),
|
|
305
|
+
top: source.length,
|
|
306
|
+
paragraph: {
|
|
307
|
+
start: -1,
|
|
308
|
+
end: -1
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
documents.set(this.lexer, document);
|
|
312
|
+
}
|
|
313
|
+
document.top = source.length;
|
|
314
|
+
if (!START.test(source)) return undefined;
|
|
315
|
+
const block = closedBlockAt(document, document.source.length - source.length);
|
|
316
|
+
if (!block) return undefined;
|
|
317
|
+
return {
|
|
318
|
+
type: "markdownInfoBlock",
|
|
319
|
+
raw: source.slice(0, block.length),
|
|
320
|
+
blockType: block.type,
|
|
321
|
+
title: block.title,
|
|
322
|
+
tokens: this.lexer.blockTokens(block.body, [])
|
|
323
|
+
};
|
|
324
|
+
},
|
|
325
|
+
renderer(token) {
|
|
326
|
+
return renderBlock({
|
|
327
|
+
type: token.blockType,
|
|
328
|
+
title: token.title,
|
|
329
|
+
bodyHtml: this.parser.parse(token.tokens ?? [])
|
|
330
|
+
}, messages);
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
return {
|
|
334
|
+
extensions: [extension],
|
|
335
|
+
tokenizer: {
|
|
336
|
+
// A setext heading's text is a paragraph, so it ends before a block as
|
|
337
|
+
// well; marked does not clip it at `start` itself.
|
|
338
|
+
lheading(source) {
|
|
339
|
+
const document = documents.get(this.lexer);
|
|
340
|
+
if (!document || source.length !== document.top) return false;
|
|
341
|
+
const position = document.source.length - source.length;
|
|
342
|
+
const next = nextBlockStart(document, position, paragraphRule(this.lexer));
|
|
343
|
+
return next === undefined ? false : Tokenizer.prototype.lheading.call(this, source.slice(0, next - position));
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
};
|
|
348
|
+
const extensions = new WeakMap();
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* `marked` extension for info blocks at the top level of a document. Blocks
|
|
352
|
+
* inside lists, quotes, or another block stay ordinary text. The body goes
|
|
353
|
+
* through the host's own Markdown renderer, so its escaping and link rules
|
|
354
|
+
* apply unchanged. Locales that share a UI message catalog get the same
|
|
355
|
+
* extension, so a host may cache one `marked` instance per extension.
|
|
356
|
+
*/
|
|
357
|
+
export const markdownInfoBlocks = (options = {}) => {
|
|
358
|
+
const messages = resolveUiMessages(options.locale);
|
|
359
|
+
const extension = extensions.get(messages) ?? createExtension(messages);
|
|
360
|
+
extensions.set(messages, extension);
|
|
361
|
+
return extension;
|
|
362
|
+
};
|
|
363
|
+
//# sourceMappingURL=markdown-info-blocks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sources":["content/markdown-info-blocks.ts"],"sourcesContent":["import { Lexer, type MarkedExtension, Tokenizer, type TokenizerAndRendererExtension } from \"marked\";\nimport { resolveUiMessages, type UiMessages } from \"../intl/messages\";\nimport { NOTICE_CARD_CLASSES, type NoticeTone } from \"../surfaces/NoticeCard\";\n\n/**\n * Markdown info blocks: one grammar and one look for every Markdown surface.\n *\n * ```markdown\n * :::warning Before deleting\n * Deleting an item cannot be undone.\n * :::\n * ```\n *\n * The opening line holds `:::`, a type in any letter case, and an optional\n * plain-text title. The block ends at a line holding only `:::`, indented no\n * deeper than the opener; code, lists, quotes, and HTML inside the body cannot\n * end it. The body is ordinary Markdown. A block that is never closed stays\n * ordinary text.\n */\n\nconst TONES = {\n note: \"neutral\",\n info: \"info\",\n success: \"success\",\n warning: \"warning\",\n danger: \"danger\",\n} as const satisfies Record<string, NoticeTone>;\n\nexport type MarkdownInfoBlockType = keyof typeof TONES;\n\nexport type MarkdownInfoBlock = {\n type: MarkdownInfoBlockType;\n /** Plain-text title from the opening line. */\n title?: string;\n /** Raw Markdown between the opening and closing lines, with `\\n` line breaks. */\n body: string;\n /** Source length through the closing line, or the whole source when the block is not closed. */\n length: number;\n closed: boolean;\n};\n\nexport type MarkdownInfoBlockRenderInput = {\n type: MarkdownInfoBlockType;\n title?: string;\n /** Already rendered body HTML. The caller owns its sanitization. */\n bodyHtml: string;\n /** Locale for the screen-reader name of an untitled block. */\n locale?: string;\n};\n\nconst OPENER = /^( {0,3}):::(note|info|success|warning|danger)(?:[ \\t]+(.*?))?[ \\t]*$/i;\nconst START = /^ {0,3}:::(?:note|info|success|warning|danger)(?=[ \\t\\n]|$)/i;\nconst OPENERS = /(?<=^|\\n) {0,3}:::(?:note|info|success|warning|danger)(?=[ \\t\\n]|$)/gi;\nconst CLOSING = /^( {0,3}):::[ \\t]*$/;\nconst FENCE = /^( *)(`{3,}|~{3,})(.*)$/;\n\ntype ContainerKind = \"code\" | \"fences\" | \"list\" | \"blockquote\" | \"html\";\ntype ContainerToken = NonNullable<ReturnType<Tokenizer[ContainerKind]>>;\n\nconst LABELS: Record<MarkdownInfoBlockType, (t: UiMessages) => string> = {\n note: (t) => t.infoBlockNote,\n info: (t) => t.infoBlockInfo,\n success: (t) => t.infoBlockSuccess,\n warning: (t) => t.infoBlockWarning,\n danger: (t) => t.infoBlockDanger,\n};\n\nconst escapeHtml = (value: string): string =>\n value.replaceAll(\"&\", \"&\").replaceAll(\"<\", \"<\").replaceAll(\">\", \">\").replaceAll('\"', \""\").replaceAll(\"'\", \"'\");\n\nconst lineEnd = (source: string, from: number): number => {\n const end = source.indexOf(\"\\n\", from);\n return end < 0 ? source.length : end;\n};\n\nconst closes = (line: string, indent: number): boolean => {\n const closing = CLOSING.exec(line);\n return !!closing && closing[1]!.length <= indent;\n};\n\n/** Column where a list item's text starts, as CommonMark counts it. */\nconst itemTextIndent = (raw: string): number => {\n const [, bullet = \"\", gap = \"\"] = /^( {0,3}(?:[-+*]|\\d{1,9}[.)]))( *)/.exec(raw) ?? [];\n return bullet.length + (gap.length >= 1 && gap.length <= 4 && raw[bullet.length + gap.length] !== \"\\n\" ? gap.length : 1);\n};\n\n/**\n * Offset of the block's closing line inside a list or quote, or -1. Lazy\n * paragraph lines may hold it; a list item's own fenced code cannot.\n */\nconst closingLineIn = (token: ContainerToken, indent: number): number => {\n if (token.type !== \"list\" && token.type !== \"blockquote\") return -1;\n const items = token.type === \"list\" ? token.items : [];\n let item = -1;\n let itemStart = 0;\n let itemEnd = 0;\n let text = 0;\n let fence = \"\";\n for (let from = 0; from < token.raw.length; ) {\n while (item + 1 < items.length && from >= itemEnd) {\n item++;\n itemStart = itemEnd;\n itemEnd += items[item]!.raw.length;\n text = itemTextIndent(items[item]!.raw);\n fence = \"\";\n }\n const end = lineEnd(token.raw, from);\n const line = token.raw.slice(from, end);\n // A line indented less than the item's text has left the item and its fence.\n if (fence && line.trim() && line.length - line.trimStart().length < text) fence = \"\";\n // The first line of an item carries its bullet where the text would be indented.\n const marker = items.length ? FENCE.exec(from === itemStart ? \" \".repeat(text) + line.slice(text) : line) : null;\n if (fence) {\n if (marker && marker[2]![0] === fence[0] && marker[2]!.length >= fence.length && !marker[3]!.trim()) fence = \"\";\n } else if (\n marker &&\n marker[1]!.length >= text &&\n marker[1]!.length <= text + 3 &&\n !(marker[2]![0] === \"`\" && marker[3]!.includes(\"`\"))\n ) {\n fence = marker[2]!;\n } else if (closes(line, indent)) {\n return from;\n }\n from = end + 1;\n }\n return -1;\n};\n\n/** Whether `line` starts a block of its own after a paragraph line instead of continuing the paragraph, as marked decides. */\nconst interruptsParagraph = (line: string): boolean => Lexer.rules.block.gfm.paragraph.exec(`x\\n${line}\\n`)?.[0] === \"x\";\n\n/**\n * Reads info blocks in one `\\n`-separated source. The opening line reads like\n * a paragraph line, so the body continues it: only a block that may interrupt\n * a paragraph, such as a fence, a list, a quote, or block HTML, hides its\n * lines from the closing line. A scan's only state is its line and whether a\n * paragraph is open there, so a later scan that reaches a line an earlier one\n * passed ends the same way; `outcomes` remembers that, which keeps reading\n * many openers of one document linear.\n */\nconst infoBlockReader = (source: string) => {\n const outcomes = new Map<number, number>();\n let tokenizer: Tokenizer | undefined;\n\n /** Code, lists, quotes, and HTML whose lines belong to the body even when one of them reads `:::`. */\n const container = (offset: number, line: string, paragraph: boolean): ContainerToken | undefined => {\n const kind: ContainerKind | undefined = /^(?: {4}| {0,3}\\t)/.test(line)\n ? \"code\"\n : /^ {0,3}(?:`{3,}|~{3,})/.test(line)\n ? \"fences\"\n : /^ {0,3}(?:[-+*][ \\t]|\\d{1,9}[.)][ \\t])/.test(line)\n ? \"list\"\n : /^ {0,3}>/.test(line)\n ? \"blockquote\"\n : /^ {0,3}</.test(line)\n ? \"html\"\n : undefined;\n if (!kind || (paragraph && !interruptsParagraph(line))) return undefined;\n if (!tokenizer) {\n // One tokenizer per document: its lexer queues inline work that nothing drains.\n tokenizer = new Tokenizer({ gfm: true });\n new Lexer({ gfm: true, tokenizer });\n }\n return tokenizer[kind](source.slice(offset));\n };\n\n /** Offset of the closing line for a body that starts at `start`, or -1 when the block never closes. */\n const closingLine = (start: number, indent: number): number => {\n const passed: number[] = [];\n let offset = start;\n let paragraph = true;\n let result = -1;\n while (offset < source.length) {\n const key = offset * 8 + indent * 2 + (paragraph ? 1 : 0);\n const known = outcomes.get(key);\n if (known !== undefined) {\n result = known;\n break;\n }\n passed.push(key);\n const end = lineEnd(source, offset);\n const line = source.slice(offset, end);\n if (closes(line, indent)) {\n result = offset;\n break;\n }\n const token = container(offset, line, paragraph);\n if (token) {\n const closing = closingLineIn(token, indent);\n const length = closing < 0 ? token.raw.length : closing;\n if (length > 0) {\n offset += length;\n paragraph = false;\n continue;\n }\n }\n // A blank line ends a paragraph; any other line continues or starts one.\n paragraph = line.trim() !== \"\";\n offset = Math.min(end + 1, source.length);\n }\n for (const key of passed) outcomes.set(key, result);\n return result;\n };\n\n return (offset: number): MarkdownInfoBlock | null => {\n const firstEnd = lineEnd(source, offset);\n const opener = OPENER.exec(source.slice(offset, firstEnd));\n if (!opener) return null;\n const indent = opener[1]!.length;\n const type = opener[2]!.toLowerCase() as MarkdownInfoBlockType;\n const title = opener[3]?.trim() || undefined;\n const bodyStart = Math.min(firstEnd + 1, source.length);\n const closing = closingLine(bodyStart, indent);\n if (closing < 0) return { type, title, body: source.slice(bodyStart), length: source.length - offset, closed: false };\n const body = source.slice(bodyStart, closing).replace(/\\n$/, \"\");\n return { type, title, body, length: Math.min(lineEnd(source, closing) + 1, source.length) - offset, closed: true };\n };\n};\n\n/**\n * Reads the info block that starts `source`. Returns `null` when the first\n * line is not an info-block opener. Applications with their own Markdown\n * pipeline use this to recognize exactly the blocks `MarkdownView` renders.\n * Any line endings are accepted; `length` counts `source` as given.\n */\nexport const scanMarkdownInfoBlock = (source: string): MarkdownInfoBlock | null => {\n const normalized = source.replace(/\\r\\n?/g, \"\\n\");\n const block = infoBlockReader(normalized)(0);\n if (!block || normalized.length === source.length) return block;\n // Only `\\r\\n` shortens the source; count each one twice up to the block's end.\n let length = 0;\n for (let index = 0; index < block.length; index++) length += source[length] === \"\\r\" && source[length + 1] === \"\\n\" ? 2 : 1;\n return { ...block, length };\n};\n\nconst renderBlock = (input: Omit<MarkdownInfoBlockRenderInput, \"locale\">, messages: UiMessages): string => {\n const name = input.title\n ? `<p class=\"${NOTICE_CARD_CLASSES.title}\">${escapeHtml(input.title)}</p>`\n : `<span class=\"k2b-sr-only\">${escapeHtml(LABELS[input.type](messages))}: </span>`;\n return `<aside class=\"${NOTICE_CARD_CLASSES.root}\" data-tone=\"${TONES[input.type]}\" role=\"note\">${name}<div class=\"${NOTICE_CARD_CLASSES.body}\">${input.bodyHtml}</div></aside>`;\n};\n\n/**\n * Renders an info block as the calm `NoticeCard`: the tone tints the surface,\n * the text stays neutral, and there is no icon. A title is the visible heading\n * and names the block; without one, the localized type name is announced to\n * screen readers only.\n */\nexport const renderMarkdownInfoBlock = (input: MarkdownInfoBlockRenderInput): string => renderBlock(input, resolveUiMessages(input.locale));\n\n/**\n * What one `marked` lexer run knows about its document: the opening lines, the\n * blocks read so far, the length of the last top-level source, and the extent\n * of the last top-level paragraph `start` measured.\n */\ntype DocumentState = {\n source: string;\n read: (offset: number) => MarkdownInfoBlock | null;\n openers: number[];\n blocks: Map<number, MarkdownInfoBlock | null>;\n top: number;\n paragraph: { start: number; end: number };\n};\n\nconst documents = new WeakMap<object, DocumentState>();\n\n/** The closed block that starts at `offset`, read once per document. */\nconst closedBlockAt = (document: DocumentState, offset: number): MarkdownInfoBlock | null => {\n let block = document.blocks.get(offset);\n if (block === undefined) {\n const read = document.read(offset);\n block = read?.closed ? read : null;\n document.blocks.set(offset, block);\n }\n return block;\n};\n\n/** Index of the first of the ascending `offsets` at or after `from`. */\nconst firstAtOrAfter = (offsets: readonly number[], from: number): number => {\n let low = 0;\n let high = offsets.length;\n while (low < high) {\n const middle = (low + high) >> 1;\n if (offsets[middle]! < from) low = middle + 1;\n else high = middle;\n }\n return low;\n};\n\n/**\n * The first closed block after the top-level `position` inside the paragraph\n * that would start there. Openers further away cannot end that paragraph, and\n * a paragraph that starts inside the last measured one ends where it ends.\n */\nconst nextBlockStart = (document: DocumentState, position: number, rules: RegExp): number | undefined => {\n if (position < document.paragraph.start || position >= document.paragraph.end) {\n const extent = rules.exec(document.source.slice(position))?.[0].length ?? 0;\n document.paragraph = { start: position, end: position + extent };\n }\n for (let index = firstAtOrAfter(document.openers, position + 1); index < document.openers.length; index++) {\n const offset = document.openers[index]!;\n if (offset >= document.paragraph.end) return undefined;\n if (closedBlockAt(document, offset)) return offset;\n }\n return undefined;\n};\n\n/** The paragraph rule of the lexer's own flavour. */\nconst paragraphRule = (lexer: Lexer): RegExp =>\n Lexer.rules.block[lexer.options.pedantic ? \"pedantic\" : lexer.options.gfm ? \"gfm\" : \"normal\"].paragraph;\n\nconst createExtension = (messages: UiMessages): MarkedExtension => {\n const extension: TokenizerAndRendererExtension = {\n name: \"markdownInfoBlock\",\n level: \"block\",\n // marked ends a top-level paragraph where `start` points. Only a block\n // that closes renders, so `start` points at the first one inside the\n // paragraph; a split anywhere else would only make marked lex the rest of\n // the document again.\n start(source) {\n const document = documents.get(this.lexer);\n // marked asks right after the tokenizer below ran at the same top-level position.\n if (!document || source.length + 1 !== document.top) return undefined;\n const position = document.source.length - source.length - 1;\n const next = nextBlockStart(document, position, paragraphRule(this.lexer));\n return next === undefined ? undefined : next - position - 1;\n },\n tokenizer(source, tokens) {\n let document = documents.get(this.lexer);\n if (tokens !== this.lexer.tokens) {\n // Nested sources are rewritten text, not the rest of the document.\n if (document) document.top = -1;\n return undefined;\n }\n // The first top-level call sees the whole document; later ones see what is left of it.\n if (!document) {\n document = {\n source,\n read: infoBlockReader(source),\n openers: Array.from(source.matchAll(OPENERS), (opener) => opener.index),\n blocks: new Map(),\n top: source.length,\n paragraph: { start: -1, end: -1 },\n };\n documents.set(this.lexer, document);\n }\n document.top = source.length;\n if (!START.test(source)) return undefined;\n const block = closedBlockAt(document, document.source.length - source.length);\n if (!block) return undefined;\n return {\n type: \"markdownInfoBlock\",\n raw: source.slice(0, block.length),\n blockType: block.type,\n title: block.title,\n tokens: this.lexer.blockTokens(block.body, []),\n };\n },\n renderer(token) {\n return renderBlock(\n {\n type: token.blockType as MarkdownInfoBlockType,\n title: token.title as string | undefined,\n bodyHtml: this.parser.parse(token.tokens ?? []),\n },\n messages,\n );\n },\n };\n return {\n extensions: [extension],\n tokenizer: {\n // A setext heading's text is a paragraph, so it ends before a block as\n // well; marked does not clip it at `start` itself.\n lheading(source) {\n const document = documents.get(this.lexer);\n if (!document || source.length !== document.top) return false;\n const position = document.source.length - source.length;\n const next = nextBlockStart(document, position, paragraphRule(this.lexer));\n return next === undefined ? false : Tokenizer.prototype.lheading.call(this, source.slice(0, next - position));\n },\n },\n };\n};\n\nconst extensions = new WeakMap<UiMessages, MarkedExtension>();\n\n/**\n * `marked` extension for info blocks at the top level of a document. Blocks\n * inside lists, quotes, or another block stay ordinary text. The body goes\n * through the host's own Markdown renderer, so its escaping and link rules\n * apply unchanged. Locales that share a UI message catalog get the same\n * extension, so a host may cache one `marked` instance per extension.\n */\nexport const markdownInfoBlocks = (options: { locale?: string } = {}): MarkedExtension => {\n const messages = resolveUiMessages(options.locale);\n const extension = extensions.get(messages) ?? createExtension(messages);\n extensions.set(messages, extension);\n return extension;\n};\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { memo as _$memo } from "solid-js/web";
|
|
2
|
+
import { createComponent as _$createComponent } from "solid-js/web";
|
|
3
|
+
import { ssr as _$ssr } from "solid-js/web";
|
|
4
|
+
import { ssrAttribute as _$ssrAttribute } from "solid-js/web";
|
|
5
|
+
import { escape as _$escape } from "solid-js/web";
|
|
6
|
+
var _tmpl$ = ["<p role=\"alert\">", "</p>"],
|
|
7
|
+
_tmpl$2 = ["<p role=\"status\">", "</p>"],
|
|
8
|
+
_tmpl$3 = ["<div", ">", "", "</div>"],
|
|
9
|
+
_tmpl$4 = ["<p>", "</p>"],
|
|
10
|
+
_tmpl$5 = ["<i", " aria-hidden=\"true\"></i>"],
|
|
11
|
+
_tmpl$6 = ["<code>", "</code>"],
|
|
12
|
+
_tmpl$7 = ["<ol class=\"k2b-install-guide__steps\">", "</ol>"],
|
|
13
|
+
_tmpl$8 = ["<li><span class=\"k2b-install-guide__step-icon\"><i", " aria-hidden=\"true\"></i></span><div><h3>", "</h3><p>", "</p></div></li>"];
|
|
14
|
+
import { clipboard } from "@k2b/stdlib/solid";
|
|
15
|
+
import { createMemo, For, Show } from "solid-js";
|
|
16
|
+
import { Button } from "../actions/Button";
|
|
17
|
+
import { useUiMessages } from "../intl/messages";
|
|
18
|
+
import { NoticeCard } from "../surfaces/NoticeCard";
|
|
19
|
+
/**
|
|
20
|
+
* Explains how to install the current page as an app on this platform: the browser's own dialog where it offers
|
|
21
|
+
* one, otherwise the steps for Safari on iPhone, iPad, and Mac, other browsers on iPhone and iPad, Android, and other
|
|
22
|
+
* browsers. A browser inside another app cannot install, so it gets a warning and the link to copy into Safari or
|
|
23
|
+
* Chrome instead. The host owns the surrounding surface, its heading, and dismissal.
|
|
24
|
+
*/
|
|
25
|
+
export function InstallGuide(props) {
|
|
26
|
+
const messages = useUiMessages();
|
|
27
|
+
// The host may hand over another state after mount, such as the browser's own in place of the server's view.
|
|
28
|
+
const state = () => props.install;
|
|
29
|
+
const copy = clipboard.createWriter({
|
|
30
|
+
write: url => navigator.clipboard.writeText(url)
|
|
31
|
+
});
|
|
32
|
+
const embedded = () => state().platform === "in-app" || state().platform === "apple-in-app";
|
|
33
|
+
/** A browser that installs, but less reliably than the platform's own. */
|
|
34
|
+
const hint = createMemo(() => {
|
|
35
|
+
const t = messages();
|
|
36
|
+
if (state().platform === "apple-browser") return {
|
|
37
|
+
title: t.installAppleBrowserTitle,
|
|
38
|
+
detail: t.installAppleBrowserDetail
|
|
39
|
+
};
|
|
40
|
+
if (state().platform === "android-browser") return {
|
|
41
|
+
title: t.installAndroidBrowserTitle,
|
|
42
|
+
detail: t.installAndroidBrowserDetail
|
|
43
|
+
};
|
|
44
|
+
return undefined;
|
|
45
|
+
});
|
|
46
|
+
const steps = createMemo(() => {
|
|
47
|
+
const t = messages();
|
|
48
|
+
if (state().platform === "apple-mobile" || state().platform === "apple-browser") return [{
|
|
49
|
+
icon: "ti ti-share-2",
|
|
50
|
+
title: t.installOpenShare,
|
|
51
|
+
detail: state().platform === "apple-browser" ? t.installOpenShareBrowserDetail : t.installOpenShareDetail
|
|
52
|
+
}, {
|
|
53
|
+
icon: "ti ti-square-plus",
|
|
54
|
+
title: t.installHomeScreen,
|
|
55
|
+
detail: t.installHomeScreenDetail
|
|
56
|
+
}, {
|
|
57
|
+
icon: "ti ti-check",
|
|
58
|
+
title: t.installConfirmAdd,
|
|
59
|
+
detail: t.installConfirmAddDetail
|
|
60
|
+
}];
|
|
61
|
+
if (state().platform === "apple-desktop") return [{
|
|
62
|
+
icon: "ti ti-share-2",
|
|
63
|
+
title: t.installSafariMenu,
|
|
64
|
+
detail: t.installSafariMenuDetail
|
|
65
|
+
}, {
|
|
66
|
+
icon: "ti ti-app-window",
|
|
67
|
+
title: t.installAddDock,
|
|
68
|
+
detail: t.installAddDockDetail
|
|
69
|
+
}];
|
|
70
|
+
return [{
|
|
71
|
+
icon: "ti ti-dots",
|
|
72
|
+
title: t.installBrowserMenu,
|
|
73
|
+
detail: t.installBrowserMenuDetail
|
|
74
|
+
}, {
|
|
75
|
+
icon: "ti ti-square-plus",
|
|
76
|
+
title: t.installApp,
|
|
77
|
+
detail: state().platform === "android" || state().platform === "android-browser" ? t.installAndroidDetail : t.installBrowserDetail
|
|
78
|
+
}];
|
|
79
|
+
});
|
|
80
|
+
return _$ssr(_tmpl$3, _$ssrAttribute("class", props.class ? `k2b-install-guide ${_$escape(props.class, true)}` : "k2b-install-guide", false) + _$ssrAttribute("data-platform", _$escape(state().platform, true), false), _$escape(_$createComponent(Show, {
|
|
81
|
+
get when() {
|
|
82
|
+
return state().failed();
|
|
83
|
+
},
|
|
84
|
+
get children() {
|
|
85
|
+
return _$ssr(_tmpl$, _$escape(messages().installFailed));
|
|
86
|
+
}
|
|
87
|
+
})), _$escape(_$createComponent(Show, {
|
|
88
|
+
get when() {
|
|
89
|
+
return state().requested();
|
|
90
|
+
},
|
|
91
|
+
get fallback() {
|
|
92
|
+
return _$createComponent(Show, {
|
|
93
|
+
get when() {
|
|
94
|
+
return state().canPrompt() || state().busy();
|
|
95
|
+
},
|
|
96
|
+
get fallback() {
|
|
97
|
+
return _$createComponent(Show, {
|
|
98
|
+
get when() {
|
|
99
|
+
return embedded();
|
|
100
|
+
},
|
|
101
|
+
get fallback() {
|
|
102
|
+
return [_$createComponent(Show, {
|
|
103
|
+
get when() {
|
|
104
|
+
return hint();
|
|
105
|
+
},
|
|
106
|
+
children: hint => _$createComponent(NoticeCard, {
|
|
107
|
+
tone: "info",
|
|
108
|
+
get title() {
|
|
109
|
+
return hint().title;
|
|
110
|
+
},
|
|
111
|
+
get detail() {
|
|
112
|
+
return hint().detail;
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
}), _$ssr(_tmpl$7, _$escape(_$createComponent(For, {
|
|
116
|
+
get each() {
|
|
117
|
+
return steps();
|
|
118
|
+
},
|
|
119
|
+
children: step => _$ssr(_tmpl$8, _$ssrAttribute("class", _$escape(step.icon, true), false), _$escape(step.title), _$escape(step.detail))
|
|
120
|
+
}))), _$createComponent(Show, {
|
|
121
|
+
get when() {
|
|
122
|
+
return state().platform === "apple-mobile";
|
|
123
|
+
},
|
|
124
|
+
get children() {
|
|
125
|
+
return _$ssr(_tmpl$4, _$escape(messages().installSafariFallback));
|
|
126
|
+
}
|
|
127
|
+
}), _$createComponent(Show, {
|
|
128
|
+
get when() {
|
|
129
|
+
return props.note;
|
|
130
|
+
},
|
|
131
|
+
get children() {
|
|
132
|
+
return _$ssr(_tmpl$4, _$escape(props.note));
|
|
133
|
+
}
|
|
134
|
+
}), _$createComponent(Show, {
|
|
135
|
+
get when() {
|
|
136
|
+
return state().platform === "generic";
|
|
137
|
+
},
|
|
138
|
+
get children() {
|
|
139
|
+
return _$ssr(_tmpl$4, _$escape(messages().installUnavailable));
|
|
140
|
+
}
|
|
141
|
+
})];
|
|
142
|
+
},
|
|
143
|
+
get children() {
|
|
144
|
+
return [_$createComponent(NoticeCard, {
|
|
145
|
+
tone: "warning",
|
|
146
|
+
get title() {
|
|
147
|
+
return _$memo(() => state().platform === "apple-in-app")() ? messages().installAppleInAppTitle : messages().installInAppTitle;
|
|
148
|
+
},
|
|
149
|
+
get detail() {
|
|
150
|
+
return _$memo(() => state().platform === "apple-in-app")() ? messages().installAppleInAppDetail({
|
|
151
|
+
appName: props.appName
|
|
152
|
+
}) : messages().installInAppDetail({
|
|
153
|
+
appName: props.appName
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}), _$createComponent(Button, {
|
|
157
|
+
variant: "secondary",
|
|
158
|
+
onClick: () => void copy.copy(props.url),
|
|
159
|
+
get children() {
|
|
160
|
+
return [_$ssr(_tmpl$5, _$ssrAttribute("class", copy.wasCopied() ? "ti ti-check" : "ti ti-copy", false)), copy.wasCopied() ? messages().installLinkCopied : messages().installCopyLink];
|
|
161
|
+
}
|
|
162
|
+
}), _$createComponent(Show, {
|
|
163
|
+
get when() {
|
|
164
|
+
return copy.error();
|
|
165
|
+
},
|
|
166
|
+
get children() {
|
|
167
|
+
return [_$ssr(_tmpl$, _$escape(messages().installCopyFailed)), _$ssr(_tmpl$6, _$escape(props.url))];
|
|
168
|
+
}
|
|
169
|
+
}), _$createComponent(Show, {
|
|
170
|
+
get when() {
|
|
171
|
+
return props.note;
|
|
172
|
+
},
|
|
173
|
+
get children() {
|
|
174
|
+
return _$ssr(_tmpl$4, _$escape(props.note));
|
|
175
|
+
}
|
|
176
|
+
})];
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
},
|
|
180
|
+
get children() {
|
|
181
|
+
return [_$ssr(_tmpl$4, _$escape(messages().installNative({
|
|
182
|
+
appName: props.appName
|
|
183
|
+
}))), _$createComponent(Button, {
|
|
184
|
+
get loading() {
|
|
185
|
+
return state().busy();
|
|
186
|
+
},
|
|
187
|
+
onClick: () => void state().install(),
|
|
188
|
+
get children() {
|
|
189
|
+
return messages().installApp;
|
|
190
|
+
}
|
|
191
|
+
})];
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
},
|
|
195
|
+
get children() {
|
|
196
|
+
return _$ssr(_tmpl$2, _$escape(messages().installRequested));
|
|
197
|
+
}
|
|
198
|
+
})));
|
|
199
|
+
}
|
|
200
|
+
export default InstallGuide;
|
|
201
|
+
//# sourceMappingURL=InstallGuide.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sources":["feedback/InstallGuide.tsx"],"sourcesContent":["import { clipboard } from \"@k2b/stdlib/solid\";\nimport { createMemo, For, type JSX, Show } from \"solid-js\";\nimport { Button } from \"../actions/Button\";\nimport { useUiMessages } from \"../intl/messages\";\nimport { NoticeCard } from \"../surfaces/NoticeCard\";\nimport type { InstallPrompt } from \"./install\";\n\nexport type InstallGuideProps = {\n /** The name people see on their Home Screen, used in the guidance text. */\n appName: string;\n install: InstallPrompt;\n /**\n * The address a browser inside another app copies, to open it in Safari or Chrome. Shown as text when copying\n * fails, so it can be copied by hand.\n */\n url: string;\n /**\n * An application note under the installation steps or the embedded browser's link, for example what else the\n * installed app enables.\n */\n note?: string;\n class?: string;\n};\n\n/**\n * Explains how to install the current page as an app on this platform: the browser's own dialog where it offers\n * one, otherwise the steps for Safari on iPhone, iPad, and Mac, other browsers on iPhone and iPad, Android, and other\n * browsers. A browser inside another app cannot install, so it gets a warning and the link to copy into Safari or\n * Chrome instead. The host owns the surrounding surface, its heading, and dismissal.\n */\nexport function InstallGuide(props: InstallGuideProps): JSX.Element {\n const messages = useUiMessages();\n // The host may hand over another state after mount, such as the browser's own in place of the server's view.\n const state = () => props.install;\n const copy = clipboard.createWriter({ write: (url: string) => navigator.clipboard.writeText(url) });\n const embedded = () => state().platform === \"in-app\" || state().platform === \"apple-in-app\";\n /** A browser that installs, but less reliably than the platform's own. */\n const hint = createMemo(() => {\n const t = messages();\n if (state().platform === \"apple-browser\") return { title: t.installAppleBrowserTitle, detail: t.installAppleBrowserDetail };\n if (state().platform === \"android-browser\") return { title: t.installAndroidBrowserTitle, detail: t.installAndroidBrowserDetail };\n return undefined;\n });\n const steps = createMemo(() => {\n const t = messages();\n if (state().platform === \"apple-mobile\" || state().platform === \"apple-browser\")\n return [\n {\n icon: \"ti ti-share-2\",\n title: t.installOpenShare,\n detail: state().platform === \"apple-browser\" ? t.installOpenShareBrowserDetail : t.installOpenShareDetail,\n },\n { icon: \"ti ti-square-plus\", title: t.installHomeScreen, detail: t.installHomeScreenDetail },\n { icon: \"ti ti-check\", title: t.installConfirmAdd, detail: t.installConfirmAddDetail },\n ];\n if (state().platform === \"apple-desktop\")\n return [\n { icon: \"ti ti-share-2\", title: t.installSafariMenu, detail: t.installSafariMenuDetail },\n { icon: \"ti ti-app-window\", title: t.installAddDock, detail: t.installAddDockDetail },\n ];\n return [\n { icon: \"ti ti-dots\", title: t.installBrowserMenu, detail: t.installBrowserMenuDetail },\n {\n icon: \"ti ti-square-plus\",\n title: t.installApp,\n detail: state().platform === \"android\" || state().platform === \"android-browser\" ? t.installAndroidDetail : t.installBrowserDetail,\n },\n ];\n });\n return (\n <div class={props.class ? `k2b-install-guide ${props.class}` : \"k2b-install-guide\"} data-platform={state().platform}>\n <Show when={state().failed()}>\n <p role=\"alert\">{messages().installFailed}</p>\n </Show>\n <Show\n when={state().requested()}\n fallback={\n <Show\n when={state().canPrompt() || state().busy()}\n fallback={\n <Show\n when={embedded()}\n fallback={\n <>\n <Show when={hint()}>{(hint) => <NoticeCard tone=\"info\" title={hint().title} detail={hint().detail} />}</Show>\n <ol class=\"k2b-install-guide__steps\">\n <For each={steps()}>\n {(step) => (\n <li>\n <span class=\"k2b-install-guide__step-icon\">\n <i class={step.icon} aria-hidden=\"true\" />\n </span>\n <div>\n <h3>{step.title}</h3>\n <p>{step.detail}</p>\n </div>\n </li>\n )}\n </For>\n </ol>\n <Show when={state().platform === \"apple-mobile\"}>\n <p>{messages().installSafariFallback}</p>\n </Show>\n <Show when={props.note}>\n <p>{props.note}</p>\n </Show>\n <Show when={state().platform === \"generic\"}>\n <p>{messages().installUnavailable}</p>\n </Show>\n </>\n }\n >\n <NoticeCard\n tone=\"warning\"\n title={state().platform === \"apple-in-app\" ? messages().installAppleInAppTitle : messages().installInAppTitle}\n detail={\n state().platform === \"apple-in-app\"\n ? messages().installAppleInAppDetail({ appName: props.appName })\n : messages().installInAppDetail({ appName: props.appName })\n }\n />\n <Button variant=\"secondary\" onClick={() => void copy.copy(props.url)}>\n <i class={copy.wasCopied() ? \"ti ti-check\" : \"ti ti-copy\"} aria-hidden=\"true\" />\n {copy.wasCopied() ? messages().installLinkCopied : messages().installCopyLink}\n </Button>\n <Show when={copy.error()}>\n <p role=\"alert\">{messages().installCopyFailed}</p>\n <code>{props.url}</code>\n </Show>\n <Show when={props.note}>\n <p>{props.note}</p>\n </Show>\n </Show>\n }\n >\n <p>{messages().installNative({ appName: props.appName })}</p>\n <Button loading={state().busy()} onClick={() => void state().install()}>\n {messages().installApp}\n </Button>\n </Show>\n }\n >\n <p role=\"status\">{messages().installRequested}</p>\n </Show>\n </div>\n );\n}\n\nexport default InstallGuide;\n"],"mappings":"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","ignoreList":[]}
|