taggart

Simple golang tagging filesystem webapp
Log | Files | Refs

commit b1d25a7c9c2adfe70bf5cc5c7afbc4b0e8b65a7b
parent 3b8a8f50b49f54a78da911d85128f8fd01cf8a40
Author: breadcat <breadcat@users.noreply.github.com>
Date:   Mon, 29 Sep 2025 16:14:20 +0100

Support m4v files and shuffle around /file.html layout

Diffstat:
Mbackfill.sh | 2+-
Mmain.go | 2+-
Astatic/rename-file.js | 18++++++++++++++++++
Mstatic/style.css | 3+--
Mtemplates/_gallery.html | 2+-
Mtemplates/file.html | 187+++++++++++++++++++++++++++++++++++++++++++------------------------------------
6 files changed, 125 insertions(+), 89 deletions(-)

diff --git a/backfill.sh b/backfill.sh @@ -42,7 +42,7 @@ if [[ -n "$target_file" ]]; then generate_thumbnail "$file_path" "$thumb" "$override_time" else - find "$upload_dir" -maxdepth 1 -type f \( -iname "*.mp4" -o -iname "*.webm" -o -iname "*.mov" -o -iname "*.avi" -o -iname "*.mkv" \) | while read -r file; do + find "$upload_dir" -maxdepth 1 -type f \( -iname "*.mp4" -o -iname "*.webm" -o -iname "*.mov" -o -iname "*.avi" -o -iname "*.mkv" -o -iname "*.m4v" \) | while read -r file; do filename=$(basename "$file") thumb="$thumb_dir/${filename}.jpg" diff --git a/main.go b/main.go @@ -1277,7 +1277,7 @@ func processVideoFile(tempPath, finalPath string) (string, string, error) { } ext := strings.ToLower(filepath.Ext(finalPath)) - if ext == ".mp4" || ext == ".mov" || ext == ".avi" || ext == ".mkv" || ext == ".webm" { + if ext == ".mp4" || ext == ".mov" || ext == ".avi" || ext == ".mkv" || ext == ".webm" || ext == ".m4v" { if err := generateThumbnail(finalPath, config.UploadDir, filepath.Base(finalPath)); err != nil { log.Printf("Warning: could not generate thumbnail: %v", err) } diff --git a/static/rename-file.js b/static/rename-file.js @@ -0,0 +1,18 @@ +document.addEventListener("DOMContentLoaded", () => { + document.querySelectorAll(".rename-button").forEach(button => { + button.addEventListener("click", () => { + const fileID = button.dataset.fileId; + const currentName = button.dataset.currentName; + + const newName = prompt("Enter new filename (include extension):", currentName); + + if (!newName) { + return; + } + + const form = document.getElementById(`renameForm-${fileID}`); + form.querySelector('input[name="newfilename"]').value = newName; + form.submit(); + }); + }); +}); diff --git a/static/style.css b/static/style.css @@ -20,6 +20,7 @@ nav ul{flex:1 1 auto;list-style:none} nav ul.sub-menu li a:first-letter{text-transform:capitalize} /* search bar */ +div#search-container form {border-left:1px solid gray} div#search-container form input{border:1px solid gray;margin: 8px;padding:8px} div#search-container form input:focus{border:none;background-color:#3a3a3a} @@ -38,4 +39,3 @@ button#edit-description-btn {background: #007cba; color: white; padding: 6px 12p textarea#description-textarea {width: 100%; max-width: 600px; padding: 8px; border: 1px solid #ccc; border-radius: 3px; font-family: inherit; resize: vertical;} input.description-save {background: #28a745; color: white; padding: 8px 16px; border: none; border-radius: 3px; cursor: pointer;} button.description-cancel {background: #6c757d; color: white; padding: 8px 16px; border: none; border-radius: 3px; cursor: pointer;} -button.delete-tag {border:none; background:none; color:red; cursor:pointer; padding:0; margin-left:2px;} -\ No newline at end of file diff --git a/templates/_gallery.html b/templates/_gallery.html @@ -2,7 +2,7 @@ <div class="gallery-item"> <a href="/file/{{.ID}}" title="{{.Filename}}"> {{if hasAnySuffix .Filename ".jpg" ".jpeg" ".png" ".gif" ".webp"}}<img src="/uploads/{{.EscapedFilename}}" style="max-width:150px"> - {{else if hasAnySuffix .Filename ".mp4" ".webm" ".mov"}}<div class="gallery-video"><img src="/uploads/thumbnails/{{.EscapedFilename}}.jpg" style="width: 100%; display: block;" /><div class="play-button"></div></div> + {{else if hasAnySuffix .Filename ".mp4" ".webm" ".mov" ".m4v"}}<div class="gallery-video"><img src="/uploads/thumbnails/{{.EscapedFilename}}.jpg" style="width: 100%; display: block;" /><div class="play-button"></div></div> {{end}} <br>{{.Filename}}</a> </div> diff --git a/templates/file.html b/templates/file.html @@ -1,101 +1,120 @@ {{template "_header" .}} <h2>File: {{.Data.File.Filename}}</h2> -{{if hasAnySuffix .Data.File.Filename ".jpg" ".jpeg" ".png" ".gif" ".webp"}} - <a href="/uploads/{{.Data.EscapedFilename}}" target="_blank"><img src="/uploads/{{.Data.EscapedFilename}}" style="max-width:400px"></a><br> -{{else if hasAnySuffix .Data.File.Filename ".mp4" ".webm" ".mov"}} - <video id="videoPlayer" controls loop muted width="600"> - <source src="/uploads/{{.Data.EscapedFilename}}"> - </video><br> -{{else}} - <a href="/uploads/{{.Data.EscapedFilename}}">Download file</a><br> -{{end}} +<div class="file-container"> -<script src="/static/timestamps.js" defer></script> +<div class="file-sidebar"> -<div class="description-section"> - <h3>Description</h3> + <details open> + <summary>Tags</summary> + <ul> + {{range $k, $vs := .Data.File.Tags}} + <li> + {{$k}}: + {{range $i, $v := $vs}} + {{if $i}}, {{end}} + <form method="post" action="/file/{{$.Data.File.ID}}/tag/{{$k}}/{{$v}}/delete" style="display:inline;"><button class="text-button" type="submit">[x]</button></form> + <a href="/tag/{{$k}}/{{$v}}">{{$v}}</a> + {{end}} + </li> + {{else}} + <li>No tags yet</li> + {{end}} + </ul> + </details> - <!-- Display Mode --> - <div id="description-display" data-original-description="{{.Data.File.Description}}"> - {{if .Data.File.Description}} - <div id="current-description" >{{.Data.File.Description}}</div> - {{else}} - <div id="no-description">No description set</div> - {{end}} - <button id="edit-description-btn" onclick="toggleDescriptionEdit()"> - {{if .Data.File.Description}}Edit Description{{else}}Add Description{{end}} - </button> - </div> + <details> + <summary>Add Tags</summary> + <form method="post"> + Category: <input type="text" name="category" list="categories"><br> + <datalist id="categories"> + {{range .Data.Categories}} + <option value="{{.}}"> + {{end}} + </datalist> + Value: <input type="text" name="value"><br> + <button type="submit">Add Tag</button> + </form> + </details> - <!-- Edit Mode (initially hidden) --> - <div id="description-edit" style="display: none;"> - <form method="post"> - <input type="hidden" name="action" value="update_description"> - <div> - <textarea - id="description-textarea" - name="description" - rows="6" - maxlength="2048" - placeholder="Enter description..." - >{{.Data.File.Description}}</textarea> - </div> - <div style="margin-top: 8px; display: flex; align-items: center; gap: 10px;"> - <input class="description-save" type="submit" value="Save Description"> - <button class="description-cancel" type="button" onclick="cancelDescriptionEdit()">Cancel</button> - </div> - </form> - </div> -</div> + <details> + <summary>Raw URL</summary> + <input id="raw-url" value="http://{{.IP}}:{{.Port}}/uploads/{{.Data.EscapedFilename}}"><br> + <button id="copy-btn" style="margin-top: 5px;">Copy to Clipboard</button> + <span id="copy-status" style="margin-left: 10px;"></span> + <script src="/static/copy-link.js" defer></script> + </details> + + <details> + <summary>File Actions</summary> -<script src="/static/description.js" defer></script> + <script src="/static/rename-file.js" defer></script> + <form id="renameForm-{{.Data.File.ID}}" method="post" action="/file/{{.Data.File.ID}}/rename" style="display:inline;"> + <input type="hidden" name="newfilename" value="{{.Data.File.Filename}}"> + <button type="button" class="text-button rename-button" + data-file-id="{{.Data.File.ID}}" + data-current-name="{{.Data.File.Filename}}"> + [ Rename File ] + </button> + </form> + <br /> + <form method="post" action="/file/{{.Data.File.ID}}/delete" style="display:inline;"> + <button type="submit" onclick="return confirm('Are you sure you want to delete this file? This cannot be undone!')" class="text-button">[ Delete File ]</button> + </form> + </details> +</div> -<h3>Raw URL</h3> -<input id="raw-url" value="http://{{.IP}}:{{.Port}}/uploads/{{.Data.EscapedFilename}}"> -<button id="copy-btn" style="margin-top: 5px;">Copy to Clipboard</button> -<span id="copy-status" style="margin-left: 10px;"></span> -<script src="/static/copy-link.js" defer></script> +<div class="file-content"> + {{if hasAnySuffix .Data.File.Filename ".jpg" ".jpeg" ".png" ".gif" ".webp"}} + <a href="/uploads/{{.Data.EscapedFilename}}" target="_blank"><img src="/uploads/{{.Data.EscapedFilename}}" style="max-width:400px"></a><br> + {{else if hasAnySuffix .Data.File.Filename ".mp4" ".webm" ".mov" ".m4v"}} + <video id="videoPlayer" controls loop muted width="600"> + <source src="/uploads/{{.Data.EscapedFilename}}"> + </video><br> + {{else}} + <a href="/uploads/{{.Data.EscapedFilename}}">Download file</a><br> + {{end}} + <div class="description-section"> + <h3>Description</h3> -<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> - <small>Include file extension (e.g., "new-name.jpg")</small><br><br> - <button type="submit" onclick="return confirm('Are you sure you want to rename this file?')">Rename File</button> -</form> + <!-- Display Mode --> + <div id="description-display" data-original-description="{{.Data.File.Description}}"> + {{if .Data.File.Description}} + <div id="current-description" >{{.Data.File.Description}}</div> + {{else}} + <div id="no-description">No description set</div> + {{end}} + <button id="edit-description-btn" onclick="toggleDescriptionEdit()"> + {{if .Data.File.Description}}Edit Description{{else}}Add Description{{end}} + </button> + </div> -<form method="post" action="/file/{{.Data.File.ID}}/delete" style="display:inline-block;"> - <button type="submit" onclick="return confirm('Are you sure you want to delete this file? This cannot be undone!')" style="background-color: #dc3545; color: white;">Delete File</button> -</form> + <!-- Edit Mode (initially hidden) --> + <div id="description-edit" style="display: none;"> + <form method="post"> + <input type="hidden" name="action" value="update_description"> + <div> + <textarea + id="description-textarea" + name="description" + rows="6" + maxlength="2048" + placeholder="Enter description..." + >{{.Data.File.Description}}</textarea> + </div> + <div style="margin-top: 8px; display: flex; align-items: center; gap: 10px;"> + <input class="description-save" type="submit" value="Save Description"> + <button class="description-cancel" type="button" onclick="cancelDescriptionEdit()">Cancel</button> + </div> + </form> + </div> + </div> -<h3>Tags</h3> -<ul> -{{range $k, $vs := .Data.File.Tags}} - <li> - {{$k}}: - {{range $i, $v := $vs}} - {{if $i}}, {{end}} - <form method="post" action="/file/{{$.Data.File.ID}}/tag/{{$k}}/{{$v}}/delete" style="display:inline;"><button class="delete-tag" type="submit">[x]</button></form> - <a href="/tag/{{$k}}/{{$v}}">{{$v}}</a> - {{end}} - </li> -{{else}} - <li>No tags yet</li> -{{end}} -</ul> + <script src="/static/description.js" defer></script> + <script src="/static/timestamps.js" defer></script> -<h3>Assign New Tag</h3> -<form method="post"> - Category: <input type="text" name="category" list="categories"><br> - <datalist id="categories"> - {{range .Data.Categories}} - <option value="{{.}}"> - {{end}} - </datalist> - Value: <input type="text" name="value"><br> - <button type="submit">Add Tag</button> -</form> +</div> {{template "_footer"}} \ No newline at end of file