...
This commit is contained in:
		
							
								
								
									
										239
									
								
								static/js/webdav-client.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										239
									
								
								static/js/webdav-client.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,239 @@ | ||||
| /** | ||||
|  * WebDAV Client | ||||
|  * Handles all WebDAV protocol operations | ||||
|  */ | ||||
|  | ||||
| class WebDAVClient { | ||||
|     constructor(baseUrl) { | ||||
|         this.baseUrl = baseUrl; | ||||
|         this.currentCollection = null; | ||||
|     } | ||||
|      | ||||
|     setCollection(collection) { | ||||
|         this.currentCollection = collection; | ||||
|     } | ||||
|      | ||||
|     getFullUrl(path) { | ||||
|         if (!this.currentCollection) { | ||||
|             throw new Error('No collection selected'); | ||||
|         } | ||||
|         const cleanPath = path.startsWith('/') ? path.slice(1) : path; | ||||
|         return `${this.baseUrl}${this.currentCollection}/${cleanPath}`; | ||||
|     } | ||||
|      | ||||
|     async getCollections() { | ||||
|         const response = await fetch(this.baseUrl); | ||||
|         if (!response.ok) { | ||||
|             throw new Error('Failed to get collections'); | ||||
|         } | ||||
|         return await response.json(); | ||||
|     } | ||||
|      | ||||
|     async propfind(path = '', depth = '1') { | ||||
|         const url = this.getFullUrl(path); | ||||
|         const response = await fetch(url, { | ||||
|             method: 'PROPFIND', | ||||
|             headers: { | ||||
|                 'Depth': depth, | ||||
|                 'Content-Type': 'application/xml' | ||||
|             } | ||||
|         }); | ||||
|          | ||||
|         if (!response.ok) { | ||||
|             throw new Error(`PROPFIND failed: ${response.statusText}`); | ||||
|         } | ||||
|          | ||||
|         const xml = await response.text(); | ||||
|         return this.parseMultiStatus(xml); | ||||
|     } | ||||
|      | ||||
|     async get(path) { | ||||
|         const url = this.getFullUrl(path); | ||||
|         const response = await fetch(url); | ||||
|          | ||||
|         if (!response.ok) { | ||||
|             throw new Error(`GET failed: ${response.statusText}`); | ||||
|         } | ||||
|          | ||||
|         return await response.text(); | ||||
|     } | ||||
|      | ||||
|     async getBinary(path) { | ||||
|         const url = this.getFullUrl(path); | ||||
|         const response = await fetch(url); | ||||
|          | ||||
|         if (!response.ok) { | ||||
|             throw new Error(`GET failed: ${response.statusText}`); | ||||
|         } | ||||
|          | ||||
|         return await response.blob(); | ||||
|     } | ||||
|      | ||||
|     async put(path, content) { | ||||
|         const url = this.getFullUrl(path); | ||||
|         const response = await fetch(url, { | ||||
|             method: 'PUT', | ||||
|             headers: { | ||||
|                 'Content-Type': 'text/plain' | ||||
|             }, | ||||
|             body: content | ||||
|         }); | ||||
|          | ||||
|         if (!response.ok) { | ||||
|             throw new Error(`PUT failed: ${response.statusText}`); | ||||
|         } | ||||
|          | ||||
|         return true; | ||||
|     } | ||||
|      | ||||
|     async putBinary(path, content) { | ||||
|         const url = this.getFullUrl(path); | ||||
|         const response = await fetch(url, { | ||||
|             method: 'PUT', | ||||
|             body: content | ||||
|         }); | ||||
|          | ||||
|         if (!response.ok) { | ||||
|             throw new Error(`PUT failed: ${response.statusText}`); | ||||
|         } | ||||
|          | ||||
|         return true; | ||||
|     } | ||||
|      | ||||
|     async delete(path) { | ||||
|         const url = this.getFullUrl(path); | ||||
|         const response = await fetch(url, { | ||||
|             method: 'DELETE' | ||||
|         }); | ||||
|          | ||||
|         if (!response.ok) { | ||||
|             throw new Error(`DELETE failed: ${response.statusText}`); | ||||
|         } | ||||
|          | ||||
|         return true; | ||||
|     } | ||||
|      | ||||
|     async copy(sourcePath, destPath) { | ||||
|         const sourceUrl = this.getFullUrl(sourcePath); | ||||
|         const destUrl = this.getFullUrl(destPath); | ||||
|          | ||||
|         const response = await fetch(sourceUrl, { | ||||
|             method: 'COPY', | ||||
|             headers: { | ||||
|                 'Destination': destUrl | ||||
|             } | ||||
|         }); | ||||
|          | ||||
|         if (!response.ok) { | ||||
|             throw new Error(`COPY failed: ${response.statusText}`); | ||||
|         } | ||||
|          | ||||
|         return true; | ||||
|     } | ||||
|      | ||||
|     async move(sourcePath, destPath) { | ||||
|         const sourceUrl = this.getFullUrl(sourcePath); | ||||
|         const destUrl = this.getFullUrl(destPath); | ||||
|          | ||||
|         const response = await fetch(sourceUrl, { | ||||
|             method: 'MOVE', | ||||
|             headers: { | ||||
|                 'Destination': destUrl | ||||
|             } | ||||
|         }); | ||||
|          | ||||
|         if (!response.ok) { | ||||
|             throw new Error(`MOVE failed: ${response.statusText}`); | ||||
|         } | ||||
|          | ||||
|         return true; | ||||
|     } | ||||
|      | ||||
|     async mkcol(path) { | ||||
|         const url = this.getFullUrl(path); | ||||
|         const response = await fetch(url, { | ||||
|             method: 'MKCOL' | ||||
|         }); | ||||
|          | ||||
|         if (!response.ok && response.status !== 405) { // 405 means already exists | ||||
|             throw new Error(`MKCOL failed: ${response.statusText}`); | ||||
|         } | ||||
|          | ||||
|         return true; | ||||
|     } | ||||
|      | ||||
|     parseMultiStatus(xml) { | ||||
|         const parser = new DOMParser(); | ||||
|         const doc = parser.parseFromString(xml, 'text/xml'); | ||||
|         const responses = doc.getElementsByTagNameNS('DAV:', 'response'); | ||||
|          | ||||
|         const items = []; | ||||
|         for (let i = 0; i < responses.length; i++) { | ||||
|             const response = responses[i]; | ||||
|             const href = response.getElementsByTagNameNS('DAV:', 'href')[0].textContent; | ||||
|             const propstat = response.getElementsByTagNameNS('DAV:', 'propstat')[0]; | ||||
|             const prop = propstat.getElementsByTagNameNS('DAV:', 'prop')[0]; | ||||
|              | ||||
|             // Check if it's a collection (directory) | ||||
|             const resourcetype = prop.getElementsByTagNameNS('DAV:', 'resourcetype')[0]; | ||||
|             const isDirectory = resourcetype.getElementsByTagNameNS('DAV:', 'collection').length > 0; | ||||
|              | ||||
|             // Get size | ||||
|             const contentlengthEl = prop.getElementsByTagNameNS('DAV:', 'getcontentlength')[0]; | ||||
|             const size = contentlengthEl ? parseInt(contentlengthEl.textContent) : 0; | ||||
|              | ||||
|             // Extract path relative to collection | ||||
|             const pathParts = href.split(`/${this.currentCollection}/`); | ||||
|             const relativePath = pathParts.length > 1 ? pathParts[1] : ''; | ||||
|              | ||||
|             // Skip the collection root itself | ||||
|             if (!relativePath) continue; | ||||
|              | ||||
|             // Remove trailing slash from directories | ||||
|             const cleanPath = relativePath.endsWith('/') ? relativePath.slice(0, -1) : relativePath; | ||||
|              | ||||
|             items.push({ | ||||
|                 path: cleanPath, | ||||
|                 name: cleanPath.split('/').pop(), | ||||
|                 isDirectory, | ||||
|                 size | ||||
|             }); | ||||
|         } | ||||
|          | ||||
|         return items; | ||||
|     } | ||||
|      | ||||
|     buildTree(items) { | ||||
|         const root = []; | ||||
|         const map = {}; | ||||
|          | ||||
|         // Sort items by path depth and name | ||||
|         items.sort((a, b) => { | ||||
|             const depthA = a.path.split('/').length; | ||||
|             const depthB = b.path.split('/').length; | ||||
|             if (depthA !== depthB) return depthA - depthB; | ||||
|             return a.path.localeCompare(b.path); | ||||
|         }); | ||||
|          | ||||
|         items.forEach(item => { | ||||
|             const parts = item.path.split('/'); | ||||
|             const parentPath = parts.slice(0, -1).join('/'); | ||||
|              | ||||
|             const node = { | ||||
|                 ...item, | ||||
|                 children: [] | ||||
|             }; | ||||
|              | ||||
|             map[item.path] = node; | ||||
|              | ||||
|             if (parentPath && map[parentPath]) { | ||||
|                 map[parentPath].children.push(node); | ||||
|             } else { | ||||
|                 root.push(node); | ||||
|             } | ||||
|         }); | ||||
|          | ||||
|         return root; | ||||
|     } | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user