## Summary This preserves percent-encoded payloads when normalizing links fields. `lowercaseUrlOriginAndRemoveTrailingSlash` was decoding the path and query string while lowercasing the URL origin. That changes URLs where encoded payloads are semantically significant, such as Google Maps links containing `%2F` segments. Closes #18698. ## Changes - stop decoding the path/query payload in `lowercaseUrlOriginAndRemoveTrailingSlash` - preserve the raw path, query, and hash while still lowercasing the origin and trimming a trailing slash - update shared URL normalization tests to assert encoded payloads stay encoded - add a server-side regression test covering imported links field normalization ## Validation - `corepack yarn jest --config packages/twenty-shared/jest.config.mjs packages/twenty-shared/src/utils/url/__tests__/lowercaseUrlOriginAndRemoveTrailingSlash.test.ts --runInBand` - `corepack yarn jest --config packages/twenty-server/jest.config.mjs packages/twenty-server/src/engine/core-modules/record-transformer/utils/__tests__/transform-links-value.util.spec.ts --runInBand` - `corepack yarn nx test twenty-server --runInBand --testFile=src/engine/core-modules/record-transformer/utils/__tests__/transform-links-value.util.spec.ts` --------- Co-authored-by: Charles Bochet <charles@twenty.com>
69 lines
2.2 KiB
TypeScript
69 lines
2.2 KiB
TypeScript
import { NavigationMenuItemType } from 'twenty-shared/types';
|
|
import { isDefined, normalizeUrl } from 'twenty-shared/utils';
|
|
import { v4 } from 'uuid';
|
|
import type { NavigationMenuItem } from '~/generated-metadata/graphql';
|
|
|
|
import { DEFAULT_NAVIGATION_MENU_ITEM_COLOR_LINK } from '@/navigation-menu-item/common/constants/NavigationMenuItemDefaultColorLink';
|
|
import { navigationMenuItemsDraftState } from '@/navigation-menu-item/common/states/navigationMenuItemsDraftState';
|
|
import { computeInsertIndexAndPosition } from '@/navigation-menu-item/common/utils/computeInsertIndexAndPosition';
|
|
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
|
|
|
|
export const useAddLinkToNavigationMenuDraft = () => {
|
|
const setNavigationMenuItemsDraft = useSetAtomState(
|
|
navigationMenuItemsDraftState,
|
|
);
|
|
|
|
const addLinkToDraft = (
|
|
label: string,
|
|
url: string,
|
|
currentDraft: NavigationMenuItem[],
|
|
targetFolderId?: string | null,
|
|
targetIndex?: number,
|
|
): string => {
|
|
const normalizedUrl = normalizeUrl(url);
|
|
const folderId = targetFolderId ?? null;
|
|
|
|
const itemsInFolder = currentDraft.filter(
|
|
(item) =>
|
|
(item.folderId ?? null) === folderId &&
|
|
!isDefined(item.userWorkspaceId),
|
|
);
|
|
const index = targetIndex ?? itemsInFolder.length;
|
|
|
|
const { flatIndex, position } = computeInsertIndexAndPosition(
|
|
currentDraft,
|
|
folderId,
|
|
index,
|
|
);
|
|
|
|
const newItemId = v4();
|
|
const newItem: NavigationMenuItem = {
|
|
__typename: 'NavigationMenuItem',
|
|
id: newItemId,
|
|
type: NavigationMenuItemType.LINK,
|
|
viewId: undefined,
|
|
targetObjectMetadataId: undefined,
|
|
targetRecordId: undefined,
|
|
folderId: folderId ?? undefined,
|
|
position,
|
|
userWorkspaceId: undefined,
|
|
name: label.trim() || 'Link',
|
|
link: normalizedUrl,
|
|
color: DEFAULT_NAVIGATION_MENU_ITEM_COLOR_LINK,
|
|
applicationId: undefined,
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
};
|
|
|
|
const newDraft = [
|
|
...currentDraft.slice(0, flatIndex),
|
|
newItem,
|
|
...currentDraft.slice(flatIndex),
|
|
];
|
|
setNavigationMenuItemsDraft(newDraft);
|
|
return newItemId;
|
|
};
|
|
|
|
return { addLinkToDraft };
|
|
};
|