Skip to content
This repository was archived by the owner on Sep 9, 2024. It is now read-only.

Commit 9f3bd50

Browse files
authored
feat: open authoring (#938)
1 parent 4ffd38c commit 9f3bd50

File tree

74 files changed

+1713
-546
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+1713
-546
lines changed

packages/core/dev-test/backends/github/config.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ backend:
22
name: github
33
branch: main
44
repo: staticjscms/static-cms-github
5-
5+
auth_scope: repo # this is needed to fork the private repo
6+
open_authoring: true
7+
publish_mode: editorial_workflow
68
media_folder: assets/upload
79
public_folder: /assets/upload
810
media_library:

packages/core/src/__tests__/backend.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,7 @@ describe('Backend', () => {
490490
mediaFiles: [{ id: '1', draft: true }],
491491
status: WorkflowStatus.DRAFT,
492492
updatedOn: '20230-02-09T00:00:00.000Z',
493+
openAuthoring: false,
493494
});
494495
});
495496
});

packages/core/src/actions/__tests__/config.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -889,7 +889,7 @@ describe('config', () => {
889889
).toThrow("i18n locales 'en, de' are missing the default locale fr");
890890
});
891891

892-
it('should throw is default locale is missing from collection i18n config', () => {
892+
it('should throw if default locale is missing from collection i18n config', () => {
893893
expect(() =>
894894
applyDefaults(
895895
createMockConfig({

packages/core/src/actions/auth.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { currentBackend } from '../backend';
22
import { AUTH_FAILURE, AUTH_REQUEST, AUTH_REQUEST_DONE, AUTH_SUCCESS, LOGOUT } from '../constants';
33
import { invokeEvent } from '../lib/registry';
44
import { addSnackbar } from '../store/slices/snackbars';
5+
import { useOpenAuthoring } from './globalUI';
56

67
import type { AnyAction } from 'redux';
78
import type { ThunkDispatch } from 'redux-thunk';
@@ -57,6 +58,9 @@ export function authenticateUser() {
5758
return Promise.resolve(backend.currentUser())
5859
.then(user => {
5960
if (user) {
61+
if (user.useOpenAuthoring) {
62+
dispatch(useOpenAuthoring());
63+
}
6064
dispatch(authenticate(user));
6165
} else {
6266
dispatch(doneAuthenticating());
@@ -85,6 +89,9 @@ export function loginUser(credentials: Credentials) {
8589
return backend
8690
.authenticate(credentials)
8791
.then(user => {
92+
if (user.useOpenAuthoring) {
93+
dispatch(useOpenAuthoring());
94+
}
8895
dispatch(authenticate(user));
8996
})
9097
.catch((error: unknown) => {

packages/core/src/actions/config.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,16 +90,17 @@ function setI18nField<T extends BaseField = UnknownField>(field: T) {
9090

9191
function getI18nDefaults(
9292
collectionOrFileI18n: boolean | Partial<I18nInfo>,
93-
defaultI18n: I18nInfo,
93+
{ default_locale, locales = ['en'], structure = I18N_STRUCTURE_SINGLE_FILE }: Partial<I18nInfo>,
9494
): I18nInfo {
9595
if (typeof collectionOrFileI18n === 'boolean') {
96-
return defaultI18n;
96+
return { default_locale, locales, structure };
9797
} else {
98-
const locales = collectionOrFileI18n.locales || defaultI18n.locales;
99-
const defaultLocale = collectionOrFileI18n.default_locale || locales?.[0];
100-
const mergedI18n: I18nInfo = deepmerge(defaultI18n, collectionOrFileI18n);
101-
mergedI18n.locales = locales ?? [];
102-
mergedI18n.default_locale = defaultLocale;
98+
const mergedI18n: I18nInfo = deepmerge(
99+
{ default_locale, locales, structure },
100+
collectionOrFileI18n,
101+
);
102+
mergedI18n.locales = collectionOrFileI18n.locales ?? locales;
103+
mergedI18n.default_locale = collectionOrFileI18n.default_locale || locales?.[0];
103104
throwOnMissingDefaultLocale(mergedI18n);
104105
return mergedI18n;
105106
}

packages/core/src/actions/globalUI.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
/* eslint-disable import/prefer-default-export */
2-
import { THEME_CHANGE } from '../constants';
2+
import { THEME_CHANGE, USE_OPEN_AUTHORING } from '../constants';
3+
4+
export function useOpenAuthoring() {
5+
return {
6+
type: USE_OPEN_AUTHORING,
7+
} as const;
8+
}
39

410
export function changeTheme(theme: string) {
511
return { type: THEME_CHANGE, payload: theme } as const;
612
}
713

8-
export type GlobalUIAction = ReturnType<typeof changeTheme>;
14+
export type GlobalUIAction = ReturnType<typeof changeTheme | typeof useOpenAuthoring>;

packages/core/src/backend.ts

Lines changed: 52 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,13 +1024,18 @@ export class Backend<EF extends BaseField = UnknownField, BC extends BackendClas
10241024
}
10251025

10261026
const user = (await this.currentUser()) as User;
1027-
const commitMessage = commitMessageFormatter(newEntry ? 'create' : 'update', config, {
1028-
collection,
1029-
slug,
1030-
path,
1031-
authorLogin: user.login,
1032-
authorName: user.name,
1033-
});
1027+
const commitMessage = commitMessageFormatter(
1028+
newEntry ? 'create' : 'update',
1029+
config,
1030+
{
1031+
collection,
1032+
slug,
1033+
path,
1034+
authorLogin: user.login,
1035+
authorName: user.name,
1036+
},
1037+
user.useOpenAuthoring,
1038+
);
10341039

10351040
const collectionName = collection.name;
10361041

@@ -1092,11 +1097,16 @@ export class Backend<EF extends BaseField = UnknownField, BC extends BackendClas
10921097
async persistMedia(config: ConfigWithDefaults, file: AssetProxy) {
10931098
const user = (await this.currentUser()) as User;
10941099
const options = {
1095-
commitMessage: commitMessageFormatter('uploadMedia', config, {
1096-
path: file.path,
1097-
authorLogin: user.login,
1098-
authorName: user.name,
1099-
}),
1100+
commitMessage: commitMessageFormatter(
1101+
'uploadMedia',
1102+
config,
1103+
{
1104+
path: file.path,
1105+
authorLogin: user.login,
1106+
authorName: user.name,
1107+
},
1108+
user.useOpenAuthoring,
1109+
),
11001110
};
11011111
return this.implementation.persistMedia(file, options);
11021112
}
@@ -1119,13 +1129,18 @@ export class Backend<EF extends BaseField = UnknownField, BC extends BackendClas
11191129
}
11201130

11211131
const user = (await this.currentUser()) as User;
1122-
const commitMessage = commitMessageFormatter('delete', configState.config, {
1123-
collection,
1124-
slug,
1125-
path,
1126-
authorLogin: user.login,
1127-
authorName: user.name,
1128-
});
1132+
const commitMessage = commitMessageFormatter(
1133+
'delete',
1134+
configState.config,
1135+
{
1136+
collection,
1137+
slug,
1138+
path,
1139+
authorLogin: user.login,
1140+
authorName: user.name,
1141+
},
1142+
user.useOpenAuthoring,
1143+
);
11291144

11301145
let paths = [path];
11311146
if (hasI18n(collection)) {
@@ -1136,11 +1151,16 @@ export class Backend<EF extends BaseField = UnknownField, BC extends BackendClas
11361151

11371152
async deleteMedia(config: ConfigWithDefaults, path: string) {
11381153
const user = (await this.currentUser()) as User;
1139-
const commitMessage = commitMessageFormatter('deleteMedia', config, {
1140-
path,
1141-
authorLogin: user.login,
1142-
authorName: user.name,
1143-
});
1154+
const commitMessage = commitMessageFormatter(
1155+
'deleteMedia',
1156+
config,
1157+
{
1158+
path,
1159+
authorLogin: user.login,
1160+
authorName: user.name,
1161+
},
1162+
user.useOpenAuthoring,
1163+
);
11441164
return this.implementation.deleteFiles([path], commitMessage);
11451165
}
11461166

@@ -1177,7 +1197,7 @@ export class Backend<EF extends BaseField = UnknownField, BC extends BackendClas
11771197
entryData: UnpublishedEntry,
11781198
withMediaFiles: boolean,
11791199
) {
1180-
const { slug } = entryData;
1200+
const { slug, openAuthoring } = entryData;
11811201
let extension: string;
11821202
if ('files' in collection) {
11831203
const file = collection.files.find(f => f?.name === slug);
@@ -1210,10 +1230,10 @@ export class Backend<EF extends BaseField = UnknownField, BC extends BackendClas
12101230
author: entryData.pullRequestAuthor,
12111231
status: workflowStatusFromString(entryData.status),
12121232
meta: { path: prepareMetaPath(path, collection) },
1233+
openAuthoring,
12131234
});
12141235

1215-
const entryWithFormat = this.entryWithFormat(collection)(entry);
1216-
return entryWithFormat;
1236+
return this.entryWithFormat(collection)(entry);
12171237
};
12181238

12191239
const readAndFormatDataFile = async (dataFile: UnpublishedEntryDiff) => {
@@ -1223,8 +1243,8 @@ export class Backend<EF extends BaseField = UnknownField, BC extends BackendClas
12231243
dataFile.path,
12241244
dataFile.id,
12251245
);
1226-
const entryWithFormat = formatData(data, dataFile.path, dataFile.newFile);
1227-
return entryWithFormat;
1246+
1247+
return formatData(data, dataFile.path, dataFile.newFile);
12281248
};
12291249

12301250
// if the unpublished entry has no diffs, return the original
@@ -1244,8 +1264,7 @@ export class Backend<EF extends BaseField = UnknownField, BC extends BackendClas
12441264
const grouped = await groupEntries(collection, extension, entries as Entry[]);
12451265
return grouped[0];
12461266
} else {
1247-
const entryWithFormat = await readAndFormatDataFile(dataFiles[0]);
1248-
return entryWithFormat;
1267+
return readAndFormatDataFile(dataFiles[0]);
12491268
}
12501269
}
12511270

@@ -1261,8 +1280,8 @@ export class Backend<EF extends BaseField = UnknownField, BC extends BackendClas
12611280
console.warn(`Missing collection '${collectionName}' for unpublished entry '${id}'`);
12621281
return null;
12631282
}
1264-
const entry = await this.processUnpublishedEntry(collection, entryData, false);
1265-
return entry;
1283+
1284+
return this.processUnpublishedEntry(collection, entryData, false);
12661285
}),
12671286
)
12681287
).filter(Boolean) as Entry[];

packages/core/src/backends/bitbucket/API.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import {
3232
} from '@staticcms/core/lib/util/APIUtils';
3333

3434
import type { WorkflowStatus } from '@staticcms/core/constants/publishModes';
35-
import type { DataFile, PersistOptions } from '@staticcms/core/interface';
35+
import type { DataFile, PersistOptions, UnpublishedEntry } from '@staticcms/core/interface';
3636
import type { ApiRequest, FetchError } from '@staticcms/core/lib/util';
3737
import type AssetProxy from '@staticcms/core/valueObjects/AssetProxy';
3838

@@ -646,7 +646,7 @@ export default class API {
646646
return pullRequests[0];
647647
}
648648

649-
async retrieveUnpublishedEntryData(contentKey: string) {
649+
async retrieveUnpublishedEntryData(contentKey: string): Promise<UnpublishedEntry> {
650650
const { collection, slug } = parseContentKey(contentKey);
651651
const branch = branchFromContentKey(contentKey);
652652
const pullRequest = await this.getBranchPullRequest(branch);
@@ -665,6 +665,7 @@ export default class API {
665665
.map(d => ({ path: d.path, newFile: d.newFile, id: '' })),
666666
updatedAt,
667667
pullRequestAuthor,
668+
openAuthoring: false,
668669
};
669670
}
670671

packages/core/src/backends/bitbucket/implementation.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import type {
4343
DisplayURL,
4444
ImplementationFile,
4545
PersistOptions,
46+
UnpublishedEntry,
4647
User,
4748
} from '@staticcms/core/interface';
4849
import type { ApiRequest, AsyncLock, Cursor, FetchError } from '@staticcms/core/lib/util';
@@ -572,7 +573,7 @@ export default class BitbucketBackend implements BackendClass {
572573
id?: string;
573574
collection?: string;
574575
slug?: string;
575-
}) {
576+
}): Promise<UnpublishedEntry> {
576577
if (id) {
577578
const data = await this.api!.retrieveUnpublishedEntryData(id);
578579
return data;

packages/core/src/backends/git-gateway/implementation.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import type {
3636
DisplayURLObject,
3737
ImplementationFile,
3838
PersistOptions,
39+
UnpublishedEntry,
3940
User,
4041
} from '@staticcms/core/interface';
4142
import type { ApiRequest, Cursor } from '@staticcms/core/lib/util';
@@ -563,7 +564,15 @@ export default class GitGateway implements BackendClass {
563564
return this.backend!.unpublishedEntries();
564565
}
565566

566-
unpublishedEntry({ id, collection, slug }: { id?: string; collection?: string; slug?: string }) {
567+
unpublishedEntry({
568+
id,
569+
collection,
570+
slug,
571+
}: {
572+
id?: string;
573+
collection?: string;
574+
slug?: string;
575+
}): Promise<UnpublishedEntry> {
567576
return this.backend!.unpublishedEntry({ id, collection, slug });
568577
}
569578

0 commit comments

Comments
 (0)