Procházet zdrojové kódy

Merge pull request #823 from weseek/fix/822-move-and-duplicate-dont-work

Fix/822 move and duplicate dont work
Yuki Takei před 7 roky
rodič
revize
0806513dfd

+ 4 - 4
src/client/js/app.js

@@ -29,7 +29,7 @@ import SeenUserList     from './components/SeenUserList';
 import RevisionPath     from './components/Page/RevisionPath';
 import RevisionUrl      from './components/Page/RevisionUrl';
 import BookmarkButton   from './components/BookmarkButton';
-import NewPageNameInput from './components/NewPageNameInput';
+import PagePathAutoComplete from './components/PagePathAutoComplete';
 import RecentCreated from './components/RecentCreated/RecentCreated';
 
 import CustomCssEditor  from './components/Admin/CustomCssEditor';
@@ -281,9 +281,9 @@ const componentMappings = {
   'bookmark-button': <BookmarkButton pageId={pageId} crowi={crowi} />,
   'bookmark-button-lg': <BookmarkButton pageId={pageId} crowi={crowi} size="lg" />,
 
-  'create-page-name-input': <NewPageNameInput crowi={crowi} parentPageName={pagePath} />,
-  'rename-page-name-input': <NewPageNameInput crowi={crowi} parentPageName={pagePath} />,
-  'duplicate-page-name-input': <NewPageNameInput crowi={crowi} parentPageName={pagePath} />,
+  'create-page-name-input': <PagePathAutoComplete crowi={crowi} initializedPath={pagePath} addSlashToTheEnd={true} />,
+  'rename-page-name-input': <PagePathAutoComplete crowi={crowi} initializedPath={pagePath} />,
+  'duplicate-page-name-input': <PagePathAutoComplete crowi={crowi} initializedPath={pagePath} />,
 
 };
 // additional definitions if data exists

+ 0 - 82
src/client/js/components/NewPageNameInput.js

@@ -1,82 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import SearchTypeahead from './SearchTypeahead';
-
-export default class NewPageNameInput extends React.Component {
-
-  constructor(props) {
-
-    super(props);
-
-    this.state = {
-      searchError: null,
-    };
-    this.crowi = this.props.crowi;
-
-    this.onSearchError = this.onSearchError.bind(this);
-    this.onSubmit = this.onSubmit.bind(this);
-    this.getParentPageName = this.getParentPageName.bind(this);
-  }
-
-  componentDidMount() {
-  }
-
-  componentWillUnmount() {
-  }
-
-  onSearchError(err) {
-    this.setState({
-      searchError: err,
-    });
-  }
-
-  onSubmit(query) {
-    // get the closest form element
-    const elem = this.refs.rootDom;
-    const form = elem.closest('form');
-    // submit with jQuery
-    $(form).submit();
-  }
-
-  getParentPageName(path) {
-    if (path == '/') {
-      return path;
-    }
-
-    if (path.match(/.+\/$/)) {
-      return path;
-    }
-
-    return path + '/';
-  }
-
-  render() {
-    const emptyLabel = (this.state.searchError !== null)
-      ? 'Error on searching.'
-      : 'No matches found on title...';
-
-    return (
-      <div ref='rootDom'>
-        <SearchTypeahead
-          ref={this.searchTypeaheadDom}
-          crowi={this.crowi}
-          onSearchError={this.onSearchError}
-          onSubmit={this.onSubmit}
-          emptyLabel={emptyLabel}
-          placeholder="Input page name"
-          keywordOnInit={this.getParentPageName(this.props.parentPageName)}
-        />
-      </div>
-    );
-  }
-}
-
-NewPageNameInput.propTypes = {
-  crowi:          PropTypes.object.isRequired,
-  parentPageName: PropTypes.string,
-};
-
-NewPageNameInput.defaultProps = {
-  parentPageName: '',
-};

+ 67 - 0
src/client/js/components/PagePathAutoComplete.jsx

@@ -0,0 +1,67 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import * as pagePathUtils from '@commons/util/page-path-utils';
+import SearchTypeahead from './SearchTypeahead';
+
+export default class PagePathAutoComplete extends React.Component {
+
+  constructor(props) {
+
+    super(props);
+
+    this.state = {
+      searchError: null,
+    };
+    this.crowi = this.props.crowi;
+
+    this.onSubmit = this.onSubmit.bind(this);
+    this.getKeywordOnInit = this.getKeywordOnInit.bind(this);
+  }
+
+  componentDidMount() {
+  }
+
+  componentWillUnmount() {
+  }
+
+  onSubmit(query) {
+    // get the closest form element
+    const elem = this.refs.rootDom;
+    const form = elem.closest('form');
+    // submit with jQuery
+    $(form).submit();
+  }
+
+  getKeywordOnInit(path) {
+    return this.props.addSlashToTheEnd
+      ? pagePathUtils.addSlashToTheEnd(path)
+      : pagePathUtils.removeLastSlash(path);
+  }
+
+  render() {
+    return (
+      <div ref='rootDom'>
+        <SearchTypeahead
+          ref={this.searchTypeaheadDom}
+          crowi={this.crowi}
+          onSubmit={this.onSubmit}
+          inputName='new_path'
+          emptyLabelExceptError={null}
+          placeholder="Input page path"
+          keywordOnInit={this.getKeywordOnInit(this.props.initializedPath)}
+        />
+      </div>
+    );
+  }
+}
+
+PagePathAutoComplete.propTypes = {
+  crowi:            PropTypes.object.isRequired,
+  initializedPath:  PropTypes.string,
+  addSlashToTheEnd: PropTypes.bool,
+};
+
+PagePathAutoComplete.defaultProps = {
+  initializedPath: '/',
+};

+ 44 - 25
src/client/js/components/SearchTypeahead.js

@@ -23,15 +23,15 @@ export default class SearchTypeahead extends React.Component {
       searchError: null,
     };
     this.crowi = this.props.crowi;
-    this.emptyLabel = props.emptyLabel;
 
+    this.restoreInitialData = this.restoreInitialData.bind(this);
     this.search = this.search.bind(this);
     this.onInputChange = this.onInputChange.bind(this);
     this.onKeyDown = this.onKeyDown.bind(this);
     this.dispatchSubmit = this.dispatchSubmit.bind(this);
+    this.getEmptyLabel = this.getEmptyLabel.bind(this);
     this.getRestoreFormButton = this.getRestoreFormButton.bind(this);
     this.renderMenuItemChildren = this.renderMenuItemChildren.bind(this);
-    this.restoreInitialData = this.restoreInitialData.bind(this);
     this.getTypeahead = this.getTypeahead.bind(this);
   }
 
@@ -48,6 +48,14 @@ export default class SearchTypeahead extends React.Component {
   componentWillUnmount() {
   }
 
+  /**
+   * Initialize keyword
+   */
+  restoreInitialData() {
+    this.refs.typeahead.getInstance().clear();
+    this.refs.typeahead.getInstance()._updateText(this.props.keywordOnInit);
+  }
+
   search(keyword) {
 
     if (keyword === '') {
@@ -110,23 +118,20 @@ export default class SearchTypeahead extends React.Component {
     }
   }
 
-  renderMenuItemChildren(option, props, index) {
-    const page = option;
-    return (
-      <span>
-      <UserPicture user={page.lastUpdateUser} size="sm" />
-      <PagePath page={page} />
-      <PageListMeta page={page} />
-      </span>
-    );
-  }
+  getEmptyLabel() {
+    // use props.emptyLabel as is if defined
+    if (this.props.emptyLabel !== undefined) {
+      return this.props.emptyLabel;
+    }
 
-  /**
-   * Initialize keyword
-   */
-  restoreInitialData() {
-    this.refs.typeahead.getInstance().clear();
-    this.refs.typeahead.getInstance()._updateText(this.props.keywordOnInit);
+    let emptyLabelExceptError = 'No matches found on title...';
+    if (this.props.emptyLabelExceptError !== undefined) {
+      emptyLabelExceptError = this.props.emptyLabelExceptError;
+    }
+
+    return (this.state.searchError !== null)
+      ? 'Error on searching.'
+      : emptyLabelExceptError;
   }
 
   /**
@@ -142,26 +147,39 @@ export default class SearchTypeahead extends React.Component {
     );
   }
 
+  renderMenuItemChildren(option, props, index) {
+    const page = option;
+    return (
+      <span>
+      <UserPicture user={page.lastUpdateUser} size="sm" />
+      <PagePath page={page} />
+      <PageListMeta page={page} />
+      </span>
+    );
+  }
+
   render() {
-    const emptyLabel = (this.state.searchError !== null)
-      ? 'Error on searching.'
-      : 'No matches found on title...';
-    const restoreFormButton = this.getRestoreFormButton();
     const defaultSelected = (this.props.keywordOnInit != '')
       ? [{path: this.props.keywordOnInit}]
       : [];
+    const inputProps = { autoComplete: 'off' };
+    if (this.props.inputName != null) {
+      inputProps.name = this.props.inputName;
+    }
+
+    const restoreFormButton = this.getRestoreFormButton();
 
     return (
       <div className="search-typeahead">
         <AsyncTypeahead
           {...this.props}
           ref="typeahead"
-          inputProps={{autoComplete: 'off'}}
+          inputProps={inputProps}
           isLoading={this.state.isLoading}
           labelKey="path"
           minLength={0}
           options={this.state.pages} // Search result (Some page names)
-          emptyLabel={this.emptyLabel ? this.emptyLabel : emptyLabel}
+          emptyLabel={this.getEmptyLabel()}
           align='left'
           submitFormOnEnter={true}
           onSearch={this.search}
@@ -188,7 +206,9 @@ SearchTypeahead.propTypes = {
   onChange:        PropTypes.func,
   onSubmit:        PropTypes.func,
   onInputChange:   PropTypes.func,
+  inputName:       PropTypes.string,
   emptyLabel:      PropTypes.string,
+  emptyLabelExceptError: PropTypes.string,
   placeholder:     PropTypes.string,
   keywordOnInit:   PropTypes.string,
   promptText:      PropTypes.object,
@@ -201,7 +221,6 @@ SearchTypeahead.defaultProps = {
   onSearchSuccess: noop,
   onSearchError:   noop,
   onChange:        noop,
-  emptyLabel:      null,
   placeholder:     '',
   keywordOnInit:   '',
   onInputChange: () => {},

+ 35 - 2
src/lib/util/page-path-utils.js

@@ -18,7 +18,40 @@ function encodePagePath(path) {
   return paths.join('/');
 }
 
+function matchEndWithSlash(path) {
+  // https://regex101.com/r/Z21fEd/1
+  return path.match(/(.+?)(\/)?$/);
+}
+
+function isEndWithSlash(path) {
+  const match = matchEndWithSlash(path);
+  return (match[2] != null);
+}
+
+function addSlashToTheEnd(path) {
+  if (path === '/') {
+    return path;
+  }
+
+  if (!isEndWithSlash(path)) {
+    return `${path}/`;
+  }
+  return path;
+}
+
+function removeLastSlash(path) {
+  if (path === '/') {
+    return path;
+  }
+
+  const match = matchEndWithSlash(path);
+  return match[1];
+}
+
 module.exports = {
-  encodePagePath: encodePagePath,
-  encodePagesPath: encodePagesPath
+  encodePagePath,
+  encodePagesPath,
+  isEndWithSlash,
+  addSlashToTheEnd,
+  removeLastSlash,
 };

+ 4 - 4
src/server/routes/page.js

@@ -951,13 +951,13 @@ module.exports = function(crowi, app) {
    * @apiParam {String} page_id Page Id.
    * @apiParam {String} path
    * @apiParam {String} revision_id
-   * @apiParam {String} q New path name.
+   * @apiParam {String} new_path New path name.
    * @apiParam {Bool} create_redirect
    */
   api.rename = async function(req, res) {
     const pageId = req.body.page_id;
     const previousRevision = req.body.revision_id || null;
-    const newPagePath = Page.normalizePath(req.body.q);
+    const newPagePath = Page.normalizePath(req.body.new_path);
     const options = {
       createRedirectPage: req.body.create_redirect || 0,
       moveUnderTrees: req.body.move_trees || 0,
@@ -1017,11 +1017,11 @@ module.exports = function(crowi, app) {
    * @apiGroup Page
    *
    * @apiParam {String} page_id Page Id.
-   * @apiParam {String} q New path name.
+   * @apiParam {String} new_path New path name.
    */
   api.duplicate = async function(req, res) {
     const pageId = req.body.page_id;
-    const newPagePath = Page.normalizePath(req.body.q);
+    const newPagePath = Page.normalizePath(req.body.new_path);
 
     const page = await Page.findByIdAndViewer(pageId, req.user);
 

+ 1 - 1
src/server/views/modal/duplicate.html

@@ -20,7 +20,7 @@
                 {% if searchConfigured() %}
                 <div id="duplicate-page-name-input" class="page-name-input"></div>
                 {% else %}
-                <input type="text" class="form-control" name="q" id="duplicatePageName" value="{{ page.path }}">
+                <input type="text" class="form-control" name="new_path" id="duplicatePageName" value="{{ page.path }}">
                 {% endif %}
               </div>
             </div>

+ 1 - 1
src/server/views/modal/rename.html

@@ -20,7 +20,7 @@
               {% if searchConfigured() %}
               <div id="rename-page-name-input" class="page-name-input"></div>
               {% else %}
-              <input type="text" class="form-control" name="q" id="newPageName" value="{{ page.path }}">
+              <input type="text" class="form-control" name="new_path" id="newPageName" value="{{ page.path }}">
               {% endif %}
             </div>
           </div>