Merge branch 'main' into next

This commit is contained in:
Daniel Lautzenheiser 2023-06-13 16:03:21 -04:00
commit 7ca5f6d1e6
9 changed files with 156 additions and 76 deletions

View File

@ -4,6 +4,7 @@ import flatten from 'lodash/flatten';
import get from 'lodash/get'; import get from 'lodash/get';
import isError from 'lodash/isError'; import isError from 'lodash/isError';
import uniq from 'lodash/uniq'; import uniq from 'lodash/uniq';
import { dirname } from 'path';
import { resolveFormat } from './formats/formats'; import { resolveFormat } from './formats/formats';
import { commitMessageFormatter, slugFormatter } from './lib/formatters'; import { commitMessageFormatter, slugFormatter } from './lib/formatters';
@ -40,12 +41,9 @@ import {
selectMediaFolders, selectMediaFolders,
} from './lib/util/collection.util'; } from './lib/util/collection.util';
import filterEntries from './lib/util/filter.util'; import filterEntries from './lib/util/filter.util';
import { import { DRAFT_MEDIA_FILES, selectMediaFilePublicPath } from './lib/util/media.util';
DRAFT_MEDIA_FILES,
selectMediaFilePath,
selectMediaFilePublicPath,
} from './lib/util/media.util';
import { selectCustomPath, slugFromCustomPath } from './lib/util/nested.util'; import { selectCustomPath, slugFromCustomPath } from './lib/util/nested.util';
import { isNullish } from './lib/util/null.util';
import { set } from './lib/util/object.util'; import { set } from './lib/util/object.util';
import { dateParsers, expandPath, extractTemplateVars } from './lib/widgets/stringTemplate'; import { dateParsers, expandPath, extractTemplateVars } from './lib/widgets/stringTemplate';
import createEntry from './valueObjects/createEntry'; import createEntry from './valueObjects/createEntry';
@ -63,46 +61,70 @@ import type {
DisplayURL, DisplayURL,
Entry, Entry,
EntryData, EntryData,
EntryDraft,
EventData, EventData,
FilterRule, FilterRule,
ImplementationEntry, ImplementationEntry,
MediaField, MediaField,
ObjectValue,
PersistArgs, PersistArgs,
SearchQueryResponse, SearchQueryResponse,
SearchResponse, SearchResponse,
UnknownField, UnknownField,
User, User,
ValueOrNestedValue,
} from './interface'; } from './interface';
import type { AsyncLock } from './lib/util'; import type { AsyncLock } from './lib/util';
import type { RootState } from './store'; import type { RootState } from './store';
import type AssetProxy from './valueObjects/AssetProxy'; import type AssetProxy from './valueObjects/AssetProxy';
function updateAssetProxies( function updatePath(entryPath: string, assetPath: string): string | null {
assetProxies: AssetProxy[], const pathDir = dirname(entryPath);
config: Config,
collection: Collection,
entryDraft: EntryDraft,
path: string,
) {
assetProxies.map(asset => {
// update media files path based on entry path
const oldPath = asset.path;
entryDraft.entry.path = path;
const folderPath = joinUrlPath( const pathParts = assetPath.split(DRAFT_MEDIA_FILES);
collection && 'folder' in collection ? collection.folder : '', const restOfPath = pathParts.length > 1 ? pathParts[1] : null;
DRAFT_MEDIA_FILES, if (restOfPath === null) {
); return null;
}
const newPath = selectMediaFilePath( return joinUrlPath(pathDir, restOfPath).replace(/\/\//g, '');
config, }
collection,
entryDraft.entry, function updateAssetFields(data: ValueOrNestedValue, path: string): ValueOrNestedValue {
oldPath.replace(folderPath, ''), if (
asset.field, isNullish(data) ||
); typeof data === 'number' ||
asset.path = newPath; typeof data === 'boolean' ||
data instanceof Date
) {
return data;
}
if (Array.isArray(data)) {
return data.map(child => updateAssetFields(child, path));
}
if (typeof data === 'object') {
return Object.keys(data).reduce((acc, key) => {
acc[key] = updateAssetFields(data[key], path);
return acc;
}, {} as ObjectValue);
}
const newPath = updatePath(path, data);
if (!newPath) {
return data;
}
return newPath;
}
function updateAssetProxies(assetProxies: AssetProxy[], path: string) {
assetProxies.forEach(asset => {
const newPath = updatePath(path, asset.path);
if (newPath) {
asset.path = newPath;
}
}); });
} }
@ -865,13 +887,16 @@ export class Backend<EF extends BaseField = UnknownField, BC extends BackendClas
customPath, customPath,
); );
const path = customPath || (selectEntryPath(collection, slug) ?? ''); const path = customPath || (selectEntryPath(collection, slug) ?? '');
entryDraft.entry.path = path;
entryDraft.entry.data = updateAssetFields(entryDraft.entry.data, path) as ObjectValue;
updateAssetProxies(assetProxies, path);
dataFile = { dataFile = {
path, path,
slug, slug,
raw: this.entryToRaw(collection, entryDraft.entry), raw: this.entryToRaw(collection, entryDraft.entry),
}; };
updateAssetProxies(assetProxies, config, collection, entryDraft, path);
} else { } else {
const slug = entryDraft.entry.slug; const slug = entryDraft.entry.slug;
dataFile = { dataFile = {

View File

@ -96,14 +96,23 @@ const EditorInterface = ({
slug, slug,
}: TranslatedProps<EditorInterfaceProps>) => { }: TranslatedProps<EditorInterfaceProps>) => {
const { locales, defaultLocale } = useMemo(() => getI18nInfo(collection), [collection]) ?? {}; const { locales, defaultLocale } = useMemo(() => getI18nInfo(collection), [collection]) ?? {};
const [selectedLocale, setSelectedLocale] = useState<string>(locales?.[1] ?? 'en'); const translatedLocales = useMemo(
() => locales?.filter(locale => locale !== defaultLocale) ?? [],
[locales, defaultLocale],
);
const [previewActive, setPreviewActive] = useState( const [previewActive, setPreviewActive] = useState(
localStorage.getItem(PREVIEW_VISIBLE) !== 'false', localStorage.getItem(PREVIEW_VISIBLE) !== 'false',
); );
const i18nEnabled = useMemo(() => locales && locales.length > 0, [locales]);
const [i18nActive, setI18nActive] = useState( const [i18nActive, setI18nActive] = useState(
Boolean(localStorage.getItem(I18N_VISIBLE) !== 'false' && locales && locales.length > 0), Boolean(localStorage.getItem(I18N_VISIBLE) !== 'false' && i18nEnabled),
);
const [selectedLocale, setSelectedLocale] = useState<string>(
(i18nActive ? translatedLocales?.[0] : defaultLocale) ?? 'en',
); );
useEffect(() => { useEffect(() => {
@ -132,8 +141,11 @@ const EditorInterface = ({
const handleToggleI18n = useCallback(() => { const handleToggleI18n = useCallback(() => {
const newI18nActive = !i18nActive; const newI18nActive = !i18nActive;
setI18nActive(newI18nActive); setI18nActive(newI18nActive);
setSelectedLocale(selectedLocale =>
newI18nActive && selectedLocale === defaultLocale ? translatedLocales?.[0] : selectedLocale,
);
localStorage.setItem(I18N_VISIBLE, `${newI18nActive}`); localStorage.setItem(I18N_VISIBLE, `${newI18nActive}`);
}, [i18nActive]); }, [i18nActive, setSelectedLocale, translatedLocales, defaultLocale]);
const handleLocaleChange = useCallback((locale: string) => { const handleLocaleChange = useCallback((locale: string) => {
setSelectedLocale(locale); setSelectedLocale(locale);
@ -174,40 +186,60 @@ const EditorInterface = ({
setShowMobilePreview(old => !old); setShowMobilePreview(old => !old);
}, []); }, []);
const editor = ( const editor = useMemo(
<div () => (
key={defaultLocale} <div
id="control-pane" key={defaultLocale}
className={classNames( id="control-pane"
` className={classNames(
w-full
`,
(finalPreviewActive || i18nActive) &&
` `
overflow-y-auto w-full
styled-scrollbars
h-main-mobile
md:h-main
`, `,
showMobilePreview && (finalPreviewActive || i18nActive) &&
` `
hidden overflow-y-auto
lg:block styled-scrollbars
`, h-main-mobile
)} md:h-main
> `,
<EditorControlPane showMobilePreview &&
collection={collection} `
entry={entry} hidden
fields={fields} lg:block
fieldsErrors={fieldsErrors} `,
locale={defaultLocale} )}
submitted={submitted} >
hideBorder={!finalPreviewActive && !i18nActive} <EditorControlPane
slug={slug} collection={collection}
t={t} entry={entry}
/> fields={fields}
</div> fieldsErrors={fieldsErrors}
locale={i18nActive ? defaultLocale : selectedLocale}
submitted={submitted}
hideBorder={!finalPreviewActive && !i18nActive}
canChangeLocale={i18nEnabled && !i18nActive}
onLocaleChange={handleLocaleChange}
slug={slug}
t={t}
/>
</div>
),
[
defaultLocale,
finalPreviewActive,
i18nActive,
showMobilePreview,
collection,
entry,
fields,
fieldsErrors,
selectedLocale,
submitted,
i18nEnabled,
handleLocaleChange,
slug,
t,
],
); );
const editorLocale = useMemo( const editorLocale = useMemo(
@ -232,6 +264,7 @@ const EditorInterface = ({
onLocaleChange={handleLocaleChange} onLocaleChange={handleLocaleChange}
submitted={submitted} submitted={submitted}
canChangeLocale canChangeLocale
context="i18nSplit"
hideBorder hideBorder
t={t} t={t}
/> />

View File

@ -31,6 +31,7 @@ export interface EditorControlPaneProps {
slug?: string; slug?: string;
onLocaleChange?: (locale: string) => void; onLocaleChange?: (locale: string) => void;
allowDefaultLocale?: boolean; allowDefaultLocale?: boolean;
context?: 'default' | 'i18nSplit';
} }
const EditorControlPane = ({ const EditorControlPane = ({
@ -45,6 +46,7 @@ const EditorControlPane = ({
slug, slug,
onLocaleChange, onLocaleChange,
allowDefaultLocale = false, allowDefaultLocale = false,
context = 'default',
t, t,
}: TranslatedProps<EditorControlPaneProps>) => { }: TranslatedProps<EditorControlPaneProps>) => {
const pathField = useMemo( const pathField = useMemo(
@ -120,7 +122,9 @@ const EditorControlPane = ({
})} })}
canChangeLocale={canChangeLocale} canChangeLocale={canChangeLocale}
onLocaleChange={onLocaleChange} onLocaleChange={onLocaleChange}
allowDefaultLocale={allowDefaultLocale} excludeLocales={
!allowDefaultLocale && context === 'i18nSplit' ? [i18n.defaultLocale] : []
}
/> />
</div> </div>
) : null} ) : null}

View File

@ -9,8 +9,8 @@ interface LocaleDropdownProps {
defaultLocale: string; defaultLocale: string;
dropdownText: string; dropdownText: string;
canChangeLocale: boolean; canChangeLocale: boolean;
allowDefaultLocale: boolean;
onLocaleChange?: (locale: string) => void; onLocaleChange?: (locale: string) => void;
excludeLocales?: string[];
} }
const LocaleDropdown = ({ const LocaleDropdown = ({
@ -18,8 +18,8 @@ const LocaleDropdown = ({
defaultLocale, defaultLocale,
dropdownText, dropdownText,
canChangeLocale, canChangeLocale,
allowDefaultLocale,
onLocaleChange, onLocaleChange,
excludeLocales = [defaultLocale],
}: LocaleDropdownProps) => { }: LocaleDropdownProps) => {
if (!canChangeLocale) { if (!canChangeLocale) {
return ( return (
@ -41,7 +41,7 @@ const LocaleDropdown = ({
<Menu label={dropdownText}> <Menu label={dropdownText}>
<MenuGroup> <MenuGroup>
{locales {locales
.filter(locale => allowDefaultLocale || locale !== defaultLocale) .filter(locale => !excludeLocales.includes(locale))
.map(locale => ( .map(locale => (
<MenuItemButton key={locale} onClick={() => onLocaleChange?.(locale)}> <MenuItemButton key={locale} onClick={() => onLocaleChange?.(locale)}>
{locale} {locale}

View File

@ -34,7 +34,7 @@ const Login = ({
<div className="flex flex-col h-screen items-center justify-center bg-slate-50 dark:bg-slate-900"> <div className="flex flex-col h-screen items-center justify-center bg-slate-50 dark:bg-slate-900">
{config?.logo_url ? ( {config?.logo_url ? (
<div <div
className="h-40 w-80 mb-4 bg-cover bg-no-repeat bg-center object-cover" className="h-40 w-80 mb-4 bg-contain bg-no-repeat bg-center object-cover"
style={{ backgroundImage: `url('${config.logo_url}')` }} style={{ backgroundImage: `url('${config.logo_url}')` }}
/> />
) : ( ) : (

View File

@ -87,7 +87,7 @@ const Navbar = ({
> >
{config?.logo_url ? ( {config?.logo_url ? (
<div <div
className="h-10 w-10 bg-cover bg-no-repeat bg-center object-cover" className="h-10 w-10 bg-contain bg-no-repeat bg-center object-cover"
style={{ backgroundImage: `url('${config.logo_url}')` }} style={{ backgroundImage: `url('${config.logo_url}')` }}
/> />
) : ( ) : (

View File

@ -320,7 +320,6 @@ export function selectMediaFilePath(
} }
let mediaFolder = selectMediaFolder(config, collection, entryMap, field, currentFolder); let mediaFolder = selectMediaFolder(config, collection, entryMap, field, currentFolder);
if (!currentFolder) { if (!currentFolder) {
let publicFolder = trim(config['public_folder'] ?? mediaFolder, '/'); let publicFolder = trim(config['public_folder'] ?? mediaFolder, '/');
let mediaPathDir = trim(dirname(mediaPath), '/'); let mediaPathDir = trim(dirname(mediaPath), '/');
@ -341,10 +340,14 @@ export function selectMediaFilePath(
collection, collection,
entryMap, entryMap,
field, field,
mediaPathDir.replace(publicFolder, mediaFolder), publicFolder === '' && mediaPathDir.startsWith(mediaFolder)
? mediaPathDir
: mediaPathDir.replace(publicFolder, mediaFolder),
); );
} }
} }
return joinUrlPath(mediaFolder, basename(mediaPath)); return mediaPath.startsWith(mediaFolder)
? mediaPath
: joinUrlPath(mediaFolder, basename(mediaPath));
} }

View File

@ -109,8 +109,17 @@ function mediaLibrary(
return { return {
...state, ...state,
isVisible: false, isVisible: false,
insertOptions: undefined, forImage: false,
forFolder: false,
controlID: undefined,
config: undefined,
collection: undefined,
collectionFile: undefined,
field: undefined,
value: undefined,
alt: undefined, alt: undefined,
replaceIndex: undefined,
insertOptions: undefined,
}; };
case MEDIA_INSERT: { case MEDIA_INSERT: {

View File

@ -1,5 +1,11 @@
{ {
"releases": [ "releases": [
{
"date": "2023-06-13T10:00:00.000Z",
"version": "v2.5.0",
"type": "minor",
"description": "Switch i18n locales while in preview mode"
},
{ {
"date": "2023-05-29T10:00:00.000Z", "date": "2023-05-29T10:00:00.000Z",
"version": "v2.4.4", "version": "v2.4.4",