@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.
- package/app/index.html +38 -0
- package/app/src/App.tsx +2665 -0
- package/app/src/apollo.ts +119 -0
- package/app/src/auth.ts +248 -0
- package/app/src/config.ts +88 -0
- package/app/src/main.tsx +18 -0
- package/app/src/media.ts +151 -0
- package/app/src/navigation.tsx +321 -0
- package/app/src/plugins.ts +11 -0
- package/app/src/previewHost.tsx +263 -0
- package/app/src/previewToken.ts +68 -0
- package/app/src/queries.ts +591 -0
- package/app/src/virtual-cms-plugins.d.ts +6 -0
- package/app/vendor/analytics/src/__tests__/config.test.ts +91 -0
- package/app/vendor/analytics/src/config.ts +94 -0
- package/app/vendor/analytics/src/events.ts +56 -0
- package/app/vendor/analytics/src/index.ts +23 -0
- package/app/vendor/analytics/src/provider.tsx +196 -0
- package/app/vendor/design-system/src/ThemeProvider.tsx +86 -0
- package/app/vendor/design-system/src/__tests__/ApplyChangesModal.test.tsx +148 -0
- package/app/vendor/design-system/src/__tests__/BranchImport.test.tsx +46 -0
- package/app/vendor/design-system/src/__tests__/Button.test.tsx +45 -0
- package/app/vendor/design-system/src/__tests__/ChangeRequestSummary.test.tsx +57 -0
- package/app/vendor/design-system/src/__tests__/ContentBrowser.changes.test.tsx +611 -0
- package/app/vendor/design-system/src/__tests__/ContentBrowser.collab.test.tsx +322 -0
- package/app/vendor/design-system/src/__tests__/ContentBrowser.collabsync.test.tsx +264 -0
- package/app/vendor/design-system/src/__tests__/ContentBrowser.contentslot.test.tsx +53 -0
- package/app/vendor/design-system/src/__tests__/ContentBrowser.discriminator.test.tsx +142 -0
- package/app/vendor/design-system/src/__tests__/ContentBrowser.drafts.test.tsx +271 -0
- package/app/vendor/design-system/src/__tests__/ContentBrowser.fields.test.tsx +117 -0
- package/app/vendor/design-system/src/__tests__/ContentBrowser.media.test.tsx +140 -0
- package/app/vendor/design-system/src/__tests__/ContentBrowser.mixedcollab.test.tsx +63 -0
- package/app/vendor/design-system/src/__tests__/ContentBrowser.mixedvalues.test.tsx +38 -0
- package/app/vendor/design-system/src/__tests__/ContentBrowser.pagination.test.tsx +62 -0
- package/app/vendor/design-system/src/__tests__/ContentBrowser.previewtab.test.tsx +212 -0
- package/app/vendor/design-system/src/__tests__/ContentBrowser.reorder.test.tsx +45 -0
- package/app/vendor/design-system/src/__tests__/ContentBrowser.search.test.tsx +135 -0
- package/app/vendor/design-system/src/__tests__/ContentBrowser.selectvalue.test.tsx +185 -0
- package/app/vendor/design-system/src/__tests__/ContentBrowser.staged.test.tsx +132 -0
- package/app/vendor/design-system/src/__tests__/ContentBrowser.usermenu.test.tsx +56 -0
- package/app/vendor/design-system/src/__tests__/MediaBrowser.test.tsx +353 -0
- package/app/vendor/design-system/src/__tests__/MediaField.test.tsx +185 -0
- package/app/vendor/design-system/src/__tests__/Notifications.test.tsx +69 -0
- package/app/vendor/design-system/src/__tests__/Onboarding.test.tsx +287 -0
- package/app/vendor/design-system/src/__tests__/cssTokens.test.ts +201 -0
- package/app/vendor/design-system/src/__tests__/fieldComponents.test.ts +43 -0
- package/app/vendor/design-system/src/__tests__/reorder.test.ts +58 -0
- package/app/vendor/design-system/src/components/ApplyChangesModal.tsx +348 -0
- package/app/vendor/design-system/src/components/BranchImport.tsx +157 -0
- package/app/vendor/design-system/src/components/BranchMenu.tsx +192 -0
- package/app/vendor/design-system/src/components/Button.tsx +131 -0
- package/app/vendor/design-system/src/components/ChangeDetail.tsx +472 -0
- package/app/vendor/design-system/src/components/ChangeRequestSummary.tsx +173 -0
- package/app/vendor/design-system/src/components/CollabField.tsx +388 -0
- package/app/vendor/design-system/src/components/ContentBrowser.tsx +5073 -0
- package/app/vendor/design-system/src/components/Icon.tsx +28 -0
- package/app/vendor/design-system/src/components/Icon.web.tsx +31 -0
- package/app/vendor/design-system/src/components/Input.tsx +106 -0
- package/app/vendor/design-system/src/components/MediaBrowser.tsx +766 -0
- package/app/vendor/design-system/src/components/MediaField.tsx +670 -0
- package/app/vendor/design-system/src/components/MediaPreview.tsx +91 -0
- package/app/vendor/design-system/src/components/MediaPreview.web.tsx +169 -0
- package/app/vendor/design-system/src/components/NavRow.tsx +105 -0
- package/app/vendor/design-system/src/components/Notifications.tsx +301 -0
- package/app/vendor/design-system/src/components/Onboarding.tsx +751 -0
- package/app/vendor/design-system/src/components/ProjectMenu.tsx +124 -0
- package/app/vendor/design-system/src/components/Segment.tsx +87 -0
- package/app/vendor/design-system/src/components/Skeleton.tsx +216 -0
- package/app/vendor/design-system/src/components/Spinner.tsx +44 -0
- package/app/vendor/design-system/src/components/Text.tsx +85 -0
- package/app/vendor/design-system/src/components/documentDrafts.ts +213 -0
- package/app/vendor/design-system/src/components/layout.tsx +284 -0
- package/app/vendor/design-system/src/components/primitives.tsx +143 -0
- package/app/vendor/design-system/src/components/reorder.ts +40 -0
- package/app/vendor/design-system/src/fieldComponents.ts +63 -0
- package/app/vendor/design-system/src/icons.ts +102 -0
- package/app/vendor/design-system/src/index.ts +198 -0
- package/app/vendor/design-system/src/media.ts +229 -0
- package/app/vendor/design-system/src/theme.ts +116 -0
- package/app/vendor/design-system/src/web/Button.tsx +110 -0
- package/app/vendor/design-system/src/web/Icon.tsx +52 -0
- package/app/vendor/design-system/src/web/Input.tsx +39 -0
- package/app/vendor/design-system/src/web/index.ts +43 -0
- package/app/vendor/design-system/src/web/primitives.tsx +119 -0
- package/app/vendor/markdown-editor/src/MarkdownEditor.tsx +248 -0
- package/app/vendor/markdown-editor/src/Toolbar.tsx +157 -0
- package/app/vendor/markdown-editor/src/field.tsx +67 -0
- package/app/vendor/markdown-editor/src/flavors/commonmark/index.ts +27 -0
- package/app/vendor/markdown-editor/src/flavors/gfm/index.ts +30 -0
- package/app/vendor/markdown-editor/src/flavors/gfm/parser.ts +73 -0
- package/app/vendor/markdown-editor/src/flavors/gfm/schema.ts +70 -0
- package/app/vendor/markdown-editor/src/flavors/gfm/serializer.ts +82 -0
- package/app/vendor/markdown-editor/src/flavors/gfm/taskList.ts +39 -0
- package/app/vendor/markdown-editor/src/flavors/registry.ts +13 -0
- package/app/vendor/markdown-editor/src/flavors/shared/inputrules.ts +79 -0
- package/app/vendor/markdown-editor/src/flavors/shared/keymap.ts +89 -0
- package/app/vendor/markdown-editor/src/flavors/shared/placeholder.ts +24 -0
- package/app/vendor/markdown-editor/src/flavors/shared/plugins.ts +58 -0
- package/app/vendor/markdown-editor/src/flavors/types.ts +22 -0
- package/app/vendor/markdown-editor/src/formats/registry.ts +45 -0
- package/app/vendor/markdown-editor/src/icons.tsx +120 -0
- package/app/vendor/markdown-editor/src/index.ts +23 -0
- package/app/vendor/markdown-editor/src/theme.ts +249 -0
- package/app/vendor/markdown-editor/src/types.ts +58 -0
- package/app/vendor/plugin-sdk/src/__tests__/documentActions.test.ts +108 -0
- package/app/vendor/plugin-sdk/src/__tests__/loader.test.ts +75 -0
- package/app/vendor/plugin-sdk/src/__tests__/registry.test.ts +148 -0
- package/app/vendor/plugin-sdk/src/index.ts +33 -0
- package/app/vendor/plugin-sdk/src/loader.ts +45 -0
- package/app/vendor/plugin-sdk/src/navkeys.ts +17 -0
- package/app/vendor/plugin-sdk/src/react.tsx +155 -0
- package/app/vendor/plugin-sdk/src/registry.ts +337 -0
- package/app/vendor/plugin-sdk/src/types.ts +230 -0
- package/app/vendor/realtime/src/__tests__/pure.test.ts +58 -0
- package/app/vendor/realtime/src/__tests__/seeded-room.base64 +1 -0
- package/app/vendor/realtime/src/__tests__/seeding.test.ts +70 -0
- package/app/vendor/realtime/src/collab.ts +308 -0
- package/app/vendor/realtime/src/hooks.ts +57 -0
- package/app/vendor/realtime/src/index.ts +12 -0
- package/app/vendor/realtime/src/pure.ts +43 -0
- package/app/vite.config.mjs +59 -0
- package/bin/gogitcms-editor.mjs +168 -0
- package/npm-shrinkwrap.json +5696 -0
- package/package.json +74 -0
- package/src/commands/build.mjs +100 -0
- package/src/commands/dev.mjs +186 -0
- package/src/commands/init.mjs +144 -0
- package/src/commands/login.mjs +116 -0
- package/src/commands/status.mjs +68 -0
- package/src/lib/api.mjs +122 -0
- package/src/lib/appRoot.mjs +51 -0
- package/src/lib/baked.json +3 -0
- package/src/lib/config.mjs +108 -0
- package/src/lib/credentials.mjs +91 -0
- package/src/lib/defaults.mjs +26 -0
- package/src/lib/graphql.mjs +28 -0
- package/src/lib/localServer.mjs +206 -0
- package/src/lib/open.mjs +23 -0
- package/src/lib/plugins.mjs +164 -0
- package/src/lib/tokenBroker.mjs +85 -0
- package/src/tui/prompts.mjs +92 -0
- package/src/tui/screen.mjs +282 -0
- 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
|
+
});
|