summaryrefslogtreecommitdiffstats
path: root/core/src
diff options
context:
space:
mode:
authorJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>2020-08-04 10:00:27 +0200
committerJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>2020-08-04 21:36:22 +0200
commit71b62c4203a25beefeab73f73668919c813e3a50 (patch)
treee75b6b0338ed800ddf88bfe27ce6703045c18e48 /core/src
parent6eced42b7a40f5b0ea0489244583219d0ee2e7af (diff)
downloadnextcloud-server-71b62c4203a25beefeab73f73668919c813e3a50.tar.gz
nextcloud-server-71b62c4203a25beefeab73f73668919c813e3a50.zip
Show mime icon, bump bundles, make the SearchResultEntry class non-abstract, Fix header search icon, various fixes
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
Diffstat (limited to 'core/src')
-rw-r--r--core/src/components/UnifiedSearch/SearchResult.vue35
-rw-r--r--core/src/components/UnifiedSearch/SearchResultPlaceholder.vue68
-rw-r--r--core/src/services/UnifiedSearchService.js28
-rw-r--r--core/src/views/UnifiedSearch.vue73
4 files changed, 184 insertions, 20 deletions
diff --git a/core/src/components/UnifiedSearch/SearchResult.vue b/core/src/components/UnifiedSearch/SearchResult.vue
index 832770c9abe..ffd4e2bf2f2 100644
--- a/core/src/components/UnifiedSearch/SearchResult.vue
+++ b/core/src/components/UnifiedSearch/SearchResult.vue
@@ -2,22 +2,28 @@
<a :href="resourceUrl || '#'"
class="unified-search__result"
:class="{
- 'unified-search__result--focused': focused
+ 'unified-search__result--focused': focused,
}"
@click="reEmitEvent"
@focus="reEmitEvent">
+
<!-- Icon describing the result -->
<div class="unified-search__result-icon"
:class="{
'unified-search__result-icon--rounded': rounded,
'unified-search__result-icon--no-preview': !hasValidThumbnail && !loaded,
'unified-search__result-icon--with-thumbnail': hasValidThumbnail && loaded,
- [iconClass]: true
+ [icon]: !loaded && !isIconUrl,
+ }"
+ :style="{
+ backgroundImage: isIconUrl ? `url(${icon})` : '',
}"
role="img">
+
<img v-if="hasValidThumbnail"
+ v-show="loaded"
:src="thumbnailUrl"
- :alt="t('core', 'Thumbnail for {result}', {result: title})"
+ alt=""
@error="onError"
@load="onLoad">
</div>
@@ -59,7 +65,7 @@ export default {
type: String,
default: null,
},
- iconClass: {
+ icon: {
type: String,
default: '',
},
@@ -90,6 +96,24 @@ export default {
}
},
+ computed: {
+ isIconUrl() {
+ // If we're facing an absolute url
+ if (this.icon.startsWith('/')) {
+ return true
+ }
+
+ // Otherwise, let's check if this is a valid url
+ try {
+ // eslint-disable-next-line no-new
+ new URL(this.icon)
+ } catch {
+ return false
+ }
+ return true
+ },
+ },
+
watch: {
// Make sure to reset state on change even when vue recycle the component
thumbnailUrl() {
@@ -148,6 +172,7 @@ $margin: 10px;
width: $clickable-area;
height: $clickable-area;
border-radius: var(--border-radius);
+ background-repeat: no-repeat;
background-position: center center;
background-size: 32px;
&--rounded {
@@ -195,7 +220,7 @@ $margin: 10px;
&-line-two {
overflow: hidden;
flex: 1 1 100%;
- margin: 0;
+ margin: 1px 0;
white-space: nowrap;
text-overflow: ellipsis;
// Use the same color as the `a`
diff --git a/core/src/components/UnifiedSearch/SearchResultPlaceholder.vue b/core/src/components/UnifiedSearch/SearchResultPlaceholder.vue
new file mode 100644
index 00000000000..7eb0915b359
--- /dev/null
+++ b/core/src/components/UnifiedSearch/SearchResultPlaceholder.vue
@@ -0,0 +1,68 @@
+<template>
+ <svg
+ class="unified-search__result-placeholder"
+ xmlns="http://www.w3.org/2000/svg"
+ fill="url(#unified-search__result-placeholder-gradient)">
+ <defs>
+ <linearGradient id="unified-search__result-placeholder-gradient">
+ <stop offset="0%" stop-color="#ededed"><animate attributeName="stop-color"
+ values="#ededed; #ededed; #cccccc; #cccccc; #ededed"
+ dur="2s"
+ repeatCount="indefinite" /></stop>
+ <stop offset="100%" stop-color="#cccccc"><animate attributeName="stop-color"
+ values="#cccccc; #ededed; #ededed; #cccccc; #cccccc"
+ dur="2s"
+ repeatCount="indefinite" /></stop>
+ </linearGradient>
+ </defs>
+ <rect class="unified-search__result-placeholder-icon" />
+ <rect class="unified-search__result-placeholder-line-one" />
+ <rect class="unified-search__result-placeholder-line-two" :style="{width: `calc(${randWidth}%)`}" />
+ </svg>
+</template>
+
+<script>
+export default {
+ name: 'SearchResultPlaceholder',
+
+ data() {
+ return {
+ randWidth: Math.floor(Math.random() * 20) + 30,
+ }
+ },
+}
+</script>
+
+<style lang="scss" scoped>
+$clickable-area: 44px;
+$margin: 10px;
+
+.unified-search__result-placeholder {
+ width: calc(100% - 2 * #{$margin});
+ height: $clickable-area;
+ margin: $margin;
+
+ &-icon {
+ width: $clickable-area;
+ height: $clickable-area;
+ rx: var(--border-radius);
+ ry: var(--border-radius);
+ }
+
+ &-line-one,
+ &-line-two {
+ width: calc(100% - #{$margin + $clickable-area});
+ height: 1em;
+ x: $margin + $clickable-area;
+ }
+
+ &-line-one {
+ y: 5px;
+ }
+
+ &-line-two {
+ y: 25px;
+ }
+}
+
+</style>
diff --git a/core/src/services/UnifiedSearchService.js b/core/src/services/UnifiedSearchService.js
index 2e63f19767d..ff9a4aebe2a 100644
--- a/core/src/services/UnifiedSearchService.js
+++ b/core/src/services/UnifiedSearchService.js
@@ -24,15 +24,18 @@ import { loadState } from '@nextcloud/initial-state'
import axios from '@nextcloud/axios'
export const defaultLimit = loadState('unified-search', 'limit-default')
+export const activeApp = loadState('core', 'active-app')
/**
* Get the list of available search providers
+ *
+ * @returns {Array}
*/
export async function getTypes() {
try {
const { data } = await axios.get(generateUrl('/search/providers'))
if (Array.isArray(data) && data.length > 0) {
- return data
+ return sortProviders(data)
}
} catch (error) {
console.error(error)
@@ -41,6 +44,29 @@ export async function getTypes() {
}
/**
+ * Sort the providers by the current active app
+ *
+ * @param {Array} providers the providers list
+ * @returns {Array}
+ */
+export function sortProviders(providers) {
+ providers.sort((a, b) => {
+ if (a.id.startsWith(activeApp) && b.id.startsWith(activeApp)) {
+ return a.order - b.order
+ }
+
+ if (a.id.startsWith(activeApp)) {
+ return -1
+ }
+ if (b.id.startsWith(activeApp)) {
+ return 1
+ }
+ return 0
+ })
+ return providers
+}
+
+/**
* Get the list of available search providers
*
* @param {string} type the type to search
diff --git a/core/src/views/UnifiedSearch.vue b/core/src/views/UnifiedSearch.vue
index 4535e1fde5a..d7ad58ae202 100644
--- a/core/src/views/UnifiedSearch.vue
+++ b/core/src/views/UnifiedSearch.vue
@@ -27,7 +27,7 @@
@close="onClose">
<!-- Header icon -->
<template #trigger>
- <span class="icon-search-white" />
+ <Magnify class="unified-search__trigger" :size="20" fill-color="var(--color-primary-text)" />
</template>
<!-- Search input -->
@@ -36,17 +36,20 @@
v-model="query"
class="unified-search__input"
type="search"
- :placeholder="t('core', 'Search for {types} …', { types: typesNames.join(', ') })"
+ :placeholder="t('core', 'Search {types} …', { types: typesNames.join(', ').toLowerCase() })"
@input="onInputDebounced"
@keypress.enter.prevent.stop="onInputEnter">
</div>
- <EmptyContent v-if="isLoading" icon="icon-loading">
- {{ t('core', 'Searching …') }}
- </EmptyContent>
+ <template v-if="!hasResults">
+ <!-- Loading placeholders -->
+ <ul v-if="isLoading">
+ <li v-for="placeholder in [1, 2, 3]" :key="placeholder">
+ <SearchResultPlaceholder />
+ </li>
+ </ul>
- <template v-else-if="!hasResults">
- <EmptyContent v-if="isValidQuery && isDoneSearching" icon="icon-search">
+ <EmptyContent v-else-if="isValidQuery && isDoneSearching" icon="icon-search">
{{ t('core', 'No results for {query}', {query}) }}
</EmptyContent>
@@ -64,7 +67,7 @@
<!-- Grouped search results -->
<template v-else>
- <ul v-for="(list, type, typesIndex) in results"
+ <ul v-for="(list, type, typesIndex) in orderedResults"
:key="type"
class="unified-search__results"
:class="`unified-search__results-${type}`"
@@ -94,13 +97,14 @@
</template>
<script>
-import { getTypes, search, defaultLimit } from '../services/UnifiedSearchService'
+import { getTypes, search, defaultLimit, activeApp } from '../services/UnifiedSearchService'
import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent'
-
+import Magnify from 'vue-material-design-icons/Magnify'
import debounce from 'debounce'
import HeaderMenu from '../components/HeaderMenu'
import SearchResult from '../components/UnifiedSearch/SearchResult'
+import SearchResultPlaceholder from '../components/UnifiedSearch/SearchResultPlaceholder'
const minSearchLength = 2
@@ -110,7 +114,9 @@ export default {
components: {
EmptyContent,
HeaderMenu,
+ Magnify,
SearchResult,
+ SearchResultPlaceholder,
},
data() {
@@ -126,6 +132,7 @@ export default {
query: '',
focused: null,
+ activeApp,
defaultLimit,
minSearchLength,
@@ -156,6 +163,32 @@ export default {
},
/**
+ * Order results by putting the active app first
+ * @returns {Object}
+ */
+ orderedResults() {
+ const ordered = {}
+ Object.keys(this.results)
+ .sort((a, b) => {
+ if (a.startsWith(activeApp) && b.startsWith(activeApp)) {
+ return this.typesMap[a].order - this.typesMap[b].order
+ }
+ if (a.startsWith(activeApp)) {
+ return -1
+ }
+ if (b.startsWith(activeApp)) {
+ return 1
+ }
+ return 0
+ })
+ .forEach(type => {
+ ordered[type] = this.results[type]
+ })
+
+ return ordered
+ },
+
+ /**
* Is the current search too short
* @returns {boolean}
*/
@@ -176,7 +209,7 @@ export default {
* @returns {boolean}
*/
isDoneSearching() {
- return Object.values(this.reached).indexOf(false) === -1
+ return Object.values(this.reached).every(state => state === false)
},
/**
@@ -184,7 +217,7 @@ export default {
* @returns {boolean}
*/
isLoading() {
- return Object.values(this.loading).indexOf(true) !== -1
+ return Object.values(this.loading).some(state => state === true)
},
},
@@ -465,6 +498,11 @@ $margin: 10px;
$input-padding: 6px;
.unified-search {
+ &__trigger {
+ width: 20px;
+ height: 20px;
+ }
+
&__input-wrapper {
position: sticky;
// above search results
@@ -479,7 +517,14 @@ $input-padding: 6px;
height: 34px;
margin: $margin;
padding: $input-padding;
- text-overflow: ellipsis;
+ &,
+ &[placeholder],
+ &::placeholder {
+ overflow: hidden;
+ text-overflow:ellipsis;
+ white-space: nowrap;
+ }
+
}
&__results {
@@ -488,7 +533,7 @@ $input-padding: 6px;
margin: $margin;
margin-left: $margin + $input-padding;
content: attr(aria-label);
- color: var(--color-primary);
+ color: var(--color-primary-element);
}
}