summaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
authorMarco <marcoambrosini@pm.me>2024-08-01 08:35:08 +0200
committerGitHub <noreply@github.com>2024-08-01 08:35:08 +0200
commit7221019151d8b6774a605c227c1e8e6c135c32c0 (patch)
treed9b8cdeac8acb88d369d4ef44474828b810dec15 /apps
parent9855485b0dbe292315068ca90b3193fcbdbb5df7 (diff)
parent75f80cbdcbfee506d3e3741d98b1decfbb4da3c1 (diff)
downloadnextcloud-server-7221019151d8b6774a605c227c1e8e6c135c32c0.tar.gz
nextcloud-server-7221019151d8b6774a605c227c1e8e6c135c32c0.zip
Merge branch 'master' into feat/add-small-font-size-variable
Diffstat (limited to 'apps')
-rw-r--r--apps/files/js/fileactions.js2
-rw-r--r--apps/files/src/actions/deleteAction.ts2
-rw-r--r--apps/files/src/actions/downloadAction.spec.ts4
-rw-r--r--apps/files/src/actions/downloadAction.ts6
-rw-r--r--apps/files/src/actions/favoriteAction.ts2
-rw-r--r--apps/files/src/actions/sidebarAction.ts2
-rw-r--r--apps/files/src/components/DragAndDropNotice.vue2
-rw-r--r--apps/files/src/components/FileEntry/FileEntryActions.vue43
-rw-r--r--apps/files/src/components/FileEntry/FileEntryCheckbox.vue11
-rw-r--r--apps/files/src/components/FileEntry/FileEntryName.vue60
-rw-r--r--apps/files/src/components/FileEntryMixin.ts44
-rw-r--r--apps/files/src/components/FilesListTableHeader.vue2
-rw-r--r--apps/files/src/components/FilesListTableHeaderActions.vue23
-rw-r--r--apps/files/src/components/FilesListVirtual.vue18
-rw-r--r--apps/files/src/components/NavigationQuota.vue2
-rw-r--r--apps/files/src/components/TransferOwnershipDialogue.vue2
-rw-r--r--apps/files/src/components/VirtualList.vue2
-rw-r--r--apps/files/src/logger.ts (renamed from apps/files/src/logger.js)0
-rw-r--r--apps/files/src/newMenu/newTemplatesFolder.ts6
-rw-r--r--apps/files/src/services/DropService.ts2
-rw-r--r--apps/files/src/services/DropServiceUtils.ts2
-rw-r--r--apps/files/src/services/Files.ts2
-rw-r--r--apps/files/src/services/ServiceWorker.js2
-rw-r--r--apps/files/src/views/FilesList.vue82
-rw-r--r--apps/files/src/views/Navigation.vue2
-rw-r--r--apps/files/src/views/Sidebar.vue2
-rw-r--r--apps/files/src/views/TemplatePicker.vue2
-rw-r--r--apps/files_sharing/src/new/newFileRequest.ts35
-rw-r--r--apps/files_trashbin/src/actions/restoreAction.ts4
-rw-r--r--apps/systemtags/src/services/systemtags.ts27
30 files changed, 214 insertions, 181 deletions
diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js
index 93d342ed391..579040d8d60 100644
--- a/apps/files/js/fileactions.js
+++ b/apps/files/js/fileactions.js
@@ -722,7 +722,7 @@
});
}
- if (!/Android|iPhone|iPad|iPod/i.test(navigator.userAgent)) {
+ if (!/Android|iPhone|iPad|iPod/i.test(navigator.userAgent) && !!window.oc_current_user) {
this.registerAction({
name: 'EditLocally',
displayName: function(context) {
diff --git a/apps/files/src/actions/deleteAction.ts b/apps/files/src/actions/deleteAction.ts
index 53b5690d4dd..63335db194e 100644
--- a/apps/files/src/actions/deleteAction.ts
+++ b/apps/files/src/actions/deleteAction.ts
@@ -11,7 +11,7 @@ import CloseSvg from '@mdi/svg/svg/close.svg?raw'
import NetworkOffSvg from '@mdi/svg/svg/network-off.svg?raw'
import TrashCanSvg from '@mdi/svg/svg/trash-can.svg?raw'
-import logger from '../logger.js'
+import logger from '../logger.ts'
import { askConfirmation, canDisconnectOnly, canUnshareOnly, deleteNode, displayName, isTrashbinEnabled } from './deleteUtils'
const queue = new PQueue({ concurrency: 5 })
diff --git a/apps/files/src/actions/downloadAction.spec.ts b/apps/files/src/actions/downloadAction.spec.ts
index 8aca20eb4d4..59e533441be 100644
--- a/apps/files/src/actions/downloadAction.spec.ts
+++ b/apps/files/src/actions/downloadAction.spec.ts
@@ -4,7 +4,7 @@
*/
import { action } from './downloadAction'
import { expect } from '@jest/globals'
-import { File, Folder, Permission, View, FileAction } from '@nextcloud/files'
+import { File, Folder, Permission, View, FileAction, DefaultType } from '@nextcloud/files'
const view = {
id: 'files',
@@ -23,7 +23,7 @@ describe('Download action conditions tests', () => {
expect(action.id).toBe('download')
expect(action.displayName([], view)).toBe('Download')
expect(action.iconSvgInline([], view)).toBe('<svg>SvgMock</svg>')
- expect(action.default).toBeUndefined()
+ expect(action.default).toBe(DefaultType.DEFAULT)
expect(action.order).toBe(30)
})
})
diff --git a/apps/files/src/actions/downloadAction.ts b/apps/files/src/actions/downloadAction.ts
index 3ba4e23f0cf..cce1e48579a 100644
--- a/apps/files/src/actions/downloadAction.ts
+++ b/apps/files/src/actions/downloadAction.ts
@@ -4,9 +4,9 @@
*/
import type { ShareAttribute } from '../../../files_sharing/src/sharing'
-import { FileAction, Permission, Node, FileType, View } from '@nextcloud/files'
+import { FileAction, Permission, Node, FileType, View, DefaultType } from '@nextcloud/files'
+import { t } from '@nextcloud/l10n'
import { generateUrl } from '@nextcloud/router'
-import { translate as t } from '@nextcloud/l10n'
import ArrowDownSvg from '@mdi/svg/svg/arrow-down.svg?raw'
@@ -46,6 +46,8 @@ const isDownloadable = function(node: Node) {
export const action = new FileAction({
id: 'download',
+ default: DefaultType.DEFAULT,
+
displayName: () => t('files', 'Download'),
iconSvgInline: () => ArrowDownSvg,
diff --git a/apps/files/src/actions/favoriteAction.ts b/apps/files/src/actions/favoriteAction.ts
index 3815fc1a9a3..bb7d82ba2cd 100644
--- a/apps/files/src/actions/favoriteAction.ts
+++ b/apps/files/src/actions/favoriteAction.ts
@@ -12,7 +12,7 @@ import Vue from 'vue'
import StarOutlineSvg from '@mdi/svg/svg/star-outline.svg?raw'
import StarSvg from '@mdi/svg/svg/star.svg?raw'
-import logger from '../logger.js'
+import logger from '../logger.ts'
import { encodePath } from '@nextcloud/paths'
// If any of the nodes is not favorited, we display the favorite action.
diff --git a/apps/files/src/actions/sidebarAction.ts b/apps/files/src/actions/sidebarAction.ts
index 21b5dfc2878..f00088f8d0c 100644
--- a/apps/files/src/actions/sidebarAction.ts
+++ b/apps/files/src/actions/sidebarAction.ts
@@ -6,7 +6,7 @@ import { Permission, type Node, View, FileAction } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import InformationSvg from '@mdi/svg/svg/information-variant.svg?raw'
-import logger from '../logger.js'
+import logger from '../logger.ts'
export const ACTION_DETAILS = 'details'
diff --git a/apps/files/src/components/DragAndDropNotice.vue b/apps/files/src/components/DragAndDropNotice.vue
index 0483e71f43b..cc0dc9104b2 100644
--- a/apps/files/src/components/DragAndDropNotice.vue
+++ b/apps/files/src/components/DragAndDropNotice.vue
@@ -37,7 +37,7 @@ import TrayArrowDownIcon from 'vue-material-design-icons/TrayArrowDown.vue'
import { useNavigation } from '../composables/useNavigation'
import { dataTransferToFileTree, onDropExternalFiles } from '../services/DropService'
-import logger from '../logger.js'
+import logger from '../logger.ts'
export default defineComponent({
name: 'DragAndDropNotice',
diff --git a/apps/files/src/components/FileEntry/FileEntryActions.vue b/apps/files/src/components/FileEntry/FileEntryActions.vue
index 3df4289b1a0..2c3444a19fd 100644
--- a/apps/files/src/components/FileEntry/FileEntryActions.vue
+++ b/apps/files/src/components/FileEntry/FileEntryActions.vue
@@ -79,10 +79,10 @@
import type { PropType, ShallowRef } from 'vue'
import type { FileAction, Node, View } from '@nextcloud/files'
-import { DefaultType, NodeStatus, getFileActions } from '@nextcloud/files'
+import { DefaultType, NodeStatus } from '@nextcloud/files'
import { showError, showSuccess } from '@nextcloud/dialogs'
import { translate as t } from '@nextcloud/l10n'
-import { defineComponent } from 'vue'
+import { defineComponent, inject } from 'vue'
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
@@ -93,10 +93,7 @@ import ArrowLeftIcon from 'vue-material-design-icons/ArrowLeft.vue'
import CustomElementRender from '../CustomElementRender.vue'
import { useNavigation } from '../../composables/useNavigation'
-import logger from '../../logger.js'
-
-// The registered actions list
-const actions = getFileActions()
+import logger from '../../logger.ts'
export default defineComponent({
name: 'FileEntryActions',
@@ -136,10 +133,12 @@ export default defineComponent({
setup() {
const { currentView } = useNavigation()
+ const enabledFileActions = inject<FileAction[]>('enabledFileActions', [])
return {
// The file list is guaranteed to be only shown with active view
currentView: currentView as ShallowRef<View>,
+ enabledFileActions,
}
},
@@ -158,23 +157,12 @@ export default defineComponent({
return this.source.status === NodeStatus.LOADING
},
- // Sorted actions that are enabled for this node
- enabledActions() {
- if (this.source.status === NodeStatus.FAILED) {
- return []
- }
-
- return actions
- .filter(action => !action.enabled || action.enabled([this.source], this.currentView))
- .sort((a, b) => (a.order || 0) - (b.order || 0))
- },
-
// Enabled action that are displayed inline
enabledInlineActions() {
if (this.filesListWidth < 768 || this.gridMode) {
return []
}
- return this.enabledActions.filter(action => action?.inline?.(this.source, this.currentView))
+ return this.enabledFileActions.filter(action => action?.inline?.(this.source, this.currentView))
},
// Enabled action that are displayed inline with a custom render function
@@ -182,12 +170,7 @@ export default defineComponent({
if (this.gridMode) {
return []
}
- return this.enabledActions.filter(action => typeof action.renderInline === 'function')
- },
-
- // Default actions
- enabledDefaultActions() {
- return this.enabledActions.filter(action => !!action?.default)
+ return this.enabledFileActions.filter(action => typeof action.renderInline === 'function')
},
// Actions shown in the menu
@@ -202,7 +185,7 @@ export default defineComponent({
// Showing inline first for the NcActions inline prop
...this.enabledInlineActions,
// Then the rest
- ...this.enabledActions.filter(action => action.default !== DefaultType.HIDDEN && typeof action.renderInline !== 'function'),
+ ...this.enabledFileActions.filter(action => action.default !== DefaultType.HIDDEN && typeof action.renderInline !== 'function'),
].filter((value, index, self) => {
// Then we filter duplicates to prevent inline actions to be shown twice
return index === self.findIndex(action => action.id === value.id)
@@ -216,7 +199,7 @@ export default defineComponent({
},
enabledSubmenuActions() {
- return this.enabledActions
+ return this.enabledFileActions
.filter(action => action.parent)
.reduce((arr, action) => {
if (!arr[action.parent!]) {
@@ -305,14 +288,6 @@ export default defineComponent({
}
}
},
- execDefaultAction(event) {
- if (this.enabledDefaultActions.length > 0) {
- event.preventDefault()
- event.stopPropagation()
- // Execute the first default action if any
- this.enabledDefaultActions[0].exec(this.source, this.currentView, this.currentDir)
- }
- },
isMenu(id: string) {
return this.enabledSubmenuActions[id]?.length > 0
diff --git a/apps/files/src/components/FileEntry/FileEntryCheckbox.vue b/apps/files/src/components/FileEntry/FileEntryCheckbox.vue
index 987b48ef8ae..059e2f89f5f 100644
--- a/apps/files/src/components/FileEntry/FileEntryCheckbox.vue
+++ b/apps/files/src/components/FileEntry/FileEntryCheckbox.vue
@@ -14,17 +14,20 @@
</template>
<script lang="ts">
-import { Node, FileType } from '@nextcloud/files'
+import type { Node } from '@nextcloud/files'
+import type { PropType } from 'vue'
+import type { FileSource } from '../../types.ts'
+
+import { FileType } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
-import { type PropType, defineComponent } from 'vue'
+import { defineComponent } from 'vue'
import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
import { useKeyboardStore } from '../../store/keyboard.ts'
import { useSelectionStore } from '../../store/selection.ts'
-import logger from '../../logger.js'
-import type { FileSource } from '../../types.ts'
+import logger from '../../logger.ts'
export default defineComponent({
name: 'FileEntryCheckbox',
diff --git a/apps/files/src/components/FileEntry/FileEntryName.vue b/apps/files/src/components/FileEntry/FileEntryName.vue
index 7a6ad2a1051..1d45f7de17e 100644
--- a/apps/files/src/components/FileEntry/FileEntryName.vue
+++ b/apps/files/src/components/FileEntry/FileEntryName.vue
@@ -37,22 +37,23 @@
</template>
<script lang="ts">
-import type { Node } from '@nextcloud/files'
+import type { FileAction, Node } from '@nextcloud/files'
import type { PropType } from 'vue'
import axios, { isAxiosError } from '@nextcloud/axios'
import { showError, showSuccess } from '@nextcloud/dialogs'
import { emit } from '@nextcloud/event-bus'
-import { FileType, NodeStatus, Permission } from '@nextcloud/files'
+import { FileType, NodeStatus } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
-import { defineComponent } from 'vue'
+import { defineComponent, inject } from 'vue'
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
import { useNavigation } from '../../composables/useNavigation'
+import { useRouteParameters } from '../../composables/useRouteParameters.ts'
import { useRenamingStore } from '../../store/renaming.ts'
import { getFilenameValidity } from '../../utils/filenameValidity.ts'
-import logger from '../../logger.js'
+import logger from '../../logger.ts'
export default defineComponent({
name: 'FileEntryName',
@@ -96,10 +97,15 @@ export default defineComponent({
setup() {
const { currentView } = useNavigation()
+ const { directory } = useRouteParameters()
const renamingStore = useRenamingStore()
+ const defaultFileAction = inject<FileAction | undefined>('defaultFileAction')
+
return {
currentView,
+ defaultFileAction,
+ directory,
renamingStore,
}
@@ -139,32 +145,20 @@ export default defineComponent({
}
}
- const enabledDefaultActions = this.$parent?.$refs?.actions?.enabledDefaultActions
- if (enabledDefaultActions?.length > 0) {
- const action = enabledDefaultActions[0]
- const displayName = action.displayName([this.source], this.currentView)
+ if (this.defaultFileAction && this.currentView) {
+ const displayName = this.defaultFileAction.displayName([this.source], this.currentView)
return {
- is: 'a',
+ is: 'button',
params: {
+ 'aria-label': displayName,
title: displayName,
- role: 'button',
- tabindex: '0',
- },
- }
- }
-
- if (this.source?.permissions & Permission.READ) {
- return {
- is: 'a',
- params: {
- download: this.source.basename,
- href: this.source.source,
- title: t('files', 'Download file {name}', { name: `${this.basename}${this.extension}` }),
tabindex: '0',
},
}
}
+ // nothing interactive here, there is no default action
+ // so if not even the download action works we only can show the list entry
return {
is: 'span',
}
@@ -280,12 +274,15 @@ export default defineComponent({
// Reset the renaming store
this.stopRenaming()
this.$nextTick(() => {
- this.$refs.basename?.focus()
+ const nameContainter = this.$refs.basename as HTMLElement | undefined
+ nameContainter?.focus()
})
} catch (error) {
logger.error('Error while renaming file', { error })
+ // Rename back as it failed
this.source.rename(oldName)
- this.$refs.renameInput?.focus()
+ // And ensure we reset to the renaming state
+ this.startRenaming()
if (isAxiosError(error)) {
// TODO: 409 means current folder does not exist, redirect ?
@@ -293,7 +290,7 @@ export default defineComponent({
showError(t('files', 'Could not rename "{oldName}", it does not exist any more', { oldName }))
return
} else if (error?.response?.status === 412) {
- showError(t('files', 'The name "{newName}" is already used in the folder "{dir}". Please choose a different name.', { newName, dir: this.currentDir }))
+ showError(t('files', 'The name "{newName}" is already used in the folder "{dir}". Please choose a different name.', { newName, dir: this.directory }))
return
}
}
@@ -309,3 +306,16 @@ export default defineComponent({
},
})
</script>
+
+<style scoped lang="scss">
+button.files-list__row-name-link {
+ background-color: unset;
+ border: none;
+ font-weight: normal;
+
+ &:active {
+ // No active styles - handled by the row entry
+ background-color: unset !important;
+ }
+}
+</style>
diff --git a/apps/files/src/components/FileEntryMixin.ts b/apps/files/src/components/FileEntryMixin.ts
index d9117053dd8..8681e9c3cb2 100644
--- a/apps/files/src/components/FileEntryMixin.ts
+++ b/apps/files/src/components/FileEntryMixin.ts
@@ -3,11 +3,11 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-import type { ComponentPublicInstance, PropType } from 'vue'
+import type { PropType } from 'vue'
import type { FileSource } from '../types.ts'
import { showError } from '@nextcloud/dialogs'
-import { FileType, Permission, Folder, File as NcFile, NodeStatus, Node } from '@nextcloud/files'
+import { FileType, Permission, Folder, File as NcFile, NodeStatus, Node, getFileActions } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import { generateUrl } from '@nextcloud/router'
import { vOnClickOutside } from '@vueuse/components'
@@ -18,11 +18,12 @@ import { action as sidebarAction } from '../actions/sidebarAction.ts'
import { getDragAndDropPreview } from '../utils/dragUtils.ts'
import { hashCode } from '../utils/hashUtils.ts'
import { dataTransferToFileTree, onDropExternalFiles, onDropInternalFiles } from '../services/DropService.ts'
-import logger from '../logger.js'
-import FileEntryActions from '../components/FileEntry/FileEntryActions.vue'
+import logger from '../logger.ts'
Vue.directive('onClickOutside', vOnClickOutside)
+const actions = getFileActions()
+
export default defineComponent({
props: {
source: {
@@ -47,6 +48,13 @@ export default defineComponent({
},
},
+ provide() {
+ return {
+ defaultFileAction: this.defaultFileAction,
+ enabledFileActions: this.enabledFileActions,
+ }
+ },
+
data() {
return {
loading: '',
@@ -178,6 +186,23 @@ export default defineComponent({
color: `color-mix(in srgb, var(--color-main-text) ${ratio}%, var(--color-text-maxcontrast))`,
}
},
+
+ /**
+ * Sorted actions that are enabled for this node
+ */
+ enabledFileActions() {
+ if (this.source.status === NodeStatus.FAILED) {
+ return []
+ }
+
+ return actions
+ .filter(action => !action.enabled || action.enabled([this.source], this.currentView))
+ .sort((a, b) => (a.order || 0) - (b.order || 0))
+ },
+
+ defaultFileAction() {
+ return this.enabledFileActions.find((action) => action.default !== undefined)
+ },
},
watch: {
@@ -261,8 +286,15 @@ export default defineComponent({
return false
}
- const actions = this.$refs.actions as ComponentPublicInstance<typeof FileEntryActions>
- actions.execDefaultAction(event)
+ if (this.defaultFileAction) {
+ event.preventDefault()
+ event.stopPropagation()
+ // Execute the first default action if any
+ this.defaultFileAction.exec(this.source, this.currentView, this.currentDir)
+ } else {
+ // fallback to open in current tab
+ window.open(generateUrl('/f/{fileId}', { fileId: this.fileid }), '_self')
+ }
},
openDetailsIfAvailable(event) {
diff --git a/apps/files/src/components/FilesListTableHeader.vue b/apps/files/src/components/FilesListTableHeader.vue
index 723dd574da8..e91cfa055c1 100644
--- a/apps/files/src/components/FilesListTableHeader.vue
+++ b/apps/files/src/components/FilesListTableHeader.vue
@@ -68,7 +68,7 @@ import { useNavigation } from '../composables/useNavigation'
import { useFilesStore } from '../store/files.ts'
import { useSelectionStore } from '../store/selection.ts'
import filesSortingMixin from '../mixins/filesSorting.ts'
-import logger from '../logger.js'
+import logger from '../logger.ts'
export default defineComponent({
name: 'FilesListTableHeader',
diff --git a/apps/files/src/components/FilesListTableHeaderActions.vue b/apps/files/src/components/FilesListTableHeaderActions.vue
index 2916ce55e67..5e32a07eae1 100644
--- a/apps/files/src/components/FilesListTableHeaderActions.vue
+++ b/apps/files/src/components/FilesListTableHeaderActions.vue
@@ -26,21 +26,25 @@
</template>
<script lang="ts">
-import { Node, NodeStatus, View, getFileActions } from '@nextcloud/files'
+import type { Node, View } from '@nextcloud/files'
+import type { PropType } from 'vue'
+import type { FileSource } from '../types'
+
+import { NodeStatus, getFileActions } from '@nextcloud/files'
import { showError, showSuccess } from '@nextcloud/dialogs'
import { translate } from '@nextcloud/l10n'
+import { defineComponent } from 'vue'
+
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
-import Vue, { defineComponent, type PropType } from 'vue'
import { useActionsMenuStore } from '../store/actionsmenu.ts'
import { useFilesStore } from '../store/files.ts'
import { useSelectionStore } from '../store/selection.ts'
import filesListWidthMixin from '../mixins/filesListWidth.ts'
-import logger from '../logger.js'
-import type { FileSource } from '../types'
+import logger from '../logger.ts'
// The registered actions list
const actions = getFileActions()
@@ -136,11 +140,10 @@ export default defineComponent({
/**
* Get a cached note from the store
*
- * @param {number} fileId the file id to get
- * @return {Folder|File}
+ * @param source The source of the node to get
*/
- getNode(fileId) {
- return this.filesStore.getNode(fileId)
+ getNode(source: string): Node|undefined {
+ return this.filesStore.getNode(source)
},
async onActionClick(action) {
@@ -150,7 +153,7 @@ export default defineComponent({
// Set loading markers
this.loading = action.id
this.nodes.forEach(node => {
- Vue.set(node, 'status', NodeStatus.LOADING)
+ this.$set(node, 'status', NodeStatus.LOADING)
})
// Dispatch action execution
@@ -190,7 +193,7 @@ export default defineComponent({
// Remove loading markers
this.loading = null
this.nodes.forEach(node => {
- Vue.set(node, 'status', undefined)
+ this.$set(node, 'status', undefined)
})
}
},
diff --git a/apps/files/src/components/FilesListVirtual.vue b/apps/files/src/components/FilesListVirtual.vue
index 1b9475333ab..4c9cea00f9c 100644
--- a/apps/files/src/components/FilesListVirtual.vue
+++ b/apps/files/src/components/FilesListVirtual.vue
@@ -81,7 +81,7 @@ import FilesListTableFooter from './FilesListTableFooter.vue'
import FilesListTableHeader from './FilesListTableHeader.vue'
import filesListWidthMixin from '../mixins/filesListWidth.ts'
import VirtualList from './VirtualList.vue'
-import logger from '../logger.js'
+import logger from '../logger.ts'
import FilesListTableHeaderActions from './FilesListTableHeaderActions.vue'
import FileListFilters from './FileListFilters.vue'
@@ -600,24 +600,26 @@ export default defineComponent({
// Take as much space as possible
flex: 1 1 auto;
- a {
+ button.files-list__row-name-link {
display: flex;
align-items: center;
+ text-align: start;
// Fill cell height and width
width: 100%;
height: 100%;
// Necessary for flex grow to work
min-width: 0;
+ margin: 0;
// Already added to the inner text, see rule below
&:focus-visible {
- outline: none;
+ outline: none !important;
}
// Keyboard indicator a11y
&:focus .files-list__row-name-text {
- outline: 2px solid var(--color-main-text) !important;
- border-radius: 20px;
+ outline: var(--border-width-input-focused) solid var(--color-main-text) !important;
+ border-radius: var(--border-radius-element);
}
&:focus:not(:focus-visible) .files-list__row-name-text {
outline: none !important;
@@ -627,7 +629,7 @@ export default defineComponent({
.files-list__row-name-text {
color: var(--color-main-text);
// Make some space for the outline
- padding: 5px 10px;
+ padding: var(--default-grid-baseline) calc(2 * var(--default-grid-baseline));
margin-left: -10px;
// Align two name and ext
display: inline-flex;
@@ -791,10 +793,6 @@ tbody.files-list__tbody.files-list__tbody--grid {
height: var(--icon-preview-size);
}
- a.files-list__row-name-link {
- height: var(--name-height);
- }
-
.files-list__row-name-text {
margin: 0;
// Ensure that the outline is not too close to the text.
diff --git a/apps/files/src/components/NavigationQuota.vue b/apps/files/src/components/NavigationQuota.vue
index 0619f6bc3fd..0e5ef510342 100644
--- a/apps/files/src/components/NavigationQuota.vue
+++ b/apps/files/src/components/NavigationQuota.vue
@@ -37,7 +37,7 @@ import ChartPie from 'vue-material-design-icons/ChartPie.vue'
import NcAppNavigationItem from '@nextcloud/vue/dist/Components/NcAppNavigationItem.js'
import NcProgressBar from '@nextcloud/vue/dist/Components/NcProgressBar.js'
-import logger from '../logger.js'
+import logger from '../logger.ts'
export default {
name: 'NavigationQuota',
diff --git a/apps/files/src/components/TransferOwnershipDialogue.vue b/apps/files/src/components/TransferOwnershipDialogue.vue
index 346bc3bbeb8..f13e6655354 100644
--- a/apps/files/src/components/TransferOwnershipDialogue.vue
+++ b/apps/files/src/components/TransferOwnershipDialogue.vue
@@ -52,7 +52,7 @@ import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js'
import Vue from 'vue'
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
-import logger from '../logger.js'
+import logger from '../logger.ts'
const picker = getFilePickerBuilder(t('files', 'Choose a file or folder to transfer'))
.setMultiSelect(false)
diff --git a/apps/files/src/components/VirtualList.vue b/apps/files/src/components/VirtualList.vue
index 6206f86cda5..859aba6c834 100644
--- a/apps/files/src/components/VirtualList.vue
+++ b/apps/files/src/components/VirtualList.vue
@@ -59,7 +59,7 @@ import debounce from 'debounce'
import Vue from 'vue'
import filesListWidthMixin from '../mixins/filesListWidth.ts'
-import logger from '../logger.js'
+import logger from '../logger.ts'
interface RecycledPoolItem {
key: string,
diff --git a/apps/files/src/logger.js b/apps/files/src/logger.ts
index 33f87b424e0..33f87b424e0 100644
--- a/apps/files/src/logger.js
+++ b/apps/files/src/logger.ts
diff --git a/apps/files/src/newMenu/newTemplatesFolder.ts b/apps/files/src/newMenu/newTemplatesFolder.ts
index e2c27ce067f..43a6f08b525 100644
--- a/apps/files/src/newMenu/newTemplatesFolder.ts
+++ b/apps/files/src/newMenu/newTemplatesFolder.ts
@@ -15,7 +15,7 @@ import { newNodeName } from '../utils/newNodeDialog'
import PlusSvg from '@mdi/svg/svg/plus.svg?raw'
import axios from '@nextcloud/axios'
-import logger from '../logger.js'
+import logger from '../logger.ts'
let templatesPath = loadState<string|false>('files', 'templates_path', false)
logger.debug('Initial templates folder', { templatesPath })
@@ -53,9 +53,9 @@ const initTemplatesFolder = async function(directory: Folder, name: string) {
export const entry = {
id: 'template-picker',
- displayName: t('files', 'Create new templates folder'),
+ displayName: t('files', 'Create templates folder'),
iconSvgInline: PlusSvg,
- order: 10,
+ order: 30,
enabled(context: Folder): boolean {
// Templates folder already initialized
if (templatesPath) {
diff --git a/apps/files/src/services/DropService.ts b/apps/files/src/services/DropService.ts
index b947c612949..63e7c213f4f 100644
--- a/apps/files/src/services/DropService.ts
+++ b/apps/files/src/services/DropService.ts
@@ -17,7 +17,7 @@ import Vue from 'vue'
import { Directory, traverseTree, resolveConflict, createDirectoryIfNotExists } from './DropServiceUtils'
import { handleCopyMoveNodeTo } from '../actions/moveOrCopyAction'
import { MoveCopyAction } from '../actions/moveOrCopyActionUtils'
-import logger from '../logger.js'
+import logger from '../logger.ts'
/**
* This function converts a list of DataTransferItems to a file tree.
diff --git a/apps/files/src/services/DropServiceUtils.ts b/apps/files/src/services/DropServiceUtils.ts
index 27478dd956a..f10a09cfe27 100644
--- a/apps/files/src/services/DropServiceUtils.ts
+++ b/apps/files/src/services/DropServiceUtils.ts
@@ -10,7 +10,7 @@ import { openConflictPicker } from '@nextcloud/upload'
import { showError, showInfo } from '@nextcloud/dialogs'
import { translate as t } from '@nextcloud/l10n'
-import logger from '../logger.js'
+import logger from '../logger.ts'
/**
* This represents a Directory in the file tree
diff --git a/apps/files/src/services/Files.ts b/apps/files/src/services/Files.ts
index 10e553592fe..86d9f9b8e80 100644
--- a/apps/files/src/services/Files.ts
+++ b/apps/files/src/services/Files.ts
@@ -8,7 +8,7 @@ import type { FileStat, ResponseDataDetailed } from 'webdav'
import { CancelablePromise } from 'cancelable-promise'
import { File, Folder, davGetDefaultPropfind, davResultToNode, davRootPath } from '@nextcloud/files'
import { client } from './WebdavClient.ts'
-import logger from '../logger.js'
+import logger from '../logger.ts'
/**
* Slim wrapper over `@nextcloud/files` `davResultToNode` to allow using the function with `Array.map`
diff --git a/apps/files/src/services/ServiceWorker.js b/apps/files/src/services/ServiceWorker.js
index 477354d1c36..7d03af65e44 100644
--- a/apps/files/src/services/ServiceWorker.js
+++ b/apps/files/src/services/ServiceWorker.js
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { generateUrl } from '@nextcloud/router'
-import logger from '../logger.js'
+import logger from '../logger.ts'
export default () => {
if ('serviceWorker' in navigator) {
diff --git a/apps/files/src/views/FilesList.vue b/apps/files/src/views/FilesList.vue
index ca868b5d526..d486b6f1bae 100644
--- a/apps/files/src/views/FilesList.vue
+++ b/apps/files/src/views/FilesList.vue
@@ -74,32 +74,37 @@
:name="t('files', 'Loading current folder')" />
<!-- Empty content placeholder -->
- <NcEmptyContent v-else-if="!loading && isEmptyDir"
- :name="currentView?.emptyTitle || t('files', 'No files in here')"
- :description="currentView?.emptyCaption || t('files', 'Upload some content or sync with your devices!')"
- data-cy-files-content-empty>
- <template v-if="directory !== '/'" #action>
- <!-- Uploader -->
- <UploadPicker v-if="currentFolder && canUpload && !isQuotaExceeded"
- allow-folders
- class="files-list__header-upload-button"
- :content="getContent"
- :destination="currentFolder"
- :forbidden-characters="forbiddenCharacters"
- multiple
- @failed="onUploadFail"
- @uploaded="onUpload" />
- <NcButton v-else
- :aria-label="t('files', 'Go to the previous folder')"
- :to="toPreviousDir"
- type="primary">
- {{ t('files', 'Go back') }}
- </NcButton>
- </template>
- <template #icon>
- <NcIconSvgWrapper :svg="currentView.icon" />
- </template>
- </NcEmptyContent>
+ <template v-else-if="!loading && isEmptyDir">
+ <div v-if="currentView?.emptyView" class="files-list__empty-view-wrapper">
+ <div ref="customEmptyView" />
+ </div>
+ <NcEmptyContent v-else
+ :name="currentView?.emptyTitle || t('files', 'No files in here')"
+ :description="currentView?.emptyCaption || t('files', 'Upload some content or sync with your devices!')"
+ data-cy-files-content-empty>
+ <template v-if="directory !== '/'" #action>
+ <!-- Uploader -->
+ <UploadPicker v-if="currentFolder && canUpload && !isQuotaExceeded"
+ allow-folders
+ class="files-list__header-upload-button"
+ :content="getContent"
+ :destination="currentFolder"
+ :forbidden-characters="forbiddenCharacters"
+ multiple
+ @failed="onUploadFail"
+ @uploaded="onUpload" />
+ <NcButton v-else
+ :aria-label="t('files', 'Go to the previous folder')"
+ :to="toPreviousDir"
+ type="primary">
+ {{ t('files', 'Go back') }}
+ </NcButton>
+ </template>
+ <template #icon>
+ <NcIconSvgWrapper :svg="currentView.icon" />
+ </template>
+ </NcEmptyContent>
+ </template>
<!-- File list -->
<FilesListVirtual v-else
@@ -154,7 +159,7 @@ import BreadCrumbs from '../components/BreadCrumbs.vue'
import FilesListVirtual from '../components/FilesListVirtual.vue'
import filesListWidthMixin from '../mixins/filesListWidth.ts'
import filesSortingMixin from '../mixins/filesSorting.ts'
-import logger from '../logger.js'
+import logger from '../logger.ts'
import DragAndDropNotice from '../components/DragAndDropNotice.vue'
const isSharingEnabled = (getCapabilities() as { files_sharing?: boolean })?.files_sharing !== undefined
@@ -391,9 +396,27 @@ export default defineComponent({
filtersChanged() {
return this.filtersStore.filtersChanged
},
+
+ showCustomEmptyView() {
+ return !this.loading && this.isEmptyDir && this.currentView?.emptyView !== undefined
+ }
},
watch: {
+ /**
+ * Handle rendering the custom empty view
+ * @param show The current state if the custom empty view should be rendered
+ */
+ showCustomEmptyView(show: boolean) {
+ if (show) {
+ this.$nextTick(() => {
+ const el = this.$refs.customEmptyView as HTMLDivElement
+ // We can cast here because "showCustomEmptyView" assets that current view is set
+ this.currentView!.emptyView!(el)
+ })
+ }
+ },
+
currentView(newView, oldView) {
if (newView?.id === oldView?.id) {
return
@@ -679,6 +702,11 @@ export default defineComponent({
}
}
+ &__empty-view-wrapper {
+ display: flex;
+ height: 100%;
+ }
+
&__refresh-icon {
flex: 0 0 44px;
width: 44px;
diff --git a/apps/files/src/views/Navigation.vue b/apps/files/src/views/Navigation.vue
index f431ca62b5f..b0588863f5d 100644
--- a/apps/files/src/views/Navigation.vue
+++ b/apps/files/src/views/Navigation.vue
@@ -81,7 +81,7 @@ import { useNavigation } from '../composables/useNavigation'
import { useFilenameFilter } from '../composables/useFilenameFilter'
import { useFiltersStore } from '../store/filters.ts'
import { useViewConfigStore } from '../store/viewConfig.ts'
-import logger from '../logger.js'
+import logger from '../logger.ts'
export default defineComponent({
name: 'Navigation',
diff --git a/apps/files/src/views/Sidebar.vue b/apps/files/src/views/Sidebar.vue
index 8c4af5fd80f..efa090112de 100644
--- a/apps/files/src/views/Sidebar.vue
+++ b/apps/files/src/views/Sidebar.vue
@@ -108,7 +108,7 @@ import FileInfo from '../services/FileInfo.js'
import LegacyView from '../components/LegacyView.vue'
import SidebarTab from '../components/SidebarTab.vue'
import SystemTags from '../../../systemtags/src/components/SystemTags.vue'
-import logger from '../logger.js'
+import logger from '../logger.ts'
export default {
name: 'Sidebar',
diff --git a/apps/files/src/views/TemplatePicker.vue b/apps/files/src/views/TemplatePicker.vue
index 2a346ae139f..0d5b3cbaac9 100644
--- a/apps/files/src/views/TemplatePicker.vue
+++ b/apps/files/src/views/TemplatePicker.vue
@@ -60,7 +60,7 @@ import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
import NcModal from '@nextcloud/vue/dist/Components/NcModal.js'
import TemplatePreview from '../components/TemplatePreview.vue'
import TemplateFiller from '../components/TemplateFiller.vue'
-import logger from '../logger.js'
+import logger from '../logger.ts'
const border = 2
const margin = 8
diff --git a/apps/files_sharing/src/new/newFileRequest.ts b/apps/files_sharing/src/new/newFileRequest.ts
index c069bf5cdea..55b7f534610 100644
--- a/apps/files_sharing/src/new/newFileRequest.ts
+++ b/apps/files_sharing/src/new/newFileRequest.ts
@@ -4,50 +4,31 @@
*/
import type { Entry, Folder, Node } from '@nextcloud/files'
+import { defineAsyncComponent } from 'vue'
+import { spawnDialog } from '@nextcloud/dialogs'
import { translate as t } from '@nextcloud/l10n'
import FileUploadSvg from '@mdi/svg/svg/file-upload.svg?raw'
-import Vue, { defineAsyncComponent } from 'vue'
+
import Config from '../services/ConfigService'
+const sharingConfig = new Config()
const NewFileRequestDialogVue = defineAsyncComponent(() => import('../components/NewFileRequestDialog.vue'))
-const sharingConfig = new Config()
-
export const EntryId = 'file-request'
export const entry = {
id: EntryId,
displayName: t('files_sharing', 'Create file request'),
iconSvgInline: FileUploadSvg,
- order: 30,
+ order: 10,
enabled(): boolean {
// We will check for the folder permission on the dialog
return sharingConfig.isPublicShareAllowed
},
async handler(context: Folder, content: Node[]) {
- // Create document root
- const mountingPoint = document.createElement('div')
- mountingPoint.id = 'file-request-dialog'
- document.body.appendChild(mountingPoint)
-
- // Init vue app
- const NewFileRequestDialog = new Vue({
- name: 'NewFileRequestDialogRoot',
- render: (h) => h(
- NewFileRequestDialogVue,
- {
- props: {
- context,
- content,
- },
- on: {
- close: () => {
- NewFileRequestDialog.$destroy()
- },
- },
- },
- ),
- el: mountingPoint,
+ spawnDialog(NewFileRequestDialogVue, {
+ context,
+ content,
})
},
} as Entry
diff --git a/apps/files_trashbin/src/actions/restoreAction.ts b/apps/files_trashbin/src/actions/restoreAction.ts
index cda82fda466..31160183d83 100644
--- a/apps/files_trashbin/src/actions/restoreAction.ts
+++ b/apps/files_trashbin/src/actions/restoreAction.ts
@@ -3,6 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { emit } from '@nextcloud/event-bus'
+import { encodePath } from '@nextcloud/paths'
import { generateRemoteUrl } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth'
import { Permission, Node, View, registerFileAction, FileAction } from '@nextcloud/files'
@@ -10,8 +11,7 @@ import { translate as t } from '@nextcloud/l10n'
import axios from '@nextcloud/axios'
import History from '@mdi/svg/svg/history.svg?raw'
-import logger from '../../../files/src/logger.js'
-import { encodePath } from '@nextcloud/paths'
+import logger from '../../../files/src/logger.ts'
registerFileAction(new FileAction({
id: 'restore',
diff --git a/apps/systemtags/src/services/systemtags.ts b/apps/systemtags/src/services/systemtags.ts
index 8e318f13adc..f29bb83984b 100644
--- a/apps/systemtags/src/services/systemtags.ts
+++ b/apps/systemtags/src/services/systemtags.ts
@@ -6,12 +6,12 @@ import type { ContentsWithRoot } from '@nextcloud/files'
import type { FileStat, ResponseDataDetailed } from 'webdav'
import type { TagWithId } from '../types'
-import { Folder, Permission, getDavNameSpaces, getDavProperties, davGetClient, davResultToNode } from '@nextcloud/files'
-import { generateRemoteUrl } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth'
-
+import { Folder, Permission, getDavNameSpaces, getDavProperties, davGetClient, davResultToNode, davRemoteURL, davRootPath } from '@nextcloud/files'
import { fetchTags } from './api'
+const rootPath = '/systemtags'
+
const client = davGetClient()
const resultToNode = (node: FileStat) => davResultToNode(node)
@@ -20,17 +20,18 @@ const formatReportPayload = (tagId: number) => `<?xml version="1.0"?>
<d:prop>
${getDavProperties()}
</d:prop>
- <oc:filter-rules>
- <oc:systemtag>${tagId}</oc:systemtag>
- </oc:filter-rules>
+ <oc:filter-rules>
+ <oc:systemtag>${tagId}</oc:systemtag>
+ </oc:filter-rules>
</oc:filter-files>`
const tagToNode = function(tag: TagWithId): Folder {
return new Folder({
id: tag.id,
- source: generateRemoteUrl('dav/systemtags/' + tag.id),
- owner: getCurrentUser()?.uid as string,
- root: '/systemtags',
+ source: `${davRemoteURL}${rootPath}/${tag.id}`,
+ owner: String(getCurrentUser()?.uid ?? 'anonymous'),
+ root: rootPath,
+ displayname: tag.displayName,
permissions: Permission.READ,
attributes: {
...tag,
@@ -47,16 +48,16 @@ export const getContents = async (path = '/'): Promise<ContentsWithRoot> => {
return {
folder: new Folder({
id: 0,
- source: generateRemoteUrl('dav/systemtags'),
+ source: `${davRemoteURL}${rootPath}`,
owner: getCurrentUser()?.uid as string,
- root: '/systemtags',
+ root: rootPath,
permissions: Permission.NONE,
}),
contents: tagsCache.map(tagToNode),
}
}
- const tagId = parseInt(path.replace('/', ''), 10)
+ const tagId = parseInt(path.split('/', 2)[0])
const tag = tagsCache.find(tag => tag.id === tagId)
if (!tag) {
@@ -64,7 +65,7 @@ export const getContents = async (path = '/'): Promise<ContentsWithRoot> => {
}
const folder = tagToNode(tag)
- const contentsResponse = await client.getDirectoryContents('/', {
+ const contentsResponse = await client.getDirectoryContents(davRootPath, {
details: true,
// Only filter favorites if we're at the root
data: formatReportPayload(tagId),