feat: singleton array list widget (#336)
This commit is contained in:
committed by
GitHub
parent
a60d53b4ec
commit
c5e94ed16d
@ -1,4 +1,5 @@
|
||||
import { currentBackend } from '../backend';
|
||||
import { AUTH_FAILURE, AUTH_REQUEST, AUTH_REQUEST_DONE, AUTH_SUCCESS, LOGOUT } from '../constants';
|
||||
import { addSnackbar } from '../store/slices/snackbars';
|
||||
|
||||
import type { AnyAction } from 'redux';
|
||||
@ -6,12 +7,6 @@ import type { ThunkDispatch } from 'redux-thunk';
|
||||
import type { Credentials, User } from '../interface';
|
||||
import type { RootState } from '../store';
|
||||
|
||||
export const AUTH_REQUEST = 'AUTH_REQUEST';
|
||||
export const AUTH_SUCCESS = 'AUTH_SUCCESS';
|
||||
export const AUTH_FAILURE = 'AUTH_FAILURE';
|
||||
export const AUTH_REQUEST_DONE = 'AUTH_REQUEST_DONE';
|
||||
export const LOGOUT = 'LOGOUT';
|
||||
|
||||
export function authenticating() {
|
||||
return {
|
||||
type: AUTH_REQUEST,
|
||||
|
@ -5,6 +5,7 @@ import trimStart from 'lodash/trimStart';
|
||||
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 { selectDefaultSortableFields } from '../lib/util/collection.util';
|
||||
@ -23,10 +24,6 @@ import type {
|
||||
} from '../interface';
|
||||
import type { RootState } from '../store';
|
||||
|
||||
export const CONFIG_REQUEST = 'CONFIG_REQUEST';
|
||||
export const CONFIG_SUCCESS = 'CONFIG_SUCCESS';
|
||||
export const CONFIG_FAILURE = 'CONFIG_FAILURE';
|
||||
|
||||
function isObjectField<F extends BaseField = UnknownField>(field: Field<F>): field is ObjectField {
|
||||
return 'fields' in (field as ObjectField);
|
||||
}
|
||||
@ -125,7 +122,7 @@ function throwOnMissingDefaultLocale(i18n?: I18nInfo) {
|
||||
}
|
||||
|
||||
export function applyDefaults(originalConfig: Config) {
|
||||
return produce(originalConfig, config => {
|
||||
return produce(originalConfig, (config: Config) => {
|
||||
config.slug = config.slug || {};
|
||||
config.collections = config.collections || [];
|
||||
|
||||
|
@ -1,15 +1,53 @@
|
||||
import isEqual from 'lodash/isEqual';
|
||||
|
||||
import { currentBackend } from '../backend';
|
||||
import { SORT_DIRECTION_ASCENDING } from '../constants';
|
||||
import {
|
||||
ADD_DRAFT_ENTRY_MEDIA_FILE,
|
||||
CHANGE_VIEW_STYLE,
|
||||
DRAFT_CHANGE_FIELD,
|
||||
DRAFT_CREATE_DUPLICATE_FROM_ENTRY,
|
||||
DRAFT_CREATE_EMPTY,
|
||||
DRAFT_CREATE_FROM_ENTRY,
|
||||
DRAFT_CREATE_FROM_LOCAL_BACKUP,
|
||||
DRAFT_DISCARD,
|
||||
DRAFT_LOCAL_BACKUP_DELETE,
|
||||
DRAFT_LOCAL_BACKUP_RETRIEVED,
|
||||
DRAFT_VALIDATION_ERRORS,
|
||||
ENTRIES_FAILURE,
|
||||
ENTRIES_REQUEST,
|
||||
ENTRIES_SUCCESS,
|
||||
ENTRY_DELETE_FAILURE,
|
||||
ENTRY_DELETE_REQUEST,
|
||||
ENTRY_DELETE_SUCCESS,
|
||||
ENTRY_FAILURE,
|
||||
ENTRY_PERSIST_FAILURE,
|
||||
ENTRY_PERSIST_REQUEST,
|
||||
ENTRY_PERSIST_SUCCESS,
|
||||
ENTRY_REQUEST,
|
||||
ENTRY_SUCCESS,
|
||||
FILTER_ENTRIES_FAILURE,
|
||||
FILTER_ENTRIES_REQUEST,
|
||||
FILTER_ENTRIES_SUCCESS,
|
||||
GROUP_ENTRIES_FAILURE,
|
||||
GROUP_ENTRIES_REQUEST,
|
||||
GROUP_ENTRIES_SUCCESS,
|
||||
REMOVE_DRAFT_ENTRY_MEDIA_FILE,
|
||||
SORT_DIRECTION_ASCENDING,
|
||||
SORT_ENTRIES_FAILURE,
|
||||
SORT_ENTRIES_REQUEST,
|
||||
SORT_ENTRIES_SUCCESS,
|
||||
} from '../constants';
|
||||
import ValidationErrorTypes from '../constants/validationErrorTypes';
|
||||
import { duplicateDefaultI18nFields, hasI18n, I18N_FIELD, serializeI18n } from '../lib/i18n';
|
||||
import { serializeValues } from '../lib/serializeEntryValues';
|
||||
import { Cursor } from '../lib/util';
|
||||
import { selectFields, updateFieldByKey } from '../lib/util/collection.util';
|
||||
import { selectPublishedSlugs } from '../reducers';
|
||||
import { selectCollectionEntriesCursor } from '../reducers/cursors';
|
||||
import { selectEntriesSortFields, selectIsFetching } from '../reducers/entries';
|
||||
import { selectCollectionEntriesCursor } from '../reducers/selectors/cursors';
|
||||
import {
|
||||
selectEntriesSortFields,
|
||||
selectIsFetching,
|
||||
selectPublishedSlugs,
|
||||
} from '../reducers/selectors/entries';
|
||||
import { navigateToEntry } from '../routing/history';
|
||||
import { addSnackbar } from '../store/slices/snackbars';
|
||||
import { createAssetProxy } from '../valueObjects/AssetProxy';
|
||||
@ -40,52 +78,6 @@ import type {
|
||||
import type { RootState } from '../store';
|
||||
import type AssetProxy from '../valueObjects/AssetProxy';
|
||||
|
||||
/*
|
||||
* Constant Declarations
|
||||
*/
|
||||
export const ENTRY_REQUEST = 'ENTRY_REQUEST';
|
||||
export const ENTRY_SUCCESS = 'ENTRY_SUCCESS';
|
||||
export const ENTRY_FAILURE = 'ENTRY_FAILURE';
|
||||
|
||||
export const ENTRIES_REQUEST = 'ENTRIES_REQUEST';
|
||||
export const ENTRIES_SUCCESS = 'ENTRIES_SUCCESS';
|
||||
export const ENTRIES_FAILURE = 'ENTRIES_FAILURE';
|
||||
|
||||
export const SORT_ENTRIES_REQUEST = 'SORT_ENTRIES_REQUEST';
|
||||
export const SORT_ENTRIES_SUCCESS = 'SORT_ENTRIES_SUCCESS';
|
||||
export const SORT_ENTRIES_FAILURE = 'SORT_ENTRIES_FAILURE';
|
||||
|
||||
export const FILTER_ENTRIES_REQUEST = 'FILTER_ENTRIES_REQUEST';
|
||||
export const FILTER_ENTRIES_SUCCESS = 'FILTER_ENTRIES_SUCCESS';
|
||||
export const FILTER_ENTRIES_FAILURE = 'FILTER_ENTRIES_FAILURE';
|
||||
|
||||
export const GROUP_ENTRIES_REQUEST = 'GROUP_ENTRIES_REQUEST';
|
||||
export const GROUP_ENTRIES_SUCCESS = 'GROUP_ENTRIES_SUCCESS';
|
||||
export const GROUP_ENTRIES_FAILURE = 'GROUP_ENTRIES_FAILURE';
|
||||
|
||||
export const DRAFT_CREATE_FROM_ENTRY = 'DRAFT_CREATE_FROM_ENTRY';
|
||||
export const DRAFT_CREATE_EMPTY = 'DRAFT_CREATE_EMPTY';
|
||||
export const DRAFT_DISCARD = 'DRAFT_DISCARD';
|
||||
export const DRAFT_CHANGE_FIELD = 'DRAFT_CHANGE_FIELD';
|
||||
export const DRAFT_VALIDATION_ERRORS = 'DRAFT_VALIDATION_ERRORS';
|
||||
export const DRAFT_LOCAL_BACKUP_RETRIEVED = 'DRAFT_LOCAL_BACKUP_RETRIEVED';
|
||||
export const DRAFT_LOCAL_BACKUP_DELETE = 'DRAFT_LOCAL_BACKUP_DELETE';
|
||||
export const DRAFT_CREATE_FROM_LOCAL_BACKUP = 'DRAFT_CREATE_FROM_LOCAL_BACKUP';
|
||||
export const DRAFT_CREATE_DUPLICATE_FROM_ENTRY = 'DRAFT_CREATE_DUPLICATE_FROM_ENTRY';
|
||||
|
||||
export const ENTRY_PERSIST_REQUEST = 'ENTRY_PERSIST_REQUEST';
|
||||
export const ENTRY_PERSIST_SUCCESS = 'ENTRY_PERSIST_SUCCESS';
|
||||
export const ENTRY_PERSIST_FAILURE = 'ENTRY_PERSIST_FAILURE';
|
||||
|
||||
export const ENTRY_DELETE_REQUEST = 'ENTRY_DELETE_REQUEST';
|
||||
export const ENTRY_DELETE_SUCCESS = 'ENTRY_DELETE_SUCCESS';
|
||||
export const ENTRY_DELETE_FAILURE = 'ENTRY_DELETE_FAILURE';
|
||||
|
||||
export const ADD_DRAFT_ENTRY_MEDIA_FILE = 'ADD_DRAFT_ENTRY_MEDIA_FILE';
|
||||
export const REMOVE_DRAFT_ENTRY_MEDIA_FILE = 'REMOVE_DRAFT_ENTRY_MEDIA_FILE';
|
||||
|
||||
export const CHANGE_VIEW_STYLE = 'CHANGE_VIEW_STYLE';
|
||||
|
||||
/*
|
||||
* Simple Action Creators (Internal)
|
||||
* We still need to export them for tests
|
||||
@ -287,7 +279,7 @@ export function sortByField(
|
||||
return async (dispatch: ThunkDispatch<RootState, {}, AnyAction>, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
// if we're already fetching we update the sort key, but skip loading entries
|
||||
const isFetching = selectIsFetching(state.entries, collection.name);
|
||||
const isFetching = selectIsFetching(state, collection.name);
|
||||
dispatch(sortEntriesRequest(collection, key, direction));
|
||||
if (isFetching) {
|
||||
return;
|
||||
@ -307,7 +299,7 @@ export function filterByField(collection: Collection, filter: ViewFilter) {
|
||||
return async (dispatch: ThunkDispatch<RootState, {}, AnyAction>, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
// if we're already fetching we update the filter key, but skip loading entries
|
||||
const isFetching = selectIsFetching(state.entries, collection.name);
|
||||
const isFetching = selectIsFetching(state, collection.name);
|
||||
dispatch(filterEntriesRequest(collection, filter));
|
||||
if (isFetching) {
|
||||
return;
|
||||
@ -325,7 +317,7 @@ export function filterByField(collection: Collection, filter: ViewFilter) {
|
||||
export function groupByField(collection: Collection, group: ViewGroup) {
|
||||
return async (dispatch: ThunkDispatch<RootState, {}, AnyAction>, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
const isFetching = selectIsFetching(state.entries, collection.name);
|
||||
const isFetching = selectIsFetching(state, collection.name);
|
||||
dispatch({
|
||||
type: GROUP_ENTRIES_REQUEST,
|
||||
payload: {
|
||||
@ -657,7 +649,7 @@ export function loadEntries(collection: Collection, page = 0) {
|
||||
return;
|
||||
}
|
||||
const state = getState();
|
||||
const sortFields = selectEntriesSortFields(state.entries, collection.name);
|
||||
const sortFields = selectEntriesSortFields(state, collection.name);
|
||||
if (sortFields && sortFields.length > 0) {
|
||||
const field = sortFields[0];
|
||||
return dispatch(sortByField(collection, field.key, field.direction));
|
||||
@ -687,12 +679,6 @@ export function loadEntries(collection: Collection, page = 0) {
|
||||
|
||||
const cleanResponse = {
|
||||
...response,
|
||||
// The only existing backend using the pagination system is the
|
||||
// Algolia integration, which is also the only integration used
|
||||
// to list entries. Thus, this checking for an integration can
|
||||
// determine whether or not this is using the old integer-based
|
||||
// pagination API. Other backends will simply store an empty
|
||||
// cursor, which behaves identically to no cursor at all.
|
||||
cursor: !('cursor' in response && response.cursor)
|
||||
? Cursor.create({
|
||||
actions: ['next'],
|
||||
@ -759,7 +745,7 @@ export function traverseCollectionCursor(collection: Collection, action: string)
|
||||
|
||||
const { action: realAction, append } =
|
||||
action in appendActions ? appendActions[action] : { action, append: false };
|
||||
const cursor = selectCollectionEntriesCursor(state.cursors, collection.name);
|
||||
const cursor = selectCollectionEntriesCursor(state, collection.name);
|
||||
|
||||
// Handle cursors representing pages in the old, integer-based pagination API
|
||||
if (cursor.meta?.usingOldPaginationAPI ?? false) {
|
||||
|
@ -1,6 +1,14 @@
|
||||
import {
|
||||
ADD_ASSET,
|
||||
ADD_ASSETS,
|
||||
LOAD_ASSET_FAILURE,
|
||||
LOAD_ASSET_REQUEST,
|
||||
LOAD_ASSET_SUCCESS,
|
||||
REMOVE_ASSET,
|
||||
} from '../constants';
|
||||
import { isAbsolutePath } from '../lib/util';
|
||||
import { selectMediaFilePath } from '../lib/util/media.util';
|
||||
import { selectMediaFileByPath } from '../reducers/mediaLibrary';
|
||||
import { selectMediaFileByPath } from '../reducers/selectors/mediaLibrary';
|
||||
import { createAssetProxy } from '../valueObjects/AssetProxy';
|
||||
import { getMediaDisplayURL, getMediaFile, waitForMediaLibraryToLoad } from './mediaLibrary';
|
||||
|
||||
@ -10,14 +18,6 @@ import type { BaseField, Collection, Entry, Field, UnknownField } from '../inter
|
||||
import type { RootState } from '../store';
|
||||
import type AssetProxy from '../valueObjects/AssetProxy';
|
||||
|
||||
export const ADD_ASSETS = 'ADD_ASSETS';
|
||||
export const ADD_ASSET = 'ADD_ASSET';
|
||||
export const REMOVE_ASSET = 'REMOVE_ASSET';
|
||||
|
||||
export const LOAD_ASSET_REQUEST = 'LOAD_ASSET_REQUEST';
|
||||
export const LOAD_ASSET_SUCCESS = 'LOAD_ASSET_SUCCESS';
|
||||
export const LOAD_ASSET_FAILURE = 'LOAD_ASSET_FAILURE';
|
||||
|
||||
export function addAssets(assets: AssetProxy[]) {
|
||||
return { type: ADD_ASSETS, payload: assets } as const;
|
||||
}
|
||||
|
@ -1,10 +1,29 @@
|
||||
import { currentBackend } from '../backend';
|
||||
import confirm from '../components/UI/Confirm';
|
||||
import {
|
||||
MEDIA_DELETE_FAILURE,
|
||||
MEDIA_DELETE_REQUEST,
|
||||
MEDIA_DELETE_SUCCESS,
|
||||
MEDIA_DISPLAY_URL_FAILURE,
|
||||
MEDIA_DISPLAY_URL_REQUEST,
|
||||
MEDIA_DISPLAY_URL_SUCCESS,
|
||||
MEDIA_INSERT,
|
||||
MEDIA_LIBRARY_CLOSE,
|
||||
MEDIA_LIBRARY_CREATE,
|
||||
MEDIA_LIBRARY_OPEN,
|
||||
MEDIA_LOAD_FAILURE,
|
||||
MEDIA_LOAD_REQUEST,
|
||||
MEDIA_LOAD_SUCCESS,
|
||||
MEDIA_PERSIST_FAILURE,
|
||||
MEDIA_PERSIST_REQUEST,
|
||||
MEDIA_PERSIST_SUCCESS,
|
||||
MEDIA_REMOVE_INSERTED,
|
||||
} from '../constants';
|
||||
import { sanitizeSlug } from '../lib/urlHelper';
|
||||
import { basename, getBlobSHA } from '../lib/util';
|
||||
import { selectMediaFilePath, selectMediaFilePublicPath } from '../lib/util/media.util';
|
||||
import { selectEditingDraft } from '../reducers/entries';
|
||||
import { selectMediaDisplayURL, selectMediaFiles } from '../reducers/mediaLibrary';
|
||||
import { selectEditingDraft } from '../reducers/selectors/entryDraft';
|
||||
import { selectMediaDisplayURL, selectMediaFiles } from '../reducers/selectors/mediaLibrary';
|
||||
import { addSnackbar } from '../store/slices/snackbars';
|
||||
import { createAssetProxy } from '../valueObjects/AssetProxy';
|
||||
import { addDraftEntryMediaFile, removeDraftEntryMediaFile } from './entries';
|
||||
@ -25,24 +44,6 @@ import type {
|
||||
import type { RootState } from '../store';
|
||||
import type AssetProxy from '../valueObjects/AssetProxy';
|
||||
|
||||
export const MEDIA_LIBRARY_OPEN = 'MEDIA_LIBRARY_OPEN';
|
||||
export const MEDIA_LIBRARY_CLOSE = 'MEDIA_LIBRARY_CLOSE';
|
||||
export const MEDIA_LIBRARY_CREATE = 'MEDIA_LIBRARY_CREATE';
|
||||
export const MEDIA_INSERT = 'MEDIA_INSERT';
|
||||
export const MEDIA_REMOVE_INSERTED = 'MEDIA_REMOVE_INSERTED';
|
||||
export const MEDIA_LOAD_REQUEST = 'MEDIA_LOAD_REQUEST';
|
||||
export const MEDIA_LOAD_SUCCESS = 'MEDIA_LOAD_SUCCESS';
|
||||
export const MEDIA_LOAD_FAILURE = 'MEDIA_LOAD_FAILURE';
|
||||
export const MEDIA_PERSIST_REQUEST = 'MEDIA_PERSIST_REQUEST';
|
||||
export const MEDIA_PERSIST_SUCCESS = 'MEDIA_PERSIST_SUCCESS';
|
||||
export const MEDIA_PERSIST_FAILURE = 'MEDIA_PERSIST_FAILURE';
|
||||
export const MEDIA_DELETE_REQUEST = 'MEDIA_DELETE_REQUEST';
|
||||
export const MEDIA_DELETE_SUCCESS = 'MEDIA_DELETE_SUCCESS';
|
||||
export const MEDIA_DELETE_FAILURE = 'MEDIA_DELETE_FAILURE';
|
||||
export const MEDIA_DISPLAY_URL_REQUEST = 'MEDIA_DISPLAY_URL_REQUEST';
|
||||
export const MEDIA_DISPLAY_URL_SUCCESS = 'MEDIA_DISPLAY_URL_SUCCESS';
|
||||
export const MEDIA_DISPLAY_URL_FAILURE = 'MEDIA_DISPLAY_URL_FAILURE';
|
||||
|
||||
export function createMediaLibrary(instance: MediaLibraryInstance) {
|
||||
const api = {
|
||||
show: instance.show || (() => undefined),
|
||||
@ -219,7 +220,7 @@ export function persistMedia(file: File, opts: MediaOptions = {}) {
|
||||
const fileName = sanitizeSlug(file.name.toLowerCase(), config.slug);
|
||||
const existingFile = files.find(existingFile => existingFile.name.toLowerCase() === fileName);
|
||||
|
||||
const editingDraft = selectEditingDraft(state.entryDraft);
|
||||
const editingDraft = selectEditingDraft(state);
|
||||
|
||||
/**
|
||||
* Check for existing files of the same name before persisting. If no asset
|
||||
@ -308,7 +309,7 @@ export function deleteMedia(file: MediaFile) {
|
||||
dispatch(removeAsset(file.path));
|
||||
dispatch(removeDraftEntryMediaFile({ id: file.id }));
|
||||
} else {
|
||||
const editingDraft = selectEditingDraft(state.entryDraft);
|
||||
const editingDraft = selectEditingDraft(state);
|
||||
|
||||
dispatch(mediaDeleting());
|
||||
dispatch(removeAsset(file.path));
|
||||
|
@ -1,12 +1,9 @@
|
||||
import { SCROLL_SYNC_ENABLED, SET_SCROLL, TOGGLE_SCROLL } from '../constants';
|
||||
|
||||
import type { AnyAction } from 'redux';
|
||||
import type { ThunkDispatch } from 'redux-thunk';
|
||||
import type { RootState } from '../store';
|
||||
|
||||
export const SCROLL_SYNC_ENABLED = 'cms.scroll-sync-enabled';
|
||||
|
||||
export const TOGGLE_SCROLL = 'TOGGLE_SCROLL';
|
||||
export const SET_SCROLL = 'SET_SCROLL';
|
||||
|
||||
export function togglingScroll() {
|
||||
return {
|
||||
type: TOGGLE_SCROLL,
|
||||
|
@ -1,25 +1,21 @@
|
||||
import isEqual from 'lodash/isEqual';
|
||||
|
||||
import { currentBackend } from '../backend';
|
||||
import {
|
||||
QUERY_FAILURE,
|
||||
QUERY_REQUEST,
|
||||
QUERY_SUCCESS,
|
||||
SEARCH_CLEAR,
|
||||
SEARCH_ENTRIES_FAILURE,
|
||||
SEARCH_ENTRIES_REQUEST,
|
||||
SEARCH_ENTRIES_SUCCESS,
|
||||
} from '../constants';
|
||||
|
||||
import type { AnyAction } from 'redux';
|
||||
import type { ThunkDispatch } from 'redux-thunk';
|
||||
import type { Entry, SearchQueryResponse } from '../interface';
|
||||
import type { RootState } from '../store';
|
||||
|
||||
/*
|
||||
* Constant Declarations
|
||||
*/
|
||||
export const SEARCH_ENTRIES_REQUEST = 'SEARCH_ENTRIES_REQUEST';
|
||||
export const SEARCH_ENTRIES_SUCCESS = 'SEARCH_ENTRIES_SUCCESS';
|
||||
export const SEARCH_ENTRIES_FAILURE = 'SEARCH_ENTRIES_FAILURE';
|
||||
|
||||
export const QUERY_REQUEST = 'QUERY_REQUEST';
|
||||
export const QUERY_SUCCESS = 'QUERY_SUCCESS';
|
||||
export const QUERY_FAILURE = 'QUERY_FAILURE';
|
||||
|
||||
export const SEARCH_CLEAR = 'SEARCH_CLEAR';
|
||||
|
||||
/*
|
||||
* Simple Action Creators (Internal)
|
||||
* We still need to export them for tests
|
||||
|
@ -1,14 +1,11 @@
|
||||
import { currentBackend } from '../backend';
|
||||
import { STATUS_FAILURE, STATUS_REQUEST, STATUS_SUCCESS } from '../constants';
|
||||
import { addSnackbar, removeSnackbarById } from '../store/slices/snackbars';
|
||||
|
||||
import type { AnyAction } from 'redux';
|
||||
import type { ThunkDispatch } from 'redux-thunk';
|
||||
import type { RootState } from '../store';
|
||||
|
||||
export const STATUS_REQUEST = 'STATUS_REQUEST';
|
||||
export const STATUS_SUCCESS = 'STATUS_SUCCESS';
|
||||
export const STATUS_FAILURE = 'STATUS_FAILURE';
|
||||
|
||||
export function statusRequest() {
|
||||
return {
|
||||
type: STATUS_REQUEST,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { WAIT_UNTIL_ACTION } from '../store/middleware/waitUntilAction';
|
||||
import { WAIT_UNTIL_ACTION } from '../constants';
|
||||
|
||||
import type { AnyAction } from 'redux';
|
||||
import type { ThunkDispatch } from 'redux-thunk';
|
||||
|
Reference in New Issue
Block a user