|
@@ -9,28 +9,22 @@ import {
|
|
|
} from '@growi/core';
|
|
} from '@growi/core';
|
|
|
import { useTranslation } from 'next-i18next';
|
|
import { useTranslation } from 'next-i18next';
|
|
|
import { useRouter } from 'next/router';
|
|
import { useRouter } from 'next/router';
|
|
|
-import { UncontrolledTooltip, DropdownToggle } from 'reactstrap';
|
|
|
|
|
|
|
+import { UncontrolledTooltip } from 'reactstrap';
|
|
|
|
|
|
|
|
-import { bookmark, unbookmark, resumeRenameOperation } from '~/client/services/page-operation';
|
|
|
|
|
-import { apiv3Put, apiv3Post } from '~/client/util/apiv3-client';
|
|
|
|
|
|
|
+import { apiv3Post } from '~/client/util/apiv3-client';
|
|
|
import { ValidationTarget } from '~/client/util/input-validator';
|
|
import { ValidationTarget } from '~/client/util/input-validator';
|
|
|
import { toastWarning, toastError, toastSuccess } from '~/client/util/toastr';
|
|
import { toastWarning, toastError, toastSuccess } from '~/client/util/toastr';
|
|
|
import { TriangleIcon } from '~/components/Icons/TriangleIcon';
|
|
import { TriangleIcon } from '~/components/Icons/TriangleIcon';
|
|
|
import { NotAvailableForGuest } from '~/components/NotAvailableForGuest';
|
|
import { NotAvailableForGuest } from '~/components/NotAvailableForGuest';
|
|
|
import { NotAvailableForReadOnlyUser } from '~/components/NotAvailableForReadOnlyUser';
|
|
import { NotAvailableForReadOnlyUser } from '~/components/NotAvailableForReadOnlyUser';
|
|
|
-import {
|
|
|
|
|
- IPageInfoAll, IPageToDeleteWithMeta,
|
|
|
|
|
-} from '~/interfaces/page';
|
|
|
|
|
-import { useSWRMUTxCurrentUserBookmarks } from '~/stores/bookmark';
|
|
|
|
|
|
|
+import { IPageToDeleteWithMeta } from '~/interfaces/page';
|
|
|
import { IPageForPageDuplicateModal } from '~/stores/modal';
|
|
import { IPageForPageDuplicateModal } from '~/stores/modal';
|
|
|
-import { useSWRMUTxPageInfo } from '~/stores/page';
|
|
|
|
|
import { useSWRxPageChildren } from '~/stores/page-listing';
|
|
import { useSWRxPageChildren } from '~/stores/page-listing';
|
|
|
import { usePageTreeDescCountMap } from '~/stores/ui';
|
|
import { usePageTreeDescCountMap } from '~/stores/ui';
|
|
|
import { shouldRecoverPagePaths } from '~/utils/page-operation';
|
|
import { shouldRecoverPagePaths } from '~/utils/page-operation';
|
|
|
|
|
|
|
|
import ClosableTextInput from '../../Common/ClosableTextInput';
|
|
import ClosableTextInput from '../../Common/ClosableTextInput';
|
|
|
import CountBadge from '../../Common/CountBadge';
|
|
import CountBadge from '../../Common/CountBadge';
|
|
|
-import { PageItemControl } from '../../Common/Dropdown/PageItemControl';
|
|
|
|
|
|
|
|
|
|
import { ItemNode } from './ItemNode';
|
|
import { ItemNode } from './ItemNode';
|
|
|
|
|
|
|
@@ -91,12 +85,17 @@ export const NotDraggableForClosableTextInput = (props: NotDraggableProps): JSX.
|
|
|
return <div draggable onDragStart={e => e.preventDefault()}>{props.children}</div>;
|
|
return <div draggable onDragStart={e => e.preventDefault()}>{props.children}</div>;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-export const ToolOfSimpleItem = (props) => {
|
|
|
|
|
|
|
+type SimpleItemToolProps = {
|
|
|
|
|
+ page,
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+export const SimpleItemTool: FC<SimpleItemToolProps> = (props: SimpleItemToolProps) => {
|
|
|
const { t } = useTranslation();
|
|
const { t } = useTranslation();
|
|
|
const router = useRouter();
|
|
const router = useRouter();
|
|
|
const { getDescCount } = usePageTreeDescCountMap();
|
|
const { getDescCount } = usePageTreeDescCountMap();
|
|
|
|
|
|
|
|
const page = props.page;
|
|
const page = props.page;
|
|
|
|
|
+
|
|
|
const pageName = nodePath.basename(page.path ?? '') || '/';
|
|
const pageName = nodePath.basename(page.path ?? '') || '/';
|
|
|
|
|
|
|
|
const shouldShowAttentionIcon = page.processData != null ? shouldRecoverPagePaths(page.processData) : false;
|
|
const shouldShowAttentionIcon = page.processData != null ? shouldRecoverPagePaths(page.processData) : false;
|
|
@@ -141,7 +140,6 @@ export const ToolOfSimpleItem = (props) => {
|
|
|
|
|
|
|
|
const SimpleItem: FC<SimpleItemProps> = (props: SimpleItemProps) => {
|
|
const SimpleItem: FC<SimpleItemProps> = (props: SimpleItemProps) => {
|
|
|
const { t } = useTranslation();
|
|
const { t } = useTranslation();
|
|
|
- const router = useRouter();
|
|
|
|
|
|
|
|
|
|
const {
|
|
const {
|
|
|
itemNode, targetPathOrId, isOpen: _isOpen = false,
|
|
itemNode, targetPathOrId, isOpen: _isOpen = false,
|
|
@@ -154,12 +152,9 @@ const SimpleItem: FC<SimpleItemProps> = (props: SimpleItemProps) => {
|
|
|
const [currentChildren, setCurrentChildren] = useState(children);
|
|
const [currentChildren, setCurrentChildren] = useState(children);
|
|
|
const [isOpen, setIsOpen] = useState(_isOpen);
|
|
const [isOpen, setIsOpen] = useState(_isOpen);
|
|
|
const [isNewPageInputShown, setNewPageInputShown] = useState(false);
|
|
const [isNewPageInputShown, setNewPageInputShown] = useState(false);
|
|
|
- const [isRenameInputShown, setRenameInputShown] = useState(false);
|
|
|
|
|
const [isCreating, setCreating] = useState(false);
|
|
const [isCreating, setCreating] = useState(false);
|
|
|
|
|
|
|
|
const { data, mutate: mutateChildren } = useSWRxPageChildren(isOpen ? page._id : null);
|
|
const { data, mutate: mutateChildren } = useSWRxPageChildren(isOpen ? page._id : null);
|
|
|
- const { trigger: mutateCurrentUserBookmarks } = useSWRMUTxCurrentUserBookmarks();
|
|
|
|
|
- const { trigger: mutatePageInfo } = useSWRMUTxPageInfo(page._id ?? null);
|
|
|
|
|
|
|
|
|
|
// descendantCount
|
|
// descendantCount
|
|
|
const { getDescCount } = usePageTreeDescCountMap();
|
|
const { getDescCount } = usePageTreeDescCountMap();
|
|
@@ -186,83 +181,6 @@ const SimpleItem: FC<SimpleItemProps> = (props: SimpleItemProps) => {
|
|
|
}
|
|
}
|
|
|
}, [hasDescendants]);
|
|
}, [hasDescendants]);
|
|
|
|
|
|
|
|
- const bookmarkMenuItemClickHandler = async(_pageId: string, _newValue: boolean): Promise<void> => {
|
|
|
|
|
- const bookmarkOperation = _newValue ? bookmark : unbookmark;
|
|
|
|
|
- await bookmarkOperation(_pageId);
|
|
|
|
|
- mutateCurrentUserBookmarks();
|
|
|
|
|
- mutatePageInfo();
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const duplicateMenuItemClickHandler = useCallback((): void => {
|
|
|
|
|
- if (onClickDuplicateMenuItem == null) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const { _id: pageId, path } = page;
|
|
|
|
|
-
|
|
|
|
|
- if (pageId == null || path == null) {
|
|
|
|
|
- throw Error('Any of _id and path must not be null.');
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const pageToDuplicate = { pageId, path };
|
|
|
|
|
-
|
|
|
|
|
- onClickDuplicateMenuItem(pageToDuplicate);
|
|
|
|
|
- }, [onClickDuplicateMenuItem, page]);
|
|
|
|
|
-
|
|
|
|
|
- const renameMenuItemClickHandler = useCallback(() => {
|
|
|
|
|
- setRenameInputShown(true);
|
|
|
|
|
- }, []);
|
|
|
|
|
-
|
|
|
|
|
- const onPressEnterForRenameHandler = async(inputText: string) => {
|
|
|
|
|
- const parentPath = pathUtils.addTrailingSlash(nodePath.dirname(page.path ?? ''));
|
|
|
|
|
- const newPagePath = nodePath.resolve(parentPath, inputText);
|
|
|
|
|
-
|
|
|
|
|
- if (newPagePath === page.path) {
|
|
|
|
|
- setRenameInputShown(false);
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- setRenameInputShown(false);
|
|
|
|
|
- await apiv3Put('/pages/rename', {
|
|
|
|
|
- pageId: page._id,
|
|
|
|
|
- revisionId: page.revision,
|
|
|
|
|
- newPagePath,
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- if (onRenamed != null) {
|
|
|
|
|
- onRenamed(page.path, newPagePath);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- toastSuccess(t('renamed_pages', { path: page.path }));
|
|
|
|
|
- }
|
|
|
|
|
- catch (err) {
|
|
|
|
|
- setRenameInputShown(true);
|
|
|
|
|
- toastError(err);
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const deleteMenuItemClickHandler = useCallback(async(_pageId: string, pageInfo: IPageInfoAll | undefined): Promise<void> => {
|
|
|
|
|
- if (onClickDeleteMenuItem == null) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (page._id == null || page.path == null) {
|
|
|
|
|
- throw Error('_id and path must not be null.');
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const pageToDelete: IPageToDeleteWithMeta = {
|
|
|
|
|
- data: {
|
|
|
|
|
- _id: page._id,
|
|
|
|
|
- revision: page.revision as string,
|
|
|
|
|
- path: page.path,
|
|
|
|
|
- },
|
|
|
|
|
- meta: pageInfo,
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- onClickDeleteMenuItem(pageToDelete);
|
|
|
|
|
- }, [onClickDeleteMenuItem, page]);
|
|
|
|
|
-
|
|
|
|
|
const onPressEnterForCreateHandler = async(inputText: string) => {
|
|
const onPressEnterForCreateHandler = async(inputText: string) => {
|
|
|
setNewPageInputShown(false);
|
|
setNewPageInputShown(false);
|
|
|
const parentPath = pathUtils.addTrailingSlash(page.path as string);
|
|
const parentPath = pathUtils.addTrailingSlash(page.path as string);
|
|
@@ -300,33 +218,6 @@ const SimpleItem: FC<SimpleItemProps> = (props: SimpleItemProps) => {
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Users do not need to know if all pages have been renamed.
|
|
|
|
|
- * Make resuming rename operation appears to be working fine to allow users for a seamless operation.
|
|
|
|
|
- */
|
|
|
|
|
- const pathRecoveryMenuItemClickHandler = async(pageId: string): Promise<void> => {
|
|
|
|
|
- try {
|
|
|
|
|
- await resumeRenameOperation(pageId);
|
|
|
|
|
- toastSuccess(t('page_operation.paths_recovered'));
|
|
|
|
|
- }
|
|
|
|
|
- catch {
|
|
|
|
|
- toastError(t('page_operation.path_recovery_failed'));
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const pageTreeItemClickHandler = (e) => {
|
|
|
|
|
- e.preventDefault();
|
|
|
|
|
-
|
|
|
|
|
- if (page.path == null || page._id == null) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const link = pathUtils.returnPathForURL(page.path, page._id);
|
|
|
|
|
-
|
|
|
|
|
- router.push(link);
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
// didMount
|
|
// didMount
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
|
if (hasChildren()) setIsOpen(true);
|
|
if (hasChildren()) setIsOpen(true);
|
|
@@ -353,11 +244,6 @@ const SimpleItem: FC<SimpleItemProps> = (props: SimpleItemProps) => {
|
|
|
}
|
|
}
|
|
|
}, [data, isOpen, targetPathOrId]);
|
|
}, [data, isOpen, targetPathOrId]);
|
|
|
|
|
|
|
|
- // Rename process
|
|
|
|
|
- // Icon that draw attention from users for some actions
|
|
|
|
|
- const shouldShowAttentionIcon = page.processData != null ? shouldRecoverPagePaths(page.processData) : false;
|
|
|
|
|
- const pageName = nodePath.basename(page.path ?? '') || '/';
|
|
|
|
|
-
|
|
|
|
|
const ItemClassFixed = itemClass ?? SimpleItem;
|
|
const ItemClassFixed = itemClass ?? SimpleItem;
|
|
|
|
|
|
|
|
const commonProps = {
|
|
const commonProps = {
|
|
@@ -372,30 +258,16 @@ const SimpleItem: FC<SimpleItemProps> = (props: SimpleItemProps) => {
|
|
|
|
|
|
|
|
const CustomComponent = props.customComponent;
|
|
const CustomComponent = props.customComponent;
|
|
|
|
|
|
|
|
- // const ToolOfSimpleItem = () => {
|
|
|
|
|
- // return (
|
|
|
|
|
- // <>
|
|
|
|
|
- // {shouldShowAttentionIcon && (
|
|
|
|
|
- // <>
|
|
|
|
|
- // <i id="path-recovery" className="fa fa-warning mr-2 text-warning"></i>
|
|
|
|
|
- // <UncontrolledTooltip placement="top" target="path-recovery" fade={false}>
|
|
|
|
|
- // {t('tooltip.operation.attention.rename')}
|
|
|
|
|
- // </UncontrolledTooltip>
|
|
|
|
|
- // </>
|
|
|
|
|
- // )}
|
|
|
|
|
- // {page != null && page.path != null && page._id != null && (
|
|
|
|
|
- // <div className="grw-pagetree-title-anchor flex-grow-1">
|
|
|
|
|
- // <p onClick={pageTreeItemClickHandler} className={`text-truncate m-auto ${page.isEmpty && 'grw-sidebar-text-muted'}`}>{pageName}</p>
|
|
|
|
|
- // </div>
|
|
|
|
|
- // )}
|
|
|
|
|
- // {descendantCount > 0 && (
|
|
|
|
|
- // <div className="grw-pagetree-count-wrapper">
|
|
|
|
|
- // <CountBadge count={descendantCount} />
|
|
|
|
|
- // </div>
|
|
|
|
|
- // )}
|
|
|
|
|
- // </>
|
|
|
|
|
- // );
|
|
|
|
|
- // };
|
|
|
|
|
|
|
+ const SimpleItemContent = CustomComponent ?? SimpleItemTool;
|
|
|
|
|
+
|
|
|
|
|
+ const SimpleItemContentProps = {
|
|
|
|
|
+ page,
|
|
|
|
|
+ onRenamed,
|
|
|
|
|
+ onClickDuplicateMenuItem,
|
|
|
|
|
+ onClickDeleteMenuItem,
|
|
|
|
|
+ isEnableActions,
|
|
|
|
|
+ isReadOnlyUser,
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
return (
|
|
return (
|
|
|
<div
|
|
<div
|
|
@@ -423,16 +295,7 @@ const SimpleItem: FC<SimpleItemProps> = (props: SimpleItemProps) => {
|
|
|
)}
|
|
)}
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- {CustomComponent ?? (
|
|
|
|
|
- <ToolOfSimpleItem
|
|
|
|
|
- page={page}
|
|
|
|
|
- onRenamed={onRenamed}
|
|
|
|
|
- onClickDuplicateMenuItem={onClickDuplicateMenuItem}
|
|
|
|
|
- onClickDeleteMenuItem={onClickDeleteMenuItem}
|
|
|
|
|
- isEnableActions={isEnableActions}
|
|
|
|
|
- isReadOnlyUser={isReadOnlyUser}
|
|
|
|
|
- />
|
|
|
|
|
- )}
|
|
|
|
|
|
|
+ <SimpleItemContent {...SimpleItemContentProps}/>
|
|
|
|
|
|
|
|
{!pagePathUtils.isUsersTopPage(page.path ?? '') && (
|
|
{!pagePathUtils.isUsersTopPage(page.path ?? '') && (
|
|
|
<NotAvailableForGuest>
|
|
<NotAvailableForGuest>
|