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

Merge pull request #801 from weseek/feat/replace-form-usable-page-name-type-ahead

Feat/replace form usable page name type ahead
Yuki Takei 7 лет назад
Родитель
Сommit
229ed8c2c5

+ 3 - 1
src/client/js/app.js

@@ -283,7 +283,9 @@ const componentMappings = {
   'bookmark-button': <BookmarkButton pageId={pageId} crowi={crowi} />,
   'bookmark-button': <BookmarkButton pageId={pageId} crowi={crowi} />,
   'bookmark-button-lg': <BookmarkButton pageId={pageId} crowi={crowi} size="lg" />,
   'bookmark-button-lg': <BookmarkButton pageId={pageId} crowi={crowi} size="lg" />,
 
 
-  'page-name-input': <NewPageNameInput crowi={crowi} parentPageName={pagePath} />,
+  '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} />,
 
 
 };
 };
 // additional definitions if data exists
 // additional definitions if data exists

+ 2 - 2
src/client/js/components/HeaderSearchBox.js

@@ -2,7 +2,7 @@
 
 
 import React from 'react';
 import React from 'react';
 
 
-import SearchForm from './HeaderSearchBox/SearchForm';
+import HeaderSearchForm from './HeaderSearchBox/HeaderSearchForm';
 // import SearchSuggest from './HeaderSearchBox/SearchSuggest'; // omit since using react-bootstrap-typeahead in SearchForm
 // import SearchSuggest from './HeaderSearchBox/SearchSuggest'; // omit since using react-bootstrap-typeahead in SearchForm
 
 
 export default class SearchBox extends React.Component {
 export default class SearchBox extends React.Component {
@@ -14,7 +14,7 @@ export default class SearchBox extends React.Component {
   render() {
   render() {
     return (
     return (
       <div className="search-box">
       <div className="search-box">
-        <SearchForm />
+        <HeaderSearchForm />
         {/* omit since using react-bootstrap-typeahead in SearchForm
         {/* omit since using react-bootstrap-typeahead in SearchForm
         <SearchSuggest
         <SearchSuggest
           searchingKeyword={this.state.searchingKeyword}
           searchingKeyword={this.state.searchingKeyword}

+ 63 - 0
src/client/js/components/HeaderSearchBox/HeaderSearchForm.js

@@ -0,0 +1,63 @@
+import React from 'react';
+
+import FormGroup from 'react-bootstrap/es/FormGroup';
+import Button from 'react-bootstrap/es/Button';
+import InputGroup from 'react-bootstrap/es/InputGroup';
+
+import SearchForm from '../SearchForm';
+
+
+// Header.SearchForm
+export default class HeaderSearchForm extends React.Component {
+
+  constructor(props) {
+    super(props);
+
+    this.crowi = window.crowi; // FIXME
+
+    this.onSubmit = this.onSubmit.bind(this);
+  }
+
+  componentDidMount() {
+  }
+
+  componentWillUnmount() {
+  }
+
+  onSubmit() {
+    this.refs.form.submit();
+  }
+
+  render() {
+    return (
+      <form
+        ref='form'
+        action='/_search'
+        className='search-form form-group input-group search-input-group hidden-print'
+      >
+        <FormGroup>
+          <InputGroup>
+            <SearchForm
+              crowi={this.crowi}
+              onSubmit={this.onSubmit}
+              placeholder="Search ..."
+            />
+            <InputGroup.Button>
+              <Button type="submit" bsStyle="link">
+                <i className="icon-magnifier"></i>
+              </Button >
+            </InputGroup.Button>
+          </InputGroup>
+        </FormGroup>
+
+      </form>
+
+    );
+  }
+}
+
+HeaderSearchForm.propTypes = {
+};
+
+HeaderSearchForm.defaultProps = {
+};

+ 19 - 40
src/client/js/components/HeaderSearchBox/SearchForm.js → src/client/js/components/SearchForm.js

@@ -1,27 +1,19 @@
 import React from 'react';
 import React from 'react';
+import PropTypes from 'prop-types';
+import SearchTypeahead from './SearchTypeahead';
 
 
-import FormGroup from 'react-bootstrap/es/FormGroup';
-import Button from 'react-bootstrap/es/Button';
-import InputGroup from 'react-bootstrap/es/InputGroup';
-
-import SearchTypeahead from '../SearchTypeahead';
-
-
-// Header.SearchForm
+// SearchTypeahead wrapper
 export default class SearchForm extends React.Component {
 export default class SearchForm extends React.Component {
 
 
   constructor(props) {
   constructor(props) {
     super(props);
     super(props);
 
 
-    this.crowi = window.crowi; // FIXME
-
     this.state = {
     this.state = {
       searchError: null,
       searchError: null,
     };
     };
 
 
     this.onSearchError = this.onSearchError.bind(this);
     this.onSearchError = this.onSearchError.bind(this);
     this.onChange = this.onChange.bind(this);
     this.onChange = this.onChange.bind(this);
-    this.onSubmit = this.onSubmit.bind(this);
   }
   }
 
 
   componentDidMount() {
   componentDidMount() {
@@ -69,47 +61,34 @@ export default class SearchForm extends React.Component {
     );
     );
   }
   }
 
 
-  onSubmit(query) {
-    this.refs.form.submit(query);
-  }
-
   render() {
   render() {
     const emptyLabel = (this.state.searchError !== null)
     const emptyLabel = (this.state.searchError !== null)
       ? 'Error on searching.'
       ? 'Error on searching.'
       : 'No matches found on title... Hit [Enter] key so that search on contents.';
       : 'No matches found on title... Hit [Enter] key so that search on contents.';
 
 
     return (
     return (
-      <form
-        ref='form'
-        action='/_search'
-        className='search-form form-group input-group search-input-group hidden-print'
-      >
-        <FormGroup>
-          <InputGroup>
-            <SearchTypeahead
-              crowi={this.crowi}
-              onChange={this.onChange}
-              onSubmit={this.onSubmit}
-              emptyLabel={emptyLabel}
-              placeholder="Search ..."
-              promptText={this.getHelpElement()}
-            />
-            <InputGroup.Button>
-              <Button type="submit" bsStyle="link">
-                <i className="icon-magnifier"></i>
-              </Button >
-            </InputGroup.Button>
-          </InputGroup>
-        </FormGroup>
-
-      </form>
-
+      <SearchTypeahead
+        crowi={this.props.crowi}
+        onChange={this.onChange}
+        onSubmit={this.props.onSubmit}
+        onInputChange={this.props.onInputChange}
+        onSearchError={this.onSearchError}
+        emptyLabel={emptyLabel}
+        placeholder="Search ..."
+        promptText={this.getHelpElement()}
+        keywordOnInit={this.props.keyword}
+      />
     );
     );
   }
   }
 }
 }
 
 
 SearchForm.propTypes = {
 SearchForm.propTypes = {
+  crowi: PropTypes.object.isRequired,
+  keyword: PropTypes.string,
+  onSubmit: PropTypes.func.isRequired,
+  onInputChange: PropTypes.func,
 };
 };
 
 
 SearchForm.defaultProps = {
 SearchForm.defaultProps = {
+  onInputChange: () => {},
 };
 };

+ 3 - 2
src/client/js/components/SearchPage.js

@@ -3,7 +3,7 @@
 import React from 'react';
 import React from 'react';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 
 
-import SearchForm from './SearchPage/SearchForm';
+import SearchPageForm from './SearchPage/SearchPageForm';
 import SearchResult from './SearchPage/SearchResult';
 import SearchResult from './SearchPage/SearchResult';
 
 
 export default class SearchPage extends React.Component {
 export default class SearchPage extends React.Component {
@@ -92,7 +92,8 @@ export default class SearchPage extends React.Component {
     return (
     return (
       <div>
       <div>
         <div className="search-page-input">
         <div className="search-page-input">
-          <SearchForm
+          <SearchPageForm
+            crowi={this.props.crowi}
             onSearchFormChanged={this.search}
             onSearchFormChanged={this.search}
             keyword={this.state.searchingKeyword}
             keyword={this.state.searchingKeyword}
             />
             />

+ 0 - 61
src/client/js/components/SearchPage/SearchForm.js

@@ -1,61 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-// Search.SearchForm
-export default class SearchForm extends React.Component {
-
-  constructor(props) {
-    super(props);
-
-    this.state = {
-      keyword: this.props.keyword,
-      searchedKeyword: this.props.keyword,
-    };
-
-    this.handleSubmit = this.handleSubmit.bind(this);
-    this.handleChange = this.handleChange.bind(this);
-  }
-
-  search() {
-    if (this.state.searchedKeyword != this.state.keyword) {
-      this.props.onSearchFormChanged({keyword: this.state.keyword});
-      this.setState({searchedKeyword: this.state.keyword});
-    }
-  }
-
-  handleSubmit(event) {
-    event.preventDefault();
-    this.search({keyword: this.state.keyword});
-  }
-
-  handleChange(event) {
-    const keyword = event.target.value;
-    this.setState({keyword});
-  }
-
-  render() {
-    return (
-      <form className="form form-group input-group" onSubmit={this.handleSubmit}>
-        <input
-          type="text"
-          name="q"
-          value={this.state.keyword}
-          onChange={this.handleChange}
-          className="form-control"
-          />
-          <span className="input-group-btn">
-            <button type="submit" className="btn btn-default">
-              <i className="search-top-icon icon-magnifier"></i>
-            </button>
-          </span>
-      </form>
-    );
-  }
-}
-
-SearchForm.propTypes = {
-  keyword: PropTypes.string,
-  onSearchFormChanged: PropTypes.func.isRequired,
-};
-SearchForm.defaultProps = {
-};

+ 65 - 0
src/client/js/components/SearchPage/SearchPageForm.js

@@ -0,0 +1,65 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import SearchForm from '../SearchForm';
+
+// Search.SearchForm
+export default class SearchPageForm extends React.Component {
+
+  constructor(props) {
+    super(props);
+
+    this.state = {
+      keyword: this.props.keyword,
+      searchedKeyword: this.props.keyword,
+    };
+
+    this.search = this.search.bind(this);
+    this.onSubmit = this.onSubmit.bind(this);
+    this.onInputChange = this.onInputChange.bind(this);
+  }
+
+  search(keyword) {
+    if (this.state.searchedKeyword != keyword) {
+      this.props.onSearchFormChanged({keyword: keyword});
+      this.setState({searchedKeyword: keyword});
+    }
+  }
+
+  onSubmit(event) { // submit with button
+    event.preventDefault(); // prevent refreshing page
+    this.search(this.state.keyword);
+  }
+
+  onInputChange(input) { // for only submitting with button
+    this.setState({keyword: input});
+  }
+
+  render() {
+    return (
+      <form ref='form'
+        className="form form-group input-group"
+        onSubmit={this.onSubmit}
+      >
+        <SearchForm
+          crowi={this.props.crowi}
+          onSubmit={this.search}
+          keyword={this.state.searchedKeyword}
+          onInputChange={this.onInputChange}
+        />
+        <span className="input-group-btn">
+          <button type="submit" className="btn btn-default">
+            <i className="search-top-icon icon-magnifier"></i>
+          </button>
+        </span>
+      </form>
+    );
+  }
+}
+
+SearchPageForm.propTypes = {
+  crowi: PropTypes.object.isRequired,
+  keyword: PropTypes.string,
+  onSearchFormChanged: PropTypes.func.isRequired,
+};
+SearchPageForm.defaultProps = {
+};

+ 4 - 11
src/client/js/components/SearchTypeahead.js

@@ -28,7 +28,6 @@ export default class SearchTypeahead extends React.Component {
     this.search = this.search.bind(this);
     this.search = this.search.bind(this);
     this.onInputChange = this.onInputChange.bind(this);
     this.onInputChange = this.onInputChange.bind(this);
     this.onKeyDown = this.onKeyDown.bind(this);
     this.onKeyDown = this.onKeyDown.bind(this);
-    this.onChange = this.onChange.bind(this);
     this.dispatchSubmit = this.dispatchSubmit.bind(this);
     this.dispatchSubmit = this.dispatchSubmit.bind(this);
     this.getRestoreFormButton = this.getRestoreFormButton.bind(this);
     this.getRestoreFormButton = this.getRestoreFormButton.bind(this);
     this.renderMenuItemChildren = this.renderMenuItemChildren.bind(this);
     this.renderMenuItemChildren = this.renderMenuItemChildren.bind(this);
@@ -93,6 +92,7 @@ export default class SearchTypeahead extends React.Component {
 
 
   onInputChange(text) {
   onInputChange(text) {
     this.setState({input: text});
     this.setState({input: text});
+    this.props.onInputChange(text);
     if (text === '') {
     if (text === '') {
       this.setState({pages: []});
       this.setState({pages: []});
     }
     }
@@ -104,18 +104,9 @@ export default class SearchTypeahead extends React.Component {
     }
     }
   }
   }
 
 
-  onChange(selected) {
-    const page = selected[0];  // should be single page selected
-
-    // navigate to page
-    if (page != null) {
-      window.location = page.path;
-    }
-  }
-
   dispatchSubmit() {
   dispatchSubmit() {
     if (this.props.onSubmit != null) {
     if (this.props.onSubmit != null) {
-      this.props.onSubmit(this.state.keyword);
+      this.props.onSubmit(this.state.input);
     }
     }
   }
   }
 
 
@@ -196,6 +187,7 @@ SearchTypeahead.propTypes = {
   onSearchError:   PropTypes.func,
   onSearchError:   PropTypes.func,
   onChange:        PropTypes.func,
   onChange:        PropTypes.func,
   onSubmit:        PropTypes.func,
   onSubmit:        PropTypes.func,
+  onInputChange:   PropTypes.func,
   emptyLabel:      PropTypes.string,
   emptyLabel:      PropTypes.string,
   placeholder:     PropTypes.string,
   placeholder:     PropTypes.string,
   keywordOnInit:   PropTypes.string,
   keywordOnInit:   PropTypes.string,
@@ -212,4 +204,5 @@ SearchTypeahead.defaultProps = {
   emptyLabel:      null,
   emptyLabel:      null,
   placeholder:     '',
   placeholder:     '',
   keywordOnInit:   '',
   keywordOnInit:   '',
+  onInputChange: () => {},
 };
 };

+ 4 - 4
src/client/js/legacy/crowi.js

@@ -357,7 +357,7 @@ $(function() {
     // create name-value map
     // create name-value map
     let nameValueMap = {};
     let nameValueMap = {};
     $(this).serializeArray().forEach((obj) => {
     $(this).serializeArray().forEach((obj) => {
-      nameValueMap[obj.name] = obj.value;
+      nameValueMap[obj.name] = obj.value; // nameValueMap['q'] is renamed page path
     });
     });
 
 
     const data = $(this).serialize() + `&socketClientId=${crowi.getSocketClientId()}`;
     const data = $(this).serialize() + `&socketClientId=${crowi.getSocketClientId()}`;
@@ -374,7 +374,7 @@ $(function() {
         $('#renamePage .msg, #unportalize .msg').hide();
         $('#renamePage .msg, #unportalize .msg').hide();
         $(`#renamePage .msg-${res.code}, #unportalize .msg-${res.code}`).show();
         $(`#renamePage .msg-${res.code}, #unportalize .msg-${res.code}`).show();
         $('#renamePage #linkToNewPage, #unportalize #linkToNewPage').html(`
         $('#renamePage #linkToNewPage, #unportalize #linkToNewPage').html(`
-          <a href="${nameValueMap.new_path}">${nameValueMap.new_path} <i class="icon-login"></i></a>
+          <a href="${nameValueMap.q}">${nameValueMap.q} <i class="icon-login"></i></a>
         `);
         `);
       }
       }
       else {
       else {
@@ -395,7 +395,7 @@ $(function() {
     // create name-value map
     // create name-value map
     let nameValueMap = {};
     let nameValueMap = {};
     $(this).serializeArray().forEach((obj) => {
     $(this).serializeArray().forEach((obj) => {
-      nameValueMap[obj.name] = obj.value;
+      nameValueMap[obj.name] = obj.value; // nameValueMap['q'] is duplicated page path
     });
     });
 
 
     $.ajax({
     $.ajax({
@@ -409,7 +409,7 @@ $(function() {
         $('#duplicatePage .msg').hide();
         $('#duplicatePage .msg').hide();
         $(`#duplicatePage .msg-${res.code}`).show();
         $(`#duplicatePage .msg-${res.code}`).show();
         $('#duplicatePage #linkToNewPage').html(`
         $('#duplicatePage #linkToNewPage').html(`
-          <a href="${nameValueMap.new_path}">${nameValueMap.new_path} <i class="icon-login"></i></a>
+          <a href="${nameValueMap.q}">${nameValueMap.q} <i class="icon-login"></i></a>
         `);
         `);
       }
       }
       else {
       else {

+ 1 - 1
src/client/styles/scss/_create-page.scss

@@ -51,7 +51,7 @@
         margin-left: 5px;
         margin-left: 5px;
       }
       }
 
 
-      #page-name-input {
+      .page-name-input {
         flex: 1;
         flex: 1;
         input {
         input {
           min-width: 300px; // Workaround to display placeholder.
           min-width: 300px; // Workaround to display placeholder.

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

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

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

@@ -32,7 +32,7 @@
             <div class="d-flex create-page-input-container">
             <div class="d-flex create-page-input-container">
               <div class="create-page-input-row d-flex align-items-center">
               <div class="create-page-input-row d-flex align-items-center">
                 {% if searchConfigured() %}
                 {% if searchConfigured() %}
-                <div id="page-name-input"></div>
+                <div id="create-page-name-input" class="page-name-input"></div>
                 {% else %}
                 {% else %}
                 <input type="text" value="{{ parentPath(path) }}" class="page-name-input form-control " placeholder="{{ t('Input page name') }}" required />
                 <input type="text" value="{{ parentPath(path) }}" class="page-name-input form-control " placeholder="{{ t('Input page name') }}" required />
                 {% endif %}
                 {% endif %}

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

@@ -17,7 +17,11 @@
               <label for="duplicatePageName">{{ t('modal_duplicate.label.New page name') }}</label><br>
               <label for="duplicatePageName">{{ t('modal_duplicate.label.New page name') }}</label><br>
               <div class="input-group">
               <div class="input-group">
                 <span class="input-group-addon">{{ baseUrl }}</span>
                 <span class="input-group-addon">{{ baseUrl }}</span>
-                <input type="text" class="form-control" name="new_path" id="duplicatePageName" value="{{ page.path }}">
+                {% 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 }}">
+                {% endif %}
               </div>
               </div>
             </div>
             </div>
         </div>
         </div>

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

@@ -17,7 +17,11 @@
             <label for="newPageName">{{ t('modal_rename.label.New page name') }}</label><br>
             <label for="newPageName">{{ t('modal_rename.label.New page name') }}</label><br>
             <div class="input-group">
             <div class="input-group">
               <span class="input-group-addon">{{ baseUrl }}</span>
               <span class="input-group-addon">{{ baseUrl }}</span>
-              <input type="text" class="form-control" name="new_path" id="newPageName" value="{{ page.path }}">
+              {% 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 }}">
+              {% endif %}
             </div>
             </div>
           </div>
           </div>
 
 

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

@@ -37,7 +37,7 @@
             <div>
             <div>
               <input type="hidden" name="_csrf" value="{{ csrf() }}">
               <input type="hidden" name="_csrf" value="{{ csrf() }}">
               <input type="hidden" name="path" value="{{ page.path }}">
               <input type="hidden" name="path" value="{{ page.path }}">
-              <input type="hidden" class="form-control" name="new_path" id="newPageName" value="{{ unportalizedPath }}">
+              <input type="hidden" class="form-control" name="q" id="newPageName" value="{{ unportalizedPath }}">
               <input type="hidden" name="page_id" value="{{ page._id.toString() }}">
               <input type="hidden" name="page_id" value="{{ page._id.toString() }}">
               <input type="hidden" name="revision_id" value="{{ page.revision._id.toString() }}">
               <input type="hidden" name="revision_id" value="{{ page.revision._id.toString() }}">
               <button type="submit" class="btn btn-warning">Unportalize</button>
               <button type="submit" class="btn btn-warning">Unportalize</button>