diff --git a/cypress/e2e/item/create/createDocument.cy.ts b/cypress/e2e/item/create/createDocument.cy.ts
index 829634607..0203c77bf 100644
--- a/cypress/e2e/item/create/createDocument.cy.ts
+++ b/cypress/e2e/item/create/createDocument.cy.ts
@@ -4,13 +4,7 @@ import {
PackedFolderItemFactory,
} from '@graasp/sdk';
-import {
- CREATE_ITEM_BUTTON_ID,
- CREATE_ITEM_DOCUMENT_ID,
- ITEM_FORM_CONFIRM_BUTTON_ID,
- ITEM_FORM_DISPLAY_NAME_INPUT_ID,
- ITEM_FORM_NAME_INPUT_ID,
-} from '@/config/selectors';
+import { ITEM_FORM_CONFIRM_BUTTON_ID } from '@/config/selectors';
import { HOME_PATH, buildItemPath } from '../../../../src/config/paths';
import ItemLayoutMode from '../../../../src/enums/itemLayoutMode';
@@ -74,19 +68,4 @@ describe('Create Document', () => {
true,
);
});
-
- it('autoComplete document displayName', () => {
- cy.setUpApi();
- cy.visit(HOME_PATH);
-
- cy.switchMode(ItemLayoutMode.List);
-
- cy.get(`#${CREATE_ITEM_BUTTON_ID}`).click();
- cy.get(`#${CREATE_ITEM_DOCUMENT_ID}`).click();
- cy.get(`#${ITEM_FORM_NAME_INPUT_ID}`).type('Test Name');
- cy.get(`#${ITEM_FORM_DISPLAY_NAME_INPUT_ID}`).should(
- 'have.value',
- 'Test Name',
- );
- });
});
diff --git a/cypress/fixtures/links.ts b/cypress/fixtures/links.ts
index df0637f5b..e71f910a7 100644
--- a/cypress/fixtures/links.ts
+++ b/cypress/fixtures/links.ts
@@ -55,6 +55,10 @@ export const YOUTUBE_LINK_ITEM: LinkItemType = PackedLinkItemFactory({
thumbnails: ['https://i.ytimg.com/vi/FmiEgBMTPLo/maxresdefault.jpg'],
icons: ['https://www.youtube.com/s/desktop/f0ff6c1d/img/favicon_96.png'],
}),
+ settings: {
+ // this is necessary for Youtube to show the embed
+ showLinkIframe: true,
+ },
});
export const INVALID_LINK_ITEM: LinkItemType = PackedLinkItemFactory({
diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts
index f1d3ff5c8..7f47f6068 100644
--- a/cypress/support/commands.ts
+++ b/cypress/support/commands.ts
@@ -54,6 +54,7 @@ import {
mockGetItems,
mockGetItemsTags,
mockGetLatestValidationGroup,
+ mockGetLinkMetadata,
mockGetManyPublishItemInformations,
mockGetMember,
mockGetMemberMentions,
@@ -341,6 +342,8 @@ Cypress.Commands.add(
mockPatchShortLink(cachedShortLinks, patchShortLinkError);
mockDeleteShortLink(cachedShortLinks, deleteShortLinkError);
+
+ mockGetLinkMetadata();
},
);
diff --git a/cypress/support/server.ts b/cypress/support/server.ts
index 9ed69e920..294fe7d40 100644
--- a/cypress/support/server.ts
+++ b/cypress/support/server.ts
@@ -2195,3 +2195,29 @@ export const mockDeleteShortLink = (
},
).as('deleteShortLink');
};
+
+export const mockGetLinkMetadata = (): void => {
+ cy.intercept(
+ {
+ method: HttpMethod.Get,
+ url: new RegExp(`${API_HOST}/items/embedded-links/metadata*`),
+ },
+ ({ reply, url }) => {
+ let linkUrl = new URL(url).searchParams.get('link');
+
+ if (!linkUrl.includes('http')) {
+ linkUrl = `https://${linkUrl}`;
+ }
+ if (URL.canParse(linkUrl)) {
+ return reply({
+ title: 'Page title',
+ description: 'Page description',
+ html: '',
+ icons: [],
+ thumbnails: [],
+ });
+ }
+ return reply({ statusCode: StatusCodes.BAD_REQUEST });
+ },
+ ).as('getLinkMetadata');
+};
diff --git a/cypress/support/viewUtils.ts b/cypress/support/viewUtils.ts
index 752973913..c7636cceb 100644
--- a/cypress/support/viewUtils.ts
+++ b/cypress/support/viewUtils.ts
@@ -108,7 +108,8 @@ export const expectLinkViewScreenLayout = ({
}
if (!html && (settings?.showLinkButton ?? DEFAULT_LINK_SHOW_BUTTON)) {
- cy.get('[data-testid="OpenInNewIcon"]').should('be.visible');
+ // this data-testid is set in graasp/ui
+ cy.get('[data-testid="fancy-link-card"]').should('be.visible');
}
if (description) {
diff --git a/package.json b/package.json
index e8c12e8b4..223e00200 100644
--- a/package.json
+++ b/package.json
@@ -21,10 +21,10 @@
"@emotion/styled": "11.11.5",
"@graasp/chatbox": "3.1.0",
"@graasp/map": "1.12.1",
- "@graasp/query-client": "3.8.0",
- "@graasp/sdk": "4.11.0",
+ "@graasp/query-client": "3.9.0",
+ "@graasp/sdk": "4.12.0",
"@graasp/translations": "1.28.0",
- "@graasp/ui": "4.18.2",
+ "@graasp/ui": "4.19.0",
"@mui/icons-material": "5.15.18",
"@mui/lab": "5.0.0-alpha.170",
"@mui/material": "5.15.18",
diff --git a/src/components/App.tsx b/src/components/App.tsx
index f86d1e80e..b2542aa5c 100644
--- a/src/components/App.tsx
+++ b/src/components/App.tsx
@@ -1,5 +1,4 @@
-import { Outlet, useLocation } from 'react-router';
-import { Route, Routes } from 'react-router-dom';
+import { Outlet, Route, Routes, useLocation } from 'react-router-dom';
import { buildSignInPath, saveUrlForRedirection } from '@graasp/sdk';
import { CustomInitialLoader, withAuthorization } from '@graasp/ui';
diff --git a/src/components/context/FilterItemsContext.tsx b/src/components/context/FilterItemsContext.tsx
index 06b13c971..c2ec117c6 100644
--- a/src/components/context/FilterItemsContext.tsx
+++ b/src/components/context/FilterItemsContext.tsx
@@ -1,5 +1,5 @@
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
-import { useLocation } from 'react-router';
+import { useLocation } from 'react-router-dom';
import { DiscriminatedItem } from '@graasp/sdk';
diff --git a/src/components/item/ItemContent.tsx b/src/components/item/ItemContent.tsx
index cc4d4bbd6..7f7a44bb6 100644
--- a/src/components/item/ItemContent.tsx
+++ b/src/components/item/ItemContent.tsx
@@ -1,4 +1,4 @@
-import { useOutletContext } from 'react-router';
+import { useOutletContext } from 'react-router-dom';
import { Container, Skeleton, styled } from '@mui/material';
@@ -107,21 +107,19 @@ const LinkContent = ({
item: LinkItemType;
member?: Member | null;
}): JSX.Element => (
-
-
-
+
);
/**
diff --git a/src/components/item/form/DisplayNameForm.tsx b/src/components/item/form/DisplayNameForm.tsx
deleted file mode 100644
index 72f2e07af..000000000
--- a/src/components/item/form/DisplayNameForm.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import { ChangeEvent } from 'react';
-
-import ClearIcon from '@mui/icons-material/Clear';
-import InfoIcon from '@mui/icons-material/Info';
-import { IconButton, Stack, TextField, Tooltip } from '@mui/material';
-
-import { useBuilderTranslation } from '../../../config/i18n';
-import { ITEM_FORM_DISPLAY_NAME_INPUT_ID } from '../../../config/selectors';
-import { BUILDER } from '../../../langs/constants';
-import type { EditModalContentPropType } from './EditModalWrapper';
-
-export type DisplayNameFormProps = EditModalContentPropType;
-
-const DisplayNameForm = ({
- updatedProperties,
- setChanges,
-}: DisplayNameFormProps): JSX.Element => {
- const { t: translateBuilder } = useBuilderTranslation();
-
- const handleDisplayNameInput = (event: ChangeEvent<{ value: string }>) => {
- setChanges({ displayName: event.target.value });
- };
-
- const handleClearClick = () => {
- setChanges({ displayName: '' });
- };
-
- return (
-
- {translateBuilder(BUILDER.CREATE_NEW_ITEM_DISPLAY_NAME_LABEL)}
-
-
-
-
-
-
- }
- value={updatedProperties?.displayName}
- onChange={handleDisplayNameInput}
- // always shrink because setting name from defined app does not shrink automatically
- InputLabelProps={{ shrink: true }}
- // add a clear icon button
- InputProps={{
- endAdornment: (
-
-
-
- ),
- }}
- // only take full width when on small screen size
- fullWidth
- sx={{ my: 1 }}
- />
- );
-};
-
-export default DisplayNameForm;
diff --git a/src/components/item/form/DocumentForm.tsx b/src/components/item/form/DocumentForm.tsx
index 7c86682ed..fb1d92fc0 100644
--- a/src/components/item/form/DocumentForm.tsx
+++ b/src/components/item/form/DocumentForm.tsx
@@ -36,7 +36,6 @@ import {
ITEM_FORM_DOCUMENT_TEXT_ID,
} from '../../../config/selectors';
import { BUILDER } from '../../../langs/constants';
-import DisplayNameForm from './DisplayNameForm';
import type { EditModalContentPropType } from './EditModalWrapper';
import NameForm from './NameForm';
@@ -305,11 +304,6 @@ const DocumentForm = ({
required
updatedProperties={updatedProperties}
/>
-
{
height={THUMBNAIL_DIMENSION}
width={THUMBNAIL_DIMENSION}
borderRadius={2}
- bgcolor="#E4DFFF"
+ bgcolor={DEFAULT_LIGHT_PRIMARY_COLOR}
alignItems="center"
justifyContent="center"
overflow="hidden"
diff --git a/src/components/item/form/LinkForm.tsx b/src/components/item/form/LinkForm.tsx
deleted file mode 100644
index 87ede1fb4..000000000
--- a/src/components/item/form/LinkForm.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import { TextField, TextFieldProps } from '@mui/material';
-
-import { LinkItemType, buildLinkExtra, getLinkExtra } from '@graasp/sdk';
-
-import { useBuilderTranslation } from '../../../config/i18n';
-import { ITEM_FORM_LINK_INPUT_ID } from '../../../config/selectors';
-import { BUILDER } from '../../../langs/constants';
-import { isUrlValid } from '../../../utils/item';
-
-type Props = {
- onChange: (item: Partial) => void;
- item?: LinkItemType;
-};
-
-const LinkForm = ({ onChange, item }: Props): JSX.Element => {
- const { t: translateBuilder } = useBuilderTranslation();
-
- const handleLinkInput: TextFieldProps['onChange'] = (event) => {
- const newValue = event.target.value;
- const hasProtocol = /^https?:\/\//;
- onChange({
- ...item,
- name: translateBuilder(BUILDER.LINK_DEFAULT_NAME), // todo: this is replaced by iframely
- extra: buildLinkExtra({
- // when used inside the NewItem Modal this component does not receive the item prop
- // so the https will not show, but it will be added when we submit the url.
- url: hasProtocol.test(newValue) ? newValue : `https://${newValue}`,
- html: '',
- thumbnails: [],
- icons: [],
- }),
- });
- };
-
- let url = null;
- if (item?.extra) {
- ({ url } = getLinkExtra(item?.extra) || {});
- }
- const isLinkInvalid = Boolean(url?.length) && !isUrlValid(url);
-
- return (
-
- );
-};
-
-export default LinkForm;
diff --git a/src/components/item/form/link/LinkDescriptionField.tsx b/src/components/item/form/link/LinkDescriptionField.tsx
new file mode 100644
index 000000000..75c36d8b1
--- /dev/null
+++ b/src/components/item/form/link/LinkDescriptionField.tsx
@@ -0,0 +1,60 @@
+import { ChangeEvent } from 'react';
+
+import { IconButton, TextField } from '@mui/material';
+
+import { Undo2Icon, XIcon } from 'lucide-react';
+
+import { useBuilderTranslation } from '@/config/i18n';
+import { BUILDER } from '@/langs/constants';
+
+type LinkDescriptionFieldProps = {
+ value: string | null | undefined;
+ onChange: (newValue: string) => void;
+ onRestore: () => void;
+ onClear: () => void;
+ showRestore: boolean;
+};
+const LinkDescriptionField = ({
+ value,
+ onChange,
+ onRestore,
+ onClear,
+ showRestore,
+}: LinkDescriptionFieldProps): JSX.Element => {
+ const { t } = useBuilderTranslation();
+ return (
+ ) => onChange(newValue)}
+ InputProps={{
+ endAdornment: (
+ <>
+
+
+
+
+
+
+
+ >
+ ),
+ }}
+ />
+ );
+};
+export default LinkDescriptionField;
diff --git a/src/components/item/form/link/LinkForm.tsx b/src/components/item/form/link/LinkForm.tsx
new file mode 100644
index 000000000..51834171c
--- /dev/null
+++ b/src/components/item/form/link/LinkForm.tsx
@@ -0,0 +1,279 @@
+import { ChangeEvent, useEffect, useMemo, useState } from 'react';
+
+import {
+ FormControl,
+ FormControlLabel,
+ FormLabel,
+ Link,
+ Radio,
+ RadioGroup,
+ Stack,
+ Typography,
+ styled,
+} from '@mui/material';
+
+import {
+ DiscriminatedItem,
+ ItemType,
+ LinkItemType,
+ buildLinkExtra,
+ getLinkExtra,
+} from '@graasp/sdk';
+import { LinkCard, LinkItem } from '@graasp/ui';
+
+import { hooks } from '@/config/queryClient';
+
+import { useBuilderTranslation } from '../../../../config/i18n';
+import { BUILDER } from '../../../../langs/constants';
+import { isUrlValid } from '../../../../utils/item';
+import NameForm from '../NameForm';
+import LinkDescriptionField from './LinkDescriptionField';
+import LinkUrlField from './LinkUrlField';
+import {
+ LinkType,
+ getLinkType,
+ getSettingsFromLinkType,
+ normalizeURL,
+} from './linkUtils';
+
+type Props = {
+ onChange: (item: Partial) => void;
+ item?: LinkItemType;
+ updatedProperties: Partial;
+};
+
+const StyledFormControlLabel = styled(FormControlLabel)(({ theme }) => ({
+ // remove weird default margins on label
+ margin: 0,
+ padding: theme.spacing(2),
+ borderRadius: theme.spacing(2),
+ // this allows to apply a style to the current element if if contains an input element that is checked
+ '&:has(input:checked)': {
+ // here we added a bit of opacity to the color used on the background of the icons cards
+ backgroundColor: '#E4DFFFB3',
+ },
+}));
+
+const StyledDiv = styled('div')(() => ({
+ '& > div': {
+ height: '200px !important',
+ paddingBottom: '0 !important',
+ },
+}));
+
+const LinkForm = ({
+ onChange,
+ item,
+ updatedProperties,
+}: Props): JSX.Element => {
+ const [linkContent, setLinkContent] = useState('');
+ const [isDescriptionDirty, setIsDescriptionDirty] = useState(false);
+ const { t: translateBuilder } = useBuilderTranslation();
+ const { data: linkData } = hooks.useLinkMetadata(normalizeURL(linkContent));
+
+ // get value from the updatedProperties
+ const linkType = getLinkType(updatedProperties.settings);
+
+ const handleLinkInput = (value: string) => {
+ setLinkContent(value);
+ onChange({
+ extra: buildLinkExtra({
+ // when used inside the NewItem Modal this component does not receive the item prop
+ // so the https will not show, but it will be added when we submit the url.
+ url: normalizeURL(value),
+ html: '',
+ thumbnails: [],
+ icons: [],
+ }),
+ });
+ };
+
+ let url = '';
+ let description: string | undefined = '';
+ const extraProps = updatedProperties.extra;
+ if (extraProps && ItemType.LINK in extraProps) {
+ ({ url, description } = getLinkExtra(extraProps) || {});
+ }
+ // link is considered valid if it is either empty, or it is a valid url
+ const isLinkValid = linkContent.length === 0 || isUrlValid(url);
+
+ const onChangeLinkType = ({
+ target: { value },
+ }: ChangeEvent) => {
+ const settings = getSettingsFromLinkType(value);
+ onChange({ settings });
+ };
+
+ const onClickClearURL = () => {
+ setLinkContent('');
+ };
+ const onClickClearDescription = () => {
+ onChange({ description: '' });
+ setIsDescriptionDirty(false);
+ };
+ const onClickRestoreDefaultDescription = () => {
+ onChange({ description: linkData?.description });
+ setIsDescriptionDirty(false);
+ };
+ const onChangeDescription = (value: string) => {
+ setIsDescriptionDirty(true);
+ onChange({ description: value });
+ };
+
+ // apply the description from the api to the field
+ // this is only run once.
+ useEffect(
+ () => {
+ // this is the object on which we will define the props to be updated
+ const updatedProps: Partial = {};
+
+ if (!isDescriptionDirty && linkData?.description) {
+ updatedProps.description = linkData?.description;
+ }
+ if (linkData?.title) {
+ updatedProps.name = linkData.title;
+ }
+ if (linkData?.description) {
+ updatedProps.extra = buildLinkExtra({
+ ...updatedProperties.extra?.embeddedLink,
+ url: updatedProperties.extra?.embeddedLink.url || '',
+ description: linkData.description,
+ });
+ }
+ // update props in one call to remove issue of race updates
+ if (Object.keys(updatedProps).length) {
+ // this should be called only once !
+ onChange(updatedProps);
+ }
+ },
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [linkData],
+ );
+
+ const embeddedLinkPreview = useMemo(
+ () => (
+
+ ),
+ [url],
+ );
+
+ return (
+
+
+
+
+
+
+
+ {translateBuilder(BUILDER.CREATE_ITEM_LINK_TYPE_TITLE)}
+
+ {linkContent ? (
+
+ {linkContent}}
+ control={}
+ />
+
+ }
+ control={}
+ slotProps={{ typography: { width: '100%', minWidth: '0px' } }}
+ sx={{ minWidth: '0px', width: '100%' }}
+ />
+ {linkData?.html && linkData.html !== '' && (
+
+ }
+ control={}
+ slotProps={{
+ typography: {
+ width: '100%',
+ minWidth: '0px',
+ },
+ }}
+ />
+ )}
+ {
+ // only show this options when embedding is allowed and there is no html code
+ // as the html will take precedence over showing the site as an iframe
+ // and some sites like daily motion actually allow both, we want to allow show the html setting
+ linkData?.isEmbeddingAllowed && linkData?.html === '' && (
+ }
+ slotProps={{
+ typography: {
+ width: '100%',
+ minWidth: '0px',
+ },
+ }}
+ sx={{
+ // this ensure the iframe takes up all horizontal space
+ '& iframe': {
+ width: '100%',
+ },
+ }}
+ />
+ )
+ }
+
+ ) : (
+
+ {translateBuilder(BUILDER.CREATE_ITEM_LINK_TYPE_HELPER_TEXT)}
+
+ )}
+
+
+
+ );
+};
+
+export default LinkForm;
diff --git a/src/components/item/form/link/LinkUrlField.tsx b/src/components/item/form/link/LinkUrlField.tsx
new file mode 100644
index 000000000..5b312b9cc
--- /dev/null
+++ b/src/components/item/form/link/LinkUrlField.tsx
@@ -0,0 +1,55 @@
+import { ChangeEvent } from 'react';
+
+import { IconButton, TextField } from '@mui/material';
+
+import { XIcon } from 'lucide-react';
+
+import { useBuilderTranslation } from '@/config/i18n';
+import { ITEM_FORM_LINK_INPUT_ID } from '@/config/selectors';
+import { BUILDER } from '@/langs/constants';
+
+type LinkUrlFieldProps = {
+ value: string;
+ isValid: boolean;
+ onClear: () => void;
+ onChange: (newValue: string) => void;
+};
+const LinkUrlField = ({
+ value,
+ isValid,
+ onClear,
+ onChange,
+}: LinkUrlFieldProps): JSX.Element => {
+ const { t } = useBuilderTranslation();
+ return (
+ ) => onChange(newValue)}
+ helperText={isValid ? '' : t(BUILDER.CREATE_ITEM_LINK_INVALID_LINK_ERROR)}
+ InputLabelProps={{ shrink: true }}
+ InputProps={{
+ endAdornment: (
+
+
+
+ ),
+ }}
+ fullWidth
+ required
+ />
+ );
+};
+export default LinkUrlField;
diff --git a/src/components/item/form/link/linkUtils.test.ts b/src/components/item/form/link/linkUtils.test.ts
new file mode 100644
index 000000000..31dfb709a
--- /dev/null
+++ b/src/components/item/form/link/linkUtils.test.ts
@@ -0,0 +1,85 @@
+import { describe, expect, it } from 'vitest';
+
+import {
+ LinkType,
+ getLinkType,
+ getSettingsFromLinkType,
+ normalizeURL,
+} from './linkUtils';
+
+describe('Link Utils', () => {
+ describe('Normalize URL', () => {
+ it('Return correct HTTP url', () => {
+ const url = 'http://graasp.org';
+ const newUrl = normalizeURL(url);
+ expect(newUrl).toEqual(url);
+ });
+ it('Return correct HTTPS url', () => {
+ const url = 'https://graasp.org';
+ const newUrl = normalizeURL(url);
+ expect(newUrl).toEqual(url);
+ });
+ it('Adds protocol to url without', () => {
+ const url = 'graasp.org';
+ const newUrl = normalizeURL(url);
+ expect(newUrl).toEqual(`https://${url}`);
+ });
+ it('Adds protocol to url with path', () => {
+ const url = 'graasp.org/terms';
+ const newUrl = normalizeURL(url);
+ expect(newUrl).toEqual(`https://${url}`);
+ });
+ });
+
+ describe('Get Link Type', () => {
+ it('Default link type', () => {
+ const settings = {};
+ const linkType = getLinkType(settings);
+ expect(linkType).toEqual(LinkType.Default);
+ });
+ it('Default link type', () => {
+ const settings = { showLinkButton: false, showLinkIframe: false };
+ const linkType = getLinkType(settings);
+ expect(linkType).toEqual(LinkType.Default);
+ });
+ it('Fancy link type', () => {
+ const settings = { showLinkButton: true, showLinkIframe: false };
+ const linkType = getLinkType(settings);
+ expect(linkType).toEqual(LinkType.Fancy);
+ });
+ it('Embedded link type', () => {
+ const settings = { showLinkButton: false, showLinkIframe: true };
+ const linkType = getLinkType(settings);
+ expect(linkType).toEqual(LinkType.Embedded);
+ });
+ it('Prefer Embedded link type when both are true', () => {
+ const settings = { showLinkButton: true, showLinkIframe: true };
+ const linkType = getLinkType(settings);
+ expect(linkType).toEqual(LinkType.Embedded);
+ });
+ });
+
+ describe('Get settings from link type', () => {
+ it('Default type', () => {
+ const settings = getSettingsFromLinkType(LinkType.Default);
+ expect(settings).toEqual({
+ showLinkIframe: false,
+ showLinkButton: false,
+ });
+ });
+ it('Fancy type', () => {
+ const settings = getSettingsFromLinkType(LinkType.Fancy);
+ expect(settings).toEqual({
+ showLinkIframe: false,
+ showLinkButton: true,
+ });
+ });
+ it('Embedded type', () => {
+ const settings = getSettingsFromLinkType(LinkType.Embedded);
+ expect(settings).toEqual({
+ showLinkIframe: true,
+ showLinkButton: false,
+ });
+ });
+ });
+});
diff --git a/src/components/item/form/link/linkUtils.ts b/src/components/item/form/link/linkUtils.ts
new file mode 100644
index 000000000..9dc81351b
--- /dev/null
+++ b/src/components/item/form/link/linkUtils.ts
@@ -0,0 +1,79 @@
+import { ItemSettings, LinkItemSettings, UnionOfConst } from '@graasp/sdk';
+
+/**
+ * Enum representing the different ways a link can be shown
+ * `Embedded` will use an iframe to display the website content.
+ * If the link is to a rich media (video, etc), the embedded code will be used.
+ * `Fancy` will display a nice card similar to social media previews.
+ * `Default` will show the link as hyperlink text.
+ */
+export const LinkType = {
+ Embedded: 'embedded',
+ Fancy: 'fancy',
+ Default: 'default',
+} as const;
+
+/**
+ * Add the protocol on the url if not set
+ * This function allows to enter urls without the protocol (http / https)
+ * and will add it to make url compliant to the spec. Defaults to adding HTTPS.
+ * @param url input url to be normalized
+ * @returns A string representing a url with the required protocol attached
+ */
+export const normalizeURL = (url: string): string => {
+ const hasProtocol = /^https?:\/\//;
+
+ if (hasProtocol.test(url)) {
+ return url;
+ }
+ return `https://${url}`;
+};
+
+/**
+ * Find LinkType based on settings
+ * @param settings item settings from which to take the showIframe and shoWButton settings
+ * @returns the linkType to be used
+ */
+export const getLinkType = (
+ settings?: LinkItemSettings & ItemSettings,
+): UnionOfConst => {
+ if (settings?.showLinkIframe) {
+ return LinkType.Embedded;
+ }
+ if (settings?.showLinkButton) {
+ return LinkType.Fancy;
+ }
+ return LinkType.Default;
+};
+
+/**
+ * Convert a link type to settings to be saved in the item
+ * @param linkType The type of the link selected
+ * @returns the settings needed to represent that link type
+ */
+export const getSettingsFromLinkType = (
+ linkType: string,
+): { showLinkButton: boolean; showLinkIframe: boolean } => {
+ switch (linkType) {
+ case LinkType.Fancy: {
+ return {
+ showLinkIframe: false,
+ showLinkButton: true,
+ };
+ }
+ case LinkType.Embedded: {
+ return {
+ showLinkIframe: true,
+ showLinkButton: false,
+ };
+ }
+ // eslint-disable-next-line no-fallthrough
+ case LinkType.Default:
+ default: {
+ return {
+ showLinkIframe: false,
+ showLinkButton: false,
+ };
+ }
+ }
+};
diff --git a/src/components/item/publish/CategorySelection.tsx b/src/components/item/publish/CategorySelection.tsx
index e7d9edd4d..662f10891 100644
--- a/src/components/item/publish/CategorySelection.tsx
+++ b/src/components/item/publish/CategorySelection.tsx
@@ -1,5 +1,5 @@
import { SyntheticEvent } from 'react';
-import { useParams } from 'react-router';
+import { useParams } from 'react-router-dom';
import { AutocompleteChangeReason, Box, Typography } from '@mui/material';
diff --git a/src/components/item/publish/ItemPublishTab.tsx b/src/components/item/publish/ItemPublishTab.tsx
index 006228e9a..5ab000cec 100644
--- a/src/components/item/publish/ItemPublishTab.tsx
+++ b/src/components/item/publish/ItemPublishTab.tsx
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
-import { useOutletContext } from 'react-router';
+import { useOutletContext } from 'react-router-dom';
import {
Cancel,
diff --git a/src/components/item/settings/ItemLicenseSettings.tsx b/src/components/item/settings/ItemLicenseSettings.tsx
index 77b61b8fb..24bb78d40 100644
--- a/src/components/item/settings/ItemLicenseSettings.tsx
+++ b/src/components/item/settings/ItemLicenseSettings.tsx
@@ -43,7 +43,7 @@ const ItemLicenseSettings = (): JSX.Element => {
}
additionalInfo={
- item.settings?.ccLicenseAdaption && (
+ item.settings?.ccLicenseAdaption ? (
{
allowCommercialUse={allowCommercialUse}
iconSize={30}
/>
- )
+ ) : undefined
}
/>
-
+
);
diff --git a/src/components/main/ItemTypeTabs.tsx b/src/components/main/ItemTypeTabs.tsx
index 005f2514f..557431491 100644
--- a/src/components/main/ItemTypeTabs.tsx
+++ b/src/components/main/ItemTypeTabs.tsx
@@ -3,7 +3,7 @@ import { useState } from 'react';
import { Tab, Tabs, styled } from '@mui/material';
-import { ItemType, LocalFileItemExtra, MimeTypes } from '@graasp/sdk';
+import { ItemType, MimeTypes } from '@graasp/sdk';
import { ItemIcon } from '@graasp/ui';
import { useBuilderTranslation } from '../../config/i18n';
@@ -52,8 +52,7 @@ const ItemTypeTabs = ({ onTypeChange, initialValue }: Props): JSX.Element => {
);
@@ -73,7 +72,6 @@ const ItemTypeTabs = ({ onTypeChange, initialValue }: Props): JSX.Element => {
}
/>
@@ -85,7 +83,6 @@ const ItemTypeTabs = ({ onTypeChange, initialValue }: Props): JSX.Element => {
}
/>
@@ -97,7 +94,6 @@ const ItemTypeTabs = ({ onTypeChange, initialValue }: Props): JSX.Element => {
}
/>
@@ -109,7 +105,6 @@ const ItemTypeTabs = ({ onTypeChange, initialValue }: Props): JSX.Element => {
}
/>
@@ -121,7 +116,6 @@ const ItemTypeTabs = ({ onTypeChange, initialValue }: Props): JSX.Element => {
}
/>
@@ -139,7 +133,6 @@ const ItemTypeTabs = ({ onTypeChange, initialValue }: Props): JSX.Element => {
}
/>
@@ -151,7 +144,6 @@ const ItemTypeTabs = ({ onTypeChange, initialValue }: Props): JSX.Element => {
}
/>
diff --git a/src/components/main/MainMenu.tsx b/src/components/main/MainMenu.tsx
index 3cbd022c6..59fd71e45 100644
--- a/src/components/main/MainMenu.tsx
+++ b/src/components/main/MainMenu.tsx
@@ -1,4 +1,4 @@
-import { useLocation, useNavigate } from 'react-router';
+import { useLocation, useNavigate } from 'react-router-dom';
// import { BugReport } from '@mui/icons-material';
import { AutoStories, Bookmark, Delete, Folder } from '@mui/icons-material';
diff --git a/src/components/main/NewItemModal.tsx b/src/components/main/NewItemModal.tsx
index c8483af98..21fee99b8 100644
--- a/src/components/main/NewItemModal.tsx
+++ b/src/components/main/NewItemModal.tsx
@@ -1,5 +1,5 @@
import { useState } from 'react';
-import { useParams } from 'react-router';
+import { useParams } from 'react-router-dom';
import {
Dialog,
@@ -38,7 +38,7 @@ import AppForm from '../item/form/AppForm';
import DocumentForm from '../item/form/DocumentForm';
import useEtherpadForm from '../item/form/EtherpadForm';
import FolderForm from '../item/form/FolderForm';
-import LinkForm from '../item/form/LinkForm';
+import LinkForm from '../item/form/link/LinkForm';
import ImportH5P from './ImportH5P';
import ImportZip from './ImportZip';
import ItemTypeTabs from './ItemTypeTabs';
@@ -122,8 +122,7 @@ const NewItemModal = ({
// todo: notify user
return false;
}
- // eslint-disable-next-line no-console
- console.log(updatedPropertiesPerType[type]);
+
// todo: fix types
return submitAndDisableConfirmButtonFor(
() =>
@@ -230,7 +229,10 @@ const NewItemModal = ({
{translateBuilder(BUILDER.CREATE_ITEM_LINK_TITLE)}
-
+
>
);
case ItemType.DOCUMENT:
diff --git a/src/components/pages/item/ItemLoginWrapper.tsx b/src/components/pages/item/ItemLoginWrapper.tsx
index 75e1f6076..47cd81f02 100644
--- a/src/components/pages/item/ItemLoginWrapper.tsx
+++ b/src/components/pages/item/ItemLoginWrapper.tsx
@@ -1,4 +1,4 @@
-import { useParams } from 'react-router';
+import { useParams } from 'react-router-dom';
import { ItemLoginAuthorization } from '@graasp/ui';
diff --git a/src/components/pages/item/ItemPageLayout.tsx b/src/components/pages/item/ItemPageLayout.tsx
index 557bc8a36..cd237759b 100644
--- a/src/components/pages/item/ItemPageLayout.tsx
+++ b/src/components/pages/item/ItemPageLayout.tsx
@@ -1,6 +1,10 @@
import { useEffect } from 'react';
-import { Outlet, useOutletContext, useParams } from 'react-router';
-import { useSearchParams } from 'react-router-dom';
+import {
+ Outlet,
+ useOutletContext,
+ useParams,
+ useSearchParams,
+} from 'react-router-dom';
import { Box } from '@mui/material';
diff --git a/src/components/pages/item/ItemScreen.tsx b/src/components/pages/item/ItemScreen.tsx
index d9e91be80..bfc4c2da1 100644
--- a/src/components/pages/item/ItemScreen.tsx
+++ b/src/components/pages/item/ItemScreen.tsx
@@ -1,4 +1,4 @@
-import { useOutletContext } from 'react-router';
+import { useOutletContext } from 'react-router-dom';
import { ItemType } from '@graasp/sdk';
diff --git a/src/components/pages/item/ItemScreenLayout.tsx b/src/components/pages/item/ItemScreenLayout.tsx
index 3da8353cf..849f5d264 100644
--- a/src/components/pages/item/ItemScreenLayout.tsx
+++ b/src/components/pages/item/ItemScreenLayout.tsx
@@ -1,5 +1,5 @@
import { useEffect } from 'react';
-import { Outlet, useParams } from 'react-router';
+import { Outlet, useParams } from 'react-router-dom';
import { PermissionLevel, PermissionLevelCompare } from '@graasp/sdk';
import { Loader } from '@graasp/ui';
diff --git a/src/components/pages/item/ItemSettingsPage.tsx b/src/components/pages/item/ItemSettingsPage.tsx
index a0a9d0d51..1e140c8b7 100644
--- a/src/components/pages/item/ItemSettingsPage.tsx
+++ b/src/components/pages/item/ItemSettingsPage.tsx
@@ -1,5 +1,9 @@
-import { Navigate, useOutletContext, useParams } from 'react-router';
-import { useSearchParams } from 'react-router-dom';
+import {
+ Navigate,
+ useOutletContext,
+ useParams,
+ useSearchParams,
+} from 'react-router-dom';
import { PermissionLevel, PermissionLevelCompare } from '@graasp/sdk';
diff --git a/src/components/pages/item/LibrarySettingsPage.tsx b/src/components/pages/item/LibrarySettingsPage.tsx
index b0d50df02..6798e2706 100644
--- a/src/components/pages/item/LibrarySettingsPage.tsx
+++ b/src/components/pages/item/LibrarySettingsPage.tsx
@@ -1,5 +1,9 @@
-import { Navigate, useOutletContext, useParams } from 'react-router';
-import { useSearchParams } from 'react-router-dom';
+import {
+ Navigate,
+ useOutletContext,
+ useParams,
+ useSearchParams,
+} from 'react-router-dom';
import { PermissionLevel } from '@graasp/sdk';
diff --git a/src/langs/constants.ts b/src/langs/constants.ts
index 2cdbc77a7..c3b883592 100644
--- a/src/langs/constants.ts
+++ b/src/langs/constants.ts
@@ -22,6 +22,10 @@ export const BUILDER = {
CREATE_ITEM_LINK_INVALID_LINK_ERROR: 'CREATE_ITEM_LINK_INVALID_LINK_ERROR',
CREATE_ITEM_LINK_LABEL: 'CREATE_ITEM_LINK_LABEL',
CREATE_ITEM_LINK_TITLE: 'CREATE_ITEM_LINK_TITLE',
+ CREATE_ITEM_LINK_TYPE_TITLE: 'CREATE_ITEM_LINK_TYPE_TITLE',
+ CREATE_ITEM_LINK_TYPE_HELPER_TEXT: 'CREATE_ITEM_LINK_TYPE_HELPER_TEXT',
+ CREATE_ITEM_LINK_EMBED_TITLE: 'CREATE_ITEM_LINK_EMBED_TITLE',
+ CREATE_ITEM_LINK_EMBED_HELPER_TEXT: 'CREATE_ITEM_LINK_EMBED_HELPER_TEXT',
CREATE_ITEM_NEW_FOLDER_TITLE: 'CREATE_ITEM_NEW_FOLDER_TITLE',
CREATE_NEW_ITEM_APP_TITLE: 'CREATE_NEW_ITEM_APP_TITLE',
CREATE_NEW_ITEM_APP_URL_LABEL: 'CREATE_NEW_ITEM_APP_URL_LABEL',
@@ -35,6 +39,8 @@ export const BUILDER = {
CREATE_NEW_ITEM_DISPLAY_NAME_LABEL: 'CREATE_NEW_ITEM_DISPLAY_NAME_LABEL',
CREATE_NEW_ITEM_DISPLAY_NAME_HELPER_TEXT:
'CREATE_NEW_ITEM_DISPLAY_NAME_HELPER_TEXT',
+ CREATE_NEW_ITEM_DISPLAY_NAME_HELPER_TEXT_LINKFORM:
+ 'CREATE_NEW_ITEM_DISPLAY_NAME_HELPER_TEXT_LINKFORM',
CREATE_SHORTCUT_BUTTON: 'CREATE_SHORTCUT_BUTTON',
CREATE_SHORTCUT_DEFAULT_NAME: 'CREATE_SHORTCUT_DEFAULT_NAME',
CREATE_SHORTCUT_MODAL_TITLE: 'CREATE_SHORTCUT_MODAL_TITLE',
diff --git a/src/langs/en.json b/src/langs/en.json
index bcba1b6b4..578e7d90a 100644
--- a/src/langs/en.json
+++ b/src/langs/en.json
@@ -23,6 +23,10 @@
"CREATE_ITEM_LINK_TITLE": "Add a Link",
"CREATE_ITEM_NEW_FOLDER_TITLE": "Add a Folder",
"CREATE_NEW_ITEM_APP_TITLE": "Add an Application",
+ "CREATE_ITEM_LINK_TYPE_TITLE": "Preview Type",
+ "CREATE_ITEM_LINK_TYPE_HELPER_TEXT": "To preview, please firstly insert your link above...",
+ "CREATE_ITEM_LINK_EMBED_TITLE": "Embed the website",
+ "CREATE_ITEM_LINK_EMBED_HELPER_TEXT": "Some websites may limit access when embedded.",
"CREATE_NEW_ITEM_APP_URL_LABEL": "App URL",
"CREATE_NEW_ITEM_DOCUMENT_TITLE": "Add a Text",
"CREATE_NEW_ITEM_ETHERPAD_INFORMATIONS": "Etherpad is a collaborative real-time text editor",
@@ -32,6 +36,7 @@
"CREATE_NEW_ITEM_NAME_HELPER_TEXT": "Show in Graasp builder",
"CREATE_NEW_ITEM_DISPLAY_NAME_LABEL": "Display Name",
"CREATE_NEW_ITEM_DISPLAY_NAME_HELPER_TEXT": "Show in Graasp player. Defaults to the 'Name' field. Customize it to your needs.",
+ "CREATE_NEW_ITEM_DISPLAY_NAME_HELPER_TEXT_LINKFORM": "Show in Graasp player. Defaults to the 'Link' field. Customize it to your needs.",
"CREATE_SHORTCUT_BUTTON": "Create shortcut",
"CREATE_SHORTCUT_BUTTON_zero": "Create shortcut",
"CREATE_SHORTCUT_BUTTON_one": "Create shortcut to {{name}}",
diff --git a/yarn.lock b/yarn.lock
index e37356f46..72deefee2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -92,7 +92,7 @@ __metadata:
languageName: node
linkType: hard
-"@ag-grid-community/styles@npm:31.3.2, @ag-grid-community/styles@npm:^31.3.1":
+"@ag-grid-community/styles@npm:31.3.2":
version: 31.3.2
resolution: "@ag-grid-community/styles@npm:31.3.2"
checksum: 10/b651750803577419e6bbbc45b64d717d64c500332364f4523f84d9d384351f69a7d8eb83bd8343a4ec1530b0db0f26288e91e69516c6ade6e5f7dcf9a7e3078c
@@ -106,6 +106,13 @@ __metadata:
languageName: node
linkType: hard
+"@ag-grid-community/styles@npm:^31.3.1":
+ version: 31.3.1
+ resolution: "@ag-grid-community/styles@npm:31.3.1"
+ checksum: 10/34c4e80f62891fe1d8881e233df38ace6ba03eb2934b3015bb8537937164dc2c261479d8d15d5c06e36f2bb84c3b27c7b65aaab773236190729bdfe32ec2c225
+ languageName: node
+ linkType: hard
+
"@ampproject/remapping@npm:^2.2.0":
version: 2.3.0
resolution: "@ampproject/remapping@npm:2.3.0"
@@ -1574,26 +1581,26 @@ __metadata:
languageName: node
linkType: hard
-"@graasp/query-client@npm:3.8.0":
- version: 3.8.0
- resolution: "@graasp/query-client@npm:3.8.0"
+"@graasp/query-client@npm:3.9.0":
+ version: 3.9.0
+ resolution: "@graasp/query-client@npm:3.9.0"
dependencies:
"@tanstack/react-query": "npm:4.36.1"
"@tanstack/react-query-devtools": "npm:4.36.1"
- axios: "npm:1.6.8"
+ axios: "npm:1.7.2"
http-status-codes: "npm:2.3.0"
qs: "npm:6.12.1"
peerDependencies:
"@graasp/sdk": ^4.0.0
"@graasp/translations": ^1.23.0
react: ^18.0.0
- checksum: 10/8657f66530cfe5e28c28c54cb29cf392d41aae9365bf7f1dceb9ed1876211b34d7032a892d33c0317e6a90d48d4a203bd44a3cecb8f2337121f35dcc789fdbef
+ checksum: 10/23490dceb1be2f95759b296099165c2d20be0a7a5fa3b7176c034e2fd30242635d167af39e69074385704d4bc11c8b3aba694c9a5f11e954099b75c395da537b
languageName: node
linkType: hard
-"@graasp/sdk@npm:4.11.0":
- version: 4.11.0
- resolution: "@graasp/sdk@npm:4.11.0"
+"@graasp/sdk@npm:4.12.0":
+ version: 4.12.0
+ resolution: "@graasp/sdk@npm:4.12.0"
dependencies:
"@faker-js/faker": "npm:8.4.1"
filesize: "npm:10.1.2"
@@ -1602,7 +1609,7 @@ __metadata:
peerDependencies:
date-fns: ^3
uuid: ^9
- checksum: 10/629401ee513c8f74010aeb38e2efe1a36bfab2c8b4129e00fa8ab5ce10b4103de4e2b374de3aa4f63bb9bb4e13acf39a3dadb6f4dbf51439514e8ae17cc9ca95
+ checksum: 10/10f37882a6d926d0fd1e8e26edaa312a6ef12d69df98e909f4841d7873a4e15a0b45eb04f3cb6e79eb112b6e37bc1e5993591e81a13ddf8eef96c6db57f6b826
languageName: node
linkType: hard
@@ -1677,19 +1684,19 @@ __metadata:
languageName: node
linkType: hard
-"@graasp/ui@npm:4.18.2":
- version: 4.18.2
- resolution: "@graasp/ui@npm:4.18.2"
+"@graasp/ui@npm:4.19.0":
+ version: 4.19.0
+ resolution: "@graasp/ui@npm:4.19.0"
dependencies:
"@ag-grid-community/client-side-row-model": "npm:31.3.1"
"@ag-grid-community/react": "npm:^31.3.1"
"@ag-grid-community/styles": "npm:^31.3.1"
- "@storybook/react-vite": "npm:8.1.1"
+ "@storybook/react-vite": "npm:8.1.3"
http-status-codes: "npm:2.3.0"
interweave: "npm:13.1.0"
katex: "npm:0.16.10"
lodash.truncate: "npm:4.4.2"
- lucide-react: "npm:0.378.0"
+ lucide-react: "npm:0.379.0"
react-cookie-consent: "npm:9.0.0"
react-quill: "npm:2.0.0"
react-rnd: "npm:10.4.10"
@@ -1712,7 +1719,7 @@ __metadata:
react-router-dom: ^6.11.0
stylis: ^4.1.3
stylis-plugin-rtl: ^2.1.1
- checksum: 10/819c2d8bf63008d7b7cd983a2a2750d4b2259c870dfaa17e826880424ddcc729803908ee3b41661064e4102da9a79e4734cd2df4f402ca1a34592ad7b34b694c
+ checksum: 10/48dc536e60bc3ae1ce2285b7d6174f82c6726bc40473bbf43ac9d3b76e85ed64d23fa42e5340c947b0036fac6863b8d80e55ffbd24c17e722557474f2eaece49
languageName: node
linkType: hard
@@ -2546,19 +2553,19 @@ __metadata:
languageName: node
linkType: hard
-"@storybook/builder-vite@npm:8.1.1":
- version: 8.1.1
- resolution: "@storybook/builder-vite@npm:8.1.1"
- dependencies:
- "@storybook/channels": "npm:8.1.1"
- "@storybook/client-logger": "npm:8.1.1"
- "@storybook/core-common": "npm:8.1.1"
- "@storybook/core-events": "npm:8.1.1"
- "@storybook/csf-plugin": "npm:8.1.1"
- "@storybook/node-logger": "npm:8.1.1"
- "@storybook/preview": "npm:8.1.1"
- "@storybook/preview-api": "npm:8.1.1"
- "@storybook/types": "npm:8.1.1"
+"@storybook/builder-vite@npm:8.1.3":
+ version: 8.1.3
+ resolution: "@storybook/builder-vite@npm:8.1.3"
+ dependencies:
+ "@storybook/channels": "npm:8.1.3"
+ "@storybook/client-logger": "npm:8.1.3"
+ "@storybook/core-common": "npm:8.1.3"
+ "@storybook/core-events": "npm:8.1.3"
+ "@storybook/csf-plugin": "npm:8.1.3"
+ "@storybook/node-logger": "npm:8.1.3"
+ "@storybook/preview": "npm:8.1.3"
+ "@storybook/preview-api": "npm:8.1.3"
+ "@storybook/types": "npm:8.1.3"
"@types/find-cache-dir": "npm:^3.2.1"
browser-assert: "npm:^1.2.1"
es-module-lexer: "npm:^1.5.0"
@@ -2579,7 +2586,7 @@ __metadata:
optional: true
vite-plugin-glimmerx:
optional: true
- checksum: 10/e8e4d8fe49bca7da86d1f560919d13a3b1fe9649eef1e2ef75ecf7ac2a7bdaf9e83e5a85fee9fc8f39a9cb047b53fa1f77b959e16bce3c1b749a1cd77420429e
+ checksum: 10/1bb5ec6dcfcabc2a9f18e7591d22b4e351785758b2ee2deac06dec4b1f51a77c55948e6c26a9764601912f63eee9bb628d4f1d9e05e536d0e4b5246b51b22a61
languageName: node
linkType: hard
@@ -2597,16 +2604,16 @@ __metadata:
languageName: node
linkType: hard
-"@storybook/channels@npm:8.1.1":
- version: 8.1.1
- resolution: "@storybook/channels@npm:8.1.1"
+"@storybook/channels@npm:8.1.3":
+ version: 8.1.3
+ resolution: "@storybook/channels@npm:8.1.3"
dependencies:
- "@storybook/client-logger": "npm:8.1.1"
- "@storybook/core-events": "npm:8.1.1"
+ "@storybook/client-logger": "npm:8.1.3"
+ "@storybook/core-events": "npm:8.1.3"
"@storybook/global": "npm:^5.0.0"
telejson: "npm:^7.2.0"
tiny-invariant: "npm:^1.3.1"
- checksum: 10/c60349f3ea1306d17eee43cca352b5d1dc1431ebdbce80181da3f45e49fb479090094f73c6f55a10763951917ad0bbae02558fc392712a4a098d1a79710080d6
+ checksum: 10/c7e0c7765b355499aadfe8b74e9723a38d48e0f1d83dd54baa89c1cc6ec3ccc024c55bfd4d5f9d83d3b37c5e6639823da57f32096f96395d7150ed76cc420b95
languageName: node
linkType: hard
@@ -2619,12 +2626,12 @@ __metadata:
languageName: node
linkType: hard
-"@storybook/client-logger@npm:8.1.1":
- version: 8.1.1
- resolution: "@storybook/client-logger@npm:8.1.1"
+"@storybook/client-logger@npm:8.1.3":
+ version: 8.1.3
+ resolution: "@storybook/client-logger@npm:8.1.3"
dependencies:
"@storybook/global": "npm:^5.0.0"
- checksum: 10/0255552c93c66e2d11fa282a76e813861427e7cb0c3dc860e8d686bfd4cbceb37f04589d3540db39eb1e37761761d964e1d0266cbe3074e1dcc80af26cd5f153
+ checksum: 10/c7735d2ef39c07b89faade51fe26a56816eadc61e9b4ab952c087dbbd7e7ef3acfd49d437460d8503cb02d17613a4734267c5a56ccc6f9f2a1b2330b8f5459e5
languageName: node
linkType: hard
@@ -2669,14 +2676,14 @@ __metadata:
languageName: node
linkType: hard
-"@storybook/core-common@npm:8.1.1":
- version: 8.1.1
- resolution: "@storybook/core-common@npm:8.1.1"
+"@storybook/core-common@npm:8.1.3":
+ version: 8.1.3
+ resolution: "@storybook/core-common@npm:8.1.3"
dependencies:
- "@storybook/core-events": "npm:8.1.1"
- "@storybook/csf-tools": "npm:8.1.1"
- "@storybook/node-logger": "npm:8.1.1"
- "@storybook/types": "npm:8.1.1"
+ "@storybook/core-events": "npm:8.1.3"
+ "@storybook/csf-tools": "npm:8.1.3"
+ "@storybook/node-logger": "npm:8.1.3"
+ "@storybook/types": "npm:8.1.3"
"@yarnpkg/fslib": "npm:2.10.3"
"@yarnpkg/libzip": "npm:2.3.0"
chalk: "npm:^4.1.0"
@@ -2707,7 +2714,7 @@ __metadata:
peerDependenciesMeta:
prettier:
optional: true
- checksum: 10/38d98943c4e5ced16d35a477132471d04fc6da0cef1ebe4c3d4e026213b02f5625fe1250c3c7da403486a007415e06e639eb50baca30eb4beb2a945715cf55f4
+ checksum: 10/29cd5c88715b5967d59a06836dfc751a921d4d5075c9bcd99fc2f9972f77130e08a5f671224da1bc93433dc10813fbdeaa44294575ce8da4e45750bbf9ffbc11
languageName: node
linkType: hard
@@ -2720,13 +2727,13 @@ __metadata:
languageName: node
linkType: hard
-"@storybook/core-events@npm:8.1.1":
- version: 8.1.1
- resolution: "@storybook/core-events@npm:8.1.1"
+"@storybook/core-events@npm:8.1.3":
+ version: 8.1.3
+ resolution: "@storybook/core-events@npm:8.1.3"
dependencies:
"@storybook/csf": "npm:^0.1.7"
ts-dedent: "npm:^2.0.0"
- checksum: 10/46d88ff57ecb4a1ebb4698354bf52e5191b8ca676f41b467ccd8d56b63e55455aa38835f2d88b7ddcfc61f60e4ce45fab7453bc644562c0fd4b81887934d0e99
+ checksum: 10/dc4cf5a7d0f36fff14ce7e9630e44f826ea748700753cd53be64a7d7b0d3e99b227224eb72267a92baf533823579bd51a9b6999926736e0067b437ca1d0452ed
languageName: node
linkType: hard
@@ -2740,13 +2747,13 @@ __metadata:
languageName: node
linkType: hard
-"@storybook/csf-plugin@npm:8.1.1":
- version: 8.1.1
- resolution: "@storybook/csf-plugin@npm:8.1.1"
+"@storybook/csf-plugin@npm:8.1.3":
+ version: 8.1.3
+ resolution: "@storybook/csf-plugin@npm:8.1.3"
dependencies:
- "@storybook/csf-tools": "npm:8.1.1"
+ "@storybook/csf-tools": "npm:8.1.3"
unplugin: "npm:^1.3.1"
- checksum: 10/3b702f40561c2d856091ba739c9ddbe67ac0cb3fda580738581d1767ccfa33149fc0a7f60ed00b9088f699614d3afbcd7778762e2a89fcd6da9bea9781c83b42
+ checksum: 10/d987a38225594e231aa5ef45355611ff9bd96a9f1b24cf2044f09e9100b3e6b895fb420d6a226b4e36fa3cf3baee82071fd04fbef01d53a9dab50e3dccac1025
languageName: node
linkType: hard
@@ -2767,20 +2774,20 @@ __metadata:
languageName: node
linkType: hard
-"@storybook/csf-tools@npm:8.1.1":
- version: 8.1.1
- resolution: "@storybook/csf-tools@npm:8.1.1"
+"@storybook/csf-tools@npm:8.1.3":
+ version: 8.1.3
+ resolution: "@storybook/csf-tools@npm:8.1.3"
dependencies:
"@babel/generator": "npm:^7.24.4"
"@babel/parser": "npm:^7.24.4"
"@babel/traverse": "npm:^7.24.1"
"@babel/types": "npm:^7.24.0"
"@storybook/csf": "npm:^0.1.7"
- "@storybook/types": "npm:8.1.1"
+ "@storybook/types": "npm:8.1.3"
fs-extra: "npm:^11.1.0"
recast: "npm:^0.23.5"
ts-dedent: "npm:^2.0.0"
- checksum: 10/d3b2e86113ada8be829395f69aaedf6f2652512edace5dd71f4391695a2f5caa800903b63f9df21a698ce56c574b965332dad67e52ca6d4fbc3d6946dc842305
+ checksum: 10/5d385ea82cc698e6a51600c6ed7826e00925e9c8d116aefe4fd7d269096099801eb02aed303de3316c2f957d3110f39214208be24ea6566151735b4a188abc0f
languageName: node
linkType: hard
@@ -2817,19 +2824,19 @@ __metadata:
languageName: node
linkType: hard
-"@storybook/docs-tools@npm:8.1.1":
- version: 8.1.1
- resolution: "@storybook/docs-tools@npm:8.1.1"
+"@storybook/docs-tools@npm:8.1.3":
+ version: 8.1.3
+ resolution: "@storybook/docs-tools@npm:8.1.3"
dependencies:
- "@storybook/core-common": "npm:8.1.1"
- "@storybook/core-events": "npm:8.1.1"
- "@storybook/preview-api": "npm:8.1.1"
- "@storybook/types": "npm:8.1.1"
+ "@storybook/core-common": "npm:8.1.3"
+ "@storybook/core-events": "npm:8.1.3"
+ "@storybook/preview-api": "npm:8.1.3"
+ "@storybook/types": "npm:8.1.3"
"@types/doctrine": "npm:^0.0.3"
assert: "npm:^2.1.0"
doctrine: "npm:^3.0.0"
lodash: "npm:^4.17.21"
- checksum: 10/8be375dd105b52171ecd385b087f911fcf8f5d5684fa6d8c67d1d75764ec1b4fcb43104b5b2b472ded2a9149d8ac8626608af6ac37aae8011546c94c81faabec
+ checksum: 10/3eac6ddd48db53c9f8759cfcc9dd01958bb396ea194c9d4705a7a2016e6ff19996e71148cca4691ea64de5452ba5b64eba86d811dd38d805988ce1d63a216865
languageName: node
linkType: hard
@@ -2847,10 +2854,10 @@ __metadata:
languageName: node
linkType: hard
-"@storybook/node-logger@npm:8.1.1":
- version: 8.1.1
- resolution: "@storybook/node-logger@npm:8.1.1"
- checksum: 10/5c471b1dbcb85412c20981740b68c3592749e06b6d65732b649a1e6b3d8c0f9aa6897e02a694e48436d11480f951cd5889248628695a097da9e2c0efcb78ec29
+"@storybook/node-logger@npm:8.1.3":
+ version: 8.1.3
+ resolution: "@storybook/node-logger@npm:8.1.3"
+ checksum: 10/b3e372f7c56c5a8e60144ca8fd1755a964ceaf33031357649ea7da09a23756864df44f4ad081562ba81f034ac1861e4deb5bd129197e42e03f4a5d2037937cee
languageName: node
linkType: hard
@@ -2876,16 +2883,16 @@ __metadata:
languageName: node
linkType: hard
-"@storybook/preview-api@npm:8.1.1":
- version: 8.1.1
- resolution: "@storybook/preview-api@npm:8.1.1"
+"@storybook/preview-api@npm:8.1.3":
+ version: 8.1.3
+ resolution: "@storybook/preview-api@npm:8.1.3"
dependencies:
- "@storybook/channels": "npm:8.1.1"
- "@storybook/client-logger": "npm:8.1.1"
- "@storybook/core-events": "npm:8.1.1"
+ "@storybook/channels": "npm:8.1.3"
+ "@storybook/client-logger": "npm:8.1.3"
+ "@storybook/core-events": "npm:8.1.3"
"@storybook/csf": "npm:^0.1.7"
"@storybook/global": "npm:^5.0.0"
- "@storybook/types": "npm:8.1.1"
+ "@storybook/types": "npm:8.1.3"
"@types/qs": "npm:^6.9.5"
dequal: "npm:^2.0.2"
lodash: "npm:^4.17.21"
@@ -2894,7 +2901,7 @@ __metadata:
tiny-invariant: "npm:^1.3.1"
ts-dedent: "npm:^2.0.0"
util-deprecate: "npm:^1.0.2"
- checksum: 10/fb6c9e38d86f039075b80ec1b8b5eccd2ef624b4f393760de243a79b60e108e6afb25ce50a3b01eb749575f104ebeeae6829b76216273b1e81e226e3e0c08745
+ checksum: 10/5b760236cfee5fe268f9d997cdbcb7bcb7119aea583c515a1990e08573b112c8c6c979959837ae89e988bfda9767cd2c7f7b408fe780ba92ca47e52e7e7f053f
languageName: node
linkType: hard
@@ -2905,10 +2912,10 @@ __metadata:
languageName: node
linkType: hard
-"@storybook/preview@npm:8.1.1":
- version: 8.1.1
- resolution: "@storybook/preview@npm:8.1.1"
- checksum: 10/220dc8a668ec83ac38f656f3557c5c95e6cd69330b84298ab2863896fe63b39c1023296d097d235a65563db01e73345f89323f85160f027e9f57a66da9cfec5b
+"@storybook/preview@npm:8.1.3":
+ version: 8.1.3
+ resolution: "@storybook/preview@npm:8.1.3"
+ checksum: 10/84abd5e08369bb0236cc66c9300edc38bf1ce370adeced6bb90da0ea8aa58c8c05f987c1612acd16a46577c5d8b4aec0b3856931288d225a5b43979100f77245
languageName: node
linkType: hard
@@ -2922,13 +2929,13 @@ __metadata:
languageName: node
linkType: hard
-"@storybook/react-dom-shim@npm:8.1.1":
- version: 8.1.1
- resolution: "@storybook/react-dom-shim@npm:8.1.1"
+"@storybook/react-dom-shim@npm:8.1.3":
+ version: 8.1.3
+ resolution: "@storybook/react-dom-shim@npm:8.1.3"
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
- checksum: 10/db207111ea90ed6a31d7280e56586c3c048d971f6b8ac8f6708af8e71de9cb462a0138ac7618b857f1d6f188ca7bbec1d081c964e1c31a498c330da5d1843e41
+ checksum: 10/493df9b4d3f5241007c2d268e684002d499e5f6532538f40c4348fc5272b8f2732e2a94a4ccedc8f28c715e38f0acb55edcda6c540a855f344225e48159ca1e8
languageName: node
linkType: hard
@@ -2951,16 +2958,16 @@ __metadata:
languageName: node
linkType: hard
-"@storybook/react-vite@npm:8.1.1":
- version: 8.1.1
- resolution: "@storybook/react-vite@npm:8.1.1"
+"@storybook/react-vite@npm:8.1.3":
+ version: 8.1.3
+ resolution: "@storybook/react-vite@npm:8.1.3"
dependencies:
"@joshwooding/vite-plugin-react-docgen-typescript": "npm:0.3.1"
"@rollup/pluginutils": "npm:^5.0.2"
- "@storybook/builder-vite": "npm:8.1.1"
- "@storybook/node-logger": "npm:8.1.1"
- "@storybook/react": "npm:8.1.1"
- "@storybook/types": "npm:8.1.1"
+ "@storybook/builder-vite": "npm:8.1.3"
+ "@storybook/node-logger": "npm:8.1.3"
+ "@storybook/react": "npm:8.1.3"
+ "@storybook/types": "npm:8.1.3"
find-up: "npm:^5.0.0"
magic-string: "npm:^0.30.0"
react-docgen: "npm:^7.0.0"
@@ -2970,7 +2977,7 @@ __metadata:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
vite: ^4.0.0 || ^5.0.0
- checksum: 10/5d518889cafd12861bab416c32580e334cd5737862331c03052db2e65772b935c7e1803936b62bb71d904584f47e0d78bca4d6b8ec617bbf2c931122b7640fde
+ checksum: 10/a102eab5dbad5e222892ebe6e4225500ddd36537f990f884e46b6b901335e50a954eb528527f7952bc0340eda904260ac84ffe48c91da1ce718a5be14b0aefbc
languageName: node
linkType: hard
@@ -3010,16 +3017,16 @@ __metadata:
languageName: node
linkType: hard
-"@storybook/react@npm:8.1.1":
- version: 8.1.1
- resolution: "@storybook/react@npm:8.1.1"
+"@storybook/react@npm:8.1.3":
+ version: 8.1.3
+ resolution: "@storybook/react@npm:8.1.3"
dependencies:
- "@storybook/client-logger": "npm:8.1.1"
- "@storybook/docs-tools": "npm:8.1.1"
+ "@storybook/client-logger": "npm:8.1.3"
+ "@storybook/docs-tools": "npm:8.1.3"
"@storybook/global": "npm:^5.0.0"
- "@storybook/preview-api": "npm:8.1.1"
- "@storybook/react-dom-shim": "npm:8.1.1"
- "@storybook/types": "npm:8.1.1"
+ "@storybook/preview-api": "npm:8.1.3"
+ "@storybook/react-dom-shim": "npm:8.1.3"
+ "@storybook/types": "npm:8.1.3"
"@types/escodegen": "npm:^0.0.6"
"@types/estree": "npm:^0.0.51"
"@types/node": "npm:^18.0.0"
@@ -3042,7 +3049,7 @@ __metadata:
peerDependenciesMeta:
typescript:
optional: true
- checksum: 10/a2c50c12d3b284ea1bee13a93033f36968a0963c84dabb254225865faebacdbbe51e594b39abc8fe8f8d0f04a4a509fabdaa987a32614a18cb590b5f828c2fe1
+ checksum: 10/8646b3afe337b2544e23a75ea8772578ac839e3d4de29bb401acc9792ea39c464c896db261bb776ad2e2bf141dd7fbdf893c975698872b9c756fc662dec1148a
languageName: node
linkType: hard
@@ -3058,14 +3065,14 @@ __metadata:
languageName: node
linkType: hard
-"@storybook/types@npm:8.1.1":
- version: 8.1.1
- resolution: "@storybook/types@npm:8.1.1"
+"@storybook/types@npm:8.1.3":
+ version: 8.1.3
+ resolution: "@storybook/types@npm:8.1.3"
dependencies:
- "@storybook/channels": "npm:8.1.1"
+ "@storybook/channels": "npm:8.1.3"
"@types/express": "npm:^4.7.0"
file-system-cache: "npm:2.3.0"
- checksum: 10/f367334074e3f4464bcd0efdad75a0110a11d72265f8dc398d2e53a62b9d77dc159006a0db50bd63f0844c3466d7c7a099a0a81cddb87acfeb2e3d178d6cbfbb
+ checksum: 10/856dc930023a758e57a1512a9cfabf321f382fbfd78bcb27466a53850f857ed3f43351ca1a3003fbcf194dfe6296488e0dc75c6bfa80b3884e56517bfc4c8119
languageName: node
linkType: hard
@@ -4875,6 +4882,17 @@ __metadata:
languageName: node
linkType: hard
+"axios@npm:1.7.2":
+ version: 1.7.2
+ resolution: "axios@npm:1.7.2"
+ dependencies:
+ follow-redirects: "npm:^1.15.6"
+ form-data: "npm:^4.0.0"
+ proxy-from-env: "npm:^1.1.0"
+ checksum: 10/6ae80dda9736bb4762ce717f1a26ff997d94672d3a5799ad9941c24d4fb019c1dff45be8272f08d1975d7950bac281f3ba24aff5ecd49ef5a04d872ec428782f
+ languageName: node
+ linkType: hard
+
"axobject-query@npm:^3.2.1":
version: 3.2.1
resolution: "axobject-query@npm:3.2.1"
@@ -7901,10 +7919,10 @@ __metadata:
"@emotion/styled": "npm:11.11.5"
"@graasp/chatbox": "npm:3.1.0"
"@graasp/map": "npm:1.12.1"
- "@graasp/query-client": "npm:3.8.0"
- "@graasp/sdk": "npm:4.11.0"
+ "@graasp/query-client": "npm:3.9.0"
+ "@graasp/sdk": "npm:4.12.0"
"@graasp/translations": "npm:1.28.0"
- "@graasp/ui": "npm:4.18.2"
+ "@graasp/ui": "npm:4.19.0"
"@mui/icons-material": "npm:5.15.18"
"@mui/lab": "npm:5.0.0-alpha.170"
"@mui/material": "npm:5.15.18"
@@ -9750,6 +9768,15 @@ __metadata:
languageName: node
linkType: hard
+"lucide-react@npm:0.379.0":
+ version: 0.379.0
+ resolution: "lucide-react@npm:0.379.0"
+ peerDependencies:
+ react: ^16.5.1 || ^17.0.0 || ^18.0.0
+ checksum: 10/e2231158df68e08c1a4adbbd14c92380f6b4b541ce0dcacef1a05a17d8bc7e7329bb9f3f34ed0800afda82ef5c3f972749ab4d2f56a536f25ee42d81d8abcbc6
+ languageName: node
+ linkType: hard
+
"lz-string@npm:^1.5.0":
version: 1.5.0
resolution: "lz-string@npm:1.5.0"