Merge branch 'main' into next

This commit is contained in:
Daniel Lautzenheiser 2023-03-30 16:34:34 -04:00
commit 475024db37
9 changed files with 111 additions and 61 deletions

View File

@ -1,5 +1,5 @@
{ {
"$schema": "node_modules/lerna/schemas/lerna-schema.json", "$schema": "node_modules/lerna/schemas/lerna-schema.json",
"useWorkspaces": true, "useWorkspaces": true,
"version": "1.2.12" "version": "1.2.13"
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@staticcms/app", "name": "@staticcms/app",
"version": "1.2.12", "version": "1.2.13",
"license": "MIT", "license": "MIT",
"description": "Static CMS application.", "description": "Static CMS application.",
"repository": "https://github.com/StaticJsCMS/static-cms", "repository": "https://github.com/StaticJsCMS/static-cms",
@ -35,7 +35,7 @@
"@babel/eslint-parser": "7.21.3", "@babel/eslint-parser": "7.21.3",
"@babel/runtime": "7.21.0", "@babel/runtime": "7.21.0",
"@emotion/babel-preset-css-prop": "11.10.0", "@emotion/babel-preset-css-prop": "11.10.0",
"@staticcms/core": "^1.2.12", "@staticcms/core": "^1.2.13",
"buffer": "6.0.3", "buffer": "6.0.3",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",

View File

@ -141,7 +141,6 @@ collections:
label: Pattern Validation label: Pattern Validation
widget: code widget: code
pattern: ['.{12,}', 'Must have at least 12 characters'] pattern: ['.{12,}', 'Must have at least 12 characters']
allow_input: true
required: false required: false
- name: language - name: language
label: Language Selection label: Language Selection

View File

@ -1,6 +1,6 @@
{ {
"name": "@staticcms/core", "name": "@staticcms/core",
"version": "1.2.12", "version": "1.2.13",
"license": "MIT", "license": "MIT",
"description": "Static CMS core application.", "description": "Static CMS core application.",
"repository": "https://github.com/StaticJsCMS/static-cms", "repository": "https://github.com/StaticJsCMS/static-cms",

View File

@ -7,7 +7,12 @@ import yaml from 'yaml';
import { resolveBackend } from '../backend'; import { resolveBackend } from '../backend';
import { CONFIG_FAILURE, CONFIG_REQUEST, CONFIG_SUCCESS } from '../constants'; import { CONFIG_FAILURE, CONFIG_REQUEST, CONFIG_SUCCESS } from '../constants';
import validateConfig from '../constants/configSchema'; import validateConfig from '../constants/configSchema';
import { I18N, I18N_FIELD, I18N_STRUCTURE } from '../lib/i18n'; import {
I18N,
I18N_FIELD_NONE,
I18N_FIELD_TRANSLATE,
I18N_STRUCTURE_SINGLE_FILE,
} from '../lib/i18n';
import { selectDefaultSortableFields } from '../lib/util/collection.util'; import { selectDefaultSortableFields } from '../lib/util/collection.util';
import type { AnyAction } from 'redux'; import type { AnyAction } from 'redux';
@ -24,11 +29,13 @@ import type {
} from '../interface'; } from '../interface';
import type { RootState } from '../store'; import type { RootState } from '../store';
function isObjectField<F extends BaseField = UnknownField>(field: Field<F>): field is ObjectField { function isObjectField<F extends BaseField = UnknownField>(
field: Field<F>,
): field is ObjectField<F> {
return 'fields' in (field as ObjectField); return 'fields' in (field as ObjectField);
} }
function isFieldList<F extends BaseField = UnknownField>(field: Field<F>): field is ListField { function isFieldList<F extends BaseField = UnknownField>(field: Field<F>): field is ListField<F> {
return 'types' in (field as ListField) || 'field' in (field as ListField); return 'types' in (field as ListField) || 'field' in (field as ListField);
} }
@ -70,9 +77,9 @@ function setDefaultPublicFolderForField<T extends Field>(field: T) {
function setI18nField<T extends Field>(field: T) { function setI18nField<T extends Field>(field: T) {
if (field[I18N] === true) { if (field[I18N] === true) {
return { ...field, [I18N]: I18N_FIELD.TRANSLATE }; return { ...field, [I18N]: I18N_FIELD_TRANSLATE };
} else if (field[I18N] === false || !field[I18N]) { } else if (field[I18N] === false || !field[I18N]) {
return { ...field, [I18N]: I18N_FIELD.NONE }; return { ...field, [I18N]: I18N_FIELD_NONE };
} }
return field; return field;
} }
@ -104,9 +111,9 @@ function setI18nDefaultsForFields(collectionOrFileFields: Field[], hasI18n: bool
} }
function throwOnInvalidFileCollectionStructure(i18n?: I18nInfo) { function throwOnInvalidFileCollectionStructure(i18n?: I18nInfo) {
if (i18n && i18n.structure !== I18N_STRUCTURE.SINGLE_FILE) { if (i18n && i18n.structure !== I18N_STRUCTURE_SINGLE_FILE) {
throw new Error( throw new Error(
`i18n configuration for files collections is limited to ${I18N_STRUCTURE.SINGLE_FILE} structure`, `i18n configuration for files collections is limited to ${I18N_STRUCTURE_SINGLE_FILE} structure`,
); );
} }
} }

View File

@ -38,7 +38,13 @@ import {
SORT_ENTRIES_SUCCESS, SORT_ENTRIES_SUCCESS,
} from '../constants'; } from '../constants';
import ValidationErrorTypes from '../constants/validationErrorTypes'; import ValidationErrorTypes from '../constants/validationErrorTypes';
import { duplicateDefaultI18nFields, hasI18n, I18N_FIELD, serializeI18n } from '../lib/i18n'; import {
duplicateDefaultI18nFields,
hasI18n,
I18N_FIELD_DUPLICATE,
I18N_FIELD_TRANSLATE,
serializeI18n,
} from '../lib/i18n';
import { serializeValues } from '../lib/serializeEntryValues'; import { serializeValues } from '../lib/serializeEntryValues';
import { Cursor } from '../lib/util'; import { Cursor } from '../lib/util';
import { selectFields, updateFieldByKey } from '../lib/util/collection.util'; import { selectFields, updateFieldByKey } from '../lib/util/collection.util';
@ -899,7 +905,7 @@ function createEmptyDraftI18nData(collection: Collection, dataFields: Field[]) {
} }
function skipField(field: Field) { function skipField(field: Field) {
return field.i18n !== I18N_FIELD.DUPLICATE && field.i18n !== I18N_FIELD.TRANSLATE; return field.i18n !== I18N_FIELD_DUPLICATE && field.i18n !== I18N_FIELD_TRANSLATE;
} }
const i18nData = createEmptyDraftData(dataFields, skipField); const i18nData = createEmptyDraftData(dataFields, skipField);

View File

@ -8,7 +8,14 @@ import { v4 as uuid } from 'uuid';
import { formatExtensions, frontmatterFormats, extensionFormatters } from '../formats/formats'; import { formatExtensions, frontmatterFormats, extensionFormatters } from '../formats/formats';
import { getWidgets } from '../lib/registry'; import { getWidgets } from '../lib/registry';
import { I18N_STRUCTURE, I18N_FIELD } from '../lib/i18n'; import {
I18N_FIELD_DUPLICATE,
I18N_FIELD_NONE,
I18N_FIELD_TRANSLATE,
I18N_STRUCTURE_MULTIPLE_FILES,
I18N_STRUCTURE_MULTIPLE_FOLDERS,
I18N_STRUCTURE_SINGLE_FILE,
} from '../lib/i18n';
import type { ErrorObject } from 'ajv'; import type { ErrorObject } from 'ajv';
import type { Config } from '../interface'; import type { Config } from '../interface';
@ -18,7 +25,14 @@ const localeType = { type: 'string', minLength: 2, maxLength: 10, pattern: '^[a-
const i18n = { const i18n = {
type: 'object', type: 'object',
properties: { properties: {
structure: { type: 'string', enum: Object.values(I18N_STRUCTURE) }, structure: {
type: 'string',
enum: [
I18N_STRUCTURE_MULTIPLE_FILES,
I18N_STRUCTURE_MULTIPLE_FOLDERS,
I18N_STRUCTURE_SINGLE_FILE,
],
},
locales: { locales: {
type: 'array', type: 'array',
minItems: 2, minItems: 2,
@ -39,7 +53,10 @@ const i18nCollection = {
}; };
const i18nField = { const i18nField = {
oneOf: [{ type: 'boolean' }, { type: 'string', enum: Object.values(I18N_FIELD) }], oneOf: [
{ type: 'boolean' },
{ type: 'string', enum: [I18N_FIELD_DUPLICATE, I18N_FIELD_NONE, I18N_FIELD_TRANSLATE] },
],
}; };
/** /**

View File

@ -16,7 +16,14 @@ import type {
SORT_DIRECTION_NONE, SORT_DIRECTION_NONE,
} from './constants'; } from './constants';
import type { formatExtensions } from './formats/formats'; import type { formatExtensions } from './formats/formats';
import type { I18N_STRUCTURE } from './lib/i18n'; import type {
I18N_FIELD_DUPLICATE,
I18N_FIELD_NONE,
I18N_FIELD_TRANSLATE,
I18N_STRUCTURE_MULTIPLE_FILES,
I18N_STRUCTURE_MULTIPLE_FOLDERS,
I18N_STRUCTURE_SINGLE_FILE,
} from './lib/i18n';
import type { AllowedEvent } from './lib/registry'; import type { AllowedEvent } from './lib/registry';
import type Cursor from './lib/util/Cursor'; import type Cursor from './lib/util/Cursor';
import type AssetProxy from './valueObjects/AssetProxy'; import type AssetProxy from './valueObjects/AssetProxy';
@ -566,7 +573,7 @@ export interface CodeField extends BaseField {
keys?: { code: string; lang: string }; keys?: { code: string; lang: string };
output_code_only?: boolean; output_code_only?: boolean;
code_mirror_config: { code_mirror_config?: {
extra_keys?: Record<string, string>; extra_keys?: Record<string, string>;
} & Record<string, unknown>; } & Record<string, unknown>;
} }
@ -689,11 +696,11 @@ export type Field<EF extends BaseField = UnknownField> =
| ColorField | ColorField
| DateTimeField | DateTimeField
| FileOrImageField | FileOrImageField
| ListField | ListField<EF>
| MapField | MapField
| MarkdownField | MarkdownField
| NumberField | NumberField
| ObjectField | ObjectField<EF>
| RelationField | RelationField
| SelectField | SelectField
| HiddenField | HiddenField
@ -704,7 +711,7 @@ export interface ViewFilter {
id?: string; id?: string;
label: string; label: string;
field: string; field: string;
pattern: string; pattern: string | boolean | number;
} }
export interface ViewGroup { export interface ViewGroup {
@ -853,10 +860,20 @@ export interface EditorPersistOptions {
duplicate?: boolean; duplicate?: boolean;
} }
export type I18nStructure =
| typeof I18N_STRUCTURE_MULTIPLE_FILES
| typeof I18N_STRUCTURE_MULTIPLE_FOLDERS
| typeof I18N_STRUCTURE_SINGLE_FILE;
export type I18nField =
| typeof I18N_FIELD_DUPLICATE
| typeof I18N_FIELD_TRANSLATE
| typeof I18N_FIELD_NONE;
export interface I18nInfo { export interface I18nInfo {
locales: string[]; locales: string[];
defaultLocale: string; defaultLocale: string;
structure?: I18N_STRUCTURE; structure?: I18nStructure;
} }
export interface ProcessedCodeLanguage { export interface ProcessedCodeLanguage {

View File

@ -5,22 +5,26 @@ import groupBy from 'lodash/groupBy';
import { selectEntrySlug } from './util/collection.util'; import { selectEntrySlug } from './util/collection.util';
import { set } from './util/object.util'; import { set } from './util/object.util';
import type { Field, Collection, Entry, EntryData, i18nCollection, I18nInfo } from '../interface'; import type {
Field,
Collection,
Entry,
EntryData,
i18nCollection,
I18nInfo,
I18nStructure,
} from '../interface';
import type { EntryDraftState } from '../reducers/entryDraft'; import type { EntryDraftState } from '../reducers/entryDraft';
export const I18N = 'i18n'; export const I18N = 'i18n';
export enum I18N_STRUCTURE { export const I18N_STRUCTURE_MULTIPLE_FOLDERS = 'multiple_folders';
MULTIPLE_FOLDERS = 'multiple_folders', export const I18N_STRUCTURE_MULTIPLE_FILES = 'multiple_files';
MULTIPLE_FILES = 'multiple_files', export const I18N_STRUCTURE_SINGLE_FILE = 'single_file';
SINGLE_FILE = 'single_file',
}
export enum I18N_FIELD { export const I18N_FIELD_TRANSLATE = 'translate';
TRANSLATE = 'translate', export const I18N_FIELD_DUPLICATE = 'duplicate';
DUPLICATE = 'duplicate', export const I18N_FIELD_NONE = 'none';
NONE = 'none',
}
export function hasI18n(collection: Collection | i18nCollection): collection is i18nCollection { export function hasI18n(collection: Collection | i18nCollection): collection is i18nCollection {
return I18N in collection; return I18N in collection;
@ -37,22 +41,22 @@ export function getI18nInfo(collection: Collection | i18nCollection): I18nInfo |
export function getI18nFilesDepth(collection: Collection, depth: number) { export function getI18nFilesDepth(collection: Collection, depth: number) {
const { structure } = getI18nInfo(collection) as I18nInfo; const { structure } = getI18nInfo(collection) as I18nInfo;
if (structure === I18N_STRUCTURE.MULTIPLE_FOLDERS) { if (structure === I18N_STRUCTURE_MULTIPLE_FOLDERS) {
return depth + 1; return depth + 1;
} }
return depth; return depth;
} }
export function isFieldTranslatable(field: Field, locale?: string, defaultLocale?: string) { export function isFieldTranslatable(field: Field, locale?: string, defaultLocale?: string) {
return locale !== defaultLocale && field.i18n === I18N_FIELD.TRANSLATE; return locale !== defaultLocale && field.i18n === I18N_FIELD_TRANSLATE;
} }
export function isFieldDuplicate(field: Field, locale?: string, defaultLocale?: string) { export function isFieldDuplicate(field: Field, locale?: string, defaultLocale?: string) {
return locale !== defaultLocale && field.i18n === I18N_FIELD.DUPLICATE; return locale !== defaultLocale && field.i18n === I18N_FIELD_DUPLICATE;
} }
export function isFieldHidden(field: Field, locale?: string, defaultLocale?: string) { export function isFieldHidden(field: Field, locale?: string, defaultLocale?: string) {
return locale !== defaultLocale && field.i18n === I18N_FIELD.NONE; return locale !== defaultLocale && field.i18n === I18N_FIELD_NONE;
} }
export function getLocaleDataPath(locale: string) { export function getLocaleDataPath(locale: string) {
@ -65,37 +69,37 @@ export function getDataPath(locale: string, defaultLocale: string) {
} }
export function getFilePath( export function getFilePath(
structure: I18N_STRUCTURE, structure: I18nStructure,
extension: string, extension: string,
path: string, path: string,
slug: string, slug: string,
locale: string, locale: string,
) { ) {
switch (structure) { switch (structure) {
case I18N_STRUCTURE.MULTIPLE_FOLDERS: case I18N_STRUCTURE_MULTIPLE_FOLDERS:
return path.replace(`/${slug}`, `/${locale}/${slug}`); return path.replace(`/${slug}`, `/${locale}/${slug}`);
case I18N_STRUCTURE.MULTIPLE_FILES: case I18N_STRUCTURE_MULTIPLE_FILES:
return path.replace(new RegExp(`${escapeRegExp(extension)}$`), `${locale}.${extension}`); return path.replace(new RegExp(`${escapeRegExp(extension)}$`), `${locale}.${extension}`);
case I18N_STRUCTURE.SINGLE_FILE: case I18N_STRUCTURE_SINGLE_FILE:
default: default:
return path; return path;
} }
} }
export function getLocaleFromPath(structure: I18N_STRUCTURE, extension: string, path: string) { export function getLocaleFromPath(structure: I18nStructure, extension: string, path: string) {
switch (structure) { switch (structure) {
case I18N_STRUCTURE.MULTIPLE_FOLDERS: { case I18N_STRUCTURE_MULTIPLE_FOLDERS: {
const parts = path.split('/'); const parts = path.split('/');
// filename // filename
parts.pop(); parts.pop();
// locale // locale
return parts.pop(); return parts.pop();
} }
case I18N_STRUCTURE.MULTIPLE_FILES: { case I18N_STRUCTURE_MULTIPLE_FILES: {
const parts = path.slice(0, -`.${extension}`.length); const parts = path.slice(0, -`.${extension}`.length);
return parts.split('.').pop(); return parts.split('.').pop();
} }
case I18N_STRUCTURE.SINGLE_FILE: case I18N_STRUCTURE_SINGLE_FILE:
default: default:
return ''; return '';
} }
@ -109,24 +113,24 @@ export function getFilePaths(
) { ) {
const { structure, locales } = getI18nInfo(collection) as I18nInfo; const { structure, locales } = getI18nInfo(collection) as I18nInfo;
if (structure === I18N_STRUCTURE.SINGLE_FILE) { if (structure === I18N_STRUCTURE_SINGLE_FILE) {
return [path]; return [path];
} }
const paths = locales.map(locale => const paths = locales.map(locale =>
getFilePath(structure as I18N_STRUCTURE, extension, path, slug, locale), getFilePath(structure as I18nStructure, extension, path, slug, locale),
); );
return paths; return paths;
} }
export function normalizeFilePath(structure: I18N_STRUCTURE, path: string, locale: string) { export function normalizeFilePath(structure: I18nStructure, path: string, locale: string) {
switch (structure) { switch (structure) {
case I18N_STRUCTURE.MULTIPLE_FOLDERS: case I18N_STRUCTURE_MULTIPLE_FOLDERS:
return path.replace(`${locale}/`, ''); return path.replace(`${locale}/`, '');
case I18N_STRUCTURE.MULTIPLE_FILES: case I18N_STRUCTURE_MULTIPLE_FILES:
return path.replace(`.${locale}`, ''); return path.replace(`.${locale}`, '');
case I18N_STRUCTURE.SINGLE_FILE: case I18N_STRUCTURE_SINGLE_FILE:
default: default:
return path; return path;
} }
@ -142,12 +146,12 @@ export function getI18nFiles(
newPath?: string, newPath?: string,
) { ) {
const { const {
structure = I18N_STRUCTURE.SINGLE_FILE, structure = I18N_STRUCTURE_SINGLE_FILE,
defaultLocale, defaultLocale,
locales, locales,
} = getI18nInfo(collection) as I18nInfo; } = getI18nInfo(collection) as I18nInfo;
if (structure === I18N_STRUCTURE.SINGLE_FILE) { if (structure === I18N_STRUCTURE_SINGLE_FILE) {
const data = locales.reduce((map, locale) => { const data = locales.reduce((map, locale) => {
const dataPath = getDataPath(locale, defaultLocale); const dataPath = getDataPath(locale, defaultLocale);
if (map) { if (map) {
@ -230,7 +234,7 @@ export function formatI18nBackup(
function mergeValues( function mergeValues(
collection: Collection, collection: Collection,
structure: I18N_STRUCTURE, structure: I18nStructure,
defaultLocale: string, defaultLocale: string,
values: { locale: string; value: Entry }[], values: { locale: string; value: Entry }[],
) { ) {
@ -285,13 +289,13 @@ export async function getI18nEntry(
getEntryValue: (path: string) => Promise<Entry>, getEntryValue: (path: string) => Promise<Entry>,
) { ) {
const { const {
structure = I18N_STRUCTURE.SINGLE_FILE, structure = I18N_STRUCTURE_SINGLE_FILE,
locales, locales,
defaultLocale, defaultLocale,
} = getI18nInfo(collection) as I18nInfo; } = getI18nInfo(collection) as I18nInfo;
let entryValue: Entry; let entryValue: Entry;
if (structure === I18N_STRUCTURE.SINGLE_FILE) { if (structure === I18N_STRUCTURE_SINGLE_FILE) {
entryValue = mergeSingleFileValue(await getEntryValue(path), defaultLocale, locales); entryValue = mergeSingleFileValue(await getEntryValue(path), defaultLocale, locales);
} else { } else {
const entryValues = await Promise.all( const entryValues = await Promise.all(
@ -315,11 +319,11 @@ export async function getI18nEntry(
export function groupEntries(collection: Collection, extension: string, entries: Entry[]): Entry[] { export function groupEntries(collection: Collection, extension: string, entries: Entry[]): Entry[] {
const { const {
structure = I18N_STRUCTURE.SINGLE_FILE, structure = I18N_STRUCTURE_SINGLE_FILE,
defaultLocale, defaultLocale,
locales, locales,
} = getI18nInfo(collection) as I18nInfo; } = getI18nInfo(collection) as I18nInfo;
if (structure === I18N_STRUCTURE.SINGLE_FILE) { if (structure === I18N_STRUCTURE_SINGLE_FILE) {
return entries.map(e => mergeSingleFileValue(e, defaultLocale, locales)); return entries.map(e => mergeSingleFileValue(e, defaultLocale, locales));
} }
@ -349,7 +353,7 @@ export function getI18nDataFiles(
diffFiles: { path: string; id: string; newFile: boolean }[], diffFiles: { path: string; id: string; newFile: boolean }[],
) { ) {
const { structure } = getI18nInfo(collection) as I18nInfo; const { structure } = getI18nInfo(collection) as I18nInfo;
if (structure === I18N_STRUCTURE.SINGLE_FILE) { if (structure === I18N_STRUCTURE_SINGLE_FILE) {
return diffFiles; return diffFiles;
} }
const paths = getFilePaths(collection, extension, path, slug); const paths = getFilePaths(collection, extension, path, slug);
@ -386,7 +390,7 @@ export function duplicateI18nFields(
fieldPath: string, fieldPath: string,
) { ) {
const value = get(entryDraft, ['entry', 'data', ...fieldPath.split('.')]); const value = get(entryDraft, ['entry', 'data', ...fieldPath.split('.')]);
if (field.i18n === I18N_FIELD.DUPLICATE) { if (field.i18n === I18N_FIELD_DUPLICATE) {
locales locales
.filter(l => l !== defaultLocale) .filter(l => l !== defaultLocale)
.forEach(l => { .forEach(l => {