* Execute Reading List searches in Elasticsearch * add additional readinglist front tests
269 lines
7.3 KiB
JavaScript
269 lines
7.3 KiB
JavaScript
import { h, Component } from 'preact';
|
|
import { PropTypes } from 'preact-compat';
|
|
import debounceAction from '../src/utils/debounceAction';
|
|
|
|
import {
|
|
defaultState,
|
|
loadNextPage,
|
|
onSearchBoxType,
|
|
performInitialSearch,
|
|
search,
|
|
toggleTag,
|
|
clearSelectedTags,
|
|
} from '../searchableItemList/searchableItemList';
|
|
import { ItemListItem } from '../src/components/ItemList/ItemListItem';
|
|
import { ItemListItemArchiveButton } from '../src/components/ItemList/ItemListItemArchiveButton';
|
|
import { ItemListLoadMoreButton } from '../src/components/ItemList/ItemListLoadMoreButton';
|
|
import { ItemListTags } from '../src/components/ItemList/ItemListTags';
|
|
|
|
const STATUS_VIEW_VALID = 'valid,confirmed';
|
|
const STATUS_VIEW_ARCHIVED = 'archived';
|
|
const READING_LIST_ARCHIVE_PATH = '/readinglist/archive';
|
|
const READING_LIST_PATH = '/readinglist';
|
|
|
|
const FilterText = ({ selectedTags, query, value }) => {
|
|
return (
|
|
<h1>
|
|
{selectedTags.length === 0 && query.length === 0
|
|
? value
|
|
: 'Nothing with this filter 🤔'}
|
|
</h1>
|
|
);
|
|
};
|
|
|
|
export class ReadingList extends Component {
|
|
constructor(props) {
|
|
super(props);
|
|
|
|
const { availableTags, statusView } = this.props;
|
|
this.state = defaultState({ availableTags, archiving: false, statusView });
|
|
|
|
// bind and initialize all shared functions
|
|
this.onSearchBoxType = debounceAction(onSearchBoxType.bind(this), {
|
|
leading: true,
|
|
});
|
|
this.loadNextPage = loadNextPage.bind(this);
|
|
this.performInitialSearch = performInitialSearch.bind(this);
|
|
this.search = search.bind(this);
|
|
this.toggleTag = toggleTag.bind(this);
|
|
this.clearSelectedTags = clearSelectedTags.bind(this);
|
|
}
|
|
|
|
componentDidMount() {
|
|
const { statusView } = this.state;
|
|
|
|
this.performInitialSearch({
|
|
searchOptions: { status: `${statusView}` },
|
|
});
|
|
}
|
|
|
|
toggleStatusView = (event) => {
|
|
event.preventDefault();
|
|
|
|
const { query, selectedTags } = this.state;
|
|
|
|
const isStatusViewValid = this.statusViewValid();
|
|
const newStatusView = isStatusViewValid
|
|
? STATUS_VIEW_ARCHIVED
|
|
: STATUS_VIEW_VALID;
|
|
const newPath = isStatusViewValid
|
|
? READING_LIST_ARCHIVE_PATH
|
|
: READING_LIST_PATH;
|
|
|
|
// empty items so that changing the view will start from scratch
|
|
this.setState({ statusView: newStatusView, items: [] });
|
|
|
|
this.search(query, {
|
|
page: 0,
|
|
tags: selectedTags,
|
|
statusView: newStatusView,
|
|
});
|
|
|
|
// change path in the address bar
|
|
window.history.replaceState(null, null, newPath);
|
|
};
|
|
|
|
toggleArchiveStatus = (event, item) => {
|
|
event.preventDefault();
|
|
|
|
const { statusView, items, totalCount } = this.state;
|
|
window.fetch(`/reading_list_items/${item.id}`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'X-CSRF-Token': window.csrfToken,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ current_status: statusView }),
|
|
credentials: 'same-origin',
|
|
});
|
|
|
|
const t = this;
|
|
const newItems = items;
|
|
newItems.splice(newItems.indexOf(item), 1);
|
|
t.setState({
|
|
archiving: true,
|
|
items: newItems,
|
|
totalCount: totalCount - 1,
|
|
});
|
|
|
|
// hide the snackbar in a few moments
|
|
setTimeout(() => {
|
|
t.setState({ archiving: false });
|
|
}, 1000);
|
|
};
|
|
|
|
statusViewValid() {
|
|
const { statusView } = this.state;
|
|
return statusView === STATUS_VIEW_VALID;
|
|
}
|
|
|
|
renderEmptyItems() {
|
|
const { itemsLoaded, selectedTags, query } = this.state;
|
|
|
|
if (itemsLoaded && this.statusViewValid()) {
|
|
return (
|
|
<div className="items-empty">
|
|
<FilterText
|
|
selectedTags={selectedTags}
|
|
query={query}
|
|
value="Your Reading List is Lonely"
|
|
/>
|
|
<h3>
|
|
Hit the
|
|
<span className="highlight">SAVE</span>
|
|
or
|
|
<span className="highlight">
|
|
Bookmark
|
|
<span role="img" aria-label="Bookmark">
|
|
🔖
|
|
</span>
|
|
</span>
|
|
to start your Collection
|
|
</h3>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="items-empty">
|
|
<FilterText
|
|
selectedTags={selectedTags}
|
|
query={query}
|
|
value="Your Archive List is Lonely"
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
render() {
|
|
const {
|
|
items,
|
|
itemsLoaded,
|
|
totalCount,
|
|
availableTags,
|
|
selectedTags,
|
|
showLoadMoreButton,
|
|
archiving,
|
|
} = this.state;
|
|
|
|
const isStatusViewValid = this.statusViewValid();
|
|
|
|
const archiveButtonLabel = isStatusViewValid ? 'archive' : 'unarchive';
|
|
const itemsToRender = items.map((item) => {
|
|
return (
|
|
<ItemListItem item={item}>
|
|
<ItemListItemArchiveButton
|
|
text={archiveButtonLabel}
|
|
onClick={(e) => this.toggleArchiveStatus(e, item)}
|
|
/>
|
|
</ItemListItem>
|
|
);
|
|
});
|
|
|
|
const snackBar = archiving ? (
|
|
<div className="snackbar">
|
|
{isStatusViewValid ? 'Archiving...' : 'Unarchiving...'}
|
|
</div>
|
|
) : (
|
|
''
|
|
);
|
|
return (
|
|
<div className="home item-list">
|
|
<div className="side-bar">
|
|
<div className="widget filters">
|
|
<input
|
|
onKeyUp={this.onSearchBoxType}
|
|
placeHolder="search your list"
|
|
/>
|
|
<div className="filters-header">
|
|
<h4 className="filters-header-text">my tags</h4>
|
|
{Boolean(selectedTags.length) && (
|
|
<a
|
|
className="filters-header-action"
|
|
href={
|
|
isStatusViewValid
|
|
? READING_LIST_PATH
|
|
: READING_LIST_ARCHIVE_PATH
|
|
}
|
|
onClick={this.clearSelectedTags}
|
|
data-no-instant
|
|
>
|
|
clear all
|
|
</a>
|
|
)}
|
|
</div>
|
|
<ItemListTags
|
|
availableTags={availableTags}
|
|
selectedTags={selectedTags}
|
|
onClick={this.toggleTag}
|
|
/>
|
|
|
|
<div className="status-view-toggle">
|
|
<a
|
|
href={READING_LIST_ARCHIVE_PATH}
|
|
onClick={(e) => this.toggleStatusView(e)}
|
|
data-no-instant
|
|
>
|
|
{isStatusViewValid ? 'View Archive' : 'View Reading List'}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="items-container">
|
|
<div className={`results ${itemsLoaded ? 'results--loaded' : ''}`}>
|
|
<div className="results-header">
|
|
{isStatusViewValid ? 'Reading List' : 'Archive'}
|
|
{` (${totalCount > 0 ? totalCount : 'empty'})`}
|
|
</div>
|
|
<div>
|
|
{items.length > 0 ? itemsToRender : this.renderEmptyItems()}
|
|
</div>
|
|
</div>
|
|
|
|
<ItemListLoadMoreButton
|
|
show={showLoadMoreButton}
|
|
onClick={this.loadNextPage}
|
|
/>
|
|
</div>
|
|
|
|
{snackBar}
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
ReadingList.defaultProps = {
|
|
statusView: STATUS_VIEW_VALID,
|
|
};
|
|
|
|
ReadingList.propTypes = {
|
|
availableTags: PropTypes.arrayOf(PropTypes.string).isRequired,
|
|
statusView: PropTypes.oneOf([STATUS_VIEW_VALID, STATUS_VIEW_ARCHIVED]),
|
|
};
|
|
|
|
FilterText.propTypes = {
|
|
selectedTags: PropTypes.arrayOf(PropTypes.string).isRequired,
|
|
value: PropTypes.string.isRequired,
|
|
query: PropTypes.arrayOf(PropTypes.string).isRequired,
|
|
};
|