From db81e0f311b6b45317cec991103e01ea5ca60e26 Mon Sep 17 00:00:00 2001 From: Yuki Takei Date: Thu, 12 Oct 2023 01:07:11 +0000 Subject: [PATCH 01/15] add CourceView and toggler --- .../CourceDashboard/CourceDashboard.tsx | 0 .../components/CourceDashboard/index.ts | 1 + .../components/CourceView/CourceView.tsx | 40 +++++++++++++++++++ .../lms/client/components/CourceView/index.ts | 1 + apps/app/src/pages/_lms/[namespace].page.tsx | 21 ++-------- 5 files changed, 46 insertions(+), 17 deletions(-) create mode 100644 apps/app/src/features/lms/client/components/CourceDashboard/CourceDashboard.tsx create mode 100644 apps/app/src/features/lms/client/components/CourceDashboard/index.ts create mode 100644 apps/app/src/features/lms/client/components/CourceView/CourceView.tsx create mode 100644 apps/app/src/features/lms/client/components/CourceView/index.ts diff --git a/apps/app/src/features/lms/client/components/CourceDashboard/CourceDashboard.tsx b/apps/app/src/features/lms/client/components/CourceDashboard/CourceDashboard.tsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/app/src/features/lms/client/components/CourceDashboard/index.ts b/apps/app/src/features/lms/client/components/CourceDashboard/index.ts new file mode 100644 index 0000000000..cda5e005a8 --- /dev/null +++ b/apps/app/src/features/lms/client/components/CourceDashboard/index.ts @@ -0,0 +1 @@ +export * from './CourceDashboard'; diff --git a/apps/app/src/features/lms/client/components/CourceView/CourceView.tsx b/apps/app/src/features/lms/client/components/CourceView/CourceView.tsx new file mode 100644 index 0000000000..b97a41c4a2 --- /dev/null +++ b/apps/app/src/features/lms/client/components/CourceView/CourceView.tsx @@ -0,0 +1,40 @@ +import { useState } from 'react'; + +import { useCurrentPathname } from '~/stores/context'; + +import { CourceUnitList } from '../CourceUnitList'; + +export const CourceView = (): JSX.Element => { + + const { data: currentPagePath } = useCurrentPathname(); + + const [isDashboardMode, setDashboardMode] = useState(false); + + if (currentPagePath == null) { + return <>; + } + + const Toggler = () => ( +
+ + +
+ ); + + return ( + <> +
+ +
+
+ +
+ + ); +}; diff --git a/apps/app/src/features/lms/client/components/CourceView/index.ts b/apps/app/src/features/lms/client/components/CourceView/index.ts new file mode 100644 index 0000000000..c229e40eb4 --- /dev/null +++ b/apps/app/src/features/lms/client/components/CourceView/index.ts @@ -0,0 +1 @@ +export * from './CourceView'; diff --git a/apps/app/src/pages/_lms/[namespace].page.tsx b/apps/app/src/pages/_lms/[namespace].page.tsx index 8b56d0d294..b5224624af 100644 --- a/apps/app/src/pages/_lms/[namespace].page.tsx +++ b/apps/app/src/pages/_lms/[namespace].page.tsx @@ -7,6 +7,7 @@ import dynamic from 'next/dynamic'; import Head from 'next/head'; import { GrowiSubNavigation } from '~/components/Navbar/GrowiSubNavigation'; +import { CourceView } from '~/features/lms/client/components/CourceView'; import type { CrowiRequest } from '~/interfaces/crowi-request'; import { useCurrentPageId } from '~/stores/page'; import { useDrawerMode } from '~/stores/ui'; @@ -46,7 +47,7 @@ const CourcePage: NextPageWithLayout = (props: Props) => { useIsSearchPage(false); useCurrentPageId(null); - useCurrentPathname('/_lms'); + useCurrentPathname(props.currentPathname); // init sidebar config with UserUISettings and sidebarConfig useInitSidebarConfig(props.sidebarConfig, props.userUISettings); @@ -75,24 +76,10 @@ const CourcePage: NextPageWithLayout = (props: Props) => {
-
+

{props.courceTitle}

-
-
- - -
-
-
-
-
+
From 24cc4a507d9d2af5fca217e9cf0b928ba15ed30c Mon Sep 17 00:00:00 2001 From: Yuki Takei Date: Thu, 12 Oct 2023 01:10:59 +0000 Subject: [PATCH 02/15] add TabContent --- .../lms/client/components/CourceView/CourceView.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/app/src/features/lms/client/components/CourceView/CourceView.tsx b/apps/app/src/features/lms/client/components/CourceView/CourceView.tsx index b97a41c4a2..c64f95a91d 100644 --- a/apps/app/src/features/lms/client/components/CourceView/CourceView.tsx +++ b/apps/app/src/features/lms/client/components/CourceView/CourceView.tsx @@ -1,5 +1,7 @@ import { useState } from 'react'; +import { TabContent, TabPane } from 'reactstrap'; + import { useCurrentPathname } from '~/stores/context'; import { CourceUnitList } from '../CourceUnitList'; @@ -33,7 +35,14 @@ export const CourceView = (): JSX.Element => {
- + + + Dashboard + + + + +
); From 798c7e53ad8990c993c71e880a7b6e7cf68cf1c4 Mon Sep 17 00:00:00 2001 From: Yuki Takei Date: Thu, 12 Oct 2023 01:20:37 +0000 Subject: [PATCH 03/15] reorganize components --- .../components/CourceDashboard/CourceDashboard.tsx | 0 .../CourceView/CourceDashboard/CourceDashboard.tsx | 3 +++ .../{ => CourceView}/CourceDashboard/index.ts | 0 .../{ => CourceView}/CourceUnitList/CourceUnitList.tsx | 0 .../{ => CourceView}/CourceUnitList/CourceUnitRow.tsx | 0 .../{ => CourceView}/CourceUnitList/index.ts | 0 .../lms/client/components/CourceView/CourceView.tsx | 10 ++++++++-- apps/app/src/features/lms/client/components/index.ts | 2 ++ apps/app/src/pages/_lms/[namespace].page.tsx | 3 +-- apps/app/src/pages/_lms/index.page.tsx | 2 +- 10 files changed, 15 insertions(+), 5 deletions(-) delete mode 100644 apps/app/src/features/lms/client/components/CourceDashboard/CourceDashboard.tsx create mode 100644 apps/app/src/features/lms/client/components/CourceView/CourceDashboard/CourceDashboard.tsx rename apps/app/src/features/lms/client/components/{ => CourceView}/CourceDashboard/index.ts (100%) rename apps/app/src/features/lms/client/components/{ => CourceView}/CourceUnitList/CourceUnitList.tsx (100%) rename apps/app/src/features/lms/client/components/{ => CourceView}/CourceUnitList/CourceUnitRow.tsx (100%) rename apps/app/src/features/lms/client/components/{ => CourceView}/CourceUnitList/index.ts (100%) create mode 100644 apps/app/src/features/lms/client/components/index.ts diff --git a/apps/app/src/features/lms/client/components/CourceDashboard/CourceDashboard.tsx b/apps/app/src/features/lms/client/components/CourceDashboard/CourceDashboard.tsx deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/apps/app/src/features/lms/client/components/CourceView/CourceDashboard/CourceDashboard.tsx b/apps/app/src/features/lms/client/components/CourceView/CourceDashboard/CourceDashboard.tsx new file mode 100644 index 0000000000..b2f1e984b4 --- /dev/null +++ b/apps/app/src/features/lms/client/components/CourceView/CourceDashboard/CourceDashboard.tsx @@ -0,0 +1,3 @@ +export const CourceDashboard = (): JSX.Element => { + return <>(TBD) Dashboard; +}; diff --git a/apps/app/src/features/lms/client/components/CourceDashboard/index.ts b/apps/app/src/features/lms/client/components/CourceView/CourceDashboard/index.ts similarity index 100% rename from apps/app/src/features/lms/client/components/CourceDashboard/index.ts rename to apps/app/src/features/lms/client/components/CourceView/CourceDashboard/index.ts diff --git a/apps/app/src/features/lms/client/components/CourceUnitList/CourceUnitList.tsx b/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitList.tsx similarity index 100% rename from apps/app/src/features/lms/client/components/CourceUnitList/CourceUnitList.tsx rename to apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitList.tsx diff --git a/apps/app/src/features/lms/client/components/CourceUnitList/CourceUnitRow.tsx b/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitRow.tsx similarity index 100% rename from apps/app/src/features/lms/client/components/CourceUnitList/CourceUnitRow.tsx rename to apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitRow.tsx diff --git a/apps/app/src/features/lms/client/components/CourceUnitList/index.ts b/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/index.ts similarity index 100% rename from apps/app/src/features/lms/client/components/CourceUnitList/index.ts rename to apps/app/src/features/lms/client/components/CourceView/CourceUnitList/index.ts diff --git a/apps/app/src/features/lms/client/components/CourceView/CourceView.tsx b/apps/app/src/features/lms/client/components/CourceView/CourceView.tsx index c64f95a91d..87027a1f15 100644 --- a/apps/app/src/features/lms/client/components/CourceView/CourceView.tsx +++ b/apps/app/src/features/lms/client/components/CourceView/CourceView.tsx @@ -1,10 +1,16 @@ import { useState } from 'react'; +import dynamic from 'next/dynamic'; import { TabContent, TabPane } from 'reactstrap'; import { useCurrentPathname } from '~/stores/context'; -import { CourceUnitList } from '../CourceUnitList'; +import { CourceUnitList } from './CourceUnitList'; + + +const CourceDashboard = dynamic(() => import('./CourceDashboard').then(mod => mod.CourceDashboard), { + ssr: false, +}); export const CourceView = (): JSX.Element => { @@ -37,7 +43,7 @@ export const CourceView = (): JSX.Element => {
- Dashboard + diff --git a/apps/app/src/features/lms/client/components/index.ts b/apps/app/src/features/lms/client/components/index.ts new file mode 100644 index 0000000000..afffd69c26 --- /dev/null +++ b/apps/app/src/features/lms/client/components/index.ts @@ -0,0 +1,2 @@ +export * from './CourceList'; +export * from './CourceView'; diff --git a/apps/app/src/pages/_lms/[namespace].page.tsx b/apps/app/src/pages/_lms/[namespace].page.tsx index b5224624af..f1a0df5a37 100644 --- a/apps/app/src/pages/_lms/[namespace].page.tsx +++ b/apps/app/src/pages/_lms/[namespace].page.tsx @@ -7,7 +7,6 @@ import dynamic from 'next/dynamic'; import Head from 'next/head'; import { GrowiSubNavigation } from '~/components/Navbar/GrowiSubNavigation'; -import { CourceView } from '~/features/lms/client/components/CourceView'; import type { CrowiRequest } from '~/interfaces/crowi-request'; import { useCurrentPageId } from '~/stores/page'; import { useDrawerMode } from '~/stores/ui'; @@ -24,7 +23,7 @@ import { getServerSideCommonProps, getNextI18NextConfig, generateCustomTitleForPage, useInitSidebarConfig, } from '../utils/commons'; -const CourceUnitList = dynamic(() => import('~/features/lms/client/components/CourceUnitList').then(mod => mod.CourceUnitList), { ssr: false }); +const CourceView = dynamic(() => import('~/features/lms/client/components').then(mod => mod.CourceView), { ssr: false }); type Props = CommonProps & { currentUser: IUser, diff --git a/apps/app/src/pages/_lms/index.page.tsx b/apps/app/src/pages/_lms/index.page.tsx index 85d46e034f..3b24fe5e2a 100644 --- a/apps/app/src/pages/_lms/index.page.tsx +++ b/apps/app/src/pages/_lms/index.page.tsx @@ -23,7 +23,7 @@ import { getServerSideCommonProps, getNextI18NextConfig, generateCustomTitleForPage, useInitSidebarConfig, } from '../utils/commons'; -const CourceList = dynamic(() => import('~/features/lms/client/components/CourceList').then(mod => mod.CourceList), { ssr: false }); +const CourceList = dynamic(() => import('~/features/lms/client/components').then(mod => mod.CourceList), { ssr: false }); type Props = CommonProps & { currentUser: IUser, From 6469b935dbd04b1e20fd8bddc610f6647edd1f2c Mon Sep 17 00:00:00 2001 From: Yuki Takei Date: Thu, 12 Oct 2023 02:30:19 +0000 Subject: [PATCH 04/15] clean code --- .../components/CourceView/CourceUnitList/CourceUnitList.tsx | 2 +- apps/app/src/pages/_lms/[namespace].page.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitList.tsx b/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitList.tsx index 19adb18ef5..2ab43740e1 100644 --- a/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitList.tsx +++ b/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitList.tsx @@ -39,7 +39,7 @@ const CourceUnitListSubstance = (props: SubstanceProps): JSX.Element => { if (pagingResult == null) { return ( -
+
diff --git a/apps/app/src/pages/_lms/[namespace].page.tsx b/apps/app/src/pages/_lms/[namespace].page.tsx index f1a0df5a37..a42c62e79e 100644 --- a/apps/app/src/pages/_lms/[namespace].page.tsx +++ b/apps/app/src/pages/_lms/[namespace].page.tsx @@ -75,7 +75,7 @@ const CourcePage: NextPageWithLayout = (props: Props) => {
-
+

{props.courceTitle}

From 9967be69d3c6520644d668a3a1fe16e8ff7a446f Mon Sep 17 00:00:00 2001 From: Yuki Takei Date: Thu, 12 Oct 2023 02:30:26 +0000 Subject: [PATCH 05/15] add play button --- .../components/CourceView/CourceUnitList/CourceUnitRow.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitRow.tsx b/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitRow.tsx index 55b310ebb7..cede3d9e5c 100644 --- a/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitRow.tsx +++ b/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitRow.tsx @@ -7,6 +7,7 @@ import LinkedPagePath from '~/models/linked-page-path'; export const CourceUnitHead = (): JSX.Element => { return ( + Article name Author Published at @@ -27,6 +28,11 @@ export const CourceUnitRow = (props: Props): JSX.Element => { return ( + + + From 1ceaf447876be0995080399e2a1b7166bc2e7640 Mon Sep 17 00:00:00 2001 From: Yuki Takei Date: Thu, 12 Oct 2023 02:34:48 +0000 Subject: [PATCH 06/15] set align-middle --- .../components/CourceView/CourceUnitList/CourceUnitRow.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitRow.tsx b/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitRow.tsx index cede3d9e5c..aef0cecd79 100644 --- a/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitRow.tsx +++ b/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitRow.tsx @@ -33,11 +33,11 @@ export const CourceUnitRow = (props: Props): JSX.Element => { play_arrow - + - author - (TBD) + author + (TBD) ); }; From e2b16af63ddd79beddf1a9f0a84519328555ee7e Mon Sep 17 00:00:00 2001 From: Yuki Takei Date: Thu, 12 Oct 2023 11:14:02 +0000 Subject: [PATCH 07/15] refactor PagePresentationModal --- .../app/src/components/Layout/BasicLayout.tsx | 2 +- .../src/components/PagePresentationModal.tsx | 23 +++++++++++-------- apps/app/src/stores/modal.tsx | 13 ++++++++--- apps/app/src/stores/page.tsx | 6 ++--- 4 files changed, 27 insertions(+), 17 deletions(-) diff --git a/apps/app/src/components/Layout/BasicLayout.tsx b/apps/app/src/components/Layout/BasicLayout.tsx index f879814de5..47a8c611b9 100644 --- a/apps/app/src/components/Layout/BasicLayout.tsx +++ b/apps/app/src/components/Layout/BasicLayout.tsx @@ -22,7 +22,7 @@ const PageCreateModal = dynamic(() => import('../PageCreateModal'), { ssr: false const PageDuplicateModal = dynamic(() => import('../PageDuplicateModal'), { ssr: false }); const PageDeleteModal = dynamic(() => import('../PageDeleteModal'), { ssr: false }); const PageRenameModal = dynamic(() => import('../PageRenameModal'), { ssr: false }); -const PagePresentationModal = dynamic(() => import('../PagePresentationModal'), { ssr: false }); +const PagePresentationModal = dynamic(() => import('../PagePresentationModal').then(mod => mod.PagePresentationModal), { ssr: false }); const PageAccessoriesModal = dynamic(() => import('../PageAccessoriesModal').then(mod => mod.PageAccessoriesModal), { ssr: false }); const DeleteBookmarkFolderModal = dynamic(() => import('../DeleteBookmarkFolderModal').then(mod => mod.DeleteBookmarkFolderModal), { ssr: false }); // Fab diff --git a/apps/app/src/components/PagePresentationModal.tsx b/apps/app/src/components/PagePresentationModal.tsx index 282b615ce2..23795faefc 100644 --- a/apps/app/src/components/PagePresentationModal.tsx +++ b/apps/app/src/components/PagePresentationModal.tsx @@ -10,7 +10,7 @@ import { import { useIsEnabledMarp } from '~/stores/context'; import { usePagePresentationModal } from '~/stores/modal'; -import { useSWRxCurrentPage } from '~/stores/page'; +import { useSWRxCurrentPage, useSWRxPageRevision } from '~/stores/page'; import { usePresentationViewOptions } from '~/stores/renderer'; import { useNextThemes } from '~/stores/use-next-themes'; @@ -26,14 +26,21 @@ const Presentation = dynamic(() => import('./Presentation/Pre }); -const PagePresentationModal = (): JSX.Element => { +export const PagePresentationModal = (): JSX.Element => { + const { data: currentPage } = useSWRxCurrentPage(); const { data: presentationModalData, close: closePresentationModal } = usePagePresentationModal(); + const { isOpened = false, page: specifiedPage } = presentationModalData ?? {}; + + const { data: specifiedRevision } = useSWRxPageRevision( + isOpened ? specifiedPage?.pageId : undefined, + isOpened ? specifiedPage?.revisionId : undefined, + ); + const { isDarkMode } = useNextThemes(); const fullscreen = useFullScreen(); - const { data: currentPage } = useSWRxCurrentPage(); const { data: rendererOptions } = usePresentationViewOptions(); const { data: isEnabledMarp } = useIsEnabledMarp(); @@ -54,17 +61,15 @@ const PagePresentationModal = (): JSX.Element => { closePresentationModal(); }, [fullscreen, closePresentationModal]); - const isOpen = presentationModalData?.isOpened ?? false; - - if (!isOpen) { + if (!isOpened) { return <>; } - const markdown = currentPage?.revision.body; + const markdown = (specifiedRevision ?? currentPage?.revision)?.body; return ( { ); }; - -export default PagePresentationModal; diff --git a/apps/app/src/stores/modal.tsx b/apps/app/src/stores/modal.tsx index 3422319a0f..032d7958b3 100644 --- a/apps/app/src/stores/modal.tsx +++ b/apps/app/src/stores/modal.tsx @@ -251,10 +251,17 @@ export const usePutBackPageModal = (status?: PutBackPageModalStatus): SWRRespons */ type PresentationModalStatus = { isOpened: boolean, + page?: { + pageId: string, + revisionId: string, + }, } type PresentationModalStatusUtils = { - open(): Promise + open(page?: { + pageId: string, + revisionId: string, + }): Promise close(): Promise } @@ -268,8 +275,8 @@ export const usePagePresentationModal = ( return { ...swrResponse, - open: () => swrResponse.mutate({ isOpened: true }, { revalidate: true }), - close: () => swrResponse.mutate({ isOpened: false }), + open: page => swrResponse.mutate({ isOpened: true, page }, { revalidate: true }), + close: () => swrResponse.mutate({ isOpened: false, page: undefined }), }; }; diff --git a/apps/app/src/stores/page.tsx b/apps/app/src/stores/page.tsx index aa07cf3c1d..01184e7270 100644 --- a/apps/app/src/stores/page.tsx +++ b/apps/app/src/stores/page.tsx @@ -179,11 +179,11 @@ export const useSWRMUTxPageInfo = ( ); }; -export const useSWRxPageRevision = (pageId: string, revisionId: Ref): SWRResponse => { - const key = [`/revisions/${revisionId}`, pageId, revisionId]; +export const useSWRxPageRevision = (pageId: string | undefined, revisionId: Ref | undefined): SWRResponse => { + const key = (pageId == null || revisionId == null) ? null : [`/revisions/${revisionId.toString()}`, pageId]; return useSWRImmutable( key, - () => apiv3Get<{ revision: IRevisionHasId }>(`/revisions/${revisionId}`, { pageId }).then(response => response.data.revision), + ([endpoint, pageId]) => apiv3Get<{ revision: IRevisionHasId }>(endpoint, { pageId }).then(response => response.data.revision), ); }; From 7f5a5f702ebff42f8efcca13bf3fba641abc4f3c Mon Sep 17 00:00:00 2001 From: Yuki Takei Date: Thu, 12 Oct 2023 11:14:31 +0000 Subject: [PATCH 08/15] play cource unit with presentation --- .../CourceView/CourceUnitList/CourceUnitList.tsx | 14 ++++++++++++-- .../CourceView/CourceUnitList/CourceUnitRow.tsx | 5 +++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitList.tsx b/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitList.tsx index 2ab43740e1..af38b4da66 100644 --- a/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitList.tsx +++ b/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitList.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import type { IPageHasId, @@ -6,6 +6,7 @@ import type { import { IPagingResult } from '~/interfaces/paging-result'; import { useIsSharedUser } from '~/stores/context'; +import { usePagePresentationModal } from '~/stores/modal'; import { useSWRxPageList, } from '~/stores/page-listing'; @@ -21,6 +22,15 @@ const CourceUnitListSubstance = (props: SubstanceProps): JSX.Element => { const { pagingResult } = props; + const { open: openPresentationModal } = usePagePresentationModal(); + + const playHandler = useCallback((page: IPageHasId) => { + openPresentationModal({ + pageId: page._id, + revisionId: page.revision.toString(), + }); + }, [openPresentationModal]); + // const pageDeletedHandler: OnDeletedFunction = useCallback((...args) => { // const path = args[0]; // const isCompletely = args[2]; @@ -48,7 +58,7 @@ const CourceUnitListSubstance = (props: SubstanceProps): JSX.Element => { } const rowList = pagingResult.items.map(page => ( - + )); return ( diff --git a/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitRow.tsx b/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitRow.tsx index aef0cecd79..187ca948d3 100644 --- a/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitRow.tsx +++ b/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitRow.tsx @@ -18,10 +18,11 @@ export const CourceUnitHead = (): JSX.Element => { type Props = { page: IPageHasId, + onPlayButtonClicked?: (page: IPageHasId) => void, } export const CourceUnitRow = (props: Props): JSX.Element => { - const { page } = props; + const { page, onPlayButtonClicked } = props; const dPagePath: DevidedPagePath = new DevidedPagePath(page.path, false); const linkedPagePath = new LinkedPagePath(dPagePath.latter); @@ -29,7 +30,7 @@ export const CourceUnitRow = (props: Props): JSX.Element => { return ( - From 8ead9d0e74cc6341ac009b893fc5e1c5fdfd21ab Mon Sep 17 00:00:00 2001 From: Yuki Takei Date: Thu, 12 Oct 2023 11:14:52 +0000 Subject: [PATCH 09/15] improve cources list layout --- apps/app/src/pages/_lms/index.page.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/app/src/pages/_lms/index.page.tsx b/apps/app/src/pages/_lms/index.page.tsx index 3b24fe5e2a..e92a339591 100644 --- a/apps/app/src/pages/_lms/index.page.tsx +++ b/apps/app/src/pages/_lms/index.page.tsx @@ -72,7 +72,12 @@ const LmsPage: NextPageWithLayout = (props: Props) => {
- +
+

All Cources

+
+
+ +
From fe9d4e17c050ba987abf221ac7e813742e3a5316 Mon Sep 17 00:00:00 2001 From: Yuki Takei Date: Thu, 12 Oct 2023 11:37:12 +0000 Subject: [PATCH 10/15] fix warning --- .../components/CourceView/CourceUnitList/CourceUnitList.tsx | 2 +- .../features/lms/client/components/CourceView/CourceView.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitList.tsx b/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitList.tsx index af38b4da66..d63cf3b185 100644 --- a/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitList.tsx +++ b/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitList.tsx @@ -58,7 +58,7 @@ const CourceUnitListSubstance = (props: SubstanceProps): JSX.Element => { } const rowList = pagingResult.items.map(page => ( - + )); return ( diff --git a/apps/app/src/features/lms/client/components/CourceView/CourceView.tsx b/apps/app/src/features/lms/client/components/CourceView/CourceView.tsx index 87027a1f15..7b7f1f70df 100644 --- a/apps/app/src/features/lms/client/components/CourceView/CourceView.tsx +++ b/apps/app/src/features/lms/client/components/CourceView/CourceView.tsx @@ -25,11 +25,11 @@ export const CourceView = (): JSX.Element => { const Toggler = () => (
From c70310e5c909b841b6e8c3b716402c1cccea053c Mon Sep 17 00:00:00 2001 From: Yuki Takei Date: Thu, 12 Oct 2023 11:37:41 +0000 Subject: [PATCH 11/15] inject RendererConfig --- apps/app/src/pages/_lms/[namespace].page.tsx | 24 +++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/apps/app/src/pages/_lms/[namespace].page.tsx b/apps/app/src/pages/_lms/[namespace].page.tsx index a42c62e79e..f14d86ecfb 100644 --- a/apps/app/src/pages/_lms/[namespace].page.tsx +++ b/apps/app/src/pages/_lms/[namespace].page.tsx @@ -8,6 +8,7 @@ import Head from 'next/head'; import { GrowiSubNavigation } from '~/components/Navbar/GrowiSubNavigation'; import type { CrowiRequest } from '~/interfaces/crowi-request'; +import type { RendererConfig } from '~/interfaces/services/renderer'; import { useCurrentPageId } from '~/stores/page'; import { useDrawerMode } from '~/stores/ui'; @@ -15,7 +16,7 @@ import { BasicLayout } from '../../components/Layout/BasicLayout'; import { useCurrentUser, useCurrentPathname, useGrowiCloudUri, useIsSearchServiceConfigured, useIsSearchServiceReachable, - useIsSearchScopeChildrenAsDefault, useIsSearchPage, useShowPageLimitationXL, + useIsSearchScopeChildrenAsDefault, useIsSearchPage, useShowPageLimitationXL, useRendererConfig, } from '../../stores/context'; import type { NextPageWithLayout } from '../_app.page'; import type { CommonProps } from '../utils/commons'; @@ -33,6 +34,8 @@ type Props = CommonProps & { showPageLimitationXL: number, courceTitle: string, + + rendererConfig: RendererConfig, }; const CourcePage: NextPageWithLayout = (props: Props) => { @@ -51,6 +54,8 @@ const CourcePage: NextPageWithLayout = (props: Props) => { // init sidebar config with UserUISettings and sidebarConfig useInitSidebarConfig(props.sidebarConfig, props.userUISettings); + useRendererConfig(props.rendererConfig); + useShowPageLimitationXL(props.showPageLimitationXL); const { data: isDrawerMode } = useDrawerMode(); @@ -143,6 +148,23 @@ function injectServerConfigurations(context: GetServerSidePropsContext, props: P isSidebarClosedAtDockMode: configManager.getConfig('crowi', 'customize:isSidebarClosedAtDockMode'), }; + props.rendererConfig = { + isEnabledLinebreaks: configManager.getConfig('markdown', 'markdown:isEnabledLinebreaks'), + isEnabledLinebreaksInComments: configManager.getConfig('markdown', 'markdown:isEnabledLinebreaksInComments'), + isEnabledMarp: configManager.getConfig('crowi', 'customize:isEnabledMarp'), + adminPreferredIndentSize: configManager.getConfig('markdown', 'markdown:adminPreferredIndentSize'), + isIndentSizeForced: configManager.getConfig('markdown', 'markdown:isIndentSizeForced'), + + drawioUri: configManager.getConfig('crowi', 'app:drawioUri'), + plantumlUri: configManager.getConfig('crowi', 'app:plantumlUri'), + + // XSS Options + isEnabledXssPrevention: configManager.getConfig('markdown', 'markdown:rehypeSanitize:isEnabledPrevention'), + xssOption: configManager.getConfig('markdown', 'markdown:rehypeSanitize:option'), + attrWhitelist: JSON.parse(crowi.configManager.getConfig('markdown', 'markdown:rehypeSanitize:attributes')), + tagWhitelist: crowi.configManager.getConfig('markdown', 'markdown:rehypeSanitize:tagNames'), + highlightJsStyleBorder: crowi.configManager.getConfig('crowi', 'customize:highlightJsStyleBorder'), + }; } /** From cc02fed48f44f1097491baac68d7171fbc173ea5 Mon Sep 17 00:00:00 2001 From: Yuki Takei Date: Thu, 12 Oct 2023 13:12:59 +0000 Subject: [PATCH 12/15] renderer CourceEnd component --- .../src/client/services/renderer/renderer.tsx | 3 + .../src/components/PagePresentationModal.tsx | 7 +- .../CourceEnd/CourceEnd.module.scss | 7 ++ .../client/components/CourceEnd/CourceEnd.tsx | 22 ++++ .../lms/client/components/CourceEnd/index.ts | 1 + .../CourceUnitList/CourceUnitList.tsx | 26 ++++- .../lms/client/services/renderer/renderer.tsx | 103 ++++++++++++++++++ apps/app/src/stores/modal.tsx | 18 +-- 8 files changed, 171 insertions(+), 16 deletions(-) create mode 100644 apps/app/src/features/lms/client/components/CourceEnd/CourceEnd.module.scss create mode 100644 apps/app/src/features/lms/client/components/CourceEnd/CourceEnd.tsx create mode 100644 apps/app/src/features/lms/client/components/CourceEnd/index.ts create mode 100644 apps/app/src/features/lms/client/services/renderer/renderer.tsx diff --git a/apps/app/src/client/services/renderer/renderer.tsx b/apps/app/src/client/services/renderer/renderer.tsx index 4b14867830..aeee1f57ef 100644 --- a/apps/app/src/client/services/renderer/renderer.tsx +++ b/apps/app/src/client/services/renderer/renderer.tsx @@ -202,6 +202,9 @@ export const generateSimpleViewOptions = ( attachment.sanitizeOption, lsxGrowiDirective.sanitizeOption, refsGrowiDirective.sanitizeOption, + { + tagNames: ['cource-end'], + }, )] : () => {}; diff --git a/apps/app/src/components/PagePresentationModal.tsx b/apps/app/src/components/PagePresentationModal.tsx index 23795faefc..b745683e1d 100644 --- a/apps/app/src/components/PagePresentationModal.tsx +++ b/apps/app/src/components/PagePresentationModal.tsx @@ -31,7 +31,7 @@ export const PagePresentationModal = (): JSX.Element => { const { data: currentPage } = useSWRxCurrentPage(); const { data: presentationModalData, close: closePresentationModal } = usePagePresentationModal(); - const { isOpened = false, page: specifiedPage } = presentationModalData ?? {}; + const { isOpened = false, page: specifiedPage, rendererOptions: specifiedRendererOptions } = presentationModalData ?? {}; const { data: specifiedRevision } = useSWRxPageRevision( isOpened ? specifiedPage?.pageId : undefined, @@ -41,7 +41,7 @@ export const PagePresentationModal = (): JSX.Element => { const { isDarkMode } = useNextThemes(); const fullscreen = useFullScreen(); - const { data: rendererOptions } = usePresentationViewOptions(); + const { data: presentationViewOptions } = usePresentationViewOptions(); const { data: isEnabledMarp } = useIsEnabledMarp(); @@ -66,6 +66,7 @@ export const PagePresentationModal = (): JSX.Element => { } const markdown = (specifiedRevision ?? currentPage?.revision)?.body; + const rendererOptions = (specifiedRendererOptions ?? presentationViewOptions) as ReactMarkdownOptions; return ( { { rendererOptions != null && isEnabledMarp != null && ( { + + const { close } = usePagePresentationModal(); + + const clickHandler = useCallback(() => { + close(); + }, [close]); + + return ( +
+ +
+ ); +}; diff --git a/apps/app/src/features/lms/client/components/CourceEnd/index.ts b/apps/app/src/features/lms/client/components/CourceEnd/index.ts new file mode 100644 index 0000000000..3b1e916240 --- /dev/null +++ b/apps/app/src/features/lms/client/components/CourceEnd/index.ts @@ -0,0 +1 @@ +export * from './CourceEnd'; diff --git a/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitList.tsx b/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitList.tsx index d63cf3b185..4a55e03044 100644 --- a/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitList.tsx +++ b/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitList.tsx @@ -1,16 +1,19 @@ -import React, { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import type { IPageHasId, } from '@growi/core'; import { IPagingResult } from '~/interfaces/paging-result'; -import { useIsSharedUser } from '~/stores/context'; +import { useIsSharedUser, useRendererConfig } from '~/stores/context'; import { usePagePresentationModal } from '~/stores/modal'; +import { useCurrentPagePath } from '~/stores/page'; import { useSWRxPageList, } from '~/stores/page-listing'; +import { generatePresentationCourceUnitOptions } from '../../../services/renderer/renderer'; + import { CourceUnitHead, CourceUnitRow } from './CourceUnitRow'; @@ -22,14 +25,27 @@ const CourceUnitListSubstance = (props: SubstanceProps): JSX.Element => { const { pagingResult } = props; + const { data: currentPagePath } = useCurrentPagePath(); const { open: openPresentationModal } = usePagePresentationModal(); + const { data: rendererConfig } = useRendererConfig(); + + const rendererOptions = useMemo(() => { + if (rendererConfig == null || currentPagePath == null) { + return null; + } + return generatePresentationCourceUnitOptions(rendererConfig, currentPagePath); + }, [currentPagePath, rendererConfig]); + const playHandler = useCallback((page: IPageHasId) => { openPresentationModal({ - pageId: page._id, - revisionId: page.revision.toString(), + page: { + pageId: page._id, + revisionId: page.revision.toString(), + }, + rendererOptions, }); - }, [openPresentationModal]); + }, [openPresentationModal, rendererOptions]); // const pageDeletedHandler: OnDeletedFunction = useCallback((...args) => { // const path = args[0]; diff --git a/apps/app/src/features/lms/client/services/renderer/renderer.tsx b/apps/app/src/features/lms/client/services/renderer/renderer.tsx new file mode 100644 index 0000000000..8fcf44019a --- /dev/null +++ b/apps/app/src/features/lms/client/services/renderer/renderer.tsx @@ -0,0 +1,103 @@ +import * as refsGrowiDirective from '@growi/remark-attachment-refs/dist/client'; +import * as drawio from '@growi/remark-drawio'; +// eslint-disable-next-line import/extensions +import * as lsxGrowiDirective from '@growi/remark-lsx/dist/client'; +import { Schema as SanitizeOption } from 'hast-util-sanitize'; +import katex from 'rehype-katex'; +import sanitize from 'rehype-sanitize'; +import breaks from 'remark-breaks'; +import math from 'remark-math'; +import deepmerge from 'ts-deepmerge'; +import type { Pluggable } from 'unified'; + +import { LightBox } from '~/components/ReactMarkdownComponents/LightBox'; +import { RichAttachment } from '~/components/ReactMarkdownComponents/RichAttachment'; +import * as mermaid from '~/features/mermaid'; +import { RehypeSanitizeOption } from '~/interfaces/rehype'; +import type { RendererOptions } from '~/interfaces/renderer-options'; +import type { RendererConfig } from '~/interfaces/services/renderer'; +import * as attachment from '~/services/renderer/remark-plugins/attachment'; +import * as plantuml from '~/services/renderer/remark-plugins/plantuml'; +import * as xsvToTable from '~/services/renderer/remark-plugins/xsv-to-table'; +import { + commonSanitizeOption, generateCommonOptions, injectCustomSanitizeOption, verifySanitizePlugin, +} from '~/services/renderer/renderer'; + +import { CourceEnd } from '../../components/CourceEnd'; + + +const sanitizeOptionForCourceEnd: SanitizeOption = { + tagNames: ['cource-end'], +}; + +export const generatePresentationCourceUnitOptions = ( + config: RendererConfig, + pagePath: string, +): RendererOptions => { + const options = generateCommonOptions(pagePath); + + const { remarkPlugins, rehypePlugins, components } = options; + + // add remark plugins + remarkPlugins.push( + math, + [plantuml.remarkPlugin, { plantumlUri: config.plantumlUri }], + drawio.remarkPlugin, + mermaid.remarkPlugin, + xsvToTable.remarkPlugin, + attachment.remarkPlugin, + lsxGrowiDirective.remarkPlugin, + refsGrowiDirective.remarkPlugin, + ); + + const isEnabledLinebreaks = config.isEnabledLinebreaks; + + if (isEnabledLinebreaks) { + remarkPlugins.push(breaks); + } + + if (config.xssOption === RehypeSanitizeOption.CUSTOM) { + injectCustomSanitizeOption(config); + } + + + const rehypeSanitizePlugin: Pluggable | (() => void) = config.isEnabledXssPrevention + ? [sanitize, deepmerge( + commonSanitizeOption, + drawio.sanitizeOption, + mermaid.sanitizeOption, + attachment.sanitizeOption, + lsxGrowiDirective.sanitizeOption, + refsGrowiDirective.sanitizeOption, + sanitizeOptionForCourceEnd, + )] + : () => {}; + + // add rehype plugins + rehypePlugins.push( + [lsxGrowiDirective.rehypePlugin, { pagePath, isSharedPage: config.isSharedPage }], + [refsGrowiDirective.rehypePlugin, { pagePath }], + rehypeSanitizePlugin, + katex, + ); + + // add components + if (components != null) { + components.lsx = lsxGrowiDirective.LsxImmutable; + components.ref = refsGrowiDirective.RefImmutable; + components.refs = refsGrowiDirective.RefsImmutable; + components.refimg = refsGrowiDirective.RefImgImmutable; + components.refsimg = refsGrowiDirective.RefsImgImmutable; + components.gallery = refsGrowiDirective.GalleryImmutable; + components.drawio = drawio.DrawioViewer; + components.mermaid = mermaid.MermaidViewer; + components.attachment = RichAttachment; + components.img = LightBox; + components['cource-end'] = CourceEnd; + } + + if (config.isEnabledXssPrevention) { + verifySanitizePlugin(options, false); + } + return options; +}; diff --git a/apps/app/src/stores/modal.tsx b/apps/app/src/stores/modal.tsx index 032d7958b3..b8cbcb09e1 100644 --- a/apps/app/src/stores/modal.tsx +++ b/apps/app/src/stores/modal.tsx @@ -8,6 +8,7 @@ import { SWRResponse } from 'swr'; import Linker from '~/client/models/Linker'; import MarkdownTable from '~/client/models/MarkdownTable'; import { BookmarkFolderItems } from '~/interfaces/bookmark-info'; +import type { RendererOptions } from '~/interfaces/renderer-options'; import { OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction, OnPutBackedFunction, onDeletedBookmarkFolderFunction, } from '~/interfaces/ui'; @@ -249,19 +250,19 @@ export const usePutBackPageModal = (status?: PutBackPageModalStatus): SWRRespons /* * PagePresentationModal */ -type PresentationModalStatus = { - isOpened: boolean, +type PresentationModalRenderingOpts = { page?: { pageId: string, revisionId: string, }, + rendererOptions?: RendererOptions | null, +} +type PresentationModalStatus = PresentationModalRenderingOpts & { + isOpened: boolean, } type PresentationModalStatusUtils = { - open(page?: { - pageId: string, - revisionId: string, - }): Promise + open(opts?: PresentationModalRenderingOpts): Promise close(): Promise } @@ -269,14 +270,15 @@ export const usePagePresentationModal = ( status?: PresentationModalStatus, ): SWRResponse & PresentationModalStatusUtils => { const initialData: PresentationModalStatus = { + ...status, isOpened: false, }; const swrResponse = useStaticSWR('presentationModalStatus', status, { fallbackData: initialData }); return { ...swrResponse, - open: page => swrResponse.mutate({ isOpened: true, page }, { revalidate: true }), - close: () => swrResponse.mutate({ isOpened: false, page: undefined }), + open: opts => swrResponse.mutate({ isOpened: true, ...opts }, { revalidate: true }), + close: () => swrResponse.mutate({ isOpened: false, page: undefined, rendererOptions: undefined }), }; }; From df7efa74702ccba2d7cccf49161e5c4723be9523 Mon Sep 17 00:00:00 2001 From: Yuki Takei Date: Thu, 12 Oct 2023 15:07:13 +0000 Subject: [PATCH 13/15] update console.js --- apps/app/src/server/console.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/app/src/server/console.js b/apps/app/src/server/console.js index ffc3882e4f..d2f4fcc24a 100644 --- a/apps/app/src/server/console.js +++ b/apps/app/src/server/console.js @@ -6,7 +6,11 @@ const mongoose = require('mongoose'); const { getMongoUri, mongoOptions } = require('~/server/util/mongoose-utils'); -const models = require('./models'); +const models = { + ...require('./models'), + ...require('~/features/cms/server/models'), + ...require('~/features/lms/server/models'), +}; Object.keys(models).forEach((modelName) => { global[modelName] = models[modelName]; From e30224b3097f51d97c7ad6239452b672f37a3bbb Mon Sep 17 00:00:00 2001 From: Yuki Takei Date: Thu, 12 Oct 2023 15:45:02 +0000 Subject: [PATCH 14/15] update columns --- .../components/ArticleList/ArticleListRow.tsx | 17 +++++++++++++---- .../CourceUnitList/CourceUnitRow.tsx | 19 +++++++++++++++---- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/apps/app/src/features/cms/client/components/ArticleList/ArticleListRow.tsx b/apps/app/src/features/cms/client/components/ArticleList/ArticleListRow.tsx index 2f67828ac2..aeb9c5d807 100644 --- a/apps/app/src/features/cms/client/components/ArticleList/ArticleListRow.tsx +++ b/apps/app/src/features/cms/client/components/ArticleList/ArticleListRow.tsx @@ -1,15 +1,24 @@ import type { IPageHasId } from '@growi/core'; import { DevidedPagePath } from '@growi/core/dist/models'; +import { format } from 'date-fns'; import PagePathHierarchicalLink from '~/components/PagePathHierarchicalLink'; import LinkedPagePath from '~/models/linked-page-path'; + +const formatDate = (date: Date | null) => { + if (date == null) { + return ''; + } + return format(new Date(date), 'yyyy-MM-dd HH:mm'); +}; + export const ArticleListHead = (): JSX.Element => { return ( Article name Author - Published at + Last update ); }; @@ -21,7 +30,7 @@ type Props = { export const ArticleListRow = (props: Props): JSX.Element => { const { page } = props; - const author = page.creator.name; + const { creator, updatedAt } = page; const dPagePath: DevidedPagePath = new DevidedPagePath(page.path, false); const linkedPagePath = new LinkedPagePath(dPagePath.latter); @@ -31,8 +40,8 @@ export const ArticleListRow = (props: Props): JSX.Element => { - {author} - (TBD) + {creator.name} + {formatDate(updatedAt)} ); }; diff --git a/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitRow.tsx b/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitRow.tsx index 187ca948d3..6a46a4068d 100644 --- a/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitRow.tsx +++ b/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitRow.tsx @@ -1,16 +1,26 @@ import type { IPageHasId } from '@growi/core'; import { DevidedPagePath } from '@growi/core/dist/models'; +import { format } from 'date-fns'; import PagePathHierarchicalLink from '~/components/PagePathHierarchicalLink'; import LinkedPagePath from '~/models/linked-page-path'; + +const formatDate = (date: Date | null) => { + if (date == null) { + return ''; + } + return format(new Date(date), 'yyyy-MM-dd HH:mm'); +}; + + export const CourceUnitHead = (): JSX.Element => { return ( - Article name + Cource unit name Author - Published at + Last update ); }; @@ -23,6 +33,7 @@ type Props = { export const CourceUnitRow = (props: Props): JSX.Element => { const { page, onPlayButtonClicked } = props; + const { creator, updatedAt } = page; const dPagePath: DevidedPagePath = new DevidedPagePath(page.path, false); const linkedPagePath = new LinkedPagePath(dPagePath.latter); @@ -37,8 +48,8 @@ export const CourceUnitRow = (props: Props): JSX.Element => { - author - (TBD) + {creator.name} + {formatDate(updatedAt)} ); }; From 894972971236759131087d05b7431381db19244c Mon Sep 17 00:00:00 2001 From: Yuki Takei Date: Thu, 12 Oct 2023 15:45:20 +0000 Subject: [PATCH 15/15] improve title --- apps/app/src/pages/_cms/index.page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/app/src/pages/_cms/index.page.tsx b/apps/app/src/pages/_cms/index.page.tsx index c45c970adf..010483f032 100644 --- a/apps/app/src/pages/_cms/index.page.tsx +++ b/apps/app/src/pages/_cms/index.page.tsx @@ -53,7 +53,7 @@ const CmsPage: NextPageWithLayout = (props: Props) => { const { data: isDrawerMode } = useDrawerMode(); - const title = generateCustomTitleForPage(props, 'GROWI CMS Manager'); + const title = generateCustomTitleForPage(props, 'Contents Management System'); return ( <> @@ -63,7 +63,7 @@ const CmsPage: NextPageWithLayout = (props: Props) => {