@momentumcms/plugins-analytics 0.3.0 → 0.4.1

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.
@@ -0,0 +1,206 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // libs/plugins/analytics/src/lib/collectors/block-field-injector.ts
21
+ var block_field_injector_exports = {};
22
+ __export(block_field_injector_exports, {
23
+ injectBlockAnalyticsFields: () => injectBlockAnalyticsFields
24
+ });
25
+ module.exports = __toCommonJS(block_field_injector_exports);
26
+
27
+ // libs/core/src/lib/collections/define-collection.ts
28
+ function defineCollection(config) {
29
+ const collection = {
30
+ timestamps: true,
31
+ // Enable timestamps by default
32
+ ...config
33
+ };
34
+ if (!collection.slug) {
35
+ throw new Error("Collection must have a slug");
36
+ }
37
+ if (!collection.fields || collection.fields.length === 0) {
38
+ throw new Error(`Collection "${collection.slug}" must have at least one field`);
39
+ }
40
+ if (!/^[a-z][a-z0-9-]*$/.test(collection.slug)) {
41
+ throw new Error(
42
+ `Collection slug "${collection.slug}" must be kebab-case (lowercase letters, numbers, and hyphens, starting with a letter)`
43
+ );
44
+ }
45
+ return collection;
46
+ }
47
+
48
+ // libs/core/src/lib/fields/field-builders.ts
49
+ function text(name, options = {}) {
50
+ return {
51
+ name,
52
+ type: "text",
53
+ ...options
54
+ };
55
+ }
56
+ function number(name, options = {}) {
57
+ return {
58
+ name,
59
+ type: "number",
60
+ ...options
61
+ };
62
+ }
63
+ function checkbox(name, options = {}) {
64
+ return {
65
+ name,
66
+ type: "checkbox",
67
+ ...options,
68
+ defaultValue: options.defaultValue ?? false
69
+ };
70
+ }
71
+ function group(name, options) {
72
+ return {
73
+ name,
74
+ type: "group",
75
+ ...options
76
+ };
77
+ }
78
+ function json(name, options = {}) {
79
+ return {
80
+ name,
81
+ type: "json",
82
+ ...options
83
+ };
84
+ }
85
+
86
+ // libs/core/src/lib/collections/media.collection.ts
87
+ var MediaCollection = defineCollection({
88
+ slug: "media",
89
+ labels: {
90
+ singular: "Media",
91
+ plural: "Media"
92
+ },
93
+ upload: {
94
+ mimeTypes: ["image/*", "application/pdf", "video/*", "audio/*"]
95
+ },
96
+ admin: {
97
+ useAsTitle: "filename",
98
+ defaultColumns: ["filename", "mimeType", "filesize", "createdAt"]
99
+ },
100
+ fields: [
101
+ text("filename", {
102
+ required: true,
103
+ label: "Filename",
104
+ description: "Original filename of the uploaded file"
105
+ }),
106
+ text("mimeType", {
107
+ required: true,
108
+ label: "MIME Type",
109
+ description: "File MIME type (e.g., image/jpeg, application/pdf)"
110
+ }),
111
+ number("filesize", {
112
+ label: "File Size",
113
+ description: "File size in bytes"
114
+ }),
115
+ text("path", {
116
+ label: "Storage Path",
117
+ description: "Path/key where the file is stored",
118
+ admin: {
119
+ hidden: true
120
+ }
121
+ }),
122
+ text("url", {
123
+ label: "URL",
124
+ description: "Public URL to access the file"
125
+ }),
126
+ text("alt", {
127
+ label: "Alt Text",
128
+ description: "Alternative text for accessibility"
129
+ }),
130
+ number("width", {
131
+ label: "Width",
132
+ description: "Image width in pixels (for images only)"
133
+ }),
134
+ number("height", {
135
+ label: "Height",
136
+ description: "Image height in pixels (for images only)"
137
+ }),
138
+ json("focalPoint", {
139
+ label: "Focal Point",
140
+ description: "Focal point coordinates for image cropping",
141
+ admin: {
142
+ hidden: true
143
+ }
144
+ })
145
+ ],
146
+ access: {
147
+ // Media is readable by anyone by default
148
+ read: () => true,
149
+ // Only authenticated users can create/update/delete
150
+ create: ({ req }) => !!req?.user,
151
+ update: ({ req }) => !!req?.user,
152
+ delete: ({ req }) => !!req?.user
153
+ }
154
+ });
155
+
156
+ // libs/plugins/analytics/src/lib/collectors/block-field-injector.ts
157
+ function createAnalyticsGroupField() {
158
+ return group("_analytics", {
159
+ label: "Analytics",
160
+ admin: { collapsible: true, defaultOpen: false },
161
+ fields: [
162
+ checkbox("trackImpressions", { label: "Track Impressions" }),
163
+ checkbox("trackHover", { label: "Track Hover" })
164
+ ]
165
+ });
166
+ }
167
+ function hasAnalyticsField(fields) {
168
+ return fields.some((f) => f.name === "_analytics" && f.type === "group");
169
+ }
170
+ function hasBlocks(field) {
171
+ return field.type === "blocks" && "blocks" in field;
172
+ }
173
+ function hasNestedFields(field) {
174
+ return "fields" in field && Array.isArray(field.fields);
175
+ }
176
+ function hasTabs(field) {
177
+ return field.type === "tabs" && "tabs" in field;
178
+ }
179
+ function injectIntoFields(fields) {
180
+ for (const field of fields) {
181
+ if (hasBlocks(field)) {
182
+ for (const blockConfig of field.blocks) {
183
+ if (!hasAnalyticsField(blockConfig.fields)) {
184
+ blockConfig.fields.push(createAnalyticsGroupField());
185
+ }
186
+ }
187
+ }
188
+ if ((field.type === "group" || field.type === "array" || field.type === "collapsible" || field.type === "row") && hasNestedFields(field)) {
189
+ injectIntoFields(field.fields);
190
+ }
191
+ if (hasTabs(field)) {
192
+ for (const tab of field.tabs) {
193
+ injectIntoFields(tab.fields);
194
+ }
195
+ }
196
+ }
197
+ }
198
+ function injectBlockAnalyticsFields(collections) {
199
+ for (const collection of collections) {
200
+ injectIntoFields(collection.fields);
201
+ }
202
+ }
203
+ // Annotate the CommonJS export names for ESM import in node:
204
+ 0 && (module.exports = {
205
+ injectBlockAnalyticsFields
206
+ });
@@ -0,0 +1,179 @@
1
+ // libs/core/src/lib/collections/define-collection.ts
2
+ function defineCollection(config) {
3
+ const collection = {
4
+ timestamps: true,
5
+ // Enable timestamps by default
6
+ ...config
7
+ };
8
+ if (!collection.slug) {
9
+ throw new Error("Collection must have a slug");
10
+ }
11
+ if (!collection.fields || collection.fields.length === 0) {
12
+ throw new Error(`Collection "${collection.slug}" must have at least one field`);
13
+ }
14
+ if (!/^[a-z][a-z0-9-]*$/.test(collection.slug)) {
15
+ throw new Error(
16
+ `Collection slug "${collection.slug}" must be kebab-case (lowercase letters, numbers, and hyphens, starting with a letter)`
17
+ );
18
+ }
19
+ return collection;
20
+ }
21
+
22
+ // libs/core/src/lib/fields/field-builders.ts
23
+ function text(name, options = {}) {
24
+ return {
25
+ name,
26
+ type: "text",
27
+ ...options
28
+ };
29
+ }
30
+ function number(name, options = {}) {
31
+ return {
32
+ name,
33
+ type: "number",
34
+ ...options
35
+ };
36
+ }
37
+ function checkbox(name, options = {}) {
38
+ return {
39
+ name,
40
+ type: "checkbox",
41
+ ...options,
42
+ defaultValue: options.defaultValue ?? false
43
+ };
44
+ }
45
+ function group(name, options) {
46
+ return {
47
+ name,
48
+ type: "group",
49
+ ...options
50
+ };
51
+ }
52
+ function json(name, options = {}) {
53
+ return {
54
+ name,
55
+ type: "json",
56
+ ...options
57
+ };
58
+ }
59
+
60
+ // libs/core/src/lib/collections/media.collection.ts
61
+ var MediaCollection = defineCollection({
62
+ slug: "media",
63
+ labels: {
64
+ singular: "Media",
65
+ plural: "Media"
66
+ },
67
+ upload: {
68
+ mimeTypes: ["image/*", "application/pdf", "video/*", "audio/*"]
69
+ },
70
+ admin: {
71
+ useAsTitle: "filename",
72
+ defaultColumns: ["filename", "mimeType", "filesize", "createdAt"]
73
+ },
74
+ fields: [
75
+ text("filename", {
76
+ required: true,
77
+ label: "Filename",
78
+ description: "Original filename of the uploaded file"
79
+ }),
80
+ text("mimeType", {
81
+ required: true,
82
+ label: "MIME Type",
83
+ description: "File MIME type (e.g., image/jpeg, application/pdf)"
84
+ }),
85
+ number("filesize", {
86
+ label: "File Size",
87
+ description: "File size in bytes"
88
+ }),
89
+ text("path", {
90
+ label: "Storage Path",
91
+ description: "Path/key where the file is stored",
92
+ admin: {
93
+ hidden: true
94
+ }
95
+ }),
96
+ text("url", {
97
+ label: "URL",
98
+ description: "Public URL to access the file"
99
+ }),
100
+ text("alt", {
101
+ label: "Alt Text",
102
+ description: "Alternative text for accessibility"
103
+ }),
104
+ number("width", {
105
+ label: "Width",
106
+ description: "Image width in pixels (for images only)"
107
+ }),
108
+ number("height", {
109
+ label: "Height",
110
+ description: "Image height in pixels (for images only)"
111
+ }),
112
+ json("focalPoint", {
113
+ label: "Focal Point",
114
+ description: "Focal point coordinates for image cropping",
115
+ admin: {
116
+ hidden: true
117
+ }
118
+ })
119
+ ],
120
+ access: {
121
+ // Media is readable by anyone by default
122
+ read: () => true,
123
+ // Only authenticated users can create/update/delete
124
+ create: ({ req }) => !!req?.user,
125
+ update: ({ req }) => !!req?.user,
126
+ delete: ({ req }) => !!req?.user
127
+ }
128
+ });
129
+
130
+ // libs/plugins/analytics/src/lib/collectors/block-field-injector.ts
131
+ function createAnalyticsGroupField() {
132
+ return group("_analytics", {
133
+ label: "Analytics",
134
+ admin: { collapsible: true, defaultOpen: false },
135
+ fields: [
136
+ checkbox("trackImpressions", { label: "Track Impressions" }),
137
+ checkbox("trackHover", { label: "Track Hover" })
138
+ ]
139
+ });
140
+ }
141
+ function hasAnalyticsField(fields) {
142
+ return fields.some((f) => f.name === "_analytics" && f.type === "group");
143
+ }
144
+ function hasBlocks(field) {
145
+ return field.type === "blocks" && "blocks" in field;
146
+ }
147
+ function hasNestedFields(field) {
148
+ return "fields" in field && Array.isArray(field.fields);
149
+ }
150
+ function hasTabs(field) {
151
+ return field.type === "tabs" && "tabs" in field;
152
+ }
153
+ function injectIntoFields(fields) {
154
+ for (const field of fields) {
155
+ if (hasBlocks(field)) {
156
+ for (const blockConfig of field.blocks) {
157
+ if (!hasAnalyticsField(blockConfig.fields)) {
158
+ blockConfig.fields.push(createAnalyticsGroupField());
159
+ }
160
+ }
161
+ }
162
+ if ((field.type === "group" || field.type === "array" || field.type === "collapsible" || field.type === "row") && hasNestedFields(field)) {
163
+ injectIntoFields(field.fields);
164
+ }
165
+ if (hasTabs(field)) {
166
+ for (const tab of field.tabs) {
167
+ injectIntoFields(tab.fields);
168
+ }
169
+ }
170
+ }
171
+ }
172
+ function injectBlockAnalyticsFields(collections) {
173
+ for (const collection of collections) {
174
+ injectIntoFields(collection.fields);
175
+ }
176
+ }
177
+ export {
178
+ injectBlockAnalyticsFields
179
+ };
@@ -0,0 +1,208 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var __decorateClass = (decorators, target, key, kind) => {
20
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
21
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
22
+ if (decorator = decorators[i])
23
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
24
+ if (kind && result)
25
+ __defProp(target, key, result);
26
+ return result;
27
+ };
28
+
29
+ // libs/plugins/analytics/src/lib/page-view-tracker.ts
30
+ var page_view_tracker_exports = {};
31
+ __export(page_view_tracker_exports, {
32
+ DEFAULT_EXCLUDE_PREFIXES: () => DEFAULT_EXCLUDE_PREFIXES,
33
+ PAGE_VIEW_TRACKING_CONFIG: () => PAGE_VIEW_TRACKING_CONFIG,
34
+ PageViewTrackerService: () => PageViewTrackerService,
35
+ buildPageViewEvent: () => buildPageViewEvent,
36
+ providePageViewTracking: () => providePageViewTracking,
37
+ shouldTrackNavigation: () => shouldTrackNavigation
38
+ });
39
+ module.exports = __toCommonJS(page_view_tracker_exports);
40
+ var import_core = require("@angular/core");
41
+ var import_common = require("@angular/common");
42
+ var import_router = require("@angular/router");
43
+ var import_http = require("@angular/common/http");
44
+ var import_rxjs = require("rxjs");
45
+
46
+ // libs/plugins/analytics/src/lib/utils/content-route-matcher.ts
47
+ function escapeRegex(str) {
48
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
49
+ }
50
+ function compileContentRoute(collection, pattern) {
51
+ const segments = pattern.split("/").filter(Boolean);
52
+ const paramNames = [];
53
+ let staticCount = 0;
54
+ const regexParts = segments.map((seg) => {
55
+ if (seg.startsWith(":")) {
56
+ paramNames.push(seg.slice(1));
57
+ return "([^/]+)";
58
+ }
59
+ staticCount++;
60
+ return escapeRegex(seg);
61
+ });
62
+ const regexStr = "^/" + regexParts.join("/") + "/?$";
63
+ return {
64
+ collection,
65
+ pattern,
66
+ regex: new RegExp(regexStr),
67
+ paramNames,
68
+ staticSegments: staticCount
69
+ };
70
+ }
71
+ function compileContentRoutes(routes) {
72
+ const compiled = Object.entries(routes).map(
73
+ ([collection, pattern]) => compileContentRoute(collection, pattern)
74
+ );
75
+ compiled.sort((a, b) => b.staticSegments - a.staticSegments);
76
+ return compiled;
77
+ }
78
+ function matchContentRoute(path, routes) {
79
+ for (const route of routes) {
80
+ const match = route.regex.exec(path);
81
+ if (match) {
82
+ const params = {};
83
+ for (let i = 0; i < route.paramNames.length; i++) {
84
+ params[route.paramNames[i]] = match[i + 1];
85
+ }
86
+ return { collection: route.collection, params };
87
+ }
88
+ }
89
+ return void 0;
90
+ }
91
+
92
+ // libs/plugins/analytics/src/lib/page-view-tracker.utils.ts
93
+ var DEFAULT_EXCLUDE_PREFIXES = ["/admin", "/api/"];
94
+ function shouldTrackNavigation(url, isFirstNavigation, excludePrefixes) {
95
+ if (isFirstNavigation)
96
+ return false;
97
+ const path = url.split("?")[0].split("#")[0];
98
+ for (const prefix of excludePrefixes) {
99
+ if (path.startsWith(prefix))
100
+ return false;
101
+ }
102
+ return true;
103
+ }
104
+ function buildPageViewEvent(url, compiledRoutes) {
105
+ const path = url.split("?")[0].split("#")[0];
106
+ const properties = { path };
107
+ if (compiledRoutes) {
108
+ const routeMatch = matchContentRoute(path, compiledRoutes);
109
+ if (routeMatch) {
110
+ properties["collection"] = routeMatch.collection;
111
+ properties["slug"] = routeMatch.params["slug"];
112
+ }
113
+ }
114
+ return {
115
+ name: "page_view",
116
+ category: "page",
117
+ properties
118
+ };
119
+ }
120
+
121
+ // libs/plugins/analytics/src/lib/page-view-tracker.ts
122
+ var PAGE_VIEW_TRACKING_CONFIG = new import_core.InjectionToken(
123
+ "PAGE_VIEW_TRACKING_CONFIG"
124
+ );
125
+ function getOrCreateStorageId(storage, key) {
126
+ let id = storage.getItem(key);
127
+ if (!id) {
128
+ id = Date.now().toString(36) + Math.random().toString(36).slice(2, 10);
129
+ storage.setItem(key, id);
130
+ }
131
+ return id;
132
+ }
133
+ var PageViewTrackerService = class {
134
+ constructor() {
135
+ this.router = (0, import_core.inject)(import_router.Router);
136
+ this.http = (0, import_core.inject)(import_http.HttpClient);
137
+ this.platformId = (0, import_core.inject)(import_core.PLATFORM_ID);
138
+ this.destroyRef = (0, import_core.inject)(import_core.DestroyRef);
139
+ this.config = (0, import_core.inject)(PAGE_VIEW_TRACKING_CONFIG);
140
+ this.doc = (0, import_core.inject)(import_common.DOCUMENT);
141
+ this.isFirstNavigation = true;
142
+ this.endpoint = this.config.endpoint ?? "/api/analytics/collect";
143
+ this.compiledRoutes = this.config.contentRoutes ? compileContentRoutes(this.config.contentRoutes) : void 0;
144
+ this.excludePrefixes = this.config.excludePrefixes ? [...DEFAULT_EXCLUDE_PREFIXES, ...this.config.excludePrefixes] : DEFAULT_EXCLUDE_PREFIXES;
145
+ if (!(0, import_common.isPlatformBrowser)(this.platformId))
146
+ return;
147
+ const sub = this.router.events.pipe((0, import_rxjs.filter)((e) => e instanceof import_router.NavigationEnd)).subscribe((event) => {
148
+ const url = event.urlAfterRedirects;
149
+ if (!shouldTrackNavigation(url, this.isFirstNavigation, this.excludePrefixes)) {
150
+ this.isFirstNavigation = false;
151
+ return;
152
+ }
153
+ this.isFirstNavigation = false;
154
+ this.trackPageView(url);
155
+ });
156
+ this.destroyRef.onDestroy(() => sub.unsubscribe());
157
+ }
158
+ trackPageView(url) {
159
+ const eventPayload = buildPageViewEvent(url, this.compiledRoutes);
160
+ const win = this.doc.defaultView;
161
+ let visitorId;
162
+ let sessionId;
163
+ try {
164
+ if (win?.localStorage)
165
+ visitorId = getOrCreateStorageId(win.localStorage, "_m_vid");
166
+ if (win?.sessionStorage)
167
+ sessionId = getOrCreateStorageId(win.sessionStorage, "_m_sid");
168
+ } catch {
169
+ }
170
+ const clientEvent = {
171
+ ...eventPayload,
172
+ context: {
173
+ url: win?.location?.href ?? "",
174
+ referrer: this.doc.referrer
175
+ },
176
+ visitorId,
177
+ sessionId
178
+ };
179
+ this.http.post(this.endpoint, { events: [clientEvent] }).subscribe();
180
+ }
181
+ };
182
+ PageViewTrackerService = __decorateClass([
183
+ (0, import_core.Injectable)()
184
+ ], PageViewTrackerService);
185
+ function providePageViewTracking(config) {
186
+ return (0, import_core.makeEnvironmentProviders)([
187
+ { provide: PAGE_VIEW_TRACKING_CONFIG, useValue: config },
188
+ PageViewTrackerService,
189
+ {
190
+ provide: import_core.ENVIRONMENT_INITIALIZER,
191
+ multi: true,
192
+ useFactory: () => {
193
+ (0, import_core.inject)(PageViewTrackerService);
194
+ return () => {
195
+ };
196
+ }
197
+ }
198
+ ]);
199
+ }
200
+ // Annotate the CommonJS export names for ESM import in node:
201
+ 0 && (module.exports = {
202
+ DEFAULT_EXCLUDE_PREFIXES,
203
+ PAGE_VIEW_TRACKING_CONFIG,
204
+ PageViewTrackerService,
205
+ buildPageViewEvent,
206
+ providePageViewTracking,
207
+ shouldTrackNavigation
208
+ });