diff --git a/lib/components/FilePicker/FilePicker.vue b/lib/components/FilePicker/FilePicker.vue
index eb5d6ebd..1dd27f6e 100644
--- a/lib/components/FilePicker/FilePicker.vue
+++ b/lib/components/FilePicker/FilePicker.vue
@@ -9,7 +9,7 @@
+ @create-node="onCreateFolder" />
{{ viewHeadline }}
@@ -53,13 +53,12 @@ import FileList from './FileList.vue'
import FilePickerBreadcrumbs from './FilePickerBreadcrumbs.vue'
import FilePickerNavigation from './FilePickerNavigation.vue'
-import { davRootPath } from '@nextcloud/files'
import { NcEmptyContent } from '@nextcloud/vue'
-import { join } from 'path'
import { computed, onMounted, ref, toRef } from 'vue'
import { showError } from '../../toast'
import { useDAVFiles } from '../../usables/dav'
import { useMimeFilter } from '../../usables/mime'
+import { useIsPublic } from '../../usables/isPublic'
import { t } from '../../utils/l10n'
const props = withDefaults(defineProps<{
@@ -118,6 +117,11 @@ const emit = defineEmits<{
(e: 'close', v?: Node[]): void
}>()
+/**
+ * Whether we are on a public endpoint (e.g. public share)
+ */
+const { isPublic } = useIsPublic()
+
/**
* Props to be passed to the underlying Dialog component
*/
@@ -203,7 +207,7 @@ const filterString = ref('')
const { isSupportedMimeType } = useMimeFilter(toRef(props, 'mimetypeFilter')) // vue 3.3 will allow cleaner syntax of toRef(() => props.mimetypeFilter)
-const { files, isLoading, loadFiles, getFile, client } = useDAVFiles(currentView, currentPath)
+const { files, isLoading, loadFiles, getFile, createDirectory } = useDAVFiles(currentView, currentPath, isPublic)
onMounted(() => loadFiles())
@@ -243,13 +247,14 @@ const noFilesDescription = computed(() => {
* Handle creating new folder (breadcrumb menu)
* @param name The new folder name
*/
-const onCreateFolder = (name: string) => {
- client
- .createDirectory(join(davRootPath, currentPath.value, name))
- // reload file list
- .then(() => loadFiles())
+const onCreateFolder = async (name: string) => {
+ try {
+ await createDirectory(name)
+ } catch (error) {
+ console.warn('Could not create new folder', { name, error })
// show error to user
- .catch((e) => showError(t('Could not create the new folder')))
+ showError(t('Could not create the new folder'))
+ }
}
diff --git a/lib/components/FilePicker/FilePickerNavigation.vue b/lib/components/FilePicker/FilePickerNavigation.vue
index 6e237c30..7b61a8d2 100644
--- a/lib/components/FilePicker/FilePickerNavigation.vue
+++ b/lib/components/FilePicker/FilePickerNavigation.vue
@@ -12,31 +12,33 @@
-
-
- -
-
-
-
-
- {{ view.label }}
-
-
-
- emit('update:currentView', v.id)" />
+
+
+
+ -
+
+
+
+
+ {{ view.label }}
+
+
+
+ emit('update:currentView', v.id)" />
+
@@ -48,9 +50,12 @@ import IconMagnify from 'vue-material-design-icons/Magnify.vue'
import IconStar from 'vue-material-design-icons/Star.vue'
import { NcButton, NcSelect, NcTextField } from '@nextcloud/vue'
-import { t } from '../../utils/l10n'
import { computed } from 'vue'
import { Fragment } from 'vue-frag'
+import { t } from '../../utils/l10n'
+import { useIsPublic } from '../../usables/isPublic'
+
+const { isPublic } = useIsPublic()
const allViews = [{
id: 'files',
diff --git a/lib/usables/dav.ts b/lib/usables/dav.ts
index 6fb70459..ff220875 100644
--- a/lib/usables/dav.ts
+++ b/lib/usables/dav.ts
@@ -19,25 +19,76 @@
* along with this program. If not, see .
*
*/
-import type { Node } from '@nextcloud/files'
+import type { Folder, Node } from '@nextcloud/files'
import type { ComputedRef, Ref } from 'vue'
-import type { FileStat, ResponseDataDetailed, WebDAVClient } from 'webdav'
+import type { FileStat, ResponseDataDetailed, SearchResult } from 'webdav'
-import { davGetClient, davGetDefaultPropfind, davGetFavoritesReport, davGetRecentSearch, davResultToNode, davRootPath } from '@nextcloud/files'
+import { davGetClient, davGetDefaultPropfind, davGetRecentSearch, davRemoteURL, davResultToNode, davRootPath, getFavoriteNodes } from '@nextcloud/files'
import { generateRemoteUrl } from '@nextcloud/router'
-import { ref, watch } from 'vue'
+import { dirname, join } from 'path'
+import { computed, ref, watch } from 'vue'
/**
* Handle file loading using WebDAV
*
* @param currentView Reference to the current files view
* @param currentPath Reference to the current files path
+ * @param isPublicEndpoint Whether the filepicker is used on a public share
*/
-export const useDAVFiles = function(currentView: Ref<'files'|'recent'|'favorites'> | ComputedRef<'files'|'recent'|'favorites'>, currentPath: Ref | ComputedRef): { isLoading: Ref, client: WebDAVClient, files: Ref, loadFiles: () => void, getFile: (path: string) => Promise } {
+export const useDAVFiles = function(
+ currentView: Ref<'files'|'recent'|'favorites'> | ComputedRef<'files'|'recent'|'favorites'>,
+ currentPath: Ref | ComputedRef,
+ isPublicEndpoint: Ref | ComputedRef,
+): { isLoading: Ref, createDirectory: (name: string) => Promise, files: Ref, loadFiles: () => Promise, getFile: (path: string) => Promise } {
+
+ const defaultRootPath = computed(() => isPublicEndpoint.value ? '/' : davRootPath)
+
+ const defaultRemoteUrl = computed(() => {
+ if (isPublicEndpoint.value) {
+ return generateRemoteUrl('webdav').replace('/remote.php', '/public.php')
+ }
+ return davRemoteURL
+ })
+
/**
* The WebDAV client
*/
- const client = davGetClient(generateRemoteUrl('dav'))
+ const client = computed(() => {
+ if (isPublicEndpoint.value) {
+ const token = (document.getElementById('sharingToken')! as HTMLInputElement).value
+ const autorization = btoa(`${token}:null`)
+
+ const client = davGetClient(defaultRemoteUrl.value)
+ client.setHeaders({ Authorization: `Basic ${autorization}` })
+ return client
+ }
+
+ return davGetClient()
+ })
+
+ const resultToNode = (result: FileStat) => {
+ const node = davResultToNode(result, defaultRootPath.value, defaultRemoteUrl.value)
+ // Fixed for @nextcloud/files 3.1.0 but not supported on Nextcloud 27 so patching it
+ if (isPublicEndpoint.value) {
+ return new Proxy(node, {
+ get(node, prop) {
+ if (prop === 'dirname' || prop === 'path') {
+ const source = node.source
+ let path = source.slice(defaultRemoteUrl.value.length)
+ if (path[0] !== '/') {
+ path = `/${path}`
+ }
+ if (prop === 'dirname') {
+ return dirname(path)
+ }
+ return path
+ }
+ return (node as never)[prop]
+ },
+ })
+ }
+ return node
+ }
/**
* All queried files
@@ -49,15 +100,32 @@ export const useDAVFiles = function(currentView: Ref<'files'|'recent'|'favorites
*/
const isLoading = ref(true)
+ /**
+ * Create a new directory in the current path
+ * @param name Name of the new directory
+ * @return {Promise} The created directory
+ */
+ async function createDirectory(name: string): Promise {
+ const path = join(currentPath.value, name)
+
+ await client.value.createDirectory(join(defaultRootPath.value, path))
+ const directory = await getFile(path) as Folder
+ files.value.push(directory)
+ return directory
+ }
+
/**
* Get information for one file
* @param path The path of the file or folder
+ * @param rootPath The dav root path to use (or the default is nothing set)
*/
- async function getFile(path: string) {
- const result = await client.stat(`${davRootPath}${path}`, {
+ async function getFile(path: string, rootPath: string|undefined = undefined) {
+ rootPath = rootPath ?? defaultRootPath.value
+
+ const { data } = await client.value.stat(`${rootPath}${path}`, {
details: true,
}) as ResponseDataDetailed
- return davResultToNode(result.data)
+ return resultToNode(data)
}
/**
@@ -67,34 +135,26 @@ export const useDAVFiles = function(currentView: Ref<'files'|'recent'|'favorites
isLoading.value = true
if (currentView.value === 'favorites') {
- files.value = await client.getDirectoryContents(`${davRootPath}${currentPath.value}`, {
- details: true,
- data: davGetFavoritesReport(),
- headers: {
- method: 'REPORT',
- },
- includeSelf: false,
- }).then((result) => (result as ResponseDataDetailed).data.map((data) => davResultToNode(data)))
+ files.value = await getFavoriteNodes(client.value, currentPath.value, defaultRootPath.value)
} else if (currentView.value === 'recent') {
// unix timestamp in seconds, two weeks ago
const lastTwoWeek = Math.round(Date.now() / 1000) - (60 * 60 * 24 * 14)
- const results = await client.getDirectoryContents(currentPath.value, {
+ const { data } = await client.value.search('/', {
details: true,
data: davGetRecentSearch(lastTwoWeek),
- headers: {
- method: 'SEARCH',
- 'Content-Type': 'application/xml; charset=utf-8',
- },
- deep: true,
- }) as ResponseDataDetailed
-
- files.value = results.data.map((r) => davResultToNode(r))
+ }) as ResponseDataDetailed
+ files.value = data.results.map(resultToNode)
} else {
- const results = await client.getDirectoryContents(`${davRootPath}${currentPath.value}`, {
+ const results = await client.value.getDirectoryContents(`${defaultRootPath.value}${currentPath.value}`, {
details: true,
data: davGetDefaultPropfind(),
}) as ResponseDataDetailed
- files.value = results.data.map((r) => davResultToNode(r))
+ files.value = results.data.map(resultToNode)
+
+ // Hack for the public endpoint which always returns folder itself
+ if (isPublicEndpoint.value) {
+ files.value = files.value.filter((file) => file.path !== currentPath.value)
+ }
}
isLoading.value = false
@@ -110,6 +170,6 @@ export const useDAVFiles = function(currentView: Ref<'files'|'recent'|'favorites
files,
loadFiles: () => loadDAVFiles(),
getFile,
- client,
+ createDirectory,
}
}
diff --git a/lib/usables/isPublic.ts b/lib/usables/isPublic.ts
new file mode 100644
index 00000000..d41951a1
--- /dev/null
+++ b/lib/usables/isPublic.ts
@@ -0,0 +1,15 @@
+import { onBeforeMount, ref } from 'vue'
+
+/**
+ * Check whether the component is mounted in a public share
+ */
+export const useIsPublic = () => {
+ const checkIsPublic = () => (document.getElementById('isPublic') as HTMLInputElement|null)?.value === '1'
+
+ const isPublic = ref(true)
+ onBeforeMount(() => { isPublic.value = checkIsPublic() })
+
+ return {
+ isPublic,
+ }
+}