-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6 from weseek/feat/41-cms-top
feat: CMS top page
- Loading branch information
Showing
13 changed files
with
315 additions
and
0 deletions.
There are no files selected for viewing
47 changes: 47 additions & 0 deletions
47
apps/app/src/features/cms/client/components/CmsList/CmsList.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import Link from 'next/link'; | ||
|
||
import { create } from '../../services/cms-namespace'; | ||
import { useSWRxCmsNamespaces } from '../../stores/cms-namespace'; | ||
|
||
export const CmsList = (): JSX.Element => { | ||
const { data } = useSWRxCmsNamespaces(); | ||
|
||
if (data == null) { // data should be not null by `suspense: true` | ||
return <></>; | ||
} | ||
|
||
return ( | ||
<table className="table table-bordered grw-duplicated-paths-table"> | ||
<thead> | ||
<tr> | ||
<th className="col-4">namespace</th> | ||
<th>desc</th> | ||
</tr> | ||
</thead> | ||
<tbody className="overflow-auto"> | ||
{data.map((cmsNamespace) => { | ||
const { namespace, desc } = cmsNamespace; | ||
return ( | ||
<tr key={namespace}> | ||
<td> | ||
<Link href={`/_cms/${namespace}`} prefetch={false}> | ||
{namespace} | ||
</Link> | ||
</td> | ||
<td> | ||
{desc} | ||
</td> | ||
</tr> | ||
); | ||
})} | ||
<tr> | ||
<td colSpan={2} className="text-center"> | ||
<button type="button" className="btn btn-outline-secondary"> | ||
<span className="icon icon-plus" /> Add | ||
</button> | ||
</td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './CmsList'; |
11 changes: 11 additions & 0 deletions
11
apps/app/src/features/cms/client/services/cms-namespace.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { apiv3Post } from '~/client/util/apiv3-client'; | ||
|
||
import { ICmsNamespace } from '../../interfaces'; | ||
|
||
export const create = async(namespace: string, desc?: string): Promise<void> => { | ||
const newNamespace: ICmsNamespace = { | ||
namespace, | ||
desc, | ||
}; | ||
await apiv3Post('/cms/namespace', { data: newNamespace }); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import useSWR, { type SWRConfiguration, type SWRResponse } from 'swr'; | ||
|
||
import { apiv3Get } from '~/client/util/apiv3-client'; | ||
|
||
import type { ICmsNamespace } from '../../interfaces'; | ||
|
||
type ListCmsNamespaceResults = { | ||
data: ICmsNamespace[], | ||
} | ||
|
||
export const useSWRxCmsNamespaces = (config?: SWRConfiguration): SWRResponse<ICmsNamespace[], Error> => { | ||
return useSWR( | ||
'/cms/namespace', | ||
async(endpoint) => { | ||
try { | ||
const res = await apiv3Get<ListCmsNamespaceResults>(endpoint); | ||
return res.data.data; | ||
} | ||
catch (err) { | ||
throw new Error(err); | ||
} | ||
}, | ||
config, | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export type ICmsNamespaceAttribute = Map<string, any>; | ||
export type ICmsNamespace = { | ||
namespace: string, | ||
desc?: string, | ||
attributes?: ICmsNamespaceAttribute[], | ||
meta?: Map<string, any>, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './cms-namespace'; |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { | ||
Schema, type Model, type Document, | ||
} from 'mongoose'; | ||
|
||
import { getOrCreateModel } from '~/server/util/mongoose-utils'; | ||
|
||
import type { ICmsNamespace } from '../../interfaces'; | ||
|
||
export interface ICmsNamespaceDocument extends ICmsNamespace, Document { | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-empty-interface | ||
export interface ICmsNamespaceModel extends Model<ICmsNamespaceDocument> { | ||
} | ||
|
||
const cmsNamespaceSchema = new Schema<ICmsNamespaceDocument, ICmsNamespace>({ | ||
namespace: { type: String, required: true, unique: true }, | ||
desc: { type: String }, | ||
attributes: [Map], | ||
meta: Map, | ||
}); | ||
|
||
export const CmsNamespace = getOrCreateModel<ICmsNamespaceDocument, ICmsNamespaceModel>('CmsNamespace', cmsNamespaceSchema); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './cms-namespace'; |
31 changes: 31 additions & 0 deletions
31
apps/app/src/features/cms/server/routes/apiv3/cms-namespace.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import express, { Request, Router } from 'express'; | ||
|
||
import Crowi from '~/server/crowi'; | ||
import { ApiV3Response } from '~/server/routes/apiv3/interfaces/apiv3-response'; | ||
|
||
import { CmsNamespace } from '../../models'; | ||
|
||
|
||
/* | ||
* Validators | ||
*/ | ||
const validator = { | ||
}; | ||
|
||
module.exports = (crowi: Crowi): Router => { | ||
const loginRequiredStrictly = require('~/server/middlewares/login-required')(crowi); | ||
|
||
const router = express.Router(); | ||
|
||
router.get('/', loginRequiredStrictly, async(req: Request, res: ApiV3Response) => { | ||
try { | ||
const data = await CmsNamespace.find({}); | ||
return res.apiv3({ data }); | ||
} | ||
catch (err) { | ||
return res.apiv3Err(err); | ||
} | ||
}); | ||
|
||
return router; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import express, { Router } from 'express'; | ||
|
||
import Crowi from '~/server/crowi'; | ||
|
||
|
||
module.exports = (crowi: Crowi): Router => { | ||
const router = express.Router(); | ||
|
||
router.use('/namespace', require('./cms-namespace')(crowi)); | ||
|
||
return router; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
import React, { ReactNode } from 'react'; | ||
|
||
import type { IUser, IUserHasId } from '@growi/core'; | ||
import type { GetServerSideProps, GetServerSidePropsContext } from 'next'; | ||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; | ||
import dynamic from 'next/dynamic'; | ||
import Head from 'next/head'; | ||
|
||
import { GrowiSubNavigation } from '~/components/Navbar/GrowiSubNavigation'; | ||
import type { CrowiRequest } from '~/interfaces/crowi-request'; | ||
import { useCurrentPageId } from '~/stores/page'; | ||
import { useDrawerMode } from '~/stores/ui'; | ||
|
||
import { BasicLayout } from '../../components/Layout/BasicLayout'; | ||
import { | ||
useCurrentUser, useCurrentPathname, useGrowiCloudUri, | ||
useIsSearchServiceConfigured, useIsSearchServiceReachable, | ||
useIsSearchScopeChildrenAsDefault, useIsSearchPage, useShowPageLimitationXL, | ||
} from '../../stores/context'; | ||
import type { NextPageWithLayout } from '../_app.page'; | ||
import type { CommonProps } from '../utils/commons'; | ||
import { | ||
getServerSideCommonProps, getNextI18NextConfig, generateCustomTitleForPage, useInitSidebarConfig, | ||
} from '../utils/commons'; | ||
|
||
const CmsList = dynamic(() => import('~/features/cms/client/components/CmsList').then(mod => mod.CmsList), { ssr: false }); | ||
|
||
type Props = CommonProps & { | ||
currentUser: IUser, | ||
isSearchServiceConfigured: boolean, | ||
isSearchServiceReachable: boolean, | ||
isSearchScopeChildrenAsDefault: boolean, | ||
showPageLimitationXL: number, | ||
}; | ||
|
||
const CmsPage: NextPageWithLayout<CommonProps> = (props: Props) => { | ||
useCurrentUser(props.currentUser ?? null); | ||
|
||
useGrowiCloudUri(props.growiCloudUri); | ||
|
||
useIsSearchServiceConfigured(props.isSearchServiceConfigured); | ||
useIsSearchServiceReachable(props.isSearchServiceReachable); | ||
useIsSearchScopeChildrenAsDefault(props.isSearchScopeChildrenAsDefault); | ||
|
||
useIsSearchPage(false); | ||
useCurrentPageId(null); | ||
useCurrentPathname('/_cms'); | ||
|
||
// init sidebar config with UserUISettings and sidebarConfig | ||
useInitSidebarConfig(props.sidebarConfig, props.userUISettings); | ||
|
||
useShowPageLimitationXL(props.showPageLimitationXL); | ||
|
||
const { data: isDrawerMode } = useDrawerMode(); | ||
|
||
const title = generateCustomTitleForPage(props, 'GROWI CMS Manager'); | ||
|
||
return ( | ||
<> | ||
<Head> | ||
<title>{title}</title> | ||
</Head> | ||
<div className="dynamic-layout-root"> | ||
<header className="py-0 position-relative"> | ||
<GrowiSubNavigation | ||
pagePath="/GROWI CMS Manager" | ||
showDrawerToggler={isDrawerMode} | ||
isTagLabelsDisabled | ||
isDrawerMode={isDrawerMode} | ||
additionalClasses={['container-fluid']} | ||
/> | ||
</header> | ||
|
||
<div className="content-main container-lg grw-container-convertible mb-5 pb-5"> | ||
<CmsList /> | ||
</div> | ||
|
||
<div id="grw-fav-sticky-trigger" className="sticky-top"></div> | ||
</div> | ||
</> | ||
); | ||
}; | ||
|
||
type LayoutProps = Props & { | ||
children?: ReactNode, | ||
} | ||
|
||
const Layout = ({ children, ...props }: LayoutProps): JSX.Element => { | ||
// init sidebar config with UserUISettings and sidebarConfig | ||
useInitSidebarConfig(props.sidebarConfig, props.userUISettings); | ||
|
||
return <BasicLayout>{children}</BasicLayout>; | ||
}; | ||
|
||
CmsPage.getLayout = function getLayout(page) { | ||
return ( | ||
<> | ||
<Layout {...page.props}> | ||
{page} | ||
</Layout> | ||
</> | ||
); | ||
}; | ||
|
||
function injectServerConfigurations(context: GetServerSidePropsContext, props: Props): void { | ||
const req: CrowiRequest = context.req as CrowiRequest; | ||
const { crowi } = req; | ||
const { | ||
searchService, configManager, | ||
} = crowi; | ||
|
||
props.isSearchServiceConfigured = searchService.isConfigured; | ||
props.isSearchServiceReachable = searchService.isReachable; | ||
props.isSearchScopeChildrenAsDefault = configManager.getConfig('crowi', 'customize:isSearchScopeChildrenAsDefault'); | ||
props.showPageLimitationXL = crowi.configManager.getConfig('crowi', 'customize:showPageLimitationXL'); | ||
|
||
props.sidebarConfig = { | ||
isSidebarDrawerMode: configManager.getConfig('crowi', 'customize:isSidebarDrawerMode'), | ||
isSidebarClosedAtDockMode: configManager.getConfig('crowi', 'customize:isSidebarClosedAtDockMode'), | ||
}; | ||
|
||
} | ||
|
||
/** | ||
* for Server Side Translations | ||
* @param context | ||
* @param props | ||
* @param namespacesRequired | ||
*/ | ||
async function injectNextI18NextConfigurations(context: GetServerSidePropsContext, props: Props, namespacesRequired?: string[] | undefined): Promise<void> { | ||
const nextI18NextConfig = await getNextI18NextConfig(serverSideTranslations, context, namespacesRequired); | ||
props._nextI18Next = nextI18NextConfig._nextI18Next; | ||
} | ||
|
||
export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => { | ||
const req = context.req as CrowiRequest<IUserHasId & any>; | ||
const { user } = req; | ||
const result = await getServerSideCommonProps(context); | ||
|
||
if (!('props' in result)) { | ||
throw new Error('invalid getSSP result'); | ||
} | ||
const props: Props = result.props as Props; | ||
|
||
if (user != null) { | ||
props.currentUser = user.toObject(); | ||
} | ||
injectServerConfigurations(context, props); | ||
await injectNextI18NextConfigurations(context, props, ['translation']); | ||
|
||
return { | ||
props, | ||
}; | ||
}; | ||
|
||
export default CmsPage; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters