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/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..b745683e1d 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,15 +26,22 @@ 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, rendererOptions: specifiedRendererOptions } = 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: presentationViewOptions } = usePresentationViewOptions(); const { data: isEnabledMarp } = useIsEnabledMarp(); @@ -54,17 +61,16 @@ 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; + const rendererOptions = (specifiedRendererOptions ?? presentationViewOptions) as ReactMarkdownOptions; return ( { { rendererOptions != null && isEnabledMarp != null && ( { ); }; - -export default PagePresentationModal; 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/CourceEnd/CourceEnd.module.scss b/apps/app/src/features/lms/client/components/CourceEnd/CourceEnd.module.scss new file mode 100644 index 0000000000..77da5a326d --- /dev/null +++ b/apps/app/src/features/lms/client/components/CourceEnd/CourceEnd.module.scss @@ -0,0 +1,7 @@ +.lms-cource-end :global { + .btn { + width: 400px; + height: 150px; + pointer-events: auto; + } +} diff --git a/apps/app/src/features/lms/client/components/CourceEnd/CourceEnd.tsx b/apps/app/src/features/lms/client/components/CourceEnd/CourceEnd.tsx new file mode 100644 index 0000000000..40c613f908 --- /dev/null +++ b/apps/app/src/features/lms/client/components/CourceEnd/CourceEnd.tsx @@ -0,0 +1,22 @@ +import { useCallback } from 'react'; + +import { usePagePresentationModal } from '~/stores/modal'; + +import styles from './CourceEnd.module.scss'; + +export const CourceEnd = (): JSX.Element => { + + 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/CourceUnitList/CourceUnitRow.tsx b/apps/app/src/features/lms/client/components/CourceUnitList/CourceUnitRow.tsx deleted file mode 100644 index 55b310ebb7..0000000000 --- a/apps/app/src/features/lms/client/components/CourceUnitList/CourceUnitRow.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import type { IPageHasId } from '@growi/core'; -import { DevidedPagePath } from '@growi/core/dist/models'; - -import PagePathHierarchicalLink from '~/components/PagePathHierarchicalLink'; -import LinkedPagePath from '~/models/linked-page-path'; - -export const CourceUnitHead = (): JSX.Element => { - return ( - - Article name - Author - Published at - - ); -}; - - -type Props = { - page: IPageHasId, -} - -export const CourceUnitRow = (props: Props): JSX.Element => { - const { page } = props; - - const dPagePath: DevidedPagePath = new DevidedPagePath(page.path, false); - const linkedPagePath = new LinkedPagePath(dPagePath.latter); - - return ( - - - - - author - (TBD) - - ); -}; 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/CourceView/CourceDashboard/index.ts b/apps/app/src/features/lms/client/components/CourceView/CourceDashboard/index.ts new file mode 100644 index 0000000000..cda5e005a8 --- /dev/null +++ b/apps/app/src/features/lms/client/components/CourceView/CourceDashboard/index.ts @@ -0,0 +1 @@ +export * from './CourceDashboard'; 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 62% 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 index 19adb18ef5..4a55e03044 100644 --- a/apps/app/src/features/lms/client/components/CourceUnitList/CourceUnitList.tsx +++ b/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitList.tsx @@ -1,15 +1,19 @@ -import React 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'; @@ -21,6 +25,28 @@ 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({ + page: { + pageId: page._id, + revisionId: page.revision.toString(), + }, + rendererOptions, + }); + }, [openPresentationModal, rendererOptions]); + // const pageDeletedHandler: OnDeletedFunction = useCallback((...args) => { // const path = args[0]; // const isCompletely = args[2]; @@ -39,7 +65,7 @@ const CourceUnitListSubstance = (props: SubstanceProps): JSX.Element => { if (pagingResult == null) { return ( -
+
@@ -48,7 +74,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 new file mode 100644 index 0000000000..6a46a4068d --- /dev/null +++ b/apps/app/src/features/lms/client/components/CourceView/CourceUnitList/CourceUnitRow.tsx @@ -0,0 +1,55 @@ +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 ( + + + Cource unit name + Author + Last update + + ); +}; + + +type Props = { + page: IPageHasId, + onPlayButtonClicked?: (page: IPageHasId) => void, +} + +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); + + return ( + + + + + + + + {creator.name} + {formatDate(updatedAt)} + + ); +}; 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 new file mode 100644 index 0000000000..7b7f1f70df --- /dev/null +++ b/apps/app/src/features/lms/client/components/CourceView/CourceView.tsx @@ -0,0 +1,55 @@ +import { useState } from 'react'; + +import dynamic from 'next/dynamic'; +import { TabContent, TabPane } from 'reactstrap'; + +import { useCurrentPathname } from '~/stores/context'; + +import { CourceUnitList } from './CourceUnitList'; + + +const CourceDashboard = dynamic(() => import('./CourceDashboard').then(mod => mod.CourceDashboard), { + ssr: false, +}); + +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/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/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/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) => {
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, @@ -33,6 +34,8 @@ type Props = CommonProps & { showPageLimitationXL: number, courceTitle: string, + + rendererConfig: RendererConfig, }; const CourcePage: NextPageWithLayout = (props: Props) => { @@ -46,11 +49,13 @@ 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); + useRendererConfig(props.rendererConfig); + useShowPageLimitationXL(props.showPageLimitationXL); const { data: isDrawerMode } = useDrawerMode(); @@ -75,24 +80,10 @@ const CourcePage: NextPageWithLayout = (props: Props) => {
-
+

{props.courceTitle}

-
-
- - -
-
-
-
-
+
@@ -157,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'), + }; } /** diff --git a/apps/app/src/pages/_lms/index.page.tsx b/apps/app/src/pages/_lms/index.page.tsx index 85d46e034f..e92a339591 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, @@ -72,7 +72,12 @@ const LmsPage: NextPageWithLayout = (props: Props) => {
- +
+

All Cources

+
+
+ +
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]; diff --git a/apps/app/src/stores/modal.tsx b/apps/app/src/stores/modal.tsx index 3422319a0f..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,12 +250,19 @@ export const usePutBackPageModal = (status?: PutBackPageModalStatus): SWRRespons /* * PagePresentationModal */ -type PresentationModalStatus = { +type PresentationModalRenderingOpts = { + page?: { + pageId: string, + revisionId: string, + }, + rendererOptions?: RendererOptions | null, +} +type PresentationModalStatus = PresentationModalRenderingOpts & { isOpened: boolean, } type PresentationModalStatusUtils = { - open(): Promise + open(opts?: PresentationModalRenderingOpts): Promise close(): Promise } @@ -262,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: () => swrResponse.mutate({ isOpened: true }, { revalidate: true }), - close: () => swrResponse.mutate({ isOpened: false }), + open: opts => swrResponse.mutate({ isOpened: true, ...opts }, { revalidate: true }), + close: () => swrResponse.mutate({ isOpened: false, page: undefined, rendererOptions: 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), ); };