Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/dav/lib/Capabilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public function __construct(
}

/**
* @return array{dav: array{chunking: string, public_shares_chunking: bool, search_supports_creation_time: bool, search_supports_upload_time: bool, bulkupload?: string, absence-supported?: bool, absence-replacement?: bool}}
* @return array{dav: array{chunking: string, public_shares_chunking: bool, search_supports_creation_time: bool, search_supports_upload_time: bool, search_supports_last_activity: bool, bulkupload?: string, absence-supported?: bool, absence-replacement?: bool}}
*/
public function getCapabilities() {
$capabilities = [
Expand All @@ -29,6 +29,7 @@ public function getCapabilities() {
'public_shares_chunking' => true,
'search_supports_creation_time' => true,
'search_supports_upload_time' => true,
'search_supports_last_activity' => true,
]
];
if ($this->config->getSystemValueBool('bulkupload.enabled', true)) {
Expand Down
6 changes: 6 additions & 0 deletions apps/dav/lib/Connector/Sabre/FilesPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class FilesPlugin extends ServerPlugin {
public const METADATA_ETAG_PROPERTYNAME = '{http://nextcloud.org/ns}metadata_etag';
public const UPLOAD_TIME_PROPERTYNAME = '{http://nextcloud.org/ns}upload_time';
public const CREATION_TIME_PROPERTYNAME = '{http://nextcloud.org/ns}creation_time';
public const LAST_ACTIVITY_PROPERTYNAME = '{http://nextcloud.org/ns}last_activity';
public const SHARE_NOTE = '{http://nextcloud.org/ns}note';
public const SHARE_HIDE_DOWNLOAD_PROPERTYNAME = '{http://nextcloud.org/ns}hide-download';
public const SUBFOLDER_COUNT_PROPERTYNAME = '{http://nextcloud.org/ns}contained-folder-count';
Expand Down Expand Up @@ -441,6 +442,11 @@ public function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node)
return $node->getFileInfo()->getCreationTime();
});

$propFind->handle(self::LAST_ACTIVITY_PROPERTYNAME, function () use ($node) {
$fileInfo = $node->getFileInfo();
return max($fileInfo->getUploadTime(), $fileInfo->getMTime());
});

foreach ($node->getFileInfo()->getMetadata() as $metadataKey => $metadataValue) {
$propFind->handle(self::FILE_METADATA_PREFIX . $metadataKey, $metadataValue);
}
Expand Down
5 changes: 5 additions & 0 deletions apps/dav/lib/Files/FileSearchBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ public function getPropertyDefinitionsForScope(string $href, ?string $path): arr
new SearchPropertyDefinition('{DAV:}getlastmodified', true, true, true, SearchPropertyDefinition::DATATYPE_DATETIME),
new SearchPropertyDefinition('{DAV:}creationdate', true, true, true, SearchPropertyDefinition::DATATYPE_DATETIME),
new SearchPropertyDefinition('{http://nextcloud.org/ns}upload_time', true, true, true, SearchPropertyDefinition::DATATYPE_DATETIME),
new SearchPropertyDefinition('{http://nextcloud.org/ns}last_activity', true, false, true, SearchPropertyDefinition::DATATYPE_DATETIME),
new SearchPropertyDefinition(FilesPlugin::SIZE_PROPERTYNAME, true, true, true, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
new SearchPropertyDefinition(TagsPlugin::FAVORITE_PROPERTYNAME, true, true, true, SearchPropertyDefinition::DATATYPE_BOOLEAN),
new SearchPropertyDefinition(FilesPlugin::INTERNAL_FILEID_PROPERTYNAME, true, true, false, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
Expand Down Expand Up @@ -304,6 +305,8 @@ private function getSearchResultProperty(SearchResult $result, SearchPropertyDef
return $node->getNode()->getCreationTime();
case '{http://nextcloud.org/ns}upload_time':
return $node->getNode()->getUploadTime();
case '{http://nextcloud.org/ns}last_activity':
return max($node->getNode()->getUploadTime(), $node->getNode()->getMTime());
case FilesPlugin::SIZE_PROPERTYNAME:
return $node->getSize();
case FilesPlugin::INTERNAL_FILEID_PROPERTYNAME:
Expand Down Expand Up @@ -332,6 +335,8 @@ private function transformQuery(Query $query, ?SearchBinaryOperator $scopeOperat
$direction = $order->order === Order::ASC ? ISearchOrder::DIRECTION_ASCENDING : ISearchOrder::DIRECTION_DESCENDING;
if (str_starts_with($order->property->name, FilesPlugin::FILE_METADATA_PREFIX)) {
return new SearchOrder($direction, substr($order->property->name, strlen(FilesPlugin::FILE_METADATA_PREFIX)), IMetadataQuery::EXTRA);
} elseif ($order->property->name === FilesPlugin::LAST_ACTIVITY_PROPERTYNAME) {
return new SearchOrder($direction, 'last_activity');
} else {
return new SearchOrder($direction, $this->mapPropertyNameToColumn($order->property));
}
Expand Down
6 changes: 5 additions & 1 deletion apps/dav/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
"chunking",
"public_shares_chunking",
"search_supports_creation_time",
"search_supports_upload_time"
"search_supports_upload_time",
"search_supports_last_activity"
],
"properties": {
"chunking": {
Expand All @@ -47,6 +48,9 @@
"search_supports_upload_time": {
"type": "boolean"
},
"search_supports_last_activity": {
"type": "boolean"
},
"bulkupload": {
"type": "string"
},
Expand Down
3 changes: 3 additions & 0 deletions apps/dav/tests/unit/CapabilitiesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public function testGetCapabilities(): void {
'public_shares_chunking' => true,
'search_supports_creation_time' => true,
'search_supports_upload_time' => true,
'search_supports_last_activity' => true,
],
];
$this->assertSame($expected, $capabilities->getCapabilities());
Expand All @@ -55,6 +56,7 @@ public function testGetCapabilitiesWithBulkUpload(): void {
'public_shares_chunking' => true,
'search_supports_creation_time' => true,
'search_supports_upload_time' => true,
'search_supports_last_activity' => true,
'bulkupload' => '1.0',
],
];
Expand All @@ -78,6 +80,7 @@ public function testGetCapabilitiesWithAbsence(): void {
'public_shares_chunking' => true,
'search_supports_creation_time' => true,
'search_supports_upload_time' => true,
'search_supports_last_activity' => true,
'absence-supported' => true,
'absence-replacement' => true,
],
Expand Down
32 changes: 30 additions & 2 deletions apps/files/lib/Service/UserConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ class UserConfig {
'default' => true,
'allowed' => [true, false],
],
[
// Maximum number of files to display in the recent section
'key' => 'recent_files_limit',
'default' => 100,
'min' => 1,
'max' => 100,
],
];
protected ?IUser $user = null;

Expand Down Expand Up @@ -118,7 +125,7 @@ private function getAllowedConfigValues(string $key): array {
* Get the default config value for a given key
*
* @param string $key a valid config key
* @return string|bool
* @return string|bool|int
*/
private function getDefaultConfigValue(string $key) {
foreach (self::ALLOWED_CONFIGS as $config) {
Expand Down Expand Up @@ -146,7 +153,13 @@ public function setConfig(string $key, $value): void {
throw new \InvalidArgumentException('Unknown config key');
}

if (!in_array($value, $this->getAllowedConfigValues($key))) {
$config = $this->getConfigDefinition($key);

if (isset($config['min'], $config['max'])) {
if ((int)$value < $config['min'] || (int)$value > $config['max']) {
throw new \InvalidArgumentException('Invalid config value');
}
} elseif (!in_array($value, $this->getAllowedConfigValues($key))) {
throw new \InvalidArgumentException('Invalid config value');
}

Expand Down Expand Up @@ -179,4 +192,19 @@ public function getConfigs(): array {

return array_combine($this->getAllowedConfigKeys(), $userConfigs);
}

/**
* Get the config definition for a given key
*
* @param string $key
* @return array
*/
private function getConfigDefinition(string $key): array {
foreach (self::ALLOWED_CONFIGS as $config) {
if ($config['key'] === $key) {
return $config;
}
}
return [];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<!--
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->

<script lang="ts" setup>
import { t } from '@nextcloud/l10n'
import { NcInputField } from '@nextcloud/vue'
import debounce from 'debounce'
import NcAppSettingsSection from '@nextcloud/vue/components/NcAppSettingsSection'
import NcFormBox from '@nextcloud/vue/components/NcFormBox'
import { useUserConfigStore } from '../../store/userconfig.ts'

const store = useUserConfigStore()
const debouncedUpdate = debounce((value: number) => {
store.update('recent_files_limit', value)
}, 500)
</script>

<template>
<NcAppSettingsSection id="recent" :name="t('files', 'Recent view')">
<NcFormBox>
<NcInputField
v-model="store.userConfig.recent_files_limit"
type="number"
:min="1"
:max="100"
:label="t('files', 'Maximum number of files shown in the Recent view')"
@update:model-value="debouncedUpdate(Number($event))" />
</NcFormBox>
</NcAppSettingsSection>
</template>
2 changes: 1 addition & 1 deletion apps/files/src/services/Recent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export async function getContents(path = '/', options: { signal: AbortSignal }):
const contentsResponse = await client.search('/', {
signal: options.signal,
details: true,
data: getRecentSearch(lastTwoWeeksTimestamp),
data: getRecentSearch(lastTwoWeeksTimestamp, store.userConfig.recent_files_limit + 1),
}) as ResponseDataDetailed<SearchResult>

const contents = contentsResponse.data.results
Expand Down
1 change: 1 addition & 0 deletions apps/files/src/store/userconfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const initialUserConfig = loadState<UserConfig>('files', 'config', {
show_mime_column: true,
sort_favorites_first: true,
sort_folders_first: true,
recent_files_limit: 100,

show_dialog_deletion: false,
show_dialog_file_extension: true,
Expand Down
3 changes: 2 additions & 1 deletion apps/files/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,15 @@ export interface PathOptions {

// User config store
export interface UserConfig {
[key: string]: boolean | string | undefined
[key: string]: boolean | string | number | undefined

crop_image_previews: boolean
default_view: 'files' | 'personal'
folder_tree: boolean
grid_view: boolean
sort_favorites_first: boolean
sort_folders_first: boolean
recent_files_limit: number

show_files_extensions: boolean
show_hidden: boolean
Expand Down
2 changes: 2 additions & 0 deletions apps/files/src/views/FilesAppSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import NcAppSettingsDialog from '@nextcloud/vue/components/NcAppSettingsDialog'
import FilesAppSettingsAppearance from '../components/FilesAppSettings/FilesAppSettingsAppearance.vue'
import FilesAppSettingsGeneral from '../components/FilesAppSettings/FilesAppSettingsGeneral.vue'
import FilesAppSettingsLegacyApi from '../components/FilesAppSettings/FilesAppSettingsLegacyApi.vue'
import FilesAppSettingsRecent from '../components/FilesAppSettings/FilesAppSettingsRecent.vue'
import FilesAppSettingsShortcuts from '../components/FilesAppSettings/FilesAppSettingsShortcuts.vue'
import FilesAppSettingsWarnings from '../components/FilesAppSettings/FilesAppSettingsWarnings.vue'
import FilesAppSettingsWebDav from '../components/FilesAppSettings/FilesAppSettingsWebDav.vue'
Expand Down Expand Up @@ -57,6 +58,7 @@ async function showKeyboardShortcuts() {
<FilesAppSettingsLegacyApi />
<FilesAppSettingsWarnings />
<FilesAppSettingsWebDav />
<FilesAppSettingsRecent />
<FilesAppSettingsShortcuts />
</NcAppSettingsDialog>
</template>
4 changes: 2 additions & 2 deletions dist/files-init.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/files-init.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/files-main.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/files-main.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/theming-settings-personal.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* extracted by css-entry-points-plugin */
@import './theming-theming-settings-personal-BGvZ2soH.chunk.css';
@import './theming-theming-settings-personal-Tw2nBJ_u.chunk.css';
@import './createElementId-DhjFt1I9-C_oBIsvc.chunk.css';
@import './autolink-U5pBzLgI-R3us1MM8.chunk.css';
@import './NcModal-DHryP_87-CU2wYsLf.chunk.css';
Expand Down

This file was deleted.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions lib/private/Files/Cache/QuerySearchHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,7 @@ public function searchInCaches(ISearchQuery $searchQuery, array $caches): array

$requestedFields = $this->searchBuilder->extractRequestedFields($searchQuery->getSearchOperation());

$joinExtendedCache = in_array('creation_time', $requestedFields) || in_array('upload_time', $requestedFields);

$query = $builder->selectFileCache('file', $joinExtendedCache);
$query = $builder->selectFileCache('file', true);

if (in_array('systemtag', $requestedFields)) {
$this->equipQueryForSystemTags($query, $this->requireUser($searchQuery));
Expand Down
Loading
Loading