core-legacy-unified-search.mjs.map 43 KB

1
  1. {"version":3,"file":"core-legacy-unified-search.mjs","sources":["../core/src/components/UnifiedSearch/LegacySearchResult.vue","../core/src/components/UnifiedSearch/SearchResultPlaceholders.vue","../core/src/services/LegacyUnifiedSearchService.js","../core/src/views/LegacyUnifiedSearch.vue","../core/src/legacy-unified-search.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors\n - SPDX-License-Identifier: AGPL-3.0-or-later\n-->\n<template>\n\t<a :href=\"resourceUrl || '#'\"\n\t\tclass=\"unified-search__result\"\n\t\t:class=\"{\n\t\t\t'unified-search__result--focused': focused,\n\t\t}\"\n\t\t@click=\"reEmitEvent\"\n\t\t@focus=\"reEmitEvent\">\n\n\t\t<!-- Icon describing the result -->\n\t\t<div class=\"unified-search__result-icon\"\n\t\t\t:class=\"{\n\t\t\t\t'unified-search__result-icon--rounded': rounded,\n\t\t\t\t'unified-search__result-icon--no-preview': !hasValidThumbnail && !loaded,\n\t\t\t\t'unified-search__result-icon--with-thumbnail': hasValidThumbnail && loaded,\n\t\t\t\t[icon]: !loaded && !isIconUrl,\n\t\t\t}\"\n\t\t\t:style=\"{\n\t\t\t\tbackgroundImage: isIconUrl ? `url(${icon})` : '',\n\t\t\t}\">\n\n\t\t\t<img v-if=\"hasValidThumbnail\"\n\t\t\t\tv-show=\"loaded\"\n\t\t\t\t:src=\"thumbnailUrl\"\n\t\t\t\talt=\"\"\n\t\t\t\t@error=\"onError\"\n\t\t\t\t@load=\"onLoad\">\n\t\t</div>\n\n\t\t<!-- Title and sub-title -->\n\t\t<span class=\"unified-search__result-content\">\n\t\t\t<span class=\"unified-search__result-line-one\" :title=\"title\">\n\t\t\t\t<NcHighlight :text=\"title\" :search=\"query\" />\n\t\t\t</span>\n\t\t\t<span v-if=\"subline\" class=\"unified-search__result-line-two\" :title=\"subline\">{{ subline }}</span>\n\t\t</span>\n\t</a>\n</template>\n\n<script>\nimport NcHighlight from '@nextcloud/vue/dist/Components/NcHighlight.js'\n\nexport default {\n\tname: 'LegacySearchResult',\n\n\tcomponents: {\n\t\tNcHighlight,\n\t},\n\n\tprops: {\n\t\tthumbnailUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\t\tsubline: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\t\tresourceUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\t\ticon: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t\trounded: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t\tquery: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Only used for the first result as a visual feedback\n\t\t * so we can keep the search input focused but pressing\n\t\t * enter still opens the first result\n\t\t */\n\t\tfocused: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\thasValidThumbnail: this.thumbnailUrl && this.thumbnailUrl.trim() !== '',\n\t\t\tloaded: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tisIconUrl() {\n\t\t\t// If we're facing an absolute url\n\t\t\tif (this.icon.startsWith('/')) {\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\t// Otherwise, let's check if this is a valid url\n\t\t\ttry {\n\t\t\t\t// eslint-disable-next-line no-new\n\t\t\t\tnew URL(this.icon)\n\t\t\t} catch {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn true\n\t\t},\n\t},\n\n\twatch: {\n\t\t// Make sure to reset state on change even when vue recycle the component\n\t\tthumbnailUrl() {\n\t\t\tthis.hasValidThumbnail = this.thumbnailUrl && this.thumbnailUrl.trim() !== ''\n\t\t\tthis.loaded = false\n\t\t},\n\t},\n\n\tmethods: {\n\t\treEmitEvent(e) {\n\t\t\tthis.$emit(e.type, e)\n\t\t},\n\n\t\t/**\n\t\t * If the image fails to load, fallback to iconClass\n\t\t */\n\t\tonError() {\n\t\t\tthis.hasValidThumbnail = false\n\t\t},\n\n\t\tonLoad() {\n\t\t\tthis.loaded = true\n\t\t},\n\t},\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@use \"sass:math\";\n\n$clickable-area: 44px;\n$margin: 10px;\n\n.unified-search__result {\n\tdisplay: flex;\n\talign-items: center;\n\theight: $clickable-area;\n\tpadding: $margin;\n\tborder: 2px solid transparent;\n\tborder-radius: var(--border-radius-large) !important;\n\n\t&--focused {\n\t\tbackground-color: var(--color-background-hover);\n\t}\n\n\t&:active,\n\t&:hover,\n\t&:focus {\n\t\tbackground-color: var(--color-background-hover);\n\t\tborder: 2px solid var(--color-border-maxcontrast);\n\t}\n\n\t* {\n\t\tcursor: pointer;\n\t}\n\n\t&-icon {\n\t\toverflow: hidden;\n\t\twidth: $clickable-area;\n\t\theight: $clickable-area;\n\t\tborder-radius: var(--border-radius);\n\t\tbackground-repeat: no-repeat;\n\t\tbackground-position: center center;\n\t\tbackground-size: 32px;\n\t\t&--rounded {\n\t\t\tborder-radius: math.div($clickable-area, 2);\n\t\t}\n\t\t&--no-preview {\n\t\t\tbackground-size: 32px;\n\t\t}\n\t\t&--with-thumbnail {\n\t\t\tbackground-size: cover;\n\t\t}\n\t\t&--with-thumbnail:not(&--rounded) {\n\t\t\t// compensate for border\n\t\t\tmax-width: $clickable-area - 2px;\n\t\t\tmax-height: $clickable-area - 2px;\n\t\t\tborder: 1px solid var(--color-border);\n\t\t}\n\n\t\timg {\n\t\t\t// Make sure to keep ratio\n\t\t\twidth: 100%;\n\t\t\theight: 100%;\n\n\t\t\tobject-fit: cover;\n\t\t\tobject-position: center;\n\t\t}\n\t}\n\n\t&-icon,\n\t&-actions {\n\t\tflex: 0 0 $clickable-area;\n\t}\n\n\t&-content {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tflex: 1 1 100%;\n\t\tflex-wrap: wrap;\n\t\t// Set to minimum and gro from it\n\t\tmin-width: 0;\n\t\tpadding-left: $margin;\n\t}\n\n\t&-line-one,\n\t&-line-two {\n\t\toverflow: hidden;\n\t\tflex: 1 1 100%;\n\t\tmargin: 1px 0;\n\t\twhite-space: nowrap;\n\t\ttext-overflow: ellipsis;\n\t\t// Use the same color as the `a`\n\t\tcolor: inherit;\n\t\tfont-size: inherit;\n\t}\n\t&-line-two {\n\t\topacity: .7;\n\t\tfont-size: var(--default-font-size);\n\t}\n}\n\n</style>\n","<!--\n - SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors\n - SPDX-License-Identifier: AGPL-3.0-or-later\n-->\n<template>\n\t<ul>\n\t\t<!-- Placeholder animation -->\n\t\t<svg class=\"unified-search__result-placeholder-gradient\">\n\t\t\t<defs>\n\t\t\t\t<linearGradient id=\"unified-search__result-placeholder-gradient\">\n\t\t\t\t\t<stop offset=\"0%\" :stop-color=\"light\">\n\t\t\t\t\t\t<animate attributeName=\"stop-color\"\n\t\t\t\t\t\t\t:values=\"`${light}; ${light}; ${dark}; ${dark}; ${light}`\"\n\t\t\t\t\t\t\tdur=\"2s\"\n\t\t\t\t\t\t\trepeatCount=\"indefinite\" />\n\t\t\t\t\t</stop>\n\t\t\t\t\t<stop offset=\"100%\" :stop-color=\"dark\">\n\t\t\t\t\t\t<animate attributeName=\"stop-color\"\n\t\t\t\t\t\t\t:values=\"`${dark}; ${light}; ${light}; ${dark}; ${dark}`\"\n\t\t\t\t\t\t\tdur=\"2s\"\n\t\t\t\t\t\t\trepeatCount=\"indefinite\" />\n\t\t\t\t\t</stop>\n\t\t\t\t</linearGradient>\n\t\t\t</defs>\n\t\t</svg>\n\n\t\t<!-- Placeholders -->\n\t\t<li v-for=\"placeholder in [1, 2, 3]\" :key=\"placeholder\">\n\t\t\t<svg class=\"unified-search__result-placeholder\"\n\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\tfill=\"url(#unified-search__result-placeholder-gradient)\">\n\t\t\t\t<rect class=\"unified-search__result-placeholder-icon\" />\n\t\t\t\t<rect class=\"unified-search__result-placeholder-line-one\" />\n\t\t\t\t<rect class=\"unified-search__result-placeholder-line-two\" :style=\"{width: `calc(${randWidth()}%)`}\" />\n\t\t\t</svg>\n\t\t</li>\n\t</ul>\n</template>\n\n<script>\nexport default {\n\tname: 'SearchResultPlaceholders',\n\n\tdata() {\n\t\treturn {\n\t\t\tlight: null,\n\t\t\tdark: null,\n\t\t}\n\t},\n\tmounted() {\n\t\tconst styles = getComputedStyle(document.documentElement)\n\t\tthis.dark = styles.getPropertyValue('--color-placeholder-dark')\n\t\tthis.light = styles.getPropertyValue('--color-placeholder-light')\n\t},\n\n\tmethods: {\n\t\trandWidth() {\n\t\t\treturn Math.floor(Math.random() * 20) + 30\n\t\t},\n\t},\n}\n</script>\n\n<style lang=\"scss\" scoped>\n$clickable-area: 44px;\n$margin: 10px;\n\n.unified-search__result-placeholder-gradient {\n\tposition: fixed;\n\theight: 0;\n\twidth: 0;\n\tz-index: -1;\n}\n\n.unified-search__result-placeholder {\n\twidth: calc(100% - 2 * #{$margin});\n\theight: $clickable-area;\n\tmargin: $margin;\n\n\t&-icon {\n\t\twidth: $clickable-area;\n\t\theight: $clickable-area;\n\t\trx: var(--border-radius);\n\t\try: var(--border-radius);\n\t}\n\n\t&-line-one,\n\t&-line-two {\n\t\twidth: calc(100% - #{$margin + $clickable-area});\n\t\theight: 1em;\n\t\tx: $margin + $clickable-area;\n\t}\n\n\t&-line-one {\n\t\ty: 5px;\n\t}\n\n\t&-line-two {\n\t\ty: 25px;\n\t}\n}\n\n</style>\n","/**\n * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport { generateOcsUrl } from '@nextcloud/router'\nimport { loadState } from '@nextcloud/initial-state'\nimport axios from '@nextcloud/axios'\n\nexport const defaultLimit = loadState('unified-search', 'limit-default')\nexport const minSearchLength = loadState('unified-search', 'min-search-length', 1)\nexport const enableLiveSearch = loadState('unified-search', 'live-search', true)\n\nexport const regexFilterIn = /(^|\\s)in:([a-z_-]+)/ig\nexport const regexFilterNot = /(^|\\s)-in:([a-z_-]+)/ig\n\n/**\n * Create a cancel token\n *\n * @return {import('axios').CancelTokenSource}\n */\nconst createCancelToken = () => axios.CancelToken.source()\n\n/**\n * Get the list of available search providers\n *\n * @return {Promise<Array>}\n */\nexport async function getTypes() {\n\ttry {\n\t\tconst { data } = await axios.get(generateOcsUrl('search/providers'), {\n\t\t\tparams: {\n\t\t\t\t// Sending which location we're currently at\n\t\t\t\tfrom: window.location.pathname.replace('/index.php', '') + window.location.search,\n\t\t\t},\n\t\t})\n\t\tif ('ocs' in data && 'data' in data.ocs && Array.isArray(data.ocs.data) && data.ocs.data.length > 0) {\n\t\t\t// Providers are sorted by the api based on their order key\n\t\t\treturn data.ocs.data\n\t\t}\n\t} catch (error) {\n\t\tconsole.error(error)\n\t}\n\treturn []\n}\n\n/**\n * Get the list of available search providers\n *\n * @param {object} options destructuring object\n * @param {string} options.type the type to search\n * @param {string} options.query the search\n * @param {number|string|undefined} options.cursor the offset for paginated searches\n * @return {object} {request: Promise, cancel: Promise}\n */\nexport function search({ type, query, cursor }) {\n\t/**\n\t * Generate an axios cancel token\n\t */\n\tconst cancelToken = createCancelToken()\n\n\tconst request = async () => axios.get(generateOcsUrl('search/providers/{type}/search', { type }), {\n\t\tcancelToken: cancelToken.token,\n\t\tparams: {\n\t\t\tterm: query,\n\t\t\tcursor,\n\t\t\t// Sending which location we're currently at\n\t\t\tfrom: window.location.pathname.replace('/index.php', '') + window.location.search,\n\t\t},\n\t})\n\n\treturn {\n\t\trequest,\n\t\tcancel: cancelToken.cancel,\n\t}\n}\n","<!--\n - SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors\n - SPDX-License-Identifier: AGPL-3.0-or-later\n-->\n<template>\n\t<NcHeaderMenu id=\"unified-search\"\n\t\tclass=\"unified-search\"\n\t\t:exclude-click-outside-selectors=\"['.popover']\"\n\t\t:open.sync=\"open\"\n\t\t:aria-label=\"ariaLabel\"\n\t\t@open=\"onOpen\"\n\t\t@close=\"onClose\">\n\t\t<!-- Header icon -->\n\t\t<template #trigger>\n\t\t\t<Magnify class=\"unified-search__trigger-icon\" :size=\"20\" />\n\t\t</template>\n\n\t\t<!-- Search form & filters wrapper -->\n\t\t<div class=\"unified-search__input-wrapper\">\n\t\t\t<div class=\"unified-search__input-row\">\n\t\t\t\t<NcTextField ref=\"input\"\n\t\t\t\t\t:value.sync=\"query\"\n\t\t\t\t\ttrailing-button-icon=\"close\"\n\t\t\t\t\t:label=\"ariaLabel\"\n\t\t\t\t\t:trailing-button-label=\"t('core','Reset search')\"\n\t\t\t\t\t:show-trailing-button=\"query !== ''\"\n\t\t\t\t\taria-describedby=\"unified-search-desc\"\n\t\t\t\t\tclass=\"unified-search__form-input\"\n\t\t\t\t\t:class=\"{'unified-search__form-input--with-reset': !!query}\"\n\t\t\t\t\t:placeholder=\"t('core', 'Search {types} …', { types: typesNames.join(', ') })\"\n\t\t\t\t\t@trailing-button-click=\"onReset\"\n\t\t\t\t\t@input=\"onInputDebounced\" />\n\t\t\t\t<p id=\"unified-search-desc\" class=\"hidden-visually\">\n\t\t\t\t\t{{ t('core', 'Search starts once you start typing and results may be reached with the arrow keys') }}\n\t\t\t\t</p>\n\n\t\t\t\t<!-- Search filters -->\n\t\t\t\t<NcActions v-if=\"availableFilters.length > 1\"\n\t\t\t\t\tclass=\"unified-search__filters\"\n\t\t\t\t\tplacement=\"bottom-end\"\n\t\t\t\t\tcontainer=\".unified-search__input-wrapper\">\n\t\t\t\t\t<!-- FIXME use element ref for container after https://github.com/nextcloud/nextcloud-vue/pull/3462 -->\n\t\t\t\t\t<NcActionButton v-for=\"filter in availableFilters\"\n\t\t\t\t\t\t:key=\"filter\"\n\t\t\t\t\t\ticon=\"icon-filter\"\n\t\t\t\t\t\t@click.stop=\"onClickFilter(`in:${filter}`)\">\n\t\t\t\t\t\t{{ t('core', 'Search for {name} only', { name: typesMap[filter] }) }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</NcActions>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<template v-if=\"!hasResults\">\n\t\t\t<!-- Loading placeholders -->\n\t\t\t<SearchResultPlaceholders v-if=\"isLoading\" />\n\n\t\t\t<NcEmptyContent v-else-if=\"isValidQuery\"\n\t\t\t\t:title=\"validQueryTitle\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Magnify />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\n\t\t\t<NcEmptyContent v-else-if=\"!isLoading || isShortQuery\"\n\t\t\t\t:title=\"t('core', 'Start typing to search')\"\n\t\t\t\t:description=\"shortQueryDescription\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Magnify />\n\t\t\t\t</template>\n\t\t\t</NcEmptyContent>\n\t\t</template>\n\n\t\t<!-- Grouped search results -->\n\t\t<template v-for=\"({list, type}, typesIndex) in orderedResults\" v-else>\n\t\t\t<h2 :key=\"type\" class=\"unified-search__results-header\">\n\t\t\t\t{{ typesMap[type] }}\n\t\t\t</h2>\n\t\t\t<ul :key=\"type\"\n\t\t\t\tclass=\"unified-search__results\"\n\t\t\t\t:class=\"`unified-search__results-${type}`\"\n\t\t\t\t:aria-label=\"typesMap[type]\">\n\t\t\t\t<!-- Search results -->\n\t\t\t\t<li v-for=\"(result, index) in limitIfAny(list, type)\" :key=\"result.resourceUrl\">\n\t\t\t\t\t<SearchResult v-bind=\"result\"\n\t\t\t\t\t\t:query=\"query\"\n\t\t\t\t\t\t:focused=\"focused === 0 && typesIndex === 0 && index === 0\"\n\t\t\t\t\t\t@focus=\"setFocusedIndex\" />\n\t\t\t\t</li>\n\n\t\t\t\t<!-- Load more button -->\n\t\t\t\t<li>\n\t\t\t\t\t<SearchResult v-if=\"!reached[type]\"\n\t\t\t\t\t\tclass=\"unified-search__result-more\"\n\t\t\t\t\t\t:title=\"loading[type]\n\t\t\t\t\t\t\t? t('core', 'Loading more results …')\n\t\t\t\t\t\t\t: t('core', 'Load more results')\"\n\t\t\t\t\t\t:icon-class=\"loading[type] ? 'icon-loading-small' : ''\"\n\t\t\t\t\t\t@click.prevent.stop=\"loadMore(type)\"\n\t\t\t\t\t\t@focus=\"setFocusedIndex\" />\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</template>\n\t</NcHeaderMenu>\n</template>\n\n<script>\nimport debounce from 'debounce'\nimport { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'\nimport { showError } from '@nextcloud/dialogs'\n\nimport NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'\nimport NcActions from '@nextcloud/vue/dist/Components/NcActions.js'\nimport NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'\nimport NcHeaderMenu from '@nextcloud/vue/dist/Components/NcHeaderMenu.js'\nimport NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'\n\nimport Magnify from 'vue-material-design-icons/Magnify.vue'\n\nimport SearchResult from '../components/UnifiedSearch/LegacySearchResult.vue'\nimport SearchResultPlaceholders from '../components/UnifiedSearch/SearchResultPlaceholders.vue'\n\nimport { minSearchLength, getTypes, search, defaultLimit, regexFilterIn, regexFilterNot, enableLiveSearch } from '../services/LegacyUnifiedSearchService.js'\n\nconst REQUEST_FAILED = 0\nconst REQUEST_OK = 1\nconst REQUEST_CANCELED = 2\n\nexport default {\n\tname: 'LegacyUnifiedSearch',\n\n\tcomponents: {\n\t\tMagnify,\n\t\tNcActionButton,\n\t\tNcActions,\n\t\tNcEmptyContent,\n\t\tNcHeaderMenu,\n\t\tSearchResult,\n\t\tSearchResultPlaceholders,\n\t\tNcTextField,\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\ttypes: [],\n\n\t\t\t// Cursors per types\n\t\t\tcursors: {},\n\t\t\t// Various search limits per types\n\t\t\tlimits: {},\n\t\t\t// Loading types\n\t\t\tloading: {},\n\t\t\t// Reached search types\n\t\t\treached: {},\n\t\t\t// Pending cancellable requests\n\t\t\trequests: [],\n\t\t\t// List of all results\n\t\t\tresults: {},\n\n\t\t\tquery: '',\n\t\t\tfocused: null,\n\t\t\ttriggered: false,\n\n\t\t\tdefaultLimit,\n\t\t\tminSearchLength,\n\t\t\tenableLiveSearch,\n\n\t\t\topen: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\ttypesIDs() {\n\t\t\treturn this.types.map(type => type.id)\n\t\t},\n\t\ttypesNames() {\n\t\t\treturn this.types.map(type => type.name)\n\t\t},\n\t\ttypesMap() {\n\t\t\treturn this.types.reduce((prev, curr) => {\n\t\t\t\tprev[curr.id] = curr.name\n\t\t\t\treturn prev\n\t\t\t}, {})\n\t\t},\n\n\t\tariaLabel() {\n\t\t\treturn t('core', 'Search')\n\t\t},\n\n\t\t/**\n\t\t * Is there any result to display\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\thasResults() {\n\t\t\treturn Object.keys(this.results).length !== 0\n\t\t},\n\n\t\t/**\n\t\t * Return ordered results\n\t\t *\n\t\t * @return {Array}\n\t\t */\n\t\torderedResults() {\n\t\t\treturn this.typesIDs\n\t\t\t\t.filter(type => type in this.results)\n\t\t\t\t.map(type => ({\n\t\t\t\t\ttype,\n\t\t\t\t\tlist: this.results[type],\n\t\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Available filters\n\t\t * We only show filters that are available on the results\n\t\t *\n\t\t * @return {string[]}\n\t\t */\n\t\tavailableFilters() {\n\t\t\treturn Object.keys(this.results)\n\t\t},\n\n\t\t/**\n\t\t * Applied filters\n\t\t *\n\t\t * @return {string[]}\n\t\t */\n\t\tusedFiltersIn() {\n\t\t\tlet match\n\t\t\tconst filters = []\n\t\t\twhile ((match = regexFilterIn.exec(this.query)) !== null) {\n\t\t\t\tfilters.push(match[2])\n\t\t\t}\n\t\t\treturn filters\n\t\t},\n\n\t\t/**\n\t\t * Applied anti filters\n\t\t *\n\t\t * @return {string[]}\n\t\t */\n\t\tusedFiltersNot() {\n\t\t\tlet match\n\t\t\tconst filters = []\n\t\t\twhile ((match = regexFilterNot.exec(this.query)) !== null) {\n\t\t\t\tfilters.push(match[2])\n\t\t\t}\n\t\t\treturn filters\n\t\t},\n\n\t\t/**\n\t\t * Valid query empty content title\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tvalidQueryTitle() {\n\t\t\treturn this.triggered\n\t\t\t\t? t('core', 'No results for {query}', { query: this.query })\n\t\t\t\t: t('core', 'Press Enter to start searching')\n\t\t},\n\n\t\t/**\n\t\t * Short query empty content description\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tshortQueryDescription() {\n\t\t\tif (!this.isShortQuery) {\n\t\t\t\treturn ''\n\t\t\t}\n\n\t\t\treturn n('core',\n\t\t\t\t'Please enter {minSearchLength} character or more to search',\n\t\t\t\t'Please enter {minSearchLength} characters or more to search',\n\t\t\t\tthis.minSearchLength,\n\t\t\t\t{ minSearchLength: this.minSearchLength })\n\t\t},\n\n\t\t/**\n\t\t * Is the current search too short\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisShortQuery() {\n\t\t\treturn this.query && this.query.trim().length < minSearchLength\n\t\t},\n\n\t\t/**\n\t\t * Is the current search valid\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisValidQuery() {\n\t\t\treturn this.query && this.query.trim() !== '' && !this.isShortQuery\n\t\t},\n\n\t\t/**\n\t\t * Have we reached the end of all types searches\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisDoneSearching() {\n\t\t\treturn Object.values(this.reached).every(state => state === false)\n\t\t},\n\n\t\t/**\n\t\t * Is there any search in progress\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisLoading() {\n\t\t\treturn Object.values(this.loading).some(state => state === true)\n\t\t},\n\t},\n\n\tasync created() {\n\t\tthis.types = await getTypes()\n\t\tthis.logger.debug('Unified Search initialized with the following providers', this.types)\n\t},\n\n\tbeforeDestroy() {\n\t\tunsubscribe('files:navigation:changed', this.onNavigationChange)\n\t},\n\n\tmounted() {\n\t\t// subscribe in mounted, as onNavigationChange relys on $el\n\t\tsubscribe('files:navigation:changed', this.onNavigationChange)\n\n\t\tif (OCP.Accessibility.disableKeyboardShortcuts()) {\n\t\t\treturn\n\t\t}\n\n\t\tdocument.addEventListener('keydown', (event) => {\n\t\t\t// if not already opened, allows us to trigger default browser on second keydown\n\t\t\tif (event.ctrlKey && event.code === 'KeyF' && !this.open) {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tthis.open = true\n\t\t\t} else if (event.ctrlKey && event.key === 'f' && this.open) {\n\t\t\t\t// User wants to use the native browser search, so we close ours again\n\t\t\t\tthis.open = false\n\t\t\t}\n\n\t\t\t// https://www.w3.org/WAI/GL/wiki/Using_ARIA_menus\n\t\t\tif (this.open) {\n\t\t\t\t// If arrow down, focus next result\n\t\t\t\tif (event.key === 'ArrowDown') {\n\t\t\t\t\tthis.focusNext(event)\n\t\t\t\t}\n\n\t\t\t\t// If arrow up, focus prev result\n\t\t\t\tif (event.key === 'ArrowUp') {\n\t\t\t\t\tthis.focusPrev(event)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t},\n\n\tmethods: {\n\t\tasync onOpen() {\n\t\t\t// Update types list in the background\n\t\t\tthis.types = await getTypes()\n\t\t},\n\t\tonClose() {\n\t\t\temit('nextcloud:unified-search.close')\n\t\t},\n\n\t\tonNavigationChange() {\n\t\t\tthis.$el?.querySelector?.('form[role=\"search\"]')?.reset?.()\n\t\t},\n\n\t\t/**\n\t\t * Reset the search state\n\t\t */\n\t\tonReset() {\n\t\t\temit('nextcloud:unified-search.reset')\n\t\t\tthis.logger.debug('Search reset')\n\t\t\tthis.query = ''\n\t\t\tthis.resetState()\n\t\t\tthis.focusInput()\n\t\t},\n\t\tasync resetState() {\n\t\t\tthis.cursors = {}\n\t\t\tthis.limits = {}\n\t\t\tthis.reached = {}\n\t\t\tthis.results = {}\n\t\t\tthis.focused = null\n\t\t\tthis.triggered = false\n\t\t\tawait this.cancelPendingRequests()\n\t\t},\n\n\t\t/**\n\t\t * Cancel any ongoing searches\n\t\t */\n\t\tasync cancelPendingRequests() {\n\t\t\t// Cloning so we can keep processing other requests\n\t\t\tconst requests = this.requests.slice(0)\n\t\t\tthis.requests = []\n\n\t\t\t// Cancel all pending requests\n\t\t\tawait Promise.all(requests.map(cancel => cancel()))\n\t\t},\n\n\t\t/**\n\t\t * Focus the search input on next tick\n\t\t */\n\t\tfocusInput() {\n\t\t\tthis.$nextTick(() => {\n\t\t\t\tthis.$refs.input.focus()\n\t\t\t\tthis.$refs.input.select()\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * If we have results already, open first one\n\t\t * If not, trigger the search again\n\t\t */\n\t\tonInputEnter() {\n\t\t\tif (this.hasResults) {\n\t\t\t\tconst results = this.getResultsList()\n\t\t\t\tresults[0].click()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.onInput()\n\t\t},\n\n\t\t/**\n\t\t * Start searching on input\n\t\t */\n\t\tasync onInput() {\n\t\t\t// emit the search query\n\t\t\temit('nextcloud:unified-search.search', { query: this.query })\n\n\t\t\t// Do not search if not long enough\n\t\t\tif (this.query.trim() === '' || this.isShortQuery) {\n\t\t\t\tfor (const type of this.typesIDs) {\n\t\t\t\t\tthis.$delete(this.results, type)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tlet types = this.typesIDs\n\t\t\tlet query = this.query\n\n\t\t\t// Filter out types\n\t\t\tif (this.usedFiltersNot.length > 0) {\n\t\t\t\ttypes = this.typesIDs.filter(type => this.usedFiltersNot.indexOf(type) === -1)\n\t\t\t}\n\n\t\t\t// Only use those filters if any and check if they are valid\n\t\t\tif (this.usedFiltersIn.length > 0) {\n\t\t\t\ttypes = this.typesIDs.filter(type => this.usedFiltersIn.indexOf(type) > -1)\n\t\t\t}\n\n\t\t\t// Remove any filters from the query\n\t\t\tquery = query.replace(regexFilterIn, '').replace(regexFilterNot, '')\n\n\t\t\t// Reset search if the query changed\n\t\t\tawait this.resetState()\n\t\t\tthis.triggered = true\n\n\t\t\tif (!types.length) {\n\t\t\t\t// no results since no types were selected\n\t\t\t\tthis.logger.error('No types to search in')\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.$set(this.loading, 'all', true)\n\t\t\tthis.logger.debug(`Searching ${query} in`, types)\n\n\t\t\tPromise.all(types.map(async type => {\n\t\t\t\ttry {\n\t\t\t\t\t// Init cancellable request\n\t\t\t\t\tconst { request, cancel } = search({ type, query })\n\t\t\t\t\tthis.requests.push(cancel)\n\n\t\t\t\t\t// Fetch results\n\t\t\t\t\tconst { data } = await request()\n\n\t\t\t\t\t// Process results\n\t\t\t\t\tif (data.ocs.data.entries.length > 0) {\n\t\t\t\t\t\tthis.$set(this.results, type, data.ocs.data.entries)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.$delete(this.results, type)\n\t\t\t\t\t}\n\n\t\t\t\t\t// Save cursor if any\n\t\t\t\t\tif (data.ocs.data.cursor) {\n\t\t\t\t\t\tthis.$set(this.cursors, type, data.ocs.data.cursor)\n\t\t\t\t\t} else if (!data.ocs.data.isPaginated) {\n\t\t\t\t\t// If no cursor and no pagination, we save the default amount\n\t\t\t\t\t// provided by server's initial state `defaultLimit`\n\t\t\t\t\t\tthis.$set(this.limits, type, this.defaultLimit)\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check if we reached end of pagination\n\t\t\t\t\tif (data.ocs.data.entries.length < this.defaultLimit) {\n\t\t\t\t\t\tthis.$set(this.reached, type, true)\n\t\t\t\t\t}\n\n\t\t\t\t\t// If none already focused, focus the first rendered result\n\t\t\t\t\tif (this.focused === null) {\n\t\t\t\t\t\tthis.focused = 0\n\t\t\t\t\t}\n\t\t\t\t\treturn REQUEST_OK\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthis.$delete(this.results, type)\n\n\t\t\t\t\t// If this is not a cancelled throw\n\t\t\t\t\tif (error.response && error.response.status) {\n\t\t\t\t\t\tthis.logger.error(`Error searching for ${this.typesMap[type]}`, error)\n\t\t\t\t\t\tshowError(this.t('core', 'An error occurred while searching for {type}', { type: this.typesMap[type] }))\n\t\t\t\t\t\treturn REQUEST_FAILED\n\t\t\t\t\t}\n\t\t\t\t\treturn REQUEST_CANCELED\n\t\t\t\t}\n\t\t\t})).then(results => {\n\t\t\t\t// Do not declare loading finished if the request have been cancelled\n\t\t\t\t// This means another search was triggered and we're therefore still loading\n\t\t\t\tif (results.some(result => result === REQUEST_CANCELED)) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// We finished all searches\n\t\t\t\tthis.loading = {}\n\t\t\t})\n\t\t},\n\t\tonInputDebounced: enableLiveSearch\n\t\t\t? debounce(function(e) {\n\t\t\t\tthis.onInput(e)\n\t\t\t}, 500)\n\t\t\t: function() {\n\t\t\t\tthis.triggered = false\n\t\t\t},\n\n\t\t/**\n\t\t * Load more results for the provided type\n\t\t *\n\t\t * @param {string} type type\n\t\t */\n\t\tasync loadMore(type) {\n\t\t\t// If already loading, ignore\n\t\t\tif (this.loading[type]) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (this.cursors[type]) {\n\t\t\t\t// Init cancellable request\n\t\t\t\tconst { request, cancel } = search({ type, query: this.query, cursor: this.cursors[type] })\n\t\t\t\tthis.requests.push(cancel)\n\n\t\t\t\t// Fetch results\n\t\t\t\tconst { data } = await request()\n\n\t\t\t\t// Save cursor if any\n\t\t\t\tif (data.ocs.data.cursor) {\n\t\t\t\t\tthis.$set(this.cursors, type, data.ocs.data.cursor)\n\t\t\t\t}\n\n\t\t\t\t// Process results\n\t\t\t\tif (data.ocs.data.entries.length > 0) {\n\t\t\t\t\tthis.results[type].push(...data.ocs.data.entries)\n\t\t\t\t}\n\n\t\t\t\t// Check if we reached end of pagination\n\t\t\t\tif (data.ocs.data.entries.length < this.defaultLimit) {\n\t\t\t\t\tthis.$set(this.reached, type, true)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// If no cursor, we might have all the results already,\n\t\t\t\t// let's fake pagination and show the next xxx entries\n\t\t\t\tif (this.limits[type] && this.limits[type] >= 0) {\n\t\t\t\t\tthis.limits[type] += this.defaultLimit\n\n\t\t\t\t\t// Check if we reached end of pagination\n\t\t\t\t\tif (this.limits[type] >= this.results[type].length) {\n\t\t\t\t\t\tthis.$set(this.reached, type, true)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Focus result after render\n\t\t\tif (this.focused !== null) {\n\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\tthis.focusIndex(this.focused)\n\t\t\t\t})\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Return a subset of the array if the search provider\n\t\t * doesn't supports pagination\n\t\t *\n\t\t * @param {Array} list the results\n\t\t * @param {string} type the type\n\t\t * @return {Array}\n\t\t */\n\t\tlimitIfAny(list, type) {\n\t\t\tif (type in this.limits) {\n\t\t\t\treturn list.slice(0, this.limits[type])\n\t\t\t}\n\t\t\treturn list\n\t\t},\n\n\t\tgetResultsList() {\n\t\t\treturn this.$el.querySelectorAll('.unified-search__results .unified-search__result')\n\t\t},\n\n\t\t/**\n\t\t * Focus the first result if any\n\t\t *\n\t\t * @param {Event} event the keydown event\n\t\t */\n\t\tfocusFirst(event) {\n\t\t\tconst results = this.getResultsList()\n\t\t\tif (results && results.length > 0) {\n\t\t\t\tif (event) {\n\t\t\t\t\tevent.preventDefault()\n\t\t\t\t}\n\t\t\t\tthis.focused = 0\n\t\t\t\tthis.focusIndex(this.focused)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Focus the next result if any\n\t\t *\n\t\t * @param {Event} event the keydown event\n\t\t */\n\t\tfocusNext(event) {\n\t\t\tif (this.focused === null) {\n\t\t\t\tthis.focusFirst(event)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst results = this.getResultsList()\n\t\t\t// If we're not focusing the last, focus the next one\n\t\t\tif (results && results.length > 0 && this.focused + 1 < results.length) {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tthis.focused++\n\t\t\t\tthis.focusIndex(this.focused)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Focus the previous result if any\n\t\t *\n\t\t * @param {Event} event the keydown event\n\t\t */\n\t\tfocusPrev(event) {\n\t\t\tif (this.focused === null) {\n\t\t\t\tthis.focusFirst(event)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst results = this.getResultsList()\n\t\t\t// If we're not focusing the first, focus the previous one\n\t\t\tif (results && results.length > 0 && this.focused > 0) {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tthis.focused--\n\t\t\t\tthis.focusIndex(this.focused)\n\t\t\t}\n\n\t\t},\n\n\t\t/**\n\t\t * Focus the specified result index if it exists\n\t\t *\n\t\t * @param {number} index the result index\n\t\t */\n\t\tfocusIndex(index) {\n\t\t\tconst results = this.getResultsList()\n\t\t\tif (results && results[index]) {\n\t\t\t\tresults[index].focus()\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Set the current focused element based on the target\n\t\t *\n\t\t * @param {Event} event the focus event\n\t\t */\n\t\tsetFocusedIndex(event) {\n\t\t\tconst entry = event.target\n\t\t\tconst results = this.getResultsList()\n\t\t\tconst index = [...results].findIndex(search => search === entry)\n\t\t\tif (index > -1) {\n\t\t\t\t// let's not use focusIndex as the entry is already focused\n\t\t\t\tthis.focused = index\n\t\t\t}\n\t\t},\n\n\t\tonClickFilter(filter) {\n\t\t\tthis.query = `${this.query} ${filter}`\n\t\t\t\t.replace(/ {2}/g, ' ')\n\t\t\t\t.trim()\n\t\t\tthis.onInput()\n\t\t},\n\t},\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@use \"sass:math\";\n\n$margin: 10px;\n$input-height: 34px;\n$input-padding: 10px;\n\n.unified-search {\n\t&__trigger-icon {\n\t\tcolor: var(--color-background-plain-text) !important;\n\t}\n\n\t&__input-wrapper {\n\t\tposition: sticky;\n\t\t// above search results\n\t\tz-index: 2;\n\t\ttop: 0;\n\t\tdisplay: inline-flex;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\twidth: 100%;\n\t\tbackground-color: var(--color-main-background);\n\n\t\tlabel[for=\"unified-search__input\"] {\n\t\t\talign-self: flex-start;\n\t\t\tfont-weight: bold;\n\t\t\tfont-size: 19px;\n\t\t\tmargin-left: 13px;\n\t\t}\n\t}\n\n\t&__form-input {\n\t\tmargin: 0 !important;\n\t\t&:focus,\n\t\t&:focus-visible,\n\t\t&:active {\n\t\t\tborder-color: 2px solid var(--color-main-text) !important;\n\t\t\tbox-shadow: 0 0 0 2px var(--color-main-background) !important;\n\t\t}\n\t}\n\n\t&__input-row {\n\t\tdisplay: flex;\n\t\twidth: 100%;\n\t\talign-items: center;\n\t}\n\n\t&__filters {\n\t\tmargin: $margin 0 $margin math.div($margin, 2);\n\t\tpadding-top: 5px;\n\t\tul {\n\t\t\tdisplay: inline-flex;\n\t\t\tjustify-content: space-between;\n\t\t}\n\t}\n\n\t&__form {\n\t\tposition: relative;\n\t\twidth: 100%;\n\t\tmargin: $margin 0;\n\n\t\t// Loading spinner\n\t\t&::after {\n\t\t\tright: $input-padding;\n\t\t\tleft: auto;\n\t\t}\n\n\t\t&-input,\n\t\t&-reset {\n\t\t\tmargin: math.div($input-padding, 2);\n\t\t}\n\n\t\t&-input {\n\t\t\twidth: 100%;\n\t\t\theight: $input-height;\n\t\t\tpadding: $input-padding;\n\n\t\t\t&,\n\t\t\t&[placeholder],\n\t\t\t&::placeholder {\n\t\t\t\toverflow: hidden;\n\t\t\t\twhite-space: nowrap;\n\t\t\t\ttext-overflow: ellipsis;\n\t\t\t}\n\n\t\t\t// Hide webkit clear search\n\t\t\t&::-webkit-search-decoration,\n\t\t\t&::-webkit-search-cancel-button,\n\t\t\t&::-webkit-search-results-button,\n\t\t\t&::-webkit-search-results-decoration {\n\t\t\t\t-webkit-appearance: none;\n\t\t\t}\n\t\t}\n\n\t\t&-reset, &-submit {\n\t\t\tposition: absolute;\n\t\t\ttop: 0;\n\t\t\tright: 4px;\n\t\t\twidth: $input-height - $input-padding;\n\t\t\theight: $input-height - $input-padding;\n\t\t\tmin-height: 30px;\n\t\t\tpadding: 0;\n\t\t\topacity: .5;\n\t\t\tborder: none;\n\t\t\tbackground-color: transparent;\n\t\t\tmargin-right: 0;\n\n\t\t\t&:hover,\n\t\t\t&:focus,\n\t\t\t&:active {\n\t\t\t\topacity: 1;\n\t\t\t}\n\t\t}\n\n\t\t&-submit {\n\t\t\tright: 28px;\n\t\t}\n\t}\n\n\t&__results {\n\t\t&-header {\n\t\t\tdisplay: block;\n\t\t\tmargin: $margin;\n\t\t\tmargin-bottom: $margin - 4px;\n\t\t\tmargin-left: 13px;\n\t\t\tcolor: var(--color-primary-element);\n\t\t\tfont-size: 19px;\n\t\t\tfont-weight: bold;\n\t\t}\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tgap: 4px;\n\t}\n\n\t.unified-search__result-more::v-deep {\n\t\tcolor: var(--color-text-maxcontrast);\n\t}\n\n\t.empty-content {\n\t\tmargin: 10vh 0;\n\n\t\t::v-deep .empty-content__title {\n\t\t\tfont-weight: normal;\n font-size: var(--default-font-size);\n\t\t\ttext-align: center;\n\t\t}\n\t}\n}\n\n</style>\n","/**\n * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport { getLoggerBuilder } from '@nextcloud/logger'\nimport { translate as t, translatePlural as n } from '@nextcloud/l10n'\nimport Vue from 'vue'\n\nimport UnifiedSearch from './views/LegacyUnifiedSearch.vue'\n\nconst logger = getLoggerBuilder()\n\t.setApp('unified-search')\n\t.detectUser()\n\t.build()\n\nVue.mixin({\n\tdata() {\n\t\treturn {\n\t\t\tlogger,\n\t\t}\n\t},\n\tmethods: {\n\t\tt,\n\t\tn,\n\t},\n})\n\nexport default new Vue({\n\tel: '#unified-search',\n\t// eslint-disable-next-line vue/match-component-file-name\n\tname: 'UnifiedSearchRoot',\n\trender: h => h(UnifiedSearch),\n})\n"],"names":["_sfc_main","NcHighlight","e","styles","defaultLimit","loadState","minSearchLength","enableLiveSearch","regexFilterIn","regexFilterNot","createCancelToken","axios","getTypes","data","generateOcsUrl","error","search","type","query","cursor","cancelToken","REQUEST_FAILED","REQUEST_OK","REQUEST_CANCELED","Magnify","NcActionButton","NcActions","NcEmptyContent","NcHeaderMenu","SearchResult","SearchResultPlaceholders","NcTextField","prev","curr","match","filters","state","unsubscribe","subscribe","event","emit","_d","_c","_b","_a","requests","cancel","types","request","showError","results","result","debounce","list","index","entry","filter","logger","getLoggerBuilder","Vue","t","n","legacyUnifiedSearch","h","UnifiedSearch"],"mappings":"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"}