@lyra/default-layout
Version:
The default layout components for Lyra
329 lines (273 loc) • 10.4 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _union2 = require('lodash/union');
var _union3 = _interopRequireDefault(_union2);
var _flatten2 = require('lodash/flatten');
var _flatten3 = _interopRequireDefault(_flatten2);
var _compact2 = require('lodash/compact');
var _compact3 = _interopRequireDefault(_compact2);
var _flow2 = require('lodash/flow');
var _flow3 = _interopRequireDefault(_flow2);
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _schema = require('part:@lyra/base/schema?');
var _schema2 = _interopRequireDefault(_schema);
var _client = require('part:@lyra/base/client?');
var _client2 = _interopRequireDefault(_client);
var _preview = require('part:@lyra/base/preview?');
var _preview2 = _interopRequireDefault(_preview);
var _draftUtils = require('part:@lyra/base/util/draft-utils');
var _rxjs = require('rxjs');
var _operators = require('rxjs/operators');
var _router = require('part:@lyra/base/router');
var _searchIcon = require('part:@lyra/base/search-icon');
var _searchIcon2 = _interopRequireDefault(_searchIcon);
var _spinner = require('part:@lyra/components/loading/spinner');
var _spinner2 = _interopRequireDefault(_spinner);
var _reactClickOutside = require('react-click-outside');
var _reactClickOutside2 = _interopRequireDefault(_reactClickOutside);
var _reactInk = require('react-ink');
var _reactInk2 = _interopRequireDefault(_reactInk);
var _Search = require('./styles/Search.css');
var _Search2 = _interopRequireDefault(_Search);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function isParentOf(possibleParent, possibleChild) {
let current = possibleChild;
while (current) {
if (current === possibleParent) {
return true;
}
current = current.parentNode;
}
return false;
}
// Removes published documents that also has a draft
function removeDupes(documents) {
const drafts = documents.map(doc => doc._id).filter(_draftUtils.isDraftId);
return documents.filter(doc => {
const draftId = (0, _draftUtils.getDraftId)(doc._id);
const publishedId = (0, _draftUtils.getPublishedId)(doc._id);
const hasDraft = drafts.includes(draftId);
const isPublished = doc._id === publishedId;
return isPublished ? !hasDraft : true;
});
}
const combineFields = (0, _flow3.default)([_flatten3.default, _union3.default, _compact3.default]);
function search(query) {
if (!_client2.default) {
throw new Error('Lyra client is missing');
}
const candidateTypes = _schema2.default.getTypeNames().filter(typeName => !typeName.startsWith('lyra.')).map(typeName => _schema2.default.get(typeName));
const terms = query.split(/\s+/).filter(Boolean);
const uniqueFields = combineFields(candidateTypes.map(type => type.__unstable_searchFields));
const constraints = terms.map(term => uniqueFields.map(field => `${field} match '${term}*'`));
const constraintString = constraints.map(constraint => `(${constraint.join(' || ')})`).join(' && ');
return _client2.default.observable.fetch(`*[${constraintString}][0...10]`);
}
exports.default = (0, _reactClickOutside2.default)(class Search extends _react2.default.Component {
constructor(...args) {
var _temp;
return _temp = super(...args), this.input$ = new _rxjs.Subject(), this.componentWillUnmount$ = new _rxjs.Subject(), this.state = {
isOpen: false,
hits: [],
activeIndex: -1,
inputValue: ''
}, this.handleInputChange = event => {
this.input$.next(event);
this.open();
}, this.handleKeyPress = event => {
this.inputElement.focus();
}, this.handleKeyDown = event => {
if (event.key === 'Backspace') {
this.inputElement.focus();
}
if (event.key === 'Escape') {
this.close();
}
if (event.key === 'Enter') {
this.listElement.querySelector(`[data-hit-index="${this.state.activeIndex}"]`).click();
}
var _state = this.state;
const isOpen = _state.isOpen,
hits = _state.hits,
activeIndex = _state.activeIndex;
const isArrowKey = ['ArrowUp', 'ArrowDown'].includes(event.key);
if (!isOpen && isArrowKey) {
this.open();
return;
}
const lastIndex = hits.length - 1;
if (isArrowKey) {
event.preventDefault();
let nextIndex = activeIndex + (event.key === 'ArrowUp' ? -1 : 1);
if (nextIndex < 0) {
nextIndex = lastIndex;
}
if (nextIndex > lastIndex) {
nextIndex = 0;
}
this.setState({ activeIndex: nextIndex });
}
}, this.handleClickOutside = el => {
if (this.state.isOpen) {
this.close();
}
}, this.handleHitClick = el => {
this.close();
}, this.handleInputClick = el => {
this.open();
}, this.handleFocus = el => {
this.open();
}, this.handleBlur = el => {
if (!isParentOf(this.rootElement, el.relatedTarget)) {
this.close();
}
}, this.setInput = el => {
this.inputElement = el;
}, this.setListElement = el => {
this.listElement = el;
}, this.setRootElement = el => {
this.rootElement = el;
}, this.handleHitMouseDown = ev => {
this.setState({
activeIndex: Number(ev.currentTarget.getAttribute('data-hit-index'))
});
}, this.handleHitMouseUp = () => {
this.inputElement.focus();
}, this.renderItem = (item, index) => {
const type = _schema2.default.get(item._type);
const activeIndex = this.state.activeIndex;
return _react2.default.createElement(
_router.IntentLink,
{
intent: 'edit',
params: { id: item._id, type: type.name },
className: activeIndex === index ? _Search2.default.activeLink : _Search2.default.link,
'data-hit-index': index,
onMouseDown: this.handleHitMouseDown,
onMouseUp: this.handleHitMouseUp,
onClick: this.handleHitClick,
tabIndex: -1
},
_react2.default.createElement(
'div',
{ className: _Search2.default.itemType },
type.title
),
_react2.default.createElement(_preview2.default, { value: item, layout: 'default', type: type }),
_react2.default.createElement(_reactInk2.default, { duration: 200, opacity: 0.1, radius: 200 })
);
}, _temp;
}
componentDidMount() {
this.input$.asObservable().pipe((0, _operators.map)(event => event.target.value), (0, _operators.tap)(inputValue => this.setState({ inputValue })), (0, _operators.takeUntil)(this.componentWillUnmount$.asObservable())).subscribe();
this.input$.asObservable().pipe((0, _operators.map)(event => event.target.value), (0, _operators.debounceTime)(100), (0, _operators.tap)(() => {
this.setState({
isSearching: true
});
}), (0, _operators.switchMap)(search),
// we need this filtering because the search may return documents of types not in schema
(0, _operators.map)(hits => hits.filter(hit => _schema2.default.has(hit._type))), (0, _operators.map)(removeDupes), (0, _operators.tap)(hits => {
this.setState({
isSearching: false,
hits: hits
});
}), (0, _operators.takeUntil)(this.componentWillUnmount$.asObservable())).subscribe();
}
componentWillUnmount() {
this.componentWillUnmount$.next();
this.componentWillUnmount$.complete();
}
close() {
this.setOpen(false);
}
open() {
this.setOpen(true);
}
setOpen(isOpen) {
this.setState({ isOpen });
}
componentDidUpdate(prevProps, prevState) {
if (!prevState.isOpen && this.state.isOpen) {
this.inputElement.select();
}
}
render() {
var _state2 = this.state;
const isSearching = _state2.isSearching,
hits = _state2.hits,
isOpen = _state2.isOpen,
inputValue = _state2.inputValue;
return _react2.default.createElement(
'div',
{ className: _Search2.default.root, ref: this.setRootElement },
_react2.default.createElement(
'div',
{ className: _Search2.default.inner },
_react2.default.createElement(
'label',
{ className: _Search2.default.label },
_react2.default.createElement(
'i',
{ className: _Search2.default.icon, 'aria-hidden': true },
_react2.default.createElement(_searchIcon2.default, null)
)
),
_react2.default.createElement('input', {
className: _Search2.default.input,
type: 'search',
value: isOpen ? inputValue : '',
onInput: this.handleInputChange,
onBlur: this.handleBlur,
onClick: this.handleInputClick,
onFocus: this.handleFocus,
onKeyDown: this.handleKeyDown,
placeholder: 'Search\u2026',
ref: this.setInput
})
),
isOpen && (inputValue || isSearching || hits > 0) && _react2.default.createElement(
'div',
{ className: _Search2.default.result },
_react2.default.createElement(
'div',
{ className: _Search2.default.spinner },
isSearching && _react2.default.createElement(_spinner2.default, null)
),
inputValue && !isSearching && (!hits || hits.length === 0) && _react2.default.createElement(
'div',
{ className: _Search2.default.noHits },
'Could not find',
' ',
_react2.default.createElement(
'strong',
null,
'\u201C',
inputValue,
'\u201D'
)
),
!isSearching && hits && hits.length > 0 && _react2.default.createElement(
'div',
{ className: _Search2.default.listContainer },
_react2.default.createElement(
'ul',
{
className: _Search2.default.hits,
onKeyDown: this.handleKeyDown,
onKeyPress: this.handleKeyPress,
ref: this.setListElement
},
hits.map((hit, index) => _react2.default.createElement(
'li',
{ key: hit._id, className: _Search2.default.hit },
this.renderItem(hit, index)
))
)
)
)
);
}
});