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",
"useWorkspaces": true,
"version": "1.2.12"
"version": "1.2.13"
}

View File

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

View File

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

View File

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

View File

@ -7,7 +7,12 @@ import yaml from 'yaml';
import { resolveBackend } from '../backend';
import { CONFIG_FAILURE, CONFIG_REQUEST, CONFIG_SUCCESS } from '../constants';
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 type { AnyAction } from 'redux';
@ -24,11 +29,13 @@ import type {
} from '../interface';
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);
}
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);
}
@ -70,9 +77,9 @@ function setDefaultPublicFolderForField<T extends Field>(field: T) {
function setI18nField<T extends Field>(field: T) {
if (field[I18N] === true) {
return { ...field, [I18N]: I18N_FIELD.TRANSLATE };
return { ...field, [I18N]: I18N_FIELD_TRANSLATE };
} else if (field[I18N] === false || !field[I18N]) {
return { ...field, [I18N]: I18N_FIELD.NONE };
return { ...field, [I18N]: I18N_FIELD_NONE };
}
return field;
}
@ -104,9 +111,9 @@ function setI18nDefaultsForFields(collectionOrFileFields: Field[], hasI18n: bool
}
function throwOnInvalidFileCollectionStructure(i18n?: I18nInfo) {
if (i18n && i18n.structure !== I18N_STRUCTURE.SINGLE_FILE) {
if (i18n && i18n.structure !== I18N_STRUCTURE_SINGLE_FILE) {
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,
} from '../constants';
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 { Cursor } from '../lib/util';
import { selectFields, updateFieldByKey } from '../lib/util/collection.util';
@ -899,7 +905,7 @@ function createEmptyDraftI18nData(collection: Collection, dataFields: 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);

View File

@ -8,7 +8,14 @@ import { v4 as uuid } from 'uuid';
import { formatExtensions, frontmatterFormats, extensionFormatters } from '../formats/formats';
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 { Config } from '../interface';
@ -18,7 +25,14 @@ const localeType = { type: 'string', minLength: 2, maxLength: 10, pattern: '^[a-
const i18n = {
type: 'object',
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: {
type: 'array',
minItems: 2,
@ -39,7 +53,10 @@ const i18nCollection = {
};
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,
} from './constants';
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 Cursor from './lib/util/Cursor';
import type AssetProxy from './valueObjects/AssetProxy';
@ -566,7 +573,7 @@ export interface CodeField extends BaseField {
keys?: { code: string; lang: string };
output_code_only?: boolean;
code_mirror_config: {
code_mirror_config?: {
extra_keys?: Record<string, string>;
} & Record<string, unknown>;
}
@ -689,11 +696,11 @@ export type Field<EF extends BaseField = UnknownField> =
| ColorField
| DateTimeField
| FileOrImageField
| ListField
| ListField<EF>
| MapField
| MarkdownField
| NumberField
| ObjectField
| ObjectField<EF>
| RelationField
| SelectField
| HiddenField
@ -704,7 +711,7 @@ export interface ViewFilter {
id?: string;
label: string;
field: string;
pattern: string;
pattern: string | boolean | number;
}
export interface ViewGroup {
@ -853,10 +860,20 @@ export interface EditorPersistOptions {
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 {
locales: string[];
defaultLocale: string;
structure?: I18N_STRUCTURE;
structure?: I18nStructure;
}
export interface ProcessedCodeLanguage {

View File

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