commit 12418b12ae7d6e565f21359426570278cec3f0fa
parent 6d52b2946655a5506db31e28fe93f1c5a19c5c58
Author: breadcat <breadcat@users.noreply.github.com>
Date:   Mon, 22 Sep 2025 16:17:44 +0100
Raw URL copy/paste function
Diffstat:
2 files changed, 74 insertions(+), 8 deletions(-)
diff --git a/main.go b/main.go
@@ -45,6 +45,8 @@ type TagDisplay struct {
 type PageData struct {
 	Title string
 	Data  interface{}
+	IP    string
+	Port  string
 }
 
 func main() {
@@ -351,6 +353,22 @@ func uploadHandler(w http.ResponseWriter, r *http.Request) {
 	http.Redirect(w, r, fmt.Sprintf("/file/%d", id), http.StatusSeeOther)
 }
 
+// raw local IP for raw address
+func getLocalIP() (string, error) {
+    addrs, err := net.InterfaceAddrs()
+    if err != nil {
+        return "", err
+    }
+    for _, addr := range addrs {
+        if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
+            if ipnet.IP.To4() != nil {
+                return ipnet.IP.String(), nil
+            }
+        }
+    }
+    return "", fmt.Errorf("no connected network interface found")
+}
+
 // Router for file operations, tag deletion, rename, and delete
 func fileRouter(w http.ResponseWriter, r *http.Request) {
 	parts := strings.Split(r.URL.Path, "/")
@@ -552,13 +570,22 @@ func fileHandler(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	pageData := PageData{
-		Title: f.Filename,
-		Data: struct {
-			File       File
-			Categories []string
-		}{f, cats},
-	}
+    // IP and port for raw URL
+    ip, _ := getLocalIP()
+    port := strings.TrimPrefix(config.ServerPort, ":")
+    // Escape filename for copy/paste
+    escaped := url.PathEscape(f.Filename)
+
+    pageData := PageData{
+        Title: f.Filename,
+        Data: struct {
+            File       File
+            Categories []string
+            EscapedFilename string
+        }{f, cats, escaped},
+        IP:   ip,
+        Port: port,
+    }
 
 	tmpl.ExecuteTemplate(w, "file.html", pageData)
 }
diff --git a/templates/file.html b/templates/file.html
@@ -1,5 +1,4 @@
 {{template "_header" .}}
-File {{.Data.File.Filename}}
 <h2>File: {{.Data.File.Filename}}</h2>
 
 {{if hasAnySuffix .Data.File.Filename ".jpg" ".jpeg" ".png" ".gif" ".webp"}}
@@ -12,6 +11,46 @@ File {{.Data.File.Filename}}
   <a href="/uploads/{{.Data.File.Filename}}">Download file</a><br>
 {{end}}
 
+<h3>Raw URL</h3>
+<pre id="raw-url">http://{{.IP}}:{{.Port}}/uploads/{{.Data.EscapedFilename}}</pre>
+<button id="copy-btn" style="margin-top: 5px;">Copy to Clipboard</button>
+<span id="copy-status" style="margin-left: 10px;"></span>
+
+<script>
+document.getElementById("copy-btn").addEventListener("click", function() {
+    const text = document.getElementById("raw-url").textContent.trim();
+    const status = document.getElementById("copy-status");
+
+    // Fallback approach using a temporary textarea
+    const textarea = document.createElement("textarea");
+    textarea.value = text;
+    textarea.style.position = "fixed"; // prevent scrolling
+    textarea.style.left = "-9999px"; // off-screen
+    document.body.appendChild(textarea);
+    textarea.focus();
+    textarea.select();
+
+    let successful = false;
+    try {
+        successful = document.execCommand("copy");
+    } catch (err) {
+        successful = false;
+    }
+
+    document.body.removeChild(textarea);
+
+    if (successful) {
+        status.textContent = "✓ Copied!";
+        status.style.color = "green";
+    } else {
+        status.textContent = "✗ Copy failed";
+        status.style.color = "red";
+    }
+});
+</script>
+
+
+
 <h3>File Actions</h3>
 <form method="post" action="/file/{{.Data.File.ID}}/rename" style="display:inline-block; margin-right: 20px;">
   New filename: <input type="text" name="newfilename" value="{{.Data.File.Filename}}" required style="width:300px"><br>