Skip to content

Commit fd9e19b

Browse files
Merge pull request #58694 from nextcloud/backport/58562/stable33
[stable33] feat: set creation_time on file creation and render recently created icon
2 parents efb1b9d + 21afe9f commit fd9e19b

14 files changed

Lines changed: 144 additions & 11 deletions

File tree

apps/dav/lib/Capabilities.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ public function __construct(
1818
}
1919

2020
/**
21-
* @return array{dav: array{chunking: string, public_shares_chunking: bool, bulkupload?: string, absence-supported?: bool, absence-replacement?: bool}}
21+
* @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}}
2222
*/
2323
public function getCapabilities() {
2424
$capabilities = [
2525
'dav' => [
2626
'chunking' => '1.0',
2727
'public_shares_chunking' => true,
28+
'search_supports_creation_time' => true,
29+
'search_supports_upload_time' => true,
2830
]
2931
];
3032
if ($this->config->getSystemValueBool('bulkupload.enabled', true)) {

apps/dav/lib/Files/FileSearchBackend.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ public function getPropertyDefinitionsForScope(string $href, ?string $path): arr
8686
new SearchPropertyDefinition('{DAV:}displayname', true, true, true),
8787
new SearchPropertyDefinition('{DAV:}getcontenttype', true, true, true),
8888
new SearchPropertyDefinition('{DAV:}getlastmodified', true, true, true, SearchPropertyDefinition::DATATYPE_DATETIME),
89+
new SearchPropertyDefinition('{DAV:}creationdate', true, true, true, SearchPropertyDefinition::DATATYPE_DATETIME),
8990
new SearchPropertyDefinition('{http://nextcloud.org/ns}upload_time', true, true, true, SearchPropertyDefinition::DATATYPE_DATETIME),
9091
new SearchPropertyDefinition(FilesPlugin::SIZE_PROPERTYNAME, true, true, true, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
9192
new SearchPropertyDefinition(TagsPlugin::FAVORITE_PROPERTYNAME, true, true, true, SearchPropertyDefinition::DATATYPE_BOOLEAN),
@@ -299,6 +300,8 @@ private function getSearchResultProperty(SearchResult $result, SearchPropertyDef
299300
return $node->getName();
300301
case '{DAV:}getlastmodified':
301302
return $node->getLastModified();
303+
case '{DAV:}creationdate':
304+
return $node->getNode()->getCreationTime();
302305
case '{http://nextcloud.org/ns}upload_time':
303306
return $node->getNode()->getUploadTime();
304307
case FilesPlugin::SIZE_PROPERTYNAME:
@@ -461,6 +464,8 @@ private function mapPropertyNameToColumn(SearchPropertyDefinition $property) {
461464
return 'mimetype';
462465
case '{DAV:}getlastmodified':
463466
return 'mtime';
467+
case '{DAV:}creationdate':
468+
return 'creation_time';
464469
case '{http://nextcloud.org/ns}upload_time':
465470
return 'upload_time';
466471
case FilesPlugin::SIZE_PROPERTYNAME:

apps/dav/openapi.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@
3030
"type": "object",
3131
"required": [
3232
"chunking",
33-
"public_shares_chunking"
33+
"public_shares_chunking",
34+
"search_supports_creation_time",
35+
"search_supports_upload_time"
3436
],
3537
"properties": {
3638
"chunking": {
@@ -39,6 +41,12 @@
3941
"public_shares_chunking": {
4042
"type": "boolean"
4143
},
44+
"search_supports_creation_time": {
45+
"type": "boolean"
46+
},
47+
"search_supports_upload_time": {
48+
"type": "boolean"
49+
},
4250
"bulkupload": {
4351
"type": "string"
4452
},

apps/dav/tests/unit/CapabilitiesTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ public function testGetCapabilities(): void {
3131
'dav' => [
3232
'chunking' => '1.0',
3333
'public_shares_chunking' => true,
34+
'search_supports_creation_time' => true,
35+
'search_supports_upload_time' => true,
3436
],
3537
];
3638
$this->assertSame($expected, $capabilities->getCapabilities());
@@ -51,6 +53,8 @@ public function testGetCapabilitiesWithBulkUpload(): void {
5153
'dav' => [
5254
'chunking' => '1.0',
5355
'public_shares_chunking' => true,
56+
'search_supports_creation_time' => true,
57+
'search_supports_upload_time' => true,
5458
'bulkupload' => '1.0',
5559
],
5660
];
@@ -72,6 +76,8 @@ public function testGetCapabilitiesWithAbsence(): void {
7276
'dav' => [
7377
'chunking' => '1.0',
7478
'public_shares_chunking' => true,
79+
'search_supports_creation_time' => true,
80+
'search_supports_upload_time' => true,
7581
'absence-supported' => true,
7682
'absence-replacement' => true,
7783
],

apps/files/src/components/FileEntry/FileEntryPreview.vue

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@
4242
<FavoriteIcon v-once />
4343
</span>
4444

45+
<!-- Recently created icon -->
46+
<span v-else-if="isRecentView && isRecentlyCreated" class="files-list__row-icon-recently-created">
47+
<RecentlyCreatedIcon v-once />
48+
</span>
49+
4550
<component
4651
:is="fileOverlay"
4752
v-if="fileOverlay"
@@ -71,6 +76,7 @@ import PlayCircleIcon from 'vue-material-design-icons/PlayCircle.vue'
7176
import TagIcon from 'vue-material-design-icons/Tag.vue'
7277
import CollectivesIcon from './CollectivesIcon.vue'
7378
import FavoriteIcon from './FavoriteIcon.vue'
79+
import RecentlyCreatedIcon from './RecentlyCreatedIcon.vue'
7480
import { usePreviewImage } from '../../composables/usePreviewImage.ts'
7581
import logger from '../../logger.ts'
7682
import { isLivePhoto } from '../../services/LivePhotos.ts'
@@ -91,6 +97,7 @@ export default defineComponent({
9197
LinkIcon,
9298
NetworkIcon,
9399
TagIcon,
100+
RecentlyCreatedIcon,
94101
},
95102
96103
props: {
@@ -138,6 +145,21 @@ export default defineComponent({
138145
return this.source.attributes.favorite === 1
139146
},
140147
148+
isRecentlyCreated(): boolean {
149+
if (!this.source.crtime) {
150+
return false
151+
}
152+
153+
const oneDayAgo = new Date()
154+
oneDayAgo.setDate(oneDayAgo.getDate() - 1)
155+
156+
return this.source.crtime > oneDayAgo
157+
},
158+
159+
isRecentView(): boolean {
160+
return this.$route?.params?.view === 'recent'
161+
},
162+
141163
userConfig(): UserConfig {
142164
return this.userConfigStore.userConfig
143165
},
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<!--
2+
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
3+
- SPDX-License-Identifier: AGPL-3.0-or-later
4+
-->
5+
<template>
6+
<NcIconSvgWrapper class="recently-created-marker-icon" :name="t('files', 'Recently created')" :path="mdiPlus" />
7+
</template>
8+
9+
<script lang="ts">
10+
import { mdiPlus } from '@mdi/js'
11+
import { t } from '@nextcloud/l10n'
12+
import { defineComponent } from 'vue'
13+
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
14+
15+
/**
16+
* A recently created icon to be used for overlaying recently created entries like the file preview / icon
17+
* It has a stroke around the icon to ensure enough contrast for accessibility.
18+
*
19+
* If the background has a hover state you might want to also apply it to the stroke like this:
20+
* ```scss
21+
* .parent:hover :deep(.recently-created-marker-icon svg path) {
22+
* stroke: var(--color-background-hover);
23+
* }
24+
* ```
25+
*/
26+
export default defineComponent({
27+
name: 'RecentlyCreatedIcon',
28+
components: {
29+
NcIconSvgWrapper,
30+
},
31+
32+
setup() {
33+
return {
34+
mdiPlus,
35+
}
36+
},
37+
38+
methods: {
39+
t,
40+
},
41+
})
42+
</script>
43+
44+
<style lang="scss" scoped>
45+
.recently-created-marker-icon {
46+
color: var(--color-element-success);
47+
// Override NcIconSvgWrapper defaults (clickable area)
48+
min-width: unset !important;
49+
min-height: unset !important;
50+
51+
:deep() {
52+
svg {
53+
// We added a stroke for a11y so we must increase the size to include the stroke
54+
width: 20px !important;
55+
height: 20px !important;
56+
57+
// Override NcIconSvgWrapper defaults of 20px
58+
max-width: unset !important;
59+
max-height: unset !important;
60+
61+
// Show a border around the icon for better contrast
62+
path {
63+
stroke: var(--color-main-background);
64+
stroke-width: 8px;
65+
stroke-linejoin: round;
66+
paint-order: stroke;
67+
}
68+
}
69+
}
70+
}
71+
</style>

apps/files/src/components/FilesListVirtual.vue

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -743,7 +743,7 @@ export default defineComponent({
743743
& > span {
744744
justify-content: flex-start;
745745
746-
&:not(.files-list__row-icon-favorite) svg {
746+
&:not(.files-list__row-icon-favorite):not(.files-list__row-icon-recently-created) svg {
747747
width: var(--icon-preview-size);
748748
height: var(--icon-preview-size);
749749
}
@@ -791,7 +791,8 @@ export default defineComponent({
791791
}
792792
}
793793
794-
&-favorite {
794+
&-favorite,
795+
&-recently-created {
795796
position: absolute;
796797
top: 0px;
797798
inset-inline-end: -10px;
@@ -993,8 +994,9 @@ export default defineComponent({
993994
}
994995
}
995996
996-
// Star icon in the top right
997-
.files-list__row-icon-favorite {
997+
// Icon in the top right
998+
.files-list__row-icon-favorite,
999+
.files-list__row-icon-recently-created {
9981000
position: absolute;
9991001
top: 0;
10001002
inset-inline-end: 0;

dist/files-main.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/files-main.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/private/Files/Cache/Cache.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,12 @@ public function getFolderContentsById($fileId) {
247247
* @throws \RuntimeException
248248
*/
249249
public function put($file, array $data) {
250+
// do not carry over creation_time to file versions, as each new version would otherwise
251+
// create a filecache_extended entry with the same creation_time as the original file
252+
if (str_starts_with($file, 'files_versions/')) {
253+
unset($data['creation_time']);
254+
}
255+
250256
if (($id = $this->getId($file)) > -1) {
251257
$this->update($id, $data);
252258
return $id;

0 commit comments

Comments
 (0)