@decocms/apps-website 7.2.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/src/types.ts ADDED
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Core types for the website app.
3
+ *
4
+ * Defines the matcher/flag system types locally so we don't depend on
5
+ * @deco/deco/blocks — everything is self-contained in @decocms/apps.
6
+ */
7
+
8
+ // -------------------------------------------------------------------------
9
+ // Script
10
+ // -------------------------------------------------------------------------
11
+
12
+ export type Script = { src: string | ((req: Request) => string) };
13
+
14
+ // -------------------------------------------------------------------------
15
+ // Matcher system
16
+ // -------------------------------------------------------------------------
17
+
18
+ /**
19
+ * Context passed to matchers at request time.
20
+ * The framework populates this from the incoming request.
21
+ */
22
+ export interface MatchContext {
23
+ request: Request;
24
+ device: "mobile" | "tablet" | "desktop";
25
+ siteId: number;
26
+ }
27
+
28
+ /**
29
+ * A matcher is a function that evaluates request context and returns a boolean.
30
+ */
31
+ export type Matcher = (ctx: MatchContext) => boolean;
32
+
33
+ // -------------------------------------------------------------------------
34
+ // Flag system
35
+ // -------------------------------------------------------------------------
36
+
37
+ /**
38
+ * A feature flag with a matcher and two branches.
39
+ * The framework evaluates the matcher at request time and selects the
40
+ * appropriate branch value.
41
+ */
42
+ export interface FlagObj<T> {
43
+ matcher: Matcher;
44
+ true: T;
45
+ false: T;
46
+ name: string;
47
+ }
48
+
49
+ /**
50
+ * A multivariate flag with multiple variants, each with its own matcher.
51
+ */
52
+ export interface MultivariateFlag<T> {
53
+ variants: Variant<T>[];
54
+ }
55
+
56
+ /**
57
+ * A single variant in a multivariate flag.
58
+ */
59
+ export interface Variant<T> {
60
+ matcher?: Matcher;
61
+ value: T;
62
+ weight?: number;
63
+ }
64
+
65
+ // -------------------------------------------------------------------------
66
+ // Theme / Font types
67
+ // -------------------------------------------------------------------------
68
+
69
+ export interface Variable {
70
+ name: string;
71
+ value: string;
72
+ }
73
+
74
+ export type Font = {
75
+ family: string;
76
+ styleSheet: string;
77
+ };
78
+
79
+ // -------------------------------------------------------------------------
80
+ // SEO types
81
+ // -------------------------------------------------------------------------
82
+
83
+ /** @description Recommended: 1200 x 630 px (up to 5MB) */
84
+ export type ImageWidget = string;
85
+
86
+ export type OGType = "website" | "article";
87
+
88
+ export interface SeoConfig {
89
+ title?: string;
90
+ /**
91
+ * @title Title template
92
+ * @description add a %s whenever you want it to be replaced with the product name, category name or search term
93
+ * @default %s
94
+ */
95
+ titleTemplate?: string;
96
+ description?: string;
97
+ /**
98
+ * @title Description template
99
+ * @description add a %s whenever you want it to be replaced with the product name, category name or search term
100
+ * @default %s
101
+ */
102
+ descriptionTemplate?: string;
103
+ /** @default website */
104
+ type?: OGType;
105
+ /** @description Recommended: 1200 x 630 px (up to 5MB) */
106
+ image?: ImageWidget;
107
+ /** @description Recommended: 16 x 16 px */
108
+ favicon?: ImageWidget;
109
+ /** @description Suggested color that browsers should use to customize the display */
110
+ themeColor?: string;
111
+ /**
112
+ * @title Disable indexing
113
+ * @description In testing, you can use this to prevent search engines from indexing your site
114
+ */
115
+ noIndexing?: boolean;
116
+ }
117
+
118
+ // -------------------------------------------------------------------------
119
+ // Website app config
120
+ // -------------------------------------------------------------------------
121
+
122
+ export interface WebsiteConfig {
123
+ /** @title Seo */
124
+ seo?: SeoConfig;
125
+ }
@@ -0,0 +1,24 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { stripHTML } from "./html";
3
+
4
+ describe("stripHTML", () => {
5
+ it("strips HTML tags from string", () => {
6
+ expect(stripHTML("<p>Hello <strong>World</strong></p>")).toBe("Hello World");
7
+ });
8
+
9
+ it("returns plain text unchanged", () => {
10
+ expect(stripHTML("no tags here")).toBe("no tags here");
11
+ });
12
+
13
+ it("handles empty string", () => {
14
+ expect(stripHTML("")).toBe("");
15
+ });
16
+
17
+ it("strips self-closing tags", () => {
18
+ expect(stripHTML("Hello<br/>World")).toBe("HelloWorld");
19
+ });
20
+
21
+ it("strips nested tags", () => {
22
+ expect(stripHTML("<div><p><span>text</span></p></div>")).toBe("text");
23
+ });
24
+ });
@@ -0,0 +1 @@
1
+ export const stripHTML = (str: string) => str.replace(/(<([^>]+)>)/gi, "");
@@ -0,0 +1,40 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { haversine, toRadians } from "./location";
3
+
4
+ describe("toRadians", () => {
5
+ it("converts 0 degrees to 0 radians", () => {
6
+ expect(toRadians(0)).toBe(0);
7
+ });
8
+
9
+ it("converts 180 degrees to PI radians", () => {
10
+ expect(toRadians(180)).toBeCloseTo(Math.PI);
11
+ });
12
+
13
+ it("converts 90 degrees to PI/2 radians", () => {
14
+ expect(toRadians(90)).toBeCloseTo(Math.PI / 2);
15
+ });
16
+ });
17
+
18
+ describe("haversine", () => {
19
+ it("returns 0 for same coordinates", () => {
20
+ expect(haversine("-23.5505,-46.6333", "-23.5505,-46.6333")).toBeCloseTo(0, 0);
21
+ });
22
+
23
+ it("calculates distance between São Paulo and Rio (~360km)", () => {
24
+ const sp = "-23.5505,-46.6333";
25
+ const rj = "-22.9068,-43.1729";
26
+ const distance = haversine(sp, rj);
27
+ // ~360km
28
+ expect(distance).toBeGreaterThan(350000);
29
+ expect(distance).toBeLessThan(380000);
30
+ });
31
+
32
+ it("calculates distance between New York and London (~5570km)", () => {
33
+ const ny = "40.7128,-74.0060";
34
+ const london = "51.5074,-0.1278";
35
+ const distance = haversine(ny, london);
36
+ // ~5570km
37
+ expect(distance).toBeGreaterThan(5500000);
38
+ expect(distance).toBeLessThan(5700000);
39
+ });
40
+ });
@@ -0,0 +1,20 @@
1
+ export const toRadians = (degrees: number): number => {
2
+ return degrees * (Math.PI / 180);
3
+ };
4
+
5
+ export const haversine = (coordinate1: string, coordinate2: string): number => {
6
+ const [lat1, lng1] = coordinate1.split(",").map((value) => Number(value));
7
+ const [lat2, lng2] = coordinate2.split(",").map((value) => Number(value));
8
+
9
+ const earthRadius = 6371000; // Earth radius in meters
10
+ const dLat = toRadians(lat1 - lat2);
11
+ const dLon = toRadians(lng1 - lng2);
12
+
13
+ const halfChordLengthSquared =
14
+ Math.sin(dLat / 2) * Math.sin(dLat / 2) +
15
+ Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(toRadians(lat1)) * Math.cos(toRadians(lat2));
16
+
17
+ const angularDistance =
18
+ 2 * Math.atan2(Math.sqrt(halfChordLengthSquared), Math.sqrt(1 - halfChordLengthSquared));
19
+ return earthRadius * angularDistance;
20
+ };
@@ -0,0 +1,20 @@
1
+ import type { MultivariateFlag, Variant } from "../types";
2
+
3
+ /**
4
+ * @title Multivariate
5
+ */
6
+ export interface MultivariateProps<T> {
7
+ /**
8
+ * @minItems 1
9
+ * @addBehavior 1
10
+ */
11
+ variants: Variant<T>[];
12
+ }
13
+
14
+ /**
15
+ * @title Variant
16
+ * @label hidden
17
+ */
18
+ export default function multivariate<T>(props: MultivariateProps<T>): MultivariateFlag<T> {
19
+ return props;
20
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist"
5
+ },
6
+ "include": ["src/**/*"]
7
+ }