mirror of
https://github.com/nextcloud/server.git
synced 2026-06-09 00:32:29 -04:00
feat: set creation_time on file creation and render recently created icon
Signed-off-by: Cristian Scheid <cristianscheid@gmail.com>
This commit is contained in:
parent
34484b591a
commit
7f89490cef
8 changed files with 129 additions and 5 deletions
|
|
@ -86,6 +86,7 @@ class FileSearchBackend implements ISearchBackend {
|
|||
new SearchPropertyDefinition('{DAV:}displayname', true, true, true),
|
||||
new SearchPropertyDefinition('{DAV:}getcontenttype', true, true, true),
|
||||
new SearchPropertyDefinition('{DAV:}getlastmodified', true, true, true, SearchPropertyDefinition::DATATYPE_DATETIME),
|
||||
new SearchPropertyDefinition('{http://nextcloud.org/ns}creation_time', true, true, true, SearchPropertyDefinition::DATATYPE_DATETIME),
|
||||
new SearchPropertyDefinition('{http://nextcloud.org/ns}upload_time', true, true, 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),
|
||||
|
|
@ -299,6 +300,8 @@ class FileSearchBackend implements ISearchBackend {
|
|||
return $node->getName();
|
||||
case '{DAV:}getlastmodified':
|
||||
return $node->getLastModified();
|
||||
case '{http://nextcloud.org/ns}creation_time':
|
||||
return $node->getNode()->getCreationTime();
|
||||
case '{http://nextcloud.org/ns}upload_time':
|
||||
return $node->getNode()->getUploadTime();
|
||||
case FilesPlugin::SIZE_PROPERTYNAME:
|
||||
|
|
@ -461,6 +464,8 @@ class FileSearchBackend implements ISearchBackend {
|
|||
return 'mimetype';
|
||||
case '{DAV:}getlastmodified':
|
||||
return 'mtime';
|
||||
case '{http://nextcloud.org/ns}creation_time':
|
||||
return 'creation_time';
|
||||
case '{http://nextcloud.org/ns}upload_time':
|
||||
return 'upload_time';
|
||||
case FilesPlugin::SIZE_PROPERTYNAME:
|
||||
|
|
|
|||
|
|
@ -42,6 +42,11 @@
|
|||
<FavoriteIcon v-once />
|
||||
</span>
|
||||
|
||||
<!-- Recently created icon -->
|
||||
<span v-else-if="isRecentView && isRecentlyCreated" class="files-list__row-icon-recently-created">
|
||||
<RecentlyCreatedIcon v-once />
|
||||
</span>
|
||||
|
||||
<component
|
||||
:is="fileOverlay"
|
||||
v-if="fileOverlay"
|
||||
|
|
@ -71,6 +76,7 @@ import PlayCircleIcon from 'vue-material-design-icons/PlayCircle.vue'
|
|||
import TagIcon from 'vue-material-design-icons/Tag.vue'
|
||||
import CollectivesIcon from './CollectivesIcon.vue'
|
||||
import FavoriteIcon from './FavoriteIcon.vue'
|
||||
import RecentlyCreatedIcon from './RecentlyCreatedIcon.vue'
|
||||
import { usePreviewImage } from '../../composables/usePreviewImage.ts'
|
||||
import logger from '../../logger.ts'
|
||||
import { isLivePhoto } from '../../services/LivePhotos.ts'
|
||||
|
|
@ -91,6 +97,7 @@ export default defineComponent({
|
|||
LinkIcon,
|
||||
NetworkIcon,
|
||||
TagIcon,
|
||||
RecentlyCreatedIcon,
|
||||
},
|
||||
|
||||
props: {
|
||||
|
|
@ -138,6 +145,29 @@ export default defineComponent({
|
|||
return this.source.attributes.favorite === 1
|
||||
},
|
||||
|
||||
isRecentlyCreated(): boolean {
|
||||
if (this.source.attributes.upload_time) {
|
||||
return false
|
||||
}
|
||||
|
||||
const creationDate = this.source.attributes.creationdate
|
||||
? new Date(this.source.attributes.creationdate)
|
||||
: null
|
||||
|
||||
if (!creationDate) {
|
||||
return false
|
||||
}
|
||||
|
||||
const oneDayAgo = new Date()
|
||||
oneDayAgo.setDate(oneDayAgo.getDate() - 1)
|
||||
|
||||
return creationDate > oneDayAgo
|
||||
},
|
||||
|
||||
isRecentView(): boolean {
|
||||
return this.$route?.params?.view === 'recent'
|
||||
},
|
||||
|
||||
userConfig(): UserConfig {
|
||||
return this.userConfigStore.userConfig
|
||||
},
|
||||
|
|
|
|||
78
apps/files/src/components/FileEntry/RecentlyCreatedIcon.vue
Normal file
78
apps/files/src/components/FileEntry/RecentlyCreatedIcon.vue
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
<!--
|
||||
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
<template>
|
||||
<NcIconSvgWrapper class="recently-created-marker-icon" :name="t('files', 'Recently created')" :svg="PlusSvg" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import PlusSvg from '@mdi/svg/svg/plus.svg?raw'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import { defineComponent } from 'vue'
|
||||
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
|
||||
|
||||
/**
|
||||
* A recently created icon to be used for overlaying recently created entries like the file preview / icon
|
||||
* It has a stroke around the icon to ensure enough contrast for accessibility.
|
||||
*
|
||||
* If the background has a hover state you might want to also apply it to the stroke like this:
|
||||
* ```scss
|
||||
* .parent:hover :deep(.favorite-marker-icon svg path) {
|
||||
* stroke: var(--color-background-hover);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export default defineComponent({
|
||||
name: 'RecentlyCreatedIcon',
|
||||
components: {
|
||||
NcIconSvgWrapper,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
PlusSvg,
|
||||
}
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
await this.$nextTick()
|
||||
// MDI default viewBox is "0 0 24 24" but we add a stroke of 10px so we must adjust it
|
||||
const el = this.$el.querySelector('svg')
|
||||
el?.setAttribute?.('viewBox', '-4 -4 30 30')
|
||||
},
|
||||
|
||||
methods: {
|
||||
t,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.recently-created-marker-icon {
|
||||
color: var(--color-element-success);
|
||||
// Override NcIconSvgWrapper defaults (clickable area)
|
||||
min-width: unset !important;
|
||||
min-height: unset !important;
|
||||
|
||||
:deep() {
|
||||
svg {
|
||||
// We added a stroke for a11y so we must increase the size to include the stroke
|
||||
width: 20px !important;
|
||||
height: 20px !important;
|
||||
|
||||
// Override NcIconSvgWrapper defaults of 20px
|
||||
max-width: unset !important;
|
||||
max-height: unset !important;
|
||||
|
||||
// Show a border around the icon for better contrast
|
||||
path {
|
||||
stroke: var(--color-main-background);
|
||||
stroke-width: 8px;
|
||||
stroke-linejoin: round;
|
||||
paint-order: stroke;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -743,7 +743,7 @@ export default defineComponent({
|
|||
& > span {
|
||||
justify-content: flex-start;
|
||||
|
||||
&:not(.files-list__row-icon-favorite) svg {
|
||||
&:not(.files-list__row-icon-favorite):not(.files-list__row-icon-recently-created) svg {
|
||||
width: var(--icon-preview-size);
|
||||
height: var(--icon-preview-size);
|
||||
}
|
||||
|
|
@ -791,7 +791,8 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
|
||||
&-favorite {
|
||||
&-favorite,
|
||||
&-recently-created {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
inset-inline-end: -10px;
|
||||
|
|
@ -993,8 +994,9 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
|
||||
// Star icon in the top right
|
||||
.files-list__row-icon-favorite {
|
||||
// Icon in the top right
|
||||
.files-list__row-icon-favorite,
|
||||
.files-list__row-icon-recently-created {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
inset-inline-end: 0;
|
||||
|
|
|
|||
|
|
@ -250,6 +250,12 @@ class Cache implements ICache {
|
|||
* @throws \RuntimeException
|
||||
*/
|
||||
public function put($file, array $data) {
|
||||
// do not carry over creation_time to file versions, as each new version would otherwise
|
||||
// create a filecache_extended entry with the same creation_time as the original file
|
||||
if (str_starts_with($file, 'files_versions/')) {
|
||||
unset($data['creation_time']);
|
||||
}
|
||||
|
||||
if (($id = $this->getId($file)) > -1) {
|
||||
$this->update($id, $data);
|
||||
return $id;
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ class QuerySearchHelper {
|
|||
|
||||
$requestedFields = $this->searchBuilder->extractRequestedFields($searchQuery->getSearchOperation());
|
||||
|
||||
$joinExtendedCache = in_array('upload_time', $requestedFields);
|
||||
$joinExtendedCache = in_array('creation_time', $requestedFields) || in_array('upload_time', $requestedFields);
|
||||
|
||||
$query = $builder->selectFileCache('file', $joinExtendedCache);
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ class SearchBuilder {
|
|||
'share_with' => 'string',
|
||||
'share_type' => 'integer',
|
||||
'owner' => 'string',
|
||||
'creation_time' => 'integer',
|
||||
'upload_time' => 'integer',
|
||||
];
|
||||
|
||||
|
|
@ -258,6 +259,7 @@ class SearchBuilder {
|
|||
'share_with' => ['eq'],
|
||||
'share_type' => ['eq'],
|
||||
'owner' => ['eq'],
|
||||
'creation_time' => ['eq', 'gt', 'lt', 'gte', 'lte'],
|
||||
'upload_time' => ['eq', 'gt', 'lt', 'gte', 'lte'],
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -175,6 +175,7 @@ class Folder extends Node implements IFolder {
|
|||
throw new NotPermittedException('Could not create path "' . $fullPath . '"');
|
||||
}
|
||||
$node = new File($this->root, $this->view, $fullPath, null, $this);
|
||||
$this->view->putFileInfo($fullPath, ['creation_time' => time()]);
|
||||
$this->sendHooks(['postWrite', 'postCreate'], [$node]);
|
||||
return $node;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue