Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
Size: Mime:
import * as tslib_1 from "tslib";
import { oneStorage } from '@skava/persistence';
import debounce from 'lodash/debounce';
import uniq from 'lodash/uniq';
import serialize from 'serialize-javascript';
import { isArray, isObj, isSafe, isString, isNonEmptyString } from 'exotic';
import { observable, action, computed } from 'xmobx/mobx';
import { oneRouter } from '@skava/router';
// --- container
import { ObservableContainer } from '@skava/packages/libraries/observable-container';
import { searchBaseUrl } from 'src/bootstrap/api/config';
// import { stateForSearch } from 'src/views/organisms/ConsentPopup'
import { searchSchema } from '@skava/packages/core/schemas';
import { defaultSuggestionList, fixture, wording } from './fixture';
import flattenSnap from './transform';
import { searchSuggestionBindings } from './bindings';
const SEARCH_SUGGESTIONS_RECENT_MAX = 5;
// for compat
const fromChildParentToPipes = (recommendation) => {
    const { child, parent } = recommendation;
    // currently always is required, or failure
    if (child !== '' && parent !== '') {
        return recommendation.parent + '|||' + recommendation.child;
    }
    if (parent) {
        return recommendation.parent;
    }
    return '';
};
const transformSearchRecommendations = (response) => {
    // coerce? gql always should always default
    const gql = response.data.suggestion;
    const list = gql.suggestions.map(fromChildParentToPipes);
    // @todo optimize & fix on gql side?
    const uniqList = uniq(list);
    return uniqList;
};
// @todo this makes no sense logically... isEmpty = isNonEmpty ?
// is already in exotic... @todo @@perf
const isNonEmptyArray = (x) => isArray(x) && x.length > 0;
const isEmptySearchResponse = (response) => {
    return isObj(response) && !isNonEmptyArray(response.data.suggestion.suggestions);
};
// @@perf dedupe
const isAlphanumeric = (x) => isString(x) && /[^A-Za-z0-9]+/.test(x);
const toSafeSuggestions = (search) => {
    const safeSearch = serialize(search);
    const suggestions = [wording.noSearchSuggestion];
    return suggestions;
};
/**
 * @tutorial https://jira.skava.net/confluence/display/TWL/Setting+Up+the+Search+Feature
 *
 * @example https://demostage.skavaone.com/skavastream/core/v5/skavastore/searchsuggestion?campaignId=2495&search=&offset=0&limit=10&locale=en_US&storeId=0
 *
 * @description
 * this is just a simple async connection on an observable class for all our requests
 * probably will get rid of it later & merge anything required into requests, easy to swap out
 * it's useful here to help us define use our dynamic params to do the calls
 * but it's pretty generic just from routing and stores
 * could all be extracted from localStorage
 */
class SearchApi extends ObservableContainer {
    constructor() {
        super(...arguments);
        /**
         * @example search: 'shoe'
         * @example {"type":"suggestion","properties":{"suggestion":[{"value":["empty something"]}]}}
         */
        this.getSearchSuggestions = async (search) => {
            if (search !== '') {
                const response = await searchSuggestionBindings(search);
                console.log('response of searchList', response);
                // should be auto saved by requests...?
                // @note serialize to disallow xss
                const list = isEmptySearchResponse(response)
                    ? toSafeSuggestions(search)
                    : transformSearchRecommendations(response);
                return {
                    value: list,
                    response: response.data.suggestion.suggestions,
                };
            }
            // this.oneStorage.set('search_suggestion_list', list)
            // @todo why would someone return response here? wrong @invalid
            // @todo - for tree model compat
            return { value: defaultSuggestionList };
        };
    }
}
SearchApi.debugName = 'SearchApi';
const searchApi = new SearchApi();
/**
 * === @todo move to deps ===
 */
const serializeSearchTerm = (value) => {
    if (isAlphanumeric(value) === false) {
        return serialize(value);
    }
    else {
        return value.replace(/\%/gim, '');
    }
};
const isSearchNameQuoted = (searchText) => isString(searchText) && searchText.startsWith('"') && searchText.endsWith('"');
const removeSearchTextQuote = (searchText) => searchText.slice(1, searchText.length - 1);
const searchNameUnquote = (searchName) => isSearchNameQuoted(searchName) ? removeSearchTextQuote(searchName) : searchName;
class SearchContainer extends ObservableContainer {
    constructor() {
        super();
        /**
         * @todo @@perf EMPTY
         */
        this.inputReference = {};
        this.schemaData = searchSchema(searchBaseUrl);
        this.placeholder = wording.placeholder;
        this.isVisible = false;
        this.isActive = false;
        this.suggestionList = [];
        this.recentList = [];
        this.label = 'keyword';
        this.value = undefined;
        /**
         * @type { Action }
         * @todo should us mobx type for array...
         * @param text text on the item in recent results
         * @api https://mobx.js.org/refguide/array.html
         */
        this.handleRemoveRecentItem = (text, event) => {
            event.preventDefault();
            event.stopPropagation();
            if (this.recentList.includes(text)) {
                // const index = this.recentList.indexOf(text)
                const success = this.recentList.remove(text);
                oneStorage.set('search_recent_list', this.recentList);
                console.debug('REMOVING', text, success);
            }
            else {
                console.debug('TRIED_TO_REMOVE_NOT_AVAILABLE', text, this.recentList);
            }
        };
        /**
         * @event click
         * @type {Action}
         * @param text text on the item in recent results
         */
        this.handleOnClickFor = (text) => (event) => {
            this.value = text;
            this.inputReference.observableState.value = text;
            this.isVisible = false;
            console.debug('[SearchContainer] handleOnClickFor: ' + text);
            this.handleFromSearchSuggestionOrSubmit(text);
        };
        /**
         * @todo weird to re-assign actions, thought this is not allowed
         */
        this.updateSearchSuggestions = debounce(this.updateSearchSuggestions.bind(this), 600);
        this.afterCreate();
    }
    afterCreate() {
        /**
         * @todo @fixme should change transform in @@graphql @@haircut
         */
        const initialSuggestion = fixture;
        this.recentList = oneStorage.get('search_recent_list') || [];
        this.setLabelValue(initialSuggestion);
    }
    setLabelValue(labelValue) {
        const { label, value } = flattenSnap(labelValue);
        this.label = label;
        this.value = value;
    }
    /**
     * @type {Action}
     * @param {String} value from search input
     * @return {Promise} api call
     *
     * @todo broken api https://jira.skava.net/browse/SKREACT-639?filter=26436
     *
     * @example {
     *  "type":"suggestion",
     *  "properties":{
     *    "suggestion":[{"value":
     *      ["men in electronics|||%2528%252bcampaignid%253a2495%2b%252bavailable%253atrue%2b%252bcategorylevel2%253a%2522men%2522%2b%252bcategorylevel1%253a%2522electronics%2522%2529","men in shoes|||%2528%252bcampaignid%253a2495%2b%252bavailable%253atrue%2b%252bcategorylevel2%253a%2522men%2522%2b%252bcategorylevel1%253a%2522shoes%2522%2529","men in clothing|||%2528%252bcampaignid%253a2495%2b%252bavailable%253atrue%2b%252bcategorylevel2%253a%2522men%2522%2b%252bcategorylevel1%253a%2522clothing%2522%2529"]}]}}
     *
     * @see http://54.161.84.34:3006/api/searchsuggestion?campaignId=212&search=men&offset=0&limit=20&locale=en_US
     */
    updateSearchSuggestions(value) {
        return searchApi
            .getSearchSuggestions(value)
            .then(suggestions => {
            this.suggestionList = suggestions.value || [];
            this.suggestionList = uniq(this.suggestionList);
            this.setLabelValue(suggestions);
        })
            .catch(typeError => {
            console.error(typeError);
            throw typeError;
        });
    }
    updateSearchToDefaultIfEmpty(value) {
        if (value === '' || value === '""') {
            this.afterCreate();
        }
    }
    // =================== ui ===================
    /**
     * @todo @james evaluate a better way to deal with this
     *              rather than 2-way coupling
     *
     * @see atoms/SearchInput
     * @note dom.observableState is on search input
     */
    setInputReference(dom) {
        this.inputReference = dom;
    }
    /**
     * @event clear
     */
    handleClearRecent(event) {
        console.debug('[SearchContainer] handleClearRecent');
        oneStorage.remove('search_recent_list');
        this.recentList = [];
    }
    /**
     * @tutorial https://reactjs.org/docs/events.html#keyboard-events
     */
    handleKeyboard(event) {
        console.debug('[SearchContainer] handleKeyboard');
        this.show();
        const target = event.target;
        const value = target.value;
        // ignore unsafe & empty
        if (isSafe(value) === false || value === '') {
            console.debug('empty');
            this.updateSearchToDefaultIfEmpty('');
            return;
        }
        else {
            this.updateSearchSuggestions(value);
        }
        // @TODO
        if (event.eventName === 'Enter') {
            this.goToSearch(value);
        }
    }
    show() {
        if (this.isVisible === false) {
            this.isVisible = true;
        }
    }
    hide() {
        if (this.isVisible === true) {
            this.isVisible = false;
        }
    }
    /**
     * @description changes url to go to search page
     */
    goToSearch(value) {
        /**
         * if it is not just string or number...
         */
        const searchTerm = serializeSearchTerm(value).trim();
        const isValidSearchTerm = isNonEmptyString(searchTerm);
        const safeSearch = encodeURIComponent(searchNameUnquote(searchTerm));
        if (isValidSearchTerm) {
            oneRouter.update('/search/' + safeSearch);
        }
    }
    /**
     * @param value value of dom state
     */
    updateSuggestionsAndSave(value) {
        console.debug('[SearchContainer] updateSuggestionsAndSave: ' + value);
        // we cannot lose event persistance here, one of the reasons to add event store
        if (value === '') {
            this.updateSearchToDefaultIfEmpty('');
        }
        else {
            this.updateSearchSuggestions(value);
            this.saveSearch(value);
        }
    }
    /**
     * @param value value to save in recent search
     * @see https://jira.skava.net/browse/SKREACT-4485
     */
    saveSearch(value) {
        if (this.recentList.length >= SEARCH_SUGGESTIONS_RECENT_MAX) {
            this.recentList.shift();
        }
        if (this.recentList.includes(value) === false) {
            this.recentList.push(value);
        }
        oneStorage.set('search_recent_list', this.recentList);
    }
    /**
     * @event submit ui
     */
    handleSubmit(event) {
        event.preventDefault();
        const value = this.inputReference.observableState.value;
        if (value) {
            this.handleFromSearchSuggestionOrSubmit(value);
        }
    }
    /**
     * @param value search value
     */
    handleFromSearchSuggestionOrSubmit(value) {
        this.updateSuggestionsAndSave(value);
        this.goToSearch(value);
        this.handleClickBoundary();
    }
    /**
     * @event focus
     * @description Show search suggestions on focus.
     * @todo typing here on target
     */
    handleSearchBarFocus(props) {
        console.debug('[SearchContainer] handleSearchBarFocus');
        const target = props.event.target;
        const value = target.value;
        const isFocus = props.type === 'focus' || props.type === 'boundary';
        const searchBarFocus = () => {
            this.updateSearchSuggestions(value);
            //  && !stateForSearch.isModalVisible
            if (isFocus) {
                this.isVisible = true;
            }
            else {
                this.isVisible = false;
                this.isActive = !this.isActive;
            }
        };
        /**
         * @description on tap event not working if provided less than 1000 ms for setInterval
         */
        const intervalTime = isFocus ? 10 : 1000;
        setTimeout(searchBarFocus, intervalTime);
        return searchBarFocus;
    }
    /**
     * @event boundaryClick
     */
    handleClickBoundary(event) {
        this.hide();
    }
    /**
     * @event clear
     */
    handleSearchBarClear(component) {
        console.debug('[SearchContainer] handleSearchBarClear');
        component.observableState.value = '';
        this.show();
    }
    /**
     * @event cancel
     */
    handleOnCancel(component) {
        this.hide();
    }
    // ========= computed ======
    get searchTerm() {
        return this.value || oneRouter.get('searchTerm') || '';
    }
}
SearchContainer.debugName = 'Search';
tslib_1.__decorate([
    observable
], SearchContainer.prototype, "placeholder", void 0);
tslib_1.__decorate([
    observable
], SearchContainer.prototype, "isVisible", void 0);
tslib_1.__decorate([
    observable
], SearchContainer.prototype, "isActive", void 0);
tslib_1.__decorate([
    observable
], SearchContainer.prototype, "suggestionList", void 0);
tslib_1.__decorate([
    observable
], SearchContainer.prototype, "recentList", void 0);
tslib_1.__decorate([
    observable
], SearchContainer.prototype, "label", void 0);
tslib_1.__decorate([
    observable
], SearchContainer.prototype, "value", void 0);
tslib_1.__decorate([
    action
], SearchContainer.prototype, "afterCreate", null);
tslib_1.__decorate([
    action
], SearchContainer.prototype, "setLabelValue", null);
tslib_1.__decorate([
    action
], SearchContainer.prototype, "updateSearchSuggestions", null);
tslib_1.__decorate([
    action
], SearchContainer.prototype, "updateSearchToDefaultIfEmpty", null);
tslib_1.__decorate([
    action.bound
], SearchContainer.prototype, "setInputReference", null);
tslib_1.__decorate([
    action.bound
], SearchContainer.prototype, "handleClearRecent", null);
tslib_1.__decorate([
    action.bound
], SearchContainer.prototype, "handleKeyboard", null);
tslib_1.__decorate([
    action.bound
], SearchContainer.prototype, "show", null);
tslib_1.__decorate([
    action.bound
], SearchContainer.prototype, "hide", null);
tslib_1.__decorate([
    action.bound
], SearchContainer.prototype, "goToSearch", null);
tslib_1.__decorate([
    action.bound
], SearchContainer.prototype, "updateSuggestionsAndSave", null);
tslib_1.__decorate([
    action.bound
], SearchContainer.prototype, "saveSearch", null);
tslib_1.__decorate([
    action.bound
], SearchContainer.prototype, "handleSubmit", null);
tslib_1.__decorate([
    action
], SearchContainer.prototype, "handleFromSearchSuggestionOrSubmit", null);
tslib_1.__decorate([
    action.bound
], SearchContainer.prototype, "handleSearchBarFocus", null);
tslib_1.__decorate([
    action.bound
], SearchContainer.prototype, "handleClickBoundary", null);
tslib_1.__decorate([
    action.bound
], SearchContainer.prototype, "handleSearchBarClear", null);
tslib_1.__decorate([
    action.bound
], SearchContainer.prototype, "handleOnCancel", null);
tslib_1.__decorate([
    computed
], SearchContainer.prototype, "searchTerm", null);
const searchContainer = new SearchContainer();
export { searchContainer, SearchContainer, defaultSuggestionList };
export default searchContainer;
//# sourceMappingURL=container.js.map