Просмотр исходного кода

Merge branch 'feat/page-rename-v5' into imprv/delete-single-page

Taichi Masuyama 4 лет назад
Родитель
Сommit
bd7663bde2

+ 5 - 2
packages/app/resource/locales/en_US/translation.json

@@ -178,7 +178,9 @@
     "error_message": "Some values ​​are incorrect",
     "required": "%s is required",
     "invalid_syntax": "The syntax of %s is invalid.",
-    "title_required": "Title is required."
+    "title_required": "Title is required.",
+    "slashed_are_not_yet_supported": "Titles containing slashes are not yet supported"
+
   },
   "not_found_page": {
     "Create Page": "Create Page",
@@ -972,7 +974,8 @@
     "password_and_confirm_password_does_not_match": "Password and confirm password does not match"
   },
   "pagetree": {
-    "private_legacy_pages": "Private Legacy Pages"
+    "private_legacy_pages": "Private Legacy Pages",
+    "cannot_rename_a_title_that_contains_slash": "Cannot rename a title that contains '/'"
   },
   "duplicated_page_alert" : {
     "same_page_name_exists": "Same page name exits as「{{pageName}}」",

+ 4 - 2
packages/app/resource/locales/ja_JP/translation.json

@@ -180,7 +180,8 @@
     "error_message": "いくつかの値が設定されていません",
     "required": "%sに値を入力してください",
     "invalid_syntax": "%sの構文が不正です",
-    "title_required": "タイトルを入力してください"
+    "title_required": "タイトルを入力してください",
+    "slashed_are_not_yet_supported": "スラッシュを含むタイトルにはまだ対応していません"
   },
   "not_found_page": {
     "Create Page": "ページを作成する",
@@ -965,7 +966,8 @@
     "password_and_confirm_password_does_not_match": "パスワードと確認パスワードが一致しません"
   },
   "pagetree": {
-    "private_legacy_pages": "待避所"
+    "private_legacy_pages": "待避所",
+    "cannot_rename_a_title_that_contains_slash": "`/` が含まれているタイトルにリネームできません"
   },
   "duplicated_page_alert" : {
     "same_page_name_exists": "ページ名 「{{pageName}}」が重複しています",

+ 4 - 2
packages/app/resource/locales/zh_CN/translation.json

@@ -178,7 +178,8 @@
 		"error_message": "有些值不正确",
 		"required": "%s 是必需的",
 		"invalid_syntax": "%s的语法无效。",
-    "title_required": "标题是必需的。"
+    "title_required": "标题是必需的。",
+    "slashed_are_not_yet_supported": "スラッシュを含むタイトルにはまだ対応していません"
   },
   "not_found_page": {
     "Create Page": "创建页面",
@@ -975,7 +976,8 @@
     "password_and_confirm_password_does_not_match": "密码和确认密码不匹配"
   },
   "pagetree": {
-    "private_legacy_pages": "私人遗留页面"
+    "private_legacy_pages": "私人遗留页面",
+    "cannot_rename_a_title_that_contains_slash": "不能重命名包含 ’/' 的标题"
   },
   "duplicated_page_alert" : {
     "same_page_name_exists": "页面名称「{{pageName}}」是重复的",

+ 11 - 3
packages/app/src/components/Common/ClosableTextInput.tsx

@@ -17,9 +17,10 @@ export type AlertInfo = {
 
 type ClosableTextInputProps = {
   isShown: boolean
+  value?: string
   placeholder?: string
   inputValidator?(text: string): AlertInfo | Promise<AlertInfo> | null
-  onPressEnter?(): void
+  onPressEnter?(inputText: string | null): void
   onClickOutside?(): void
 }
 
@@ -27,14 +28,18 @@ const ClosableTextInput: FC<ClosableTextInputProps> = memo((props: ClosableTextI
   const { t } = useTranslation();
   const inputRef = useRef<HTMLInputElement>(null);
 
+  const [inputText, setInputText] = useState(props.value);
   const [currentAlertInfo, setAlertInfo] = useState<AlertInfo | null>(null);
 
   const onChangeHandler = async(e) => {
     if (props.inputValidator == null) { return }
 
-    const alertInfo = await props.inputValidator(e.target.value);
+    const inputText = e.target.value;
+
+    const alertInfo = await props.inputValidator(inputText);
 
     setAlertInfo(alertInfo);
+    setInputText(inputText);
   };
 
   const onPressEnter = () => {
@@ -42,7 +47,9 @@ const ClosableTextInput: FC<ClosableTextInputProps> = memo((props: ClosableTextI
       return;
     }
 
-    props.onPressEnter();
+    const text = inputText != null ? inputText.trim() : null;
+
+    props.onPressEnter(text);
   };
 
   const onKeyDownHandler = (e) => {
@@ -94,6 +101,7 @@ const ClosableTextInput: FC<ClosableTextInputProps> = memo((props: ClosableTextI
   return (
     <div className={props.isShown ? 'd-block' : 'd-none'}>
       <input
+        value={inputText}
         ref={inputRef}
         type="text"
         className="form-control"

+ 33 - 7
packages/app/src/components/Sidebar/PageTree/Item.tsx

@@ -5,7 +5,7 @@ import nodePath from 'path';
 import { useTranslation } from 'react-i18next';
 import { pagePathUtils } from '@growi/core';
 import { useDrag, useDrop } from 'react-dnd';
-import { toastWarning } from '~/client/util/apiNotification';
+import { toastWarning, toastError } from '~/client/util/apiNotification';
 
 import { ItemNode } from './ItemNode';
 import { IPageHasId } from '~/interfaces/page';
@@ -13,6 +13,7 @@ import { useSWRxPageChildren } from '../../../stores/page-listing';
 import ClosableTextInput, { AlertInfo, AlertType } from '../../Common/ClosableTextInput';
 import PageItemControl from '../../Common/Dropdown/PageItemControl';
 import { IPageForPageDeleteModal } from '~/components/PageDeleteModal';
+import { apiv3Put } from '~/client/util/apiv3-client';
 
 import TriangleIcon from '~/components/Icons/TriangleIcon';
 
@@ -118,6 +119,7 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
 
   const { page, children } = itemNode;
 
+  const [pageTitle, setPageTitle] = useState(page.path);
   const [currentChildren, setCurrentChildren] = useState(children);
   const [isOpen, setIsOpen] = useState(_isOpen);
   const [isNewPageInputShown, setNewPageInputShown] = useState(false);
@@ -194,12 +196,28 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
     setRenameInputShown(true);
   }, []);
 
-  // TODO: make a put request to pages/title
-  const onPressEnterForRenameHandler = () => {
-    toastWarning(t('search_result.currently_not_implemented'));
-    setRenameInputShown(false);
+  const onPressEnterForRenameHandler = async(inputText: string) => {
+    if (inputText == null || inputText === '' || inputText.trim() === '' || inputText.includes('/')) {
+      return;
+    }
+
+    const parentPath = nodePath.dirname(page.path as string);
+    const newPagePath = `${parentPath}/${inputText}`;
+
+    try {
+      setPageTitle(inputText);
+      setRenameInputShown(false);
+      await apiv3Put('/pages/rename', { newPagePath, pageId: page._id, revisionId: page.revision });
+    }
+    catch (err) {
+      // open ClosableInput and set pageTitle back to the previous title
+      setPageTitle(nodePath.basename(pageTitle as string));
+      setRenameInputShown(true);
+      toastError(err);
+    }
   };
 
+
   // TODO: go to create page page
   const onPressEnterForCreateHandler = () => {
     toastWarning(t('search_result.currently_not_implemented'));
@@ -207,13 +225,20 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
   };
 
   const inputValidator = (title: string | null): AlertInfo | null => {
-    if (title == null || title === '') {
+    if (title == null || title === '' || title.trim() === '') {
       return {
         type: AlertType.WARNING,
         message: t('form_validation.title_required'),
       };
     }
 
+    if (title.includes('/')) {
+      return {
+        type: AlertType.WARNING,
+        message: t('form_validation.slashed_are_not_yet_supported'),
+      };
+    }
+
     return null;
   };
 
@@ -261,6 +286,7 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
         { isRenameInputShown && (
           <ClosableTextInput
             isShown
+            value={nodePath.basename(pageTitle as string)}
             placeholder={t('Input page name')}
             onClickOutside={() => { setRenameInputShown(false) }}
             onPressEnter={onPressEnterForRenameHandler}
@@ -269,7 +295,7 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
         )}
         { !isRenameInputShown && (
           <a href={page._id} className="grw-pagetree-title-anchor flex-grow-1">
-            <p className={`text-truncate m-auto ${page.isEmpty && 'text-muted'}`}>{nodePath.basename(page.path as string) || '/'}</p>
+            <p className={`text-truncate m-auto ${page.isEmpty && 'text-muted'}`}>{nodePath.basename(pageTitle as string) || '/'}</p>
           </a>
         )}
         <div className="grw-pagetree-count-wrapper">