UNPKG

@lyra/default-layout

Version:

The default layout components for Lyra

329 lines (273 loc) 10.4 kB
'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) )) ) ) ) ); } });