diff --git a/frontend/app-development/features/textEditor/TextEditor.test.tsx b/frontend/app-development/features/textEditor/TextEditor.test.tsx
index aa1242afc12..963e305a824 100644
--- a/frontend/app-development/features/textEditor/TextEditor.test.tsx
+++ b/frontend/app-development/features/textEditor/TextEditor.test.tsx
@@ -36,6 +36,9 @@ jest.mock('react-router-dom', () => ({
},
}));
+// Need to mock the scrollIntoView function
+window.HTMLElement.prototype.scrollIntoView = jest.fn();
+
describe('TextEditor', () => {
it('renders the component', async () => {
await render();
diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json
index 8ef7c55ac55..bd0bc97ad33 100644
--- a/frontend/language/src/nb.json
+++ b/frontend/language/src/nb.json
@@ -163,7 +163,7 @@
"dashboard.search": "Søk etter applikasjon",
"dashboard.search_result": "Søkeresultat",
"dashboard.select_datamodelling_format": "Velg datamodelleringsformat",
- "dashboard.service_name_has_illegal_characters": "Navnet er ugyldig. Det kan kun inneholde små bokstaver, sifre og bindestreker (-), og må begynne på en bokstav og ende med en bokstav eller et tall.",
+ "dashboard.service_name_has_illegal_characters": "Navnet er ugyldig. Det må være minimum tre tegn, kan kun inneholde små bokstaver (utenom æ, ø, og å), sifre og bindestreker (-), og må begynne på en bokstav og ende med en bokstav eller et tall.",
"dashboard.service_name_is_too_long": "Repository-navnet kan maksimalt bestå av 30 tegn",
"dashboard.service_saved_name_description": "Unik identifikator for appen. Brukes som navn på repository samt i alle URLer og i Altinn sine APIer. Bør derfor være både kort og beskrivende, f.eks. \"sykemelding\" eller \"skattemelding-2019\".",
"dashboard.service_saved_name_description_cannot_be_changed": "Dette navnet kan ikke endres.",
diff --git a/frontend/packages/text-editor/src/TextEditor.test.tsx b/frontend/packages/text-editor/src/TextEditor.test.tsx
index aa774d5627d..97b26aa2cc9 100644
--- a/frontend/packages/text-editor/src/TextEditor.test.tsx
+++ b/frontend/packages/text-editor/src/TextEditor.test.tsx
@@ -8,6 +8,7 @@ import { ITextResource, ITextResources } from 'app-shared/types/global';
import * as testids from '../../../testing/testids';
const user = userEvent.setup();
+let mockScrollIntoView = jest.fn();
describe('TextEditor', () => {
const nb: ITextResource[] = [
@@ -37,6 +38,11 @@ describe('TextEditor', () => {
};
return rtlRender();
};
+ beforeEach(() => {
+ // Need to mock the scrollIntoView function
+ mockScrollIntoView = jest.fn();
+ window.HTMLElement.prototype.scrollIntoView = mockScrollIntoView;
+ });
it('fires upsertTextResource when Add new is clicked', async () => {
jest.spyOn(global.Math, 'random').mockReturnValue(0);
@@ -72,8 +78,8 @@ describe('TextEditor', () => {
user.click(
screen.getByRole('button', {
name: textMock('schema_editor.language_confirm_deletion'),
- })
- )
+ }),
+ ),
);
expect(handleDeleteLang).toHaveBeenCalledWith('en');
@@ -95,8 +101,8 @@ describe('TextEditor', () => {
user.click(
screen.getByRole('button', {
name: textMock('schema_editor.language_confirm_deletion'),
- })
- )
+ }),
+ ),
);
expect(handleDeleteLang).toHaveBeenCalledWith('en');
@@ -122,6 +128,19 @@ describe('TextEditor', () => {
expect(setSelectedLangCodes).toHaveBeenCalledWith(['nb', 'en']);
});
+ it('Calls ScrollIntoView when a new languages is selected', async () => {
+ renderTextEditor({
+ availableLanguages: ['nb', 'en', 'tw', 'ku'],
+ selectedLangCodes: ['nb', 'en', 'tw'],
+ });
+ const kurdishCheckbox = screen.getByRole('checkbox', {
+ name: /kurdisk/i,
+ });
+ await act(() => user.click(kurdishCheckbox));
+
+ expect(mockScrollIntoView).toHaveBeenCalledTimes(1);
+ });
+
it('signals correctly when a translation is changed', async () => {
const upsertTextResource = jest.fn();
renderTextEditor({
@@ -158,8 +177,8 @@ describe('TextEditor', () => {
user.click(
screen.getByRole('button', {
name: textMock('schema_editor.textRow-deletion-confirm'),
- })
- )
+ }),
+ ),
);
await expect(onTextIdChange).toHaveBeenCalledWith({ oldId: nb[0].id });
diff --git a/frontend/packages/text-editor/src/TextEditor.tsx b/frontend/packages/text-editor/src/TextEditor.tsx
index 0d2a6528b44..3e49dd7669b 100644
--- a/frontend/packages/text-editor/src/TextEditor.tsx
+++ b/frontend/packages/text-editor/src/TextEditor.tsx
@@ -1,4 +1,4 @@
-import React, { useMemo } from 'react';
+import React, { useEffect, useMemo, useRef } from 'react';
import classes from './TextEditor.module.css';
import type {
LangCode,
@@ -43,11 +43,28 @@ export const TextEditor = ({
}: TextEditorProps) => {
const { t } = useTranslation();
const resourceRows = mapResourceFilesToTableRows(textResourceFiles);
+
+ const previousSelectedLanguages = useRef([]);
+
const availableLangCodesFiltered = useMemo(
() => availableLanguages?.filter((code) => ISO6391.validate(code)),
[availableLanguages],
);
+ useEffect(() => {
+ const addedLanguage = selectedLangCodes.find(
+ (lang) => !previousSelectedLanguages.current.includes(lang),
+ );
+
+ if (addedLanguage) {
+ const elementToFocus: HTMLElement = document.getElementById('header-lang' + addedLanguage);
+ if (elementToFocus) {
+ elementToFocus.scrollIntoView({ behavior: 'smooth', inline: 'center' });
+ }
+ }
+ previousSelectedLanguages.current = selectedLangCodes;
+ }, [selectedLangCodes.length, selectedLangCodes]);
+
const handleAddNewEntryClick = () => {
const textId = `id_${getRandNumber()}`;
availableLangCodesFiltered.forEach((language) =>
diff --git a/frontend/packages/text-editor/src/TextList.tsx b/frontend/packages/text-editor/src/TextList.tsx
index 09bd15a7313..808cee35296 100644
--- a/frontend/packages/text-editor/src/TextList.tsx
+++ b/frontend/packages/text-editor/src/TextList.tsx
@@ -26,6 +26,7 @@ export const TextList = ({
}: TextListProps) => {
const textIds = useMemo(() => resourceRows.map((row) => row.textKey), [resourceRows]);
const idExists = (textId: string): boolean => textIds.includes(textId);
+ const getTableHeaderCellId = (language: string): string => `header-lang${language}`;
return (
@@ -33,7 +34,9 @@ export const TextList = ({
{selectedLanguages.map((language) => (
- {getLangName({ code: language })}
+
))}
Tekstnøkkel
Variabler
diff --git a/frontend/packages/ux-editor/src/containers/DesignView/DesignView.tsx b/frontend/packages/ux-editor/src/containers/DesignView/DesignView.tsx
index fadd6f02ad4..0821cfaf25d 100644
--- a/frontend/packages/ux-editor/src/containers/DesignView/DesignView.tsx
+++ b/frontend/packages/ux-editor/src/containers/DesignView/DesignView.tsx
@@ -39,7 +39,11 @@ export const DesignView = (): ReactNode => {
const dispatch = useDispatch();
const { org, app } = useStudioUrlParams();
const { selectedLayoutSet } = useAppContext();
- const addLayoutMutation = useAddLayoutMutation(org, app, selectedLayoutSet);
+ const { mutate: addLayoutMutation, isPending } = useAddLayoutMutation(
+ org,
+ app,
+ selectedLayoutSet,
+ );
const { data: layouts } = useFormLayoutsQuery(org, app, selectedLayoutSet);
const { data: instanceId } = useInstanceIdQuery(org, app);
const { data: formLayoutSettings } = useFormLayoutSettingsQuery(org, app, selectedLayoutSet);
@@ -99,23 +103,25 @@ export const DesignView = (): ReactNode => {
*/
const handleAddPage = (isReceipt: boolean) => {
if (isReceipt) {
- addLayoutMutation.mutate({ layoutName: 'Kvittering', isReceiptPage: true });
+ addLayoutMutation({ layoutName: 'Kvittering', isReceiptPage: true });
setSearchParams((prevParams) => ({ ...prevParams, layout: 'Kvittering' }));
setOpenAccordion('Kvittering');
} else {
- let newNum = 1;
- let newLayoutName = `${t('ux_editor.page')}${layoutOrder.length + newNum}`;
+ if (!isPending) {
+ let newNum = 1;
+ let newLayoutName = `${t('ux_editor.page')}${layoutOrder.length + newNum}`;
- while (layoutOrder.indexOf(newLayoutName) > -1) {
- newNum += 1;
- newLayoutName = `${t('ux_editor.page')}${newNum}`;
- }
+ while (layoutOrder.indexOf(newLayoutName) > -1) {
+ newNum += 1;
+ newLayoutName = `${t('ux_editor.page')}${newNum}`;
+ }
- addLayoutMutation.mutate({ layoutName: newLayoutName, isReceiptPage: false });
- setSearchParams((prevParams) => ({ ...prevParams, layout: newLayoutName }));
- setSelectedLayoutInLocalStorage(instanceId, newLayoutName);
- dispatch(FormLayoutActions.updateSelectedLayout(newLayoutName));
- setOpenAccordion(newLayoutName);
+ addLayoutMutation({ layoutName: newLayoutName, isReceiptPage: false });
+ setSearchParams((prevParams) => ({ ...prevParams, layout: newLayoutName }));
+ setSelectedLayoutInLocalStorage(instanceId, newLayoutName);
+ dispatch(FormLayoutActions.updateSelectedLayout(newLayoutName));
+ setOpenAccordion(newLayoutName);
+ }
}
};