Compare commits

...

29 Commits

Author SHA1 Message Date
Roeland Jago Douma d247f198a9 Merge pull request #23000 from nextcloud/version/20.0.0/RC2
20 RC2
2020-09-24 13:41:12 +02:00
Roeland Jago Douma e29b5c6d92 Merge pull request #23026 from nextcloud/backport/23014/stable20
[stable20] Make 'Reasons to use Nextcloud' button translatable, fix #22977
2020-09-24 11:31:39 +02:00
Roeland Jago Douma fa0f815dda Merge pull request #23001 from nextcloud/backport/22940/stable20
[stable20] Never copy the share link when the password is forced
2020-09-24 09:37:22 +02:00
Jan C. Borchardt 2be71de5a1 Make 'Reasons to use Nextcloud' button translatable, fix #22977
Signed-off-by: Jan C. Borchardt <hey@jancborchardt.net>
2020-09-24 07:26:18 +00:00
Roeland Jago Douma 724276c7a7 Merge pull request #23018 from nextcloud/backport/23016/stable20
[stable20] Don't log a known shared section
2020-09-24 09:24:42 +02:00
Joas Schilling 26603c7cdd Don't log "duplicate section" for the shared "connected-accounts" section
Signed-off-by: Joas Schilling <coding@schilljs.com>
2020-09-23 11:03:50 +00:00
Joas Schilling 7edced3807 Merge pull request #23009 from nextcloud/backport/23008/stable20
[stable20] Add padding to the empty content and center it
2020-09-23 10:46:50 +02:00
Joas Schilling 3daddfce14 Add padding to the empty content and center it
Signed-off-by: Joas Schilling <coding@schilljs.com>
2020-09-22 19:16:30 +00:00
Joas Schilling 9838d54cb5 Never copy the share link when the password is forced
Signed-off-by: Joas Schilling <coding@schilljs.com>
2020-09-22 11:45:58 +00:00
Roeland Jago Douma 46babff37b Merge pull request #22928 from nextcloud/backport/22915/stable20
[stable20] improve handling of out of space errors for smb
2020-09-22 13:08:21 +02:00
Roeland Jago Douma ff8eb8dfa2 20 RC2
Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
2020-09-22 13:05:52 +02:00
John Molakvoæ 289bc8e345 Merge pull request #22946 from nextcloud/backport/22924/stable20
[stable20] Make sure most app names don’t ellipsize, fix #22845, fix #22219
2020-09-19 00:02:01 +02:00
Morris Jobke a0e8a78945 Fix transifex name of dashboard app
Signed-off-by: Morris Jobke <hey@morrisjobke.de>
2020-09-18 20:47:38 +02:00
Morris Jobke 065f3e125e Merge pull request #22950 from nextcloud/backport/22949/stable20
[stable20] Add transifex config for all new apps
2020-09-18 20:43:29 +02:00
Morris Jobke bf58dcb247 Add transifex config for all new apps
Signed-off-by: Morris Jobke <hey@morrisjobke.de>
2020-09-18 18:42:09 +00:00
Jan C. Borchardt ada7ad6930 Make sure most app names don’t ellipsize, fix #22845, fix #22219
Signed-off-by: Jan C. Borchardt <hey@jancborchardt.net>
2020-09-18 16:18:48 +00:00
Morris Jobke 5d81cb36b5 Merge pull request #22938 from nextcloud/backport/22911/stable20
[stable20] Allow to run occ preview:repair in parallel
2020-09-18 18:14:29 +02:00
Morris Jobke 03d00afe31 Merge pull request #22935 from nextcloud/backport/22868/stable20
[stable20] Fix/unified search papercuts
2020-09-18 18:14:17 +02:00
Morris Jobke b35daf665f Migrate verbose messages to inline syntax of writeln()
Signed-off-by: Morris Jobke <hey@morrisjobke.de>
2020-09-18 11:13:52 +00:00
Morris Jobke 55393939ce Show lock messages only in verbose mode
Signed-off-by: Morris Jobke <hey@morrisjobke.de>
2020-09-18 11:13:52 +00:00
Morris Jobke e9e5a02d7c Allow to run occ preview:repair in parallel
Signed-off-by: Morris Jobke <hey@morrisjobke.de>
2020-09-18 11:13:52 +00:00
John Molakvoæ (skjnldsv) 7ad973494a Prevent empty search placeholder
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
2020-09-18 09:36:41 +00:00
John Molakvoæ (skjnldsv) 5646144fae Build assets and fix unified search event syntax
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
2020-09-18 09:36:40 +00:00
John Molakvoæ (skjnldsv) 839f597921 Properly show loading state if there are still pending requests
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
2020-09-18 09:36:39 +00:00
John Molakvoæ (skjnldsv) 3c6319f275 Properly use form role=search and unify reset button
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
2020-09-18 09:36:39 +00:00
John Molakvoæ (skjnldsv) 2672f5da59 Fix loading error catch
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
2020-09-18 09:36:39 +00:00
Roeland Jago Douma 2000e2faa5 Merge pull request #22932 from nextcloud/backport/22925/stable20
[stable20] Dashboard: Fix accessibility skip links
2020-09-18 11:32:38 +02:00
Jan C. Borchardt 0504873a8a Dashboard: Fix accessibility skip links
Signed-off-by: Jan C. Borchardt <hey@jancborchardt.net>
2020-09-18 06:35:28 +00:00
Robin Appelman eba4723428 improve handling of out of space errors for smb
Signed-off-by: Robin Appelman <robin@icewind.nl>
2020-09-17 18:47:33 +00:00
56 changed files with 376 additions and 146 deletions
+48
View File
@@ -133,3 +133,51 @@ file_filter = translationfiles/<lang>/accessibility.po
source_file = translationfiles/templates/accessibility.pot
source_lang = en
type = PO
[nextcloud.provisioning_api]
file_filter = translationfiles/<lang>/provisioning_api.po
source_file = translationfiles/templates/provisioning_api.pot
source_lang = en
type = PO
[nextcloud.lookup_server_connector]
file_filter = translationfiles/<lang>/lookup_server_connector.po
source_file = translationfiles/templates/lookup_server_connector.pot
source_lang = en
type = PO
[nextcloud.dashboard-shipped-with-server]
file_filter = translationfiles/<lang>/dashboard.po
source_file = translationfiles/templates/dashboard.pot
source_lang = en
type = PO
[nextcloud.contactsinteraction]
file_filter = translationfiles/<lang>/contactsinteraction.po
source_file = translationfiles/templates/contactsinteraction.pot
source_lang = en
type = PO
[nextcloud.cloud_federation_api]
file_filter = translationfiles/<lang>/cloud_federation_api.po
source_file = translationfiles/templates/cloud_federation_api.pot
source_lang = en
type = PO
[nextcloud.admin_audit]
file_filter = translationfiles/<lang>/admin_audit.po
source_file = translationfiles/templates/admin_audit.pot
source_lang = en
type = PO
[nextcloud.user_status]
file_filter = translationfiles/<lang>/user_status.po
source_file = translationfiles/templates/user_status.pot
source_lang = en
type = PO
[nextcloud.weather_status]
file_filter = translationfiles/<lang>/weather_status.po
source_file = translationfiles/templates/weather_status.pot
source_lang = en
type = PO
View File
+9
View File
@@ -1,3 +1,12 @@
// Suppress "Skip to navigation of app" link since the app does not have a navigation
.skip-navigation:not(.skip-content) {
display: none;
}
// Fix position of "Skip to main content" link since the other link is gone
.skip-navigation.skip-content {
left: 3px;
}
#header {
background: transparent !important;
--color-header: rgba(24, 24, 24, 1);
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
View File
+5
View File
@@ -215,6 +215,7 @@ export default {
},
mounted() {
this.updateGlobalStyles()
this.updateSkipLink()
window.addEventListener('scroll', this.handleScroll)
setInterval(() => {
@@ -321,6 +322,10 @@ export default {
document.body.classList.remove('dashboard--dark')
}
},
updateSkipLink() {
// Make sure "Skip to main content" link points to the app content
document.getElementsByClassName('skip-navigation')[0].setAttribute('href', '#app-dashboard')
},
updateStatusCheckbox(app, checked) {
if (checked) {
this.enableStatus(app)
@@ -44,6 +44,7 @@ use Icewind\SMB\Exception\Exception;
use Icewind\SMB\Exception\ForbiddenException;
use Icewind\SMB\Exception\InvalidArgumentException;
use Icewind\SMB\Exception\NotFoundException;
use Icewind\SMB\Exception\OutOfSpaceException;
use Icewind\SMB\Exception\TimedOutException;
use Icewind\SMB\IFileInfo;
use Icewind\SMB\Native\NativeServer;
@@ -57,6 +58,7 @@ use OC\Files\Filesystem;
use OC\Files\Storage\Common;
use OCA\Files_External\Lib\Notify\SMBNotifyHandler;
use OCP\Constants;
use OCP\Files\EntityTooLargeException;
use OCP\Files\Notify\IChange;
use OCP\Files\Notify\IRenameChange;
use OCP\Files\Storage\INotifyStorage;
@@ -497,6 +499,8 @@ class SMB extends Common implements INotifyStorage {
return false;
} catch (ForbiddenException $e) {
return false;
} catch (OutOfSpaceException $e) {
throw new EntityTooLargeException("not enough available space to create file", 0, $e);
} catch (ConnectException $e) {
$this->logger->logException($e, ['message' => 'Error while opening file']);
throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
@@ -538,6 +542,8 @@ class SMB extends Common implements INotifyStorage {
return true;
}
return false;
} catch (OutOfSpaceException $e) {
throw new EntityTooLargeException("not enough available space to create file", 0, $e);
} catch (ConnectException $e) {
$this->logger->logException($e, ['message' => 'Error while creating file']);
throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -708,7 +708,7 @@ export default {
// Execute the copy link method
// freshly created share component
// ! somehow does not works on firefox !
if (update || !this.config.enforcePasswordForPublicLink) {
if (!this.config.enforcePasswordForPublicLink) {
// Only copy the link when the password was not forced,
// otherwise the user needs to copy/paste the password before finishing the share.
component.copyLink()
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+4 -4
View File
@@ -426,8 +426,8 @@ export default {
/**
* Register search
*/
subscribe('nextcloud:unified-search:search', this.search)
subscribe('nextcloud:unified-search:reset', this.resetSearch)
subscribe('nextcloud:unified-search.search', this.search)
subscribe('nextcloud:unified-search.reset', this.resetSearch)
/**
* If disabled group but empty, redirect
@@ -435,8 +435,8 @@ export default {
this.redirectIfDisabled()
},
beforeDestroy() {
unsubscribe('nextcloud:unified-search:search', this.search)
unsubscribe('nextcloud:unified-search:reset', this.resetSearch)
unsubscribe('nextcloud:unified-search.search', this.search)
unsubscribe('nextcloud:unified-search.reset', this.resetSearch)
},
methods: {
+4 -4
View File
@@ -280,12 +280,12 @@ export default {
},
mounted() {
subscribe('nextcloud:unified-search:search', this.setSearch)
subscribe('nextcloud:unified-search:reset', this.resetSearch)
subscribe('nextcloud:unified-search.search', this.setSearch)
subscribe('nextcloud:unified-search.reset', this.resetSearch)
},
beforeDestroy() {
unsubscribe('nextcloud:unified-search:search', this.setSearch)
unsubscribe('nextcloud:unified-search:reset', this.resetSearch)
unsubscribe('nextcloud:unified-search.search', this.setSearch)
unsubscribe('nextcloud:unified-search.reset', this.resetSearch)
},
methods: {
@@ -1,6 +1,6 @@
<div class="section development-notice">
<p>
<a href="<?php p($_['reasons-use-nextcloud-pdf-link']); ?>" id="open-reasons-use-nextcloud-pdf" class="link-button icon-file" target="_blank">Reasons to use Nextcloud in your organization</a>
<a href="<?php p($_['reasons-use-nextcloud-pdf-link']); ?>" id="open-reasons-use-nextcloud-pdf" class="link-button icon-file" target="_blank"><?php p($l->t('Reasons to use Nextcloud in your organization'));?></a>
</p>
<p>
<?php print_unescaped(str_replace(
View File
View File
+22 -12
View File
@@ -33,6 +33,8 @@ use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\IConfig;
use OCP\ILogger;
use OCP\Lock\ILockingProvider;
use OCP\Lock\LockedException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputInterface;
@@ -54,11 +56,14 @@ class Repair extends Command {
private $memoryLimit;
/** @var int */
private $memoryTreshold;
/** @var ILockingProvider */
private $lockingProvider;
public function __construct(IConfig $config, IRootFolder $rootFolder, ILogger $logger, IniGetWrapper $phpIni) {
public function __construct(IConfig $config, IRootFolder $rootFolder, ILogger $logger, IniGetWrapper $phpIni, ILockingProvider $lockingProvider) {
$this->config = $config;
$this->rootFolder = $rootFolder;
$this->logger = $logger;
$this->lockingProvider = $lockingProvider;
$this->memoryLimit = $phpIni->getBytes('memory_limit');
$this->memoryTreshold = $this->memoryLimit - 25 * 1024 * 1024;
@@ -95,8 +100,6 @@ class Repair extends Command {
$output->writeln("");
}
$verbose = $output->isVerbose();
$instanceId = $this->config->getSystemValueString('instanceid');
$output->writeln("This will migrate all previews from the old preview location to the new one.");
@@ -218,14 +221,21 @@ class Repair extends Command {
return 1;
}
$lockName = 'occ preview:repair lock ' . $oldPreviewFolder->getId();
try {
$section1->writeln(" Locking \"$lockName\" ", OutputInterface::VERBOSITY_VERBOSE);
$this->lockingProvider->acquireLock($lockName, ILockingProvider::LOCK_EXCLUSIVE);
} catch (LockedException $e) {
$section1->writeln(" Skipping because it is locked - another process seems to work on this ");
continue;
}
$previews = $oldPreviewFolder->getDirectoryListing();
if ($previews !== []) {
try {
$this->rootFolder->get("appdata_$instanceId/preview/$newFoldername");
} catch (NotFoundException $e) {
if ($verbose) {
$section1->writeln(" Create folder preview/$newFoldername");
}
$section1->writeln(" Create folder preview/$newFoldername", OutputInterface::VERBOSITY_VERBOSE);
if (!$dryMode) {
$this->rootFolder->newFolder("appdata_$instanceId/preview/$newFoldername");
}
@@ -240,9 +250,7 @@ class Repair extends Command {
$progressBar->advance();
continue;
}
if ($verbose) {
$section1->writeln(" Move preview/$name/$previewName to preview/$newFoldername");
}
$section1->writeln(" Move preview/$name/$previewName to preview/$newFoldername", OutputInterface::VERBOSITY_VERBOSE);
if (!$dryMode) {
try {
$preview->move("appdata_$instanceId/preview/$newFoldername/$previewName");
@@ -253,9 +261,7 @@ class Repair extends Command {
}
}
if ($oldPreviewFolder->getDirectoryListing() === []) {
if ($verbose) {
$section1->writeln(" Delete empty folder preview/$name");
}
$section1->writeln(" Delete empty folder preview/$name", OutputInterface::VERBOSITY_VERBOSE);
if (!$dryMode) {
try {
$oldPreviewFolder->delete();
@@ -264,6 +270,10 @@ class Repair extends Command {
}
}
}
$this->lockingProvider->releaseLock($lockName, ILockingProvider::LOCK_EXCLUSIVE);
$section1->writeln(" Unlocked", OutputInterface::VERBOSITY_VERBOSE);
$section1->writeln(" Finished migrating previews of file with fileId $name ");
$progressBar->advance();
}
+15 -3
View File
@@ -433,7 +433,7 @@ nav[role='navigation'] {
li {
position: relative;
cursor: pointer;
margin: 0 2px;
padding: 0 2px;
display: flex;
justify-content: center;
@@ -446,6 +446,9 @@ nav[role='navigation'] {
align-items: center;
justify-content: center;
opacity: .6;
// Make sure most app names dont ellipsize
letter-spacing: -0.5px;
font-size: 12px;
}
/* focused app visual feedback */
@@ -453,19 +456,28 @@ nav[role='navigation'] {
a:focus,
a.active {
opacity: 1;
font-weight: bold;
}
// Text size back to normal for hover/focus
&:hover a,
a:focus {
font-size: 14px;
}
&:hover a + span,
a:focus + span,
&:hover span,
&:focus span,
a:focus span {
a:focus span,
a.active span {
display: inline-block;
text-overflow: initial;
width: auto;
overflow: hidden;
padding: 0 5px;
z-index: 2;
margin-bottom: -1px; // for vertical alignment
}
/* hidden apps menu */
@@ -482,7 +494,7 @@ nav[role='navigation'] {
position: absolute;
color: var(--color-primary-text);
bottom: 2px;
width: calc(100% - 4px);
width: 100%;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,4 +1,4 @@
/*
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+1 -1
View File
@@ -1,4 +1,4 @@
/*
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+1 -1
View File
@@ -1,4 +1,4 @@
/*
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+1 -1
View File
@@ -1,4 +1,4 @@
/*
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+24 -6
View File
@@ -28,6 +28,12 @@ export const minSearchLength = 2
export const regexFilterIn = /[^-]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
*
@@ -54,13 +60,20 @@ export async function getTypes() {
/**
* Get the list of available search providers
*
* @param {string} type the type to search
* @param {string} query the search
* @param {int|string|undefined} cursor the offset for paginated searches
* @returns {Promise}
* @param {Object} options destructuring object
* @param {string} options.type the type to search
* @param {string} options.query the search
* @param {int|string|undefined} options.cursor the offset for paginated searches
* @returns {Object} {request: Promise, cancel: Promise}
*/
export function search(type, query, cursor) {
return axios.get(generateOcsUrl('search', 2) + `providers/${type}/search`, {
export function search({ type, query, cursor }) {
/**
* Generate an axios cancel token
*/
const cancelToken = createCancelToken()
const request = async() => axios.get(generateOcsUrl('search', 2) + `providers/${type}/search`, {
cancelToken: cancelToken.token,
params: {
term: query,
cursor,
@@ -68,4 +81,9 @@ export function search(type, query, cursor) {
from: window.location.pathname.replace('/index.php', '') + window.location.search,
},
})
return {
request,
cancel: cancelToken.cancel,
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
/*
/**
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+12 -1
View File
@@ -19,8 +19,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { getRequestToken } from '@nextcloud/auth'
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 Vue from 'vue'
@@ -32,7 +33,17 @@ __webpack_nonce__ = btoa(getRequestToken())
// eslint-disable-next-line camelcase
__webpack_public_path__ = generateFilePath('core', '', 'js/')
const logger = getLoggerBuilder()
.setApp('unified-search')
.detectUser()
.build()
Vue.mixin({
data() {
return {
logger,
}
},
methods: {
t,
n,
+186 -75
View File
@@ -31,16 +31,31 @@
<Magnify class="unified-search__trigger" :size="20" fill-color="var(--color-primary-text)" />
</template>
<!-- Search input -->
<!-- Search form & filters wrapper -->
<div class="unified-search__input-wrapper">
<input ref="input"
v-model="query"
class="unified-search__input"
type="search"
:placeholder="t('core', 'Search {types} …', { types: typesNames.join(', ').toLowerCase() })"
@input="onInputDebounced"
@keypress.enter.prevent.stop="onInputEnter"
@search="onSearch">
<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"
v-model="query"
class="unified-search__form-input"
type="search"
:class="{'unified-search__form-input--with-reset': !!query}"
:placeholder="t('core', 'Search {types} …', { types: typesNames.join(', ').toLowerCase() })"
@input="onInputDebounced"
@keypress.enter.prevent.stop="onInputEnter">
<!-- 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 -->
<Actions v-if="availableFilters.length > 1" class="unified-search__filters" placement="bottom">
<ActionButton v-for="type in availableFilters"
@@ -57,7 +72,7 @@
<!-- Loading placeholders -->
<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}) }}
</EmptyContent>
@@ -107,6 +122,7 @@
<script>
import { emit } from '@nextcloud/event-bus'
import { minSearchLength, getTypes, search, defaultLimit, regexFilterIn, regexFilterNot } from '../services/UnifiedSearchService'
import { showError } from '@nextcloud/dialogs'
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
import Actions from '@nextcloud/vue/dist/Components/Actions'
import debounce from 'debounce'
@@ -117,6 +133,10 @@ import HeaderMenu from '../components/HeaderMenu'
import SearchResult from '../components/UnifiedSearch/SearchResult'
import SearchResultPlaceholders from '../components/UnifiedSearch/SearchResultPlaceholders'
const REQUEST_FAILED = 0
const REQUEST_OK = 1
const REQUEST_CANCELED = 2
export default {
name: 'UnifiedSearch',
@@ -134,10 +154,17 @@ export default {
return {
types: [],
// Cursors per types
cursors: {},
// Various search limits per types
limits: {},
// Loading types
loading: {},
// Reached search types
reached: {},
// Pending cancellable requests
requests: [],
// List of all results
results: {},
query: '',
@@ -255,7 +282,7 @@ export default {
async created() {
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() {
@@ -289,24 +316,38 @@ export default {
this.types = await getTypes()
},
onClose() {
emit('nextcloud:unified-search:close')
emit('nextcloud:unified-search.close')
},
/**
* Reset the search state
*/
resetSearch() {
emit('nextcloud:unified-search:reset')
onReset() {
emit('nextcloud:unified-search.reset')
this.logger.debug('Search reset')
this.query = ''
this.resetState()
this.focusInput()
},
resetState() {
async resetState() {
this.cursors = {}
this.limits = {}
this.loading = {}
this.reached = {}
this.results = {}
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 not, trigger the search again
@@ -349,7 +378,7 @@ export default {
*/
async onInput() {
// 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
if (this.query.trim() === '' || this.isShortQuery) {
@@ -372,42 +401,65 @@ export default {
// Remove any filters from the query
query = query.replace(regexFilterIn, '').replace(regexFilterNot, '')
console.debug('Searching', query, 'in', types)
// 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 => {
this.$set(this.loading, type, true)
const request = await search(type, query)
Promise.all(types.map(async type => {
try {
// Init cancellable request
const { request, cancel } = search({ type, query })
this.requests.push(cancel)
// Process results
if (request.data.ocs.data.entries.length > 0) {
this.$set(this.results, type, request.data.ocs.data.entries)
} else {
this.$delete(this.results, type)
}
// Fetch results
const { data } = await request()
// Save cursor if any
if (request.data.ocs.data.cursor) {
this.$set(this.cursors, type, request.data.ocs.data.cursor)
} else if (!request.data.ocs.data.isPaginated) {
// Process results
if (data.ocs.data.entries.length > 0) {
this.$set(this.results, type, data.ocs.data.entries)
} else {
this.$delete(this.results, type)
}
// Save cursor if any
if (data.ocs.data.cursor) {
this.$set(this.cursors, type, data.ocs.data.cursor)
} else if (!data.ocs.data.isPaginated) {
// If no cursor and no pagination, we save the default amount
// 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
if (request.data.ocs.data.entries.length < this.defaultLimit) {
this.$set(this.reached, type, true)
}
// Check if we reached end of pagination
if (data.ocs.data.entries.length < this.defaultLimit) {
this.$set(this.reached, type, true)
}
// If none already focused, focus the first rendered result
if (this.focused === null) {
this.focused = 0
}
// If none already focused, focus the first rendered result
if (this.focused === null) {
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) {
@@ -423,22 +475,27 @@ export default {
if (this.loading[type]) {
return
}
this.$set(this.loading, type, true)
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
if (request.data.ocs.data.cursor) {
this.$set(this.cursors, type, request.data.ocs.data.cursor)
if (data.ocs.data.cursor) {
this.$set(this.cursors, type, data.ocs.data.cursor)
}
if (request.data.ocs.data.entries.length > 0) {
this.results[type].push(...request.data.ocs.data.entries)
// Process results
if (data.ocs.data.entries.length > 0) {
this.results[type].push(...data.ocs.data.entries)
}
// 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)
}
} else
@@ -460,8 +517,6 @@ export default {
this.focusIndex(this.focused)
})
}
this.$set(this.loading, type, false)
},
/**
@@ -574,6 +629,7 @@ export default {
<style lang="scss" scoped>
$margin: 10px;
$input-height: 34px;
$input-padding: 6px;
.unified-search {
@@ -583,13 +639,13 @@ $input-padding: 6px;
}
&__input-wrapper {
width: 100%;
position: sticky;
// above search results
z-index: 2;
top: 0;
display: inline-flex;
align-items: center;
width: 100%;
background-color: var(--color-main-background);
}
@@ -601,17 +657,67 @@ $input-padding: 6px;
}
}
&__input {
&__form {
position: relative;
width: 100%;
height: 34px;
margin: $margin;
padding: $input-padding;
&,
&[placeholder],
&::placeholder {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
// Loading spinner
&::after {
right: $input-padding;
left: auto;
}
&-input,
&-reset {
margin: $input-padding / 2;
}
&-input {
width: 100%;
height: $input-height;
padding: $input-padding;
&,
&[placeholder],
&::placeholder {
overflow: hidden;
white-space: nowrap;
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;
}
}
}
@@ -635,6 +741,11 @@ $input-padding: 6px;
.empty-content {
margin: 10vh 0;
::v-deep .empty-content__title {
padding: 0 15px;
text-align: center;
}
}
}
+1 -1
View File
@@ -122,7 +122,7 @@ class Manager implements IManager {
$sectionID = $section->getID();
if (isset($this->sections[$type][$sectionID])) {
if ($sectionID !== 'connected-accounts' && isset($this->sections[$type][$sectionID])) {
$this->log->logException(new \InvalidArgumentException('Section with the same ID already registered: ' . $sectionID . ', class: ' . $class), ['level' => ILogger::INFO]);
continue;
}
+2 -2
View File
@@ -29,10 +29,10 @@
// between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel
// when updating major/minor version number.
$OC_Version = [20, 0, 0, 7];
$OC_Version = [20, 0, 0, 8];
// The human readable string
$OC_VersionString = '20.0.0 RC1';
$OC_VersionString = '20.0.0 RC2';
$OC_VersionCanBeUpgradedFrom = [
'nextcloud' => [