@rimelight/ui 0.0.2 → 0.0.3

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 (103) hide show
  1. package/package.json +5 -1
  2. package/src/components/carousel/carousel-script.ts +188 -188
  3. package/src/components/carousel/index.ts +1 -1
  4. package/src/components/input-otp/InputOtpTypes.ts +6 -6
  5. package/src/components/radio-group/RadioGroupTypes.ts +6 -6
  6. package/src/components/select/SelectTypes.ts +13 -13
  7. package/src/components/switch/SwitchTypes.ts +6 -6
  8. package/src/components/toast/index.ts +1 -1
  9. package/src/components/toast/toast-manager.ts +234 -234
  10. package/src/components/toggle/ToggleTypes.ts +14 -14
  11. package/src/config/cookies.ts +5 -5
  12. package/src/config/index.ts +3 -3
  13. package/src/config/links.ts +4 -4
  14. package/src/config/site.config.ts +45 -45
  15. package/src/domain/cms/index.ts +1 -1
  16. package/src/domain/cms/types.ts +20 -20
  17. package/src/domain/i18n/constants.ts +21 -21
  18. package/src/domain/i18n/cookie-storage.ts +13 -13
  19. package/src/domain/i18n/country-to-locale-map.ts +67 -67
  20. package/src/domain/i18n/fetcher.ts +268 -268
  21. package/src/domain/i18n/flags.ts +16 -16
  22. package/src/domain/i18n/format.ts +90 -90
  23. package/src/domain/i18n/index.ts +10 -10
  24. package/src/domain/i18n/middleware/i18n.ts +160 -160
  25. package/src/domain/i18n/resolve-locale.ts +55 -55
  26. package/src/domain/i18n/schema.ts +29 -29
  27. package/src/domain/index.ts +4 -4
  28. package/src/domain/seo/constants.ts +20 -20
  29. package/src/domain/seo/index.ts +3 -3
  30. package/src/domain/seo/services/index.ts +3 -3
  31. package/src/domain/seo/services/llms.ts +53 -53
  32. package/src/domain/seo/services/robots.ts +12 -12
  33. package/src/domain/seo/services/sitemap.ts +56 -56
  34. package/src/domain/seo/utils.ts +37 -37
  35. package/src/domain/theme/constants.ts +6 -6
  36. package/src/domain/theme/index.ts +2 -2
  37. package/src/domain/theme/schema.ts +10 -10
  38. package/src/env.d.ts +49 -49
  39. package/src/integrations/index.ts +1 -0
  40. package/src/integrations/sri.ts +92 -0
  41. package/src/lib/component-preview.ts +73 -73
  42. package/src/lib/index.ts +6 -6
  43. package/src/lib/remark/remark-modified-time.ts +13 -13
  44. package/src/lib/showcase-preview-elements.ts +408 -408
  45. package/src/lib/showcase-renderer.ts +218 -218
  46. package/src/lib/showcase-utils.ts +189 -189
  47. package/src/lib/turnstile.ts +35 -35
  48. package/src/lib/utils/starwind/positioning.ts +318 -318
  49. package/src/locales/de/blog.json +9 -9
  50. package/src/locales/de/common.json +19 -19
  51. package/src/locales/de/errors.json +6 -6
  52. package/src/locales/de/home.json +20 -20
  53. package/src/locales/de/messages.json +8 -8
  54. package/src/locales/de/nav.json +13 -13
  55. package/src/locales/de/projects.json +9 -9
  56. package/src/locales/de/services.json +43 -43
  57. package/src/locales/en/blog.json +9 -9
  58. package/src/locales/en/chat.json +29 -29
  59. package/src/locales/en/common.json +19 -19
  60. package/src/locales/en/cookie_notice.json +11 -11
  61. package/src/locales/en/editor.json +24 -24
  62. package/src/locales/en/errors.json +6 -6
  63. package/src/locales/en/home.json +20 -20
  64. package/src/locales/en/messages.json +8 -8
  65. package/src/locales/en/nav.json +13 -13
  66. package/src/locales/en/projects.json +9 -9
  67. package/src/locales/en/services.json +43 -43
  68. package/src/locales/es/blog.json +9 -9
  69. package/src/locales/es/common.json +19 -19
  70. package/src/locales/es/errors.json +6 -6
  71. package/src/locales/es/home.json +20 -20
  72. package/src/locales/es/messages.json +8 -8
  73. package/src/locales/es/nav.json +13 -13
  74. package/src/locales/es/projects.json +9 -9
  75. package/src/locales/es/services.json +43 -43
  76. package/src/locales/ja/blog.json +9 -9
  77. package/src/locales/ja/common.json +19 -19
  78. package/src/locales/ja/errors.json +6 -6
  79. package/src/locales/ja/home.json +20 -20
  80. package/src/locales/ja/messages.json +8 -8
  81. package/src/locales/ja/nav.json +13 -13
  82. package/src/locales/ja/projects.json +9 -9
  83. package/src/locales/ja/services.json +43 -43
  84. package/src/locales/pt/blog.json +9 -9
  85. package/src/locales/pt/chat.json +29 -29
  86. package/src/locales/pt/common.json +19 -19
  87. package/src/locales/pt/cookie_notice.json +11 -11
  88. package/src/locales/pt/editor.json +24 -24
  89. package/src/locales/pt/errors.json +6 -6
  90. package/src/locales/pt/home.json +20 -20
  91. package/src/locales/pt/messages.json +8 -8
  92. package/src/locales/pt/nav.json +13 -13
  93. package/src/locales/pt/projects.json +9 -9
  94. package/src/locales/pt/services.json +43 -43
  95. package/src/middleware/index.ts +1 -0
  96. package/src/middleware/sri.ts +76 -0
  97. package/src/utils/index.ts +2 -2
  98. package/src/utils/server/cookies.ts +98 -98
  99. package/src/utils/server/index.ts +1 -1
  100. package/src/utils/shared/deep-merge.ts +36 -36
  101. package/src/utils/shared/embed.ts +105 -105
  102. package/src/utils/shared/index.ts +3 -3
  103. package/src/utils/shared/parse-boolean.ts +14 -14
@@ -1,98 +1,98 @@
1
- import type { AstroCookies } from "astro"
2
- import { z } from "astro/zod"
3
-
4
- import { COOKIE_TTL } from "../../config"
5
-
6
- type AstroCookieOptions = NonNullable<Parameters<AstroCookies["set"]>[2]>
7
-
8
- const DEFAULT_OPTIONS: AstroCookieOptions = {
9
- path: "/",
10
- httpOnly: true,
11
- secure: import.meta.env.PROD,
12
- sameSite: "lax",
13
- maxAge: COOKIE_TTL.oneYear
14
- }
15
-
16
- export type SetCookieOverrides = Partial<AstroCookieOptions>
17
-
18
- // ------------------------------
19
- // SETTER
20
- // ------------------------------
21
- export function setCookie<T = string>(
22
- cookies: AstroCookies,
23
- name: string,
24
- value: T,
25
- overrides?: SetCookieOverrides
26
- ): void {
27
- const stringValue = typeof value === "string" ? value : JSON.stringify(value)
28
-
29
- cookies.set(name, stringValue, {
30
- ...DEFAULT_OPTIONS,
31
- ...overrides
32
- })
33
- }
34
-
35
- // ------------------------------
36
- // STRING COOKIE WITH SCHEMA
37
- // ------------------------------
38
- export function getCookie(cookies: AstroCookies, name: string): string | null {
39
- const entry = cookies.get(name)
40
- if (!entry) return null
41
- return entry.value
42
- }
43
-
44
- // ------------------------------
45
- // STRING COOKIE WITH SCHEMA
46
- // ------------------------------
47
- export function getCookieWithSchema<S extends z.ZodTypeAny>(
48
- cookies: AstroCookies,
49
- name: string,
50
- schema: S
51
- ): z.infer<S> | null {
52
- const entry = cookies.get(name)
53
- if (!entry) return null
54
-
55
- const raw = entry.value
56
-
57
- const result = schema.safeParse(raw)
58
- if (result.success) return result.data as z.infer<S>
59
-
60
- if (!import.meta.env.PROD) {
61
- console.warn(`Invalid cookie "${name}"`, z.treeifyError(result.error))
62
- }
63
-
64
- return null
65
- }
66
-
67
- // ------------------------------
68
- // JSON COOKIE
69
- // ------------------------------
70
- export function getJsonCookieWithSchema<S extends z.ZodTypeAny>(
71
- cookies: AstroCookies,
72
- name: string,
73
- schema: S
74
- ): z.infer<S> | null {
75
- const entry = cookies.get(name)
76
- if (!entry) return null
77
-
78
- const raw = entry.value
79
-
80
- let parsed: unknown
81
- try {
82
- parsed = JSON.parse(raw)
83
- } catch {
84
- if (!import.meta.env.PROD) {
85
- console.warn(`Failed to parse JSON cookie "${name}"`)
86
- }
87
- return null
88
- }
89
-
90
- const result = schema.safeParse(parsed)
91
- if (result.success) return result.data as z.infer<S>
92
-
93
- if (!import.meta.env.PROD) {
94
- console.warn(`Invalid JSON cookie "${name}"`, z.treeifyError(result.error))
95
- }
96
-
97
- return null
98
- }
1
+ import type { AstroCookies } from "astro"
2
+ import { z } from "astro/zod"
3
+
4
+ import { COOKIE_TTL } from "../../config"
5
+
6
+ type AstroCookieOptions = NonNullable<Parameters<AstroCookies["set"]>[2]>
7
+
8
+ const DEFAULT_OPTIONS: AstroCookieOptions = {
9
+ path: "/",
10
+ httpOnly: true,
11
+ secure: import.meta.env.PROD,
12
+ sameSite: "lax",
13
+ maxAge: COOKIE_TTL.oneYear
14
+ }
15
+
16
+ export type SetCookieOverrides = Partial<AstroCookieOptions>
17
+
18
+ // ------------------------------
19
+ // SETTER
20
+ // ------------------------------
21
+ export function setCookie<T = string>(
22
+ cookies: AstroCookies,
23
+ name: string,
24
+ value: T,
25
+ overrides?: SetCookieOverrides
26
+ ): void {
27
+ const stringValue = typeof value === "string" ? value : JSON.stringify(value)
28
+
29
+ cookies.set(name, stringValue, {
30
+ ...DEFAULT_OPTIONS,
31
+ ...overrides
32
+ })
33
+ }
34
+
35
+ // ------------------------------
36
+ // STRING COOKIE WITH SCHEMA
37
+ // ------------------------------
38
+ export function getCookie(cookies: AstroCookies, name: string): string | null {
39
+ const entry = cookies.get(name)
40
+ if (!entry) return null
41
+ return entry.value
42
+ }
43
+
44
+ // ------------------------------
45
+ // STRING COOKIE WITH SCHEMA
46
+ // ------------------------------
47
+ export function getCookieWithSchema<S extends z.ZodTypeAny>(
48
+ cookies: AstroCookies,
49
+ name: string,
50
+ schema: S
51
+ ): z.infer<S> | null {
52
+ const entry = cookies.get(name)
53
+ if (!entry) return null
54
+
55
+ const raw = entry.value
56
+
57
+ const result = schema.safeParse(raw)
58
+ if (result.success) return result.data as z.infer<S>
59
+
60
+ if (!import.meta.env.PROD) {
61
+ console.warn(`Invalid cookie "${name}"`, z.treeifyError(result.error))
62
+ }
63
+
64
+ return null
65
+ }
66
+
67
+ // ------------------------------
68
+ // JSON COOKIE
69
+ // ------------------------------
70
+ export function getJsonCookieWithSchema<S extends z.ZodTypeAny>(
71
+ cookies: AstroCookies,
72
+ name: string,
73
+ schema: S
74
+ ): z.infer<S> | null {
75
+ const entry = cookies.get(name)
76
+ if (!entry) return null
77
+
78
+ const raw = entry.value
79
+
80
+ let parsed: unknown
81
+ try {
82
+ parsed = JSON.parse(raw)
83
+ } catch {
84
+ if (!import.meta.env.PROD) {
85
+ console.warn(`Failed to parse JSON cookie "${name}"`)
86
+ }
87
+ return null
88
+ }
89
+
90
+ const result = schema.safeParse(parsed)
91
+ if (result.success) return result.data as z.infer<S>
92
+
93
+ if (!import.meta.env.PROD) {
94
+ console.warn(`Invalid JSON cookie "${name}"`, z.treeifyError(result.error))
95
+ }
96
+
97
+ return null
98
+ }
@@ -1 +1 @@
1
- export * from "./cookies"
1
+ export * from "./cookies"
@@ -1,36 +1,36 @@
1
- function isObject(value: unknown): value is Record<string, unknown> {
2
- return typeof value === "object" && value !== null && !Array.isArray(value)
3
- }
4
-
5
- /**
6
- * Deep-merge two dictionaries. - `base` provides known structure (fallback) - `overrides` can
7
- * provide partial values - Both types are preserved and merged correctly
8
- */
9
- function assertRecord(obj: unknown): asserts obj is Record<string, unknown> {
10
- if (!obj || typeof obj !== "object") throw new Error("Expected object")
11
- }
12
-
13
- function assertA<T>(obj: unknown): asserts obj is T {
14
- if (!obj) throw new Error("Expected result")
15
- }
16
-
17
- export function deepMerge<A extends object, B extends Partial<A>>(base: A, overrides: B): A {
18
- const result = { ...base }
19
- assertRecord(result)
20
-
21
- for (const key of Object.keys(overrides)) {
22
- const overrideValue = Reflect.get(overrides, key)
23
- const baseValue = Reflect.get(base, key)
24
-
25
- if (overrideValue === undefined) continue
26
-
27
- if (isObject(baseValue) && isObject(overrideValue)) {
28
- Reflect.set(result, key, deepMerge(baseValue as object, overrideValue as Partial<object>))
29
- } else {
30
- Reflect.set(result, key, overrideValue)
31
- }
32
- }
33
-
34
- assertA<A>(result)
35
- return result
36
- }
1
+ function isObject(value: unknown): value is Record<string, unknown> {
2
+ return typeof value === "object" && value !== null && !Array.isArray(value)
3
+ }
4
+
5
+ /**
6
+ * Deep-merge two dictionaries. - `base` provides known structure (fallback) - `overrides` can
7
+ * provide partial values - Both types are preserved and merged correctly
8
+ */
9
+ function assertRecord(obj: unknown): asserts obj is Record<string, unknown> {
10
+ if (!obj || typeof obj !== "object") throw new Error("Expected object")
11
+ }
12
+
13
+ function assertA<T>(obj: unknown): asserts obj is T {
14
+ if (!obj) throw new Error("Expected result")
15
+ }
16
+
17
+ export function deepMerge<A extends object, B extends Partial<A>>(base: A, overrides: B): A {
18
+ const result = { ...base }
19
+ assertRecord(result)
20
+
21
+ for (const key of Object.keys(overrides)) {
22
+ const overrideValue = Reflect.get(overrides, key)
23
+ const baseValue = Reflect.get(base, key)
24
+
25
+ if (overrideValue === undefined) continue
26
+
27
+ if (isObject(baseValue) && isObject(overrideValue)) {
28
+ Reflect.set(result, key, deepMerge(baseValue as object, overrideValue as Partial<object>))
29
+ } else {
30
+ Reflect.set(result, key, overrideValue)
31
+ }
32
+ }
33
+
34
+ assertA<A>(result)
35
+ return result
36
+ }
@@ -1,105 +1,105 @@
1
- const PLACEHOLDER_THUMBNAIL = "/assets/placeholder.png"
2
-
3
- const YOUTUBE_EMBED_BASE = "https://www.youtube.com/embed/"
4
-
5
- const VALID_ID_PATTERN = /^[\w-]{11}$/
6
-
7
- const KNOWN_PREFIXES = ["www.", "m.", "music.", "gaming."]
8
-
9
- const YOUTUBE_THUMB_BASE = "https://i3.ytimg.com/vi/"
10
-
11
- const MAXRES_THUMBNAIL = "maxresdefault.jpg"
12
-
13
- const DEFAULT_THUMBNAIL = "hqdefault.jpg"
14
-
15
- const thumbnailAvailabilityCache = new Map<string, boolean>()
16
-
17
- function normalizeHost(host: string): string {
18
- for (const prefix of KNOWN_PREFIXES) {
19
- if (host.startsWith(prefix)) {
20
- return host.slice(prefix.length)
21
- }
22
- }
23
- return host
24
- }
25
-
26
- function sanitizeVideoId(id: string | null): string | null {
27
- if (!id) return null
28
- return VALID_ID_PATTERN.test(id) ? id : null
29
- }
30
-
31
- export function extractYoutubeVideoId(rawUrl: string): string | null {
32
- if (!rawUrl) return null
33
-
34
- try {
35
- const url = new URL(rawUrl)
36
- const host = normalizeHost(url.hostname)
37
-
38
- if (host === "youtu.be") {
39
- const id = url.pathname.split("/").find(Boolean) ?? null
40
- return sanitizeVideoId(id)
41
- }
42
-
43
- if (host.endsWith("youtube.com")) {
44
- const paramsId = sanitizeVideoId(url.searchParams.get("v"))
45
- if (paramsId) return paramsId
46
-
47
- const segments = url.pathname.split("/").filter(Boolean)
48
- if (!segments.length) return null
49
-
50
- if (segments[0] === "embed" || segments[0] === "shorts" || segments[0] === "live") {
51
- return sanitizeVideoId(segments[1] ?? null)
52
- }
53
-
54
- return sanitizeVideoId(segments[segments.length - 1] ?? null)
55
- }
56
- } catch {
57
- return null
58
- }
59
-
60
- return null
61
- }
62
-
63
- async function isThumbnailAvailable(url: string): Promise<boolean> {
64
- if (thumbnailAvailabilityCache.has(url)) {
65
- return thumbnailAvailabilityCache.get(url)!
66
- }
67
-
68
- if (typeof fetch !== "function") {
69
- return false
70
- }
71
-
72
- try {
73
- const response = await fetch(url, { method: "HEAD" })
74
- let available = response.ok
75
-
76
- if (!available && response.status === 405) {
77
- const getResponse = await fetch(url, { method: "GET" })
78
- available = getResponse.ok
79
- }
80
-
81
- thumbnailAvailabilityCache.set(url, available)
82
- return available
83
- } catch {
84
- return false
85
- }
86
- }
87
-
88
- export async function getYoutubeThumbnail(rawUrl: string): Promise<string> {
89
- const id = extractYoutubeVideoId(rawUrl)
90
- if (!id) return PLACEHOLDER_THUMBNAIL
91
-
92
- const baseUrl = `${YOUTUBE_THUMB_BASE}${id}`
93
- const maxResUrl = `${baseUrl}/${MAXRES_THUMBNAIL}`
94
-
95
- if (await isThumbnailAvailable(maxResUrl)) {
96
- return maxResUrl
97
- }
98
-
99
- return `${baseUrl}/${DEFAULT_THUMBNAIL}`
100
- }
101
-
102
- export function getYoutubeEmbedUrl(rawUrl: string): string | null {
103
- const id = extractYoutubeVideoId(rawUrl)
104
- return id ? `${YOUTUBE_EMBED_BASE}${id}?autoplay=1` : null
105
- }
1
+ const PLACEHOLDER_THUMBNAIL = "/assets/placeholder.png"
2
+
3
+ const YOUTUBE_EMBED_BASE = "https://www.youtube.com/embed/"
4
+
5
+ const VALID_ID_PATTERN = /^[\w-]{11}$/
6
+
7
+ const KNOWN_PREFIXES = ["www.", "m.", "music.", "gaming."]
8
+
9
+ const YOUTUBE_THUMB_BASE = "https://i3.ytimg.com/vi/"
10
+
11
+ const MAXRES_THUMBNAIL = "maxresdefault.jpg"
12
+
13
+ const DEFAULT_THUMBNAIL = "hqdefault.jpg"
14
+
15
+ const thumbnailAvailabilityCache = new Map<string, boolean>()
16
+
17
+ function normalizeHost(host: string): string {
18
+ for (const prefix of KNOWN_PREFIXES) {
19
+ if (host.startsWith(prefix)) {
20
+ return host.slice(prefix.length)
21
+ }
22
+ }
23
+ return host
24
+ }
25
+
26
+ function sanitizeVideoId(id: string | null): string | null {
27
+ if (!id) return null
28
+ return VALID_ID_PATTERN.test(id) ? id : null
29
+ }
30
+
31
+ export function extractYoutubeVideoId(rawUrl: string): string | null {
32
+ if (!rawUrl) return null
33
+
34
+ try {
35
+ const url = new URL(rawUrl)
36
+ const host = normalizeHost(url.hostname)
37
+
38
+ if (host === "youtu.be") {
39
+ const id = url.pathname.split("/").find(Boolean) ?? null
40
+ return sanitizeVideoId(id)
41
+ }
42
+
43
+ if (host.endsWith("youtube.com")) {
44
+ const paramsId = sanitizeVideoId(url.searchParams.get("v"))
45
+ if (paramsId) return paramsId
46
+
47
+ const segments = url.pathname.split("/").filter(Boolean)
48
+ if (!segments.length) return null
49
+
50
+ if (segments[0] === "embed" || segments[0] === "shorts" || segments[0] === "live") {
51
+ return sanitizeVideoId(segments[1] ?? null)
52
+ }
53
+
54
+ return sanitizeVideoId(segments[segments.length - 1] ?? null)
55
+ }
56
+ } catch {
57
+ return null
58
+ }
59
+
60
+ return null
61
+ }
62
+
63
+ async function isThumbnailAvailable(url: string): Promise<boolean> {
64
+ if (thumbnailAvailabilityCache.has(url)) {
65
+ return thumbnailAvailabilityCache.get(url)!
66
+ }
67
+
68
+ if (typeof fetch !== "function") {
69
+ return false
70
+ }
71
+
72
+ try {
73
+ const response = await fetch(url, { method: "HEAD" })
74
+ let available = response.ok
75
+
76
+ if (!available && response.status === 405) {
77
+ const getResponse = await fetch(url, { method: "GET" })
78
+ available = getResponse.ok
79
+ }
80
+
81
+ thumbnailAvailabilityCache.set(url, available)
82
+ return available
83
+ } catch {
84
+ return false
85
+ }
86
+ }
87
+
88
+ export async function getYoutubeThumbnail(rawUrl: string): Promise<string> {
89
+ const id = extractYoutubeVideoId(rawUrl)
90
+ if (!id) return PLACEHOLDER_THUMBNAIL
91
+
92
+ const baseUrl = `${YOUTUBE_THUMB_BASE}${id}`
93
+ const maxResUrl = `${baseUrl}/${MAXRES_THUMBNAIL}`
94
+
95
+ if (await isThumbnailAvailable(maxResUrl)) {
96
+ return maxResUrl
97
+ }
98
+
99
+ return `${baseUrl}/${DEFAULT_THUMBNAIL}`
100
+ }
101
+
102
+ export function getYoutubeEmbedUrl(rawUrl: string): string | null {
103
+ const id = extractYoutubeVideoId(rawUrl)
104
+ return id ? `${YOUTUBE_EMBED_BASE}${id}?autoplay=1` : null
105
+ }
@@ -1,3 +1,3 @@
1
- export * from "./parse-boolean"
2
- export * from "./deep-merge"
3
- export * from "./embed"
1
+ export * from "./parse-boolean"
2
+ export * from "./deep-merge"
3
+ export * from "./embed"
@@ -1,14 +1,14 @@
1
- /**
2
- * Parse a boolean flag from Wrangler-env string.
3
- *
4
- * Accepts: "1", "true", "on" → true "0", "false", "off" → false undefined → defaultValue
5
- */
6
- export function parseBooleanFlag(value: string | undefined, defaultValue: boolean): boolean {
7
- if (value === undefined) return defaultValue
8
-
9
- const v = value.toLowerCase()
10
- if (v === "1" || v === "true" || v === "on") return true
11
- if (v === "0" || v === "false" || v === "off") return false
12
-
13
- return defaultValue
14
- }
1
+ /**
2
+ * Parse a boolean flag from Wrangler-env string.
3
+ *
4
+ * Accepts: "1", "true", "on" → true "0", "false", "off" → false undefined → defaultValue
5
+ */
6
+ export function parseBooleanFlag(value: string | undefined, defaultValue: boolean): boolean {
7
+ if (value === undefined) return defaultValue
8
+
9
+ const v = value.toLowerCase()
10
+ if (v === "1" || v === "true" || v === "on") return true
11
+ if (v === "0" || v === "false" || v === "off") return false
12
+
13
+ return defaultValue
14
+ }