diff --git a/src/snek/app.py b/src/snek/app.py index f730849..9822d2d 100644 --- a/src/snek/app.py +++ b/src/snek/app.py @@ -44,6 +44,7 @@ from snek.view.avatar import AvatarView from snek.view.channel import ChannelAttachmentView,ChannelAttachmentUploadView, ChannelView from snek.view.docs import DocsHTMLView, DocsMDView from snek.view.drive import DriveApiView, DriveView +from snek.view.channel import ChannelDriveApiView from snek.view.index import IndexView from snek.view.login import LoginView from snek.view.logout import LogoutView @@ -286,6 +287,9 @@ class Application(BaseApplication): self.router.add_view( "/channel/{channel_uid}/attachment.bin", ChannelAttachmentView ) + self.router.add_view( + "/channel/{channel_uid}/drive.json", ChannelDriveApiView + ) self.router.add_view( "/channel/{channel_uid}/attachment.sock", ChannelAttachmentUploadView ) diff --git a/src/snek/static/file-manager.js b/src/snek/static/file-manager.js index f5bba7d..8cd768c 100644 --- a/src/snek/static/file-manager.js +++ b/src/snek/static/file-manager.js @@ -1,4 +1,6 @@ /* A  custom element that talks to /api/files */ +import { NjetComponent } from "/njet.js"; + class FileBrowser extends HTMLElement { constructor() { super(); @@ -6,9 +8,11 @@ class FileBrowser extends HTMLElement { this.path = ""; // current virtual path ("" = ROOT) this.offset = 0; // pagination offset this.limit = 40; // items per request + this.url = '/drive.json' } connectedCallback() { + this.url = this.getAttribute("url") || this.url; this.path = this.getAttribute("path") || ""; this.renderShell(); this.load(); @@ -58,7 +62,7 @@ class FileBrowser extends HTMLElement { // ---------- Networking ---------------------------------------------- async load() { const r = await fetch( - `/drive.json?path=${encodeURIComponent(this.path)}&offset=${this.offset}&limit=${this.limit}`, + this.url + `?path=${encodeURIComponent(this.path)}&offset=${this.offset}&limit=${this.limit}` ); if (!r.ok) { console.error(await r.text()); diff --git a/src/snek/static/njet.js b/src/snek/static/njet.js index 3739f7f..65aee05 100644 --- a/src/snek/static/njet.js +++ b/src/snek/static/njet.js @@ -134,11 +134,26 @@ class Njet extends HTMLElement { get rest() { return Njet._root._rest } - + attach(element) { + this._attachedTo = element + this._attachedTo.addEventListener("resize", () => { + this.updatePosition() + }) + } + updatePosition(){ + if(this._attachedTo) + { + this.style.width = `${this._attachedTo.offsetWidth}` + this.style.height = `${this._attachedTo.offsetHeight}` + this.style.left = `${this._attachedTo.offsetLeft}` + this.style.top = `${this._attachedTo.offsetTop}` + this.style.position = 'fixed' + } + } _subscriptions = {} _elements = [] _rest = null - + _attachedTo = null match(args) { return Object.entries(args).every(([key, value]) => this[key] === value); } @@ -312,15 +327,6 @@ class NjetDialog extends Component { this.innerHTML = ''; const { title, content, primaryButton, secondaryButton } = this.config; this.classList.add('njet-dialog'); - this.style.position = 'fixed'; - this.style.top = '50%'; - this.style.left = '50%'; - this.style.transform = 'translate(-50%, -50%)'; - this.style.padding = '20px'; - this.style.border = '1px solid #444'; - this.style.backgroundColor = '#fff'; - this.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)'; - this.style.minWidth = '300px'; if (title) { const header = document.createElement('h2'); header.textContent = title; @@ -357,22 +363,14 @@ class NjetWindow extends Component { render() { this.innerHTML = ''; const { title, content, primaryButton, secondaryButton } = this.config; - this.classList.add('njet-dialog'); - this.style.position = 'fixed'; - this.style.top = '50%'; - this.style.left = '50%'; - this.style.transform = 'translate(-50%, -50%)'; - this.style.padding = '20px'; - this.style.border = '1px solid #444'; - this.style.backgroundColor = '#fff'; - this.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)'; - this.style.minWidth = '300px'; + this.classList.add('njet-window'); + if (title) { const header = document.createElement('h2'); header.textContent = title; this.appendChild(header); } - + this.config.items.forEach(item => this.appendChild(item)); } diff --git a/src/snek/templates/channel.html b/src/snek/templates/channel.html index bd19376..af9733b 100644 --- a/src/snek/templates/channel.html +++ b/src/snek/templates/channel.html @@ -2,7 +2,10 @@ diff --git a/src/snek/view/channel.py b/src/snek/view/channel.py index 178800d..0ed975d 100644 --- a/src/snek/view/channel.py +++ b/src/snek/view/channel.py @@ -13,6 +13,17 @@ from snek.system.view import BaseView register_heif_opener() +from snek.view.drive import DriveApiView + +class ChannelDriveApiView(DriveApiView): + async def get_target(self): + target = await self.services.channel.get_home_folder(self.request.match_info.get("channel_uid")) + target.mkdir(parents=True, exist_ok=True) + return target + + async def get_download_url(self, rel): + return f"/channel/{self.request.match_info.get('channel_uid')}/drive/{urllib.parse.quote(rel)}" + class ChannelAttachmentView(BaseView): async def get(self): relative_path = self.request.match_info.get("relative_url") diff --git a/src/snek/view/drive.py b/src/snek/view/drive.py index e972539..2798657 100644 --- a/src/snek/view/drive.py +++ b/src/snek/view/drive.py @@ -28,8 +28,19 @@ class DriveView(BaseView): class DriveApiView(BaseView): - async def get(self): + + login_required = True + + async def get_target(self): target = await self.services.user.get_home_folder(self.session.get("uid")) + return target + + async def get_download_url(self, rel): + return f"/drive/{urllib.parse.quote(rel)}" + + async def get(self): + target = await self.get_target() + original_target = target rel = self.request.query.get("path", "") offset = int(self.request.query.get("offset", 0)) limit = int(self.request.query.get("limit", 20)) @@ -40,6 +51,9 @@ class DriveApiView(BaseView): if not target.exists(): return web.json_response({"error": "Not found"}, status=404) + if not target.relative_to(original_target): + return web.json_response({"error": "Not found"}, status=404) + if target.is_dir(): entries = [] for p in sorted( @@ -79,7 +93,7 @@ class DriveApiView(BaseView): } ) - url = self.request.url.with_path(f"/drive/{urllib.parse.quote(rel)}") + url = self.request.url.with_path(await self.get_download_url(rel)) return web.json_response( { "name": target.name,