diff --git a/packages/core/src/backend.ts b/packages/core/src/backend.ts index 813ea934..618cb7df 100644 --- a/packages/core/src/backend.ts +++ b/packages/core/src/backend.ts @@ -4,6 +4,7 @@ import flatten from 'lodash/flatten'; import get from 'lodash/get'; import isError from 'lodash/isError'; import uniq from 'lodash/uniq'; +import { dirname } from 'path'; import { resolveFormat } from './formats/formats'; import { commitMessageFormatter, slugFormatter } from './lib/formatters'; @@ -40,12 +41,9 @@ import { selectMediaFolders, } from './lib/util/collection.util'; import filterEntries from './lib/util/filter.util'; -import { - DRAFT_MEDIA_FILES, - selectMediaFilePath, - selectMediaFilePublicPath, -} from './lib/util/media.util'; +import { DRAFT_MEDIA_FILES, selectMediaFilePublicPath } from './lib/util/media.util'; import { selectCustomPath, slugFromCustomPath } from './lib/util/nested.util'; +import { isNullish } from './lib/util/null.util'; import { set } from './lib/util/object.util'; import { dateParsers, expandPath, extractTemplateVars } from './lib/widgets/stringTemplate'; import createEntry from './valueObjects/createEntry'; @@ -63,46 +61,70 @@ import type { DisplayURL, Entry, EntryData, - EntryDraft, EventData, FilterRule, ImplementationEntry, MediaField, + ObjectValue, PersistArgs, SearchQueryResponse, SearchResponse, UnknownField, User, + ValueOrNestedValue, } from './interface'; import type { AsyncLock } from './lib/util'; import type { RootState } from './store'; import type AssetProxy from './valueObjects/AssetProxy'; -function updateAssetProxies( - assetProxies: AssetProxy[], - 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; +function updatePath(entryPath: string, assetPath: string): string | null { + const pathDir = dirname(entryPath); - const folderPath = joinUrlPath( - collection && 'folder' in collection ? collection.folder : '', - DRAFT_MEDIA_FILES, - ); + const pathParts = assetPath.split(DRAFT_MEDIA_FILES); + const restOfPath = pathParts.length > 1 ? pathParts[1] : null; + if (restOfPath === null) { + return null; + } - const newPath = selectMediaFilePath( - config, - collection, - entryDraft.entry, - oldPath.replace(folderPath, ''), - asset.field, - ); - asset.path = newPath; + return joinUrlPath(pathDir, restOfPath).replace(/\/\//g, ''); +} + +function updateAssetFields(data: ValueOrNestedValue, path: string): ValueOrNestedValue { + if ( + isNullish(data) || + typeof data === 'number' || + 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) => { const { locales, defaultLocale } = useMemo(() => getI18nInfo(collection), [collection]) ?? {}; - const [selectedLocale, setSelectedLocale] = useState(locales?.[1] ?? 'en'); + const translatedLocales = useMemo( + () => locales?.filter(locale => locale !== defaultLocale) ?? [], + [locales, defaultLocale], + ); const [previewActive, setPreviewActive] = useState( localStorage.getItem(PREVIEW_VISIBLE) !== 'false', ); + const i18nEnabled = useMemo(() => locales && locales.length > 0, [locales]); + 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( + (i18nActive ? translatedLocales?.[0] : defaultLocale) ?? 'en', ); useEffect(() => { @@ -132,8 +141,11 @@ const EditorInterface = ({ const handleToggleI18n = useCallback(() => { const newI18nActive = !i18nActive; setI18nActive(newI18nActive); + setSelectedLocale(selectedLocale => + newI18nActive && selectedLocale === defaultLocale ? translatedLocales?.[0] : selectedLocale, + ); localStorage.setItem(I18N_VISIBLE, `${newI18nActive}`); - }, [i18nActive]); + }, [i18nActive, setSelectedLocale, translatedLocales, defaultLocale]); const handleLocaleChange = useCallback((locale: string) => { setSelectedLocale(locale); @@ -174,40 +186,60 @@ const EditorInterface = ({ setShowMobilePreview(old => !old); }, []); - const editor = ( -
( + + (finalPreviewActive || i18nActive) && + ` + overflow-y-auto + styled-scrollbars + h-main-mobile + md:h-main + `, + showMobilePreview && + ` + hidden + lg:block + `, + )} + > + +
+ ), + [ + defaultLocale, + finalPreviewActive, + i18nActive, + showMobilePreview, + collection, + entry, + fields, + fieldsErrors, + selectedLocale, + submitted, + i18nEnabled, + handleLocaleChange, + slug, + t, + ], ); const editorLocale = useMemo( @@ -232,6 +264,7 @@ const EditorInterface = ({ onLocaleChange={handleLocaleChange} submitted={submitted} canChangeLocale + context="i18nSplit" hideBorder t={t} /> diff --git a/packages/core/src/components/entry-editor/editor-control-pane/EditorControlPane.tsx b/packages/core/src/components/entry-editor/editor-control-pane/EditorControlPane.tsx index 22a23ab3..8908abe8 100644 --- a/packages/core/src/components/entry-editor/editor-control-pane/EditorControlPane.tsx +++ b/packages/core/src/components/entry-editor/editor-control-pane/EditorControlPane.tsx @@ -31,6 +31,7 @@ export interface EditorControlPaneProps { slug?: string; onLocaleChange?: (locale: string) => void; allowDefaultLocale?: boolean; + context?: 'default' | 'i18nSplit'; } const EditorControlPane = ({ @@ -45,6 +46,7 @@ const EditorControlPane = ({ slug, onLocaleChange, allowDefaultLocale = false, + context = 'default', t, }: TranslatedProps) => { const pathField = useMemo( @@ -120,7 +122,9 @@ const EditorControlPane = ({ })} canChangeLocale={canChangeLocale} onLocaleChange={onLocaleChange} - allowDefaultLocale={allowDefaultLocale} + excludeLocales={ + !allowDefaultLocale && context === 'i18nSplit' ? [i18n.defaultLocale] : [] + } /> ) : null} diff --git a/packages/core/src/components/entry-editor/editor-control-pane/LocaleDropdown.tsx b/packages/core/src/components/entry-editor/editor-control-pane/LocaleDropdown.tsx index 0ee96c94..c54f2531 100644 --- a/packages/core/src/components/entry-editor/editor-control-pane/LocaleDropdown.tsx +++ b/packages/core/src/components/entry-editor/editor-control-pane/LocaleDropdown.tsx @@ -9,8 +9,8 @@ interface LocaleDropdownProps { defaultLocale: string; dropdownText: string; canChangeLocale: boolean; - allowDefaultLocale: boolean; onLocaleChange?: (locale: string) => void; + excludeLocales?: string[]; } const LocaleDropdown = ({ @@ -18,8 +18,8 @@ const LocaleDropdown = ({ defaultLocale, dropdownText, canChangeLocale, - allowDefaultLocale, onLocaleChange, + excludeLocales = [defaultLocale], }: LocaleDropdownProps) => { if (!canChangeLocale) { return ( @@ -41,7 +41,7 @@ const LocaleDropdown = ({ {locales - .filter(locale => allowDefaultLocale || locale !== defaultLocale) + .filter(locale => !excludeLocales.includes(locale)) .map(locale => ( onLocaleChange?.(locale)}> {locale} diff --git a/packages/core/src/components/login/Login.tsx b/packages/core/src/components/login/Login.tsx index ab66e7bc..e489b663 100644 --- a/packages/core/src/components/login/Login.tsx +++ b/packages/core/src/components/login/Login.tsx @@ -34,7 +34,7 @@ const Login = ({
{config?.logo_url ? (
) : ( diff --git a/packages/core/src/components/navbar/Navbar.tsx b/packages/core/src/components/navbar/Navbar.tsx index df6d75d3..397275f6 100644 --- a/packages/core/src/components/navbar/Navbar.tsx +++ b/packages/core/src/components/navbar/Navbar.tsx @@ -87,7 +87,7 @@ const Navbar = ({ > {config?.logo_url ? (
) : ( diff --git a/packages/core/src/lib/util/media.util.ts b/packages/core/src/lib/util/media.util.ts index 51a2dda6..98b2c252 100644 --- a/packages/core/src/lib/util/media.util.ts +++ b/packages/core/src/lib/util/media.util.ts @@ -320,7 +320,6 @@ export function selectMediaFilePath( } let mediaFolder = selectMediaFolder(config, collection, entryMap, field, currentFolder); - if (!currentFolder) { let publicFolder = trim(config['public_folder'] ?? mediaFolder, '/'); let mediaPathDir = trim(dirname(mediaPath), '/'); @@ -341,10 +340,14 @@ export function selectMediaFilePath( collection, entryMap, 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)); } diff --git a/packages/core/src/reducers/mediaLibrary.ts b/packages/core/src/reducers/mediaLibrary.ts index 70f1fe52..d516225c 100644 --- a/packages/core/src/reducers/mediaLibrary.ts +++ b/packages/core/src/reducers/mediaLibrary.ts @@ -109,8 +109,17 @@ function mediaLibrary( return { ...state, isVisible: false, - insertOptions: undefined, + forImage: false, + forFolder: false, + controlID: undefined, + config: undefined, + collection: undefined, + collectionFile: undefined, + field: undefined, + value: undefined, alt: undefined, + replaceIndex: undefined, + insertOptions: undefined, }; case MEDIA_INSERT: { diff --git a/packages/docs/content/releases.json b/packages/docs/content/releases.json index 52ec77fb..22945c6a 100644 --- a/packages/docs/content/releases.json +++ b/packages/docs/content/releases.json @@ -1,5 +1,11 @@ { "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", "version": "v2.4.4",