Răsfoiți Sursa

add AdditionalMenuItems for contextual subnavigation

Yuki Takei 4 ani în urmă
părinte
comite
1d63dd97db

+ 8 - 0
packages/app/src/components/Common/Dropdown/PageItemControl.tsx

@@ -15,6 +15,9 @@ import { useSWRxPageInfo } from '~/stores/page';
 
 const logger = loggerFactory('growi:cli:PageItemControl');
 
+
+export type AdditionalMenuItemsRendererProps = { pageInfo: IPageInfoCommon | IPageInfo };
+
 type CommonProps = {
   pageInfo?: IPageInfoCommon | IPageInfo,
   isEnableActions?: boolean,
@@ -22,6 +25,8 @@ type CommonProps = {
   onClickBookmarkMenuItem?: (pageId: string, newValue?: boolean) => Promise<void>,
   onClickRenameMenuItem?: (pageId: string) => Promise<void>,
   onClickDeleteMenuItem?: (pageId: string) => Promise<void>,
+
+  additionalMenuItemRenderer?: React.FunctionComponent<AdditionalMenuItemsRendererProps>,
 }
 
 
@@ -35,6 +40,7 @@ const PageItemControlDropdownMenu = React.memo((props: DropdownMenuProps): JSX.E
   const {
     pageId, pageInfo, isEnableActions, hideBookmarkMenuItem,
     onClickBookmarkMenuItem, onClickRenameMenuItem, onClickDeleteMenuItem,
+    additionalMenuItemRenderer: AdditionalMenuItems,
   } = props;
 
 
@@ -107,6 +113,8 @@ const PageItemControlDropdownMenu = React.memo((props: DropdownMenuProps): JSX.E
         </DropdownItem>
       ) }
 
+      { AdditionalMenuItems && <AdditionalMenuItems pageInfo={pageInfo} /> }
+
       {/* divider */}
       {/* Delete */}
       { isExistPageInfo(pageInfo) && isEnableActions && pageInfo.isMovable && (

+ 58 - 2
packages/app/src/components/Navbar/GrowiContextualSubNavigation.tsx

@@ -1,6 +1,12 @@
 import React, { useCallback } from 'react';
 import PropTypes from 'prop-types';
 
+import { useTranslation } from 'react-i18next';
+
+import { DropdownItem } from 'reactstrap';
+
+import urljoin from 'url-join';
+
 import { withUnstatedContainers } from '../UnstatedUtils';
 import EditorContainer from '~/client/services/EditorContainer';
 import {
@@ -13,6 +19,7 @@ import {
 } from '~/stores/context';
 import { useSWRTagsInfo } from '~/stores/page';
 
+import { AdditionalMenuItemsRendererProps } from '../Common/Dropdown/PageItemControl';
 import { SubNavButtons } from './SubNavButtons';
 import PageEditorModeManager from './PageEditorModeManager';
 
@@ -20,6 +27,52 @@ import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { apiPost } from '~/client/util/apiv1-client';
 import { IPageHasId } from '~/interfaces/page';
 import { GrowiSubNavigation } from './GrowiSubNavigation';
+import PresentationIcon from '../Icons/PresentationIcon';
+
+
+type AdditionalMenuItemsProps = AdditionalMenuItemsRendererProps & {
+  pageId: string,
+  revisionId: string,
+}
+
+const AdditionalMenuItems = (props: AdditionalMenuItemsProps): JSX.Element => {
+  const { t } = useTranslation();
+
+  const { pageInfo, pageId, revisionId } = props;
+
+  const exportPageHandler = useCallback(async(format: string): Promise<void> => {
+    const url = new URL(urljoin(window.location.origin, '_api/v3/page/export', pageId));
+    url.searchParams.append('format', format);
+    url.searchParams.append('revisionId', revisionId);
+    window.location.href = url.href;
+  }, [pageId, revisionId]);
+
+  return (
+    <>
+      <DropdownItem divider />
+
+      {/* Presentation */}
+      <DropdownItem onClick={() => { /* TODO: implement in https://redmine.weseek.co.jp/issues/87672 */ }}>
+        <i className="icon-fw"><PresentationIcon /></i>
+        { t('Presentation Mode') }
+      </DropdownItem>
+
+      {/* Export markdown */}
+      <DropdownItem onClick={() => exportPageHandler('md')}>
+        <i className="icon-fw icon-cloud-download"></i>
+        {t('export_bulk.export_page_markdown')}
+      </DropdownItem>
+
+      <DropdownItem divider />
+
+      {/* Create template */}
+      <DropdownItem onClick={() => { /* TODO: implement in https://redmine.weseek.co.jp/issues/87673 */ }}>
+        <i className="icon-fw icon-magic-wand"></i> { t('template.option_label.create/edit') }
+      </DropdownItem>
+    </>
+  );
+};
+
 
 const GrowiContextualSubNavigation = (props) => {
   const { data: isDeviceSmallerThanMd } = useIsDeviceSmallerThanMd();
@@ -47,6 +100,7 @@ const GrowiContextualSubNavigation = (props) => {
   } = props;
 
   const isViewMode = editorMode === EditorMode.View;
+  const isPageExists = pageId != null;
 
   const tagsUpdatedHandler = useCallback(async(newTags: string[]) => {
     // It will not be reflected in the DB until the page is refreshed
@@ -78,12 +132,14 @@ const GrowiContextualSubNavigation = (props) => {
     return (
       <>
         <div className="h-50 d-flex flex-column align-items-end justify-content-center">
-          { isViewMode && (
+          { isPageExists && isViewMode && (
             <SubNavButtons
               isCompactMode={isCompactMode}
               pageId={pageId}
+              revisionId={revisionId}
               disableSeenUserInfoPopover={isSharedUser}
               showPageControlDropdown={isAbleToShowPageManagement}
+              additionalMenuItemRenderer={props => <AdditionalMenuItems {...props} pageId={pageId} revisionId={revisionId} />}
             />
           ) }
         </div>
@@ -100,7 +156,7 @@ const GrowiContextualSubNavigation = (props) => {
       </>
     );
   }, [
-    pageId,
+    pageId, revisionId, isPageExists,
     editorMode, mutateEditorMode,
     isCompactMode, isDeviceSmallerThanMd, isGuestUser, isSharedUser,
     isViewMode, isAbleToShowPageEditorModeManager, isAbleToShowPageManagement,

+ 10 - 6
packages/app/src/components/Navbar/SubNavButtons.tsx

@@ -12,23 +12,25 @@ import LikeButtons from '../LikeButtons';
 import BookmarkButtons from '../BookmarkButtons';
 import SeenUserInfo from '../User/SeenUserInfo';
 import { toggleBookmark, toggleLike, toggleSubscribe } from '~/client/services/page-operation';
-import { PageItemControl } from '../Common/Dropdown/PageItemControl';
+import { AdditionalMenuItemsRendererProps, PageItemControl } from '../Common/Dropdown/PageItemControl';
 
 
 type CommonProps = {
   isCompactMode?: boolean,
   disableSeenUserInfoPopover?: boolean,
   showPageControlDropdown?: boolean,
+  additionalMenuItemRenderer?: React.FunctionComponent<AdditionalMenuItemsRendererProps>,
 }
 
 type SubNavButtonsSubstanceProps= CommonProps & {
   pageId: string,
+  revisionId: string,
   pageInfo: IPageInfo,
 }
 
 const SubNavButtonsSubstance = (props: SubNavButtonsSubstanceProps): JSX.Element => {
   const {
-    pageInfo, pageId, isCompactMode, disableSeenUserInfoPopover, showPageControlDropdown,
+    pageInfo, pageId, isCompactMode, disableSeenUserInfoPopover, showPageControlDropdown, additionalMenuItemRenderer,
   } = props;
 
   const { data: isGuestUser } = useIsGuestUser();
@@ -106,6 +108,7 @@ const SubNavButtonsSubstance = (props: SubNavButtonsSubstanceProps): JSX.Element
           pageId={pageId}
           pageInfo={pageInfo}
           isEnableActions={!isGuestUser}
+          additionalMenuItemRenderer={additionalMenuItemRenderer}
           hideBookmarkMenuItem
         />
         // <PageManagement
@@ -123,15 +126,16 @@ const SubNavButtonsSubstance = (props: SubNavButtonsSubstanceProps): JSX.Element
 };
 
 type SubNavButtonsProps= CommonProps & {
-  pageId?: string | null,
+  pageId: string,
+  revisionId?: string | null,
 };
 
 export const SubNavButtons = (props: SubNavButtonsProps): JSX.Element => {
-  const { pageId } = props;
+  const { pageId, revisionId } = props;
 
   const { data: pageInfo, error } = useSWRxPageInfo(pageId ?? null);
 
-  if (pageId == null || pageInfo == null || error != null) {
+  if (revisionId == null || pageInfo == null || error != null) {
     return <></>;
   }
 
@@ -139,5 +143,5 @@ export const SubNavButtons = (props: SubNavButtonsProps): JSX.Element => {
     return <></>;
   }
 
-  return <SubNavButtonsSubstance {...props} pageInfo={pageInfo} pageId={pageId} />;
+  return <SubNavButtonsSubstance {...props} pageInfo={pageInfo} pageId={pageId} revisionId={revisionId} />;
 };