@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.
Files changed (204) hide show
  1. package/README.md +3 -2
  2. package/dist/browser/actions/ContextMenu.js +3 -1
  3. package/dist/browser/actions/ContextMenu.js.map +1 -1
  4. package/dist/browser/actions/Dropdown.js +91 -46
  5. package/dist/browser/actions/Dropdown.js.map +1 -1
  6. package/dist/browser/actions/FilterChip.js +4 -1
  7. package/dist/browser/actions/FilterChip.js.map +1 -1
  8. package/dist/browser/actions/GestureMenu.js +467 -0
  9. package/dist/browser/actions/GestureMenu.js.map +1 -0
  10. package/dist/browser/actions/index.js +1 -0
  11. package/dist/browser/actions/index.js.map +1 -1
  12. package/dist/browser/chat/ChatComposer.js +1255 -534
  13. package/dist/browser/chat/ChatComposer.js.map +1 -1
  14. package/dist/browser/chat/ChatPrimitives.js +9 -3
  15. package/dist/browser/chat/ChatPrimitives.js.map +1 -1
  16. package/dist/browser/chat/ChatTimeline.js +19 -1
  17. package/dist/browser/chat/ChatTimeline.js.map +1 -1
  18. package/dist/browser/chat/chat-behavior.js +9 -0
  19. package/dist/browser/chat/chat-behavior.js.map +1 -1
  20. package/dist/browser/chat/index.js.map +1 -1
  21. package/dist/browser/chat/types.js.map +1 -1
  22. package/dist/browser/content/Calendar.js +579 -533
  23. package/dist/browser/content/Calendar.js.map +1 -1
  24. package/dist/browser/content/Lightbox.js +76 -40
  25. package/dist/browser/content/Lightbox.js.map +1 -1
  26. package/dist/browser/content/MessageRow.js +2 -2
  27. package/dist/browser/content/MessageRow.js.map +1 -1
  28. package/dist/browser/content/PdfPreview.js +146 -79
  29. package/dist/browser/content/PdfPreview.js.map +1 -1
  30. package/dist/browser/content/Timeline.js +1038 -0
  31. package/dist/browser/content/Timeline.js.map +1 -0
  32. package/dist/browser/content/VideoPlayer.js +29 -9
  33. package/dist/browser/content/VideoPlayer.js.map +1 -1
  34. package/dist/browser/content/chart-inspection.js +7 -2
  35. package/dist/browser/content/chart-inspection.js.map +1 -1
  36. package/dist/browser/content/index.js +1 -0
  37. package/dist/browser/content/index.js.map +1 -1
  38. package/dist/browser/content/timeline-model.js +579 -0
  39. package/dist/browser/content/timeline-model.js.map +1 -0
  40. package/dist/browser/feedback/dialog-core.js +26 -14
  41. package/dist/browser/feedback/dialog-core.js.map +1 -1
  42. package/dist/browser/inputs/ChoiceChips.js +66 -0
  43. package/dist/browser/inputs/ChoiceChips.js.map +1 -0
  44. package/dist/browser/inputs/ChoiceGroups.js +53 -12
  45. package/dist/browser/inputs/ChoiceGroups.js.map +1 -1
  46. package/dist/browser/inputs/FileInputs.js +48 -33
  47. package/dist/browser/inputs/FileInputs.js.map +1 -1
  48. package/dist/browser/inputs/SignatureInput.js +558 -0
  49. package/dist/browser/inputs/SignatureInput.js.map +1 -0
  50. package/dist/browser/inputs/TextInput.js +13 -1
  51. package/dist/browser/inputs/TextInput.js.map +1 -1
  52. package/dist/browser/inputs/emoji/EmojiPicker.js +841 -0
  53. package/dist/browser/inputs/emoji/EmojiPicker.js.map +1 -0
  54. package/dist/browser/inputs/emoji/emoji-data.js +1903 -0
  55. package/dist/browser/inputs/emoji/emoji-data.js.map +1 -0
  56. package/dist/browser/inputs/emoji/emoji-index.js +209 -0
  57. package/dist/browser/inputs/emoji/emoji-index.js.map +1 -0
  58. package/dist/browser/inputs/emoji/emoji-messages.js +64 -0
  59. package/dist/browser/inputs/emoji/emoji-messages.js.map +1 -0
  60. package/dist/browser/inputs/index.js +5 -0
  61. package/dist/browser/inputs/index.js.map +1 -1
  62. package/dist/browser/inputs/markdown/actions.js +6 -2
  63. package/dist/browser/inputs/markdown/actions.js.map +1 -1
  64. package/dist/browser/inputs/markdown/code-zone.js +38 -2
  65. package/dist/browser/inputs/markdown/code-zone.js.map +1 -1
  66. package/dist/browser/inputs/signature-messages.js +39 -0
  67. package/dist/browser/inputs/signature-messages.js.map +1 -0
  68. package/dist/browser/inputs/signature.js +208 -0
  69. package/dist/browser/inputs/signature.js.map +1 -0
  70. package/dist/browser/internal/render-error.js +49 -0
  71. package/dist/browser/internal/render-error.js.map +1 -0
  72. package/dist/browser/intl/messages.js +102 -0
  73. package/dist/browser/intl/messages.js.map +1 -1
  74. package/dist/browser/layout/AppWorkspace.js +321 -222
  75. package/dist/browser/layout/AppWorkspace.js.map +1 -1
  76. package/dist/browser/layout/FloatingWindow.js +4 -1
  77. package/dist/browser/layout/FloatingWindow.js.map +1 -1
  78. package/dist/browser/layout/SidebarItemPreview.js +5 -1
  79. package/dist/browser/layout/SidebarItemPreview.js.map +1 -1
  80. package/dist/browser/layout/index.js.map +1 -1
  81. package/dist/browser/surfaces/ResourceCard.js +212 -0
  82. package/dist/browser/surfaces/ResourceCard.js.map +1 -0
  83. package/dist/browser/surfaces/index.js +1 -0
  84. package/dist/browser/surfaces/index.js.map +1 -1
  85. package/dist/licenses/emojibase-data.txt +21 -0
  86. package/dist/ssr/actions/ContextMenu.js +3 -1
  87. package/dist/ssr/actions/ContextMenu.js.map +1 -1
  88. package/dist/ssr/actions/Dropdown.js +49 -12
  89. package/dist/ssr/actions/Dropdown.js.map +1 -1
  90. package/dist/ssr/actions/FilterChip.js +4 -1
  91. package/dist/ssr/actions/FilterChip.js.map +1 -1
  92. package/dist/ssr/actions/GestureMenu.js +411 -0
  93. package/dist/ssr/actions/GestureMenu.js.map +1 -0
  94. package/dist/ssr/actions/index.js +1 -0
  95. package/dist/ssr/actions/index.js.map +1 -1
  96. package/dist/ssr/chat/ChatComposer.js +767 -214
  97. package/dist/ssr/chat/ChatComposer.js.map +1 -1
  98. package/dist/ssr/chat/ChatPrimitives.js +9 -3
  99. package/dist/ssr/chat/ChatPrimitives.js.map +1 -1
  100. package/dist/ssr/chat/ChatTimeline.js +19 -1
  101. package/dist/ssr/chat/ChatTimeline.js.map +1 -1
  102. package/dist/ssr/chat/chat-behavior.js +9 -0
  103. package/dist/ssr/chat/chat-behavior.js.map +1 -1
  104. package/dist/ssr/chat/index.js.map +1 -1
  105. package/dist/ssr/chat/types.js.map +1 -1
  106. package/dist/ssr/content/Calendar.js +183 -142
  107. package/dist/ssr/content/Calendar.js.map +1 -1
  108. package/dist/ssr/content/Lightbox.js +19 -5
  109. package/dist/ssr/content/Lightbox.js.map +1 -1
  110. package/dist/ssr/content/MessageRow.js +2 -2
  111. package/dist/ssr/content/MessageRow.js.map +1 -1
  112. package/dist/ssr/content/PdfPreview.js +77 -36
  113. package/dist/ssr/content/PdfPreview.js.map +1 -1
  114. package/dist/ssr/content/Timeline.js +804 -0
  115. package/dist/ssr/content/Timeline.js.map +1 -0
  116. package/dist/ssr/content/VideoPlayer.js +29 -9
  117. package/dist/ssr/content/VideoPlayer.js.map +1 -1
  118. package/dist/ssr/content/chart-inspection.js +7 -2
  119. package/dist/ssr/content/chart-inspection.js.map +1 -1
  120. package/dist/ssr/content/index.js +1 -0
  121. package/dist/ssr/content/index.js.map +1 -1
  122. package/dist/ssr/content/timeline-model.js +579 -0
  123. package/dist/ssr/content/timeline-model.js.map +1 -0
  124. package/dist/ssr/feedback/dialog-core.js +26 -14
  125. package/dist/ssr/feedback/dialog-core.js.map +1 -1
  126. package/dist/ssr/inputs/ChoiceChips.js +66 -0
  127. package/dist/ssr/inputs/ChoiceChips.js.map +1 -0
  128. package/dist/ssr/inputs/ChoiceGroups.js +12 -4
  129. package/dist/ssr/inputs/ChoiceGroups.js.map +1 -1
  130. package/dist/ssr/inputs/FileInputs.js +19 -4
  131. package/dist/ssr/inputs/FileInputs.js.map +1 -1
  132. package/dist/ssr/inputs/SignatureInput.js +404 -0
  133. package/dist/ssr/inputs/SignatureInput.js.map +1 -0
  134. package/dist/ssr/inputs/TextInput.js +13 -1
  135. package/dist/ssr/inputs/TextInput.js.map +1 -1
  136. package/dist/ssr/inputs/emoji/EmojiPicker.js +551 -0
  137. package/dist/ssr/inputs/emoji/EmojiPicker.js.map +1 -0
  138. package/dist/ssr/inputs/emoji/emoji-data.js +1903 -0
  139. package/dist/ssr/inputs/emoji/emoji-data.js.map +1 -0
  140. package/dist/ssr/inputs/emoji/emoji-index.js +209 -0
  141. package/dist/ssr/inputs/emoji/emoji-index.js.map +1 -0
  142. package/dist/ssr/inputs/emoji/emoji-messages.js +64 -0
  143. package/dist/ssr/inputs/emoji/emoji-messages.js.map +1 -0
  144. package/dist/ssr/inputs/index.js +5 -0
  145. package/dist/ssr/inputs/index.js.map +1 -1
  146. package/dist/ssr/inputs/markdown/actions.js +6 -2
  147. package/dist/ssr/inputs/markdown/actions.js.map +1 -1
  148. package/dist/ssr/inputs/markdown/code-zone.js +38 -2
  149. package/dist/ssr/inputs/markdown/code-zone.js.map +1 -1
  150. package/dist/ssr/inputs/signature-messages.js +39 -0
  151. package/dist/ssr/inputs/signature-messages.js.map +1 -0
  152. package/dist/ssr/inputs/signature.js +208 -0
  153. package/dist/ssr/inputs/signature.js.map +1 -0
  154. package/dist/ssr/internal/render-error.js +49 -0
  155. package/dist/ssr/internal/render-error.js.map +1 -0
  156. package/dist/ssr/intl/messages.js +102 -0
  157. package/dist/ssr/intl/messages.js.map +1 -1
  158. package/dist/ssr/layout/AppWorkspace.js +112 -41
  159. package/dist/ssr/layout/AppWorkspace.js.map +1 -1
  160. package/dist/ssr/layout/FloatingWindow.js +4 -1
  161. package/dist/ssr/layout/FloatingWindow.js.map +1 -1
  162. package/dist/ssr/layout/SidebarItemPreview.js +5 -1
  163. package/dist/ssr/layout/SidebarItemPreview.js.map +1 -1
  164. package/dist/ssr/layout/index.js.map +1 -1
  165. package/dist/ssr/surfaces/ResourceCard.js +115 -0
  166. package/dist/ssr/surfaces/ResourceCard.js.map +1 -0
  167. package/dist/ssr/surfaces/index.js +1 -0
  168. package/dist/ssr/surfaces/index.js.map +1 -1
  169. package/dist/styles.css +1 -1
  170. package/dist/types/actions/Dropdown.d.ts +13 -1
  171. package/dist/types/actions/FilterChip.d.ts +5 -0
  172. package/dist/types/actions/GestureMenu.d.ts +35 -0
  173. package/dist/types/actions/index.d.ts +2 -0
  174. package/dist/types/chat/ChatComposer.d.ts +27 -1
  175. package/dist/types/chat/chat-behavior.d.ts +15 -0
  176. package/dist/types/chat/index.d.ts +1 -1
  177. package/dist/types/chat/types.d.ts +34 -0
  178. package/dist/types/content/Calendar.d.ts +28 -8
  179. package/dist/types/content/Lightbox.d.ts +8 -0
  180. package/dist/types/content/Timeline.d.ts +49 -0
  181. package/dist/types/content/index.d.ts +4 -2
  182. package/dist/types/content/timeline-model.d.ts +148 -0
  183. package/dist/types/feedback/dialog-core.d.ts +1 -1
  184. package/dist/types/inputs/ChoiceChips.d.ts +20 -0
  185. package/dist/types/inputs/ChoiceGroups.d.ts +11 -2
  186. package/dist/types/inputs/FileInputs.d.ts +6 -0
  187. package/dist/types/inputs/SignatureInput.d.ts +22 -0
  188. package/dist/types/inputs/emoji/EmojiPicker.d.ts +42 -0
  189. package/dist/types/inputs/emoji/emoji-data.d.ts +2 -0
  190. package/dist/types/inputs/emoji/emoji-index.d.ts +109 -0
  191. package/dist/types/inputs/emoji/emoji-messages.d.ts +30 -0
  192. package/dist/types/inputs/index.d.ts +10 -0
  193. package/dist/types/inputs/markdown/actions.d.ts +2 -0
  194. package/dist/types/inputs/markdown/code-zone.d.ts +7 -0
  195. package/dist/types/inputs/signature-messages.d.ts +11 -0
  196. package/dist/types/inputs/signature.d.ts +72 -0
  197. package/dist/types/internal/render-error.d.ts +13 -0
  198. package/dist/types/intl/messages.d.ts +51 -0
  199. package/dist/types/layout/AppWorkspace.d.ts +17 -0
  200. package/dist/types/layout/SidebarItemPreview.d.ts +2 -0
  201. package/dist/types/layout/index.d.ts +1 -1
  202. package/dist/types/surfaces/ResourceCard.d.ts +31 -0
  203. package/dist/types/surfaces/index.d.ts +2 -0
  204. 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
+ "&": "&amp;",
26
+ "<": "&lt;",
27
+ ">": "&gt;",
28
+ '"': "&quot;",
29
+ "'": "&apos;"
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) => ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\", \"'\": \"&apos;\" })[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`,