@csntgao/uni-base 0.1.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/.prettierignore +4 -0
- package/.prettierrc.json +6 -0
- package/AGENTS.md +519 -0
- package/CLAUDE.md +512 -0
- package/README.md +734 -0
- package/docs//346/225/260/345/255/227/345/221/230/345/267/245/345/256/214/346/225/264/346/212/275/345/261/211/346/216/245/345/205/245/350/257/264/346/230/216.md +110 -0
- package/docs//347/273/204/347/273/207/345/221/230/345/267/245/345/215/225/351/200/211/347/273/204/344/273/266/346/216/245/345/205/245/350/257/264/346/230/216.md +79 -0
- package/docs//347/273/204/347/273/207/345/221/230/345/267/245/345/244/232/351/200/211/347/273/204/344/273/266/346/216/245/345/205/245/350/257/264/346/230/216.md +93 -0
- package/docs//350/264/246/346/210/267/344/270/216/347/231/273/345/275/225/347/273/204/344/273/266/345/212/250/346/200/201/346/216/245/345/205/245/350/257/264/346/230/216.md +201 -0
- package/eslint.config.js +30 -0
- package/examples/vanilla/index.html +33 -0
- package/examples/vanilla/main.js +247 -0
- package/examples/vanilla/package.json +15 -0
- package/examples/vanilla/public/runtime-config.js +10 -0
- package/examples/vanilla/style.css +99 -0
- package/package.json +31 -0
- package/packages/core/package.json +30 -0
- package/packages/core/src/client.ts +1132 -0
- package/packages/core/src/customer-support-client.ts +825 -0
- package/packages/core/src/customer-support-types.ts +123 -0
- package/packages/core/src/index.ts +79 -0
- package/packages/core/src/notification-center-client.ts +462 -0
- package/packages/core/src/notification-center-types.ts +154 -0
- package/packages/core/src/protocol.ts +237 -0
- package/packages/core/src/types.ts +170 -0
- package/packages/core/tests/client.test.ts +760 -0
- package/packages/core/tests/customer-support-client.test.ts +576 -0
- package/packages/core/tests/fake-websocket.ts +55 -0
- package/packages/core/tests/notification-center-client.test.ts +467 -0
- package/packages/core/tests/protocol.test.ts +124 -0
- package/packages/core/tsconfig.json +7 -0
- package/packages/core/tsup.config.ts +11 -0
- package/packages/web-components/package.json +35 -0
- package/packages/web-components/scripts/verify-runtime-build.mjs +111 -0
- package/packages/web-components/src/account-context.ts +1540 -0
- package/packages/web-components/src/application-switcher-transport.ts +243 -0
- package/packages/web-components/src/application-switcher.ts +1103 -0
- package/packages/web-components/src/chat.ts +1754 -0
- package/packages/web-components/src/customer-support-chat.ts +862 -0
- package/packages/web-components/src/customer-support-transport.ts +269 -0
- package/packages/web-components/src/index.ts +256 -0
- package/packages/web-components/src/notification-center.ts +1066 -0
- package/packages/web-components/src/notification-popover.ts +615 -0
- package/packages/web-components/src/organization-multi-employee-selector.ts +1564 -0
- package/packages/web-components/src/organization-onboarding-transport.ts +54 -0
- package/packages/web-components/src/organization-onboarding.ts +1216 -0
- package/packages/web-components/src/organization-selector.ts +534 -0
- package/packages/web-components/src/organization-single-employee-selector.ts +114 -0
- package/packages/web-components/src/prototype-icons.ts +242 -0
- package/packages/web-components/src/safe-image-url.ts +29 -0
- package/packages/web-components/src/safe-markdown.ts +192 -0
- package/packages/web-components/src/uniplat-base-account-context-transport.ts +625 -0
- package/packages/web-components/src/uniplat-base-application-switcher-transport.ts +418 -0
- package/packages/web-components/src/uniplat-base-application-ticket.ts +254 -0
- package/packages/web-components/src/uniplat-base-identity-ticket.ts +202 -0
- package/packages/web-components/src/uniplat-base-notification-center-transport.ts +378 -0
- package/packages/web-components/src/uniplat-base-organization-multi-employee-selector-transport.ts +394 -0
- package/packages/web-components/src/uniplat-base-organization-onboarding-transport.ts +183 -0
- package/packages/web-components/src/uniplat-base-organization-single-employee-selector-transport.ts +14 -0
- package/packages/web-components/src/user-login-transport.ts +563 -0
- package/packages/web-components/src/user-login.ts +2027 -0
- package/packages/web-components/src/user-menu.ts +880 -0
- package/packages/web-components/tests/account-context.test.ts +798 -0
- package/packages/web-components/tests/application-switcher.test.ts +562 -0
- package/packages/web-components/tests/chat.test.ts +1130 -0
- package/packages/web-components/tests/customer-support-chat.test.ts +428 -0
- package/packages/web-components/tests/customer-support-transport.test.ts +79 -0
- package/packages/web-components/tests/notification-center-transport.test.ts +271 -0
- package/packages/web-components/tests/notification-center.test.ts +429 -0
- package/packages/web-components/tests/notification-popover.test.ts +283 -0
- package/packages/web-components/tests/organization-multi-employee-selector-transport.test.ts +332 -0
- package/packages/web-components/tests/organization-multi-employee-selector.test.ts +225 -0
- package/packages/web-components/tests/organization-onboarding.test.ts +260 -0
- package/packages/web-components/tests/organization-selector.test.ts +127 -0
- package/packages/web-components/tests/organization-single-employee-selector.test.ts +145 -0
- package/packages/web-components/tests/uniplat-base-account-context-transport.test.ts +496 -0
- package/packages/web-components/tests/uniplat-base-application-switcher-transport.test.ts +598 -0
- package/packages/web-components/tests/uniplat-base-application-ticket.test.ts +233 -0
- package/packages/web-components/tests/uniplat-base-identity-ticket.test.ts +226 -0
- package/packages/web-components/tests/uniplat-base-organization-onboarding-transport.test.ts +227 -0
- package/packages/web-components/tests/user-login-transport.test.ts +443 -0
- package/packages/web-components/tests/user-login.test.ts +626 -0
- package/packages/web-components/tests/user-menu.test.ts +271 -0
- package/packages/web-components/tsconfig.json +8 -0
- package/packages/web-components/tsup.config.ts +12 -0
- package/packages/web-components/tsup.runtime.config.ts +37 -0
- package/pnpm-workspace.yaml +6 -0
- package/tsconfig.base.json +21 -0
- package/vitest.config.ts +20 -0
- package//344/272/244/346/216/245/346/226/207/346/241/243.md +254 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { html, svg, type TemplateResult } from 'lit'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Exact SVG geometry from the Tabler Icons 2.47.0 assets used by the 40.1 prototype.
|
|
5
|
+
* Source: https://github.com/tabler/tabler-icons/tree/v2.47.0/icons
|
|
6
|
+
*/
|
|
7
|
+
export type PrototypeIconName =
|
|
8
|
+
| 'alert-circle'
|
|
9
|
+
| 'alert-triangle'
|
|
10
|
+
| 'arrow-left'
|
|
11
|
+
| 'arrow-right'
|
|
12
|
+
| 'bell'
|
|
13
|
+
| 'building'
|
|
14
|
+
| 'chevron-down'
|
|
15
|
+
| 'chevron-right'
|
|
16
|
+
| 'circle-check-filled'
|
|
17
|
+
| 'clock-exclamation'
|
|
18
|
+
| 'clock-hour-4'
|
|
19
|
+
| 'clipboard-check'
|
|
20
|
+
| 'coin'
|
|
21
|
+
| 'discount-check'
|
|
22
|
+
| 'discount-check-filled'
|
|
23
|
+
| 'headset'
|
|
24
|
+
| 'id-badge-2'
|
|
25
|
+
| 'layout-grid'
|
|
26
|
+
| 'logout'
|
|
27
|
+
| 'messages'
|
|
28
|
+
| 'mail-check'
|
|
29
|
+
| 'message-2'
|
|
30
|
+
| 'plus'
|
|
31
|
+
| 'robot'
|
|
32
|
+
| 'selector'
|
|
33
|
+
| 'speakerphone'
|
|
34
|
+
| 'user-cog'
|
|
35
|
+
| 'users'
|
|
36
|
+
| 'x'
|
|
37
|
+
|
|
38
|
+
export function prototypeIcon(name: PrototypeIconName): TemplateResult {
|
|
39
|
+
const content = prototypeIconContent(name)
|
|
40
|
+
return html`<svg
|
|
41
|
+
viewBox="0 0 24 24"
|
|
42
|
+
stroke-width="2"
|
|
43
|
+
stroke="currentColor"
|
|
44
|
+
fill="none"
|
|
45
|
+
stroke-linecap="round"
|
|
46
|
+
stroke-linejoin="round"
|
|
47
|
+
aria-hidden="true"
|
|
48
|
+
data-prototype-icon=${name}
|
|
49
|
+
>
|
|
50
|
+
${content}
|
|
51
|
+
</svg>`
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function prototypeIconContent(name: PrototypeIconName): TemplateResult {
|
|
55
|
+
if (name === 'layout-grid') {
|
|
56
|
+
return svg`
|
|
57
|
+
<path d="M4 4m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z" />
|
|
58
|
+
<path d="M14 4m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z" />
|
|
59
|
+
<path d="M4 14m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z" />
|
|
60
|
+
<path d="M14 14m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z" />
|
|
61
|
+
`
|
|
62
|
+
}
|
|
63
|
+
if (name === 'headset') {
|
|
64
|
+
return svg`
|
|
65
|
+
<path d="M4 14v-3a8 8 0 1 1 16 0v3" />
|
|
66
|
+
<path d="M18 19c0 1.657 -2.686 3 -6 3" />
|
|
67
|
+
<path d="M4 14a2 2 0 0 1 2 -2h1a2 2 0 0 1 2 2v3a2 2 0 0 1 -2 2h-1a2 2 0 0 1 -2 -2v-3z" />
|
|
68
|
+
<path d="M15 14a2 2 0 0 1 2 -2h1a2 2 0 0 1 2 2v3a2 2 0 0 1 -2 2h-1a2 2 0 0 1 -2 -2v-3z" />
|
|
69
|
+
`
|
|
70
|
+
}
|
|
71
|
+
if (name === 'robot') {
|
|
72
|
+
return svg`
|
|
73
|
+
<path d="M6 4m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v4a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z" />
|
|
74
|
+
<path d="M12 2v2" />
|
|
75
|
+
<path d="M9 12v9" />
|
|
76
|
+
<path d="M15 12v9" />
|
|
77
|
+
<path d="M5 16l4 -2" />
|
|
78
|
+
<path d="M15 14l4 2" />
|
|
79
|
+
<path d="M9 18h6" />
|
|
80
|
+
<path d="M10 8v.01" />
|
|
81
|
+
<path d="M14 8v.01" />
|
|
82
|
+
`
|
|
83
|
+
}
|
|
84
|
+
if (name === 'messages') {
|
|
85
|
+
return svg`
|
|
86
|
+
<path d="M21 14l-3 -3h-7a1 1 0 0 1 -1 -1v-6a1 1 0 0 1 1 -1h9a1 1 0 0 1 1 1v10" />
|
|
87
|
+
<path d="M14 15v2a1 1 0 0 1 -1 1h-7l-3 3v-10a1 1 0 0 1 1 -1h2" />
|
|
88
|
+
`
|
|
89
|
+
}
|
|
90
|
+
if (name === 'message-2') {
|
|
91
|
+
return svg`
|
|
92
|
+
<path d="M8 9h8" />
|
|
93
|
+
<path d="M8 13h6" />
|
|
94
|
+
<path d="M9 18h-3a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-3l-3 3l-3 -3z" />
|
|
95
|
+
`
|
|
96
|
+
}
|
|
97
|
+
if (name === 'arrow-left') {
|
|
98
|
+
return svg`<path d="M5 12l14 0" /><path d="M5 12l6 6" /><path d="M5 12l6 -6" />`
|
|
99
|
+
}
|
|
100
|
+
if (name === 'arrow-right') {
|
|
101
|
+
return svg`<path d="M5 12l14 0" /><path d="M13 18l6 -6" /><path d="M13 6l6 6" />`
|
|
102
|
+
}
|
|
103
|
+
if (name === 'bell') {
|
|
104
|
+
return svg`
|
|
105
|
+
<path d="M10 5a2 2 0 1 1 4 0a7 7 0 0 1 4 6v3a4 4 0 0 0 2 3h-16a4 4 0 0 0 2 -3v-3a7 7 0 0 1 4 -6" />
|
|
106
|
+
<path d="M9 17v1a3 3 0 0 0 6 0v-1" />
|
|
107
|
+
`
|
|
108
|
+
}
|
|
109
|
+
if (name === 'chevron-down') return svg`<path d="M6 9l6 6l6 -6" />`
|
|
110
|
+
if (name === 'chevron-right') return svg`<path d="M9 6l6 6l-6 6" />`
|
|
111
|
+
if (name === 'x') return svg`<path d="M18 6l-12 12" /><path d="M6 6l12 12" />`
|
|
112
|
+
if (name === 'selector') {
|
|
113
|
+
return svg`<path d="M8 9l4 -4l4 4" /><path d="M16 15l-4 4l-4 -4" />`
|
|
114
|
+
}
|
|
115
|
+
if (name === 'plus') return svg`<path d="M12 5l0 14" /><path d="M5 12l14 0" />`
|
|
116
|
+
if (name === 'logout') {
|
|
117
|
+
return svg`
|
|
118
|
+
<path d="M14 8v-2a2 2 0 0 0 -2 -2h-7a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h7a2 2 0 0 0 2 -2v-2" />
|
|
119
|
+
<path d="M9 12h12l-3 -3" />
|
|
120
|
+
<path d="M18 15l3 -3" />
|
|
121
|
+
`
|
|
122
|
+
}
|
|
123
|
+
if (name === 'users') {
|
|
124
|
+
return svg`
|
|
125
|
+
<path d="M9 7m-4 0a4 4 0 1 0 8 0a4 4 0 1 0 -8 0" />
|
|
126
|
+
<path d="M3 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2" />
|
|
127
|
+
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
|
|
128
|
+
<path d="M21 21v-2a4 4 0 0 0 -3 -3.85" />
|
|
129
|
+
`
|
|
130
|
+
}
|
|
131
|
+
if (name === 'building') {
|
|
132
|
+
return svg`
|
|
133
|
+
<path d="M3 21l18 0" />
|
|
134
|
+
<path d="M9 8l1 0" />
|
|
135
|
+
<path d="M9 12l1 0" />
|
|
136
|
+
<path d="M9 16l1 0" />
|
|
137
|
+
<path d="M14 8l1 0" />
|
|
138
|
+
<path d="M14 12l1 0" />
|
|
139
|
+
<path d="M14 16l1 0" />
|
|
140
|
+
<path d="M5 21v-16a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v16" />
|
|
141
|
+
`
|
|
142
|
+
}
|
|
143
|
+
if (name === 'clipboard-check') {
|
|
144
|
+
return svg`
|
|
145
|
+
<path d="M9 5h-2a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-12a2 2 0 0 0 -2 -2h-2" />
|
|
146
|
+
<path d="M9 3m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v0a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z" />
|
|
147
|
+
<path d="M9 14l2 2l4 -4" />
|
|
148
|
+
`
|
|
149
|
+
}
|
|
150
|
+
if (name === 'coin') {
|
|
151
|
+
return svg`
|
|
152
|
+
<path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
|
|
153
|
+
<path d="M14.8 9a2 2 0 0 0 -1.8 -1h-2a2 2 0 1 0 0 4h2a2 2 0 1 1 0 4h-2a2 2 0 0 1 -1.8 -1" />
|
|
154
|
+
<path d="M12 7v10" />
|
|
155
|
+
`
|
|
156
|
+
}
|
|
157
|
+
if (name === 'discount-check') {
|
|
158
|
+
return svg`
|
|
159
|
+
<path d="M5 7.2a2.2 2.2 0 0 1 2.2 -2.2h1a2.2 2.2 0 0 0 1.55 -.64l.7 -.7a2.2 2.2 0 0 1 3.12 0l.7 .7c.412 .41 .97 .64 1.55 .64h1a2.2 2.2 0 0 1 2.2 2.2v1c0 .58 .23 1.138 .64 1.55l.7 .7a2.2 2.2 0 0 1 0 3.12l-.7 .7a2.2 2.2 0 0 0 -.64 1.55v1a2.2 2.2 0 0 1 -2.2 2.2h-1a2.2 2.2 0 0 0 -1.55 .64l-.7 .7a2.2 2.2 0 0 1 -3.12 0l-.7 -.7a2.2 2.2 0 0 0 -1.55 -.64h-1a2.2 2.2 0 0 1 -2.2 -2.2v-1a2.2 2.2 0 0 0 -.64 -1.55l-.7 -.7a2.2 2.2 0 0 1 0 -3.12l.7 -.7a2.2 2.2 0 0 0 .64 -1.55v-1" />
|
|
160
|
+
<path d="M9 12l2 2l4 -4" />
|
|
161
|
+
`
|
|
162
|
+
}
|
|
163
|
+
if (name === 'mail-check') {
|
|
164
|
+
return svg`
|
|
165
|
+
<path d="M11 19h-6a2 2 0 0 1 -2 -2v-10a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v6" />
|
|
166
|
+
<path d="M3 7l9 6l9 -6" />
|
|
167
|
+
<path d="M15 19l2 2l4 -4" />
|
|
168
|
+
`
|
|
169
|
+
}
|
|
170
|
+
if (name === 'speakerphone') {
|
|
171
|
+
return svg`
|
|
172
|
+
<path d="M18 8a3 3 0 0 1 0 6" />
|
|
173
|
+
<path d="M10 8v11a1 1 0 0 1 -1 1h-1a1 1 0 0 1 -1 -1v-5" />
|
|
174
|
+
<path d="M12 8h0l4.524 -3.77a.9 .9 0 0 1 1.476 .692v12.156a.9 .9 0 0 1 -1.476 .692l-4.524 -3.77h-8a1 1 0 0 1 -1 -1v-4a1 1 0 0 1 1 -1h8" />
|
|
175
|
+
`
|
|
176
|
+
}
|
|
177
|
+
if (name === 'user-cog') {
|
|
178
|
+
return svg`
|
|
179
|
+
<path d="M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0" />
|
|
180
|
+
<path d="M6 21v-2a4 4 0 0 1 4 -4h2.5" />
|
|
181
|
+
<path d="M19.001 19m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
|
|
182
|
+
<path d="M19.001 15.5v1.5" />
|
|
183
|
+
<path d="M19.001 21v1.5" />
|
|
184
|
+
<path d="M22.032 17.25l-1.299 .75" />
|
|
185
|
+
<path d="M17.27 20l-1.3 .75" />
|
|
186
|
+
<path d="M15.97 17.25l1.3 .75" />
|
|
187
|
+
<path d="M20.733 20l1.3 .75" />
|
|
188
|
+
`
|
|
189
|
+
}
|
|
190
|
+
if (name === 'id-badge-2') {
|
|
191
|
+
return svg`
|
|
192
|
+
<path d="M7 12h3v4h-3z" />
|
|
193
|
+
<path d="M10 6h-6a1 1 0 0 0 -1 1v12a1 1 0 0 0 1 1h16a1 1 0 0 0 1 -1v-12a1 1 0 0 0 -1 -1h-6" />
|
|
194
|
+
<path d="M10 3m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v3a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z" />
|
|
195
|
+
<path d="M14 16h2" />
|
|
196
|
+
<path d="M14 12h4" />
|
|
197
|
+
`
|
|
198
|
+
}
|
|
199
|
+
if (name === 'alert-circle') {
|
|
200
|
+
return svg`
|
|
201
|
+
<path d="M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0" />
|
|
202
|
+
<path d="M12 8v4" />
|
|
203
|
+
<path d="M12 16h.01" />
|
|
204
|
+
`
|
|
205
|
+
}
|
|
206
|
+
if (name === 'clock-hour-4') {
|
|
207
|
+
return svg`
|
|
208
|
+
<path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
|
|
209
|
+
<path d="M12 12l3 2" />
|
|
210
|
+
<path d="M12 7v5" />
|
|
211
|
+
`
|
|
212
|
+
}
|
|
213
|
+
if (name === 'alert-triangle') {
|
|
214
|
+
return svg`
|
|
215
|
+
<path d="M12 9v4" />
|
|
216
|
+
<path
|
|
217
|
+
d="M10.363 3.591l-8.106 13.534a1.914 1.914 0 0 0 1.636 2.871h16.214a1.914 1.914 0 0 0 1.636 -2.87l-8.106 -13.536a1.914 1.914 0 0 0 -3.274 0z"
|
|
218
|
+
/>
|
|
219
|
+
<path d="M12 16h.01" />
|
|
220
|
+
`
|
|
221
|
+
}
|
|
222
|
+
if (name === 'clock-exclamation') {
|
|
223
|
+
return svg`
|
|
224
|
+
<path d="M20.986 12.502a9 9 0 1 0 -5.973 7.98" />
|
|
225
|
+
<path d="M12 7v5l3 3" />
|
|
226
|
+
<path d="M19 16v3" />
|
|
227
|
+
<path d="M19 22v.01" />
|
|
228
|
+
`
|
|
229
|
+
}
|
|
230
|
+
if (name === 'circle-check-filled') {
|
|
231
|
+
return svg`<path
|
|
232
|
+
d="M17 3.34a10 10 0 1 1 -14.995 8.984l-.005 -.324l.005 -.324a10 10 0 0 1 14.995 -8.336zm-1.293 5.953a1 1 0 0 0 -1.32 -.083l-.094 .083l-3.293 3.292l-1.293 -1.292l-.094 -.083a1 1 0 0 0 -1.403 1.403l.083 .094l2 2l.094 .083a1 1 0 0 0 1.226 0l.094 -.083l4 -4l.083 -.094a1 1 0 0 0 -.083 -1.32z"
|
|
233
|
+
stroke-width="0"
|
|
234
|
+
fill="currentColor"
|
|
235
|
+
/>`
|
|
236
|
+
}
|
|
237
|
+
return svg`<path
|
|
238
|
+
d="M12.01 2.011a3.2 3.2 0 0 1 2.113 .797l.154 .145l.698 .698a1.2 1.2 0 0 0 .71 .341l.135 .008h1a3.2 3.2 0 0 1 3.195 3.018l.005 .182v1c0 .27 .092 .533 .258 .743l.09 .1l.697 .698a3.2 3.2 0 0 1 .147 4.382l-.145 .154l-.698 .698a1.2 1.2 0 0 0 -.341 .71l-.008 .135v1a3.2 3.2 0 0 1 -3.018 3.195l-.182 .005h-1a1.2 1.2 0 0 0 -.743 .258l-.1 .09l-.698 .697a3.2 3.2 0 0 1 -4.382 .147l-.154 -.145l-.698 -.698a1.2 1.2 0 0 0 -.71 -.341l-.135 -.008h-1a3.2 3.2 0 0 1 -3.195 -3.018l-.005 -.182v-1a1.2 1.2 0 0 0 -.258 -.743l-.09 -.1l-.697 -.698a3.2 3.2 0 0 1 -.147 -4.382l.145 -.154l.698 -.698a1.2 1.2 0 0 0 .341 -.71l.008 -.135v-1l.005 -.182a3.2 3.2 0 0 1 3.013 -3.013l.182 -.005h1a1.2 1.2 0 0 0 .743 -.258l.1 -.09l.698 -.697a3.2 3.2 0 0 1 2.269 -.944zm3.697 7.282a1 1 0 0 0 -1.414 0l-3.293 3.292l-1.293 -1.292l-.094 -.083a1 1 0 0 0 -1.32 1.497l2 2l.094 .083a1 1 0 0 0 1.32 -.083l4 -4l.083 -.094a1 1 0 0 0 -.083 -1.32z"
|
|
239
|
+
stroke-width="0"
|
|
240
|
+
fill="currentColor"
|
|
241
|
+
/>`
|
|
242
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export function normalizeSafeImageUrl(value: unknown, label: string): string {
|
|
2
|
+
if (value === undefined || value === null) return ''
|
|
3
|
+
if (typeof value !== 'string') throw new TypeError(`${label} must be a string`)
|
|
4
|
+
const normalized = value.trim()
|
|
5
|
+
if (!normalized) return ''
|
|
6
|
+
if (normalized.length > 2_048) throw new TypeError(`${label} is too long`)
|
|
7
|
+
|
|
8
|
+
let url: URL
|
|
9
|
+
try {
|
|
10
|
+
const baseUrl = typeof document === 'undefined' ? 'https://localhost/' : document.baseURI
|
|
11
|
+
url = new URL(normalized, baseUrl)
|
|
12
|
+
} catch {
|
|
13
|
+
throw new TypeError(`${label} is invalid`)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!['http:', 'https:'].includes(url.protocol)) {
|
|
17
|
+
throw new TypeError(`${label} must use HTTP or HTTPS`)
|
|
18
|
+
}
|
|
19
|
+
if (url.username || url.password || url.hash) {
|
|
20
|
+
throw new TypeError(`${label} cannot contain credentials or a fragment`)
|
|
21
|
+
}
|
|
22
|
+
for (const key of url.searchParams.keys()) {
|
|
23
|
+
const normalizedKey = key.toLowerCase().replace(/[^a-z0-9]/g, '')
|
|
24
|
+
if (/(?:authorization|jwt|ticket|token)/.test(normalizedKey)) {
|
|
25
|
+
throw new TypeError(`${label} cannot contain authentication parameters`)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return url.href
|
|
29
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { html, type TemplateResult } from 'lit'
|
|
2
|
+
|
|
3
|
+
// Allow-listed Markdown renderer shared by server-authored component content.
|
|
4
|
+
// Source text is always passed through Lit interpolation; only the fixed tags
|
|
5
|
+
// below are produced. Raw HTML and unsafe link schemes therefore stay inert.
|
|
6
|
+
|
|
7
|
+
type InlineNode = TemplateResult | string
|
|
8
|
+
|
|
9
|
+
const INLINE_PATTERN =
|
|
10
|
+
/(`([^`]+)`)|(\*\*([^*]+)\*\*)|(__([^_]+)__)|(~~([^~]+)~~)|(\*([^*]+)\*)|(_([^_]+)_)|(\[([^\]]+)\]\(([^)\s]+)\))/
|
|
11
|
+
|
|
12
|
+
export function renderSafeMarkdown(source: string): TemplateResult[] {
|
|
13
|
+
const lines = String(source ?? '')
|
|
14
|
+
.replace(/\r\n?/g, '\n')
|
|
15
|
+
.split('\n')
|
|
16
|
+
const blocks: TemplateResult[] = []
|
|
17
|
+
let index = 0
|
|
18
|
+
|
|
19
|
+
while (index < lines.length) {
|
|
20
|
+
const line = lines[index] ?? ''
|
|
21
|
+
if (line.trim() === '') {
|
|
22
|
+
index += 1
|
|
23
|
+
continue
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (/^```/.test(line.trim())) {
|
|
27
|
+
const code: string[] = []
|
|
28
|
+
index += 1
|
|
29
|
+
while (index < lines.length && !/^```/.test((lines[index] ?? '').trim())) {
|
|
30
|
+
code.push(lines[index] ?? '')
|
|
31
|
+
index += 1
|
|
32
|
+
}
|
|
33
|
+
if (index < lines.length) index += 1
|
|
34
|
+
blocks.push(html`<pre class="md-pre"><code>${code.join('\n')}</code></pre>`)
|
|
35
|
+
continue
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const heading = /^(#{1,6})\s+(.+)$/.exec(line.trim())
|
|
39
|
+
if (heading) {
|
|
40
|
+
const content = renderInline(heading[2] ?? '')
|
|
41
|
+
switch (heading[1]?.length) {
|
|
42
|
+
case 1:
|
|
43
|
+
blocks.push(html`<h1 class="md-heading md-h1">${content}</h1>`)
|
|
44
|
+
break
|
|
45
|
+
case 2:
|
|
46
|
+
blocks.push(html`<h2 class="md-heading md-h2">${content}</h2>`)
|
|
47
|
+
break
|
|
48
|
+
case 3:
|
|
49
|
+
blocks.push(html`<h3 class="md-heading md-h3">${content}</h3>`)
|
|
50
|
+
break
|
|
51
|
+
default:
|
|
52
|
+
blocks.push(html`<h4 class="md-heading md-h4">${content}</h4>`)
|
|
53
|
+
}
|
|
54
|
+
index += 1
|
|
55
|
+
continue
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (/^\s{0,3}([-*_])(?:\s*\1){2,}\s*$/.test(line)) {
|
|
59
|
+
blocks.push(html`<hr class="md-hr" />`)
|
|
60
|
+
index += 1
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (/^\s*>\s?/.test(line)) {
|
|
65
|
+
const quoted: string[] = []
|
|
66
|
+
while (index < lines.length && /^\s*>\s?/.test(lines[index] ?? '')) {
|
|
67
|
+
quoted.push((lines[index] ?? '').replace(/^\s*>\s?/, ''))
|
|
68
|
+
index += 1
|
|
69
|
+
}
|
|
70
|
+
blocks.push(html`<blockquote class="md-quote">${renderLines(quoted)}</blockquote>`)
|
|
71
|
+
continue
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (isUnorderedItem(line)) {
|
|
75
|
+
const items: string[] = []
|
|
76
|
+
while (index < lines.length && isUnorderedItem(lines[index] ?? '')) {
|
|
77
|
+
items.push((lines[index] ?? '').replace(/^\s*[-*+]\s+/, ''))
|
|
78
|
+
index += 1
|
|
79
|
+
}
|
|
80
|
+
blocks.push(
|
|
81
|
+
html`<ul class="md-list md-ul">
|
|
82
|
+
${items.map((item) => html`<li>${renderInline(item)}</li>`)}
|
|
83
|
+
</ul>`,
|
|
84
|
+
)
|
|
85
|
+
continue
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (isOrderedItem(line)) {
|
|
89
|
+
const items: string[] = []
|
|
90
|
+
while (index < lines.length && isOrderedItem(lines[index] ?? '')) {
|
|
91
|
+
items.push((lines[index] ?? '').replace(/^\s*\d+[.)]\s+/, ''))
|
|
92
|
+
index += 1
|
|
93
|
+
}
|
|
94
|
+
blocks.push(
|
|
95
|
+
html`<ol class="md-list md-ol">
|
|
96
|
+
${items.map((item) => html`<li>${renderInline(item)}</li>`)}
|
|
97
|
+
</ol>`,
|
|
98
|
+
)
|
|
99
|
+
continue
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const paragraph: string[] = []
|
|
103
|
+
while (index < lines.length) {
|
|
104
|
+
const current = lines[index] ?? ''
|
|
105
|
+
if (current.trim() === '' || isBlockStart(current)) break
|
|
106
|
+
paragraph.push(current)
|
|
107
|
+
index += 1
|
|
108
|
+
}
|
|
109
|
+
blocks.push(html`<p class="md-p">${renderLines(paragraph)}</p>`)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return blocks
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function isBlockStart(line: string): boolean {
|
|
116
|
+
const trimmed = line.trim()
|
|
117
|
+
return (
|
|
118
|
+
/^```/.test(trimmed) ||
|
|
119
|
+
/^#{1,6}\s+/.test(trimmed) ||
|
|
120
|
+
/^\s{0,3}([-*_])(?:\s*\1){2,}\s*$/.test(line) ||
|
|
121
|
+
/^\s*>\s?/.test(line) ||
|
|
122
|
+
isUnorderedItem(line) ||
|
|
123
|
+
isOrderedItem(line)
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function isUnorderedItem(line: string): boolean {
|
|
128
|
+
return /^\s*[-*+]\s+/.test(line)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function isOrderedItem(line: string): boolean {
|
|
132
|
+
return /^\s*\d+[.)]\s+/.test(line)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function renderLines(lines: readonly string[]): InlineNode[] {
|
|
136
|
+
const nodes: InlineNode[] = []
|
|
137
|
+
lines.forEach((line, index) => {
|
|
138
|
+
if (index > 0) nodes.push(html`<br />`)
|
|
139
|
+
nodes.push(...renderInline(line))
|
|
140
|
+
})
|
|
141
|
+
return nodes
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function renderInline(text: string): InlineNode[] {
|
|
145
|
+
const nodes: InlineNode[] = []
|
|
146
|
+
let rest = text
|
|
147
|
+
while (rest) {
|
|
148
|
+
const match = INLINE_PATTERN.exec(rest)
|
|
149
|
+
if (!match || match.index === undefined) {
|
|
150
|
+
nodes.push(rest)
|
|
151
|
+
break
|
|
152
|
+
}
|
|
153
|
+
if (match.index > 0) nodes.push(rest.slice(0, match.index))
|
|
154
|
+
if (match[1]) {
|
|
155
|
+
nodes.push(html`<code class="md-code">${match[2] ?? ''}</code>`)
|
|
156
|
+
} else if (match[3]) {
|
|
157
|
+
nodes.push(html`<strong>${renderInline(match[4] ?? '')}</strong>`)
|
|
158
|
+
} else if (match[5]) {
|
|
159
|
+
nodes.push(html`<strong>${renderInline(match[6] ?? '')}</strong>`)
|
|
160
|
+
} else if (match[7]) {
|
|
161
|
+
nodes.push(html`<del>${renderInline(match[8] ?? '')}</del>`)
|
|
162
|
+
} else if (match[9]) {
|
|
163
|
+
nodes.push(html`<em>${renderInline(match[10] ?? '')}</em>`)
|
|
164
|
+
} else if (match[11]) {
|
|
165
|
+
nodes.push(html`<em>${renderInline(match[12] ?? '')}</em>`)
|
|
166
|
+
} else if (match[13]) {
|
|
167
|
+
const url = safeLinkUrl(match[15] ?? '')
|
|
168
|
+
const label = renderInline(match[14] ?? '')
|
|
169
|
+
nodes.push(
|
|
170
|
+
url
|
|
171
|
+
? html`<a href=${url} target="_blank" rel="noopener noreferrer">${label}</a>`
|
|
172
|
+
: (match[13] ?? ''),
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
rest = rest.slice(match.index + match[0].length)
|
|
176
|
+
}
|
|
177
|
+
return nodes
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function safeLinkUrl(raw: string): string {
|
|
181
|
+
const value = raw.trim()
|
|
182
|
+
if (!value) return ''
|
|
183
|
+
if (/^mailto:[^\s]+@[^\s]+$/i.test(value)) return value
|
|
184
|
+
try {
|
|
185
|
+
const base = typeof document === 'undefined' ? 'https://localhost/' : document.baseURI
|
|
186
|
+
const url = new URL(value, base)
|
|
187
|
+
if (['http:', 'https:'].includes(url.protocol)) return url.href
|
|
188
|
+
} catch {
|
|
189
|
+
// Unsafe or malformed links are rendered as their original Markdown text.
|
|
190
|
+
}
|
|
191
|
+
return ''
|
|
192
|
+
}
|