mirror of
https://github.com/nextcloud/server.git
synced 2026-05-21 09:35:30 -04:00
Merge pull request #40823 from nextcloud/39162-global-search-2.0
New UI for global search
This commit is contained in:
commit
95e5642fa0
97 changed files with 1413 additions and 146 deletions
|
|
@ -2395,4 +2395,11 @@ $CONFIG = [
|
|||
* Defaults to ``true``
|
||||
*/
|
||||
'reference_opengraph' => true,
|
||||
|
||||
/**
|
||||
* Enable use of old unified search
|
||||
*
|
||||
* Defaults to ``false``
|
||||
*/
|
||||
'unified_search.enabled' => false,
|
||||
];
|
||||
|
|
|
|||
98
core/src/components/GlobalSearch/CustomDateRangeModal.vue
Normal file
98
core/src/components/GlobalSearch/CustomDateRangeModal.vue
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
<template>
|
||||
<NcModal v-if="isModalOpen"
|
||||
id="global-search"
|
||||
:name="t('core', 'Date range filter')"
|
||||
:show.sync="isModalOpen"
|
||||
:size="'small'"
|
||||
:clear-view-delay="0"
|
||||
:title="t('Date range filter')"
|
||||
@close="closeModal">
|
||||
<!-- Custom date range -->
|
||||
<div class="global-search-custom-date-modal">
|
||||
<h1>{{ t('core', 'Date range filter') }}</h1>
|
||||
<div class="global-search-custom-date-modal__pickers">
|
||||
<NcDateTimePicker :id="'globalsearch-custom-date-range-start'"
|
||||
v-model="dateFilter.startFrom"
|
||||
:max="new Date()"
|
||||
:label="t('core', 'Pick start date')"
|
||||
type="date" />
|
||||
<NcDateTimePicker :id="'globalsearch-custom-date-range-end'"
|
||||
v-model="dateFilter.endAt"
|
||||
:max="new Date()"
|
||||
:label="t('core', 'Pick end date')"
|
||||
type="date" />
|
||||
</div>
|
||||
<NcButton @click="applyCustomRange">
|
||||
{{ t('core', 'Apply range') }}
|
||||
<template #icon>
|
||||
<CalendarRangeIcon :size="20" />
|
||||
</template>
|
||||
</NcButton>
|
||||
</div>
|
||||
</NcModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
|
||||
import NcDateTimePicker from '@nextcloud/vue/dist/Components/NcDateTimePickerNative.js'
|
||||
import NcModal from '@nextcloud/vue/dist/Components/NcModal.js'
|
||||
import CalendarRangeIcon from 'vue-material-design-icons/CalendarRange.vue'
|
||||
|
||||
export default {
|
||||
name: 'CustomDateRangeModal',
|
||||
components: {
|
||||
NcButton,
|
||||
NcModal,
|
||||
CalendarRangeIcon,
|
||||
NcDateTimePicker,
|
||||
},
|
||||
props: {
|
||||
isOpen: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dateFilter: { startFrom: null, endAt: null },
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isModalOpen: {
|
||||
get() {
|
||||
return this.isOpen
|
||||
},
|
||||
set(value) {
|
||||
this.$emit('update:is-open', value)
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
closeModal() {
|
||||
this.isModalOpen = false
|
||||
},
|
||||
applyCustomRange() {
|
||||
this.$emit('set:custom-date-range', this.dateFilter)
|
||||
this.closeModal()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.global-search-custom-date-modal {
|
||||
padding: 10px 20px 10px 20px;
|
||||
|
||||
h1 {
|
||||
font-size: 16px;
|
||||
font-weight: bolder;
|
||||
line-height: 2em;
|
||||
}
|
||||
|
||||
&__pickers {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
71
core/src/components/GlobalSearch/SearchFilterChip.vue
Normal file
71
core/src/components/GlobalSearch/SearchFilterChip.vue
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<template>
|
||||
<div class="chip">
|
||||
<span class="icon">
|
||||
<slot name="icon" />
|
||||
<span v-if="pretext.length"> {{ pretext }} : </span>
|
||||
</span>
|
||||
<span class="text">{{ text }}</span>
|
||||
<span class="close-icon" @click="deleteChip">
|
||||
<CloseIcon :size="16" />
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CloseIcon from 'vue-material-design-icons/CloseThick.vue'
|
||||
|
||||
export default {
|
||||
name: 'SearchFilterChip',
|
||||
components: {
|
||||
CloseIcon,
|
||||
},
|
||||
props: {
|
||||
text: String,
|
||||
pretext: String,
|
||||
},
|
||||
methods: {
|
||||
deleteChip() {
|
||||
this.$emit('delete', this.filter)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.chip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2px 4px;
|
||||
border: 1px solid var(--color-primary-element-light);
|
||||
border-radius: 20px;
|
||||
background-color: var(--color-primary-element-light);
|
||||
margin: 2px;
|
||||
font-size: 10px;
|
||||
font-weight: bolder;
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-right: 5px;
|
||||
|
||||
img {
|
||||
width: 20px;
|
||||
padding: 2px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
cursor: pointer;
|
||||
|
||||
:hover {
|
||||
border-radius: 4px;
|
||||
padding: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
157
core/src/components/GlobalSearch/SearchableList.vue
Normal file
157
core/src/components/GlobalSearch/SearchableList.vue
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
<!--
|
||||
- @copyright 2023 Marco Ambrosini <marcoambrosini@proton.me>
|
||||
-
|
||||
- @author Marco Ambrosini <marcoambrosini@proton.me>
|
||||
-
|
||||
- @license AGPL-3.0-or-later
|
||||
-
|
||||
- This program is free software: you can redistribute it and/or modify
|
||||
- it under the terms of the GNU Affero General Public License as
|
||||
- published by the Free Software Foundation, either version 3 of the
|
||||
- License, or (at your option) any later version.
|
||||
-
|
||||
- This program is distributed in the hope that it will be useful,
|
||||
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
- GNU Affero General Public License for more details.
|
||||
-
|
||||
- You should have received a copy of the GNU Affero General Public License
|
||||
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-
|
||||
-->
|
||||
|
||||
<template>
|
||||
<NcPopover :shown="opened">
|
||||
<template #trigger>
|
||||
<slot name="trigger" />
|
||||
</template>
|
||||
<div class="searchable-list__wrapper">
|
||||
<NcTextField :value.sync="searchTerm"
|
||||
:label="labelText"
|
||||
trailing-button-icon="close"
|
||||
:show-trailing-button="searchTerm !== ''"
|
||||
@trailing-button-click="clearSearch">
|
||||
<Magnify :size="20" />
|
||||
</NcTextField>
|
||||
<ul v-if="filteredList.length > 0" class="searchable-list__list">
|
||||
<li v-for="element in filteredList"
|
||||
:key="element.id"
|
||||
:title="element.displayName"
|
||||
role="button">
|
||||
<NcButton alignment="start"
|
||||
type="tertiary"
|
||||
:wide="true"
|
||||
@click="itemSelected(element)">
|
||||
<template #icon>
|
||||
<NcAvatar :user="element.user" :show-user-status="false" :hide-favorite="false" />
|
||||
</template>
|
||||
{{ element.displayName }}
|
||||
</NcButton>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-else class="searchable-list__empty-content">
|
||||
<NcEmptyContent :name="emptyContentText">
|
||||
<template #icon>
|
||||
<AlertCircleOutline />
|
||||
</template>
|
||||
</NcEmptyContent>
|
||||
</div>
|
||||
</div>
|
||||
</NcPopover>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { NcPopover, NcTextField, NcAvatar, NcEmptyContent, NcButton } from '@nextcloud/vue'
|
||||
|
||||
import AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'
|
||||
import Magnify from 'vue-material-design-icons/Magnify.vue'
|
||||
|
||||
export default {
|
||||
name: 'SearchableList',
|
||||
|
||||
components: {
|
||||
NcPopover,
|
||||
NcTextField,
|
||||
Magnify,
|
||||
AlertCircleOutline,
|
||||
NcAvatar,
|
||||
NcEmptyContent,
|
||||
NcButton,
|
||||
},
|
||||
|
||||
props: {
|
||||
labelText: {
|
||||
type: String,
|
||||
default: 'this is a label',
|
||||
},
|
||||
|
||||
searchList: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
|
||||
emptyContentText: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
opened: false,
|
||||
error: false,
|
||||
searchTerm: '',
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
filteredList() {
|
||||
return this.searchList.filter((element) => {
|
||||
if (!this.searchTerm.toLowerCase().length) {
|
||||
return true
|
||||
}
|
||||
return ['displayName'].some(prop => element[prop].toLowerCase().includes(this.searchTerm.toLowerCase()))
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
clearSearch() {
|
||||
this.searchTerm = ''
|
||||
},
|
||||
itemSelected(element) {
|
||||
this.$emit('item-selected', element)
|
||||
this.clearSearch()
|
||||
this.opened = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.searchable-list {
|
||||
&__wrapper {
|
||||
padding: calc(var(--default-grid-baseline) * 3);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
&__list {
|
||||
width: 100%;
|
||||
max-height: 284px;
|
||||
overflow-y: auto;
|
||||
margin-top: var(--default-grid-baseline);
|
||||
padding: var(--default-grid-baseline);
|
||||
|
||||
:deep(.button-vue) {
|
||||
border-radius: var(--border-radius-large) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__empty-content {
|
||||
margin-top: calc(var(--default-grid-baseline) * 3);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
55
core/src/global-search.js
Normal file
55
core/src/global-search.js
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* @copyright Copyright (c) 2020 Fon E. Noel NFEBE <fenn25.fn@gmail.com>
|
||||
*
|
||||
* @author Fon E. Noel NFEBE <fenn25.fn@gmail.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
import { getLoggerBuilder } from '@nextcloud/logger'
|
||||
import { getRequestToken } from '@nextcloud/auth'
|
||||
import { translate as t, translatePlural as n } from '@nextcloud/l10n'
|
||||
import Vue from 'vue'
|
||||
|
||||
import GlobalSearch from './views/GlobalSearch.vue'
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
__webpack_nonce__ = btoa(getRequestToken())
|
||||
|
||||
const logger = getLoggerBuilder()
|
||||
.setApp('global-search')
|
||||
.detectUser()
|
||||
.build()
|
||||
|
||||
Vue.mixin({
|
||||
data() {
|
||||
return {
|
||||
logger,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
t,
|
||||
n,
|
||||
},
|
||||
})
|
||||
|
||||
export default new Vue({
|
||||
el: '#global-search',
|
||||
// eslint-disable-next-line vue/match-component-file-name
|
||||
name: 'GlobalSearchRoot',
|
||||
render: h => h(GlobalSearch),
|
||||
})
|
||||
107
core/src/services/GlobalSearchService.js
Normal file
107
core/src/services/GlobalSearchService.js
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
/**
|
||||
* @copyright 2023, Fon E. Noel NFEBE <fenn25.fn@gmail.com>
|
||||
*
|
||||
* @author Fon E. Noel NFEBE <fenn25.fn@gmail.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
import { generateOcsUrl, generateUrl } from '@nextcloud/router'
|
||||
import axios from '@nextcloud/axios'
|
||||
|
||||
/**
|
||||
* Create a cancel token
|
||||
*
|
||||
* @return {import('axios').CancelTokenSource}
|
||||
*/
|
||||
const createCancelToken = () => axios.CancelToken.source()
|
||||
|
||||
/**
|
||||
* Get the list of available search providers
|
||||
*
|
||||
* @return {Promise<Array>}
|
||||
*/
|
||||
export async function getProviders() {
|
||||
try {
|
||||
const { data } = await axios.get(generateOcsUrl('search/providers'), {
|
||||
params: {
|
||||
// Sending which location we're currently at
|
||||
from: window.location.pathname.replace('/index.php', '') + window.location.search,
|
||||
},
|
||||
})
|
||||
if ('ocs' in data && 'data' in data.ocs && Array.isArray(data.ocs.data) && data.ocs.data.length > 0) {
|
||||
// Providers are sorted by the api based on their order key
|
||||
return data.ocs.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of available search providers
|
||||
*
|
||||
* @param {object} options destructuring object
|
||||
* @param {string} options.type the type to search
|
||||
* @param {string} options.query the search
|
||||
* @param {number|string|undefined} options.cursor the offset for paginated searches
|
||||
* @param {string} options.since the search
|
||||
* @param {string} options.until the search
|
||||
* @param {string} options.limit the search
|
||||
* @param {string} options.person the search
|
||||
* @return {object} {request: Promise, cancel: Promise}
|
||||
*/
|
||||
export function search({ type, query, cursor, since, until, limit, person }) {
|
||||
/**
|
||||
* Generate an axios cancel token
|
||||
*/
|
||||
const cancelToken = createCancelToken()
|
||||
|
||||
const request = async () => axios.get(generateOcsUrl('search/providers/{type}/search', { type }), {
|
||||
cancelToken: cancelToken.token,
|
||||
params: {
|
||||
term: query,
|
||||
cursor,
|
||||
since,
|
||||
until,
|
||||
limit,
|
||||
person,
|
||||
// Sending which location we're currently at
|
||||
from: window.location.pathname.replace('/index.php', '') + window.location.search,
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
request,
|
||||
cancel: cancelToken.cancel,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of active contacts
|
||||
*
|
||||
* @param {object} filter filter contacts by string
|
||||
* @param filter.searchTerm
|
||||
* @return {object} {request: Promise}
|
||||
*/
|
||||
export async function getContacts({ searchTerm }) {
|
||||
const { data: { contacts } } = await axios.post(generateUrl('/contactsmenu/contacts'), {
|
||||
filter: searchTerm,
|
||||
})
|
||||
return contacts
|
||||
}
|
||||
93
core/src/views/GlobalSearch.vue
Normal file
93
core/src/views/GlobalSearch.vue
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
<!--
|
||||
- @copyright Copyright (c) 2020 Fon E. Noel NFEBE <fenn25.fn@gmail.com>
|
||||
-
|
||||
- @author Fon E. Noel NFEBE <fenn25.fn@gmail.com>
|
||||
-
|
||||
- @license GNU AGPL version 3 or any later version
|
||||
-
|
||||
- This program is free software: you can redistribute it and/or modify
|
||||
- it under the terms of the GNU Affero General Public License as
|
||||
- published by the Free Software Foundation, either version 3 of the
|
||||
- License, or (at your option) any later version.
|
||||
-
|
||||
- This program is distributed in the hope that it will be useful,
|
||||
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
- GNU Affero General Public License for more details.
|
||||
-
|
||||
- You should have received a copy of the GNU Affero General Public License
|
||||
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-
|
||||
-->
|
||||
<template>
|
||||
<div class="header-menu">
|
||||
<NcButton class="global-search__button" :aria-label="t('core', 'Global search')" @click="toggleGlobalSearch">
|
||||
<template #icon>
|
||||
<Magnify class="global-search__trigger" :size="22" />
|
||||
</template>
|
||||
</NcButton>
|
||||
<GlobalSearchModal :is-visible="showGlobalSearch" :class="'global-search-modal'" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
|
||||
import Magnify from 'vue-material-design-icons/Magnify.vue'
|
||||
import GlobalSearchModal from './GlobalSearchModal.vue'
|
||||
|
||||
export default {
|
||||
name: 'GlobalSearch',
|
||||
components: {
|
||||
NcButton,
|
||||
Magnify,
|
||||
GlobalSearchModal,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showGlobalSearch: false,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
console.debug('Global search initialized!')
|
||||
},
|
||||
methods: {
|
||||
toggleGlobalSearch() {
|
||||
this.showGlobalSearch = !this.showGlobalSearch
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.header-menu {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.global-search__button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: var(--header-height);
|
||||
// height: var(--header-height);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
opacity: .85;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
filter: none !important;
|
||||
color: var(--color-primary-text) !important;
|
||||
|
||||
&:hover {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.global-search-modal {
|
||||
::v-deep .modal-container {
|
||||
height: 80%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
648
core/src/views/GlobalSearchModal.vue
Normal file
648
core/src/views/GlobalSearchModal.vue
Normal file
|
|
@ -0,0 +1,648 @@
|
|||
<template>
|
||||
<NcModal v-if="isVisible"
|
||||
id="global-search"
|
||||
:name="t('core', 'Global search')"
|
||||
:show.sync="isVisible"
|
||||
:clear-view-delay="0"
|
||||
:title="t('Global search')"
|
||||
@close="closeModal">
|
||||
<CustomDateRangeModal :is-open="showDateRangeModal"
|
||||
:class="'global-search__date-range'"
|
||||
@set:custom-date-range="setCustomDateRange"
|
||||
@update:is-open="showDateRangeModal = $event" />
|
||||
<!-- Global search form -->
|
||||
<div ref="globalSearch" class="global-search-modal">
|
||||
<h1>{{ t('core', 'Global search') }}</h1>
|
||||
<NcInputField :value.sync="searchQuery"
|
||||
type="text"
|
||||
:label="t('core', 'Search apps, files, tags, messages') + '...'"
|
||||
@update:value="debouncedFind" />
|
||||
<div class="global-search-modal__filters">
|
||||
<NcActions :menu-name="t('core', 'Apps and Settings')" :open.sync="providerActionMenuIsOpen">
|
||||
<template #icon>
|
||||
<ListBox :size="20" />
|
||||
</template>
|
||||
<NcActionButton v-for="provider in providers" :key="provider.id" @click="addProviderFilter(provider)">
|
||||
<template #icon>
|
||||
<img :src="provider.icon">
|
||||
</template>
|
||||
{{ t('core', provider.name) }}
|
||||
</NcActionButton>
|
||||
</NcActions>
|
||||
<NcActions :menu-name="t('core', 'Modified')" :open.sync="dateActionMenuIsOpen">
|
||||
<template #icon>
|
||||
<CalendarRangeIcon :size="20" />
|
||||
</template>
|
||||
<NcActionButton @click="applyQuickDateRange('today')">
|
||||
{{ t('core', 'Today') }}
|
||||
</NcActionButton>
|
||||
<NcActionButton @click="applyQuickDateRange('7days')">
|
||||
{{ t('core', 'Last 7 days') }}
|
||||
</NcActionButton>
|
||||
<NcActionButton @click="applyQuickDateRange('30days')">
|
||||
{{ t('core', 'Last 30 days') }}
|
||||
</NcActionButton>
|
||||
<NcActionButton @click="applyQuickDateRange('thisyear')">
|
||||
{{ t('core', 'This year') }}
|
||||
</NcActionButton>
|
||||
<NcActionButton @click="applyQuickDateRange('lastyear')">
|
||||
{{ t('core', 'Last year') }}
|
||||
</NcActionButton>
|
||||
<NcActionButton @click="applyQuickDateRange('custom')">
|
||||
{{ t('core', 'Custom date range') }}
|
||||
</NcActionButton>
|
||||
</NcActions>
|
||||
<SearchableList :label-text="t('core', 'Search people')"
|
||||
:search-list="userContacts"
|
||||
:empty-content-text="t('core', 'Not found')"
|
||||
@item-selected="applyPersonFilter">
|
||||
<template #trigger>
|
||||
<NcButton>
|
||||
<template #icon>
|
||||
<AccountGroup :size="20" />
|
||||
</template>
|
||||
{{ t('core', 'People') }}
|
||||
</NcButton>
|
||||
</template>
|
||||
</SearchableList>
|
||||
</div>
|
||||
<div class="global-search-modal__filters-applied">
|
||||
<FilterChip v-for="filter in filters"
|
||||
:key="filter.id"
|
||||
:text="filter.name ?? filter.text"
|
||||
:pretext="''"
|
||||
@delete="removeFilter(filter)">
|
||||
<template #icon>
|
||||
<NcAvatar v-if="filter.type === 'person'"
|
||||
:user="filter.user"
|
||||
:show-user-status="false"
|
||||
:hide-favorite="false" />
|
||||
<CalendarRangeIcon v-else-if="filter.type === 'date'" />
|
||||
<img v-else :src="filter.icon" alt="">
|
||||
</template>
|
||||
</FilterChip>
|
||||
</div>
|
||||
<div v-if="searchQuery.length === 0">
|
||||
<NcEmptyContent :name="t('core', 'Start typing in search')">
|
||||
<template #icon>
|
||||
<MagnifyIcon />
|
||||
</template>
|
||||
</NcEmptyContent>
|
||||
</div>
|
||||
<div v-for="providerResult in results" :key="providerResult.id" class="global-search-modal__results">
|
||||
<div class="results">
|
||||
<div class="result-title">
|
||||
<span>{{ providerResult.provider }}</span>
|
||||
</div>
|
||||
<ul class="result-items">
|
||||
<NcListItem v-for="(result, index) in providerResult.results"
|
||||
:key="index"
|
||||
class="result-items__item"
|
||||
:name="result.title ?? ''"
|
||||
:bold="false"
|
||||
@click="openResult(result)">
|
||||
<template #icon>
|
||||
<div v-if="result.icon"
|
||||
class="result-items__item-icon"
|
||||
:class="{
|
||||
'result-items__item-icon--no-preview': !isValidUrl(result.thumbnailUrl),
|
||||
'result-items__item-icon--with-thumbnail': isValidUrl(result.thumbnailUrl),
|
||||
[result.icon]: !isValidUrl(result.icon),
|
||||
}"
|
||||
:style="{
|
||||
backgroundImage: isValidUrl(result.icon) ? `url(${result.icon})` : '',
|
||||
}">
|
||||
<img v-if="result.thumbnailUrl" :src="result.thumbnailUrl" class="">
|
||||
</div>
|
||||
</template>
|
||||
<template #subname>
|
||||
{{ result.subline }}
|
||||
</template>
|
||||
</NcListItem>
|
||||
</ul>
|
||||
<div class="result-footer">
|
||||
<NcButton type="tertiary-no-background" @click="loadMoreResultsForProvider(providerResult.id)">
|
||||
Load more results
|
||||
<template #icon>
|
||||
<DotsHorizontalIcon :size="20" />
|
||||
</template>
|
||||
</NcButton>
|
||||
<NcButton alignment="end-reverse" type="tertiary-no-background">
|
||||
Search in {{ providerResult.provider }}
|
||||
<template #icon>
|
||||
<ArrowRight :size="20" />
|
||||
</template>
|
||||
</NcButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</NcModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ArrowRight from 'vue-material-design-icons/ArrowRight.vue'
|
||||
import AccountGroup from 'vue-material-design-icons/AccountGroup.vue'
|
||||
import CalendarRangeIcon from 'vue-material-design-icons/CalendarRange.vue'
|
||||
import CustomDateRangeModal from '../components/GlobalSearch/CustomDateRangeModal.vue'
|
||||
import DotsHorizontalIcon from 'vue-material-design-icons/DotsHorizontal.vue'
|
||||
import FilterChip from '../components/GlobalSearch/SearchFilterChip.vue'
|
||||
import ListBox from 'vue-material-design-icons/ListBox.vue'
|
||||
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
|
||||
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
|
||||
import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js'
|
||||
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
|
||||
import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
|
||||
import NcInputField from '@nextcloud/vue/dist/Components/NcInputField.js'
|
||||
import NcModal from '@nextcloud/vue/dist/Components/NcModal.js'
|
||||
import NcListItem from '@nextcloud/vue/dist/Components/NcListItem.js'
|
||||
import MagnifyIcon from 'vue-material-design-icons/Magnify.vue'
|
||||
import SearchableList from '../components/GlobalSearch/SearchableList.vue'
|
||||
|
||||
import debounce from 'debounce'
|
||||
import { getProviders, search as globalSearch, getContacts } from '../services/GlobalSearchService.js'
|
||||
|
||||
export default {
|
||||
name: 'GlobalSearchModal',
|
||||
components: {
|
||||
ArrowRight,
|
||||
AccountGroup,
|
||||
CalendarRangeIcon,
|
||||
CustomDateRangeModal,
|
||||
DotsHorizontalIcon,
|
||||
FilterChip,
|
||||
ListBox,
|
||||
NcActions,
|
||||
NcActionButton,
|
||||
NcAvatar,
|
||||
NcButton,
|
||||
NcEmptyContent,
|
||||
NcModal,
|
||||
NcListItem,
|
||||
NcInputField,
|
||||
MagnifyIcon,
|
||||
SearchableList,
|
||||
},
|
||||
props: {
|
||||
isVisible: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
providers: [],
|
||||
providerActionMenuIsOpen: false,
|
||||
dateActionMenuIsOpen: false,
|
||||
providerResultLimit: 5,
|
||||
dateFilter: { id: 'date', type: 'date', text: '', startFrom: null, endAt: null },
|
||||
personFilter: { id: 'person', type: 'person', name: '' },
|
||||
dateFilterIsApplied: false,
|
||||
personFilterIsApplied: false,
|
||||
filteredProviders: [],
|
||||
searchQuery: '',
|
||||
placesFilter: '',
|
||||
dateTimeFilter: null,
|
||||
filters: [],
|
||||
results: [],
|
||||
contacts: [],
|
||||
debouncedFind: debounce(this.find, 300),
|
||||
showDateRangeModal: false,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
userContacts: {
|
||||
get() {
|
||||
return this.contacts
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
},
|
||||
mounted() {
|
||||
getProviders().then((providers) => {
|
||||
this.providers = providers
|
||||
console.debug('Search providers', this.providers)
|
||||
})
|
||||
getContacts({ filter: '' }).then((contacts) => {
|
||||
this.contacts = this.mapContacts(contacts)
|
||||
console.debug('Contacts', this.contacts)
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
find(query) {
|
||||
if (query.length === 0) {
|
||||
this.results = []
|
||||
return
|
||||
}
|
||||
const newResults = []
|
||||
const providersToSearch = this.filteredProviders.length > 0 ? this.filteredProviders : this.providers
|
||||
const searchProvider = (provider, filters) => {
|
||||
const params = {
|
||||
type: provider.id,
|
||||
query,
|
||||
cursor: null,
|
||||
}
|
||||
|
||||
if (filters.dateFilterIsApplied) {
|
||||
if (provider.filters.since && provider.filters.until) {
|
||||
params.since = this.dateFilter.startFrom
|
||||
params.until = this.dateFilter.endAt
|
||||
} else {
|
||||
// Date filter is applied but provider does not support it, no need to search provider
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (filters.personFilterIsApplied) {
|
||||
if (provider.filters.person) {
|
||||
params.person = this.personFilter.id
|
||||
} else {
|
||||
// Person filter is applied but provider does not support it, no need to search provider
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (this.providerResultLimit > 5) {
|
||||
params.limit = this.providerResultLimit
|
||||
}
|
||||
|
||||
const request = globalSearch(params).request
|
||||
|
||||
request().then((response) => {
|
||||
newResults.push({
|
||||
id: provider.id,
|
||||
provider: provider.name,
|
||||
results: response.data.ocs.data.entries,
|
||||
})
|
||||
|
||||
console.debug('New results', newResults)
|
||||
console.debug('Global search results:', this.results)
|
||||
|
||||
this.updateResults(newResults)
|
||||
})
|
||||
}
|
||||
providersToSearch.forEach(provider => {
|
||||
const dateFilterIsApplied = this.dateFilterIsApplied
|
||||
const personFilterIsApplied = this.personFilterIsApplied
|
||||
searchProvider(provider, { dateFilterIsApplied, personFilterIsApplied })
|
||||
})
|
||||
|
||||
},
|
||||
updateResults(newResults) {
|
||||
let updatedResults = [...this.results]
|
||||
// If filters are applied, remove any previous results for providers that are not in current filters
|
||||
if (this.filters.length > 0) {
|
||||
updatedResults = updatedResults.filter(result => {
|
||||
return this.filters.some(filter => filter.id === result.id)
|
||||
})
|
||||
}
|
||||
// Process the new results
|
||||
newResults.forEach(newResult => {
|
||||
const existingResultIndex = updatedResults.findIndex(result => result.id === newResult.id)
|
||||
if (existingResultIndex !== -1) {
|
||||
if (newResult.results.length === 0) {
|
||||
// If the new results data has no matches for and existing result, remove the existing result
|
||||
updatedResults.splice(existingResultIndex, 1)
|
||||
} else {
|
||||
// If input triggered a change in existing results, update existing result
|
||||
updatedResults.splice(existingResultIndex, 1, newResult)
|
||||
}
|
||||
} else if (newResult.results.length > 0) {
|
||||
// Push the new result to the array only if its results array is not empty
|
||||
updatedResults.push(newResult)
|
||||
}
|
||||
})
|
||||
const sortedResults = updatedResults.slice(0)
|
||||
// Order results according to provider preference
|
||||
sortedResults.sort((a, b) => {
|
||||
const aProvider = this.providers.find(provider => provider.id === a.id)
|
||||
const bProvider = this.providers.find(provider => provider.id === b.id)
|
||||
const aOrder = aProvider ? aProvider.order : 0
|
||||
const bOrder = bProvider ? bProvider.order : 0
|
||||
return aOrder - bOrder
|
||||
})
|
||||
this.results = sortedResults
|
||||
},
|
||||
openResult(result) {
|
||||
if (result.resourceUrl) {
|
||||
window.location = result.resourceUrl
|
||||
}
|
||||
},
|
||||
mapContacts(contacts) {
|
||||
return contacts.map(contact => {
|
||||
return {
|
||||
// id: contact.id,
|
||||
// name: '',
|
||||
displayName: contact.fullName,
|
||||
isNoUser: false,
|
||||
subname: contact.emailAddresses[0] ? contact.emailAddresses[0] : '',
|
||||
icon: '',
|
||||
user: contact.id,
|
||||
}
|
||||
})
|
||||
},
|
||||
filterContacts(query) {
|
||||
getContacts({ filter: query }).then((contacts) => {
|
||||
this.contacts = this.mapContacts(contacts)
|
||||
console.debug(`Contacts filtered by ${query}`, this.contacts)
|
||||
})
|
||||
},
|
||||
applyPersonFilter(person) {
|
||||
this.personFilterIsApplied = true
|
||||
const existingPersonFilter = this.filters.findIndex(filter => filter.id === person.id)
|
||||
if (existingPersonFilter === -1) {
|
||||
this.personFilter.id = person.id
|
||||
this.personFilter.user = person.user
|
||||
this.personFilter.name = person.displayName
|
||||
this.filters.push(this.personFilter)
|
||||
} else {
|
||||
this.filters[existingPersonFilter].id = person.id
|
||||
this.filters[existingPersonFilter].user = person.user
|
||||
this.filters[existingPersonFilter].name = person.displayName
|
||||
}
|
||||
|
||||
this.debouncedFind(this.searchQuery)
|
||||
console.debug('Person filter applied', person)
|
||||
},
|
||||
loadMoreResultsForProvider(providerId) {
|
||||
this.providerResultLimit += 5
|
||||
this.filters = this.filters.filter(filter => filter.type !== 'provider')
|
||||
const provider = this.providers.find(provider => provider.id === providerId)
|
||||
this.addProviderFilter(provider, true)
|
||||
},
|
||||
addProviderFilter(providerFilter, loadMoreResultsForProvider = false) {
|
||||
if (!providerFilter.id) return
|
||||
this.providerResultLimit = loadMoreResultsForProvider ? this.providerResultLimit : 5
|
||||
this.providerActionMenuIsOpen = false
|
||||
const existingFilter = this.filteredProviders.find(existing => existing.id === providerFilter.id)
|
||||
if (!existingFilter) {
|
||||
this.filteredProviders.push({ id: providerFilter.id, name: providerFilter.name, icon: providerFilter.icon, type: 'provider' })
|
||||
}
|
||||
this.filters = this.syncProviderFilters(this.filters, this.filteredProviders)
|
||||
console.debug('Search filters (newly added)', this.filters)
|
||||
this.debouncedFind(this.searchQuery)
|
||||
},
|
||||
removeFilter(filter) {
|
||||
if (filter.type === 'provider') {
|
||||
for (let i = 0; i < this.filteredProviders.length; i++) {
|
||||
if (this.filteredProviders[i].id === filter.id) {
|
||||
this.filteredProviders.splice(i, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
this.filters = this.syncProviderFilters(this.filters, this.filteredProviders)
|
||||
console.debug('Search filters (recently removed)', this.filters)
|
||||
|
||||
} else {
|
||||
for (let i = 0; i < this.filters.length; i++) {
|
||||
if (this.filters[i].id === 'date') {
|
||||
this.dateFilterIsApplied = false
|
||||
this.filters.splice(i, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
this.debouncedFind(this.searchQuery)
|
||||
},
|
||||
syncProviderFilters(firstArray, secondArray) {
|
||||
// Create a copy of the first array to avoid modifying it directly.
|
||||
const synchronizedArray = firstArray.slice()
|
||||
// Remove items from the synchronizedArray that are not in the secondArray.
|
||||
synchronizedArray.forEach((item, index) => {
|
||||
const itemId = item.id
|
||||
if (item.type === 'provider') {
|
||||
if (!secondArray.some(secondItem => secondItem.id === itemId)) {
|
||||
synchronizedArray.splice(index, 1)
|
||||
}
|
||||
}
|
||||
})
|
||||
// Add items to the synchronizedArray that are in the secondArray but not in the firstArray.
|
||||
secondArray.forEach(secondItem => {
|
||||
const itemId = secondItem.id
|
||||
if (secondItem.type === 'provider') {
|
||||
if (!synchronizedArray.some(item => item.id === itemId)) {
|
||||
synchronizedArray.push(secondItem)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return synchronizedArray
|
||||
},
|
||||
updateDateFilter() {
|
||||
const currFilterIndex = this.filters.findIndex(filter => filter.id === 'date')
|
||||
if (currFilterIndex !== -1) {
|
||||
this.filters[currFilterIndex] = this.dateFilter
|
||||
} else {
|
||||
this.filters.push(this.dateFilter)
|
||||
}
|
||||
this.dateFilterIsApplied = true
|
||||
this.debouncedFind(this.searchQuery)
|
||||
},
|
||||
applyQuickDateRange(range) {
|
||||
this.dateActionMenuIsOpen = false
|
||||
const today = new Date()
|
||||
let endDate = today
|
||||
let startDate
|
||||
switch (range) {
|
||||
case 'today':
|
||||
// For 'Today', both start and end are set to today
|
||||
startDate = today
|
||||
this.dateFilter.text = t('core', 'Today')
|
||||
break
|
||||
case '7days':
|
||||
// For 'Last 7 days', start date is 7 days ago, end is today
|
||||
startDate = new Date(today)
|
||||
startDate.setDate(today.getDate() - 7)
|
||||
this.dateFilter.text = t('core', 'Last 7 days')
|
||||
break
|
||||
case '30days':
|
||||
// For 'Last 30 days', start date is 30 days ago, end is today
|
||||
startDate = new Date(today)
|
||||
startDate.setDate(today.getDate() - 30)
|
||||
this.dateFilter.text = t('core', 'Last 30 days')
|
||||
break
|
||||
case 'thisyear':
|
||||
// For 'This year', start date is the first day of the year, end is today
|
||||
startDate = new Date(today.getFullYear(), 0, 1)
|
||||
this.dateFilter.text = t('core', 'This year')
|
||||
break
|
||||
case 'lastyear':
|
||||
// For 'Last year', start date is the first day of the previous year, end is the last day of the previous year
|
||||
startDate = new Date(today.getFullYear() - 1, 0, 1)
|
||||
endDate = new Date(today.getFullYear() - 1, 11, 31)
|
||||
this.dateFilter.text = t('core', 'Last year')
|
||||
break
|
||||
case 'custom':
|
||||
this.showDateRangeModal = true
|
||||
return
|
||||
default:
|
||||
return
|
||||
|
||||
}
|
||||
this.dateFilter.startFrom = startDate
|
||||
this.dateFilter.endAt = endDate
|
||||
this.updateDateFilter()
|
||||
|
||||
},
|
||||
setCustomDateRange(event) {
|
||||
console.debug('Custom date range', event)
|
||||
this.dateFilter.startFrom = event.startFrom
|
||||
this.dateFilter.endAt = event.endAt
|
||||
this.dateFilter.text = t('core', `Between ${this.dateFilter.startFrom.toLocaleDateString()} and ${this.dateFilter.endAt.toLocaleDateString()}`)
|
||||
this.updateDateFilter()
|
||||
},
|
||||
isValidUrl(icon) {
|
||||
return /^https?:\/\//.test(icon) || icon.startsWith('//')
|
||||
},
|
||||
closeModal() {
|
||||
this.searchQuery = ''
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use "sass:math";
|
||||
$clickable-area: 44px;
|
||||
$margin: 10px;
|
||||
|
||||
.global-search-modal {
|
||||
padding: 10px 20px 10px 20px;
|
||||
height: 60%;
|
||||
|
||||
h1 {
|
||||
font-size: 16px;
|
||||
font-weight: bolder;
|
||||
line-height: 2em;
|
||||
}
|
||||
|
||||
&__filters {
|
||||
display: flex;
|
||||
padding-top: 5px;
|
||||
justify-content: space-between;
|
||||
|
||||
>*:not(:last-child) {
|
||||
// flex: 1;
|
||||
margin-right: 0.5m;
|
||||
}
|
||||
|
||||
>* {
|
||||
button {
|
||||
min-width: 160px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&__filters-applied {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
&__results {
|
||||
padding: 10px;
|
||||
|
||||
.results {
|
||||
|
||||
.result-title {
|
||||
span {
|
||||
color: var(--color-primary-element);
|
||||
font-weight: bolder;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.result-items {
|
||||
::v-deep &__item {
|
||||
a {
|
||||
border-radius: 12px;
|
||||
border: 2px solid transparent;
|
||||
border-radius: var(--border-radius-large) !important;
|
||||
|
||||
&--focused {
|
||||
background-color: var(--color-background-hover);
|
||||
}
|
||||
|
||||
&:active,
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: var(--color-background-hover);
|
||||
border: 2px solid var(--color-border-maxcontrast);
|
||||
}
|
||||
|
||||
* {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&-icon {
|
||||
overflow: hidden;
|
||||
width: $clickable-area;
|
||||
height: $clickable-area;
|
||||
border-radius: var(--border-radius);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: 32px;
|
||||
|
||||
&--rounded {
|
||||
border-radius: math.div($clickable-area, 2);
|
||||
}
|
||||
|
||||
&--no-preview {
|
||||
background-size: 32px;
|
||||
}
|
||||
|
||||
&--with-thumbnail {
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
&--with-thumbnail:not(&--rounded) {
|
||||
// compensate for border
|
||||
max-width: $clickable-area - 2px;
|
||||
max-height: $clickable-area - 2px;
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
img {
|
||||
// Make sure to keep ratio
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.result-footer {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
div.v-popper__wrapper {
|
||||
ul {
|
||||
li {
|
||||
::v-deep button.action-button {
|
||||
align-items: center !important;
|
||||
|
||||
img {
|
||||
width: 24px;
|
||||
margin: 0 4px;
|
||||
filter: var(--background-invert-if-bright);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -68,6 +68,7 @@ p($theme->getTitle());
|
|||
</div>
|
||||
|
||||
<div class="header-right">
|
||||
<div id="global-search"></div>
|
||||
<div id="unified-search"></div>
|
||||
<div id="notifications"></div>
|
||||
<div id="contactsmenu"></div>
|
||||
|
|
|
|||
6
dist/7683-7683.js → dist/1436-1436.js
vendored
6
dist/7683-7683.js → dist/1436-1436.js
vendored
File diff suppressed because one or more lines are too long
1
dist/1436-1436.js.map
vendored
Normal file
1
dist/1436-1436.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
dist/3240-3240.js
vendored
2
dist/3240-3240.js
vendored
File diff suppressed because one or more lines are too long
1
dist/3240-3240.js.map
vendored
1
dist/3240-3240.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/3998-3998.js
vendored
4
dist/3998-3998.js
vendored
|
|
@ -1,3 +1,3 @@
|
|||
/*! For license information please see 3998-3998.js.LICENSE.txt */
|
||||
"use strict";(self.webpackChunknextcloud=self.webpackChunknextcloud||[]).push([[3998],{83998:(e,n,c)=>{c.d(n,{FilePickerVue:()=>s});const s=(0,c(20144).defineAsyncComponent)((()=>Promise.all([c.e(7874),c.e(3240),c.e(9064)]).then(c.bind(c,39064))))}}]);
|
||||
//# sourceMappingURL=3998-3998.js.map?v=a49373c9d79e30e60f7b
|
||||
"use strict";(self.webpackChunknextcloud=self.webpackChunknextcloud||[]).push([[3998],{83998:(e,n,c)=>{c.d(n,{FilePickerVue:()=>s});const s=(0,c(20144).defineAsyncComponent)((()=>Promise.all([c.e(7874),c.e(8928)]).then(c.bind(c,41253))))}}]);
|
||||
//# sourceMappingURL=3998-3998.js.map?v=308c269b5c7e8357a090
|
||||
2
dist/3998-3998.js.map
vendored
2
dist/3998-3998.js.map
vendored
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"3998-3998.js?v=a49373c9d79e30e60f7b","mappings":";oIAsBA,MAAMA,GAAI,kCAAE,IAAM","sources":["webpack:///nextcloud/node_modules/@nextcloud/dialogs/dist/chunks/index-22ace80c.mjs"],"sourcesContent":["import { defineAsyncComponent as e } from \"vue\";\n/**\n * @copyright Copyright (c) 2023 Ferdinand Thiessen <opensource@fthiessen.de>\n *\n * @author Ferdinand Thiessen <opensource@fthiessen.de>\n *\n * @license AGPL-3.0-or-later\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n *\n */\nconst i = e(() => import(\"./FilePicker-5074f4ba.mjs\"));\nexport {\n i as FilePickerVue\n};\n"],"names":["i"],"sourceRoot":""}
|
||||
{"version":3,"file":"3998-3998.js?v=308c269b5c7e8357a090","mappings":";oIAsBA,MAAMA,GAAI,kCAAE,IAAM","sources":["webpack:///nextcloud/node_modules/@nextcloud/dialogs/dist/chunks/index-22ace80c.mjs"],"sourcesContent":["import { defineAsyncComponent as e } from \"vue\";\n/**\n * @copyright Copyright (c) 2023 Ferdinand Thiessen <opensource@fthiessen.de>\n *\n * @author Ferdinand Thiessen <opensource@fthiessen.de>\n *\n * @license AGPL-3.0-or-later\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n *\n */\nconst i = e(() => import(\"./FilePicker-5074f4ba.mjs\"));\nexport {\n i as FilePickerVue\n};\n"],"names":["i"],"sourceRoot":""}
|
||||
4
dist/6318-6318.js
vendored
4
dist/6318-6318.js
vendored
File diff suppressed because one or more lines are too long
2
dist/6318-6318.js.map
vendored
2
dist/6318-6318.js.map
vendored
File diff suppressed because one or more lines are too long
1
dist/7683-7683.js.map
vendored
1
dist/7683-7683.js.map
vendored
File diff suppressed because one or more lines are too long
3
dist/8928-8928.js
vendored
Normal file
3
dist/8928-8928.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -87,28 +87,6 @@
|
|||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 Ferdinand Thiessen <opensource@fthiessen.de>
|
||||
*
|
||||
* @author Ferdinand Thiessen <opensource@fthiessen.de>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 Ferdinand Thiessen <opensource@fthiessen.de>
|
||||
*
|
||||
1
dist/8928-8928.js.map
vendored
Normal file
1
dist/8928-8928.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
3
dist/9064-9064.js
vendored
3
dist/9064-9064.js
vendored
File diff suppressed because one or more lines are too long
1
dist/9064-9064.js.map
vendored
1
dist/9064-9064.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/comments-comments-app.js
vendored
4
dist/comments-comments-app.js
vendored
File diff suppressed because one or more lines are too long
2
dist/comments-comments-app.js.map
vendored
2
dist/comments-comments-app.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/core-common.js
vendored
4
dist/core-common.js
vendored
File diff suppressed because one or more lines are too long
2
dist/core-common.js.map
vendored
2
dist/core-common.js.map
vendored
File diff suppressed because one or more lines are too long
3
dist/core-global-search.js
vendored
Normal file
3
dist/core-global-search.js
vendored
Normal file
File diff suppressed because one or more lines are too long
43
dist/core-global-search.js.LICENSE.txt
vendored
Normal file
43
dist/core-global-search.js.LICENSE.txt
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* @copyright 2023, Fon E. Noel NFEBE <fenn25.fn@gmail.com>
|
||||
*
|
||||
* @author Fon E. Noel NFEBE <fenn25.fn@gmail.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2020 Fon E. Noel NFEBE <fenn25.fn@gmail.com>
|
||||
*
|
||||
* @author Fon E. Noel NFEBE <fenn25.fn@gmail.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
1
dist/core-global-search.js.map
vendored
Normal file
1
dist/core-global-search.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
4
dist/core-login.js
vendored
4
dist/core-login.js
vendored
File diff suppressed because one or more lines are too long
2
dist/core-login.js.map
vendored
2
dist/core-login.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/core-main.js
vendored
4
dist/core-main.js
vendored
File diff suppressed because one or more lines are too long
2
dist/core-main.js.map
vendored
2
dist/core-main.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/core-profile.js
vendored
4
dist/core-profile.js
vendored
File diff suppressed because one or more lines are too long
2
dist/core-profile.js.map
vendored
2
dist/core-profile.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/core-unified-search.js
vendored
4
dist/core-unified-search.js
vendored
File diff suppressed because one or more lines are too long
2
dist/core-unified-search.js.map
vendored
2
dist/core-unified-search.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/dashboard-main.js
vendored
4
dist/dashboard-main.js
vendored
File diff suppressed because one or more lines are too long
2
dist/dashboard-main.js.map
vendored
2
dist/dashboard-main.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/dav-settings-personal-availability.js
vendored
4
dist/dav-settings-personal-availability.js
vendored
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
4
dist/files-init.js
vendored
4
dist/files-init.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-init.js.map
vendored
2
dist/files-init.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files-main.js
vendored
4
dist/files-main.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-main.js.map
vendored
2
dist/files-main.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files-personal-settings.js
vendored
4
dist/files-personal-settings.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-personal-settings.js.map
vendored
2
dist/files-personal-settings.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files-reference-files.js
vendored
4
dist/files-reference-files.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-reference-files.js.map
vendored
2
dist/files-reference-files.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files-sidebar.js
vendored
4
dist/files-sidebar.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-sidebar.js.map
vendored
2
dist/files-sidebar.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files_external-init.js
vendored
4
dist/files_external-init.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files_external-init.js.map
vendored
2
dist/files_external-init.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files_reminders-init.js
vendored
4
dist/files_reminders-init.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files_reminders-init.js.map
vendored
2
dist/files_reminders-init.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files_sharing-files_sharing_tab.js
vendored
4
dist/files_sharing-files_sharing_tab.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files_sharing-files_sharing_tab.js.map
vendored
2
dist/files_sharing-files_sharing_tab.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files_sharing-personal-settings.js
vendored
4
dist/files_sharing-personal-settings.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files_sharing-personal-settings.js.map
vendored
2
dist/files_sharing-personal-settings.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files_versions-files_versions.js
vendored
4
dist/files_versions-files_versions.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files_versions-files_versions.js.map
vendored
2
dist/files_versions-files_versions.js.map
vendored
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
4
dist/settings-vue-settings-admin-security.js
vendored
4
dist/settings-vue-settings-admin-security.js
vendored
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
4
dist/settings-vue-settings-personal-info.js
vendored
4
dist/settings-vue-settings-personal-info.js
vendored
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
4
dist/theming-admin-theming.js
vendored
4
dist/theming-admin-theming.js
vendored
File diff suppressed because one or more lines are too long
2
dist/theming-admin-theming.js.map
vendored
2
dist/theming-admin-theming.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/theming-personal-theming.js
vendored
4
dist/theming-personal-theming.js
vendored
File diff suppressed because one or more lines are too long
2
dist/theming-personal-theming.js.map
vendored
2
dist/theming-personal-theming.js.map
vendored
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
4
dist/user_status-menu.js
vendored
4
dist/user_status-menu.js
vendored
File diff suppressed because one or more lines are too long
2
dist/user_status-menu.js.map
vendored
2
dist/user_status-menu.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/weather_status-weather-status.js
vendored
4
dist/weather_status-weather-status.js
vendored
File diff suppressed because one or more lines are too long
2
dist/weather_status-weather-status.js.map
vendored
2
dist/weather_status-weather-status.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/workflowengine-workflowengine.js
vendored
4
dist/workflowengine-workflowengine.js
vendored
File diff suppressed because one or more lines are too long
2
dist/workflowengine-workflowengine.js.map
vendored
2
dist/workflowengine-workflowengine.js.map
vendored
File diff suppressed because one or more lines are too long
|
|
@ -106,11 +106,18 @@ class TemplateLayout extends \OC_Template {
|
|||
|
||||
$this->initialState->provideInitialState('core', 'active-app', $this->navigationManager->getActiveEntry());
|
||||
$this->initialState->provideInitialState('core', 'apps', $this->navigationManager->getAll());
|
||||
$this->initialState->provideInitialState('unified-search', 'limit-default', (int)$this->config->getAppValue('core', 'unified-search.limit-default', (string)SearchQuery::LIMIT_DEFAULT));
|
||||
$this->initialState->provideInitialState('unified-search', 'min-search-length', (int)$this->config->getAppValue('core', 'unified-search.min-search-length', (string)1));
|
||||
$this->initialState->provideInitialState('unified-search', 'live-search', $this->config->getAppValue('core', 'unified-search.live-search', 'yes') === 'yes');
|
||||
Util::addScript('core', 'unified-search', 'core');
|
||||
|
||||
/*
|
||||
* NB : Unified search enabled, defaults to true since new advanced search is
|
||||
* unstable. Once we think otherwise, the default should be false.
|
||||
*/
|
||||
if ($this->config->getSystemValueBool('unified_search.enabled', true)) {
|
||||
$this->initialState->provideInitialState('unified-search', 'limit-default', (int)$this->config->getAppValue('core', 'unified-search.limit-default', (string)SearchQuery::LIMIT_DEFAULT));
|
||||
$this->initialState->provideInitialState('unified-search', 'min-search-length', (int)$this->config->getAppValue('core', 'unified-search.min-search-length', (string)1));
|
||||
$this->initialState->provideInitialState('unified-search', 'live-search', $this->config->getAppValue('core', 'unified-search.live-search', 'yes') === 'yes');
|
||||
Util::addScript('core', 'unified-search', 'core');
|
||||
} else {
|
||||
Util::addScript('core', 'global-search', 'core');
|
||||
}
|
||||
// Set body data-theme
|
||||
$this->assign('enabledThemes', []);
|
||||
if (\OC::$server->getAppManager()->isEnabledForUser('theming') && class_exists('\OCA\Theming\Service\ThemesService')) {
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ module.exports = {
|
|||
profile: path.join(__dirname, 'core/src', 'profile.js'),
|
||||
recommendedapps: path.join(__dirname, 'core/src', 'recommendedapps.js'),
|
||||
systemtags: path.resolve(__dirname, 'core/src', 'systemtags/merged-systemtags.js'),
|
||||
'global-search': path.join(__dirname, 'core/src', 'global-search.js'),
|
||||
'unified-search': path.join(__dirname, 'core/src', 'unified-search.js'),
|
||||
'unsupported-browser': path.join(__dirname, 'core/src', 'unsupported-browser.js'),
|
||||
'unsupported-browser-redirect': path.join(__dirname, 'core/src', 'unsupported-browser-redirect.js'),
|
||||
|
|
|
|||
Loading…
Reference in a new issue