Преглед изворни кода

refs #238
- SearchTypeahead composenent を作成
- NewPageNameInputter は SearchForm から継承するのを取りやめて、SearchTypeahead を利用するよう修正

Ryu Sato пре 8 година
родитељ
комит
8a0434ba8a
3 измењених фајлова са 242 додато и 39 уклоњено
  1. 3 1
      resource/js/app.js
  2. 75 38
      resource/js/components/NewPageNameInputter.js
  3. 164 0
      resource/js/components/SearchTypeahead.js

+ 3 - 1
resource/js/app.js

@@ -16,6 +16,7 @@ import RevisionPath     from './components/Page/RevisionPath';
 import RevisionUrl      from './components/Page/RevisionUrl';
 import RevisionUrl      from './components/Page/RevisionUrl';
 import BookmarkButton   from './components/BookmarkButton';
 import BookmarkButton   from './components/BookmarkButton';
 import NewPageNameInputter from './components/NewPageNameInputter';
 import NewPageNameInputter from './components/NewPageNameInputter';
+import SearchTypeahead  from './components/SearchTypeahead';
 
 
 if (!window) {
 if (!window) {
   window = {};
   window = {};
@@ -73,7 +74,8 @@ const componentMappings = {
   'seen-user-list': <SeenUserList pageId={pageId} crowi={crowi} />,
   'seen-user-list': <SeenUserList pageId={pageId} crowi={crowi} />,
   'bookmark-button': <BookmarkButton pageId={pageId} crowi={crowi} />,
   'bookmark-button': <BookmarkButton pageId={pageId} crowi={crowi} />,
 
 
-  'page-name-inputter': <NewPageNameInputter crowi={crowi} keyword={pagePath} />,
+  'page-name-inputter': <NewPageNameInputter crowi={crowi} parentPageName={pagePath} />,
+
 };
 };
 // additional definitions if pagePath exists
 // additional definitions if pagePath exists
 if (pagePath) {
 if (pagePath) {

+ 75 - 38
resource/js/components/NewPageNameInputter.js

@@ -1,66 +1,103 @@
-// This is the root component for #search-top
-
 import React from 'react';
 import React from 'react';
 import { FormGroup, Button, InputGroup } from 'react-bootstrap';
 import { FormGroup, Button, InputGroup } from 'react-bootstrap';
 
 
 import { AsyncTypeahead } from 'react-bootstrap-typeahead';
 import { AsyncTypeahead } from 'react-bootstrap-typeahead';
 
 
-import SearchForm from './HeaderSearchBox/SearchForm';
-// import SearchSuggest from './HeaderSearchBox/SearchSuggest'; // omit since using react-bootstrap-typeahead in SearchForm
-
+import UserPicture from './User/UserPicture';
+import PageListMeta from './PageList/PageListMeta';
 import PagePath from './PageList/PagePath';
 import PagePath from './PageList/PagePath';
-
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
+import SearchTypeahead from './SearchTypeahead';
 
 
-export default class NewPageNameInputter extends SearchForm {
+export default class NewPageNameInputter extends React.Component {
 
 
   constructor(props) {
   constructor(props) {
+
     super(props);
     super(props);
 
 
-    this.state.keyword = props.keyword
+    this.crowi = window.crowi; // FIXME
+
+    this.state = {
+      input: '',
+      keyword: '',
+      searchedKeyword: '',
+      pages: [],
+      isLoading: false,
+      searchError: null,
+    };
+
+    this.crowi = window.crowi; // FIXME
+
+    this.onSearchSuccess = this.onSearchSuccess.bind(this);
+    this.onSearchError = this.onSearchError.bind(this);
+    this.restoreForm = this.restoreForm.bind(this);
+    this.renderMenuItemChildren = this.renderMenuItemChildren.bind(this);
+    this.onInputChange = this.onInputChange.bind(this);
+  }
+
+  componentDidMount() {
+  }
+
+  componentWillUnmount() {
+  }
+
+  onSearchSuccess(res) {
+    this.setState({
+      isLoading: false,
+      keyword: '',
+      pages: res.data,
+    });
+  }
+
+  onSearchError(err) {
+    this.setState({
+      isLoading: false,
+      searchError: err,
+    });
+  }
+
+  onInputChange(text) {
+    this.setState({input: text});
+  }
+
+  restoreForm() {
+    this._searchtypeahead._typeahead.getInstance().clear();
+    this._searchtypeahead._typeahead.getInstance().input = 'hoge';
+  }
+
+  renderMenuItemChildren(option, props, index) {
+    const page = option;
+    return (
+      <span>
+        <UserPicture user={page.revision.author} />
+        <PagePath page={page} />
+        <PageListMeta page={page} />
+      </span>
+    );
   }
   }
 
 
   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...';
         : 'No matches found on title...';
-    const formClear = this.getFormClearComponent();
-    const keyword = "hoge";
 
 
     return (
     return (
-      <form
-        action="/_search"
-        className=""
-      >
-        <AsyncTypeahead
-            ref={ref => this._typeahead = ref}
-            inputProps={{name: "q", autoComplete: "off"}}
-            isLoading={this.state.isLoading}
-            labelKey="path"
-            minLength={2}
-            options={this.state.pages}
-            placeholder="Input page name"
-            emptyLabel={emptyLabel}
-            align='left'
-            submitFormOnEnter={true}
-            onSearch={this.search}
-            onInputChange={this.onInputChange}
-            renderMenuItemChildren={this.renderMenuItemChildren}
-            caseSensitive={false}
-            keyword={keyword} // 検索キーワードを表示中のページの親ページにしたいがやり方不明。[TODO]
-        />
-        {formClear}
-        <span>page: {this.state.keyword}</span> {/* 検索キーワードを表示デバッグ用 */ }
-
-      </form>
-
+      <SearchTypeahead
+        ref={searchTypeahead => this._searchtypeahead = searchTypeahead}
+        crowi={this.crowi}
+        onSearchSuccess={this.onSearchSuccess}
+        onSearchError={this.onSearchError}
+        onResetButton={this.restoreForm}
+        emptyLabel={emptyLabel}
+        keywordOnInit={this.props.parentPageName}
+      />
     );
     );
   }
   }
 }
 }
 
 
 NewPageNameInputter.propTypes = {
 NewPageNameInputter.propTypes = {
-  keyword: PropTypes.string.isRequired,
+  parentPageName: PropTypes.string.isRequired,
 };
 };
 NewPageNameInputter.defaultProps = {
 NewPageNameInputter.defaultProps = {
-  keyword: "",
+  parentPageName: "",
 };
 };

+ 164 - 0
resource/js/components/SearchTypeahead.js

@@ -0,0 +1,164 @@
+import React from 'react';
+import { FormGroup, Button, InputGroup } from 'react-bootstrap';
+
+import { AsyncTypeahead } from 'react-bootstrap-typeahead';
+
+import UserPicture from './User/UserPicture';
+import PageListMeta from './PageList/PageListMeta';
+import PagePath from './PageList/PagePath';
+import PropTypes from 'prop-types';
+
+export default class SearchTypeahead extends React.Component {
+
+  constructor(props) {
+
+    super(props);
+
+    this.crowi = window.crowi; // FIXME
+
+    this.state = {
+      input: '',
+      keyword: '',
+      searchedKeyword: '',
+      pages: [],
+      isLoading: false,
+      searchError: null,
+    };
+
+    this.search = this.search.bind(this);
+    this.onInputChange = this.onInputChange.bind(this);
+    this.onChange = this.onChange.bind(this);
+    this.getRestoreFormButton = this.getRestoreFormButton.bind(this);
+    this.renderMenuItemChildren = this.renderMenuItemChildren.bind(this);
+    this.emptyLabel = props.emptyLabel;
+  }
+
+  componentDidMount() {
+  }
+
+  componentWillUnmount() {
+  }
+
+  search(keyword) {
+
+    if (keyword === '') {
+      this.setState({
+        keyword: '',
+        searchedKeyword: '',
+      });
+      return;
+    }
+
+    this.setState({isLoading: true});
+
+    this.crowi.apiGet('/search', {q: keyword})
+      .then(res => { this._onSearchSuccess(res) })
+      .catch(err => { this._onSearchError(err) });
+  }
+
+  /**
+   * Occured when search is exit successfully
+   * @param {*} pages
+   */
+  _onSearchSuccess(res) {
+    this.setState({
+      isLoading: false,
+      keyword: '',
+      pages: res.data,
+    });
+    this.props.onSearchSuccess(res);
+  }
+
+  /**
+   * Occured when search is exit abnormaly
+   * @param {*} err
+   */
+  _onSearchError(err) {
+    this.setState({
+      isLoading: false,
+      searchError: err,
+    });
+    this.props.onSearchError(err);
+  }
+
+  getRestoreFormButton() {
+    let isHidden = (this.state.input.length === 0);
+
+    return isHidden ? <span></span> : (
+      <a className="btn btn-link search-top-clear" onClick={this.props.onResetButton} hidden={isHidden}>
+        <i className="fa fa-times-circle" />
+      </a>
+    );
+  }
+
+  onInputChange(text) {
+    this.setState({input: text});
+  }
+
+  onChange(selected) {
+    const page = selected[0];  // should be single page selected
+
+    // navigate to page
+    if (page != null) {
+        window.location = page.path;
+    }
+  }
+
+  renderMenuItemChildren(option, props, index) {
+    const page = option;
+    return (
+      <span>
+      <UserPicture user={page.revision.author} />
+      <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 keyword = "hoge";
+
+    return (
+      <form
+        action="/_search"
+        className=""
+        >
+      <AsyncTypeahead
+        ref={typeahead => this._typeahead = typeahead}
+        inputProps={{name: "q", autoComplete: "off"}}
+        isLoading={this.state.isLoading}
+        labelKey="path"
+        minLength={2}
+        options={this.state.pages}
+        placeholder="Input page name"
+        emptyLabel={this.emptyLabel ? this.emptyLabel : emptyLabel}
+        align='left'
+        submitFormOnEnter={true}
+        onSearch={this.search}
+        onInputChange={this.onInputChange}
+        renderMenuItemChildren={this.renderMenuItemChildren}
+        caseSensitive={false}
+      />
+      {restoreFormButton}
+      <span>keyword: {this.state.keyword}</span>
+      </form>
+    );
+  }
+}
+
+SearchTypeahead.propTypes = {
+  onSearchSuccess: PropTypes.func,
+  onSearchError:   PropTypes.func,
+  onResetButton:   PropTypes.func,
+  restoreAction:   PropTypes.func,
+  emptyLabel:      PropTypes.string,
+  keywordOnInit:   PropTypes.string,
+};
+
+SearchTypeahead.defaultProps = {
+  keywordOnInit:   "",
+};