@k2b/ui 0.18.0 → 0.20.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 +3 -2
- package/dist/browser/actions/ContextMenu.js +3 -1
- package/dist/browser/actions/ContextMenu.js.map +1 -1
- package/dist/browser/actions/Dropdown.js +91 -46
- package/dist/browser/actions/Dropdown.js.map +1 -1
- package/dist/browser/actions/FilterChip.js +4 -1
- package/dist/browser/actions/FilterChip.js.map +1 -1
- package/dist/browser/actions/GestureMenu.js +467 -0
- package/dist/browser/actions/GestureMenu.js.map +1 -0
- package/dist/browser/actions/index.js +1 -0
- package/dist/browser/actions/index.js.map +1 -1
- package/dist/browser/chat/ChatComposer.js +1255 -534
- package/dist/browser/chat/ChatComposer.js.map +1 -1
- package/dist/browser/chat/ChatPrimitives.js +9 -3
- package/dist/browser/chat/ChatPrimitives.js.map +1 -1
- package/dist/browser/chat/ChatTimeline.js +19 -1
- package/dist/browser/chat/ChatTimeline.js.map +1 -1
- package/dist/browser/chat/chat-behavior.js +9 -0
- package/dist/browser/chat/chat-behavior.js.map +1 -1
- package/dist/browser/chat/index.js.map +1 -1
- package/dist/browser/chat/types.js.map +1 -1
- package/dist/browser/content/Calendar.js +579 -533
- package/dist/browser/content/Calendar.js.map +1 -1
- package/dist/browser/content/Lightbox.js +76 -40
- package/dist/browser/content/Lightbox.js.map +1 -1
- package/dist/browser/content/MessageRow.js +2 -2
- package/dist/browser/content/MessageRow.js.map +1 -1
- package/dist/browser/content/PdfPreview.js +146 -79
- package/dist/browser/content/PdfPreview.js.map +1 -1
- package/dist/browser/content/Timeline.js +1038 -0
- package/dist/browser/content/Timeline.js.map +1 -0
- package/dist/browser/content/VideoPlayer.js +29 -9
- package/dist/browser/content/VideoPlayer.js.map +1 -1
- package/dist/browser/content/chart-inspection.js +7 -2
- package/dist/browser/content/chart-inspection.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/timeline-model.js +579 -0
- package/dist/browser/content/timeline-model.js.map +1 -0
- package/dist/browser/feedback/dialog-core.js +26 -14
- package/dist/browser/feedback/dialog-core.js.map +1 -1
- package/dist/browser/inputs/ChoiceChips.js +66 -0
- package/dist/browser/inputs/ChoiceChips.js.map +1 -0
- package/dist/browser/inputs/ChoiceGroups.js +53 -12
- package/dist/browser/inputs/ChoiceGroups.js.map +1 -1
- package/dist/browser/inputs/FileInputs.js +48 -33
- package/dist/browser/inputs/FileInputs.js.map +1 -1
- package/dist/browser/inputs/SignatureInput.js +558 -0
- package/dist/browser/inputs/SignatureInput.js.map +1 -0
- package/dist/browser/inputs/TextInput.js +13 -1
- package/dist/browser/inputs/TextInput.js.map +1 -1
- package/dist/browser/inputs/emoji/EmojiPicker.js +841 -0
- package/dist/browser/inputs/emoji/EmojiPicker.js.map +1 -0
- package/dist/browser/inputs/emoji/emoji-data.js +1903 -0
- package/dist/browser/inputs/emoji/emoji-data.js.map +1 -0
- package/dist/browser/inputs/emoji/emoji-index.js +209 -0
- package/dist/browser/inputs/emoji/emoji-index.js.map +1 -0
- package/dist/browser/inputs/emoji/emoji-messages.js +64 -0
- package/dist/browser/inputs/emoji/emoji-messages.js.map +1 -0
- package/dist/browser/inputs/index.js +5 -0
- package/dist/browser/inputs/index.js.map +1 -1
- package/dist/browser/inputs/markdown/actions.js +6 -2
- package/dist/browser/inputs/markdown/actions.js.map +1 -1
- package/dist/browser/inputs/markdown/code-zone.js +38 -2
- package/dist/browser/inputs/markdown/code-zone.js.map +1 -1
- package/dist/browser/inputs/signature-messages.js +39 -0
- package/dist/browser/inputs/signature-messages.js.map +1 -0
- package/dist/browser/inputs/signature.js +208 -0
- package/dist/browser/inputs/signature.js.map +1 -0
- package/dist/browser/internal/render-error.js +49 -0
- package/dist/browser/internal/render-error.js.map +1 -0
- package/dist/browser/intl/messages.js +102 -0
- package/dist/browser/intl/messages.js.map +1 -1
- package/dist/browser/layout/AppWorkspace.js +321 -222
- package/dist/browser/layout/AppWorkspace.js.map +1 -1
- package/dist/browser/layout/FloatingWindow.js +4 -1
- package/dist/browser/layout/FloatingWindow.js.map +1 -1
- package/dist/browser/layout/SidebarItemPreview.js +5 -1
- package/dist/browser/layout/SidebarItemPreview.js.map +1 -1
- package/dist/browser/layout/index.js.map +1 -1
- package/dist/browser/surfaces/ResourceCard.js +212 -0
- package/dist/browser/surfaces/ResourceCard.js.map +1 -0
- package/dist/browser/surfaces/index.js +1 -0
- package/dist/browser/surfaces/index.js.map +1 -1
- package/dist/licenses/emojibase-data.txt +21 -0
- package/dist/ssr/actions/ContextMenu.js +3 -1
- package/dist/ssr/actions/ContextMenu.js.map +1 -1
- package/dist/ssr/actions/Dropdown.js +49 -12
- package/dist/ssr/actions/Dropdown.js.map +1 -1
- package/dist/ssr/actions/FilterChip.js +4 -1
- package/dist/ssr/actions/FilterChip.js.map +1 -1
- package/dist/ssr/actions/GestureMenu.js +411 -0
- package/dist/ssr/actions/GestureMenu.js.map +1 -0
- package/dist/ssr/actions/index.js +1 -0
- package/dist/ssr/actions/index.js.map +1 -1
- package/dist/ssr/chat/ChatComposer.js +767 -214
- package/dist/ssr/chat/ChatComposer.js.map +1 -1
- package/dist/ssr/chat/ChatPrimitives.js +9 -3
- package/dist/ssr/chat/ChatPrimitives.js.map +1 -1
- package/dist/ssr/chat/ChatTimeline.js +19 -1
- package/dist/ssr/chat/ChatTimeline.js.map +1 -1
- package/dist/ssr/chat/chat-behavior.js +9 -0
- package/dist/ssr/chat/chat-behavior.js.map +1 -1
- package/dist/ssr/chat/index.js.map +1 -1
- package/dist/ssr/chat/types.js.map +1 -1
- package/dist/ssr/content/Calendar.js +183 -142
- package/dist/ssr/content/Calendar.js.map +1 -1
- package/dist/ssr/content/Lightbox.js +19 -5
- package/dist/ssr/content/Lightbox.js.map +1 -1
- package/dist/ssr/content/MessageRow.js +2 -2
- package/dist/ssr/content/MessageRow.js.map +1 -1
- package/dist/ssr/content/PdfPreview.js +77 -36
- package/dist/ssr/content/PdfPreview.js.map +1 -1
- package/dist/ssr/content/Timeline.js +804 -0
- package/dist/ssr/content/Timeline.js.map +1 -0
- package/dist/ssr/content/VideoPlayer.js +29 -9
- package/dist/ssr/content/VideoPlayer.js.map +1 -1
- package/dist/ssr/content/chart-inspection.js +7 -2
- package/dist/ssr/content/chart-inspection.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/timeline-model.js +579 -0
- package/dist/ssr/content/timeline-model.js.map +1 -0
- package/dist/ssr/feedback/dialog-core.js +26 -14
- package/dist/ssr/feedback/dialog-core.js.map +1 -1
- package/dist/ssr/inputs/ChoiceChips.js +66 -0
- package/dist/ssr/inputs/ChoiceChips.js.map +1 -0
- package/dist/ssr/inputs/ChoiceGroups.js +12 -4
- package/dist/ssr/inputs/ChoiceGroups.js.map +1 -1
- package/dist/ssr/inputs/FileInputs.js +19 -4
- package/dist/ssr/inputs/FileInputs.js.map +1 -1
- package/dist/ssr/inputs/SignatureInput.js +404 -0
- package/dist/ssr/inputs/SignatureInput.js.map +1 -0
- package/dist/ssr/inputs/TextInput.js +13 -1
- package/dist/ssr/inputs/TextInput.js.map +1 -1
- package/dist/ssr/inputs/emoji/EmojiPicker.js +551 -0
- package/dist/ssr/inputs/emoji/EmojiPicker.js.map +1 -0
- package/dist/ssr/inputs/emoji/emoji-data.js +1903 -0
- package/dist/ssr/inputs/emoji/emoji-data.js.map +1 -0
- package/dist/ssr/inputs/emoji/emoji-index.js +209 -0
- package/dist/ssr/inputs/emoji/emoji-index.js.map +1 -0
- package/dist/ssr/inputs/emoji/emoji-messages.js +64 -0
- package/dist/ssr/inputs/emoji/emoji-messages.js.map +1 -0
- package/dist/ssr/inputs/index.js +5 -0
- package/dist/ssr/inputs/index.js.map +1 -1
- package/dist/ssr/inputs/markdown/actions.js +6 -2
- package/dist/ssr/inputs/markdown/actions.js.map +1 -1
- package/dist/ssr/inputs/markdown/code-zone.js +38 -2
- package/dist/ssr/inputs/markdown/code-zone.js.map +1 -1
- package/dist/ssr/inputs/signature-messages.js +39 -0
- package/dist/ssr/inputs/signature-messages.js.map +1 -0
- package/dist/ssr/inputs/signature.js +208 -0
- package/dist/ssr/inputs/signature.js.map +1 -0
- package/dist/ssr/internal/render-error.js +49 -0
- package/dist/ssr/internal/render-error.js.map +1 -0
- package/dist/ssr/intl/messages.js +102 -0
- package/dist/ssr/intl/messages.js.map +1 -1
- package/dist/ssr/layout/AppWorkspace.js +112 -41
- package/dist/ssr/layout/AppWorkspace.js.map +1 -1
- package/dist/ssr/layout/FloatingWindow.js +4 -1
- package/dist/ssr/layout/FloatingWindow.js.map +1 -1
- package/dist/ssr/layout/SidebarItemPreview.js +5 -1
- package/dist/ssr/layout/SidebarItemPreview.js.map +1 -1
- package/dist/ssr/layout/index.js.map +1 -1
- package/dist/ssr/surfaces/ResourceCard.js +115 -0
- package/dist/ssr/surfaces/ResourceCard.js.map +1 -0
- package/dist/ssr/surfaces/index.js +1 -0
- package/dist/ssr/surfaces/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/actions/Dropdown.d.ts +13 -1
- package/dist/types/actions/FilterChip.d.ts +5 -0
- package/dist/types/actions/GestureMenu.d.ts +35 -0
- package/dist/types/actions/index.d.ts +2 -0
- package/dist/types/chat/ChatComposer.d.ts +27 -1
- package/dist/types/chat/chat-behavior.d.ts +15 -0
- package/dist/types/chat/index.d.ts +1 -1
- package/dist/types/chat/types.d.ts +34 -0
- package/dist/types/content/Calendar.d.ts +28 -8
- package/dist/types/content/Lightbox.d.ts +8 -0
- package/dist/types/content/Timeline.d.ts +49 -0
- package/dist/types/content/index.d.ts +4 -2
- package/dist/types/content/timeline-model.d.ts +148 -0
- package/dist/types/feedback/dialog-core.d.ts +1 -1
- package/dist/types/inputs/ChoiceChips.d.ts +20 -0
- package/dist/types/inputs/ChoiceGroups.d.ts +11 -2
- package/dist/types/inputs/FileInputs.d.ts +6 -0
- package/dist/types/inputs/SignatureInput.d.ts +22 -0
- package/dist/types/inputs/emoji/EmojiPicker.d.ts +42 -0
- package/dist/types/inputs/emoji/emoji-data.d.ts +2 -0
- package/dist/types/inputs/emoji/emoji-index.d.ts +109 -0
- package/dist/types/inputs/emoji/emoji-messages.d.ts +30 -0
- package/dist/types/inputs/index.d.ts +10 -0
- package/dist/types/inputs/markdown/actions.d.ts +2 -0
- package/dist/types/inputs/markdown/code-zone.d.ts +7 -0
- package/dist/types/inputs/signature-messages.d.ts +11 -0
- package/dist/types/inputs/signature.d.ts +72 -0
- package/dist/types/internal/render-error.d.ts +13 -0
- package/dist/types/intl/messages.d.ts +51 -0
- package/dist/types/layout/AppWorkspace.d.ts +17 -0
- package/dist/types/layout/SidebarItemPreview.d.ts +2 -0
- package/dist/types/layout/index.d.ts +1 -1
- package/dist/types/surfaces/ResourceCard.d.ts +31 -0
- package/dist/types/surfaces/index.d.ts +2 -0
- package/package.json +6 -5
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A signature as the field reports it. `kind` records how it was given, so the
|
|
3
|
+
* application can keep that fact next to the image. `svg` is a standalone SVG
|
|
4
|
+
* document whose ink uses `currentColor`, so it renders black as an image. A
|
|
5
|
+
* value that arrives from a browser is untrusted input: read it with
|
|
6
|
+
* `parseSignature` before storing or embedding it.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** One sampled pointer position in drawing units, with a pressure from 0 to 1. */
|
|
10
|
+
|
|
11
|
+
/** The drawing space as an SVG viewBox and one closed outline path per stroke. */
|
|
12
|
+
|
|
13
|
+
const SVG_NS = "http://www.w3.org/2000/svg";
|
|
14
|
+
/** Typed names are laid out at this size; the SVG scales as a whole. */
|
|
15
|
+
const TYPED_FONT_SIZE = 48;
|
|
16
|
+
const TYPED_PADDING = 12;
|
|
17
|
+
const round = value => Math.round(value * 10) / 10;
|
|
18
|
+
const point = (x, y) => `${round(x)} ${round(y)}`;
|
|
19
|
+
|
|
20
|
+
/** Control characters, lone surrogates, and the two noncharacters XML forbids, which a strict XML parser rejects. */
|
|
21
|
+
const NOT_XML_TEXT = /[\p{Cc}\p{Cs}\uFFFE\uFFFF]/gu;
|
|
22
|
+
|
|
23
|
+
/** Escapes text for XML content or attributes and drops characters XML cannot hold, so the document stays well-formed. */
|
|
24
|
+
export const escapeXml = text => text.replace(NOT_XML_TEXT, "").replace(/[&<>"']/g, char => ({
|
|
25
|
+
"&": "&",
|
|
26
|
+
"<": "<",
|
|
27
|
+
">": ">",
|
|
28
|
+
'"': """,
|
|
29
|
+
"'": "'"
|
|
30
|
+
})[char] ?? char);
|
|
31
|
+
const unescapeXml = text => text.replace(/&(amp|lt|gt|quot|apos);/g, (_, name) => ({
|
|
32
|
+
amp: "&",
|
|
33
|
+
lt: "<",
|
|
34
|
+
gt: ">",
|
|
35
|
+
quot: '"',
|
|
36
|
+
apos: "'"
|
|
37
|
+
})[name] ?? "");
|
|
38
|
+
|
|
39
|
+
/** The name as a typed signature stores it: without characters an SVG cannot hold and without surrounding space. */
|
|
40
|
+
export const signatureName = name => name.replace(NOT_XML_TEXT, "").trim();
|
|
41
|
+
|
|
42
|
+
/** Stroke width in drawing units at a pressure: light pressure still leaves a readable line. */
|
|
43
|
+
export const strokeWidth = (size, pressure) => size * (0.35 + 0.65 * Math.min(1, Math.max(0, pressure)));
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Turns sampled points into one filled outline: both edges follow the points at
|
|
47
|
+
* half the local stroke width and are smoothed through their midpoints, and
|
|
48
|
+
* round caps close the ends. A filled outline, unlike a stroked path, can change
|
|
49
|
+
* width along the line, so pen pressure and drawing speed show in the ink.
|
|
50
|
+
*/
|
|
51
|
+
export const strokeOutline = (points, size) => {
|
|
52
|
+
const first = points[0];
|
|
53
|
+
if (!first) return "";
|
|
54
|
+
if (points.length === 1) {
|
|
55
|
+
const radius = round(strokeWidth(size, first.pressure) / 2);
|
|
56
|
+
const arc = x => `A${radius} ${radius} 0 1 0 ${point(x, first.y)}`;
|
|
57
|
+
return `M${point(first.x - radius, first.y)}${arc(first.x + radius)}${arc(first.x - radius)}Z`;
|
|
58
|
+
}
|
|
59
|
+
const left = [];
|
|
60
|
+
const right = [];
|
|
61
|
+
points.forEach((current, index) => {
|
|
62
|
+
const before = points[index - 1] ?? current;
|
|
63
|
+
const after = points[index + 1] ?? current;
|
|
64
|
+
const dx = after.x - before.x;
|
|
65
|
+
const dy = after.y - before.y;
|
|
66
|
+
const length = Math.hypot(dx, dy) || 1;
|
|
67
|
+
const radius = strokeWidth(size, current.pressure) / 2;
|
|
68
|
+
const nx = -dy / length * radius;
|
|
69
|
+
const ny = dx / length * radius;
|
|
70
|
+
left.push({
|
|
71
|
+
x: current.x + nx,
|
|
72
|
+
y: current.y + ny,
|
|
73
|
+
radius
|
|
74
|
+
});
|
|
75
|
+
right.push({
|
|
76
|
+
x: current.x - nx,
|
|
77
|
+
y: current.y - ny,
|
|
78
|
+
radius
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
right.reverse();
|
|
82
|
+
// Quadratic segments through the midpoints of neighbouring edge points keep the outline smooth without overshoot.
|
|
83
|
+
const edge = side => side.map((current, index) => {
|
|
84
|
+
if (index === 0) return "";
|
|
85
|
+
const next = side[index + 1];
|
|
86
|
+
if (!next) return `L${point(current.x, current.y)}`;
|
|
87
|
+
return `Q${point(current.x, current.y)} ${point((current.x + next.x) / 2, (current.y + next.y) / 2)}`;
|
|
88
|
+
}).join("");
|
|
89
|
+
const cap = (from, to) => `A${round(from.radius)} ${round(from.radius)} 0 0 0 ${point(to.x, to.y)}`;
|
|
90
|
+
const start = left[0] ?? right[0];
|
|
91
|
+
const end = left[left.length - 1];
|
|
92
|
+
const back = right[0];
|
|
93
|
+
const backEnd = right[right.length - 1];
|
|
94
|
+
if (!start || !end || !back || !backEnd) return "";
|
|
95
|
+
return `M${point(start.x, start.y)}${edge(left)}${cap(end, back)}${edge(right)}${cap(backEnd, start)}Z`;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/** Serializes a drawing as a standalone SVG document; an empty drawing has no value. */
|
|
99
|
+
export const drawingToSvg = drawing => {
|
|
100
|
+
if (drawing.strokes.length === 0) return null;
|
|
101
|
+
const width = round(drawing.width);
|
|
102
|
+
const height = round(drawing.height);
|
|
103
|
+
const paths = drawing.strokes.map(stroke => `<path d="${escapeXml(stroke)}"/>`).join("");
|
|
104
|
+
return `<svg xmlns="${SVG_NS}" viewBox="${round(drawing.x)} ${round(drawing.y)} ${width} ${height}" width="${width}" height="${height}"><g fill="currentColor">${paths}</g></svg>`;
|
|
105
|
+
};
|
|
106
|
+
const NUMBER = String.raw`-?\d+(?:\.\d+)?`;
|
|
107
|
+
const SVG_NS_PATTERN = String.raw`http://www\.w3\.org/2000/svg`;
|
|
108
|
+
// Exactly the markup drawingToSvg and typedToSvg write. Path data may only hold the commands and numbers strokeOutline
|
|
109
|
+
// emits, and no attribute or text may hold markup, so a match cannot carry scripts, links, or other elements.
|
|
110
|
+
const DRAWN_SVG = new RegExp(`^<svg xmlns="${SVG_NS_PATTERN}" viewBox="(${NUMBER}) (${NUMBER}) (${NUMBER}) (${NUMBER})" width="${NUMBER}" height="${NUMBER}">` + `<g fill="currentColor">((?:<path d="[MLQAZ\\d .-]+"/>)+)</g></svg>$`);
|
|
111
|
+
const TYPED_SVG = new RegExp(`^<svg xmlns="${SVG_NS_PATTERN}" viewBox="0 0 (\\d+) \\d+" width="\\d+" height="\\d+">` + `<text x="\\d+" y="\\d+" font-family="([^"<>]*)" font-size="\\d+" fill="currentColor">[^<>]*</text></svg>$`);
|
|
112
|
+
|
|
113
|
+
/** Reads the drawing space and strokes back from an SVG drawingToSvg wrote, so a stored signature can be extended or undone. */
|
|
114
|
+
export const svgToDrawing = svg => {
|
|
115
|
+
const match = DRAWN_SVG.exec(svg);
|
|
116
|
+
if (!match) return null;
|
|
117
|
+
const [x = 0, y = 0, width = 0, height = 0] = match.slice(1, 5).map(Number);
|
|
118
|
+
if (width <= 0 || height <= 0) return null;
|
|
119
|
+
const strokes = [...(match[5] ?? "").matchAll(/<path d="([^"]+)"\/>/g)].flatMap(path => path[1] ? [path[1]] : []);
|
|
120
|
+
return {
|
|
121
|
+
x,
|
|
122
|
+
y,
|
|
123
|
+
width,
|
|
124
|
+
height,
|
|
125
|
+
strokes
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Lays out a typed name as an SVG document in the given font family. `measure`
|
|
131
|
+
* returns the text width at `TYPED_FONT_SIZE`; without one, the width is estimated.
|
|
132
|
+
*/
|
|
133
|
+
export const typedToSvg = (name, fontFamily, measure) => {
|
|
134
|
+
const font = `${TYPED_FONT_SIZE}px ${fontFamily}`;
|
|
135
|
+
const textWidth = measure?.(name, font) ?? name.length * TYPED_FONT_SIZE * 0.5;
|
|
136
|
+
const width = Math.ceil(textWidth + TYPED_PADDING * 2);
|
|
137
|
+
const height = Math.ceil(TYPED_FONT_SIZE * 1.6);
|
|
138
|
+
const baseline = Math.round(TYPED_FONT_SIZE * 1.15);
|
|
139
|
+
return `<svg xmlns="${SVG_NS}" viewBox="0 0 ${width} ${height}" width="${width}" height="${height}">` + `<text x="${TYPED_PADDING}" y="${baseline}" font-family="${escapeXml(fontFamily)}" font-size="${TYPED_FONT_SIZE}" fill="currentColor">${escapeXml(name)}</text></svg>`;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Reads a submitted signature on the server: the JSON string of the field's
|
|
144
|
+
* hidden input, or a value the browser sent as JSON. It accepts only markup
|
|
145
|
+
* the field writes and returns it rebuilt, so the result is safe to store and
|
|
146
|
+
* to embed. An empty or invalid value returns `null`.
|
|
147
|
+
*/
|
|
148
|
+
export const parseSignature = input => {
|
|
149
|
+
let value = input;
|
|
150
|
+
if (typeof input === "string") {
|
|
151
|
+
if (!input) return null;
|
|
152
|
+
try {
|
|
153
|
+
value = JSON.parse(input);
|
|
154
|
+
} catch {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (typeof value !== "object" || value === null || !("kind" in value) || !("svg" in value) || typeof value.svg !== "string") return null;
|
|
159
|
+
if (value.kind === "drawn") {
|
|
160
|
+
const drawing = svgToDrawing(value.svg);
|
|
161
|
+
const svg = drawing && drawingToSvg(drawing);
|
|
162
|
+
return svg ? {
|
|
163
|
+
kind: "drawn",
|
|
164
|
+
svg
|
|
165
|
+
} : null;
|
|
166
|
+
}
|
|
167
|
+
if (value.kind !== "typed" || !("name" in value) || typeof value.name !== "string") return null;
|
|
168
|
+
const name = signatureName(value.name);
|
|
169
|
+
const match = TYPED_SVG.exec(value.svg);
|
|
170
|
+
if (!name || !match) return null;
|
|
171
|
+
const textWidth = Number(match[1]) - TYPED_PADDING * 2;
|
|
172
|
+
return {
|
|
173
|
+
kind: "typed",
|
|
174
|
+
name,
|
|
175
|
+
svg: typedToSvg(name, unescapeXml(match[2] ?? ""), () => Math.max(0, textWidth))
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* Rasterizes a signature into a PNG data URL in the browser, for a document or
|
|
180
|
+
* service that needs a bitmap. A typed signature renders with the fonts
|
|
181
|
+
* installed on this device: an SVG image cannot use the page's web fonts.
|
|
182
|
+
*/
|
|
183
|
+
export const signatureToPng = async (value, options = {}) => {
|
|
184
|
+
const viewBox = /viewBox="-?[\d.]+ -?[\d.]+ ([\d.]+) ([\d.]+)"/.exec(value.svg);
|
|
185
|
+
if (!viewBox) throw new Error("The signature SVG has no drawing size.");
|
|
186
|
+
const scale = options.scale ?? 2;
|
|
187
|
+
const width = Math.max(1, Math.round(Number(viewBox[1]) * scale));
|
|
188
|
+
const height = Math.max(1, Math.round(Number(viewBox[2]) * scale));
|
|
189
|
+
const svg = value.svg.replace("<svg ", `<svg color="${escapeXml(options.color ?? "#000000")}" `);
|
|
190
|
+
const image = new Image();
|
|
191
|
+
image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
|
|
192
|
+
await image.decode();
|
|
193
|
+
const canvas = document.createElement("canvas");
|
|
194
|
+
canvas.width = width;
|
|
195
|
+
canvas.height = height;
|
|
196
|
+
const context = canvas.getContext("2d");
|
|
197
|
+
if (!context) throw new Error("This browser cannot draw a signature image.");
|
|
198
|
+
if (options.background) {
|
|
199
|
+
context.fillStyle = options.background;
|
|
200
|
+
context.fillRect(0, 0, width, height);
|
|
201
|
+
}
|
|
202
|
+
context.drawImage(image, 0, 0, width, height);
|
|
203
|
+
const url = canvas.toDataURL("image/png");
|
|
204
|
+
// A canvas beyond the browser's size limit returns "data:," instead of failing.
|
|
205
|
+
if (!url.startsWith("data:image/png")) throw new Error("The signature is too large for a PNG at this scale.");
|
|
206
|
+
return url;
|
|
207
|
+
};
|
|
208
|
+
//# sourceMappingURL=signature.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sources":["inputs/signature.ts"],"sourcesContent":["/**\n * A signature as the field reports it. `kind` records how it was given, so the\n * application can keep that fact next to the image. `svg` is a standalone SVG\n * document whose ink uses `currentColor`, so it renders black as an image. A\n * value that arrives from a browser is untrusted input: read it with\n * `parseSignature` before storing or embedding it.\n */\nexport type SignatureValue = { kind: \"drawn\"; svg: string } | { kind: \"typed\"; name: string; svg: string };\n\n/** One sampled pointer position in drawing units, with a pressure from 0 to 1. */\nexport type SignaturePoint = { x: number; y: number; pressure: number };\n\n/** The drawing space as an SVG viewBox and one closed outline path per stroke. */\nexport type SignatureDrawing = { x: number; y: number; width: number; height: number; strokes: readonly string[] };\n\nconst SVG_NS = \"http://www.w3.org/2000/svg\";\n/** Typed names are laid out at this size; the SVG scales as a whole. */\nconst TYPED_FONT_SIZE = 48;\nconst TYPED_PADDING = 12;\n\nconst round = (value: number) => Math.round(value * 10) / 10;\nconst point = (x: number, y: number) => `${round(x)} ${round(y)}`;\n\n/** Control characters, lone surrogates, and the two noncharacters XML forbids, which a strict XML parser rejects. */\nconst NOT_XML_TEXT = /[\\p{Cc}\\p{Cs}\\uFFFE\\uFFFF]/gu;\n\n/** Escapes text for XML content or attributes and drops characters XML cannot hold, so the document stays well-formed. */\nexport const escapeXml = (text: string): string =>\n text\n .replace(NOT_XML_TEXT, \"\")\n .replace(/[&<>\"']/g, (char) => ({ \"&\": \"&\", \"<\": \"<\", \">\": \">\", '\"': \""\", \"'\": \"'\" })[char] ?? char);\n\nconst unescapeXml = (text: string): string =>\n text.replace(/&(amp|lt|gt|quot|apos);/g, (_, name: string) => ({ amp: \"&\", lt: \"<\", gt: \">\", quot: '\"', apos: \"'\" })[name] ?? \"\");\n\n/** The name as a typed signature stores it: without characters an SVG cannot hold and without surrounding space. */\nexport const signatureName = (name: string): string => name.replace(NOT_XML_TEXT, \"\").trim();\n\n/** Stroke width in drawing units at a pressure: light pressure still leaves a readable line. */\nexport const strokeWidth = (size: number, pressure: number): number => size * (0.35 + 0.65 * Math.min(1, Math.max(0, pressure)));\n\n/**\n * Turns sampled points into one filled outline: both edges follow the points at\n * half the local stroke width and are smoothed through their midpoints, and\n * round caps close the ends. A filled outline, unlike a stroked path, can change\n * width along the line, so pen pressure and drawing speed show in the ink.\n */\nexport const strokeOutline = (points: readonly SignaturePoint[], size: number): string => {\n const first = points[0];\n if (!first) return \"\";\n if (points.length === 1) {\n const radius = round(strokeWidth(size, first.pressure) / 2);\n const arc = (x: number) => `A${radius} ${radius} 0 1 0 ${point(x, first.y)}`;\n return `M${point(first.x - radius, first.y)}${arc(first.x + radius)}${arc(first.x - radius)}Z`;\n }\n type Edge = { x: number; y: number; radius: number };\n const left: Edge[] = [];\n const right: Edge[] = [];\n points.forEach((current, index) => {\n const before = points[index - 1] ?? current;\n const after = points[index + 1] ?? current;\n const dx = after.x - before.x;\n const dy = after.y - before.y;\n const length = Math.hypot(dx, dy) || 1;\n const radius = strokeWidth(size, current.pressure) / 2;\n const nx = (-dy / length) * radius;\n const ny = (dx / length) * radius;\n left.push({ x: current.x + nx, y: current.y + ny, radius });\n right.push({ x: current.x - nx, y: current.y - ny, radius });\n });\n right.reverse();\n // Quadratic segments through the midpoints of neighbouring edge points keep the outline smooth without overshoot.\n const edge = (side: readonly Edge[]) =>\n side\n .map((current, index) => {\n if (index === 0) return \"\";\n const next = side[index + 1];\n if (!next) return `L${point(current.x, current.y)}`;\n return `Q${point(current.x, current.y)} ${point((current.x + next.x) / 2, (current.y + next.y) / 2)}`;\n })\n .join(\"\");\n const cap = (from: Edge, to: Edge) => `A${round(from.radius)} ${round(from.radius)} 0 0 0 ${point(to.x, to.y)}`;\n const start = left[0] ?? right[0];\n const end = left[left.length - 1];\n const back = right[0];\n const backEnd = right[right.length - 1];\n if (!start || !end || !back || !backEnd) return \"\";\n return `M${point(start.x, start.y)}${edge(left)}${cap(end, back)}${edge(right)}${cap(backEnd, start)}Z`;\n};\n\n/** Serializes a drawing as a standalone SVG document; an empty drawing has no value. */\nexport const drawingToSvg = (drawing: SignatureDrawing): string | null => {\n if (drawing.strokes.length === 0) return null;\n const width = round(drawing.width);\n const height = round(drawing.height);\n const paths = drawing.strokes.map((stroke) => `<path d=\"${escapeXml(stroke)}\"/>`).join(\"\");\n return `<svg xmlns=\"${SVG_NS}\" viewBox=\"${round(drawing.x)} ${round(drawing.y)} ${width} ${height}\" width=\"${width}\" height=\"${height}\"><g fill=\"currentColor\">${paths}</g></svg>`;\n};\n\nconst NUMBER = String.raw`-?\\d+(?:\\.\\d+)?`;\nconst SVG_NS_PATTERN = String.raw`http://www\\.w3\\.org/2000/svg`;\n// Exactly the markup drawingToSvg and typedToSvg write. Path data may only hold the commands and numbers strokeOutline\n// emits, and no attribute or text may hold markup, so a match cannot carry scripts, links, or other elements.\nconst DRAWN_SVG = new RegExp(\n `^<svg xmlns=\"${SVG_NS_PATTERN}\" viewBox=\"(${NUMBER}) (${NUMBER}) (${NUMBER}) (${NUMBER})\" width=\"${NUMBER}\" height=\"${NUMBER}\">` +\n `<g fill=\"currentColor\">((?:<path d=\"[MLQAZ\\\\d .-]+\"/>)+)</g></svg>$`,\n);\nconst TYPED_SVG = new RegExp(\n `^<svg xmlns=\"${SVG_NS_PATTERN}\" viewBox=\"0 0 (\\\\d+) \\\\d+\" width=\"\\\\d+\" height=\"\\\\d+\">` +\n `<text x=\"\\\\d+\" y=\"\\\\d+\" font-family=\"([^\"<>]*)\" font-size=\"\\\\d+\" fill=\"currentColor\">[^<>]*</text></svg>$`,\n);\n\n/** Reads the drawing space and strokes back from an SVG drawingToSvg wrote, so a stored signature can be extended or undone. */\nexport const svgToDrawing = (svg: string): SignatureDrawing | null => {\n const match = DRAWN_SVG.exec(svg);\n if (!match) return null;\n const [x = 0, y = 0, width = 0, height = 0] = match.slice(1, 5).map(Number);\n if (width <= 0 || height <= 0) return null;\n const strokes = [...(match[5] ?? \"\").matchAll(/<path d=\"([^\"]+)\"\\/>/g)].flatMap((path) => (path[1] ? [path[1]] : []));\n return { x, y, width, height, strokes };\n};\n\n/**\n * Lays out a typed name as an SVG document in the given font family. `measure`\n * returns the text width at `TYPED_FONT_SIZE`; without one, the width is estimated.\n */\nexport const typedToSvg = (name: string, fontFamily: string, measure?: (text: string, font: string) => number | undefined): string => {\n const font = `${TYPED_FONT_SIZE}px ${fontFamily}`;\n const textWidth = measure?.(name, font) ?? name.length * TYPED_FONT_SIZE * 0.5;\n const width = Math.ceil(textWidth + TYPED_PADDING * 2);\n const height = Math.ceil(TYPED_FONT_SIZE * 1.6);\n const baseline = Math.round(TYPED_FONT_SIZE * 1.15);\n return (\n `<svg xmlns=\"${SVG_NS}\" viewBox=\"0 0 ${width} ${height}\" width=\"${width}\" height=\"${height}\">` +\n `<text x=\"${TYPED_PADDING}\" y=\"${baseline}\" font-family=\"${escapeXml(fontFamily)}\" font-size=\"${TYPED_FONT_SIZE}\" fill=\"currentColor\">${escapeXml(name)}</text></svg>`\n );\n};\n\n/**\n * Reads a submitted signature on the server: the JSON string of the field's\n * hidden input, or a value the browser sent as JSON. It accepts only markup\n * the field writes and returns it rebuilt, so the result is safe to store and\n * to embed. An empty or invalid value returns `null`.\n */\nexport const parseSignature = (input: unknown): SignatureValue | null => {\n let value: unknown = input;\n if (typeof input === \"string\") {\n if (!input) return null;\n try {\n value = JSON.parse(input);\n } catch {\n return null;\n }\n }\n if (typeof value !== \"object\" || value === null || !(\"kind\" in value) || !(\"svg\" in value) || typeof value.svg !== \"string\") return null;\n if (value.kind === \"drawn\") {\n const drawing = svgToDrawing(value.svg);\n const svg = drawing && drawingToSvg(drawing);\n return svg ? { kind: \"drawn\", svg } : null;\n }\n if (value.kind !== \"typed\" || !(\"name\" in value) || typeof value.name !== \"string\") return null;\n const name = signatureName(value.name);\n const match = TYPED_SVG.exec(value.svg);\n if (!name || !match) return null;\n const textWidth = Number(match[1]) - TYPED_PADDING * 2;\n return { kind: \"typed\", name, svg: typedToSvg(name, unescapeXml(match[2] ?? \"\"), () => Math.max(0, textWidth)) };\n};\n\nexport type SignaturePngOptions = {\n /** Device pixels per SVG unit. Defaults to `2`. */\n scale?: number;\n /** Ink colour. Defaults to `\"#000000\"`. */\n color?: string;\n /** Background fill. Defaults to transparent. */\n background?: string;\n};\n\n/**\n * Rasterizes a signature into a PNG data URL in the browser, for a document or\n * service that needs a bitmap. A typed signature renders with the fonts\n * installed on this device: an SVG image cannot use the page's web fonts.\n */\nexport const signatureToPng = async (value: SignatureValue, options: SignaturePngOptions = {}): Promise<string> => {\n const viewBox = /viewBox=\"-?[\\d.]+ -?[\\d.]+ ([\\d.]+) ([\\d.]+)\"/.exec(value.svg);\n if (!viewBox) throw new Error(\"The signature SVG has no drawing size.\");\n const scale = options.scale ?? 2;\n const width = Math.max(1, Math.round(Number(viewBox[1]) * scale));\n const height = Math.max(1, Math.round(Number(viewBox[2]) * scale));\n const svg = value.svg.replace(\"<svg \", `<svg color=\"${escapeXml(options.color ?? \"#000000\")}\" `);\n const image = new Image();\n image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;\n await image.decode();\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n const context = canvas.getContext(\"2d\");\n if (!context) throw new Error(\"This browser cannot draw a signature image.\");\n if (options.background) {\n context.fillStyle = options.background;\n context.fillRect(0, 0, width, height);\n }\n context.drawImage(image, 0, 0, width, height);\n const url = canvas.toDataURL(\"image/png\");\n // A canvas beyond the browser's size limit returns \"data:,\" instead of failing.\n if (!url.startsWith(\"data:image/png\")) throw new Error(\"The signature is too large for a PNG at this scale.\");\n return url;\n};\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { memo as _$memo } from "solid-js/web";
|
|
2
|
+
import { createComponent as _$createComponent } from "solid-js/web";
|
|
3
|
+
import { ErrorBoundary, untrack } from "solid-js";
|
|
4
|
+
import { Button } from "../actions/Button";
|
|
5
|
+
import { useUiMessages } from "../intl/messages";
|
|
6
|
+
import Placeholder from "../surfaces/Placeholder";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Guards a Solid root that @k2b/ui creates itself, such as a dialog or a floating window. These roots are mostly
|
|
10
|
+
* opened from a click handler, outside any owner and any island boundary. Without a boundary of their own, content
|
|
11
|
+
* that throws on an update throws into the code that wrote the signal and leaves other parts of the page stale.
|
|
12
|
+
* The content is created untracked, as `render()` and components create it, so a signal it reads directly does not
|
|
13
|
+
* rebuild it. `onClose` adds a Close button for a root whose content draws its own close control.
|
|
14
|
+
*/
|
|
15
|
+
export function RenderErrorBoundary(props) {
|
|
16
|
+
return _$createComponent(ErrorBoundary, {
|
|
17
|
+
fallback: error => {
|
|
18
|
+
globalThis.reportError(error);
|
|
19
|
+
props.onError?.();
|
|
20
|
+
return _$createComponent(RenderErrorNotice, {
|
|
21
|
+
get onClose() {
|
|
22
|
+
return props.onClose;
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
},
|
|
26
|
+
get children() {
|
|
27
|
+
return untrack(props.content);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function RenderErrorNotice(props) {
|
|
32
|
+
const messages = useUiMessages();
|
|
33
|
+
return _$createComponent(Placeholder, {
|
|
34
|
+
state: "error",
|
|
35
|
+
get title() {
|
|
36
|
+
return messages().couldNotDisplayContent;
|
|
37
|
+
},
|
|
38
|
+
get action() {
|
|
39
|
+
return _$memo(() => !!props.onClose)() ? _$createComponent(Button, {
|
|
40
|
+
variant: "secondary",
|
|
41
|
+
onClick: () => props.onClose?.(),
|
|
42
|
+
get children() {
|
|
43
|
+
return messages().close;
|
|
44
|
+
}
|
|
45
|
+
}) : undefined;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=render-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sources":["internal/render-error.tsx"],"sourcesContent":["import { ErrorBoundary, type JSX, untrack } from \"solid-js\";\nimport { Button } from \"../actions/Button\";\nimport { useUiMessages } from \"../intl/messages\";\nimport Placeholder from \"../surfaces/Placeholder\";\n\n/**\n * Guards a Solid root that @k2b/ui creates itself, such as a dialog or a floating window. These roots are mostly\n * opened from a click handler, outside any owner and any island boundary. Without a boundary of their own, content\n * that throws on an update throws into the code that wrote the signal and leaves other parts of the page stale.\n * The content is created untracked, as `render()` and components create it, so a signal it reads directly does not\n * rebuild it. `onClose` adds a Close button for a root whose content draws its own close control.\n */\nexport function RenderErrorBoundary(props: { content: () => JSX.Element; onClose?: () => void; onError?: () => void }): JSX.Element {\n return (\n <ErrorBoundary\n fallback={(error: unknown) => {\n globalThis.reportError(error);\n props.onError?.();\n return <RenderErrorNotice onClose={props.onClose} />;\n }}\n >\n {untrack(props.content)}\n </ErrorBoundary>\n );\n}\n\nfunction RenderErrorNotice(props: { onClose?: () => void }): JSX.Element {\n const messages = useUiMessages();\n return (\n <Placeholder\n state=\"error\"\n title={messages().couldNotDisplayContent}\n action={\n props.onClose ? (\n <Button variant=\"secondary\" onClick={() => props.onClose?.()}>\n {messages().close}\n </Button>\n ) : undefined\n }\n />\n );\n}\n"],"mappings":";;AAAA,SAAS,aAAa,EAAY,OAAO,QAAQ,UAAU;AAC3D,SAAS,MAAM,QAAQ,mBAAmB;AAC1C,SAAS,aAAa,QAAQ,kBAAkB;AAChD,OAAO,WAAW,MAAM,yBAAyB;;AAEjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAAS,mBAAmB,CAAC,KAAiF,EAAe;EAClI,yBACG,aAAa;IACZ,QAAQ,EAAG,KAAc,IAAK;MAC5B,UAAU,CAAC,WAAW,CAAC,KAAK,CAAC;MAC7B,KAAK,CAAC,OAAO,GAAG,CAAC;MACjB,yBAAQ,iBAAiB;QAAA,IAAC,OAAO;UAAA,OAAE,KAAK,CAAC,OAAO;QAAA;MAAA;IAClD,CAAC;IAAA;MAAA,OAEA,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC;IAAA;EAAA;AAG7B;AAEA,SAAS,iBAAiB,CAAC,KAA+B,EAAe;EACvE,MAAM,QAAQ,GAAG,aAAa,CAAC,CAAC;EAChC,yBACG,WAAW;IACV,KAAK;IAAA,IACL,KAAK;MAAA,OAAE,QAAQ,CAAC,CAAC,CAAC,sBAAsB;IAAA;IAAA,IACxC,MAAM;MAAA,OACJ,oBAAK,CAAC,OAAO,wBACV,MAAM;QAAC,OAAO;QAAa,OAAO,EAAE,MAAM,KAAK,CAAC,OAAO,GAAG,CAAC;QAAA;UAAA,OACzD,QAAQ,CAAC,CAAC,CAAC,KAAK;QAAA;MAAA,KAEjB,SAAS;IAAA;EAAA;AAIrB","ignoreList":[]}
|
|
@@ -54,6 +54,7 @@ const uiMessages = i18n.define({
|
|
|
54
54
|
copy: "Copy",
|
|
55
55
|
closeLightbox: "Close lightbox",
|
|
56
56
|
couldNotLoadData: "Could not load this data",
|
|
57
|
+
couldNotDisplayContent: "This content could not be displayed.",
|
|
57
58
|
datePicker: "Date picker",
|
|
58
59
|
dataTable: "Data table",
|
|
59
60
|
datePresets: "Date presets",
|
|
@@ -200,6 +201,28 @@ const uiMessages = i18n.define({
|
|
|
200
201
|
start,
|
|
201
202
|
end
|
|
202
203
|
}) => `${title}, ${start} to ${end}`,
|
|
204
|
+
timeline: "Timeline",
|
|
205
|
+
timelineHint: "Arrow keys move between items, Page Up and Page Down between days, Home and End to the first and last item of a day, T to now.",
|
|
206
|
+
timelineTimeRange: ({
|
|
207
|
+
start,
|
|
208
|
+
end
|
|
209
|
+
}) => `${start} to ${end}`,
|
|
210
|
+
timelineDone: "done",
|
|
211
|
+
timelineOpen: "open",
|
|
212
|
+
timelineNight: ({
|
|
213
|
+
start,
|
|
214
|
+
end
|
|
215
|
+
}) => `Night · ${start}–${end}`,
|
|
216
|
+
timelineNothingPlanned: "Nothing planned",
|
|
217
|
+
timelineFree: ({
|
|
218
|
+
duration
|
|
219
|
+
}) => `${duration} free`,
|
|
220
|
+
timelineMore: ({
|
|
221
|
+
count
|
|
222
|
+
}) => `${count} more`,
|
|
223
|
+
timelineNow: ({
|
|
224
|
+
time
|
|
225
|
+
}) => `Now, ${time}`,
|
|
203
226
|
today: "Today",
|
|
204
227
|
day: "Day",
|
|
205
228
|
week: "Week",
|
|
@@ -263,6 +286,10 @@ const uiMessages = i18n.define({
|
|
|
263
286
|
time
|
|
264
287
|
}) => `Last reply ${time}`,
|
|
265
288
|
messageStop: "Stop",
|
|
289
|
+
resourceUntitled: "Untitled",
|
|
290
|
+
resourceNoAccess: "No access",
|
|
291
|
+
resourceDeleted: "Item deleted",
|
|
292
|
+
resourceUnavailable: "Not available right now",
|
|
266
293
|
conversationMessages: ({
|
|
267
294
|
label
|
|
268
295
|
}) => `${label} messages`,
|
|
@@ -410,6 +437,13 @@ const uiMessages = i18n.define({
|
|
|
410
437
|
label
|
|
411
438
|
}) => `Move ${label} up`,
|
|
412
439
|
rowAction: "Row action",
|
|
440
|
+
sidebarUnread: ({
|
|
441
|
+
count
|
|
442
|
+
}) => `${count} unread`,
|
|
443
|
+
sidebarUnreadMany: "more than 99 unread",
|
|
444
|
+
sidebarNewActivity: "new activity",
|
|
445
|
+
sidebarMentioned: "mentions you",
|
|
446
|
+
sidebarMuted: "muted",
|
|
413
447
|
addToNamed: ({
|
|
414
448
|
name
|
|
415
449
|
}) => `Add to ${name}`,
|
|
@@ -446,6 +480,8 @@ const uiMessages = i18n.define({
|
|
|
446
480
|
status
|
|
447
481
|
}) => `PDF preview failed with HTTP ${status}`,
|
|
448
482
|
pdfPreviewTabBlocked: "The browser blocked the new tab. Allow pop-ups and try again.",
|
|
483
|
+
pdfPreviewNoViewer: "This PDF cannot be shown here",
|
|
484
|
+
pdfPreviewNoViewerDescription: "Your browser cannot show PDFs inside a page. Open the document to view it.",
|
|
449
485
|
pdfPreviewRendering: "Rendering PDF preview…",
|
|
450
486
|
pickIcon: "Pick an icon...",
|
|
451
487
|
postMessage: "Post message",
|
|
@@ -457,6 +493,21 @@ const uiMessages = i18n.define({
|
|
|
457
493
|
sending: "Sending",
|
|
458
494
|
steering: "Steering",
|
|
459
495
|
writeMessage: "Write a message or type / ...",
|
|
496
|
+
writeConversationMessage: "Write a message",
|
|
497
|
+
composerFormatting: "Formatting",
|
|
498
|
+
insertEmoji: "Insert emoji",
|
|
499
|
+
emojiSuggestions: "Emoji suggestions",
|
|
500
|
+
dictate: "Dictate",
|
|
501
|
+
dictateOrHold: "Dictate. Hold to record a voice message.",
|
|
502
|
+
stopDictation: "Stop dictation",
|
|
503
|
+
microphoneOptions: "Microphone options",
|
|
504
|
+
recordVoiceMessage: "Record voice message",
|
|
505
|
+
dictationListening: "Dictating …",
|
|
506
|
+
dictationRefining: "Refining the text …",
|
|
507
|
+
dictationRefined: "Text refined",
|
|
508
|
+
dictationUnrefined: "Text not refined",
|
|
509
|
+
dictationInterrupted: "Dictation interrupted",
|
|
510
|
+
restoreOriginal: "Restore original",
|
|
460
511
|
openInNewTab: ({
|
|
461
512
|
name
|
|
462
513
|
}) => `Open ${name} in a new tab`,
|
|
@@ -655,6 +706,7 @@ const uiMessages = i18n.define({
|
|
|
655
706
|
copy: "Kopieren",
|
|
656
707
|
closeLightbox: "Bildansicht schließen",
|
|
657
708
|
couldNotLoadData: "Diese Daten konnten nicht geladen werden",
|
|
709
|
+
couldNotDisplayContent: "Dieser Inhalt konnte nicht angezeigt werden.",
|
|
658
710
|
datePicker: "Datumsauswahl",
|
|
659
711
|
dataTable: "Datentabelle",
|
|
660
712
|
datePresets: "Datumsvorgaben",
|
|
@@ -801,6 +853,28 @@ const uiMessages = i18n.define({
|
|
|
801
853
|
start,
|
|
802
854
|
end
|
|
803
855
|
}) => `${title}, ${start} bis ${end}`,
|
|
856
|
+
timeline: "Zeitleiste",
|
|
857
|
+
timelineHint: "Pfeiltasten wechseln den Eintrag, Bild auf und Bild ab den Tag, Pos1 und Ende springen zum ersten und letzten Eintrag des Tages, T zu jetzt.",
|
|
858
|
+
timelineTimeRange: ({
|
|
859
|
+
start,
|
|
860
|
+
end
|
|
861
|
+
}) => `${start} bis ${end}`,
|
|
862
|
+
timelineDone: "erledigt",
|
|
863
|
+
timelineOpen: "offen",
|
|
864
|
+
timelineNight: ({
|
|
865
|
+
start,
|
|
866
|
+
end
|
|
867
|
+
}) => `Nacht · ${start}–${end}`,
|
|
868
|
+
timelineNothingPlanned: "Nichts geplant",
|
|
869
|
+
timelineFree: ({
|
|
870
|
+
duration
|
|
871
|
+
}) => `${duration} frei`,
|
|
872
|
+
timelineMore: ({
|
|
873
|
+
count
|
|
874
|
+
}) => `${count} weitere`,
|
|
875
|
+
timelineNow: ({
|
|
876
|
+
time
|
|
877
|
+
}) => `Jetzt, ${time}`,
|
|
804
878
|
today: "Heute",
|
|
805
879
|
day: "Tag",
|
|
806
880
|
week: "Woche",
|
|
@@ -864,6 +938,10 @@ const uiMessages = i18n.define({
|
|
|
864
938
|
time
|
|
865
939
|
}) => `Letzte Antwort ${time}`,
|
|
866
940
|
messageStop: "Stoppen",
|
|
941
|
+
resourceUntitled: "Ohne Titel",
|
|
942
|
+
resourceNoAccess: "Kein Zugriff",
|
|
943
|
+
resourceDeleted: "Element gelöscht",
|
|
944
|
+
resourceUnavailable: "Gerade nicht verfügbar",
|
|
867
945
|
conversationMessages: ({
|
|
868
946
|
label
|
|
869
947
|
}) => `Nachrichten in ${label}`,
|
|
@@ -1011,6 +1089,13 @@ const uiMessages = i18n.define({
|
|
|
1011
1089
|
label
|
|
1012
1090
|
}) => `${label} nach oben verschieben`,
|
|
1013
1091
|
rowAction: "Zeilenaktion",
|
|
1092
|
+
sidebarUnread: ({
|
|
1093
|
+
count
|
|
1094
|
+
}) => `${count} ungelesen`,
|
|
1095
|
+
sidebarUnreadMany: "mehr als 99 ungelesen",
|
|
1096
|
+
sidebarNewActivity: "neue Aktivität",
|
|
1097
|
+
sidebarMentioned: "erwähnt dich",
|
|
1098
|
+
sidebarMuted: "stummgeschaltet",
|
|
1014
1099
|
addToNamed: ({
|
|
1015
1100
|
name
|
|
1016
1101
|
}) => `Zu ${name} hinzufügen`,
|
|
@@ -1047,6 +1132,8 @@ const uiMessages = i18n.define({
|
|
|
1047
1132
|
status
|
|
1048
1133
|
}) => `PDF-Vorschau ist mit HTTP ${status} fehlgeschlagen`,
|
|
1049
1134
|
pdfPreviewTabBlocked: "Der Browser hat den neuen Tab blockiert. Erlaube Pop-ups und versuche es erneut.",
|
|
1135
|
+
pdfPreviewNoViewer: "Dieses PDF kann hier nicht angezeigt werden",
|
|
1136
|
+
pdfPreviewNoViewerDescription: "Dein Browser kann PDFs nicht innerhalb einer Seite anzeigen. Öffne das Dokument, um es anzusehen.",
|
|
1050
1137
|
pdfPreviewRendering: "PDF-Vorschau wird erstellt…",
|
|
1051
1138
|
pickIcon: "Symbol auswählen...",
|
|
1052
1139
|
postMessage: "Nachricht posten",
|
|
@@ -1058,6 +1145,21 @@ const uiMessages = i18n.define({
|
|
|
1058
1145
|
sending: "Wird gesendet",
|
|
1059
1146
|
steering: "Wird gesteuert",
|
|
1060
1147
|
writeMessage: "Nachricht schreiben oder / eingeben ...",
|
|
1148
|
+
writeConversationMessage: "Nachricht schreiben",
|
|
1149
|
+
composerFormatting: "Formatierung",
|
|
1150
|
+
insertEmoji: "Emoji einfügen",
|
|
1151
|
+
emojiSuggestions: "Emoji-Vorschläge",
|
|
1152
|
+
dictate: "Diktieren",
|
|
1153
|
+
dictateOrHold: "Diktieren. Halten nimmt eine Sprachnachricht auf.",
|
|
1154
|
+
stopDictation: "Diktat beenden",
|
|
1155
|
+
microphoneOptions: "Mikrofon-Optionen",
|
|
1156
|
+
recordVoiceMessage: "Sprachnachricht aufnehmen",
|
|
1157
|
+
dictationListening: "Diktat läuft …",
|
|
1158
|
+
dictationRefining: "Text wird verfeinert …",
|
|
1159
|
+
dictationRefined: "Text verfeinert",
|
|
1160
|
+
dictationUnrefined: "Text nicht verfeinert",
|
|
1161
|
+
dictationInterrupted: "Diktat unterbrochen",
|
|
1162
|
+
restoreOriginal: "Original wiederherstellen",
|
|
1061
1163
|
openInNewTab: ({
|
|
1062
1164
|
name
|
|
1063
1165
|
}) => `${name} in neuem Tab öffnen`,
|