Merge pull request #22935 from nextcloud/backport/22868/stable20
[stable20] Fix/unified search papercuts
This commit is contained in:
commit
03d00afe31
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -426,8 +426,8 @@ export default {
|
||||||
/**
|
/**
|
||||||
* Register search
|
* Register search
|
||||||
*/
|
*/
|
||||||
subscribe('nextcloud:unified-search:search', this.search)
|
subscribe('nextcloud:unified-search.search', this.search)
|
||||||
subscribe('nextcloud:unified-search:reset', this.resetSearch)
|
subscribe('nextcloud:unified-search.reset', this.resetSearch)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If disabled group but empty, redirect
|
* If disabled group but empty, redirect
|
||||||
|
@ -435,8 +435,8 @@ export default {
|
||||||
this.redirectIfDisabled()
|
this.redirectIfDisabled()
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
unsubscribe('nextcloud:unified-search:search', this.search)
|
unsubscribe('nextcloud:unified-search.search', this.search)
|
||||||
unsubscribe('nextcloud:unified-search:reset', this.resetSearch)
|
unsubscribe('nextcloud:unified-search.reset', this.resetSearch)
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
@ -280,12 +280,12 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
subscribe('nextcloud:unified-search:search', this.setSearch)
|
subscribe('nextcloud:unified-search.search', this.setSearch)
|
||||||
subscribe('nextcloud:unified-search:reset', this.resetSearch)
|
subscribe('nextcloud:unified-search.reset', this.resetSearch)
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
unsubscribe('nextcloud:unified-search:search', this.setSearch)
|
unsubscribe('nextcloud:unified-search.search', this.setSearch)
|
||||||
unsubscribe('nextcloud:unified-search:reset', this.resetSearch)
|
unsubscribe('nextcloud:unified-search.reset', this.resetSearch)
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,4 +1,4 @@
|
||||||
/*
|
/**
|
||||||
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
|
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||||
*
|
*
|
||||||
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
|
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/*
|
/**
|
||||||
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
|
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||||
*
|
*
|
||||||
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
|
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/*
|
/**
|
||||||
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
|
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||||
*
|
*
|
||||||
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
|
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/*
|
/**
|
||||||
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
|
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||||
*
|
*
|
||||||
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
|
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||||
|
|
|
@ -28,6 +28,12 @@ export const minSearchLength = 2
|
||||||
export const regexFilterIn = /[^-]in:([a-z_-]+)/ig
|
export const regexFilterIn = /[^-]in:([a-z_-]+)/ig
|
||||||
export const regexFilterNot = /-in:([a-z_-]+)/ig
|
export const regexFilterNot = /-in:([a-z_-]+)/ig
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a cancel token
|
||||||
|
* @returns {CancelTokenSource}
|
||||||
|
*/
|
||||||
|
const createCancelToken = () => axios.CancelToken.source()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the list of available search providers
|
* Get the list of available search providers
|
||||||
*
|
*
|
||||||
|
@ -54,13 +60,20 @@ export async function getTypes() {
|
||||||
/**
|
/**
|
||||||
* Get the list of available search providers
|
* Get the list of available search providers
|
||||||
*
|
*
|
||||||
* @param {string} type the type to search
|
* @param {Object} options destructuring object
|
||||||
* @param {string} query the search
|
* @param {string} options.type the type to search
|
||||||
* @param {int|string|undefined} cursor the offset for paginated searches
|
* @param {string} options.query the search
|
||||||
* @returns {Promise}
|
* @param {int|string|undefined} options.cursor the offset for paginated searches
|
||||||
|
* @returns {Object} {request: Promise, cancel: Promise}
|
||||||
*/
|
*/
|
||||||
export function search(type, query, cursor) {
|
export function search({ type, query, cursor }) {
|
||||||
return axios.get(generateOcsUrl('search', 2) + `providers/${type}/search`, {
|
/**
|
||||||
|
* Generate an axios cancel token
|
||||||
|
*/
|
||||||
|
const cancelToken = createCancelToken()
|
||||||
|
|
||||||
|
const request = async() => axios.get(generateOcsUrl('search', 2) + `providers/${type}/search`, {
|
||||||
|
cancelToken: cancelToken.token,
|
||||||
params: {
|
params: {
|
||||||
term: query,
|
term: query,
|
||||||
cursor,
|
cursor,
|
||||||
|
@ -68,4 +81,9 @@ export function search(type, query, cursor) {
|
||||||
from: window.location.pathname.replace('/index.php', '') + window.location.search,
|
from: window.location.pathname.replace('/index.php', '') + window.location.search,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
request,
|
||||||
|
cancel: cancelToken.cancel,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/*
|
/**
|
||||||
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
|
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||||
*
|
*
|
||||||
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
|
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||||
|
|
|
@ -19,8 +19,9 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getRequestToken } from '@nextcloud/auth'
|
|
||||||
import { generateFilePath } from '@nextcloud/router'
|
import { generateFilePath } from '@nextcloud/router'
|
||||||
|
import { getLoggerBuilder } from '@nextcloud/logger'
|
||||||
|
import { getRequestToken } from '@nextcloud/auth'
|
||||||
import { translate as t, translatePlural as n } from '@nextcloud/l10n'
|
import { translate as t, translatePlural as n } from '@nextcloud/l10n'
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
@ -32,7 +33,17 @@ __webpack_nonce__ = btoa(getRequestToken())
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
__webpack_public_path__ = generateFilePath('core', '', 'js/')
|
__webpack_public_path__ = generateFilePath('core', '', 'js/')
|
||||||
|
|
||||||
|
const logger = getLoggerBuilder()
|
||||||
|
.setApp('unified-search')
|
||||||
|
.detectUser()
|
||||||
|
.build()
|
||||||
|
|
||||||
Vue.mixin({
|
Vue.mixin({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
logger,
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
t,
|
t,
|
||||||
n,
|
n,
|
||||||
|
|
|
@ -31,16 +31,31 @@
|
||||||
<Magnify class="unified-search__trigger" :size="20" fill-color="var(--color-primary-text)" />
|
<Magnify class="unified-search__trigger" :size="20" fill-color="var(--color-primary-text)" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- Search input -->
|
<!-- Search form & filters wrapper -->
|
||||||
<div class="unified-search__input-wrapper">
|
<div class="unified-search__input-wrapper">
|
||||||
|
<form class="unified-search__form"
|
||||||
|
role="search"
|
||||||
|
:class="{'icon-loading-small': isLoading}"
|
||||||
|
@submit.prevent.stop="onInputEnter"
|
||||||
|
@reset.prevent.stop="onReset">
|
||||||
|
<!-- Search input -->
|
||||||
<input ref="input"
|
<input ref="input"
|
||||||
v-model="query"
|
v-model="query"
|
||||||
class="unified-search__input"
|
class="unified-search__form-input"
|
||||||
type="search"
|
type="search"
|
||||||
|
:class="{'unified-search__form-input--with-reset': !!query}"
|
||||||
:placeholder="t('core', 'Search {types} …', { types: typesNames.join(', ').toLowerCase() })"
|
:placeholder="t('core', 'Search {types} …', { types: typesNames.join(', ').toLowerCase() })"
|
||||||
@input="onInputDebounced"
|
@input="onInputDebounced"
|
||||||
@keypress.enter.prevent.stop="onInputEnter"
|
@keypress.enter.prevent.stop="onInputEnter">
|
||||||
@search="onSearch">
|
|
||||||
|
<!-- Reset search button -->
|
||||||
|
<input v-if="!!query && !isLoading"
|
||||||
|
type="reset"
|
||||||
|
class="unified-search__form-reset icon-close"
|
||||||
|
:aria-label="t('core','Reset search')"
|
||||||
|
value="">
|
||||||
|
</form>
|
||||||
|
|
||||||
<!-- Search filters -->
|
<!-- Search filters -->
|
||||||
<Actions v-if="availableFilters.length > 1" class="unified-search__filters" placement="bottom">
|
<Actions v-if="availableFilters.length > 1" class="unified-search__filters" placement="bottom">
|
||||||
<ActionButton v-for="type in availableFilters"
|
<ActionButton v-for="type in availableFilters"
|
||||||
|
@ -57,7 +72,7 @@
|
||||||
<!-- Loading placeholders -->
|
<!-- Loading placeholders -->
|
||||||
<SearchResultPlaceholders v-if="isLoading" />
|
<SearchResultPlaceholders v-if="isLoading" />
|
||||||
|
|
||||||
<EmptyContent v-else-if="isValidQuery && isDoneSearching" icon="icon-search">
|
<EmptyContent v-else-if="isValidQuery" icon="icon-search">
|
||||||
{{ t('core', 'No results for {query}', {query}) }}
|
{{ t('core', 'No results for {query}', {query}) }}
|
||||||
</EmptyContent>
|
</EmptyContent>
|
||||||
|
|
||||||
|
@ -107,6 +122,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { emit } from '@nextcloud/event-bus'
|
import { emit } from '@nextcloud/event-bus'
|
||||||
import { minSearchLength, getTypes, search, defaultLimit, regexFilterIn, regexFilterNot } from '../services/UnifiedSearchService'
|
import { minSearchLength, getTypes, search, defaultLimit, regexFilterIn, regexFilterNot } from '../services/UnifiedSearchService'
|
||||||
|
import { showError } from '@nextcloud/dialogs'
|
||||||
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
|
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
|
||||||
import Actions from '@nextcloud/vue/dist/Components/Actions'
|
import Actions from '@nextcloud/vue/dist/Components/Actions'
|
||||||
import debounce from 'debounce'
|
import debounce from 'debounce'
|
||||||
|
@ -117,6 +133,10 @@ import HeaderMenu from '../components/HeaderMenu'
|
||||||
import SearchResult from '../components/UnifiedSearch/SearchResult'
|
import SearchResult from '../components/UnifiedSearch/SearchResult'
|
||||||
import SearchResultPlaceholders from '../components/UnifiedSearch/SearchResultPlaceholders'
|
import SearchResultPlaceholders from '../components/UnifiedSearch/SearchResultPlaceholders'
|
||||||
|
|
||||||
|
const REQUEST_FAILED = 0
|
||||||
|
const REQUEST_OK = 1
|
||||||
|
const REQUEST_CANCELED = 2
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'UnifiedSearch',
|
name: 'UnifiedSearch',
|
||||||
|
|
||||||
|
@ -134,10 +154,17 @@ export default {
|
||||||
return {
|
return {
|
||||||
types: [],
|
types: [],
|
||||||
|
|
||||||
|
// Cursors per types
|
||||||
cursors: {},
|
cursors: {},
|
||||||
|
// Various search limits per types
|
||||||
limits: {},
|
limits: {},
|
||||||
|
// Loading types
|
||||||
loading: {},
|
loading: {},
|
||||||
|
// Reached search types
|
||||||
reached: {},
|
reached: {},
|
||||||
|
// Pending cancellable requests
|
||||||
|
requests: [],
|
||||||
|
// List of all results
|
||||||
results: {},
|
results: {},
|
||||||
|
|
||||||
query: '',
|
query: '',
|
||||||
|
@ -255,7 +282,7 @@ export default {
|
||||||
|
|
||||||
async created() {
|
async created() {
|
||||||
this.types = await getTypes()
|
this.types = await getTypes()
|
||||||
console.debug('Unified Search initialized with the following providers', this.types)
|
this.logger.debug('Unified Search initialized with the following providers', this.types)
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
|
@ -289,24 +316,38 @@ export default {
|
||||||
this.types = await getTypes()
|
this.types = await getTypes()
|
||||||
},
|
},
|
||||||
onClose() {
|
onClose() {
|
||||||
emit('nextcloud:unified-search:close')
|
emit('nextcloud:unified-search.close')
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset the search state
|
* Reset the search state
|
||||||
*/
|
*/
|
||||||
resetSearch() {
|
onReset() {
|
||||||
emit('nextcloud:unified-search:reset')
|
emit('nextcloud:unified-search.reset')
|
||||||
|
this.logger.debug('Search reset')
|
||||||
this.query = ''
|
this.query = ''
|
||||||
this.resetState()
|
this.resetState()
|
||||||
|
this.focusInput()
|
||||||
},
|
},
|
||||||
resetState() {
|
async resetState() {
|
||||||
this.cursors = {}
|
this.cursors = {}
|
||||||
this.limits = {}
|
this.limits = {}
|
||||||
this.loading = {}
|
|
||||||
this.reached = {}
|
this.reached = {}
|
||||||
this.results = {}
|
this.results = {}
|
||||||
this.focused = null
|
this.focused = null
|
||||||
|
await this.cancelPendingRequests()
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel any ongoing searches
|
||||||
|
*/
|
||||||
|
async cancelPendingRequests() {
|
||||||
|
// Cloning so we can keep processing other requests
|
||||||
|
const requests = this.requests.slice(0)
|
||||||
|
this.requests = []
|
||||||
|
|
||||||
|
// Cancel all pending requests
|
||||||
|
await Promise.all(requests.map(cancel => cancel()))
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -319,18 +360,6 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Watch the search event on the input
|
|
||||||
* Used to detect the reset button press
|
|
||||||
* @param {Event} event the search event
|
|
||||||
*/
|
|
||||||
onSearch(event) {
|
|
||||||
// If value is empty, the reset button has been pressed
|
|
||||||
if (event.target.value === '') {
|
|
||||||
this.resetSearch()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If we have results already, open first one
|
* If we have results already, open first one
|
||||||
* If not, trigger the search again
|
* If not, trigger the search again
|
||||||
|
@ -349,7 +378,7 @@ export default {
|
||||||
*/
|
*/
|
||||||
async onInput() {
|
async onInput() {
|
||||||
// emit the search query
|
// emit the search query
|
||||||
emit('nextcloud:unified-search:search', { query: this.query })
|
emit('nextcloud:unified-search.search', { query: this.query })
|
||||||
|
|
||||||
// Do not search if not long enough
|
// Do not search if not long enough
|
||||||
if (this.query.trim() === '' || this.isShortQuery) {
|
if (this.query.trim() === '' || this.isShortQuery) {
|
||||||
|
@ -372,33 +401,38 @@ export default {
|
||||||
// Remove any filters from the query
|
// Remove any filters from the query
|
||||||
query = query.replace(regexFilterIn, '').replace(regexFilterNot, '')
|
query = query.replace(regexFilterIn, '').replace(regexFilterNot, '')
|
||||||
|
|
||||||
console.debug('Searching', query, 'in', types)
|
|
||||||
|
|
||||||
// Reset search if the query changed
|
// Reset search if the query changed
|
||||||
this.resetState()
|
await this.resetState()
|
||||||
|
this.$set(this.loading, 'all', true)
|
||||||
|
this.logger.debug(`Searching ${query} in`, types)
|
||||||
|
|
||||||
types.forEach(async type => {
|
Promise.all(types.map(async type => {
|
||||||
this.$set(this.loading, type, true)
|
try {
|
||||||
const request = await search(type, query)
|
// Init cancellable request
|
||||||
|
const { request, cancel } = search({ type, query })
|
||||||
|
this.requests.push(cancel)
|
||||||
|
|
||||||
|
// Fetch results
|
||||||
|
const { data } = await request()
|
||||||
|
|
||||||
// Process results
|
// Process results
|
||||||
if (request.data.ocs.data.entries.length > 0) {
|
if (data.ocs.data.entries.length > 0) {
|
||||||
this.$set(this.results, type, request.data.ocs.data.entries)
|
this.$set(this.results, type, data.ocs.data.entries)
|
||||||
} else {
|
} else {
|
||||||
this.$delete(this.results, type)
|
this.$delete(this.results, type)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save cursor if any
|
// Save cursor if any
|
||||||
if (request.data.ocs.data.cursor) {
|
if (data.ocs.data.cursor) {
|
||||||
this.$set(this.cursors, type, request.data.ocs.data.cursor)
|
this.$set(this.cursors, type, data.ocs.data.cursor)
|
||||||
} else if (!request.data.ocs.data.isPaginated) {
|
} else if (!data.ocs.data.isPaginated) {
|
||||||
// If no cursor and no pagination, we save the default amount
|
// If no cursor and no pagination, we save the default amount
|
||||||
// provided by server's initial state `defaultLimit`
|
// provided by server's initial state `defaultLimit`
|
||||||
this.$set(this.limits, type, this.defaultLimit)
|
this.$set(this.limits, type, this.defaultLimit)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we reached end of pagination
|
// Check if we reached end of pagination
|
||||||
if (request.data.ocs.data.entries.length < this.defaultLimit) {
|
if (data.ocs.data.entries.length < this.defaultLimit) {
|
||||||
this.$set(this.reached, type, true)
|
this.$set(this.reached, type, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -406,8 +440,26 @@ export default {
|
||||||
if (this.focused === null) {
|
if (this.focused === null) {
|
||||||
this.focused = 0
|
this.focused = 0
|
||||||
}
|
}
|
||||||
|
return REQUEST_OK
|
||||||
|
} catch (error) {
|
||||||
|
this.$delete(this.results, type)
|
||||||
|
|
||||||
this.$set(this.loading, type, false)
|
// If this is not a cancelled throw
|
||||||
|
if (error.response && error.response.status) {
|
||||||
|
this.logger.error(`Error searching for ${this.typesMap[type]}`, error)
|
||||||
|
showError(this.t('core', 'An error occurred while searching for {type}', { type: this.typesMap[type] }))
|
||||||
|
return REQUEST_FAILED
|
||||||
|
}
|
||||||
|
return REQUEST_CANCELED
|
||||||
|
}
|
||||||
|
})).then(results => {
|
||||||
|
// Do not declare loading finished if the request have been cancelled
|
||||||
|
// This means another search was triggered and we're therefore still loading
|
||||||
|
if (results.some(result => result === REQUEST_CANCELED)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// We finished all searches
|
||||||
|
this.loading = {}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onInputDebounced: debounce(function(e) {
|
onInputDebounced: debounce(function(e) {
|
||||||
|
@ -423,22 +475,27 @@ export default {
|
||||||
if (this.loading[type]) {
|
if (this.loading[type]) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.$set(this.loading, type, true)
|
|
||||||
|
|
||||||
if (this.cursors[type]) {
|
if (this.cursors[type]) {
|
||||||
const request = await search(type, this.query, this.cursors[type])
|
// Init cancellable request
|
||||||
|
const { request, cancel } = search({ type, query: this.query, cursor: this.cursors[type] })
|
||||||
|
this.requests.push(cancel)
|
||||||
|
|
||||||
|
// Fetch results
|
||||||
|
const { data } = await request()
|
||||||
|
|
||||||
// Save cursor if any
|
// Save cursor if any
|
||||||
if (request.data.ocs.data.cursor) {
|
if (data.ocs.data.cursor) {
|
||||||
this.$set(this.cursors, type, request.data.ocs.data.cursor)
|
this.$set(this.cursors, type, data.ocs.data.cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.data.ocs.data.entries.length > 0) {
|
// Process results
|
||||||
this.results[type].push(...request.data.ocs.data.entries)
|
if (data.ocs.data.entries.length > 0) {
|
||||||
|
this.results[type].push(...data.ocs.data.entries)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we reached end of pagination
|
// Check if we reached end of pagination
|
||||||
if (request.data.ocs.data.entries.length < this.defaultLimit) {
|
if (data.ocs.data.entries.length < this.defaultLimit) {
|
||||||
this.$set(this.reached, type, true)
|
this.$set(this.reached, type, true)
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
|
@ -460,8 +517,6 @@ export default {
|
||||||
this.focusIndex(this.focused)
|
this.focusIndex(this.focused)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$set(this.loading, type, false)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -574,6 +629,7 @@ export default {
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
$margin: 10px;
|
$margin: 10px;
|
||||||
|
$input-height: 34px;
|
||||||
$input-padding: 6px;
|
$input-padding: 6px;
|
||||||
|
|
||||||
.unified-search {
|
.unified-search {
|
||||||
|
@ -583,13 +639,13 @@ $input-padding: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__input-wrapper {
|
&__input-wrapper {
|
||||||
width: 100%;
|
|
||||||
position: sticky;
|
position: sticky;
|
||||||
// above search results
|
// above search results
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
top: 0;
|
top: 0;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
background-color: var(--color-main-background);
|
background-color: var(--color-main-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -601,11 +657,27 @@ $input-padding: 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__input {
|
&__form {
|
||||||
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 34px;
|
|
||||||
margin: $margin;
|
margin: $margin;
|
||||||
|
|
||||||
|
// Loading spinner
|
||||||
|
&::after {
|
||||||
|
right: $input-padding;
|
||||||
|
left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-input,
|
||||||
|
&-reset {
|
||||||
|
margin: $input-padding / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-input {
|
||||||
|
width: 100%;
|
||||||
|
height: $input-height;
|
||||||
padding: $input-padding;
|
padding: $input-padding;
|
||||||
|
|
||||||
&,
|
&,
|
||||||
&[placeholder],
|
&[placeholder],
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
|
@ -613,6 +685,40 @@ $input-padding: 6px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hide webkit clear search
|
||||||
|
&::-webkit-search-decoration,
|
||||||
|
&::-webkit-search-cancel-button,
|
||||||
|
&::-webkit-search-results-button,
|
||||||
|
&::-webkit-search-results-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ellipsis earlier if reset button is here
|
||||||
|
.icon-loading-small &,
|
||||||
|
&--with-reset {
|
||||||
|
padding-right: $input-height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-reset {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: $input-height - $input-padding;
|
||||||
|
height: $input-height - $input-padding;
|
||||||
|
padding: 0;
|
||||||
|
opacity: .5;
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
margin-right: 0;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__filters {
|
&__filters {
|
||||||
|
|
Loading…
Reference in New Issue