Add contact antispam and fix gallery video playback.
English-only messages, rate limiting, min fill time, and normalized email validation; improve modal video serving with posters, correct MIME types, and no gzip on gallery media. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4,7 +4,7 @@ templ ContactForm(enabled bool) {
|
||||
<section id="contact" class="contact">
|
||||
<h2>Contact</h2>
|
||||
<p class="contact-intro">
|
||||
Interested in a project or collaboration? Send a message below.
|
||||
Interested in a project or collaboration? Send a message below. Messages must be in English.
|
||||
</p>
|
||||
if !enabled {
|
||||
<p class="contact-unavailable">
|
||||
@@ -33,7 +33,7 @@ templ ContactForm(enabled bool) {
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="message">Message</label>
|
||||
<textarea id="message" name="message" required rows="6" maxlength="8000"></textarea>
|
||||
<textarea id="message" name="message" required minlength="20" rows="6" maxlength="8000" placeholder="Write at least a few sentences in English."></textarea>
|
||||
</div>
|
||||
<input
|
||||
class="hp-field"
|
||||
@@ -74,3 +74,9 @@ templ ContactSendError() {
|
||||
<a href="mailto:hello@technical.kiwi">hello@technical.kiwi</a>.</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
templ ContactRateLimited() {
|
||||
<div class="alert alert-error" role="alert">
|
||||
<p>Too many messages from your network. Please try again later.</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -65,7 +65,15 @@ templ ImageModal(img gallery.Image, prevPath, nextPath string) {
|
||||
</button>
|
||||
<figure class="modal-figure">
|
||||
if img.IsVideo {
|
||||
<video src={ img.URL } controls playsinline preload="metadata"></video>
|
||||
<div class="modal-video">
|
||||
<video controls playsinline preload="auto" poster={ img.ThumbURL }>
|
||||
<source src={ img.URL } type={ videoMIME(img.Filename) }/>
|
||||
</video>
|
||||
<p class="video-unavailable" hidden>
|
||||
Playback is not supported in this browser.
|
||||
<a href={ img.URL } download={ img.Filename }>Download the video</a>
|
||||
</p>
|
||||
</div>
|
||||
} else {
|
||||
<img src={ img.URL } alt={ img.Filename }/>
|
||||
}
|
||||
|
||||
@@ -1,7 +1,22 @@
|
||||
package templates
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func currentYear() string {
|
||||
return time.Now().Format("2006")
|
||||
}
|
||||
|
||||
func videoMIME(filename string) string {
|
||||
switch strings.ToLower(filepath.Ext(filename)) {
|
||||
case ".webm":
|
||||
return "video/webm"
|
||||
case ".mov":
|
||||
return "video/quicktime"
|
||||
default:
|
||||
return "video/mp4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,8 +70,25 @@ templ Layout(title string, preloadImage string, content templ.Component) {
|
||||
hydrateLazyThumbs(document);
|
||||
});
|
||||
|
||||
function initModalVideos(root) {
|
||||
root.querySelectorAll(".modal-video video").forEach(function (video) {
|
||||
var wrap = video.closest(".modal-video");
|
||||
var notice = wrap && wrap.querySelector(".video-unavailable");
|
||||
video.addEventListener(
|
||||
"error",
|
||||
function () {
|
||||
video.classList.add("video-broken");
|
||||
if (notice) notice.hidden = false;
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
video.load();
|
||||
});
|
||||
}
|
||||
|
||||
document.body.addEventListener("htmx:afterSwap", function (event) {
|
||||
hydrateLazyThumbs(event.target);
|
||||
initModalVideos(event.target);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user