package web import ( "net/url" "strings" "gitea.dooplex.hu/admin/felhom-controller/internal/infra" ) // FileBrowser Quantum deep links (R-75). // // The template is taken VERBATIM from the shipped frontend router (SPIKE P2, read out of the // bundle), not guessed: // // function Ms(n,t,…){ … let a=q2(t), s=`/files/${encodeURIComponent(n)}${a}` … } // q2 = … .map(r=>encodeURIComponent(r)).join("/") // per-SEGMENT encoding // function H2(n){ t=i.split("/")[2]; … } // source is the 3rd path segment // // So a link is constructible server-side from (sourceName, relPath) alone — no internal id, no // index, no client-side state. // // A cold link (no FileBrowser session) is NOT lost: the router guard redirects to // /login?redirect= and the login handler navigates back to it. The customer may still have // to sign in, which is why the UI copy must not promise one click. // fbSourceRoot is the FileBrowser origin for a customer domain. FileBrowser is published at // files. by the base-infra traefik labels. func fbSourceRoot(domain string) string { return "https://files." + domain } // fileBrowserLink builds a deep link into a named FileBrowser source at relPath. // // ENCODING TRAP, measured in SPIKE P2 — use url.PathEscape, NEVER url.QueryEscape: // // "Média & könyvtár" PathEscape=M%C3%A9dia%20&%20k%C3%B6nyvt%C3%A1r QueryEscape=M%C3%A9dia+%26+k%C3%B6nyvt%C3%A1r // "a+b" PathEscape=a+b QueryEscape=a%2Bb // // QueryEscape encodes a space as "+", which inside a PATH segment means a literal plus and breaks // the link. PathEscape leaves "&" unescaped, which is correct here: "&" is a legal path sub-delim, // and html/template escapes it to "&" in the href attribute, which the browser decodes back to // "&". The two escapings compose — so do NOT pre-escape for HTML here. // // Source names cannot contain "/" (they come from filepath.Base or the ASCII import constant), which // is what keeps the router's split("/")[2] round-trip intact. func fileBrowserLink(domain, sourceName, relPath string) string { var b strings.Builder b.WriteString(fbSourceRoot(domain)) b.WriteString("/files/") b.WriteString(url.PathEscape(sourceName)) for _, seg := range strings.Split(strings.Trim(relPath, "/"), "/") { if seg == "" { continue } b.WriteString("/") b.WriteString(url.PathEscape(seg)) } return b.String() } // importFolderLink is the deep link to a drop-zone app's folder inside the canonical import source. func importFolderLink(domain, appDir string) string { return fileBrowserLink(domain, infra.FileBrowserImportLabel, appDir) }