Skip to content

Commit

Permalink
Fix expander states for invalid fields
Browse files Browse the repository at this point in the history
  • Loading branch information
kyoshino committed Jul 2, 2024
1 parent 65d3b7b commit d5afb6f
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 34 deletions.
16 changes: 6 additions & 10 deletions src/lib/components/contents/details/preview/field-preview.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@
import { escapeRegExp } from '@sveltia/utils/string';
import { previews } from '$lib/components/contents/details/widgets';
import { entryDraft } from '$lib/services/contents/draft';
import {
joinExpanderKeyPathSegments,
syncExpanderStates,
} from '$lib/services/contents/draft/editor';
import { getExpanderKeys, syncExpanderStates } from '$lib/services/contents/draft/editor';
import { defaultI18nConfig } from '$lib/services/contents/i18n';
/**
Expand Down Expand Up @@ -33,20 +30,21 @@
? /** @type {RelationField | SelectField} */ (fieldConfig).multiple
: undefined;
$: isList = widgetName === 'list' || (hasMultiple && multiple);
$: ({ collection, collectionFile, currentValues } =
$: ({ collectionName, fileName, collection, collectionFile, currentValues } =
$entryDraft ?? /** @type {EntryDraft} */ ({}));
$: valueMap = currentValues[locale];
$: ({ i18nEnabled, defaultLocale } = (collectionFile ?? collection)?._i18n ?? defaultI18nConfig);
$: canTranslate = i18nEnabled && (i18n === true || i18n === 'translate');
$: canDuplicate = i18nEnabled && i18n === 'duplicate';
$: keyPathRegex = new RegExp(`^${escapeRegExp(keyPath)}\\.\\d+$`);
// Multiple values are flattened in the value map object
$: currentValue = isList
? Object.entries(currentValues[locale])
? Object.entries(valueMap)
.filter(([_keyPath]) => _keyPath.match(keyPathRegex))
.map(([, val]) => val)
.filter((val) => val !== undefined)
: currentValues[locale][keyPath];
: valueMap[keyPath];
/**
* Called whenever the preview field is clicked. Highlight the corresponding editor field by
Expand All @@ -55,9 +53,7 @@
const highlightEditorField = () => {
syncExpanderStates(
Object.fromEntries(
keyPath
.split('.')
.map((_key, index, arr) => [joinExpanderKeyPathSegments(arr, index + 1), true]),
getExpanderKeys({ collectionName, fileName, valueMap, keyPath }).map((key) => [key, true]),
),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,13 @@
// Initialize the expander state
syncExpanderStates({
[parentExpandedKeyPath]: !minimizeCollapsed,
...Object.fromEntries(items.map((__, index) => [`${keyPath}.${index}`, !collapsed])),
...Object.fromEntries(
items.map((__, index) => {
const key = `${keyPath}.${index}`;
return [key, expanderStates?._[key] ?? !collapsed];
}),
),
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@
widgetId = generateUUID('short');
// Initialize the expander state
syncExpanderStates({ [parentExpandedKeyPath]: !collapsed });
syncExpanderStates({
[parentExpandedKeyPath]: expanderStates?._[parentExpandedKeyPath] ?? !collapsed,
});
});
/**
Expand Down
76 changes: 61 additions & 15 deletions src/lib/services/contents/draft/editor.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { IndexedDB, LocalStorage } from '@sveltia/utils/storage';
import equal from 'fast-deep-equal';
import { get, writable } from 'svelte/store';
import { entryDraft } from '$lib/services/contents/draft';
import { backend } from '$lib/services/backends';
import { entryDraft } from '$lib/services/contents/draft';
import { getFieldConfig } from '$lib/services/contents/entry';

/** @type {IndexedDB | null | undefined} */
let settingsDB = undefined;
Expand Down Expand Up @@ -76,30 +77,75 @@ export const syncExpanderStates = (stateMap) => {
};

/**
* Join key path segments for the expander UI state.
* @param {string[]} arr - Key path array, e.g. `testimonials.0.authors.2.foo.bar`.
* @param {number} end - End index for `Array.slice()`.
* @returns {string} Joined string, e.g. `testimonials.0.authors.10.foo#.bar#`.
* Get a list of keys for the expander states, given the key path. The returned keys could include
* nested lists and objects.
* @param {object} args - Partial arguments for {@link getFieldConfig}.
* @param {string} args.collectionName - Collection name.
* @param {string} [args.fileName] - File name.
* @param {FlattenedEntryContent} args.valueMap - Object holding current entry values.
* @param {FieldKeyPath} args.keyPath - Key path, e.g. `testimonials.0.authors.2.foo`.
* @returns {string[]} Keys, e.g. `['testimonials', 'testimonials.0', 'testimonials.0.authors',
* 'testimonials.0.authors.2', 'testimonials.0.authors.2.foo']`.
*/
export const joinExpanderKeyPathSegments = (arr, end) =>
arr
.slice(0, end)
.map((k) => `${k}#`)
.join('.')
.replaceAll(/#\.(\d+)#/g, '.$1');
export const getExpanderKeys = ({ collectionName, fileName, valueMap, keyPath }) => {
const keys = new Set();

keyPath.split('.').forEach((_keyPart, index, arr) => {
const _keyPath = arr.slice(0, index + 1).join('.');
const config = getFieldConfig({ collectionName, fileName, valueMap, keyPath: _keyPath });

if (config?.widget === 'object') {
if (_keyPath.match(/\.\d+$/)) {
keys.add(_keyPath);
}

keys.add(`${_keyPath}#`);
} else if (config?.widget === 'list') {
keys.add(_keyPath.match(/\.\d+$/) ? _keyPath : `${_keyPath}#`);
} else if (index > 0) {
const parentKeyPath = arr.slice(0, index).join('.');

const parentConfig = getFieldConfig({
collectionName,
fileName,
valueMap,
keyPath: parentKeyPath,
});

if (parentConfig?.widget === 'object' && /** @type {ObjectField} */ (parentConfig).fields) {
keys.add(`${parentKeyPath}.${parentConfig.name}#`);
}

if (parentConfig?.widget === 'list' && /** @type {ListField} */ (parentConfig).field) {
keys.add(_keyPath);
}
}
});

return [...keys];
};

/**
* Expand any invalid fields, including the parent list/object(s).
* @param {object} args - Partial arguments for {@link getFieldConfig}.
* @param {string} args.collectionName - Collection name.
* @param {string} [args.fileName] - File name.
* @param {Record<LocaleCode, FlattenedEntryContent>} args.currentValues - Field values.
*/
export const expandInvalidFields = () => {
export const expandInvalidFields = ({ collectionName, fileName, currentValues }) => {
/** @type {Record<FieldKeyPath, boolean>} */
const stateMap = {};

Object.values(get(entryDraft)?.validities ?? {}).forEach((validities) => {
Object.entries(get(entryDraft)?.validities ?? {}).forEach(([locale, validities]) => {
Object.entries(validities).forEach(([keyPath, { valid }]) => {
if (!valid) {
keyPath.split('.').forEach((_key, index, arr) => {
stateMap[joinExpanderKeyPathSegments(arr, index + 1)] = true;
getExpanderKeys({
collectionName,
fileName,
valueMap: currentValues[locale],
keyPath,
}).forEach((key) => {
stateMap[key] = true;
});
}
});
Expand Down
15 changes: 8 additions & 7 deletions src/lib/services/contents/draft/save.js
Original file line number Diff line number Diff line change
Expand Up @@ -574,13 +574,6 @@ export const createSavingEntryData = async ({
* @throws {Error} When the entry could not be validated or saved.
*/
export const saveEntry = async ({ skipCI = undefined } = {}) => {
if (!validateEntry()) {
expandInvalidFields();

throw new Error('validation_failed');
}

const _user = /** @type {User} */ (get(user));
const draft = /** @type {EntryDraft} */ (get(entryDraft));

const {
Expand All @@ -596,6 +589,14 @@ export const saveEntry = async ({ skipCI = undefined } = {}) => {
files,
} = draft;

if (!validateEntry()) {
expandInvalidFields({ collectionName, fileName, currentValues });

throw new Error('validation_failed');
}

const _user = /** @type {User} */ (get(user));

const {
identifier_field: identifierField = 'title',
slug: slugTemplate = `{{${identifierField}}}`,
Expand Down

0 comments on commit d5afb6f

Please sign in to comment.