diff --git a/cypress/e2e/item/hide/hideItem.cy.ts b/cypress/e2e/item/hide/hideItem.cy.ts index bb5efd3f8..57ce63b02 100644 --- a/cypress/e2e/item/hide/hideItem.cy.ts +++ b/cypress/e2e/item/hide/hideItem.cy.ts @@ -4,9 +4,15 @@ import { PermissionLevel, } from '@graasp/sdk'; -import { HOME_PATH, buildItemPath } from '../../../../src/config/paths'; +import { + HOME_PATH, + buildItemPath, + buildItemSettingsPath, + buildItemSharePath, +} from '../../../../src/config/paths'; import { HIDDEN_ITEM_BUTTON_CLASS, + SETTINGS_HIDE_ITEM_ID, buildHideButtonId, buildItemsGridMoreButtonSelector, } from '../../../../src/config/selectors'; @@ -100,4 +106,70 @@ describe('Hide Item', () => { expect(classList.some((c) => c.includes('disabled'))).to.be.true; }); }); + + describe('Sharing tab', () => { + it('Hide an item', () => { + cy.visit(buildItemSharePath(ITEM.id)); + + cy.get(`#${SETTINGS_HIDE_ITEM_ID}`).click(); + + cy.wait(`@postItemTag-${ItemTagType.Hidden}`).then( + ({ request: { url } }) => { + expect(url).to.contain(ItemTagType.Hidden); + expect(url).to.contain(ITEM.id); + }, + ); + }); + it('Show an item', () => { + cy.visit(buildItemSharePath(HIDDEN_ITEM.id)); + + cy.get(`#${SETTINGS_HIDE_ITEM_ID}`).click(); + + cy.wait(`@deleteItemTag-${ItemTagType.Hidden}`).then( + ({ request: { url } }) => { + expect(url).to.contain(ItemTagType.Hidden); + expect(url).to.contain(HIDDEN_ITEM.id); + }, + ); + }); + + it('Cannot hide child of hidden item', () => { + cy.visit(buildItemSharePath(CHILD_HIDDEN_ITEM.id)); + + cy.get(`#${SETTINGS_HIDE_ITEM_ID}`).should('be.disabled'); + }); + }); + + describe('Settings', () => { + it('Hide an item', () => { + cy.visit(buildItemSettingsPath(ITEM.id)); + + cy.get(`#${SETTINGS_HIDE_ITEM_ID}`).click(); + + cy.wait(`@postItemTag-${ItemTagType.Hidden}`).then( + ({ request: { url } }) => { + expect(url).to.contain(ItemTagType.Hidden); + expect(url).to.contain(ITEM.id); + }, + ); + }); + it('Show an item', () => { + cy.visit(buildItemSettingsPath(HIDDEN_ITEM.id)); + + cy.get(`#${SETTINGS_HIDE_ITEM_ID}`).click(); + + cy.wait(`@deleteItemTag-${ItemTagType.Hidden}`).then( + ({ request: { url } }) => { + expect(url).to.contain(ItemTagType.Hidden); + expect(url).to.contain(HIDDEN_ITEM.id); + }, + ); + }); + + it('Cannot hide child of hidden item', () => { + cy.visit(buildItemSettingsPath(CHILD_HIDDEN_ITEM.id)); + + cy.get(`#${SETTINGS_HIDE_ITEM_ID}`).should('be.disabled'); + }); + }); }); diff --git a/cypress/e2e/item/share/changeVisibility.cy.ts b/cypress/e2e/item/share/changeVisibility.cy.ts index bad7ef205..f81f54565 100644 --- a/cypress/e2e/item/share/changeVisibility.cy.ts +++ b/cypress/e2e/item/share/changeVisibility.cy.ts @@ -13,6 +13,7 @@ import { SHARE_ITEM_PSEUDONYMIZED_SCHEMA_ID, SHARE_ITEM_VISIBILITY_SELECT_ID, UPDATE_VISIBILITY_MODAL_VALIDATE_BUTTON, + VISIBILITY_HIDDEN_ALERT_ID, buildDataCyWrapper, buildShareButtonId, } from '../../../../src/config/selectors'; @@ -138,6 +139,24 @@ describe('Visibility of an Item', () => { }); }); + it('Show hidden alert', () => { + const item = PackedFolderItemFactory({}, { hiddenTag: {} }); + const ITEM_LOGIN_ITEM = { + ...item, + itemLoginSchema: { + item, + type: ItemLoginSchemaType.Username, + id: 'efaf3d5a-5688-11eb-ae93-0242ac130002', + createdAt: '2021-08-11T12:56:36.834Z', + updatedAt: '2021-08-11T12:56:36.834Z', + }, + }; + cy.setUpApi({ items: [ITEM_LOGIN_ITEM] }); + cy.visit(buildItemPath(item.id)); + cy.get(`#${buildShareButtonId(item.id)}`).click(); + cy.get(`#${VISIBILITY_HIDDEN_ALERT_ID}`).should('be.visible'); + }); + describe('Change visibility of published item', () => { it('User should validate the change to private', () => { const item = PublishedItemFactory( diff --git a/cypress/e2e/memberships/viewMemberships.cy.ts b/cypress/e2e/memberships/viewMemberships.cy.ts index b750eae9c..4fa5da059 100644 --- a/cypress/e2e/memberships/viewMemberships.cy.ts +++ b/cypress/e2e/memberships/viewMemberships.cy.ts @@ -51,6 +51,20 @@ const membershipsWithoutAdmin = [ }), ]; +const checkItemMembershipRow = ({ + id, + name, + permission, +}: { + id: string; + name: string; + permission: 'disabled' | PermissionLevel; +}): void => { + cy.get(buildDataCyWrapper(buildItemMembershipRowId(id))) + .should('contain', name) + .should('contain', i18n.t(permission, { ns: namespaces.enums })); +}; + describe('View Memberships - Individual', () => { beforeEach(() => { cy.setUpApi({ @@ -93,6 +107,211 @@ describe('View Memberships - Individual', () => { }); }); +describe('View Memberships - Hidden item', () => { + it('view disabled memberships for hidden item', () => { + const hiddenItem = PackedFolderItemFactory({}, { hiddenTag: {} }); + const adminHiddenMembership = buildItemMembership({ + item: hiddenItem, + permission: PermissionLevel.Admin, + account: MEMBERS.ANNA, + creator: MEMBERS.ANNA, + }); + const writeHiddenMembership = buildItemMembership({ + item: hiddenItem, + permission: PermissionLevel.Write, + account: MEMBERS.EVAN, + creator: MEMBERS.ANNA, + }); + const readHiddenMembership = buildItemMembership({ + item: hiddenItem, + permission: PermissionLevel.Read, + account: MEMBERS.GARRY, + creator: MEMBERS.ANNA, + }); + const itemLoginSchema = ItemLoginSchemaFactory({ + type: ItemLoginSchemaType.Username, + item: hiddenItem, + }); + const guestMemberships = [ + buildItemMembership({ + item: hiddenItem, + permission: PermissionLevel.Read, + account: GuestFactory({ + itemLoginSchema, + }), + creator: MEMBERS.ANNA, + }), + buildItemMembership({ + item: hiddenItem, + permission: PermissionLevel.Read, + account: GuestFactory({ + itemLoginSchema, + }), + creator: MEMBERS.ANNA, + }), + ]; + const item = hiddenItem; + cy.setUpApi({ + items: [ + { + ...hiddenItem, + itemLoginSchema, + memberships: [ + adminHiddenMembership, + writeHiddenMembership, + readHiddenMembership, + ...guestMemberships, + ], + }, + ], + }); + i18n.changeLanguage(CURRENT_USER.extra.lang); + cy.visit(buildItemSharePath(item.id)); + + // admin and write are enabled + checkItemMembershipRow({ + id: adminHiddenMembership.id, + name: adminHiddenMembership.account.name, + permission: adminHiddenMembership.permission, + }); + checkItemMembershipRow({ + id: writeHiddenMembership.id, + name: writeHiddenMembership.account.name, + permission: writeHiddenMembership.permission, + }); + + // read are disabled + checkItemMembershipRow({ + id: readHiddenMembership.id, + name: readHiddenMembership.account.name, + permission: ItemLoginSchemaStatus.Disabled, + }); + + // guests are disabled + for (const { account, id } of guestMemberships) { + const { name } = Object.values( + guestMemberships.map((m) => m.account), + ).find(({ id: mId }) => mId === account.id); + + // check name and disabled permission + checkItemMembershipRow({ + id, + name, + permission: ItemLoginSchemaStatus.Disabled, + }); + // check delete button exists + cy.get(`#${buildItemMembershipRowDeleteButtonId(id)}`).should('exist'); + } + }); + it('view frozen guest membership', () => { + const itemLoginSchema = ItemLoginSchemaFactory({ + type: ItemLoginSchemaType.Username, + item: itemWithAdmin, + status: ItemLoginSchemaStatus.Freeze, + }); + const guestMemberships = [ + buildItemMembership({ + item: itemWithAdmin, + permission: PermissionLevel.Read, + account: GuestFactory({ + itemLoginSchema, + }), + creator: MEMBERS.ANNA, + }), + buildItemMembership({ + item: itemWithAdmin, + permission: PermissionLevel.Read, + account: GuestFactory({ + itemLoginSchema, + }), + creator: MEMBERS.ANNA, + }), + ]; + const item = itemWithAdmin; + cy.setUpApi({ + items: [ + { + ...itemWithAdmin, + itemLoginSchema, + memberships: [adminMembership, ...guestMemberships], + }, + ], + }); + i18n.changeLanguage(CURRENT_USER.extra.lang); + cy.visit(buildItemSharePath(item.id)); + // editable rows + for (const { permission, account, id } of guestMemberships) { + const { name } = Object.values( + guestMemberships.map((m) => m.account), + ).find(({ id: mId }) => mId === account.id); + + // check name and disabled permission + cy.get(buildDataCyWrapper(buildItemMembershipRowId(id))) + .should('contain', name) + .should('contain', i18n.t(permission, { ns: namespaces.enums })); + + // check delete button exists + cy.get(`#${buildItemMembershipRowDeleteButtonId(id)}`).should('exist'); + } + }); + + it('view disabled guest membership', () => { + const itemLoginSchema = ItemLoginSchemaFactory({ + type: ItemLoginSchemaType.Username, + item: itemWithAdmin, + status: ItemLoginSchemaStatus.Disabled, + }); + const guestMemberships = [ + buildItemMembership({ + item: itemWithAdmin, + permission: PermissionLevel.Read, + account: GuestFactory({ + itemLoginSchema, + }), + creator: MEMBERS.ANNA, + }), + buildItemMembership({ + item: itemWithAdmin, + permission: PermissionLevel.Read, + account: GuestFactory({ + itemLoginSchema, + }), + creator: MEMBERS.ANNA, + }), + ]; + const item = itemWithAdmin; + cy.setUpApi({ + items: [ + { + ...itemWithAdmin, + itemLoginSchema, + memberships: [adminMembership, ...guestMemberships], + }, + ], + }); + i18n.changeLanguage(CURRENT_USER.extra.lang); + cy.visit(buildItemSharePath(item.id)); + // editable rows + for (const { permission, account, id } of guestMemberships) { + const { name } = Object.values( + guestMemberships.map((m) => m.account), + ).find(({ id: mId }) => mId === account.id); + + // check name and disabled permission + cy.get(buildDataCyWrapper(buildItemMembershipRowId(id))) + .should('contain', name) + .should('not.contain', permission) + .should( + 'contain', + i18n.t(ItemLoginSchemaStatus.Disabled, { ns: namespaces.enums }), + ); + + // check delete button exists + cy.get(`#${buildItemMembershipRowDeleteButtonId(id)}`).should('exist'); + } + }); +}); + describe('View Memberships - Guest', () => { it('view guest membership', () => { const itemLoginSchema = ItemLoginSchemaFactory({ diff --git a/src/components/hooks/useVisibility.tsx b/src/components/hooks/useVisibility.tsx index 8366ea67c..198a957a4 100644 --- a/src/components/hooks/useVisibility.tsx +++ b/src/components/hooks/useVisibility.tsx @@ -23,7 +23,6 @@ const { type UseVisibility = { isLoading: boolean; - isError: boolean; isDisabled: boolean; itemPublishEntry: ItemPublished | null | undefined; itemLoginSchema: ItemLoginSchema | undefined; @@ -52,26 +51,26 @@ export const useVisibility = (item: PackedItem): UseVisibility => { // is disabled const [isDisabled, setIsDisabled] = useState(false); useEffect(() => { - // disable setting if any visiblity is set on any parent items - setIsDisabled( - Boolean( - (itemLoginSchema && itemLoginSchema?.item?.path !== item?.path) || - (item?.public && item?.public?.item?.path !== item?.path), - ), - ); + // disable setting if item login schema is enabled and is not defined on the current item + const shouldItemLoginBeDisabled = + itemLoginSchema && + itemLoginSchema?.status !== ItemLoginSchemaStatus.Disabled && + itemLoginSchema?.item?.path !== item?.path; + + // disable setting if item is public and is not defined on the current item + const shouldPublicBeDisabled = + item?.public && item?.public?.item?.path !== item?.path; + + setIsDisabled(Boolean(shouldItemLoginBeDisabled || shouldPublicBeDisabled)); }, [itemLoginSchema, item]); // is loading - const [isLoading, setIsLoading] = useState(false); - useEffect(() => { - setIsLoading(isItemPublishEntryLoading || isItemLoginLoading); - }, [isItemPublishEntryLoading, isItemLoginLoading]); - - // is error - const [isError] = useState(false); + const isLoading = isItemPublishEntryLoading || isItemLoginLoading; // visibility - const [visibility, setVisibility] = useState(); + const [visibility, setVisibility] = useState( + SETTINGS.ITEM_PRIVATE.name, + ); useEffect(() => { switch (true) { case Boolean(item.public): { @@ -140,21 +139,20 @@ export const useVisibility = (item: PackedItem): UseVisibility => { } }, [ - deleteItemTag, - item.id, - itemLoginSchema, itemPublishEntry, - postItemTag, item.public, - putItemLoginSchema, + item.id, unpublish, + deleteItemTag, + itemLoginSchema, + putItemLoginSchema, + postItemTag, ], ); return useMemo( () => ({ isLoading, - isError, isDisabled, itemPublishEntry, itemLoginSchema, @@ -164,7 +162,6 @@ export const useVisibility = (item: PackedItem): UseVisibility => { }), [ isLoading, - isError, isDisabled, itemPublishEntry, itemLoginSchema, diff --git a/src/components/item/FolderContent.tsx b/src/components/item/FolderContent.tsx index 975814933..e9bf73b8b 100644 --- a/src/components/item/FolderContent.tsx +++ b/src/components/item/FolderContent.tsx @@ -155,11 +155,16 @@ const FolderContent = ({ item }: { item: PackedItem }): JSX.Element => { ); if (children) { - const sortedChildren = children.sort(sortFn); + const sortedChildren = children.toSorted(sortFn); return ( <> - + { return ( - + - + ); diff --git a/src/components/item/settings/ItemSettingsProperties.tsx b/src/components/item/settings/ItemSettingsProperties.tsx index 094aa6fe4..7cb56f5ea 100644 --- a/src/components/item/settings/ItemSettingsProperties.tsx +++ b/src/components/item/settings/ItemSettingsProperties.tsx @@ -1,6 +1,6 @@ import { Stack } from '@mui/material'; -import { DiscriminatedItem, ItemType } from '@graasp/sdk'; +import { ItemType, PackedItem } from '@graasp/sdk'; import { ActionButton, PinButton } from '@graasp/ui'; import { BarChart3, MessageSquareOff, MessageSquareText } from 'lucide-react'; @@ -21,6 +21,7 @@ import { import { BUILDER } from '@/langs/constants'; import DescriptionPlacementForm from '../form/DescriptionPlacementForm'; +import HideSettingCheckbox from '../sharing/HideSettingCheckbox'; import ItemSettingCheckBoxProperty from './ItemSettingCheckBoxProperty'; import LinkSettings from './LinkSettings'; import FileAlignmentSetting from './file/FileAlignmentSetting'; @@ -28,10 +29,10 @@ import FileMaxWidthSetting from './file/FileMaxWidthSetting'; import { SettingVariant } from './settingTypes'; type Props = { - item: DiscriminatedItem; + item: PackedItem; }; -type ItemSetting = DiscriminatedItem['settings']; +type ItemSetting = PackedItem['settings']; const ItemSettingsProperties = ({ item }: Props): JSX.Element => { const { t: translateBuilder } = useBuilderTranslation(); @@ -142,6 +143,8 @@ const ItemSettingsProperties = ({ item }: Props): JSX.Element => { } /> + + { + const { mutate: postItemTag } = mutations.usePostItemTag(); + const { mutate: deleteItemTag } = mutations.useDeleteItemTag(); + const { t: translateBuilder } = useBuilderTranslation(); + + const isDisabled = item.hidden && item.hidden.item.id !== item.id; + + const onClick = (checked: boolean): void => { + if (!checked) { + postItemTag({ itemId: item.id, type: ItemTagType.Hidden }); + } else { + deleteItemTag({ + itemId: item.id, + type: ItemTagType.Hidden, + }); + } + }; + + return ( + : } + checked={Boolean(!item.hidden)} + onClick={onClick} + valueText={(() => { + if (isDisabled) { + return translateBuilder(BUILDER.HIDE_ITEM_HIDDEN_PARENT_INFORMATION); + } + return item.hidden + ? translateBuilder(BUILDER.HIDE_ITEM_SETTING_DESCRIPTION_ENABLED) + : translateBuilder(BUILDER.HIDE_ITEM_SETTING_DESCRIPTION_DISABLED); + })()} + /> + ); +}; + +export default HideSettingCheckbox; diff --git a/src/components/item/sharing/ItemSharingTab.tsx b/src/components/item/sharing/ItemSharingTab.tsx index 088c46fc7..709768acf 100644 --- a/src/components/item/sharing/ItemSharingTab.tsx +++ b/src/components/item/sharing/ItemSharingTab.tsx @@ -1,14 +1,23 @@ import { useOutletContext } from 'react-router-dom'; -import { Box, Container, Divider, Stack, Typography } from '@mui/material'; +import { + Alert, + Box, + Container, + Divider, + Stack, + Typography, +} from '@mui/material'; import { AccountType } from '@graasp/sdk'; import { OutletType } from '@/components/pages/item/type'; +import { VISIBILITY_HIDDEN_ALERT_ID } from '@/config/selectors'; import { useBuilderTranslation } from '../../../config/i18n'; import { hooks } from '../../../config/queryClient'; import { BUILDER } from '../../../langs/constants'; +import HideSettingCheckbox from './HideSettingCheckbox'; import VisibilitySelect from './VisibilitySelect'; import MembershipTabs from './membershipTable/MembershipTabs'; import ShortLinksRenderer from './shortLink/ShortLinksRenderer'; @@ -39,7 +48,22 @@ const ItemSharingTab = (): JSX.Element => { {translateBuilder(BUILDER.ITEM_SETTINGS_VISIBILITY_TITLE)} - + {item.hidden ? ( + + {translateBuilder( + BUILDER.ITEM_SETTINGS_VISIBILITY_HIDDEN_INFORMATION, + )} + + ) : ( + + + + )} + diff --git a/src/components/item/sharing/VisibilitySelect.tsx b/src/components/item/sharing/VisibilitySelect.tsx index fa796ee0b..5409510ff 100644 --- a/src/components/item/sharing/VisibilitySelect.tsx +++ b/src/components/item/sharing/VisibilitySelect.tsx @@ -23,7 +23,6 @@ const VisibilitySelect = ({ item, edit }: Props): JSX.Element | null => { const { visibility, - isError, isDisabled, itemLoginSchema, isLoading, @@ -46,12 +45,6 @@ const VisibilitySelect = ({ item, edit }: Props): JSX.Element | null => { return ; } - // hide visibility select if cannot access item tags - // this happens when accessing a public item - if (isError) { - return null; - } - const renderVisiblityIndication = () => { switch (visibility) { case SETTINGS.ITEM_LOGIN.name: diff --git a/src/components/item/sharing/membershipTable/GuestItemMembershipTableRow.tsx b/src/components/item/sharing/membershipTable/GuestItemMembershipTableRow.tsx index f869ac24b..9a344e5f4 100644 --- a/src/components/item/sharing/membershipTable/GuestItemMembershipTableRow.tsx +++ b/src/components/item/sharing/membershipTable/GuestItemMembershipTableRow.tsx @@ -2,40 +2,28 @@ import { TableCell, Tooltip, Typography } from '@mui/material'; import { DiscriminatedItem, - ItemLoginSchema, ItemLoginSchemaStatus, ItemMembership, } from '@graasp/sdk'; import { useBuilderTranslation, useEnumsTranslation } from '@/config/i18n'; -import { hooks } from '@/config/queryClient'; import { BUILDER } from '@/langs/constants'; import { buildItemMembershipRowId } from '../../../../config/selectors'; import DeleteItemMembershipButton from './DeleteItemMembershipButton'; import { StyledTableRow } from './StyledTableRow'; -const DISABLED_COLOR = '#a5a5a5'; - const GuestItemMembershipTableRow = ({ data, itemId, - itemLoginSchema, + isDisabled, }: { data: ItemMembership; itemId: DiscriminatedItem['id']; - itemLoginSchema?: ItemLoginSchema; + isDisabled?: boolean; }): JSX.Element => { const { t: translateEnums } = useEnumsTranslation(); const { t: translateBuilder } = useBuilderTranslation(); - const { data: currentAccount } = hooks.useCurrentMember(); - - const itemLoginSchemaIsDisabled = - !itemLoginSchema || - itemLoginSchema.status === ItemLoginSchemaStatus.Disabled; - - const isDisabled = - currentAccount?.id !== data.account.id && itemLoginSchemaIsDisabled; return ( @@ -59,7 +47,7 @@ const GuestItemMembershipTableRow = ({ {isDisabled ? ( - + {translateEnums(ItemLoginSchemaStatus.Disabled)} ) : ( diff --git a/src/components/item/sharing/membershipTable/ItemMembershipTableRow.tsx b/src/components/item/sharing/membershipTable/ItemMembershipTableRow.tsx index ab3660fef..9664329a9 100644 --- a/src/components/item/sharing/membershipTable/ItemMembershipTableRow.tsx +++ b/src/components/item/sharing/membershipTable/ItemMembershipTableRow.tsx @@ -3,9 +3,11 @@ import { TableCell, Typography } from '@mui/material'; import { AccountType, DiscriminatedItem, + ItemLoginSchemaStatus, ItemMembership, PermissionLevel, } from '@graasp/sdk'; +import { DEFAULT_TEXT_DISABLED_COLOR } from '@graasp/ui'; import { useEnumsTranslation } from '@/config/i18n'; import { mutations } from '@/config/queryClient'; @@ -23,11 +25,13 @@ const ItemMembershipTableRow = ({ isOnlyAdmin = false, item, data, + isDisabled, }: { data: ItemMembership; item: DiscriminatedItem; allowDowngrade?: boolean; isOnlyAdmin?: boolean; + isDisabled: boolean; }): JSX.Element => { const { t: translateEnums } = useEnumsTranslation(); @@ -53,15 +57,31 @@ const ItemMembershipTableRow = ({ return ( - + {data.account.name} - + {data.account.type === AccountType.Individual && data.account.email} - {translateEnums(data.permission)} + + {isDisabled ? ( + + {translateEnums(ItemLoginSchemaStatus.Disabled)} + + ) : ( + translateEnums(data.permission) + )} + {!isOnlyAdmin && ( diff --git a/src/components/item/sharing/membershipTable/ItemMembershipsTable.tsx b/src/components/item/sharing/membershipTable/ItemMembershipsTable.tsx index 165ce1b00..a78086098 100644 --- a/src/components/item/sharing/membershipTable/ItemMembershipsTable.tsx +++ b/src/components/item/sharing/membershipTable/ItemMembershipsTable.tsx @@ -10,7 +10,12 @@ import { TableRow, } from '@mui/material'; -import { AccountType, PermissionLevel } from '@graasp/sdk'; +import { + AccountType, + ItemLoginSchemaStatus, + PermissionLevel, + PermissionLevelCompare, +} from '@graasp/sdk'; import groupby from 'lodash.groupby'; @@ -66,6 +71,11 @@ const ItemMembershipsTable = ({ showEmail = true }: Props): JSX.Element => { const { data: itemLoginSchema } = hooks.useItemLoginSchema({ itemId: item.id, }); + const { data: currentAccount } = hooks.useCurrentMember(); + + const itemLoginSchemaIsDisabled = + !itemLoginSchema || + itemLoginSchema.status === ItemLoginSchemaStatus.Disabled; if (memberships) { // map memberships to corresponding row layout and meaningful data to sort @@ -87,13 +97,21 @@ const ItemMembershipsTable = ({ showEmail = true }: Props): JSX.Element => { // can downgrade for same item im.item.path === item.path } + isDisabled={ + Boolean(item.hidden) && + PermissionLevelCompare.lt(im.permission, PermissionLevel.Write) + } /> ) : ( ), })); diff --git a/src/config/constants.ts b/src/config/constants.ts index 8d8be74cd..c628a3d20 100644 --- a/src/config/constants.ts +++ b/src/config/constants.ts @@ -50,6 +50,9 @@ export const SETTINGS = { ITEM_PUBLIC: { name: 'public-item', }, + ITEM_HIDDEN: { + name: 'hidden', + }, // this tag doesn't exist but is used if none of the visiblity tag is set ITEM_PRIVATE: { name: 'private-item', diff --git a/src/config/selectors.ts b/src/config/selectors.ts index a7482dce9..a495101bd 100644 --- a/src/config/selectors.ts +++ b/src/config/selectors.ts @@ -173,6 +173,7 @@ export const SETTINGS_CHATBOX_TOGGLE_ID = 'settingsChatboxToggle'; export const SETTINGS_COLLAPSE_TOGGLE_ID = 'settingsCollapseToggle'; export const SETTINGS_RESIZE_TOGGLE_ID = 'settingsResizeToggle'; export const SETTINGS_SAVE_ACTIONS_TOGGLE_ID = 'settingsSaveActionsToggle'; +export const SETTINGS_HIDE_ITEM_ID = 'settingsHideItem'; export const ITEMS_TABLE_RESTORE_SELECTED_ITEMS_ID = 'itemsTableRestoreSelectedItems'; @@ -448,3 +449,4 @@ export const MEMBERSHIP_REQUEST_ACCEPT_BUTTON_SELECTOR = export const MEMBERSHIP_REQUEST_REJECT_BUTTON_SELECTOR = 'membershipRequestRejectButton'; export const ENROLL_BUTTON_SELECTOR = 'enrollButton'; +export const VISIBILITY_HIDDEN_ALERT_ID = 'visibilityHiddenAlert'; diff --git a/src/langs/constants.ts b/src/langs/constants.ts index d99571052..eb65dd8f8 100644 --- a/src/langs/constants.ts +++ b/src/langs/constants.ts @@ -76,6 +76,11 @@ export const BUILDER = { FLAG_ITEM_BUTTON: 'FLAG_ITEM_BUTTON', FLAG_ITEM_MODAL_TITLE: 'FLAG_ITEM_MODAL_TITLE', HIDE_ITEM_HIDDEN_PARENT_INFORMATION: 'HIDE_ITEM_HIDDEN_PARENT_INFORMATION', + HIDE_ITEM_SETTING_DESCRIPTION_ENABLED: + 'HIDE_ITEM_SETTING_DESCRIPTION_ENABLED', + HIDE_ITEM_SETTING_DESCRIPTION_DISABLED: + 'HIDE_ITEM_SETTING_DESCRIPTION_DISABLED', + HIDE_ITEM_SETTING_TITLE: 'HIDE_ITEM_SETTING_TITLE', HIDE_ITEM_HIDE_TEXT: 'HIDE_ITEM_HIDE_TEXT', HIDE_ITEM_SHOW_TEXT: 'HIDE_ITEM_SHOW_TEXT', HOME_TITLE: 'HOME_TITLE', @@ -287,6 +292,8 @@ export const BUILDER = { 'ITEM_SETTINGS_VISIBILITY_PRIVATE_INFORMATION', ITEM_SETTINGS_VISIBILITY_PRIVATE_LABEL: 'ITEM_SETTINGS_VISIBILITY_PRIVATE_LABEL', + ITEM_SETTINGS_VISIBILITY_HIDDEN_LABEL: + 'ITEM_SETTINGS_VISIBILITY_HIDDEN_LABEL', ITEM_SETTINGS_VISIBILITY_PSEUDONYMIZED_LABEL: 'ITEM_SETTINGS_VISIBILITY_PSEUDONYMIZED_LABEL', ITEM_SETTINGS_VISIBILITY_PSEUDONYMIZED_SCHEMA_PSEUDONYM_AND_PASSWORD_LABEL: @@ -297,6 +304,8 @@ export const BUILDER = { 'ITEM_SETTINGS_VISIBILITY_PSEUDONYMIZED_SCHEMA_SELECT_MESSAGE', ITEM_SETTINGS_VISIBILITY_PUBLIC_INFORMATIONS: 'ITEM_SETTINGS_VISIBILITY_PUBLIC_INFORMATIONS', + ITEM_SETTINGS_VISIBILITY_HIDDEN_INFORMATION: + 'ITEM_SETTINGS_VISIBILITY_HIDDEN_INFORMATION', ITEM_SETTINGS_VISIBILITY_PUBLIC_LABEL: 'ITEM_SETTINGS_VISIBILITY_PUBLIC_LABEL', ITEM_SETTINGS_VISIBILITY_TITLE: 'ITEM_SETTINGS_VISIBILITY_TITLE', diff --git a/src/langs/en.json b/src/langs/en.json index c49f7f6b4..f6acace03 100644 --- a/src/langs/en.json +++ b/src/langs/en.json @@ -79,7 +79,10 @@ "BOOKMARKED_ITEMS_TITLE": "Bookmarks", "FLAG_ITEM_BUTTON": "Flag", "FLAG_ITEM_MODAL_TITLE": "Flag Item", + "HIDE_ITEM_SETTING_TITLE": "Hide for readers", "HIDE_ITEM_HIDDEN_PARENT_INFORMATION": "This item is hidden because its parent item is hidden.", + "HIDE_ITEM_SETTING_DESCRIPTION_ENABLED": "Readers cannot see this item and its content.", + "HIDE_ITEM_SETTING_DESCRIPTION_DISABLED": "Readers can see this item and its content.", "HIDE_ITEM_HIDE_TEXT": "Hide", "HIDE_ITEM_SHOW_TEXT": "Show", "HOME_TITLE": "Home", @@ -145,12 +148,14 @@ "ITEM_SETTINGS_CO_EDITORS_TITLE": "Co-Editors", "ITEM_SETTINGS_VISIBILITY_CANNOT_EDIT_PARENT_MESSAGE": "You cannot modify the visibility because it is defined in one of the parent of this item.", "ITEM_SETTINGS_VISIBILITY_PRIVATE_INFORMATION": "This item is private. Only authorized members can access it.", + "ITEM_SETTINGS_VISIBILITY_HIDDEN_LABEL": "Hidden", "ITEM_SETTINGS_VISIBILITY_PRIVATE_LABEL": "Private", "ITEM_SETTINGS_VISIBILITY_PSEUDONYMIZED_LABEL": "Pseudonymized", "ITEM_SETTINGS_VISIBILITY_PSEUDONYMIZED_SCHEMA_PSEUDONYM_AND_PASSWORD_LABEL": "Pseudonym and Password", "ITEM_SETTINGS_VISIBILITY_PSEUDONYMIZED_SCHEMA_PSEUDONYM_LABEL": "Pseudonym", "ITEM_SETTINGS_VISIBILITY_PSEUDONYMIZED_SCHEMA_SELECT_MESSAGE": "This item is accessible if the visitor provides a ", "ITEM_SETTINGS_VISIBILITY_PUBLIC_INFORMATIONS": "This item is public. Anyone can access it.\n Note: Items will be unpublished automatically if you change the visibility state from public to others.", + "ITEM_SETTINGS_VISIBILITY_HIDDEN_INFORMATION": "This item is hidden. Only admins and writers can access this item.", "ITEM_SETTINGS_VISIBILITY_PUBLIC_LABEL": "Public", "ITEM_SETTINGS_VISIBILITY_TITLE": "Visibility", "ITEM_TAGS_PLACEHOLDER": "Add tags like english, biology, lab, plants, high school", diff --git a/src/langs/fr.json b/src/langs/fr.json index a234923b0..230eb8a5c 100644 --- a/src/langs/fr.json +++ b/src/langs/fr.json @@ -79,7 +79,10 @@ "BOOKMARKED_ITEMS_TITLE": "Signets", "FLAG_ITEM_BUTTON": "Signaler", "FLAG_ITEM_MODAL_TITLE": "Signaler l'élément", - "HIDE_ITEM_HIDDEN_PARENT_INFORMATION": "Cet élément est caché car son élément parent est caché.", + "HIDE_ITEM_SETTING_TITLE": "Rendre visible pour les lecteurs", + "HIDE_ITEM_SETTING_DESCRIPTION_ENABLED": "Les lecteurs ne peuvent pas voir cet élément, ni son contenu.", + "HIDE_ITEM_SETTING_DESCRIPTION_DISABLED": "Les lecteurs peuvent voir cet élément et son contenu.", + "HIDE_ITEM_HIDDEN_PARENT_INFORMATION": "Cet élément est caché car son parent est caché.", "HIDE_ITEM_HIDE_TEXT": "Cacher", "HIDE_ITEM_SHOW_TEXT": "Montrer", "HOME_TITLE": "Accueil", @@ -146,11 +149,13 @@ "ITEM_SETTINGS_VISIBILITY_CANNOT_EDIT_PARENT_MESSAGE": "Vous ne pouvez pas modifier la visibilité car elle est définie dans l'un des parents de cet élément.", "ITEM_SETTINGS_VISIBILITY_PRIVATE_INFORMATION": "Cet élément est privé. Seuls les utilisateurs autorisés peuvent y accéder.", "ITEM_SETTINGS_VISIBILITY_PRIVATE_LABEL": "Privé", - "ITEM_SETTINGS_VISIBILITY_PSEUDONYMIZED_LABEL": "Pseudomisé", + "ITEM_SETTINGS_VISIBILITY_HIDDEN_LABEL": "Caché", + "ITEM_SETTINGS_VISIBILITY_PSEUDONYMIZED_LABEL": "Pseudonyme", "ITEM_SETTINGS_VISIBILITY_PSEUDONYMIZED_SCHEMA_PSEUDONYM_AND_PASSWORD_LABEL": "Pseudonyme et Mot de passe", "ITEM_SETTINGS_VISIBILITY_PSEUDONYMIZED_SCHEMA_PSEUDONYM_LABEL": "Pseudonyme", "ITEM_SETTINGS_VISIBILITY_PSEUDONYMIZED_SCHEMA_SELECT_MESSAGE": "Cet élément est accessible si le visiteur fournit un", "ITEM_SETTINGS_VISIBILITY_PUBLIC_INFORMATIONS": "Cet élément est public. N'importe quel visiteur peut accéder à cet élément.", + "ITEM_SETTINGS_VISIBILITY_HIDDEN_INFORMATION": "Cet élément est caché. Seuls les utilisateurs avec accès administrateur ou écriture peuvent accéder à cet élément.", "ITEM_SETTINGS_VISIBILITY_PUBLIC_LABEL": "Public", "ITEM_SETTINGS_VISIBILITY_TITLE": "Visibilité", "ITEM_TAGS_PLACEHOLDER": "Ajouter des tags tels que biologie, plantes, effet de serre",