committed by
GitHub
parent
ba1cde4e01
commit
c55d1f912f
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@staticcms/core",
|
||||
"version": "1.0.0-alpha38",
|
||||
"version": "1.0.0-alpha44",
|
||||
"license": "MIT",
|
||||
"description": "Static CMS core application.",
|
||||
"repository": "https://github.com/StaticJsCMS/static-cms",
|
||||
|
@ -5,7 +5,6 @@ import trimStart from 'lodash/trimStart';
|
||||
import yaml from 'yaml';
|
||||
|
||||
import { resolveBackend } from '../backend';
|
||||
import { FILES, FOLDER } from '../constants/collectionTypes';
|
||||
import { validateConfig } from '../constants/configSchema';
|
||||
import { I18N, I18N_FIELD, I18N_STRUCTURE } from '../lib/i18n';
|
||||
import { selectDefaultSortableFields } from '../lib/util/collection.util';
|
||||
@ -22,6 +21,7 @@ import type {
|
||||
ListField,
|
||||
LocalBackend,
|
||||
ObjectField,
|
||||
UnknownField,
|
||||
} from '../interface';
|
||||
import type { RootState } from '../store';
|
||||
|
||||
@ -29,11 +29,11 @@ export const CONFIG_REQUEST = 'CONFIG_REQUEST';
|
||||
export const CONFIG_SUCCESS = 'CONFIG_SUCCESS';
|
||||
export const CONFIG_FAILURE = 'CONFIG_FAILURE';
|
||||
|
||||
function isObjectField(field: Field): field is BaseField & ObjectField {
|
||||
function isObjectField<F extends BaseField = UnknownField>(field: Field<F>): field is ObjectField {
|
||||
return 'fields' in (field as ObjectField);
|
||||
}
|
||||
|
||||
function isFieldList(field: Field): field is BaseField & ListField {
|
||||
function isFieldList<F extends BaseField = UnknownField>(field: Field<F>): field is ListField {
|
||||
return 'types' in (field as ListField) || 'field' in (field as ListField);
|
||||
}
|
||||
|
||||
@ -186,15 +186,13 @@ export function applyDefaults(originalConfig: Config) {
|
||||
delete collection[I18N];
|
||||
}
|
||||
|
||||
if (collection.fields) {
|
||||
if ('fields' in collection && collection.fields) {
|
||||
collection.fields = setI18nDefaultsForFields(collection.fields, Boolean(collectionI18n));
|
||||
}
|
||||
|
||||
const { folder, files, view_filters, view_groups } = collection;
|
||||
|
||||
if (folder) {
|
||||
collection.type = FOLDER;
|
||||
const { view_filters, view_groups } = collection;
|
||||
|
||||
if ('folder' in collection && collection.folder) {
|
||||
if (collection.path && !collection.media_folder) {
|
||||
// default value for media folder when using the path config
|
||||
collection.media_folder = '';
|
||||
@ -204,21 +202,17 @@ export function applyDefaults(originalConfig: Config) {
|
||||
collection.public_folder = collection.media_folder;
|
||||
}
|
||||
|
||||
if (collection.fields) {
|
||||
if ('fields' in collection && collection.fields) {
|
||||
collection.fields = traverseFieldsJS(collection.fields, setDefaultPublicFolderForField);
|
||||
}
|
||||
|
||||
collection.folder = trim(folder, '/');
|
||||
collection.folder = trim(collection.folder, '/');
|
||||
}
|
||||
|
||||
if (files) {
|
||||
collection.type = FILES;
|
||||
|
||||
if ('files' in collection && collection.files) {
|
||||
throwOnInvalidFileCollectionStructure(collectionI18n);
|
||||
|
||||
delete collection.nested;
|
||||
|
||||
for (const file of files) {
|
||||
for (const file of collection.files) {
|
||||
file.file = trimStart(file.file, '/');
|
||||
|
||||
if ('media_folder' in file && !('public_folder' in file)) {
|
||||
|
@ -1,9 +1,9 @@
|
||||
import isEqual from 'lodash/isEqual';
|
||||
|
||||
import { currentBackend } from '../backend';
|
||||
import { SORT_DIRECTION_ASCENDING } from '../constants';
|
||||
import ValidationErrorTypes from '../constants/validationErrorTypes';
|
||||
import { getSearchIntegrationProvider } from '../integrations';
|
||||
import { SortDirection } from '../interface';
|
||||
import { duplicateDefaultI18nFields, hasI18n, I18N_FIELD, serializeI18n } from '../lib/i18n';
|
||||
import { serializeValues } from '../lib/serializeEntryValues';
|
||||
import { Cursor } from '../lib/util';
|
||||
@ -33,6 +33,7 @@ import type {
|
||||
I18nSettings,
|
||||
ImplementationMediaFile,
|
||||
ObjectValue,
|
||||
SortDirection,
|
||||
ValueOrNestedValue,
|
||||
ViewFilter,
|
||||
ViewGroup,
|
||||
@ -292,7 +293,7 @@ async function getAllEntries(state: RootState, collection: Collection) {
|
||||
export function sortByField(
|
||||
collection: Collection,
|
||||
key: string,
|
||||
direction: SortDirection = SortDirection.Ascending,
|
||||
direction: SortDirection = SORT_DIRECTION_ASCENDING,
|
||||
) {
|
||||
return async (dispatch: ThunkDispatch<RootState, {}, AnyAction>, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
@ -838,6 +839,10 @@ function processValue(unsafe: string) {
|
||||
|
||||
export function createEmptyDraft(collection: Collection, search: string) {
|
||||
return async (dispatch: ThunkDispatch<RootState, {}, AnyAction>, getState: () => RootState) => {
|
||||
if ('files' in collection) {
|
||||
return;
|
||||
}
|
||||
|
||||
const params = new URLSearchParams(search);
|
||||
params.forEach((value, key) => {
|
||||
collection = updateFieldByKey(collection, key, field => {
|
||||
@ -885,7 +890,7 @@ export function createEmptyDraftData(
|
||||
}
|
||||
|
||||
const subfields = 'fields' in item && item.fields;
|
||||
const list = item.widget == 'list';
|
||||
const list = item.widget === 'list';
|
||||
const name = item.name;
|
||||
const defaultValue = (('default' in item ? item.default : null) ?? null) as EntryData;
|
||||
|
||||
|
@ -6,7 +6,7 @@ import { getMediaDisplayURL, getMediaFile, waitForMediaLibraryToLoad } from './m
|
||||
|
||||
import type { AnyAction } from 'redux';
|
||||
import type { ThunkDispatch } from 'redux-thunk';
|
||||
import type { Collection, Entry, Field } from '../interface';
|
||||
import type { BaseField, Collection, Entry, Field, UnknownField } from '../interface';
|
||||
import type { RootState } from '../store';
|
||||
import type AssetProxy from '../valueObjects/AssetProxy';
|
||||
|
||||
@ -83,11 +83,11 @@ async function loadAsset(
|
||||
|
||||
const promiseCache: Record<string, Promise<AssetProxy>> = {};
|
||||
|
||||
export function getAsset(
|
||||
export function getAsset<F extends BaseField = UnknownField>(
|
||||
collection: Collection | null | undefined,
|
||||
entry: Entry | null | undefined,
|
||||
path: string,
|
||||
field?: Field,
|
||||
field?: F,
|
||||
) {
|
||||
return (
|
||||
dispatch: ThunkDispatch<RootState, {}, AnyAction>,
|
||||
@ -102,7 +102,13 @@ export function getAsset(
|
||||
return Promise.resolve(emptyAsset);
|
||||
}
|
||||
|
||||
const resolvedPath = selectMediaFilePath(state.config.config, collection, entry, path, field);
|
||||
const resolvedPath = selectMediaFilePath(
|
||||
state.config.config,
|
||||
collection,
|
||||
entry,
|
||||
path,
|
||||
field as Field,
|
||||
);
|
||||
let { asset, isLoading, error } = state.medias[resolvedPath] || {};
|
||||
if (isLoading) {
|
||||
return promiseCache[resolvedPath];
|
||||
|
@ -14,11 +14,13 @@ import { waitUntilWithTimeout } from './waitUntil';
|
||||
import type { AnyAction } from 'redux';
|
||||
import type { ThunkDispatch } from 'redux-thunk';
|
||||
import type {
|
||||
BaseField,
|
||||
DisplayURLState,
|
||||
Field,
|
||||
ImplementationMediaFile,
|
||||
MediaFile,
|
||||
MediaLibraryInstance,
|
||||
UnknownField,
|
||||
} from '../interface';
|
||||
import type { RootState } from '../store';
|
||||
import type AssetProxy from '../valueObjects/AssetProxy';
|
||||
@ -72,7 +74,7 @@ export function removeMediaControl(id: string) {
|
||||
};
|
||||
}
|
||||
|
||||
export function openMediaLibrary(
|
||||
export function openMediaLibrary<F extends BaseField = UnknownField>(
|
||||
payload: {
|
||||
controlID?: string;
|
||||
forImage?: boolean;
|
||||
@ -80,17 +82,27 @@ export function openMediaLibrary(
|
||||
allowMultiple?: boolean;
|
||||
replaceIndex?: number;
|
||||
config?: Record<string, unknown>;
|
||||
field?: Field;
|
||||
field?: F;
|
||||
} = {},
|
||||
) {
|
||||
return (dispatch: ThunkDispatch<RootState, {}, AnyAction>, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
const mediaLibrary = state.mediaLibrary.externalLibrary;
|
||||
const { controlID, value, config = {}, allowMultiple, forImage, replaceIndex, field } = payload;
|
||||
if (mediaLibrary) {
|
||||
const { controlID: id, value, config = {}, allowMultiple, forImage } = payload;
|
||||
mediaLibrary.show({ id, value, config, allowMultiple, imagesOnly: forImage });
|
||||
mediaLibrary.show({ id: controlID, value, config, allowMultiple, imagesOnly: forImage });
|
||||
}
|
||||
dispatch(mediaLibraryOpened(payload));
|
||||
dispatch(
|
||||
mediaLibraryOpened({
|
||||
controlID,
|
||||
forImage,
|
||||
value,
|
||||
allowMultiple,
|
||||
replaceIndex,
|
||||
config,
|
||||
field: field as Field,
|
||||
}),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,6 @@ import get from 'lodash/get';
|
||||
import isError from 'lodash/isError';
|
||||
import uniq from 'lodash/uniq';
|
||||
|
||||
import { FILES, FOLDER } from './constants/collectionTypes';
|
||||
import { resolveFormat } from './formats/formats';
|
||||
import { commitMessageFormatter, slugFormatter } from './lib/formatters';
|
||||
import {
|
||||
@ -262,7 +261,7 @@ interface PersistArgs {
|
||||
|
||||
function collectionDepth(collection: Collection) {
|
||||
let depth;
|
||||
depth = collection.nested?.depth || getPathDepth(collection.path ?? '');
|
||||
depth = 'nested' in collection && collection.nested?.depth || getPathDepth(collection.path ?? '');
|
||||
|
||||
if (hasI18n(collection)) {
|
||||
depth = getI18nFilesDepth(collection, depth);
|
||||
@ -441,20 +440,17 @@ export class Backend {
|
||||
async listEntries(collection: Collection) {
|
||||
const extension = selectFolderEntryExtension(collection);
|
||||
let listMethod: () => Promise<ImplementationEntry[]>;
|
||||
const collectionType = collection.type;
|
||||
if (collectionType === FOLDER) {
|
||||
if ('folder' in collection) {
|
||||
listMethod = () => {
|
||||
const depth = collectionDepth(collection);
|
||||
return this.implementation.entriesByFolder(collection.folder as string, extension, depth);
|
||||
};
|
||||
} else if (collectionType === FILES) {
|
||||
const files = collection.files!.map(collectionFile => ({
|
||||
} else {
|
||||
const files = collection.files.map(collectionFile => ({
|
||||
path: collectionFile!.file,
|
||||
label: collectionFile!.label,
|
||||
}));
|
||||
listMethod = () => this.implementation.entriesByFiles(files);
|
||||
} else {
|
||||
throw new Error(`Unknown collection type: ${collectionType}`);
|
||||
}
|
||||
const loadedEntries = await listMethod();
|
||||
/*
|
||||
@ -481,7 +477,7 @@ export class Backend {
|
||||
// returns all the collected entries. Used to retrieve all entries
|
||||
// for local searches and queries.
|
||||
async listAllEntries(collection: Collection) {
|
||||
if (collection.folder && this.implementation.allEntriesByFolder) {
|
||||
if ('folder' in collection && collection.folder && this.implementation.allEntriesByFolder) {
|
||||
const depth = collectionDepth(collection);
|
||||
const extension = selectFolderEntryExtension(collection);
|
||||
return this.implementation
|
||||
@ -513,8 +509,8 @@ export class Backend {
|
||||
// TODO: pass search fields in as an argument
|
||||
let searchFields: (string | null | undefined)[] = [];
|
||||
|
||||
if (collection.type === FILES) {
|
||||
collection.files?.forEach(f => {
|
||||
if ('files' in collection) {
|
||||
collection.files.forEach(f => {
|
||||
const topLevelFields = f!.fields.map(f => f!.name);
|
||||
searchFields = [...searchFields, ...topLevelFields];
|
||||
});
|
||||
@ -970,19 +966,19 @@ export class Backend {
|
||||
}
|
||||
|
||||
fieldsOrder(collection: Collection, entry: Entry) {
|
||||
const fields = collection.fields;
|
||||
if (fields) {
|
||||
return collection.fields.map(f => f!.name);
|
||||
if ('fields' in collection) {
|
||||
return collection.fields?.map(f => f!.name) ?? [];
|
||||
} else {
|
||||
const files = collection.files ?? [];
|
||||
const file: CollectionFile | null = files.filter(f => f!.name === entry.slug)?.[0] ?? null;
|
||||
|
||||
if (file == null) {
|
||||
throw new Error(`No file found for ${entry.slug} in ${collection.name}`);
|
||||
}
|
||||
return file.fields.map(f => f.name);
|
||||
}
|
||||
|
||||
const files = collection.files;
|
||||
const file: CollectionFile | null =
|
||||
(files ?? []).filter(f => f!.name === entry.slug)?.[0] ?? null;
|
||||
|
||||
if (file == null) {
|
||||
throw new Error(`No file found for ${entry.slug} in ${collection.name}`);
|
||||
}
|
||||
return file.fields.map(f => f.name);
|
||||
return [];
|
||||
}
|
||||
|
||||
filterEntries(collection: { entries: Entry[] }, filterRule: FilterRule) {
|
||||
|
@ -87,7 +87,10 @@ const Header = ({
|
||||
}, []);
|
||||
|
||||
const createableCollections = useMemo(
|
||||
() => Object.values(collections).filter(collection => collection.create),
|
||||
() =>
|
||||
Object.values(collections).filter(collection =>
|
||||
'folder' in collection ? collection.create ?? false : false,
|
||||
),
|
||||
[collections],
|
||||
);
|
||||
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
sortByField as sortByFieldAction,
|
||||
} from '../../actions/entries';
|
||||
import { components } from '../../components/UI/styles';
|
||||
import { SortDirection } from '../../interface';
|
||||
import { SORT_DIRECTION_ASCENDING } from '../../constants';
|
||||
import { getNewEntryUrl } from '../../lib/urlHelper';
|
||||
import {
|
||||
selectSortableFields,
|
||||
@ -31,7 +31,13 @@ import Sidebar from './Sidebar';
|
||||
|
||||
import type { ComponentType } from 'react';
|
||||
import type { ConnectedProps } from 'react-redux';
|
||||
import type { Collection, TranslatedProps, ViewFilter, ViewGroup } from '../../interface';
|
||||
import type {
|
||||
Collection,
|
||||
SortDirection,
|
||||
TranslatedProps,
|
||||
ViewFilter,
|
||||
ViewGroup,
|
||||
} from '../../interface';
|
||||
import type { RootState } from '../../store';
|
||||
|
||||
const CollectionMain = styled('main')`
|
||||
@ -77,7 +83,7 @@ const CollectionView = ({
|
||||
}, [collection]);
|
||||
|
||||
const newEntryUrl = useMemo(() => {
|
||||
let url = collection.create ? getNewEntryUrl(collectionName) : '';
|
||||
let url = 'fields' in collection && collection.create ? getNewEntryUrl(collectionName) : '';
|
||||
if (url && filterTerm) {
|
||||
url = getNewEntryUrl(collectionName);
|
||||
if (filterTerm) {
|
||||
@ -163,7 +169,7 @@ const CollectionView = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultSort = collection.sortable_fields.default;
|
||||
const defaultSort = collection.sortable_fields?.default;
|
||||
if (!defaultSort || !defaultSort.field) {
|
||||
if (!readyToLoad) {
|
||||
setReadyToLoad(true);
|
||||
@ -177,7 +183,7 @@ const CollectionView = ({
|
||||
|
||||
const sortEntries = () => {
|
||||
setTimeout(async () => {
|
||||
await onSortClick(defaultSort.field, defaultSort.direction ?? SortDirection.Ascending);
|
||||
await onSortClick(defaultSort.field, defaultSort.direction ?? SORT_DIRECTION_ASCENDING);
|
||||
|
||||
if (alive) {
|
||||
setReadyToLoad(true);
|
||||
@ -218,8 +224,8 @@ const CollectionView = ({
|
||||
sortableFields={sortableFields}
|
||||
onSortClick={onSortClick}
|
||||
sort={sort}
|
||||
viewFilters={viewFilters}
|
||||
viewGroups={viewGroups}
|
||||
viewFilters={viewFilters ?? []}
|
||||
viewGroups={viewGroups ?? []}
|
||||
t={t}
|
||||
onFilterClick={onFilterClick}
|
||||
onGroupClick={onGroupClick}
|
||||
|
@ -94,9 +94,10 @@ function mapStateToProps(state: RootState, ownProps: EntryCardOwnProps) {
|
||||
...ownProps,
|
||||
path: `/collections/${collection.name}/entries/${entry.slug}`,
|
||||
image,
|
||||
imageField: collection.fields?.find(
|
||||
f => f.name === inferedFields.imageField && f.widget === 'image',
|
||||
),
|
||||
imageField:
|
||||
'fields' in collection
|
||||
? collection.fields?.find(f => f.name === inferedFields.imageField && f.widget === 'image')
|
||||
: undefined,
|
||||
isLoadingAsset,
|
||||
};
|
||||
}
|
||||
|
@ -165,7 +165,7 @@ export function walk(treeData: TreeNodeData[], callback: (node: TreeNodeData) =>
|
||||
}
|
||||
|
||||
export function getTreeData(collection: Collection, entries: Entry[]): TreeNodeData[] {
|
||||
const collectionFolder = collection.folder ?? '';
|
||||
const collectionFolder = 'folder' in collection ? collection.folder : '';
|
||||
const rootFolder = '/';
|
||||
const entriesObj = entries.map(e => ({ ...e, path: e.path.slice(collectionFolder.length) }));
|
||||
|
||||
|
@ -8,9 +8,13 @@ import MenuItem from '@mui/material/MenuItem';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { translate } from 'react-polyglot';
|
||||
|
||||
import { SortDirection } from '../../interface';
|
||||
import {
|
||||
SORT_DIRECTION_ASCENDING,
|
||||
SORT_DIRECTION_DESCENDING,
|
||||
SORT_DIRECTION_NONE,
|
||||
} from '../../constants';
|
||||
|
||||
import type { SortableField, SortMap, TranslatedProps } from '../../interface';
|
||||
import type { SortableField, SortDirection, SortMap, TranslatedProps } from '../../interface';
|
||||
|
||||
const StyledMenuIconWrapper = styled('div')`
|
||||
width: 32px;
|
||||
@ -22,12 +26,12 @@ const StyledMenuIconWrapper = styled('div')`
|
||||
|
||||
function nextSortDirection(direction: SortDirection) {
|
||||
switch (direction) {
|
||||
case SortDirection.Ascending:
|
||||
return SortDirection.Descending;
|
||||
case SortDirection.Descending:
|
||||
return SortDirection.None;
|
||||
case SORT_DIRECTION_ASCENDING:
|
||||
return SORT_DIRECTION_DESCENDING;
|
||||
case SORT_DIRECTION_DESCENDING:
|
||||
return SORT_DIRECTION_NONE;
|
||||
default:
|
||||
return SortDirection.Ascending;
|
||||
return SORT_DIRECTION_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,7 +57,7 @@ const SortControl = ({ t, fields, onSortClick, sort }: TranslatedProps<SortContr
|
||||
}
|
||||
|
||||
const sortValues = Object.values(sort);
|
||||
if (Object.values(sortValues).length < 1 || sortValues[0].direction === SortDirection.None) {
|
||||
if (Object.values(sortValues).length < 1 || sortValues[0].direction === SORT_DIRECTION_NONE) {
|
||||
return { key: undefined, direction: undefined };
|
||||
}
|
||||
|
||||
@ -83,7 +87,7 @@ const SortControl = ({ t, fields, onSortClick, sort }: TranslatedProps<SortContr
|
||||
}}
|
||||
>
|
||||
{fields.map(field => {
|
||||
const sortDir = sort?.[field.name]?.direction ?? SortDirection.None;
|
||||
const sortDir = sort?.[field.name]?.direction ?? SORT_DIRECTION_NONE;
|
||||
const nextSortDir = nextSortDirection(sortDir);
|
||||
return (
|
||||
<MenuItem
|
||||
@ -94,7 +98,7 @@ const SortControl = ({ t, fields, onSortClick, sort }: TranslatedProps<SortContr
|
||||
<ListItemText>{field.label ?? field.name}</ListItemText>
|
||||
<StyledMenuIconWrapper>
|
||||
{field.name === selectedSort.key ? (
|
||||
selectedSort.direction === SortDirection.Ascending ? (
|
||||
selectedSort.direction === SORT_DIRECTION_ASCENDING ? (
|
||||
<KeyboardArrowUpIcon fontSize="small" />
|
||||
) : (
|
||||
<KeyboardArrowDownIcon fontSize="small" />
|
||||
|
@ -7,7 +7,6 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { ScrollSync, ScrollSyncPane } from 'react-scroll-sync';
|
||||
|
||||
import { colorsRaw, components, zIndex } from '../../components/UI/styles';
|
||||
import { FILES } from '../../constants/collectionTypes';
|
||||
import { transientOptions } from '../../lib';
|
||||
import { getI18nInfo, getPreviewEntry, hasI18n } from '../../lib/i18n';
|
||||
import { getFileFromSlug } from '../../lib/util/collection.util';
|
||||
@ -221,7 +220,7 @@ const EditorInterface = ({
|
||||
let preview = collection.editor?.preview ?? true;
|
||||
let frame = collection.editor?.frame ?? true;
|
||||
|
||||
if (collection.type === FILES) {
|
||||
if ('files' in collection) {
|
||||
const file = getFileFromSlug(collection, entry.slug);
|
||||
if (file?.editor?.preview !== undefined) {
|
||||
preview = file.editor.preview;
|
||||
|
@ -106,7 +106,10 @@ const EditorToolbar = ({
|
||||
t,
|
||||
editorBackLink,
|
||||
}: TranslatedProps<EditorToolbarProps>) => {
|
||||
const canCreate = useMemo(() => collection.create ?? false, [collection.create]);
|
||||
const canCreate = useMemo(
|
||||
() => ('folder' in collection && collection.create) ?? false,
|
||||
[collection],
|
||||
);
|
||||
const canDelete = useMemo(() => selectAllowDeletion(collection), [collection]);
|
||||
const isPublished = useMemo(() => !isNewEntry && !hasChanged, [hasChanged, isNewEntry]);
|
||||
|
||||
|
3
core/src/constants.ts
Normal file
3
core/src/constants.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export const SORT_DIRECTION_ASCENDING = 'Ascending';
|
||||
export const SORT_DIRECTION_DESCENDING = 'Descending';
|
||||
export const SORT_DIRECTION_NONE = 'None';
|
@ -1,4 +0,0 @@
|
||||
export const FILES = 'file_based_collection';
|
||||
export const FOLDER = 'folder_based_collection';
|
||||
|
||||
export type CollectionType = typeof FILES | typeof FOLDER;
|
@ -1,6 +1,6 @@
|
||||
import type {
|
||||
EditorPlugin as MarkdownPlugin,
|
||||
EditorType as MarkdownEditorType
|
||||
EditorPlugin as MarkdownPlugin,
|
||||
EditorType as MarkdownEditorType,
|
||||
} from '@toast-ui/editor/types/editor';
|
||||
import type { ToolbarItemOptions as MarkdownToolbarItemOptions } from '@toast-ui/editor/types/ui';
|
||||
import type { PropertiesSchema } from 'ajv/dist/types/json-schema';
|
||||
@ -8,6 +8,11 @@ import type { ComponentType, FunctionComponent, ReactNode } from 'react';
|
||||
import type { t, TranslateProps as ReactPolyglotTranslateProps } from 'react-polyglot';
|
||||
import type { MediaFile as BackendMediaFile } from './backend';
|
||||
import type { EditorControlProps } from './components/Editor/EditorControlPane/EditorControl';
|
||||
import type {
|
||||
SORT_DIRECTION_ASCENDING,
|
||||
SORT_DIRECTION_DESCENDING,
|
||||
SORT_DIRECTION_NONE,
|
||||
} from './constants';
|
||||
import type { formatExtensions } from './formats/formats';
|
||||
import type { I18N_STRUCTURE } from './lib/i18n';
|
||||
import type { AllowedEvent } from './lib/registry';
|
||||
@ -162,38 +167,47 @@ export interface i18nCollection<EF extends BaseField = UnknownField>
|
||||
i18n: Required<Collection<EF>>['i18n'];
|
||||
}
|
||||
|
||||
export interface Collection<EF extends BaseField = UnknownField> {
|
||||
export interface BaseCollection {
|
||||
name: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
folder?: string;
|
||||
files?: CollectionFile<EF>[];
|
||||
fields: Field<EF>[];
|
||||
isFetching?: boolean;
|
||||
media_folder?: string;
|
||||
public_folder?: string;
|
||||
summary?: string;
|
||||
filter?: FilterRule;
|
||||
type: 'file_based_collection' | 'folder_based_collection';
|
||||
extension?: string;
|
||||
format?: Format;
|
||||
frontmatter_delimiter?: string | [string, string];
|
||||
create?: boolean;
|
||||
delete?: boolean;
|
||||
identifier_field?: string;
|
||||
path?: string;
|
||||
slug?: string;
|
||||
label_singular?: string;
|
||||
label: string;
|
||||
sortable_fields: SortableFields;
|
||||
view_filters: ViewFilter[];
|
||||
view_groups: ViewGroup[];
|
||||
nested?: Nested;
|
||||
sortable_fields?: SortableFields;
|
||||
view_filters?: ViewFilter[];
|
||||
view_groups?: ViewGroup[];
|
||||
i18n?: boolean | I18nInfo;
|
||||
hide?: boolean;
|
||||
editor?: EditorConfig;
|
||||
identifier_field?: string;
|
||||
path?: string;
|
||||
extension?: string;
|
||||
format?: Format;
|
||||
frontmatter_delimiter?: string | [string, string];
|
||||
slug?: string;
|
||||
media_folder?: string;
|
||||
public_folder?: string;
|
||||
}
|
||||
|
||||
export interface FilesCollection<EF extends BaseField = UnknownField> extends BaseCollection {
|
||||
files: CollectionFile<EF>[];
|
||||
}
|
||||
|
||||
export interface FolderCollection<EF extends BaseField = UnknownField> extends BaseCollection {
|
||||
folder: string;
|
||||
fields: Field<EF>[];
|
||||
create?: boolean;
|
||||
delete?: boolean;
|
||||
nested?: Nested;
|
||||
}
|
||||
|
||||
export type Collection<EF extends BaseField = UnknownField> =
|
||||
| FilesCollection<EF>
|
||||
| FolderCollection<EF>;
|
||||
|
||||
export type Collections<EF extends BaseField = UnknownField> = Record<string, Collection<EF>>;
|
||||
|
||||
export interface MediaLibraryInstance {
|
||||
@ -220,7 +234,10 @@ export interface DisplayURLState {
|
||||
|
||||
export type TranslatedProps<T> = T & ReactPolyglotTranslateProps;
|
||||
|
||||
export type GetAssetFunction = (path: string, field?: Field) => Promise<AssetProxy>;
|
||||
export type GetAssetFunction<F extends BaseField = UnknownField> = (
|
||||
path: string,
|
||||
field?: F,
|
||||
) => Promise<AssetProxy>;
|
||||
|
||||
export interface WidgetControlProps<T, F extends BaseField = UnknownField> {
|
||||
collection: Collection<F>;
|
||||
@ -230,7 +247,7 @@ export interface WidgetControlProps<T, F extends BaseField = UnknownField> {
|
||||
fieldsErrors: FieldsErrors;
|
||||
submitted: boolean;
|
||||
forList: boolean;
|
||||
getAsset: GetAssetFunction;
|
||||
getAsset: GetAssetFunction<F>;
|
||||
isDisabled: boolean;
|
||||
isFieldDuplicate: EditorControlProps['isFieldDuplicate'];
|
||||
isFieldHidden: EditorControlProps['isFieldHidden'];
|
||||
@ -250,22 +267,16 @@ export interface WidgetControlProps<T, F extends BaseField = UnknownField> {
|
||||
value: T | undefined | null;
|
||||
}
|
||||
|
||||
export interface WidgetPreviewProps<
|
||||
T = unknown,
|
||||
F extends BaseField = UnknownField
|
||||
> {
|
||||
export interface WidgetPreviewProps<T = unknown, F extends BaseField = UnknownField> {
|
||||
config: Config<F>;
|
||||
collection: Collection<F>;
|
||||
entry: Entry;
|
||||
field: RenderedField<F>;
|
||||
getAsset: GetAssetFunction;
|
||||
getAsset: GetAssetFunction<F>;
|
||||
value: T | undefined | null;
|
||||
}
|
||||
|
||||
export type WidgetPreviewComponent<
|
||||
T = unknown,
|
||||
F extends BaseField = UnknownField
|
||||
> =
|
||||
export type WidgetPreviewComponent<T = unknown, F extends BaseField = UnknownField> =
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
| React.ReactElement<unknown, string | React.JSXElementConstructor<any>>
|
||||
| ComponentType<WidgetPreviewProps<T, F>>;
|
||||
@ -288,14 +299,15 @@ export interface TemplatePreviewProps<T = EntryData, EF extends BaseField = Unkn
|
||||
entry: Entry<T>;
|
||||
document: Document | undefined | null;
|
||||
window: Window | undefined | null;
|
||||
getAsset: GetAssetFunction;
|
||||
getAsset: GetAssetFunction<Field<EF>>;
|
||||
widgetFor: (name: T extends EntryData ? string : keyof T) => ReactNode;
|
||||
widgetsFor: WidgetsFor<T>;
|
||||
}
|
||||
|
||||
export type TemplatePreviewComponent<T = EntryData, EF extends BaseField = UnknownField> = ComponentType<
|
||||
TemplatePreviewProps<T, EF>
|
||||
>;
|
||||
export type TemplatePreviewComponent<
|
||||
T = EntryData,
|
||||
EF extends BaseField = UnknownField,
|
||||
> = ComponentType<TemplatePreviewProps<T, EF>>;
|
||||
|
||||
export interface WidgetOptions<T = unknown, F extends BaseField = UnknownField> {
|
||||
validator?: Widget<T, F>['validator'];
|
||||
@ -490,6 +502,7 @@ export interface BaseField {
|
||||
pattern?: [string, string];
|
||||
i18n?: boolean | 'translate' | 'duplicate' | 'none';
|
||||
comment?: string;
|
||||
widget: string;
|
||||
}
|
||||
|
||||
export interface BooleanField extends BaseField {
|
||||
@ -623,7 +636,7 @@ export interface HiddenField extends BaseField {
|
||||
|
||||
export interface StringOrTextField extends BaseField {
|
||||
// This is the default widget, so declaring its type is optional.
|
||||
widget?: 'string' | 'text';
|
||||
widget: 'string' | 'text';
|
||||
default?: string;
|
||||
}
|
||||
|
||||
@ -662,11 +675,10 @@ export interface ViewGroup {
|
||||
pattern?: string;
|
||||
}
|
||||
|
||||
export enum SortDirection {
|
||||
Ascending = 'Ascending',
|
||||
Descending = 'Descending',
|
||||
None = 'None',
|
||||
}
|
||||
export type SortDirection =
|
||||
| typeof SORT_DIRECTION_ASCENDING
|
||||
| typeof SORT_DIRECTION_DESCENDING
|
||||
| typeof SORT_DIRECTION_NONE;
|
||||
|
||||
export interface SortableFieldsDefault {
|
||||
field: string;
|
||||
@ -870,3 +882,8 @@ export interface MarkdownEditorOptions {
|
||||
toolbarItems?: MarkdownToolbarItemsFactory;
|
||||
plugins?: MarkdownPluginFactory[];
|
||||
}
|
||||
|
||||
export enum CollectionType {
|
||||
FOLDER,
|
||||
FILES,
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
addFileTemplateFields,
|
||||
compileStringTemplate,
|
||||
keyToPathArray,
|
||||
parseDateFromEntry,
|
||||
parseDateFromEntry
|
||||
} from './widgets/stringTemplate';
|
||||
|
||||
import type { Collection, Config, Entry, EntryData, Slug } from '../interface';
|
||||
@ -111,7 +111,9 @@ export function summaryFormatter(summaryTemplate: string, entry: Entry, collecti
|
||||
const date = parseDateFromEntry(entry, selectInferedField(collection, 'date')) || null;
|
||||
const identifier = get(entryData, keyToPathArray(selectIdentifier(collection)));
|
||||
|
||||
entryData = addFileTemplateFields(entry.path, entryData, collection.folder) ?? {};
|
||||
entryData =
|
||||
addFileTemplateFields(entry.path, entryData, 'folder' in collection ? collection.folder : '') ??
|
||||
{};
|
||||
// allow commit information in summary template
|
||||
if (entry.author && !selectField(collection, COMMIT_AUTHOR)) {
|
||||
entryData = set(entryData, COMMIT_AUTHOR, entry.author);
|
||||
@ -136,7 +138,11 @@ export function folderFormatter(
|
||||
}
|
||||
|
||||
let fields = set(entry.data, folderKey, defaultFolder) as EntryData;
|
||||
fields = addFileTemplateFields(entry.path, fields, collection.folder);
|
||||
fields = addFileTemplateFields(
|
||||
entry.path,
|
||||
fields,
|
||||
'folder' in collection ? collection.folder : '',
|
||||
);
|
||||
|
||||
const date = parseDateFromEntry(entry, selectInferedField(collection, 'date')) || null;
|
||||
const identifier = get(fields, keyToPathArray(selectIdentifier(collection)));
|
||||
|
@ -22,13 +22,13 @@ export enum I18N_FIELD {
|
||||
NONE = 'none',
|
||||
}
|
||||
|
||||
export function hasI18n(collection: Collection): collection is i18nCollection {
|
||||
export function hasI18n(collection: Collection | i18nCollection): collection is i18nCollection {
|
||||
return I18N in collection;
|
||||
}
|
||||
|
||||
export function getI18nInfo(collection: i18nCollection): I18nInfo;
|
||||
export function getI18nInfo(collection: Collection): I18nInfo | null;
|
||||
export function getI18nInfo(collection: Collection): I18nInfo | null {
|
||||
export function getI18nInfo(collection: Collection | i18nCollection): I18nInfo | null {
|
||||
if (!hasI18n(collection) || typeof collection[I18N] !== 'object') {
|
||||
return null;
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import type {
|
||||
BackendClass,
|
||||
BackendInitializer,
|
||||
BackendInitializerOptions,
|
||||
BaseField,
|
||||
Config,
|
||||
CustomIcon,
|
||||
Entry,
|
||||
@ -19,6 +20,7 @@ import type {
|
||||
PreviewStyle,
|
||||
PreviewStyleOptions,
|
||||
TemplatePreviewComponent,
|
||||
UnknownField,
|
||||
Widget,
|
||||
WidgetOptions,
|
||||
WidgetParam,
|
||||
@ -124,21 +126,21 @@ export function getPreviewTemplate(name: string): TemplatePreviewComponent<Entry
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function registerWidget(widgets: WidgetParam<any, any>[]): void;
|
||||
export function registerWidget(widget: WidgetParam): void;
|
||||
export function registerWidget<T = unknown>(
|
||||
export function registerWidget<T = unknown, F extends BaseField = UnknownField>(
|
||||
name: string,
|
||||
control: string | Widget<T>['control'],
|
||||
preview?: Widget<T>['preview'],
|
||||
options?: WidgetOptions,
|
||||
control: string | Widget<T, F>['control'],
|
||||
preview?: Widget<T, F>['preview'],
|
||||
options?: WidgetOptions<T, F>,
|
||||
): void;
|
||||
export function registerWidget<T = unknown>(
|
||||
name: string | WidgetParam<T> | WidgetParam[],
|
||||
control?: string | Widget<T>['control'],
|
||||
preview?: Widget<T>['preview'],
|
||||
export function registerWidget<T = unknown, F extends BaseField = UnknownField>(
|
||||
name: string | WidgetParam<T, F> | WidgetParam[],
|
||||
control?: string | Widget<T, F>['control'],
|
||||
preview?: Widget<T, F>['preview'],
|
||||
{
|
||||
schema,
|
||||
validator = () => false,
|
||||
getValidValue = (value: unknown) => value,
|
||||
}: WidgetOptions = {},
|
||||
getValidValue = (value: T | null | undefined) => value,
|
||||
}: WidgetOptions<T, F> = {},
|
||||
): void {
|
||||
if (Array.isArray(name)) {
|
||||
name.forEach(widget => {
|
||||
@ -184,12 +186,12 @@ export function registerWidget<T = unknown>(
|
||||
throw Error(`Widget "${widgetName}" registered without \`controlComponent\`.`);
|
||||
}
|
||||
registry.widgets[widgetName] = {
|
||||
control: control as Widget['control'],
|
||||
preview: preview as Widget['preview'],
|
||||
validator: validator as Widget['validator'],
|
||||
getValidValue: getValidValue as Widget['getValidValue'],
|
||||
control,
|
||||
preview,
|
||||
validator,
|
||||
getValidValue,
|
||||
schema,
|
||||
};
|
||||
} as unknown as Widget;
|
||||
} else {
|
||||
console.error('`registerWidget` failed, called with incorrect arguments.');
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import get from 'lodash/get';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { FILES, FOLDER } from '../../constants/collectionTypes';
|
||||
import { COMMIT_AUTHOR, COMMIT_DATE } from '../../constants/commitProps';
|
||||
import {
|
||||
IDENTIFIER_FIELDS,
|
||||
@ -19,114 +18,94 @@ import type { Backend } from '../../backend';
|
||||
import type { InferredField } from '../../constants/fieldInference';
|
||||
import type {
|
||||
Collection,
|
||||
CollectionFile,
|
||||
Config,
|
||||
Entry,
|
||||
Field,
|
||||
FilesCollection,
|
||||
ObjectField,
|
||||
SortableField,
|
||||
} from '../../interface';
|
||||
|
||||
const selectors = {
|
||||
[FOLDER]: {
|
||||
entryExtension(collection: Collection) {
|
||||
return (collection.extension || formatExtensions[collection.format ?? 'frontmatter']).replace(
|
||||
/^\./,
|
||||
'',
|
||||
);
|
||||
},
|
||||
fields(collection: Collection) {
|
||||
return collection.fields;
|
||||
},
|
||||
entryPath(collection: Collection, slug: string) {
|
||||
const folder = (collection.folder as string).replace(/\/$/, '');
|
||||
return `${folder}/${slug}.${this.entryExtension(collection)}`;
|
||||
},
|
||||
entrySlug(collection: Collection, path: string) {
|
||||
const folder = (collection.folder as string).replace(/\/$/, '');
|
||||
const slug = path
|
||||
.split(folder + '/')
|
||||
.pop()
|
||||
?.replace(new RegExp(`\\.${this.entryExtension(collection)}$`), '');
|
||||
|
||||
return slug;
|
||||
},
|
||||
allowNewEntries(collection: Collection) {
|
||||
return collection.create;
|
||||
},
|
||||
allowDeletion(collection: Collection) {
|
||||
return collection.delete ?? true;
|
||||
},
|
||||
templateName(collection: Collection) {
|
||||
return collection.name;
|
||||
},
|
||||
},
|
||||
[FILES]: {
|
||||
fileForEntry(collection: Collection, slug?: string) {
|
||||
const files = collection.files;
|
||||
if (!slug) {
|
||||
return files?.[0];
|
||||
}
|
||||
return files && files.filter(f => f?.name === slug)?.[0];
|
||||
},
|
||||
fields(collection: Collection, slug?: string) {
|
||||
const file = this.fileForEntry(collection, slug);
|
||||
return file && file.fields;
|
||||
},
|
||||
entryPath(collection: Collection, slug: string) {
|
||||
const file = this.fileForEntry(collection, slug);
|
||||
return file && file.file;
|
||||
},
|
||||
entrySlug(collection: Collection, path: string) {
|
||||
const file = (collection.files as CollectionFile[]).filter(f => f?.file === path)?.[0];
|
||||
return file && file.name;
|
||||
},
|
||||
entryLabel(collection: Collection, slug: string) {
|
||||
const file = this.fileForEntry(collection, slug);
|
||||
return file && file.label;
|
||||
},
|
||||
allowNewEntries() {
|
||||
return false;
|
||||
},
|
||||
allowDeletion(collection: Collection) {
|
||||
return collection.delete ?? false;
|
||||
},
|
||||
templateName(_collection: Collection, slug: string) {
|
||||
return slug;
|
||||
},
|
||||
},
|
||||
};
|
||||
function fileForEntry(collection: FilesCollection, slug?: string) {
|
||||
const files = collection.files;
|
||||
if (!slug) {
|
||||
return files?.[0];
|
||||
}
|
||||
return files && files.filter(f => f?.name === slug)?.[0];
|
||||
}
|
||||
|
||||
export function selectFields(collection: Collection, slug?: string) {
|
||||
return selectors[collection.type].fields(collection, slug);
|
||||
if ('fields' in collection) {
|
||||
return collection.fields;
|
||||
}
|
||||
|
||||
const file = fileForEntry(collection, slug);
|
||||
return file && file.fields;
|
||||
}
|
||||
|
||||
export function selectFolderEntryExtension(collection: Collection) {
|
||||
return selectors[FOLDER].entryExtension(collection);
|
||||
return (collection.extension || formatExtensions[collection.format ?? 'frontmatter']).replace(
|
||||
/^\./,
|
||||
'',
|
||||
);
|
||||
}
|
||||
|
||||
export function selectFileEntryLabel(collection: Collection, slug: string) {
|
||||
return selectors[FILES].entryLabel(collection, slug);
|
||||
if ('fields' in collection) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const file = fileForEntry(collection, slug);
|
||||
return file && file.label;
|
||||
}
|
||||
|
||||
export function selectEntryPath(collection: Collection, slug: string) {
|
||||
return selectors[collection.type].entryPath(collection, slug);
|
||||
if ('fields' in collection) {
|
||||
const folder = collection.folder.replace(/\/$/, '');
|
||||
return `${folder}/${slug}.${selectFolderEntryExtension(collection)}`;
|
||||
}
|
||||
|
||||
const file = fileForEntry(collection, slug);
|
||||
return file && file.file;
|
||||
}
|
||||
|
||||
export function selectEntrySlug(collection: Collection, path: string) {
|
||||
return selectors[collection.type].entrySlug(collection, path);
|
||||
if ('fields' in collection) {
|
||||
const folder = (collection.folder as string).replace(/\/$/, '');
|
||||
const slug = path
|
||||
.split(folder + '/')
|
||||
.pop()
|
||||
?.replace(new RegExp(`\\.${selectFolderEntryExtension(collection)}$`), '');
|
||||
|
||||
return slug;
|
||||
}
|
||||
|
||||
const file = collection.files.filter(f => f?.file === path)?.[0];
|
||||
return file && file.name;
|
||||
}
|
||||
|
||||
export function selectAllowNewEntries(collection: Collection) {
|
||||
return selectors[collection.type].allowNewEntries(collection);
|
||||
if ('fields' in collection) {
|
||||
return collection.create ?? true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function selectAllowDeletion(collection: Collection) {
|
||||
return selectors[collection.type].allowDeletion(collection);
|
||||
if ('fields' in collection) {
|
||||
return collection.delete ?? true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function selectTemplateName(collection: Collection, slug: string) {
|
||||
return selectors[collection.type].templateName(collection, slug);
|
||||
if ('fields' in collection) {
|
||||
return collection.name;
|
||||
}
|
||||
|
||||
return slug;
|
||||
}
|
||||
|
||||
export function selectEntryCollectionTitle(collection: Collection, entry: Entry): string {
|
||||
@ -137,7 +116,7 @@ export function selectEntryCollectionTitle(collection: Collection, entry: Entry)
|
||||
}
|
||||
|
||||
// if the collection is a file collection return the label of the entry
|
||||
if (collection.type == FILES) {
|
||||
if ('files' in collection && collection.files) {
|
||||
const label = selectFileEntryLabel(collection, entry.slug);
|
||||
if (label) {
|
||||
return label;
|
||||
@ -250,7 +229,7 @@ function getFieldsWithMediaFolders(fields: Field[]) {
|
||||
return fieldsWithMediaFolders;
|
||||
}
|
||||
|
||||
export function getFileFromSlug(collection: Collection, slug: string) {
|
||||
export function getFileFromSlug(collection: FilesCollection, slug: string) {
|
||||
return collection.files?.find(f => f.name === slug);
|
||||
}
|
||||
|
||||
@ -258,7 +237,7 @@ export function selectFieldsWithMediaFolders(collection: Collection, slug: strin
|
||||
if ('folder' in collection) {
|
||||
const fields = collection.fields;
|
||||
return getFieldsWithMediaFolders(fields);
|
||||
} else if ('files' in collection) {
|
||||
} else {
|
||||
const fields = getFileFromSlug(collection, slug)?.fields || [];
|
||||
return getFieldsWithMediaFolders(fields);
|
||||
}
|
||||
@ -274,17 +253,15 @@ export function selectMediaFolders(config: Config, collection: Collection, entry
|
||||
if (file) {
|
||||
folders.unshift(selectMediaFolder(config, collection, entry, undefined));
|
||||
}
|
||||
}
|
||||
if ('media_folder' in collection) {
|
||||
} else if ('media_folder' in collection) {
|
||||
// stop evaluating media folders at collection level
|
||||
const newCollection = { ...collection };
|
||||
delete newCollection.files;
|
||||
folders.unshift(selectMediaFolder(config, newCollection, entry, undefined));
|
||||
}
|
||||
|
||||
return [...new Set(...folders)];
|
||||
}
|
||||
export function getFieldsNames(fields: (Field | Field)[] | undefined, prefix = '') {
|
||||
export function getFieldsNames(fields: Field[] | undefined, prefix = '') {
|
||||
let names = fields?.map(f => `${prefix}${f.name}`) ?? [];
|
||||
|
||||
fields?.forEach((f, index) => {
|
||||
@ -347,7 +324,9 @@ export function updateFieldByKey(
|
||||
}
|
||||
}
|
||||
|
||||
collection.fields = traverseFields(collection.fields ?? [], updateAndBreak, () => updated);
|
||||
if ('fields' in collection) {
|
||||
collection.fields = traverseFields(collection.fields ?? [], updateAndBreak, () => updated);
|
||||
}
|
||||
|
||||
return collection;
|
||||
}
|
||||
@ -355,7 +334,7 @@ export function updateFieldByKey(
|
||||
export function selectIdentifier(collection: Collection) {
|
||||
const identifier = collection.identifier_field;
|
||||
const identifierFields = identifier ? [identifier, ...IDENTIFIER_FIELDS] : [...IDENTIFIER_FIELDS];
|
||||
const fieldNames = getFieldsNames(collection.fields ?? []);
|
||||
const fieldNames = getFieldsNames('fields' in collection ? collection.fields ?? [] : []);
|
||||
return identifierFields.find(id =>
|
||||
fieldNames.find(name => name.toLowerCase().trim() === id.toLowerCase().trim()),
|
||||
);
|
||||
@ -377,7 +356,7 @@ export function selectInferedField(collection: Collection, fieldName: string) {
|
||||
}
|
||||
>
|
||||
)[fieldName];
|
||||
const fields = collection.fields as (Field | Field)[];
|
||||
const fields = 'fields' in collection ? collection.fields ?? [] : [];
|
||||
let field;
|
||||
|
||||
// If collection has no fields or fieldName is not defined within inferables list, return null
|
||||
|
@ -6,15 +6,18 @@ import type { Collection, Field } from '../../interface';
|
||||
export function selectField(collection: Collection, key: string) {
|
||||
const array = keyToPathArray(key);
|
||||
let name: string | undefined;
|
||||
let field;
|
||||
let fields = collection.fields ?? [];
|
||||
while ((name = array.shift()) && fields) {
|
||||
field = fields.find(f => f.name === name);
|
||||
if (field) {
|
||||
if ('fields' in field) {
|
||||
fields = field?.fields ?? [];
|
||||
} else if ('types' in field) {
|
||||
fields = field?.types ?? [];
|
||||
let field: Field | undefined;
|
||||
|
||||
if ('fields' in collection) {
|
||||
let fields = collection.fields ?? [];
|
||||
while ((name = array.shift()) && fields) {
|
||||
field = fields.find(f => f.name === name);
|
||||
if (field) {
|
||||
if ('fields' in field) {
|
||||
fields = field?.fields ?? [];
|
||||
} else if ('types' in field) {
|
||||
fields = field?.types ?? [];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ function hasCustomFolder(
|
||||
return true;
|
||||
}
|
||||
|
||||
if (collection.files) {
|
||||
if ('files' in collection) {
|
||||
const file = getFileField(collection.files, slug);
|
||||
if (file && file[folderKey]) {
|
||||
return true;
|
||||
@ -78,7 +78,7 @@ function evaluateFolder(
|
||||
collection[folderKey] = `{{${folderKey}}}`;
|
||||
}
|
||||
|
||||
if (collection.files) {
|
||||
if ('files' in collection) {
|
||||
// files collection evaluate the collection template
|
||||
// then move on to the specific file configuration denoted by the slug
|
||||
currentFolder = folderFormatter(
|
||||
@ -244,7 +244,7 @@ export function selectMediaFolder(
|
||||
const entryPath = entryMap?.path;
|
||||
mediaFolder = entryPath
|
||||
? join(dirname(entryPath), folder)
|
||||
: join(collection!.folder as string, DRAFT_MEDIA_FILES);
|
||||
: join(collection && 'folder' in collection ? collection.folder : '', DRAFT_MEDIA_FILES);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,11 +21,11 @@ import {
|
||||
GROUP_ENTRIES_SUCCESS,
|
||||
SORT_ENTRIES_FAILURE,
|
||||
SORT_ENTRIES_REQUEST,
|
||||
SORT_ENTRIES_SUCCESS,
|
||||
SORT_ENTRIES_SUCCESS
|
||||
} from '../actions/entries';
|
||||
import { SEARCH_ENTRIES_SUCCESS } from '../actions/search';
|
||||
import { SORT_DIRECTION_ASCENDING, SORT_DIRECTION_NONE } from '../constants';
|
||||
import { VIEW_STYLE_LIST } from '../constants/collectionViews';
|
||||
import { SortDirection } from '../interface';
|
||||
import { set } from '../lib/util/object.util';
|
||||
import { selectSortDataPath } from '../lib/util/sort.util';
|
||||
|
||||
@ -44,7 +44,7 @@ import type {
|
||||
Pages,
|
||||
Sort,
|
||||
SortMap,
|
||||
SortObject,
|
||||
SortObject
|
||||
} from '../interface';
|
||||
import type { EntryDraftState } from './entryDraft';
|
||||
|
||||
@ -568,7 +568,7 @@ export function selectEntriesGroupField(entries: EntriesState, collection: strin
|
||||
|
||||
export function selectEntriesSortFields(entries: EntriesState, collection: string) {
|
||||
const sort = selectEntriesSort(entries, collection);
|
||||
const values = Object.values(sort ?? {}).filter(v => v?.direction !== SortDirection.None) || [];
|
||||
const values = Object.values(sort ?? {}).filter(v => v?.direction !== SORT_DIRECTION_NONE) || [];
|
||||
|
||||
return values;
|
||||
}
|
||||
@ -605,7 +605,7 @@ export function selectEntries(state: EntriesState, collection: Collection) {
|
||||
const sortFields = selectEntriesSortFields(state, collectionName);
|
||||
if (sortFields && sortFields.length > 0) {
|
||||
const keys = sortFields.map(v => selectSortDataPath(collection, v.key));
|
||||
const orders = sortFields.map(v => (v.direction === SortDirection.Ascending ? 'asc' : 'desc'));
|
||||
const orders = sortFields.map(v => (v.direction === SORT_DIRECTION_ASCENDING ? 'asc' : 'desc'));
|
||||
entries = orderBy(entries, keys, orders);
|
||||
}
|
||||
|
||||
|
13
core/src/types/tomlify-j0.4.d.ts
vendored
13
core/src/types/tomlify-j0.4.d.ts
vendored
@ -1,13 +0,0 @@
|
||||
declare module 'tomlify-j0.4' {
|
||||
interface ToTomlOptions {
|
||||
replace?(key: string, value: unknown): string | false;
|
||||
sort?(a: string, b: string): number;
|
||||
}
|
||||
|
||||
interface Tomlify {
|
||||
toToml(data: object, options?: ToTomlOptions): string;
|
||||
}
|
||||
|
||||
const tomlify: Tomlify;
|
||||
export default tomlify;
|
||||
}
|
@ -7,7 +7,7 @@ import type { FileOrImageField, GetAssetFunction, WidgetPreviewProps } from '../
|
||||
|
||||
interface FileLinkProps {
|
||||
value: string;
|
||||
getAsset: GetAssetFunction;
|
||||
getAsset: GetAssetFunction<FileOrImageField>;
|
||||
field: FileOrImageField;
|
||||
}
|
||||
|
||||
@ -40,7 +40,7 @@ const StyledFileLink = styled(FileLink)`
|
||||
|
||||
interface FileLinkListProps {
|
||||
values: string[];
|
||||
getAsset: GetAssetFunction;
|
||||
getAsset: GetAssetFunction<FileOrImageField>;
|
||||
field: FileOrImageField;
|
||||
}
|
||||
|
||||
|
@ -126,7 +126,7 @@ const SortableImageButtons = ({ onRemove, onReplace }: SortableImageButtonsProps
|
||||
|
||||
interface SortableImageProps {
|
||||
itemValue: string;
|
||||
getAsset: GetAssetFunction;
|
||||
getAsset: GetAssetFunction<FileOrImageField>;
|
||||
field: FileOrImageField;
|
||||
onRemove: MouseEventHandler;
|
||||
onReplace: MouseEventHandler;
|
||||
@ -167,7 +167,7 @@ const StyledSortableMultiImageWrapper = styled('div')`
|
||||
|
||||
interface SortableMultiImageWrapperProps {
|
||||
items: string[];
|
||||
getAsset: GetAssetFunction;
|
||||
getAsset: GetAssetFunction<FileOrImageField>;
|
||||
field: FileOrImageField;
|
||||
onRemoveOne: (index: number) => MouseEventHandler;
|
||||
onReplaceOne: (index: number) => MouseEventHandler;
|
||||
|
@ -18,7 +18,7 @@ const StyledImage = styled(({ src }: StyledImageProps) => (
|
||||
`;
|
||||
|
||||
interface ImageAssetProps {
|
||||
getAsset: GetAssetFunction;
|
||||
getAsset: GetAssetFunction<FileOrImageField>;
|
||||
value: string;
|
||||
field: FileOrImageField;
|
||||
}
|
||||
|
11
core/src/widgets/list/ListPreview.tsx
Normal file
11
core/src/widgets/list/ListPreview.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
import WidgetPreviewContainer from '../../components/UI/WidgetPreviewContainer';
|
||||
|
||||
import type { ListField, ObjectValue, WidgetPreviewProps } from '../../interface';
|
||||
|
||||
function ObjectPreview({ field }: WidgetPreviewProps<ObjectValue[], ListField>) {
|
||||
return <WidgetPreviewContainer>{field.fields ?? null}</WidgetPreviewContainer>;
|
||||
}
|
||||
|
||||
export default ObjectPreview;
|
@ -1,5 +1,5 @@
|
||||
import ObjectPreview from '../object/ObjectPreview';
|
||||
import controlComponent from './ListControl';
|
||||
import previewComponent from './ListPreview';
|
||||
import schema from './schema';
|
||||
|
||||
import type { ListField, WidgetParam, ObjectValue } from '../../interface';
|
||||
@ -8,7 +8,7 @@ const ListWidget = (): WidgetParam<ObjectValue[], ListField> => {
|
||||
return {
|
||||
name: 'list',
|
||||
controlComponent,
|
||||
previewComponent: ObjectPreview,
|
||||
previewComponent,
|
||||
options: {
|
||||
schema,
|
||||
},
|
||||
|
@ -5,7 +5,7 @@ import type AssetProxy from '../../../valueObjects/AssetProxy';
|
||||
|
||||
interface UseMediaProps {
|
||||
value: string | undefined | null;
|
||||
getAsset: GetAssetFunction;
|
||||
getAsset: GetAssetFunction<MarkdownField>;
|
||||
field: MarkdownField;
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ import Outline from '../../components/UI/Outline';
|
||||
import { transientOptions } from '../../lib';
|
||||
import { compileStringTemplate } from '../../lib/widgets/stringTemplate';
|
||||
|
||||
import type { ListField, ObjectField, ObjectValue, WidgetControlProps } from '../../interface';
|
||||
import type { ObjectField, ObjectValue, WidgetControlProps } from '../../interface';
|
||||
|
||||
const StyledObjectControlWrapper = styled('div')`
|
||||
position: relative;
|
||||
@ -59,7 +59,7 @@ const ObjectControl = ({
|
||||
i18n,
|
||||
hasErrors,
|
||||
value = {},
|
||||
}: WidgetControlProps<ObjectValue, ObjectField | ListField>) => {
|
||||
}: WidgetControlProps<ObjectValue, ObjectField>) => {
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
|
||||
const handleCollapseToggle = useCallback(() => {
|
||||
|
@ -2,11 +2,9 @@ import React from 'react';
|
||||
|
||||
import WidgetPreviewContainer from '../../components/UI/WidgetPreviewContainer';
|
||||
|
||||
import type { WidgetPreviewProps, ObjectField, ListField, ObjectValue } from '../../interface';
|
||||
import type { ObjectField, ObjectValue, WidgetPreviewProps } from '../../interface';
|
||||
|
||||
function ObjectPreview({
|
||||
field,
|
||||
}: WidgetPreviewProps<ObjectValue | ObjectValue[], ObjectField | ListField>) {
|
||||
function ObjectPreview({ field }: WidgetPreviewProps<ObjectValue, ObjectField>) {
|
||||
return <WidgetPreviewContainer>{field.fields ?? null}</WidgetPreviewContainer>;
|
||||
}
|
||||
|
||||
|
@ -2,9 +2,9 @@ import controlComponent from './ObjectControl';
|
||||
import previewComponent from './ObjectPreview';
|
||||
import schema from './schema';
|
||||
|
||||
import type { ListField, ObjectField, WidgetParam, ObjectValue } from '../../interface';
|
||||
import type { ObjectField, ObjectValue, WidgetParam } from '../../interface';
|
||||
|
||||
const ObjectWidget = (): WidgetParam<ObjectValue, ObjectField | ListField> => {
|
||||
const ObjectWidget = (): WidgetParam<ObjectValue, ObjectField> => {
|
||||
return {
|
||||
name: 'object',
|
||||
controlComponent,
|
||||
|
3199
core/yarn.lock
3199
core/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user