@gogitcms/editor 0.23.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 (143) hide show
  1. package/app/index.html +38 -0
  2. package/app/src/App.tsx +2665 -0
  3. package/app/src/apollo.ts +119 -0
  4. package/app/src/auth.ts +248 -0
  5. package/app/src/config.ts +88 -0
  6. package/app/src/main.tsx +18 -0
  7. package/app/src/media.ts +151 -0
  8. package/app/src/navigation.tsx +321 -0
  9. package/app/src/plugins.ts +11 -0
  10. package/app/src/previewHost.tsx +263 -0
  11. package/app/src/previewToken.ts +68 -0
  12. package/app/src/queries.ts +591 -0
  13. package/app/src/virtual-cms-plugins.d.ts +6 -0
  14. package/app/vendor/analytics/src/__tests__/config.test.ts +91 -0
  15. package/app/vendor/analytics/src/config.ts +94 -0
  16. package/app/vendor/analytics/src/events.ts +56 -0
  17. package/app/vendor/analytics/src/index.ts +23 -0
  18. package/app/vendor/analytics/src/provider.tsx +196 -0
  19. package/app/vendor/design-system/src/ThemeProvider.tsx +86 -0
  20. package/app/vendor/design-system/src/__tests__/ApplyChangesModal.test.tsx +148 -0
  21. package/app/vendor/design-system/src/__tests__/BranchImport.test.tsx +46 -0
  22. package/app/vendor/design-system/src/__tests__/Button.test.tsx +45 -0
  23. package/app/vendor/design-system/src/__tests__/ChangeRequestSummary.test.tsx +57 -0
  24. package/app/vendor/design-system/src/__tests__/ContentBrowser.changes.test.tsx +611 -0
  25. package/app/vendor/design-system/src/__tests__/ContentBrowser.collab.test.tsx +322 -0
  26. package/app/vendor/design-system/src/__tests__/ContentBrowser.collabsync.test.tsx +264 -0
  27. package/app/vendor/design-system/src/__tests__/ContentBrowser.contentslot.test.tsx +53 -0
  28. package/app/vendor/design-system/src/__tests__/ContentBrowser.discriminator.test.tsx +142 -0
  29. package/app/vendor/design-system/src/__tests__/ContentBrowser.drafts.test.tsx +271 -0
  30. package/app/vendor/design-system/src/__tests__/ContentBrowser.fields.test.tsx +117 -0
  31. package/app/vendor/design-system/src/__tests__/ContentBrowser.media.test.tsx +140 -0
  32. package/app/vendor/design-system/src/__tests__/ContentBrowser.mixedcollab.test.tsx +63 -0
  33. package/app/vendor/design-system/src/__tests__/ContentBrowser.mixedvalues.test.tsx +38 -0
  34. package/app/vendor/design-system/src/__tests__/ContentBrowser.pagination.test.tsx +62 -0
  35. package/app/vendor/design-system/src/__tests__/ContentBrowser.previewtab.test.tsx +212 -0
  36. package/app/vendor/design-system/src/__tests__/ContentBrowser.reorder.test.tsx +45 -0
  37. package/app/vendor/design-system/src/__tests__/ContentBrowser.search.test.tsx +135 -0
  38. package/app/vendor/design-system/src/__tests__/ContentBrowser.selectvalue.test.tsx +185 -0
  39. package/app/vendor/design-system/src/__tests__/ContentBrowser.staged.test.tsx +132 -0
  40. package/app/vendor/design-system/src/__tests__/ContentBrowser.usermenu.test.tsx +56 -0
  41. package/app/vendor/design-system/src/__tests__/MediaBrowser.test.tsx +353 -0
  42. package/app/vendor/design-system/src/__tests__/MediaField.test.tsx +185 -0
  43. package/app/vendor/design-system/src/__tests__/Notifications.test.tsx +69 -0
  44. package/app/vendor/design-system/src/__tests__/Onboarding.test.tsx +287 -0
  45. package/app/vendor/design-system/src/__tests__/cssTokens.test.ts +201 -0
  46. package/app/vendor/design-system/src/__tests__/fieldComponents.test.ts +43 -0
  47. package/app/vendor/design-system/src/__tests__/reorder.test.ts +58 -0
  48. package/app/vendor/design-system/src/components/ApplyChangesModal.tsx +348 -0
  49. package/app/vendor/design-system/src/components/BranchImport.tsx +157 -0
  50. package/app/vendor/design-system/src/components/BranchMenu.tsx +192 -0
  51. package/app/vendor/design-system/src/components/Button.tsx +131 -0
  52. package/app/vendor/design-system/src/components/ChangeDetail.tsx +472 -0
  53. package/app/vendor/design-system/src/components/ChangeRequestSummary.tsx +173 -0
  54. package/app/vendor/design-system/src/components/CollabField.tsx +388 -0
  55. package/app/vendor/design-system/src/components/ContentBrowser.tsx +5073 -0
  56. package/app/vendor/design-system/src/components/Icon.tsx +28 -0
  57. package/app/vendor/design-system/src/components/Icon.web.tsx +31 -0
  58. package/app/vendor/design-system/src/components/Input.tsx +106 -0
  59. package/app/vendor/design-system/src/components/MediaBrowser.tsx +766 -0
  60. package/app/vendor/design-system/src/components/MediaField.tsx +670 -0
  61. package/app/vendor/design-system/src/components/MediaPreview.tsx +91 -0
  62. package/app/vendor/design-system/src/components/MediaPreview.web.tsx +169 -0
  63. package/app/vendor/design-system/src/components/NavRow.tsx +105 -0
  64. package/app/vendor/design-system/src/components/Notifications.tsx +301 -0
  65. package/app/vendor/design-system/src/components/Onboarding.tsx +751 -0
  66. package/app/vendor/design-system/src/components/ProjectMenu.tsx +124 -0
  67. package/app/vendor/design-system/src/components/Segment.tsx +87 -0
  68. package/app/vendor/design-system/src/components/Skeleton.tsx +216 -0
  69. package/app/vendor/design-system/src/components/Spinner.tsx +44 -0
  70. package/app/vendor/design-system/src/components/Text.tsx +85 -0
  71. package/app/vendor/design-system/src/components/documentDrafts.ts +213 -0
  72. package/app/vendor/design-system/src/components/layout.tsx +284 -0
  73. package/app/vendor/design-system/src/components/primitives.tsx +143 -0
  74. package/app/vendor/design-system/src/components/reorder.ts +40 -0
  75. package/app/vendor/design-system/src/fieldComponents.ts +63 -0
  76. package/app/vendor/design-system/src/icons.ts +102 -0
  77. package/app/vendor/design-system/src/index.ts +198 -0
  78. package/app/vendor/design-system/src/media.ts +229 -0
  79. package/app/vendor/design-system/src/theme.ts +116 -0
  80. package/app/vendor/design-system/src/web/Button.tsx +110 -0
  81. package/app/vendor/design-system/src/web/Icon.tsx +52 -0
  82. package/app/vendor/design-system/src/web/Input.tsx +39 -0
  83. package/app/vendor/design-system/src/web/index.ts +43 -0
  84. package/app/vendor/design-system/src/web/primitives.tsx +119 -0
  85. package/app/vendor/markdown-editor/src/MarkdownEditor.tsx +248 -0
  86. package/app/vendor/markdown-editor/src/Toolbar.tsx +157 -0
  87. package/app/vendor/markdown-editor/src/field.tsx +67 -0
  88. package/app/vendor/markdown-editor/src/flavors/commonmark/index.ts +27 -0
  89. package/app/vendor/markdown-editor/src/flavors/gfm/index.ts +30 -0
  90. package/app/vendor/markdown-editor/src/flavors/gfm/parser.ts +73 -0
  91. package/app/vendor/markdown-editor/src/flavors/gfm/schema.ts +70 -0
  92. package/app/vendor/markdown-editor/src/flavors/gfm/serializer.ts +82 -0
  93. package/app/vendor/markdown-editor/src/flavors/gfm/taskList.ts +39 -0
  94. package/app/vendor/markdown-editor/src/flavors/registry.ts +13 -0
  95. package/app/vendor/markdown-editor/src/flavors/shared/inputrules.ts +79 -0
  96. package/app/vendor/markdown-editor/src/flavors/shared/keymap.ts +89 -0
  97. package/app/vendor/markdown-editor/src/flavors/shared/placeholder.ts +24 -0
  98. package/app/vendor/markdown-editor/src/flavors/shared/plugins.ts +58 -0
  99. package/app/vendor/markdown-editor/src/flavors/types.ts +22 -0
  100. package/app/vendor/markdown-editor/src/formats/registry.ts +45 -0
  101. package/app/vendor/markdown-editor/src/icons.tsx +120 -0
  102. package/app/vendor/markdown-editor/src/index.ts +23 -0
  103. package/app/vendor/markdown-editor/src/theme.ts +249 -0
  104. package/app/vendor/markdown-editor/src/types.ts +58 -0
  105. package/app/vendor/plugin-sdk/src/__tests__/documentActions.test.ts +108 -0
  106. package/app/vendor/plugin-sdk/src/__tests__/loader.test.ts +75 -0
  107. package/app/vendor/plugin-sdk/src/__tests__/registry.test.ts +148 -0
  108. package/app/vendor/plugin-sdk/src/index.ts +33 -0
  109. package/app/vendor/plugin-sdk/src/loader.ts +45 -0
  110. package/app/vendor/plugin-sdk/src/navkeys.ts +17 -0
  111. package/app/vendor/plugin-sdk/src/react.tsx +155 -0
  112. package/app/vendor/plugin-sdk/src/registry.ts +337 -0
  113. package/app/vendor/plugin-sdk/src/types.ts +230 -0
  114. package/app/vendor/realtime/src/__tests__/pure.test.ts +58 -0
  115. package/app/vendor/realtime/src/__tests__/seeded-room.base64 +1 -0
  116. package/app/vendor/realtime/src/__tests__/seeding.test.ts +70 -0
  117. package/app/vendor/realtime/src/collab.ts +308 -0
  118. package/app/vendor/realtime/src/hooks.ts +57 -0
  119. package/app/vendor/realtime/src/index.ts +12 -0
  120. package/app/vendor/realtime/src/pure.ts +43 -0
  121. package/app/vite.config.mjs +59 -0
  122. package/bin/gogitcms-editor.mjs +168 -0
  123. package/npm-shrinkwrap.json +5696 -0
  124. package/package.json +74 -0
  125. package/src/commands/build.mjs +100 -0
  126. package/src/commands/dev.mjs +186 -0
  127. package/src/commands/init.mjs +144 -0
  128. package/src/commands/login.mjs +116 -0
  129. package/src/commands/status.mjs +68 -0
  130. package/src/lib/api.mjs +122 -0
  131. package/src/lib/appRoot.mjs +51 -0
  132. package/src/lib/baked.json +3 -0
  133. package/src/lib/config.mjs +108 -0
  134. package/src/lib/credentials.mjs +91 -0
  135. package/src/lib/defaults.mjs +26 -0
  136. package/src/lib/graphql.mjs +28 -0
  137. package/src/lib/localServer.mjs +206 -0
  138. package/src/lib/open.mjs +23 -0
  139. package/src/lib/plugins.mjs +164 -0
  140. package/src/lib/tokenBroker.mjs +85 -0
  141. package/src/tui/prompts.mjs +92 -0
  142. package/src/tui/screen.mjs +282 -0
  143. package/src/tui/theme.mjs +40 -0
@@ -0,0 +1,591 @@
1
+ import { gql } from "@apollo/client";
2
+
3
+ export const LOGIN = gql`
4
+ mutation Login($email: String!, $password: String!) {
5
+ login(email: $email, password: $password) {
6
+ accessToken
7
+ refreshToken
8
+ }
9
+ }
10
+ `;
11
+
12
+ export const WORKSPACES = gql`
13
+ query Workspaces {
14
+ workspaces {
15
+ id
16
+ name
17
+ slug
18
+ }
19
+ }
20
+ `;
21
+
22
+ export const REPOSITORIES = gql`
23
+ query Repositories($workspaceId: ID) {
24
+ repositories(workspaceId: $workspaceId) {
25
+ id
26
+ owner
27
+ name
28
+ branches {
29
+ id
30
+ name
31
+ protected
32
+ requiresChangeRequest
33
+ writableProjects
34
+ }
35
+ defaultBranch
36
+ projects {
37
+ name
38
+ label
39
+ orphaned
40
+ }
41
+ }
42
+ }
43
+ `;
44
+
45
+ // ---- changes: this branch vs. the repository's default branch ---------------
46
+ // All three return empty on the default branch (it has no changes against
47
+ // itself), so they are safe to query unconditionally.
48
+
49
+ export const BRANCH_CHANGE_SUMMARY = gql`
50
+ query BranchChangeSummary($repositoryId: ID!, $branchId: ID!, $targetBranchId: ID) {
51
+ branchChangeSummary(repositoryId: $repositoryId, branchId: $branchId, targetBranchId: $targetBranchId) {
52
+ project
53
+ collection
54
+ label
55
+ singleton
56
+ count
57
+ kind
58
+ }
59
+ }
60
+ `;
61
+
62
+ export const BRANCH_MEDIA_CHANGES = gql`
63
+ query BranchMediaChanges($repositoryId: ID!, $branchId: ID!, $targetBranchId: ID) {
64
+ branchMediaChanges(repositoryId: $repositoryId, branchId: $branchId, targetBranchId: $targetBranchId) {
65
+ status
66
+ set
67
+ path
68
+ previousPath
69
+ before { size mimeType kind width height pending url }
70
+ after { size mimeType kind width height pending url }
71
+ }
72
+ }
73
+ `;
74
+
75
+ export const BRANCH_CHANGES = gql`
76
+ query BranchChanges($repositoryId: ID!, $branchId: ID!, $targetBranchId: ID, $collection: String) {
77
+ branchChanges(repositoryId: $repositoryId, branchId: $branchId, targetBranchId: $targetBranchId, collection: $collection) {
78
+ project
79
+ status
80
+ collection
81
+ path
82
+ previousPath
83
+ label
84
+ added
85
+ removed
86
+ documentId
87
+ }
88
+ }
89
+ `;
90
+
91
+ export const BRANCH_CHANGE = gql`
92
+ query BranchChange($repositoryId: ID!, $branchId: ID!, $targetBranchId: ID, $path: String!) {
93
+ branchChange(repositoryId: $repositoryId, branchId: $branchId, targetBranchId: $targetBranchId, path: $path) {
94
+ path
95
+ previousPath
96
+ status
97
+ collection
98
+ label
99
+ added
100
+ removed
101
+ bodyBefore
102
+ bodyAfter
103
+ fields {
104
+ name
105
+ label
106
+ kind
107
+ before
108
+ after
109
+ }
110
+ }
111
+ }
112
+ `;
113
+
114
+ // The merge run's fields, shared by the mutation, query and subscription so the
115
+ // Apollo cache normalizes them onto one MergeRun by id.
116
+ const MERGE_RUN_FIELDS = gql`
117
+ fragment MergeRunFields on MergeRun {
118
+ id
119
+ status
120
+ step
121
+ sourceBranch
122
+ targetBranch
123
+ commit
124
+ filesChanged
125
+ added
126
+ removed
127
+ error
128
+ escalationFiles
129
+ conflicts {
130
+ id
131
+ path
132
+ collection
133
+ field
134
+ isBody
135
+ before
136
+ after
137
+ resolution
138
+ }
139
+ }
140
+ `;
141
+
142
+ // A change request's fields. The `full` variant (summary page) also pulls the
143
+ // live PR description and comments; the list/open check omits them.
144
+ const CHANGE_REQUEST_FIELDS = gql`
145
+ fragment ChangeRequestFields on ChangeRequest {
146
+ id
147
+ status
148
+ sourceBranch
149
+ targetBranch
150
+ prNumber
151
+ prUrl
152
+ explanation
153
+ conflictFiles
154
+ mergeBranch
155
+ createdAt
156
+ }
157
+ `;
158
+
159
+ export const APPLY_CHANGES = gql`
160
+ ${MERGE_RUN_FIELDS}
161
+ mutation ApplyChanges($repositoryId: ID!, $sourceBranchId: ID!, $targetBranchId: ID!) {
162
+ applyChanges(repositoryId: $repositoryId, sourceBranchId: $sourceBranchId, targetBranchId: $targetBranchId) {
163
+ ...MergeRunFields
164
+ }
165
+ }
166
+ `;
167
+
168
+ export const MERGE_RUN = gql`
169
+ ${MERGE_RUN_FIELDS}
170
+ query MergeRun($id: ID!) {
171
+ mergeRun(id: $id) {
172
+ ...MergeRunFields
173
+ }
174
+ }
175
+ `;
176
+
177
+ export const MERGE_RUN_PROGRESS = gql`
178
+ ${MERGE_RUN_FIELDS}
179
+ subscription MergeRunProgress($id: ID!) {
180
+ mergeRunProgress(id: $id) {
181
+ ...MergeRunFields
182
+ }
183
+ }
184
+ `;
185
+
186
+ export const RESOLVE_MERGE_CONFLICT = gql`
187
+ ${MERGE_RUN_FIELDS}
188
+ mutation ResolveMergeConflict($runId: ID!, $conflictId: ID!, $choice: String!, $override: Any) {
189
+ resolveMergeConflict(runId: $runId, conflictId: $conflictId, choice: $choice, override: $override) {
190
+ ...MergeRunFields
191
+ }
192
+ }
193
+ `;
194
+
195
+ export const OPEN_CHANGE_REQUEST = gql`
196
+ ${CHANGE_REQUEST_FIELDS}
197
+ query OpenChangeRequest($repositoryId: ID!, $sourceBranchId: ID!, $targetBranchId: ID!) {
198
+ openChangeRequest(repositoryId: $repositoryId, sourceBranchId: $sourceBranchId, targetBranchId: $targetBranchId) {
199
+ ...ChangeRequestFields
200
+ }
201
+ }
202
+ `;
203
+
204
+ export const CHANGE_REQUEST_FOR_BRANCH = gql`
205
+ ${CHANGE_REQUEST_FIELDS}
206
+ query ChangeRequestForBranch($repositoryId: ID!, $branchId: ID!) {
207
+ changeRequestForBranch(repositoryId: $repositoryId, branchId: $branchId) {
208
+ ...ChangeRequestFields
209
+ }
210
+ }
211
+ `;
212
+
213
+ export const CHANGE_REQUEST = gql`
214
+ ${CHANGE_REQUEST_FIELDS}
215
+ query ChangeRequest($id: ID!) {
216
+ changeRequest(id: $id) {
217
+ ...ChangeRequestFields
218
+ description
219
+ comments {
220
+ author
221
+ body
222
+ createdAt
223
+ }
224
+ }
225
+ }
226
+ `;
227
+
228
+ export const CREATE_CHANGE_REQUEST = gql`
229
+ ${CHANGE_REQUEST_FIELDS}
230
+ mutation CreateChangeRequest($runId: ID!, $explanation: String!) {
231
+ createChangeRequest(runId: $runId, explanation: $explanation) {
232
+ ...ChangeRequestFields
233
+ }
234
+ }
235
+ `;
236
+
237
+ export const CREATE_BRANCH = gql`
238
+ mutation CreateBranch($repositoryId: ID!, $fromBranchId: ID!, $name: String!) {
239
+ createBranch(repositoryId: $repositoryId, fromBranchId: $fromBranchId, name: $name) {
240
+ id
241
+ name
242
+ }
243
+ }
244
+ `;
245
+
246
+ // The scalar (leaf) attributes of a FieldDef. Reused at each nesting depth since
247
+ // GraphQL selections can't self-recurse.
248
+ const FIELD_DEF_BASE = gql`
249
+ fragment FieldDefBase on FieldDef {
250
+ name
251
+ label
252
+ type
253
+ required
254
+ component
255
+ source
256
+ format
257
+ flavor
258
+ media
259
+ storeAs
260
+ of
261
+ discriminator
262
+ pattern
263
+ enumValues
264
+ min
265
+ max
266
+ minItems
267
+ maxItems
268
+ }
269
+ `;
270
+
271
+ // Object/array nesting is expanded to a fixed depth (~3 levels of object fields,
272
+ // ~2 for mixed-list variant fields) — enough for realistic content models.
273
+ export const COLLECTIONS = gql`
274
+ query Collections($repositoryId: ID!, $branchId: ID!, $project: String) {
275
+ collections(repositoryId: $repositoryId, branchId: $branchId, project: $project) {
276
+ name
277
+ label
278
+ path
279
+ singleton
280
+ documentCount
281
+ filename
282
+ canCreate
283
+ canUpdate
284
+ canDelete
285
+ fields {
286
+ ...FieldDefBase
287
+ fields {
288
+ ...FieldDefBase
289
+ fields {
290
+ ...FieldDefBase
291
+ }
292
+ variants {
293
+ name
294
+ label
295
+ fields {
296
+ ...FieldDefBase
297
+ }
298
+ }
299
+ }
300
+ variants {
301
+ name
302
+ label
303
+ fields {
304
+ ...FieldDefBase
305
+ fields {
306
+ ...FieldDefBase
307
+ }
308
+ }
309
+ }
310
+ }
311
+ }
312
+ }
313
+ ${FIELD_DEF_BASE}
314
+ `;
315
+
316
+ // The collection list — display fields only. A document's full fields+body are
317
+ // fetched lazily per open tab via DOCUMENT, so browsing a large collection
318
+ // doesn't pull every body into memory.
319
+ export const DOCUMENTS = gql`
320
+ query Documents(
321
+ $repositoryId: ID!
322
+ $branchId: ID!
323
+ $project: String
324
+ $collection: String!
325
+ $limit: Int
326
+ $offset: Int
327
+ $search: String
328
+ $filters: [DocumentFilter!]
329
+ ) {
330
+ documents(
331
+ repositoryId: $repositoryId
332
+ branchId: $branchId
333
+ project: $project
334
+ collection: $collection
335
+ limit: $limit
336
+ offset: $offset
337
+ search: $search
338
+ filters: $filters
339
+ ) {
340
+ id
341
+ path
342
+ label
343
+ }
344
+ }
345
+ `;
346
+
347
+ // Count of documents matching a collection's search/filters — drives the
348
+ // filtered result total (pagination) and the faceted panel's optimistic "Show N".
349
+ export const DOCUMENT_COUNT = gql`
350
+ query DocumentCount($repositoryId: ID!, $branchId: ID!, $project: String, $collection: String!, $search: String, $filters: [DocumentFilter!]) {
351
+ documentCount(repositoryId: $repositoryId, branchId: $branchId, project: $project, collection: $collection, search: $search, filters: $filters)
352
+ }
353
+ `;
354
+
355
+ // One document's full content, loaded when its tab is opened (its own loading
356
+ // cycle). Independent of the active collection, so tabs persist across it.
357
+ export const DOCUMENT = gql`
358
+ query Document($repositoryId: ID!, $id: ID!) {
359
+ document(repositoryId: $repositoryId, id: $id) {
360
+ id
361
+ path
362
+ label
363
+ fields
364
+ body
365
+ }
366
+ }
367
+ `;
368
+
369
+ // Current user identity, used to label the local participant in collaborative
370
+ // editing (presence outlines/cursors). Cached once per session.
371
+ export const ME = gql`
372
+ query Me {
373
+ me {
374
+ id
375
+ email
376
+ }
377
+ }
378
+ `;
379
+
380
+ export const NOTIFICATIONS = gql`
381
+ query Notifications($repositoryId: ID, $limit: Int) {
382
+ notifications(repositoryId: $repositoryId, limit: $limit) {
383
+ id
384
+ title
385
+ body
386
+ level
387
+ read
388
+ createdAt
389
+ authorName
390
+ authorEmail
391
+ }
392
+ unreadNotificationCount(repositoryId: $repositoryId)
393
+ }
394
+ `;
395
+
396
+ // ---- branch import ---------------------------------------------------------
397
+ // The query answers the editor's opening question (does this branch have content
398
+ // yet?); the subscription keeps answering it for the life of the session, since
399
+ // a push webhook can start an import at any moment. Both select the same fields
400
+ // so the second normalizes over the first in the Apollo cache.
401
+ const BRANCH_IMPORT_FIELDS = gql`
402
+ fragment BranchImportFields on BranchImport {
403
+ branchId
404
+ status
405
+ running
406
+ imported
407
+ error
408
+ errorCode
409
+ }
410
+ `;
411
+
412
+ export const BRANCH_IMPORT = gql`
413
+ ${BRANCH_IMPORT_FIELDS}
414
+ query BranchImport($repositoryId: ID!, $branchId: ID!) {
415
+ branchImport(repositoryId: $repositoryId, branchId: $branchId) {
416
+ ...BranchImportFields
417
+ }
418
+ }
419
+ `;
420
+
421
+ export const BRANCH_IMPORT_PROGRESS = gql`
422
+ ${BRANCH_IMPORT_FIELDS}
423
+ subscription BranchImportProgress($repositoryId: ID!, $branchId: ID!) {
424
+ branchImportProgress(repositoryId: $repositoryId, branchId: $branchId) {
425
+ ...BranchImportFields
426
+ }
427
+ }
428
+ `;
429
+
430
+ export const MARK_NOTIFICATIONS_READ = gql`
431
+ mutation MarkNotificationsRead($repositoryId: ID) {
432
+ markNotificationsRead(all: true, repositoryId: $repositoryId)
433
+ }
434
+ `;
435
+
436
+ export const UPDATE_DOCUMENT = gql`
437
+ mutation UpdateDocument($repositoryId: ID!, $id: ID!, $fields: JSON!, $body: String) {
438
+ updateDocument(repositoryId: $repositoryId, id: $id, fields: $fields, body: $body) {
439
+ id
440
+ path
441
+ label
442
+ fields
443
+ body
444
+ }
445
+ }
446
+ `;
447
+
448
+ export const CREATE_DOCUMENT = gql`
449
+ mutation CreateDocument($repositoryId: ID!, $branchId: ID!, $project: String, $collection: String!, $fields: JSON!, $body: String, $filename: String) {
450
+ createDocument(repositoryId: $repositoryId, branchId: $branchId, project: $project, collection: $collection, fields: $fields, body: $body, filename: $filename) {
451
+ id
452
+ path
453
+ label
454
+ fields
455
+ body
456
+ }
457
+ }
458
+ `;
459
+
460
+ export const DELETE_DOCUMENTS = gql`
461
+ mutation DeleteDocuments($repositoryId: ID!, $ids: [ID!]!) {
462
+ deleteDocuments(repositoryId: $repositoryId, ids: $ids)
463
+ }
464
+ `;
465
+
466
+ export const MOVE_DOCUMENTS = gql`
467
+ mutation MoveDocuments($repositoryId: ID!, $ids: [ID!]!, $sourceFolder: String, $targetFolder: String!) {
468
+ moveDocuments(repositoryId: $repositoryId, ids: $ids, sourceFolder: $sourceFolder, targetFolder: $targetFolder)
469
+ }
470
+ `;
471
+
472
+ // ---- media ----------------------------------------------------------------
473
+
474
+ // The scalar attributes of a Media, reused by every media query.
475
+ const MEDIA_BASE = gql`
476
+ fragment MediaBase on Media {
477
+ id
478
+ set
479
+ repoPath
480
+ publicPath
481
+ cdnPath
482
+ mimeType
483
+ extension
484
+ kind
485
+ size
486
+ width
487
+ height
488
+ pending
489
+ url
490
+ }
491
+ `;
492
+
493
+ export const MEDIA_SETS = gql`
494
+ query MediaSets($repositoryId: ID!, $branchId: ID!, $project: String) {
495
+ mediaSets(repositoryId: $repositoryId, branchId: $branchId, project: $project) {
496
+ name
497
+ path
498
+ mimeTypes
499
+ publicBase
500
+ cdnBase
501
+ count
502
+ }
503
+ }
504
+ `;
505
+
506
+ export const MEDIA = gql`
507
+ ${MEDIA_BASE}
508
+ query Media($repositoryId: ID!, $branchId: ID!, $project: String, $set: String, $kinds: [String!], $search: String, $filters: [MediaFilter!], $limit: Int, $offset: Int) {
509
+ media(repositoryId: $repositoryId, branchId: $branchId, project: $project, set: $set, kinds: $kinds, search: $search, filters: $filters, limit: $limit, offset: $offset) {
510
+ ...MediaBase
511
+ }
512
+ }
513
+ `;
514
+
515
+ export const MEDIA_COUNT = gql`
516
+ query MediaCount($repositoryId: ID!, $branchId: ID!, $project: String, $set: String, $kinds: [String!], $search: String, $filters: [MediaFilter!]) {
517
+ mediaCount(repositoryId: $repositoryId, branchId: $branchId, project: $project, set: $set, kinds: $kinds, search: $search, filters: $filters)
518
+ }
519
+ `;
520
+
521
+ export const MEDIA_FACETS = gql`
522
+ query MediaFacets($repositoryId: ID!, $branchId: ID!, $project: String, $set: String) {
523
+ mediaFacets(repositoryId: $repositoryId, branchId: $branchId, project: $project, set: $set) {
524
+ extensions { value count }
525
+ mimeTypes { value count }
526
+ kinds { value count }
527
+ minSize
528
+ maxSize
529
+ total
530
+ }
531
+ }
532
+ `;
533
+
534
+ export const MEDIA_BY_ID = gql`
535
+ ${MEDIA_BASE}
536
+ query MediaById($repositoryId: ID!, $branchId: ID!, $project: String, $id: ID!) {
537
+ mediaById(repositoryId: $repositoryId, branchId: $branchId, project: $project, id: $id) {
538
+ ...MediaBase
539
+ }
540
+ }
541
+ `;
542
+
543
+ // Batched: one round trip resolves every media string in a document.
544
+ export const RESOLVE_MEDIA = gql`
545
+ ${MEDIA_BASE}
546
+ query ResolveMedia($repositoryId: ID!, $branchId: ID!, $project: String, $paths: [String!]!, $documentPath: String) {
547
+ resolveMedia(repositoryId: $repositoryId, branchId: $branchId, project: $project, paths: $paths, documentPath: $documentPath) {
548
+ input
549
+ aliasKind
550
+ ambiguous
551
+ media {
552
+ ...MediaBase
553
+ }
554
+ candidates {
555
+ ...MediaBase
556
+ }
557
+ }
558
+ }
559
+ `;
560
+
561
+ export const CREATE_MEDIA_UPLOAD = gql`
562
+ mutation CreateMediaUpload($repositoryId: ID!, $branchId: ID!, $project: String, $set: String!, $filename: String!, $contentType: String!, $size: Int!, $folder: String) {
563
+ createMediaUpload(repositoryId: $repositoryId, branchId: $branchId, project: $project, set: $set, filename: $filename, contentType: $contentType, size: $size, folder: $folder) {
564
+ uploadId
565
+ url
566
+ repoPath
567
+ expiresIn
568
+ }
569
+ }
570
+ `;
571
+
572
+ export const FINALIZE_MEDIA_UPLOAD = gql`
573
+ ${MEDIA_BASE}
574
+ mutation FinalizeMediaUpload($repositoryId: ID!, $branchId: ID!, $project: String, $uploadId: String!) {
575
+ finalizeMediaUpload(repositoryId: $repositoryId, branchId: $branchId, project: $project, uploadId: $uploadId) {
576
+ ...MediaBase
577
+ }
578
+ }
579
+ `;
580
+
581
+ export const RENAME_DOCUMENT = gql`
582
+ mutation RenameDocument($repositoryId: ID!, $id: ID!, $filename: String, $folder: String) {
583
+ renameDocument(repositoryId: $repositoryId, id: $id, filename: $filename, folder: $folder) {
584
+ id
585
+ path
586
+ label
587
+ fields
588
+ body
589
+ }
590
+ }
591
+ `;
@@ -0,0 +1,6 @@
1
+ // Provided by the cms-frontend CLI (bin/cms-frontend.mjs cmsPluginsVite):
2
+ // the plugin entries generated from cms.config.js `plugins`.
3
+ declare module "virtual:cms-plugins" {
4
+ import type { PluginLoadEntry } from "@gogitcms/plugin-sdk";
5
+ export const plugins: PluginLoadEntry[];
6
+ }
@@ -0,0 +1,91 @@
1
+ import {
2
+ DEFAULT_API_HOST,
3
+ isAnalyticsEnabled,
4
+ posthogOptions,
5
+ resolveAnalyticsConfig,
6
+ } from "../config";
7
+
8
+ // Analytics being off unless it is deliberately switched on is the property
9
+ // that matters most here: a self-hosted CMS must not phone home.
10
+ describe("resolveAnalyticsConfig", () => {
11
+ it("is disabled with no configuration at all", () => {
12
+ for (const raw of [undefined, null, {}]) {
13
+ expect(isAnalyticsEnabled(resolveAnalyticsConfig(raw))).toBe(false);
14
+ }
15
+ });
16
+
17
+ it("is disabled when the key is blank or whitespace", () => {
18
+ for (const POSTHOG_API_KEY of ["", " "]) {
19
+ expect(isAnalyticsEnabled(resolveAnalyticsConfig({ POSTHOG_API_KEY }))).toBe(false);
20
+ }
21
+ });
22
+
23
+ it("is enabled by an API key alone, defaulting the host", () => {
24
+ const cfg = resolveAnalyticsConfig({ POSTHOG_API_KEY: "phc_abc" });
25
+ expect(isAnalyticsEnabled(cfg)).toBe(true);
26
+ expect(cfg.apiHost).toBe(DEFAULT_API_HOST);
27
+ });
28
+
29
+ it("trims values, so a stray newline from a build arg can't break the host", () => {
30
+ const cfg = resolveAnalyticsConfig({
31
+ POSTHOG_API_KEY: " phc_abc\n",
32
+ POSTHOG_API_HOST: " https://eu.i.posthog.com ",
33
+ });
34
+ expect(cfg.apiKey).toBe("phc_abc");
35
+ expect(cfg.apiHost).toBe("https://eu.i.posthog.com");
36
+ });
37
+
38
+ it("keeps session recording off unless explicitly opted in", () => {
39
+ const off = ["", "false", "1", "yes", undefined];
40
+ for (const POSTHOG_SESSION_RECORDING of off) {
41
+ const cfg = resolveAnalyticsConfig({ POSTHOG_API_KEY: "phc_abc", POSTHOG_SESSION_RECORDING });
42
+ expect(cfg.sessionRecording).toBe(false);
43
+ }
44
+ const on = resolveAnalyticsConfig({ POSTHOG_API_KEY: "phc_abc", POSTHOG_SESSION_RECORDING: "true" });
45
+ expect(on.sessionRecording).toBe(true);
46
+ });
47
+
48
+ it("leaves optional fields undefined rather than empty strings", () => {
49
+ const cfg = resolveAnalyticsConfig({ POSTHOG_API_KEY: "phc_abc", POSTHOG_UI_HOST: "", POSTHOG_ENVIRONMENT: " " });
50
+ expect(cfg.uiHost).toBeUndefined();
51
+ expect(cfg.environment).toBeUndefined();
52
+ });
53
+ });
54
+
55
+ describe("posthogOptions", () => {
56
+ const cfg = resolveAnalyticsConfig({ POSTHOG_API_KEY: "phc_abc" });
57
+
58
+ it("defaults to privacy-preserving settings for a content editor", () => {
59
+ const opts = posthogOptions(cfg);
60
+ // The CMS renders unpublished customer content; replay must be opt-in.
61
+ expect(opts.disable_session_recording).toBe(true);
62
+ expect(opts.mask_all_text).toBe(true);
63
+ expect(opts.session_recording).toEqual({ maskAllInputs: true });
64
+ // Anonymous visitors shouldn't create person records.
65
+ expect(opts.person_profiles).toBe("identified_only");
66
+ });
67
+
68
+ it("enables recording only when opted in, still masked", () => {
69
+ const opts = posthogOptions(
70
+ resolveAnalyticsConfig({ POSTHOG_API_KEY: "phc_abc", POSTHOG_SESSION_RECORDING: "true" }),
71
+ );
72
+ expect(opts.disable_session_recording).toBe(false);
73
+ expect(opts.mask_all_text).toBe(true);
74
+ });
75
+
76
+ // Left undefined, posthog-js defers to the project's remote config, which is
77
+ // how error tracking silently stayed off everywhere. Assert the explicit opt-in.
78
+ it("turns on exception autocapture rather than leaving it to remote config", () => {
79
+ expect(posthogOptions(cfg).capture_exceptions).toBe(true);
80
+ });
81
+
82
+ it("pins posthog-js defaults so an upgrade can't change what is captured", () => {
83
+ expect(posthogOptions(cfg).defaults).toBe("2025-05-24");
84
+ });
85
+
86
+ it("omits ui_host unless one is configured", () => {
87
+ expect("ui_host" in posthogOptions(cfg)).toBe(false);
88
+ const withUi = posthogOptions(resolveAnalyticsConfig({ POSTHOG_API_KEY: "k", POSTHOG_UI_HOST: "https://eu.posthog.com" }));
89
+ expect(withUi.ui_host).toBe("https://eu.posthog.com");
90
+ });
91
+ });