aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files
diff options
context:
space:
mode:
Diffstat (limited to 'apps/files')
-rw-r--r--apps/files/src/actions/sidebarAction.ts6
-rw-r--r--apps/files/src/components/FileEntry.vue22
-rw-r--r--apps/files/src/components/FilesListVirtual.vue15
-rw-r--r--apps/files/src/newMenu/newFolder.ts2
-rw-r--r--apps/files/src/views/FilesList.vue84
5 files changed, 105 insertions, 24 deletions
diff --git a/apps/files/src/actions/sidebarAction.ts b/apps/files/src/actions/sidebarAction.ts
index e9d18dbf719..aa265d6709a 100644
--- a/apps/files/src/actions/sidebarAction.ts
+++ b/apps/files/src/actions/sidebarAction.ts
@@ -19,7 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
-import { Permission, type Node, View, registerFileAction, FileAction } from '@nextcloud/files'
+import { Permission, type Node, View, registerFileAction, FileAction, FileType } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import InformationSvg from '@mdi/svg/svg/information-variant.svg?raw'
@@ -51,7 +51,7 @@ export const action = new FileAction({
return (nodes[0].root?.startsWith('/files/') && nodes[0].permissions !== Permission.NONE) ?? false
},
- async exec(node: Node, view: View) {
+ async exec(node: Node, view: View, dir: string) {
try {
// TODO: migrate Sidebar to use a Node instead
await window.OCA.Files.Sidebar.open(node.path)
@@ -60,7 +60,7 @@ export const action = new FileAction({
window.OCP.Files.Router.goToRoute(
null,
{ view: view.id, fileid: node.fileid },
- { dir: node.dirname },
+ { dir },
true,
)
diff --git a/apps/files/src/components/FileEntry.vue b/apps/files/src/components/FileEntry.vue
index fce8b7ed263..3d30ba1055c 100644
--- a/apps/files/src/components/FileEntry.vue
+++ b/apps/files/src/components/FileEntry.vue
@@ -166,12 +166,15 @@
</template>
<script lang='ts'>
+import type { PropType } from 'vue'
+import type { Node } from '@nextcloud/files'
+
import { CancelablePromise } from 'cancelable-promise'
import { debounce } from 'debounce'
import { emit } from '@nextcloud/event-bus'
import { extname } from 'path'
import { generateUrl } from '@nextcloud/router'
-import { getFileActions, DefaultType, FileType, formatFileSize, Permission, NodeStatus } from '@nextcloud/files'
+import { getFileActions, DefaultType, FileType, formatFileSize, Permission, Folder, File } from '@nextcloud/files'
import { showError, showSuccess } from '@nextcloud/dialogs'
import { translate } from '@nextcloud/l10n'
import { vOnClickOutside } from '@vueuse/components'
@@ -186,7 +189,7 @@ import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
import Vue from 'vue'
-import { ACTION_DETAILS } from '../actions/sidebarAction.ts'
+import { action as sidebarAction } from '../actions/sidebarAction.ts'
import { hashCode } from '../utils/hashUtils.ts'
import { isCachedPreview } from '../services/PreviewService.ts'
import { useActionsMenuStore } from '../store/actionsmenu.ts'
@@ -235,7 +238,7 @@ export default Vue.extend({
default: false,
},
source: {
- type: Object,
+ type: [Folder, File, Node] as PropType<Node>,
required: true,
},
index: {
@@ -243,7 +246,7 @@ export default Vue.extend({
required: true,
},
nodes: {
- type: Array,
+ type: Array as PropType<Node[]>,
required: true,
},
filesListWidth: {
@@ -295,7 +298,7 @@ export default Vue.extend({
currentDir() {
// Remove any trailing slash but leave root slash
- return (this.$route?.query?.dir || '/').replace(/^(.+)\/$/, '$1')
+ return (this.$route?.query?.dir?.toString() || '/').replace(/^(.+)\/$/, '$1')
},
currentFileId() {
return this.$route.params.fileid || this.$route.query.fileid || null
@@ -660,11 +663,10 @@ export default Vue.extend({
},
openDetailsIfAvailable(event) {
- const detailsAction = this.enabledActions.find(action => action.id === ACTION_DETAILS)
- if (detailsAction) {
- event.preventDefault()
- event.stopPropagation()
- detailsAction.exec(this.source, this.currentView)
+ event.preventDefault()
+ event.stopPropagation()
+ if (sidebarAction?.enabled?.([this.source], this.currentView)) {
+ sidebarAction.exec(this.source, this.currentView, this.currentDir)
}
},
diff --git a/apps/files/src/components/FilesListVirtual.vue b/apps/files/src/components/FilesListVirtual.vue
index 8c7242561e9..9938969437d 100644
--- a/apps/files/src/components/FilesListVirtual.vue
+++ b/apps/files/src/components/FilesListVirtual.vue
@@ -67,11 +67,13 @@
</template>
<script lang="ts">
+import type { PropType } from 'vue'
+import type { Node } from '@nextcloud/files'
+
import { translate, translatePlural } from '@nextcloud/l10n'
-import { getFileListHeaders, type Node } from '@nextcloud/files'
+import { getFileListHeaders, Folder, View } from '@nextcloud/files'
import { showError } from '@nextcloud/dialogs'
import Vue from 'vue'
-import VirtualList from './VirtualList.vue'
import { action as sidebarAction } from '../actions/sidebarAction.ts'
import FileEntry from './FileEntry.vue'
@@ -80,6 +82,7 @@ import FilesListTableFooter from './FilesListTableFooter.vue'
import FilesListTableHeader from './FilesListTableHeader.vue'
import filesListWidthMixin from '../mixins/filesListWidth.ts'
import logger from '../logger.js'
+import VirtualList from './VirtualList.vue'
export default Vue.extend({
name: 'FilesListVirtual',
@@ -97,15 +100,15 @@ export default Vue.extend({
props: {
currentView: {
- type: Object,
+ type: View,
required: true,
},
currentFolder: {
- type: Object,
+ type: Folder,
required: true,
},
nodes: {
- type: Array,
+ type: Array as PropType<Node[]>,
required: true,
},
},
@@ -179,7 +182,7 @@ export default Vue.extend({
const node = this.nodes.find(n => n.fileid === this.fileId) as Node
if (node && sidebarAction?.enabled?.([node], this.currentView)) {
logger.debug('Opening sidebar on file ' + node.path, { node })
- sidebarAction.exec(node, this.currentView, this.currentFolder)
+ sidebarAction.exec(node, this.currentView, this.currentFolder.path)
}
}
},
diff --git a/apps/files/src/newMenu/newFolder.ts b/apps/files/src/newMenu/newFolder.ts
index 399e6c1649a..1bf178de183 100644
--- a/apps/files/src/newMenu/newFolder.ts
+++ b/apps/files/src/newMenu/newFolder.ts
@@ -69,7 +69,7 @@ const entry = {
iconSvgInline: FolderPlusSvg,
async handler(context: Folder, content: Node[]) {
const contentNames = content.map((node: Node) => node.basename)
- const name = getUniqueName(t('files', 'New Folder'), contentNames)
+ const name = getUniqueName(t('files', 'New folder'), contentNames)
const { fileid, source } = await createNewFolder(context.source, name)
// Create the folder in the store
diff --git a/apps/files/src/views/FilesList.vue b/apps/files/src/views/FilesList.vue
index fbd483fd23f..0b858a15e4d 100644
--- a/apps/files/src/views/FilesList.vue
+++ b/apps/files/src/views/FilesList.vue
@@ -25,8 +25,20 @@
<!-- Current folder breadcrumbs -->
<BreadCrumbs :path="dir" @reload="fetchContent">
<template #actions>
+ <NcButton v-if="canShare"
+ :aria-label="shareButtonLabel"
+ :class="{ 'files-list__header-share-button--shared': shareButtonType }"
+ :title="shareButtonLabel"
+ class="files-list__header-share-button"
+ type="tertiary"
+ @click="openSharingSidebar">
+ <template #icon>
+ <LinkIcon v-if="shareButtonType === Type.SHARE_TYPE_LINK" />
+ <ShareVariantIcon v-else :size="20" />
+ </template>
+ </NcButton>
<!-- Uploader -->
- <UploadPicker v-if="currentFolder"
+ <UploadPicker v-if="currentFolder && canUpload"
:content="dirContents"
:destination="currentFolder"
:multiple="true"
@@ -77,18 +89,24 @@ import type { Upload } from '@nextcloud/upload'
import type { UserConfig } from '../types.ts'
import type { View, ContentsWithRoot } from '@nextcloud/files'
-import { Folder, Node } from '@nextcloud/files'
+import { Folder, Node, Permission } from '@nextcloud/files'
+import { getCapabilities } from '@nextcloud/capabilities'
import { join, dirname } from 'path'
import { orderBy } from 'natural-orderby'
import { translate } from '@nextcloud/l10n'
import { UploadPicker } from '@nextcloud/upload'
+import { Type } from '@nextcloud/sharing'
+import Vue from 'vue'
+
import NcAppContent from '@nextcloud/vue/dist/Components/NcAppContent.js'
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
-import Vue from 'vue'
+import LinkIcon from 'vue-material-design-icons/Link.vue'
+import ShareVariantIcon from 'vue-material-design-icons/ShareVariant.vue'
+import { action as sidebarAction } from '../actions/sidebarAction.ts'
import { useFilesStore } from '../store/files.ts'
import { usePathsStore } from '../store/paths.ts'
import { useSelectionStore } from '../store/selection.ts'
@@ -100,17 +118,21 @@ import FilesListVirtual from '../components/FilesListVirtual.vue'
import filesSortingMixin from '../mixins/filesSorting.ts'
import logger from '../logger.js'
+const isSharingEnabled = getCapabilities()?.files_sharing !== undefined
+
export default Vue.extend({
name: 'FilesList',
components: {
BreadCrumbs,
FilesListVirtual,
+ LinkIcon,
NcAppContent,
NcButton,
NcEmptyContent,
NcIconSvgWrapper,
NcLoadingIcon,
+ ShareVariantIcon,
UploadPicker,
},
@@ -139,6 +161,7 @@ export default Vue.extend({
return {
loading: true,
promise: null,
+ Type,
}
},
@@ -157,7 +180,7 @@ export default Vue.extend({
*/
dir(): string {
// Remove any trailing slash but leave root slash
- return (this.$route?.query?.dir || '/').replace(/^(.+)\/$/, '$1')
+ return (this.$route?.query?.dir?.toString() || '/').replace(/^(.+)\/$/, '$1')
},
/**
@@ -242,6 +265,43 @@ export default Vue.extend({
const dir = this.dir.split('/').slice(0, -1).join('/') || '/'
return { ...this.$route, query: { dir } }
},
+
+ shareAttributes(): number[]|undefined {
+ if (!this.currentFolder?.attributes?.['share-types']) {
+ return undefined
+ }
+ return Object.values(this.currentFolder?.attributes?.['share-types'] || {}).flat() as number[]
+ },
+ shareButtonLabel() {
+ if (!this.shareAttributes) {
+ return this.t('files', 'Share')
+ }
+
+ if (this.shareButtonType === Type.SHARE_TYPE_LINK) {
+ return this.t('files', 'Shared by link')
+ }
+ return this.t('files', 'Shared')
+ },
+ shareButtonType(): Type|null {
+ if (!this.shareAttributes) {
+ return null
+ }
+
+ // If all types are links, show the link icon
+ if (this.shareAttributes.some(type => type === Type.SHARE_TYPE_LINK)) {
+ return Type.SHARE_TYPE_LINK
+ }
+
+ return Type.SHARE_TYPE_USER
+ },
+
+ canUpload() {
+ return this.currentFolder && (this.currentFolder.permissions & Permission.CREATE) !== 0
+ },
+ canShare() {
+ return isSharingEnabled
+ && this.currentFolder && (this.currentFolder.permissions & Permission.SHARE) !== 0
+ },
},
watch: {
@@ -348,6 +408,13 @@ export default Vue.extend({
}
},
+ openSharingSidebar() {
+ if (window?.OCA?.Files?.Sidebar?.setActiveTab) {
+ window.OCA.Files.Sidebar.setActiveTab('sharing')
+ }
+ sidebarAction.exec(this.currentFolder, this.currentView, this.currentFolder.path)
+ },
+
t: translate,
},
})
@@ -378,12 +445,21 @@ $navigationToggleSize: 50px;
// Only the breadcrumbs shrinks
flex: 0 0;
}
+
+ &-share-button {
+ opacity: .3;
+ &--shared {
+ opacity: 1;
+ }
+ }
}
+
&__refresh-icon {
flex: 0 0 44px;
width: 44px;
height: 44px;
}
+
&__loading-icon {
margin: auto;
}