1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-23 20:37:42 +00:00

Remove search single page (#7339)

* 01 remove jquery-ui css

* 02 remove search page

* 03 remove pubuser_search
This commit is contained in:
Michael An
2025-01-09 10:29:30 +08:00
committed by GitHub
parent 14e56ee8b9
commit f7f69aa910
13 changed files with 2 additions and 1454 deletions

View File

@@ -40,7 +40,6 @@ const entryFiles = {
repoFolderTrash: '/repo-folder-trash.js',
orgAdmin: '/pages/org-admin',
sysAdmin: '/pages/sys-admin',
search: '/pages/search',
uploadLink: '/pages/upload-link',
subscription: '/subscription.js',
institutionAdmin: '/pages/institution-admin/index.js'

View File

@@ -5,7 +5,7 @@ import classnames from 'classnames';
import MediaQuery from 'react-responsive';
import { seafileAPI } from '../../utils/seafile-api';
import searchAPI from '../../utils/search-api';
import { gettext, siteRoot } from '../../utils/constants';
import { gettext } from '../../utils/constants';
import SearchResultItem from './search-result-item';
import SearchResultLibrary from './search-result-library';
import { Utils } from '../../utils/utils';
@@ -13,7 +13,6 @@ import toaster from '../toast';
import Loading from '../loading';
import { SEARCH_MASK, SEARCH_CONTAINER } from '../../constants/zIndexes';
import { PRIVATE_FILE_TYPE } from '../../constants';
import Icon from '../icon';
const propTypes = {
repoID: PropTypes.string,
@@ -35,7 +34,6 @@ class Search extends Component {
constructor(props) {
super(props);
this.baseSearchPageURL = `${siteRoot}search/`;
this.state = {
width: 'default',
value: '',
@@ -44,13 +42,11 @@ class Search extends Component {
highlightIndex: 0,
page: 0,
isLoading: false,
hasMore: false,
isMaskShow: false,
showRecent: true,
isResultGotten: false,
isCloseShow: false,
isSearchInputShow: false, // for mobile
searchPageUrl: this.baseSearchPageURL,
searchTypesMax: 0,
highlightSearchTypesIndex: 0,
};
@@ -367,7 +363,6 @@ class Search extends Component {
inputValue: newValue,
isLoading: false,
highlightIndex: 0,
// resultItems: [],
isResultGotten: false,
}, () => {
if (!isInRepo && trimmedValue !== '') {
@@ -392,7 +387,6 @@ class Search extends Component {
this.setState({
resultItems: results,
isLoading: false,
hasMore: false,
});
}).catch(error => {
// eslint-disable-next-line no-console
@@ -428,7 +422,6 @@ class Search extends Component {
isResultGotten: true,
page: page + 1,
isLoading: false,
hasMore: res.data.has_more,
});
} else {
this.setState({
@@ -436,7 +429,6 @@ class Search extends Component {
resultItems: [],
isLoading: false,
isResultGotten: true,
hasMore: res.data.has_more,
});
}
}).catch(error => {
@@ -450,7 +442,6 @@ class Search extends Component {
};
onNormalSearch = (queryData, cancelToken, page) => {
this.updateSearchPageURL(queryData);
queryData['per_page'] = PER_PAGE;
queryData['page'] = page;
seafileAPI.searchFiles(queryData, cancelToken).then(res => {
@@ -461,7 +452,6 @@ class Search extends Component {
isResultGotten: true,
isLoading: false,
page: page + 1,
hasMore: res.data.has_more,
});
return;
}
@@ -470,7 +460,6 @@ class Search extends Component {
resultItems: [],
isLoading: false,
isResultGotten: true,
hasMore: res.data.has_more,
});
}).catch(error => {
/* eslint-disable */
@@ -479,14 +468,6 @@ class Search extends Component {
});
};
updateSearchPageURL(queryData) {
let params = '';
for (let key in queryData) {
params += key + '=' + encodeURIComponent(queryData[key]) + '&';
}
this.setState({searchPageUrl: `${this.baseSearchPageURL}?${params.substring(0, params.length - 1)}`});
}
formatResultItems(data) {
let items = [];
for (let i = 0; i < data.length; i++) {
@@ -681,7 +662,7 @@ class Search extends Component {
};
renderResults = (resultItems, isVisited) => {
const { highlightIndex, hasMore, searchPageUrl } = this.state;
const { highlightIndex } = this.state;
const results = (
<>
@@ -700,12 +681,6 @@ class Search extends Component {
);
})}
</ul>
{(!this.props.isPublic && hasMore) &&
<div className="more-search-result mb-1 pl-2 d-flex align-items-center">
<Icon symbol="more-level" className="more-search-result-icon" />
<a href={searchPageUrl} className="more-search-result-text ml-1">{gettext('More')}</a>
</div>
}
</>
);

View File

@@ -451,18 +451,3 @@
font-size: 14px;
margin-bottom: 10px;
}
.search-result-container .more-search-result {
margin-top: -4px;
height: 40px;
}
.search-result-container .more-search-result-icon {
width: 1rem;
color: #ec8000;
margin: 0 10px;
}
.search-result-container .more-search-result-text {
font-size: .875rem;
}

View File

@@ -1,291 +0,0 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import MediaQuery from 'react-responsive';
import dayjs from 'dayjs';
import { Button, Col, Collapse, CustomInput, FormGroup, Input, Label, Row, InputGroupAddon, InputGroup } from 'reactstrap';
import { gettext } from '../../utils/constants';
import DateTimePicker from '../../components/date-and-time-picker';
const { repo_name, search_repo } = window.search.pageOptions;
class AdvancedSearch extends React.Component {
constructor(props) {
super(props);
}
getFileTypesList = (fileTypes) => {
const fileTypeItems = [gettext('Text'), gettext('Document'), gettext('Image'), gettext('Video'), gettext('Audio'), 'PDF', 'Markdown'];
let ftype = [];
for (let i = 0, len = fileTypes.length; i < len; i++) {
if (fileTypes[i]) {
ftype.push(fileTypeItems[i]);
}
}
return ftype;
};
disabledStartDate = (startValue) => {
if (!startValue) {
return false;
}
const isAfterToday = startValue.isAfter(dayjs(), 'day');
const { time_to } = this.props.stateAndValues;
const endValue = time_to;
if (!endValue) {
return isAfterToday;
}
return startValue.isAfter(endValue) || isAfterToday;
};
disabledEndDate = (endValue) => {
if (!endValue) {
return false;
}
const isAfterToday = endValue.isAfter(dayjs(), 'day');
const { time_from } = this.props.stateAndValues;
const startValue = time_from;
if (!startValue) {
return isAfterToday;
}
return endValue.isBefore(startValue) || isAfterToday;
};
render() {
const { stateAndValues } = this.props;
const { errorDateMsg, errorSizeMsg } = stateAndValues;
if (stateAndValues.isShowSearchFilter) {
const { size_from, size_to, time_from, time_to, search_repo, fileTypeItemsStatus } = stateAndValues;
const fileTypes = this.getFileTypesList(fileTypeItemsStatus);
const typesLength = fileTypes.length;
return (
<div className="search-filters">
{search_repo && <span className="mr-4">{gettext('Libraries')}{': '}{search_repo == 'all' ? gettext('All') : repo_name}</span>}
{typesLength > 0 &&
<span className="mr-4">{gettext('File Types')}{': '}
{fileTypes.map((type, index) => {
return <span key={index}>{type}{index !== (typesLength - 1) && ','}{' '}</span>;
})}
</span>
}
{(time_from && time_to) &&
<span className="mr-4">{gettext('Last Update')}{': '}{time_from.format('YYYY-MM-DD')}{' '}{gettext('to')}{' '}{time_to.format('YYYY-MM-DD')}</span>
}
{(size_from && size_to) &&
<span className="mr-4">{gettext('Size')}{': '}{size_from}{'MB - '}{size_to}{'MB'}</span>
}
</div>
);
}
else {
return (
<div className="advanced-search">
<Collapse isOpen={stateAndValues.isCollapseOpen}>
{search_repo !== 'all' &&
<div className='search-repo search-catalog'>
<Row>
<Col md="2" lg="2">{gettext('Libraries')}{': '}</Col>
<Col md="4" lg="4">
<FormGroup check>
<Label check>
<Input
type="radio" name="repo"
checked={stateAndValues.isAllRepoCheck}
onChange={() => this.props.handlerRepo(true)}
/>{gettext('In all libraries')}
</Label>
</FormGroup>
</Col>
<Col md="4" lg="4">
<FormGroup check>
<Label check>
<Input
type="radio" name="repo"
checked={!stateAndValues.isAllRepoCheck}
onChange={() => this.props.handlerRepo(false)}
/>{repo_name}
</Label>
</FormGroup>
</Col>
</Row>
</div>
}
<div className='search-file-types search-catalog'>
<Row>
<Col md="2" lg="2">{gettext('File Types')}{': '}</Col>
<Col md="4" lg="4">
<FormGroup check>
<Label check>
<Input
type="radio" name="types"
checked={!stateAndValues.isFileTypeCollapseOpen}
onChange={this.props.closeFileTypeCollapse}
/>{gettext('All file types')}
</Label>
</FormGroup>
</Col>
<Col md="4" lg="4">
<FormGroup check>
<Label check>
<Input
type="radio" name="types"
checked={stateAndValues.isFileTypeCollapseOpen}
onChange={this.props.openFileTypeCollapse}
/>{gettext('Custom file types')}
</Label>
</FormGroup>
</Col>
</Row>
<Row>
<Col md="2" lg="2"></Col>
<Col md="10" lg="10">
<Collapse isOpen={stateAndValues.isFileTypeCollapseOpen}>
<FormGroup className="search-file-types-form">
<Fragment>
<CustomInput
type="checkbox" id="checkTextFiles" label={gettext('Text files')} inline
onChange={() => this.props.handlerFileTypes(0)}
checked={stateAndValues.fileTypeItemsStatus[0]}/>
<CustomInput
type="checkbox" id="checkDocuments" label={gettext('Documents')} inline
onChange={() => this.props.handlerFileTypes(1)}
checked={stateAndValues.fileTypeItemsStatus[1]}/>
<CustomInput
type="checkbox" id="checkImages" label={gettext('Images')} inline
onChange={() => this.props.handlerFileTypes(2)}
checked={stateAndValues.fileTypeItemsStatus[2]}/>
<CustomInput
type="checkbox" id="checkVideo" label={gettext('Video')} inline
onChange={() => this.props.handlerFileTypes(3)}
checked={stateAndValues.fileTypeItemsStatus[3]}/>
<CustomInput
type="checkbox" id="checkAudio" label={gettext('Audio')} inline
onChange={() => this.props.handlerFileTypes(4)}
checked={stateAndValues.fileTypeItemsStatus[4]}/>
<CustomInput
type="checkbox" id="checkPdf" label="PDF" inline
onChange={() => this.props.handlerFileTypes(5)}
checked={stateAndValues.fileTypeItemsStatus[5]}/>
<CustomInput
type="checkbox" id="checkMarkdown" label="Markdown" inline
onChange={() => this.props.handlerFileTypes(6)}
checked={stateAndValues.fileTypeItemsStatus[6]}/>
</Fragment>
</FormGroup>
<input
type="text"
className="form-control search-input"
name="query"
autoComplete="off"
placeholder={gettext('Input file extensions here, separate with \',\'')}
onChange={this.props.handlerFileTypesInput}
value={stateAndValues.input_fexts}
onKeyDown={this.props.handleKeyDown}
/>
</Collapse>
</Col>
</Row>
</div>
<div className='search-date search-catalog'>
<Row>
<Col md="2" lg="2" className="mt-2">{gettext('Last Update')}{': '}</Col>
<Col md="4" lg="4" sm="4" xs="5" className="position-relative">
<DateTimePicker
inputWidth={'100%'}
disabledDate={this.disabledStartDate}
value={stateAndValues.time_from}
onChange={this.props.handleTimeFromInput}
showHourAndMinute={false}
/>
<span className="select-data-icon"><i className="sf3-font sf3-font-calendar-alt"></i></span>
</Col>
<div className="mt-2">-</div>
<Col md="4" lg="4" sm="4" xs="5" className="position-relative">
<DateTimePicker
inputWidth={'100%'}
disabledDate={this.disabledEndDate}
value={stateAndValues.time_to}
onChange={this.props.handleTimeToInput}
showHourAndMinute={false}
/>
<span className="select-data-icon"><i className="sf3-font sf3-font-calendar-alt"></i></span>
</Col>
</Row>
{errorDateMsg && <Row><Col md="2" lg="2"></Col><Col md="8" className="error mt-2">{errorDateMsg}</Col></Row>}
</div>
<div className='search-size search-catalog'>
<Row>
<Col md="2" lg="2" className="mt-2">{gettext('Size')}{': '}</Col>
<Col md="4" lg="4" sm="4" xs="5">
<FormGroup>
<InputGroup>
<Input
type="tel" name="size_from"
onKeyDown={this.props.handleKeyDown}
onChange={this.props.handleSizeFromInput}
value={stateAndValues.size_from}
/>
<InputGroupAddon addonType="append">{'MB'}</InputGroupAddon>
</InputGroup>
</FormGroup>
<MediaQuery query="(min-width: 768px)">
{errorSizeMsg && <div className="error mb-4">{errorSizeMsg}</div>}
<Button color="primary" onClick={this.props.handleSubmit}>{gettext('Submit')}</Button>
<Button className="ml-2" onClick={this.props.handleReset}>{gettext('Reset')}</Button>
</MediaQuery>
</Col>
<div className="mt-2">-</div>
<Col md="4" lg="4" sm="4" xs="5">
<FormGroup>
<InputGroup>
<Input
type="tel" name="size_to"
onKeyDown={this.props.handleKeyDown}
onChange={this.props.handleSizeToInput}
value={stateAndValues.size_to}
/>
<InputGroupAddon addonType="append">{'MB'}</InputGroupAddon>
</InputGroup>
</FormGroup>
</Col>
</Row>
</div>
<MediaQuery query="(max-width: 767.8px)">
{errorSizeMsg && <div className="error mb-4">{errorSizeMsg}</div>}
<Button color="primary" onClick={this.props.handleSubmit}>{gettext('Submit')}</Button>
<Button className="ml-2" onClick={this.props.handleReset}>{gettext('Reset')}</Button>
</MediaQuery>
</Collapse>
</div>
);
}
}
}
const advancedSearchPropTypes = {
openFileTypeCollapse: PropTypes.func.isRequired,
closeFileTypeCollapse: PropTypes.func.isRequired,
handlerFileTypes: PropTypes.func.isRequired,
handlerFileTypesInput: PropTypes.func.isRequired,
handleSubmit: PropTypes.func.isRequired,
handleReset: PropTypes.func.isRequired,
handlerRepo: PropTypes.func.isRequired,
handleKeyDown: PropTypes.func.isRequired,
handleTimeFromInput: PropTypes.func.isRequired,
handleTimeToInput: PropTypes.func.isRequired,
handleSizeFromInput: PropTypes.func.isRequired,
handleSizeToInput: PropTypes.func.isRequired,
stateAndValues: PropTypes.object.isRequired,
};
AdvancedSearch.propTypes = advancedSearchPropTypes;
export default AdvancedSearch;

View File

@@ -1,42 +0,0 @@
import React from 'react';
import ReactDom from 'react-dom';
import CommonToolbar from '../../components/toolbar/common-toolbar';
import Logo from '../../components/logo';
import SearchViewPanel from './main-panel';
import { siteRoot } from '../../utils/constants';
import { Utils } from '../../utils/utils';
import '../../css/layout.css';
import '../../css/toolbar.css';
import '../../css/search.css';
class SearchView extends React.Component {
constructor(props) {
super(props);
}
onSearchedClick = (selectedItem) => {
let url = selectedItem.is_dir ?
siteRoot + 'library/' + selectedItem.repo_id + '/' + selectedItem.repo_name + selectedItem.path :
siteRoot + 'lib/' + selectedItem.repo_id + '/file' + Utils.encodePath(selectedItem.path);
let newWindow = window.open('about:blank');
newWindow.location.href = url;
};
render() {
return (
<div className="w-100 h-100">
<div className="main-panel-north border-left-show">
<Logo/>
<CommonToolbar onSearchedClick={this.onSearchedClick}/>
</div>
<div className="main-panel-south">
<SearchViewPanel/>
</div>
</div>
);
}
}
ReactDom.render(<SearchView />, document.getElementById('wrapper'));

View File

@@ -1,351 +0,0 @@
import React from 'react';
import deepCopy from 'deep-copy';
import { gettext } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
import SearchResults from './search-results';
import AdvancedSearch from './advanced-search';
import toaster from '../../components/toast';
import Loading from '../../components/loading';
import '../../css/search.css';
const { q, search_repo, search_ftypes } = window.search.pageOptions;
class SearchViewPanel extends React.Component {
constructor(props) {
super(props);
this.stateHistory = null;
this.state = {
isCollapseOpen: search_repo !== 'all',
isFileTypeCollapseOpen: false,
isResultGot: false,
isLoading: true,
isAllRepoCheck: search_repo === 'all',
isShowSearchFilter: false,
// advanced search index
q: q.trim(),
search_repo: search_repo,
search_ftypes: search_ftypes,
fileTypeItemsStatus: [false, false, false, false, false, false, false],
input_fexts: '',
time_from: null,
time_to: null,
size_from: '',
size_to: '',
// search result
hasMore: false,
resultItems: [],
page: 1,
per_page: 20,
errorMsg: '',
errorDateMsg: '',
errorSizeMsg: '',
};
}
getSearchResults(params) {
this.setState({
isLoading: true,
isResultGot: false,
});
this.onNormalSearch(params);
}
onNormalSearch = (params) => {
const stateHistory = deepCopy(this.state);
seafileAPI.searchFiles(params, null).then(res => {
const { results, has_more, total } = res.data;
this.setState({
isLoading: false,
isResultGot: true,
resultItems: results,
hasMore: has_more,
total: total,
page: params.page,
isShowSearchFilter: true,
});
this.stateHistory = stateHistory;
this.stateHistory.resultItems = results;
this.stateHistory.hasMore = has_more;
this.stateHistory.page = params.page;
}).catch((error) => {
this.setState({ isLoading: false });
if (error.response) {
toaster.danger(error.response.data.detail || error.response.data.error_msg || gettext('Error'), { duration: 3 });
} else {
toaster.danger(gettext('Please check the network.'), { duration: 3 });
}
});
};
handleSearchParams = (page) => {
let params = { q: this.state.q.trim(), page: page };
const ftype = this.getFileTypesList();
if (this.state.search_repo) {params.search_repo = this.state.search_repo;}
if (this.state.search_ftypes) {params.search_ftypes = this.state.search_ftypes;}
if (this.state.per_page) {params.per_page = this.state.per_page;}
if (this.state.input_fexts) {params.input_fexts = this.state.input_fexts;}
const { time_from, time_to } = this.state;
if (time_from) {params.time_from = parseInt(time_from.valueOf() / 1000);}
if (time_to) {params.time_to = parseInt(time_to.valueOf() / 1000);}
if (this.state.size_from) {params.size_from = this.state.size_from * 1000 * 1000;}
if (this.state.size_to) {params.size_to = this.state.size_to * 1000 * 1000;}
if (ftype.length !== 0) {params.ftype = ftype;}
return params;
};
handleSubmit = () => {
if (this.compareNumber(this.state.size_from, this.state.size_to)) {
this.setState({ errorSizeMsg: gettext('Invalid file size range.') });
return;
}
if (this.getValueLength(this.state.q.trim()) < 3) {
if (this.state.q.trim().length === 0) {
this.setState({ errorMsg: gettext('It is required.') });
} else {
this.setState({ errorMsg: gettext('Required at least three letters.') });
}
if (this.state.isLoading) {
this.setState({ isLoading: false });
}
} else {
const params = this.handleSearchParams(1);
this.getSearchResults(params);
}
if (this.state.isCollapseOpen) this.setState({ isCollapseOpen: false });
};
compareNumber = (num1, num2) => {
if (!num1 || !num2) return false;
// eslint-disable-next-line
if (parseInt(num1.replace(/\-/g, '')) >= parseInt(num2.replace(/\-/g, ''))) {
return true;
} else {
return false;
}
};
showSearchFilter = () => {
this.setState({ isShowSearchFilter: true });
};
hideSearchFilter = () => {
this.setState({ isShowSearchFilter: false });
};
handleReset = () => {
this.setState({
q: q.trim(),
search_repo: search_repo,
search_ftypes: search_ftypes,
fileTypeItemsStatus: [false, false, false, false, false, false, false],
input_fexts: '',
time_from: null,
time_to: null,
size_from: '',
size_to: '',
errorMsg: '',
errorDateMsg: '',
errorSizeMsg: '',
});
};
handlePrevious = (e) => {
e.preventDefault();
if (this.stateHistory && this.state.page > 1) {
this.setState(this.stateHistory, () => {
const params = this.handleSearchParams(this.state.page - 1);
this.getSearchResults(params);
});
} else {
toaster.danger(gettext('Error'), { duration: 3 });
}
};
handleNext = (e) => {
e.preventDefault();
if (this.stateHistory && this.state.hasMore) {
this.setState(this.stateHistory, () => {
const params = this.handleSearchParams(this.state.page + 1);
this.getSearchResults(params);
});
} else {
toaster.danger(gettext('Error'), { duration: 3 });
}
};
getValueLength(str) {
let code = 0;
let len = 0;
for (let i = 0, length = str.length; i < length; i++) {
code = str.charCodeAt(i);
if (code === 10) { // solve enter problem
len += 2;
} else if (code < 0x007f) {
len += 1;
} else if (code >= 0x0080 && code <= 0x07ff) {
len += 2;
} else if (code >= 0x0800 && code <= 0xffff) {
len += 3;
}
}
return len;
}
toggleCollapse = () => {
this.setState({ isCollapseOpen: !this.state.isCollapseOpen });
this.hideSearchFilter();
};
openFileTypeCollapse = () => {
this.setState({
isFileTypeCollapseOpen: true,
search_ftypes: 'custom',
});
};
closeFileTypeCollapse = () => {
this.setState({
isFileTypeCollapseOpen: false,
fileTypeItemsStatus: Array(7).fill(false),
search_ftypes: 'all',
input_fexts: '',
});
};
handleSearchInput = (event) => {
this.setState({ q: event.target.value });
if (this.state.errorMsg) this.setState({ errorMsg: '' });
if (this.state.errorSizeMsg) this.setState({ errorSizeMsg: '' });
if (this.state.errorDateMsg) this.setState({ errorDateMsg: '' });
};
handleKeyDown = (e) => {
if (e.keyCode === 13) {
e.preventDefault();
this.handleSubmit();
}
};
handlerRepo = (isAll) => {
if (isAll) {
this.setState({
isAllRepoCheck: true,
search_repo: 'all',
});
} else {
this.setState({
isAllRepoCheck: false,
search_repo: search_repo !== 'all' ? search_repo : '',
});
}
};
handlerFileTypes = (i) => {
let newFileTypeItemsStatus = this.state.fileTypeItemsStatus;
newFileTypeItemsStatus[i] = !this.state.fileTypeItemsStatus[i];
this.setState({ fileTypeItemsStatus: newFileTypeItemsStatus });
};
getFileTypesList = () => {
const fileTypeItems = ['Text', 'Document', 'Image', 'Video', 'Audio', 'PDF', 'Markdown'];
let ftype = [];
for (let i = 0, len = this.state.fileTypeItemsStatus.length; i < len; i++) {
if (this.state.fileTypeItemsStatus[i]) {
ftype.push(fileTypeItems[i]);
}
}
return ftype;
};
handlerFileTypesInput = (event) => {
this.setState({ input_fexts: event.target.value.trim() });
};
handleTimeFromInput = (value) => {
// set the time to be '00:00:00'
this.setState({ time_from: value ? value.hours(0).minutes(0).seconds(0) : value });
if (this.state.errorDateMsg) this.setState({ errorDateMsg: '' });
};
handleTimeToInput = (value) => {
// set the time to be '23:59:59'
this.setState({ time_to: value ? value.hours(23).minutes(59).seconds(59) : value });
if (this.state.errorDateMsg) this.setState({ errorDateMsg: '' });
};
handleSizeFromInput = (event) => {
this.setState({ size_from: event.target.value >= 0 ? event.target.value : 0 });
if (this.state.errorSizeMsg) this.setState({ errorSizeMsg: '' });
};
handleSizeToInput = (event) => {
this.setState({ size_to: event.target.value >= 0 ? event.target.value : 0 });
if (this.state.errorSizeMsg) this.setState({ errorSizeMsg: '' });
};
componentDidMount() {
if (this.state.q) {
this.handleSubmit();
} else {
this.setState({ isLoading: false });
}
}
render() {
let { isCollapseOpen } = this.state;
return (
<div className="search-page">
<div className="search-page-container">
<div className="input-icon align-items-center d-flex">
<input
type="text"
className="form-control search-input"
name="query"
autoComplete="off"
value={this.state.q}
placeholder={gettext('Search files')}
onChange={this.handleSearchInput}
onKeyDown={this.handleKeyDown}
/>
<i className="search-icon-right input-icon-addon sf3-font sf3-font-search" onClick={this.handleSubmit}></i>
<i className={`action-icon sf3-font sf3-font-angles-${isCollapseOpen ? 'up' : 'down'}`} onClick={this.toggleCollapse}></i>
</div>
{this.state.errorMsg && <div className="error">{this.state.errorMsg}</div>}
<AdvancedSearch
openFileTypeCollapse={this.openFileTypeCollapse}
closeFileTypeCollapse={this.closeFileTypeCollapse}
handlerFileTypes={this.handlerFileTypes}
handlerFileTypesInput={this.handlerFileTypesInput}
handleSubmit={this.handleSubmit}
handleReset={this.handleReset}
handlerRepo={this.handlerRepo}
handleKeyDown={this.handleKeyDown}
handleTimeFromInput={this.handleTimeFromInput}
handleTimeToInput={this.handleTimeToInput}
handleSizeFromInput={this.handleSizeFromInput}
handleSizeToInput={this.handleSizeToInput}
stateAndValues={this.state}
/>
</div>
{this.state.isLoading && <Loading/>}
{(!this.state.isLoading && this.state.isResultGot) &&
<SearchResults
resultItems={this.state.resultItems}
total={this.state.total}
/>
}
{(!this.state.isLoading && this.state.isResultGot) &&
<div className="paginator">
{this.state.page !== 1 && <a href="#" onClick={this.handlePrevious}>{gettext('Previous')}</a>}
{(this.state.page !== 1 && this.state.hasMore) && <span> | </span>}
{this.state.hasMore && <a href="#" onClick={this.handleNext}>{gettext('Next')}</a>}
</div>
}
</div>
);
}
}
export default SearchViewPanel;

View File

@@ -1,91 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import dayjs from 'dayjs';
import { Utils } from '../../utils/utils';
import { siteRoot, gettext } from '../../utils/constants';
class ResultsItem extends React.Component {
constructor(props) {
super(props);
}
handlerFileURL = (item) => {
return item.is_dir ? siteRoot + 'library/' + item.repo_id + '/' + item.repo_name + item.fullpath :
siteRoot + 'lib/' + item.repo_id + '/file' + Utils.encodePath(item.fullpath);
};
handlerParentDirPath = (item) => {
let index = item.is_dir ? item.fullpath.length - item.name.length - 1 : item.fullpath.length - item.name.length;
return item.fullpath.substring(0, index);
};
handlerParentDirURL = (item) => {
return siteRoot + 'library/' + item.repo_id + '/' + item.repo_name + this.handlerParentDirPath(item);
};
render() {
let item = this.props.item;
let linkContent = decodeURI(item.fullpath).substring(1);
let folderIconUrl = linkContent ? Utils.getFolderIconUrl(false, 192) : Utils.getDefaultLibIconUrl();
let fileIconUrl = item.is_dir ? folderIconUrl : Utils.getFileIconUrl(item.name);
if (item.thumbnail_url !== '') {
fileIconUrl = item.thumbnail_url;
}
return (
<li className="search-result-item">
<img className={linkContent ? 'item-img' : 'lib-item-img'} src={fileIconUrl} alt=""/>
<div className="item-content">
<div className="item-name ellipsis">
<a href={this.handlerFileURL(item)} target="_blank" title={item.name} rel="noreferrer">{item.name}</a>
</div>
<div className="item-link ellipsis">
<a href={this.handlerParentDirURL(item)} target="_blank" rel="noreferrer" >{item.repo_name}{this.handlerParentDirPath(item)}</a>
</div>
<div className="item-link ellipsis">
{Utils.bytesToSize(item.size) + ' ' + dayjs(item.last_modified * 1000).format('YYYY-MM-DD')}
</div>
<div className="item-text ellipsis" dangerouslySetInnerHTML={{ __html: item.content_highlight }}></div>
</div>
</li>
);
}
}
const resultsItemPropTypes = {
item: PropTypes.object.isRequired,
};
ResultsItem.propTypes = resultsItemPropTypes;
class SearchResults extends React.Component {
constructor(props) {
super(props);
}
render() {
const { resultItems, total } = this.props;
return (
<div className="search-result-container position-static">
<p className="tip">{total > 0 ? (total + ' ' + (total === 1 ? gettext('result') : gettext('results'))) : gettext('No result')}</p>
<ul className="search-result-list">
{resultItems.map((item, index) => {
return <ResultsItem key={index} item={item}/>;
})}
</ul>
</div>
);
}
}
const searchResultsPropTypes = {
resultItems: PropTypes.array.isRequired,
total: PropTypes.number.isRequired
};
SearchResults.propTypes = searchResultsPropTypes;
export default SearchResults;