diff --git a/src/library-authoring/component-info/ComponentInfo.tsx b/src/library-authoring/component-info/ComponentInfo.tsx index 3e71570170..735257d732 100644 --- a/src/library-authoring/component-info/ComponentInfo.tsx +++ b/src/library-authoring/component-info/ComponentInfo.tsx @@ -11,6 +11,7 @@ import { Link } from 'react-router-dom'; import { getEditUrl } from '../components/utils'; import { ComponentMenu } from '../components'; import { ComponentDeveloperInfo } from './ComponentDeveloperInfo'; +import ComponentManagement from './ComponentManagement'; import ComponentPreview from './ComponentPreview'; import messages from './messages'; @@ -46,7 +47,7 @@ const ComponentInfo = ({ usageKey }: ComponentInfoProps) => { - Manage tab placeholder + Details tab placeholder diff --git a/src/library-authoring/component-info/ComponentInfoHeader.test.tsx b/src/library-authoring/component-info/ComponentInfoHeader.test.tsx index b0033f32ae..0d8e644000 100644 --- a/src/library-authoring/component-info/ComponentInfoHeader.test.tsx +++ b/src/library-authoring/component-info/ComponentInfoHeader.test.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import MockAdapter from 'axios-mock-adapter'; import { IntlProvider } from '@edx/frontend-platform/i18n'; import { AppProvider } from '@edx/frontend-platform/react'; diff --git a/src/library-authoring/component-info/ComponentManagement.test.tsx b/src/library-authoring/component-info/ComponentManagement.test.tsx new file mode 100644 index 0000000000..b56d33bb0f --- /dev/null +++ b/src/library-authoring/component-info/ComponentManagement.test.tsx @@ -0,0 +1,70 @@ +import { setConfig, getConfig } from '@edx/frontend-platform'; + +import { + initializeMocks, + render, + screen, +} from '../../testUtils'; +import { mockLibraryBlockMetadata } from '../data/api.mocks'; +import ComponentManagement from './ComponentManagement'; + +/* + * FIXME: Summarize the reason here + * https://stackoverflow.com/questions/47902335/innertext-is-undefined-in-jest-test + */ +const getInnerText = (element: Element) => element?.textContent + ?.split('\n') + .filter((text) => text && !text.match(/^\s+$/)) + .map((text) => text.trim()) + .join(' '); + +const matchInnerText = (nodeName: string, textToMatch: string) => (_: string, element: Element) => ( + element.nodeName === nodeName && getInnerText(element) === textToMatch +); + +describe('', () => { + it('should render draft status', async () => { + initializeMocks(); + mockLibraryBlockMetadata.applyMock(); + render(); + expect(await screen.findByText('Draft')).toBeInTheDocument(); + expect(await screen.findByText('(Never Published)')).toBeInTheDocument(); + expect(screen.getByText(matchInnerText('SPAN', 'Draft saved on June 20, 2024 at 13:54 UTC.'))).toBeInTheDocument(); + }); + + it('should render published status', async () => { + initializeMocks(); + mockLibraryBlockMetadata.applyMock(); + render(); + expect(await screen.findByText('Published')).toBeInTheDocument(); + expect(screen.getByText('Published')).toBeInTheDocument(); + expect( + screen.getByText(matchInnerText('SPAN', 'Last published on June 21, 2024 at 24:00 UTC by Luke.')), + ).toBeInTheDocument(); + }); + + it('should render the tagging info', async () => { + setConfig({ + ...getConfig(), + ENABLE_TAGGING_TAXONOMY_PAGES: 'true', + }); + initializeMocks(); + mockLibraryBlockMetadata.applyMock(); + render(); + expect(await screen.findByText('Tags')).toBeInTheDocument(); + // TODO: replace with actual data when implement tag list + expect(screen.queryByText('Tags placeholder')).toBeInTheDocument(); + }); + + it('should not render draft status', async () => { + setConfig({ + ...getConfig(), + ENABLE_TAGGING_TAXONOMY_PAGES: 'false', + }); + initializeMocks(); + mockLibraryBlockMetadata.applyMock(); + render(); + expect(await screen.findByText('Draft')).toBeInTheDocument(); + expect(screen.queryByText('Tags')).not.toBeInTheDocument(); + }); +}); diff --git a/src/library-authoring/component-info/ComponentManagement.tsx b/src/library-authoring/component-info/ComponentManagement.tsx new file mode 100644 index 0000000000..172f8331da --- /dev/null +++ b/src/library-authoring/component-info/ComponentManagement.tsx @@ -0,0 +1,57 @@ +import { getConfig } from '@edx/frontend-platform'; +import { useIntl } from '@edx/frontend-platform/i18n'; +import { Collapsible, Icon, Stack } from '@openedx/paragon'; +import { Tag } from '@openedx/paragon/icons'; + +import { useLibraryBlockMetadata } from '../data/apiHooks'; +import StatusWidget from '../generic/status-widget'; +import messages from './messages'; + +interface ComponentManagementProps { + usageKey: string; +} +const ComponentManagement = ({ usageKey }: ComponentManagementProps) => { + const intl = useIntl(); + const { data: componentMetadata } = useLibraryBlockMetadata(usageKey); + + if (!componentMetadata) { + return null; + } + + return ( + + + {[true, 'true'].includes(getConfig().ENABLE_TAGGING_TAXONOMY_PAGES) + && ( + + + {intl.formatMessage(messages.manageTabTagsTitle)} + + )} + className="border-0" + > + Tags placeholder + + )} + + + {intl.formatMessage(messages.manageTabCollectionsTitle)} + + )} + className="border-0" + > + Collections placeholder + + + ); +}; + +export default ComponentManagement; diff --git a/src/library-authoring/component-info/messages.ts b/src/library-authoring/component-info/messages.ts index e945064cee..8a9404081e 100644 --- a/src/library-authoring/component-info/messages.ts +++ b/src/library-authoring/component-info/messages.ts @@ -36,6 +36,16 @@ const messages = defineMessages({ defaultMessage: 'Manage', description: 'Title for manage tab', }, + manageTabTagsTitle: { + id: 'course-authoring.library-authoring.component.manage-tab.tags-title', + defaultMessage: 'Tags', + description: 'Title for the Tags container in the management tab', + }, + manageTabCollectionsTitle: { + id: 'course-authoring.library-authoring.component.manage-tab.collections-title', + defaultMessage: 'Collections', + description: 'Title for the Collections container in the management tab', + }, detailsTabTitle: { id: 'course-authoring.library-authoring.component.details-tab.title', defaultMessage: 'Details', diff --git a/src/library-authoring/components/LibraryComponents.test.tsx b/src/library-authoring/components/LibraryComponents.test.tsx index 695b56f04b..fd45dac12c 100644 --- a/src/library-authoring/components/LibraryComponents.test.tsx +++ b/src/library-authoring/components/LibraryComponents.test.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { AppProvider } from '@edx/frontend-platform/react'; import { initializeMockApp } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; diff --git a/src/library-authoring/data/api.mocks.ts b/src/library-authoring/data/api.mocks.ts index 6b35bac8b5..480e78d661 100644 --- a/src/library-authoring/data/api.mocks.ts +++ b/src/library-authoring/data/api.mocks.ts @@ -126,16 +126,26 @@ mockCreateLibraryBlock.newHtmlData = { blockType: 'html', displayName: 'New Text Component', hasUnpublishedChanges: true, + lastPublished: null, // or e.g. '2024-08-30T16:37:42Z', + publishedBy: null, // or e.g. 'test_author', + lastDraftCreated: '2024-07-22T21:37:49Z', + lastDraftCreatedBy: null, + created: '2024-07-22T21:37:49Z', tagsCount: 0, -} satisfies api.CreateBlockDataResponse; +} satisfies api.LibraryBlockMetadata; mockCreateLibraryBlock.newProblemData = { id: 'lb:Axim:TEST:problem:prob1', defKey: 'prob1', blockType: 'problem', displayName: 'New Problem', hasUnpublishedChanges: true, + lastPublished: null, // or e.g. '2024-08-30T16:37:42Z', + publishedBy: null, // or e.g. 'test_author', + lastDraftCreated: '2024-07-22T21:37:49Z', + lastDraftCreatedBy: null, + created: '2024-07-22T21:37:49Z', tagsCount: 0, -} satisfies api.CreateBlockDataResponse; +} satisfies api.LibraryBlockMetadata; /** Apply this mock. Returns a spy object that can tell you if it's been called. */ mockCreateLibraryBlock.applyMock = () => ( jest.spyOn(api, 'createLibraryBlock').mockImplementation(mockCreateLibraryBlock) @@ -172,3 +182,49 @@ mockXBlockFields.dataNewHtml = { } satisfies api.XBlockFields; /** Apply this mock. Returns a spy object that can tell you if it's been called. */ mockXBlockFields.applyMock = () => jest.spyOn(api, 'getXBlockFields').mockImplementation(mockXBlockFields); + +/** + * Mock for `getLibraryBlockMetadata()` + * + * This mock returns different data/responses depending on the ID of the block + * that you request. Use `mockLibraryBlockMetadata.applyMock()` to apply it to the whole + * test suite. + */ +export async function mockLibraryBlockMetadata(usageKey: string): Promise { + const thisMock = mockLibraryBlockMetadata; + switch (usageKey) { + case thisMock.usageKeyNeverPublished: return thisMock.dataNeverPublished; + case thisMock.usageKeyPublished: return thisMock.dataPublished; + default: throw new Error(`No mock has been set up for usageKey "${usageKey}"`); + } +} +mockLibraryBlockMetadata.usageKeyNeverPublished = 'lb:Axim:TEST1:html:571fe018-f3ce-45c9-8f53-5dafcb422fd1'; +mockLibraryBlockMetadata.dataNeverPublished = { + id: 'lb:Axim:TEST1:html:571fe018-f3ce-45c9-8f53-5dafcb422fd1', + defKey: null, + blockType: 'html', + displayName: 'Introduction to Testing 1', + lastPublished: null, + publishedBy: null, + lastDraftCreated: null, + lastDraftCreatedBy: null, + hasUnpublishedChanges: false, + created: '2024-06-20T13:54:21Z', + tagsCount: 0, +} satisfies api.LibraryBlockMetadata; +mockLibraryBlockMetadata.usageKeyPublished = 'lb:Axim:TEST2:html:571fe018-f3ce-45c9-8f53-5dafcb422fd2'; +mockLibraryBlockMetadata.dataPublished = { + id: 'lb:Axim:TEST2:html:571fe018-f3ce-45c9-8f53-5dafcb422fd2', + defKey: null, + blockType: 'html', + displayName: 'Introduction to Testing 2', + lastPublished: '2024-06-21T00:00:00', + publishedBy: 'Luke', + lastDraftCreated: null, + lastDraftCreatedBy: '2024-06-20T20:00:00Z', + hasUnpublishedChanges: false, + created: '2024-06-20T13:54:21Z', + tagsCount: 0, +} satisfies api.LibraryBlockMetadata; +/** Apply this mock. Returns a spy object that can tell you if it's been called. */ +mockLibraryBlockMetadata.applyMock = () => jest.spyOn(api, 'getLibraryBlockMetadata').mockImplementation(mockLibraryBlockMetadata); diff --git a/src/library-authoring/data/api.ts b/src/library-authoring/data/api.ts index da2d80390e..76388b1641 100644 --- a/src/library-authoring/data/api.ts +++ b/src/library-authoring/data/api.ts @@ -18,6 +18,14 @@ export const getLibraryBlockTypesUrl = (libraryId: string) => `${getApiBaseUrl() */ export const getCreateLibraryBlockUrl = (libraryId: string) => `${getApiBaseUrl()}/api/libraries/v2/${libraryId}/blocks/`; +/** + * Get the URL for library block metadata. + */ +export const getLibraryBlockMetadataUrl = (usageKey: string) => `${getApiBaseUrl()}/api/libraries/v2/blocks/${usageKey}/`; + +/** + * Get the URL for content library list API. + */ export const getContentLibraryV2ListApiUrl = () => `${getApiBaseUrl()}/api/libraries/v2/`; /** @@ -110,12 +118,17 @@ export interface CreateBlockDataRequest { definitionId: string; } -export interface CreateBlockDataResponse { +export interface LibraryBlockMetadata { id: string; blockType: string; defKey: string | null; displayName: string; + lastPublished: string | null; + publishedBy: string | null, + lastDraftCreated: string | null, + lastDraftCreatedBy: string | null, hasUnpublishedChanges: boolean; + created: string | null, tagsCount: number; } @@ -166,7 +179,7 @@ export async function createLibraryBlock({ libraryId, blockType, definitionId, -}: CreateBlockDataRequest): Promise { +}: CreateBlockDataRequest): Promise { const client = getAuthenticatedHttpClient(); const { data } = await client.post( getCreateLibraryBlockUrl(libraryId), @@ -229,7 +242,7 @@ export async function revertLibraryChanges(libraryId: string) { export async function libraryPasteClipboard({ libraryId, blockId, -}: LibraryPasteClipboardRequest): Promise { +}: LibraryPasteClipboardRequest): Promise { const client = getAuthenticatedHttpClient(); const { data } = await client.post( getLibraryPasteClipboardUrl(libraryId), @@ -240,6 +253,14 @@ export async function libraryPasteClipboard({ return data; } +/** + * Fetch library block metadata. + */ +export async function getLibraryBlockMetadata(usageKey: string): Promise { + const { data } = await getAuthenticatedHttpClient().get(getLibraryBlockMetadataUrl(usageKey)); + return camelCaseObject(data); +} + /** * Fetch xblock fields. */ diff --git a/src/library-authoring/data/apiHooks.ts b/src/library-authoring/data/apiHooks.ts index 656ac21f63..91b07c6a7f 100644 --- a/src/library-authoring/data/apiHooks.ts +++ b/src/library-authoring/data/apiHooks.ts @@ -21,6 +21,7 @@ import { revertLibraryChanges, updateLibraryMetadata, libraryPasteClipboard, + getLibraryBlockMetadata, getXBlockFields, updateXBlockFields, createCollection, @@ -72,6 +73,7 @@ export const xblockQueryKeys = { xblockFields: (usageKey: string) => [...xblockQueryKeys.xblock(usageKey), 'fields'], /** OLX (XML representation of the fields/content) */ xblockOLX: (usageKey: string) => [...xblockQueryKeys.xblock(usageKey), 'OLX'], + componentMetadata: (usageKey: string) => [...xblockQueryKeys.xblock(usageKey), 'componentMetadata'], }; /** @@ -197,6 +199,13 @@ export const useLibraryPasteClipboard = () => { }); }; +export const useLibraryBlockMetadata = (usageId: string) => ( + useQuery({ + queryKey: xblockQueryKeys.componentMetadata(usageId), + queryFn: () => getLibraryBlockMetadata(usageId), + }) +); + export const useXBlockFields = (usageKey: string) => ( useQuery({ queryKey: xblockQueryKeys.xblockFields(usageKey), diff --git a/src/library-authoring/generic/index.scss b/src/library-authoring/generic/index.scss new file mode 100644 index 0000000000..8e15b4671b --- /dev/null +++ b/src/library-authoring/generic/index.scss @@ -0,0 +1 @@ +@import "./status-widget/StatusWidget"; diff --git a/src/library-authoring/library-info/LibraryPublishStatus.scss b/src/library-authoring/generic/status-widget/StatusWidget.scss similarity index 87% rename from src/library-authoring/library-info/LibraryPublishStatus.scss rename to src/library-authoring/generic/status-widget/StatusWidget.scss index 9b920eea90..cdb1c4cf61 100644 --- a/src/library-authoring/library-info/LibraryPublishStatus.scss +++ b/src/library-authoring/generic/status-widget/StatusWidget.scss @@ -1,4 +1,4 @@ -.library-publish-status { +.status-widget { &.draft-status { background-color: #FDF3E9; border-top: 4px solid #F4B57B; @@ -9,3 +9,4 @@ border-top: 4px solid $info-400; } } + diff --git a/src/library-authoring/generic/status-widget/index.tsx b/src/library-authoring/generic/status-widget/index.tsx new file mode 100644 index 0000000000..19fdc71444 --- /dev/null +++ b/src/library-authoring/generic/status-widget/index.tsx @@ -0,0 +1,207 @@ +import { + FormattedDate, + FormattedMessage, + FormattedTime, + useIntl, +} from '@edx/frontend-platform/i18n'; +import { Button, Container, Stack } from '@openedx/paragon'; +import classNames from 'classnames'; + +import messages from './messages'; + +const CustomFormattedDate = ({ date }: { date: string }) => ( + + + +); + +const CustomFormattedTime = ({ date }: { date: string }) => ( + + + +); + +type DraftBodyMessageProps = { + lastDraftCreatedBy: string | null; + lastDraftCreated: string | null; + created: string | null; +}; +const DraftBodyMessage = ({ lastDraftCreatedBy, lastDraftCreated, created }: DraftBodyMessageProps) => { + if (lastDraftCreatedBy && lastDraftCreated) { + return ( + , + time: , + user: {lastDraftCreatedBy}, + }} + /> + ); + } + + if (lastDraftCreated) { + return ( + , + time: , + }} + /> + ); + } + + if (created) { + return ( + , + time: , + }} + /> + ); + } + + return null; +}; + +type StatusWidgedProps = { + lastPublished: string | null; + hasUnpublishedChanges: boolean; + hasUnpublishedDeletes?: boolean; + lastDraftCreatedBy: string | null; + lastDraftCreated: string | null; + created: string | null; + publishedBy: string | null; + numBlocks?: number; + onCommit?: () => void; + onRevert?: () => void; +}; + +/** + * This component displays the status of an entity (published, draft, etc.) and allows the user to publish + * or discard changes. + * + * This component doesn't handle fetching the data or any other side effects. It only displays the status + * and provides the buttons to commit or revert changes. + * + * @example + * ```tsx + * const { data: libraryData } = useContentLibrary(libraryId); + * const onCommit = () => { commitChanges(libraryId); }; + * const onRevert = () => { revertChanges(libraryId); }; + * + * return ; + * ``` + */ +const StatusWidget = ({ + lastPublished, + hasUnpublishedChanges, + hasUnpublishedDeletes, + lastDraftCreatedBy, + lastDraftCreated, + created, + publishedBy, + numBlocks, + onCommit, + onRevert, +}: StatusWidgedProps) => { + const intl = useIntl(); + + let isNew: boolean | undefined; + let isPublished: boolean; + let statusMessage: string; + let extraStatusMessage: string | undefined; + let bodyMessage: React.ReactNode | undefined; + + if (!lastPublished) { + // Entity is never published (new) + isNew = numBlocks != null && numBlocks === 0; // allow discarding if components are added + isPublished = false; + statusMessage = intl.formatMessage(messages.draftStatusLabel); + extraStatusMessage = intl.formatMessage(messages.neverPublishedLabel); + bodyMessage = (); + } else if (hasUnpublishedChanges || hasUnpublishedDeletes) { + // Entity is on Draft state + isPublished = false; + statusMessage = intl.formatMessage(messages.draftStatusLabel); + extraStatusMessage = intl.formatMessage(messages.unpublishedStatusLabel); + bodyMessage = (); + } else { + // Entity is published + isPublished = true; + statusMessage = intl.formatMessage(messages.publishedStatusLabel); + if (publishedBy) { + bodyMessage = ( + , + time: , + user: {publishedBy}, + }} + /> + ); + } else { + bodyMessage = ( + , + time: , + }} + /> + ); + } + } + + return ( + + + + {statusMessage} + + { extraStatusMessage && ( + + {extraStatusMessage} + + )} + + + + + {bodyMessage} + + {onCommit && ( + + )} + {onRevert && ( +
+ +
+ )} +
+
+
+ ); +}; + +export default StatusWidget; diff --git a/src/library-authoring/generic/status-widget/messages.ts b/src/library-authoring/generic/status-widget/messages.ts new file mode 100644 index 0000000000..5ac3b60745 --- /dev/null +++ b/src/library-authoring/generic/status-widget/messages.ts @@ -0,0 +1,56 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + draftStatusLabel: { + id: 'course-authoring.library-authoring.generic.status-widget.draft', + defaultMessage: 'Draft', + description: 'Label in library authoring sidebar when the entity is on draft status', + }, + neverPublishedLabel: { + id: 'course-authoring.library-authoring.generic.status-widget.never', + defaultMessage: '(Never Published)', + description: 'Label in library authoring sidebar when the entity is never published', + }, + unpublishedStatusLabel: { + id: 'course-authoring.library-authoring.generic.status-widget.unpublished', + defaultMessage: '(Unpublished Changes)', + description: 'Label in library authoring sidebar when the entity has unpublished changes', + }, + publishedStatusLabel: { + id: 'course-authoring.library-authoring.generic.status-widget.published', + defaultMessage: 'Published', + description: 'Label in library authoring sidebar when the entity is on published status', + }, + lastPublishedMsg: { + id: 'course-authoring.library-authoring.generic.status-widget.last-published', + defaultMessage: 'Last published on {date} at {time} UTC by {user}.', + description: 'Body message of the library authoring sidebar when the entity is published.', + }, + lastPublishedMsgWithoutUser: { + id: 'course-authoring.library-authoring.generic.status-widget.last-published-no-user', + defaultMessage: 'Last published on {date} at {time} UTC.', + description: 'Body message of the library authoring sidebar when the entity is published.', + }, + lastDraftMsg: { + id: 'course-authoring.library-authoring.generic.status-widget.last-draft', + defaultMessage: 'Draft saved on {date} at {time} UTC by {user}.', + description: 'Body message of the library authoring sidebar when the entity is on draft status.', + }, + lastDraftMsgWithoutUser: { + id: 'course-authoring.library-authoring.generic.status-widget.last-draft-no-user', + defaultMessage: 'Draft saved on {date} at {time} UTC.', + description: 'Body message of the library authoring sidebar when the entity is on draft status.', + }, + publishButtonLabel: { + id: 'course-authoring.library-authoring.generic.status-widget.publish-button', + defaultMessage: 'Publish', + description: 'Label of publish button for an entity.', + }, + discardChangesButtonLabel: { + id: 'course-authoring.library-authoring.generic.status-widget.discard-button', + defaultMessage: 'Discard Changes', + description: 'Label of discard changes button for an entity.', + }, +}); + +export default messages; diff --git a/src/library-authoring/index.scss b/src/library-authoring/index.scss index a82de4942f..7bfb8cae9f 100644 --- a/src/library-authoring/index.scss +++ b/src/library-authoring/index.scss @@ -1,4 +1,4 @@ -@import "library-authoring/component-info/ComponentPreview"; -@import "library-authoring/components/ComponentCard"; -@import "library-authoring/library-info/LibraryPublishStatus"; -@import "library-authoring/LibraryAuthoringPage"; +@import "./component-info/ComponentPreview"; +@import "./components/ComponentCard"; +@import "./generic"; +@import "./LibraryAuthoringPage"; \ No newline at end of file diff --git a/src/library-authoring/library-info/LibraryPublishStatus.tsx b/src/library-authoring/library-info/LibraryPublishStatus.tsx index e0602e4724..0765b0e04f 100644 --- a/src/library-authoring/library-info/LibraryPublishStatus.tsx +++ b/src/library-authoring/library-info/LibraryPublishStatus.tsx @@ -1,10 +1,10 @@ -import React, { useCallback, useContext, useMemo } from 'react'; -import classNames from 'classnames'; -import { Button, Container, Stack } from '@openedx/paragon'; -import { FormattedDate, FormattedTime, useIntl } from '@edx/frontend-platform/i18n'; +import React, { useCallback, useContext } from 'react'; +import { useIntl } from '@edx/frontend-platform/i18n'; + +import { ToastContext } from '../../generic/toast-context'; +import StatusWidget from '../generic/status-widget'; import { useCommitLibraryChanges, useRevertLibraryChanges } from '../data/apiHooks'; import { ContentLibrary } from '../data/api'; -import { ToastContext } from '../../generic/toast-context'; import messages from './messages'; type LibraryPublishStatusProps = { @@ -35,133 +35,12 @@ const LibraryPublishStatus = ({ library } : LibraryPublishStatusProps) => { }); }, []); - const { - isPublished, - isNew, - statusMessage, - extraStatusMessage, - bodyMessage, - } = useMemo(() => { - let isPublishedResult: boolean; - let isNewResult = false; - let statusMessageResult : string; - let extraStatusMessageResult : string | undefined; - let bodyMessageResult : React.ReactNode | undefined; - - const buildDate = ((date : string) => ( - - - - )); - - const buildTime = ((date: string) => ( - - - - )); - - const buildDraftBodyMessage = (() => { - if (library.lastDraftCreatedBy && library.lastDraftCreated) { - return intl.formatMessage(messages.lastDraftMsg, { - date: buildDate(library.lastDraftCreated), - time: buildTime(library.lastDraftCreated), - user: {library.lastDraftCreatedBy}, - }); - } - if (library.lastDraftCreated) { - return intl.formatMessage(messages.lastDraftMsgWithoutUser, { - date: buildDate(library.lastDraftCreated), - time: buildTime(library.lastDraftCreated), - }); - } - if (library.created) { - return intl.formatMessage(messages.lastDraftMsgWithoutUser, { - date: buildDate(library.created), - time: buildTime(library.created), - }); - } - return ''; - }); - - if (!library.lastPublished) { - // Library is never published (new) - isNewResult = library.numBlocks === 0; // allow discarding if components are added - isPublishedResult = false; - statusMessageResult = intl.formatMessage(messages.draftStatusLabel); - extraStatusMessageResult = intl.formatMessage(messages.neverPublishedLabel); - bodyMessageResult = buildDraftBodyMessage(); - } else if (library.hasUnpublishedChanges || library.hasUnpublishedDeletes) { - // Library is on Draft state - isPublishedResult = false; - statusMessageResult = intl.formatMessage(messages.draftStatusLabel); - extraStatusMessageResult = intl.formatMessage(messages.unpublishedStatusLabel); - bodyMessageResult = buildDraftBodyMessage(); - } else { - // Library is published - isPublishedResult = true; - statusMessageResult = intl.formatMessage(messages.publishedStatusLabel); - if (library.publishedBy) { - bodyMessageResult = intl.formatMessage(messages.lastPublishedMsg, { - date: buildDate(library.lastPublished), - time: buildTime(library.lastPublished), - user: {library.publishedBy}, - }); - } else { - bodyMessageResult = intl.formatMessage(messages.lastPublishedMsgWithoutUser, { - date: buildDate(library.lastPublished), - time: buildTime(library.lastPublished), - }); - } - } - return { - isPublished: isPublishedResult, - isNew: isNewResult, - statusMessage: statusMessageResult, - extraStatusMessage: extraStatusMessageResult, - bodyMessage: bodyMessageResult, - }; - }, [library]); - return ( - - - - {statusMessage} - - { extraStatusMessage && ( - - {extraStatusMessage} - - )} - - - - - {bodyMessage} - - -
- -
-
-
-
+ ); }; diff --git a/src/library-authoring/library-info/messages.ts b/src/library-authoring/library-info/messages.ts index 6b6b6dbac6..1be61a8ebd 100644 --- a/src/library-authoring/library-info/messages.ts +++ b/src/library-authoring/library-info/messages.ts @@ -26,56 +26,6 @@ const messages = defineMessages({ defaultMessage: 'Created', description: 'Created label used in Library History section.', }, - draftStatusLabel: { - id: 'course-authoring.library-authoring.sidebar.info.publish-status.draft', - defaultMessage: 'Draft', - description: 'Label in library info sidebar when the library is on draft status', - }, - neverPublishedLabel: { - id: 'course-authoring.library-authoring.sidebar.info.publish-status.never', - defaultMessage: '(Never Published)', - description: 'Label in library info sidebar when the library is never published', - }, - unpublishedStatusLabel: { - id: 'course-authoring.library-authoring.sidebar.info.publish-status.unpublished', - defaultMessage: '(Unpublished Changes)', - description: 'Label in library info sidebar when the library has unpublished changes', - }, - publishedStatusLabel: { - id: 'course-authoring.library-authoring.sidebar.info.publish-status.published', - defaultMessage: 'Published', - description: 'Label in library info sidebar when the library is on published status', - }, - publishButtonLabel: { - id: 'course-authoring.library-authoring.sidebar.info.publish-status.publish-button', - defaultMessage: 'Publish', - description: 'Label of publish button for a library.', - }, - discardChangesButtonLabel: { - id: 'course-authoring.library-authoring.sidebar.info.publish-status.discard-button', - defaultMessage: 'Discard Changes', - description: 'Label of discard changes button for a library.', - }, - lastPublishedMsg: { - id: 'course-authoring.library-authoring.sidebar.info.publish-status.last-published', - defaultMessage: 'Last published on {date} at {time} UTC by {user}.', - description: 'Body meesage of the library info sidebar when library is published.', - }, - lastPublishedMsgWithoutUser: { - id: 'course-authoring.library-authoring.sidebar.info.publish-status.last-published-no-user', - defaultMessage: 'Last published on {date} at {time} UTC.', - description: 'Body meesage of the library info sidebar when library is published.', - }, - lastDraftMsg: { - id: 'course-authoring.library-authoring.sidebar.info.publish-status.last-draft', - defaultMessage: 'Draft saved on {date} at {time} UTC by {user}.', - description: 'Body meesage of the library info sidebar when library is on draft status.', - }, - lastDraftMsgWithoutUser: { - id: 'course-authoring.library-authoring.sidebar.info.publish-status.last-draft-no-user', - defaultMessage: 'Draft saved on {date} at {time} UTC.', - description: 'Body meesage of the library info sidebar when library is on draft status.', - }, publishSuccessMsg: { id: 'course-authoring.library-authoring.publish.success', defaultMessage: 'Library published successfully', diff --git a/src/library-authoring/library-sidebar/LibrarySidebar.tsx b/src/library-authoring/library-sidebar/LibrarySidebar.tsx index 30b3c43abc..6ced6ba50e 100644 --- a/src/library-authoring/library-sidebar/LibrarySidebar.tsx +++ b/src/library-authoring/library-sidebar/LibrarySidebar.tsx @@ -21,10 +21,10 @@ type LibrarySidebarProps = { * Sidebar container for library pages. * * It's designed to "squash" the page when open. - * Uses `sidebarBodyComponent` of the `store` to + * Uses `sidebarBodyComponent` of the `context` to * choose which component is rendered. * You can add more components in `bodyComponentMap`. - * Use the slice actions to open and close this sidebar. + * Use the returned actions to open and close this sidebar. */ const LibrarySidebar = ({ library }: LibrarySidebarProps) => { const intl = useIntl();