Update: Few issues to resolve see readme
this push will conclude the majority of pulls. this repos will now, not be actively be managed or any further code pushes will not be frequent.
This commit is contained in:
@@ -1,338 +1,344 @@
|
||||
{{ define "content" }}
|
||||
<div class="min-h-screen bg-gray-100">
|
||||
|
||||
<div class="max-w-2xl mx-auto">
|
||||
<!-- Create Post Form -->
|
||||
<div class="bg-white border-b border-gray-200 p-6">
|
||||
<form
|
||||
action="/posts"
|
||||
method="POST"
|
||||
enctype="multipart/form-data"
|
||||
class="space-y-4"
|
||||
>
|
||||
<div class="flex items-start space-x-4">
|
||||
<div class="flex-shrink-0">
|
||||
<div
|
||||
class="w-10 h-10 bg-blue-500 flex items-center justify-center text-white font-semibold"
|
||||
<div class="max-w-2xl mx-auto">
|
||||
<!-- Create Post Form -->
|
||||
<div class="bg-white border-b border-gray-200 p-6">
|
||||
<form
|
||||
action="/posts"
|
||||
method="POST"
|
||||
enctype="multipart/form-data"
|
||||
class="space-y-4"
|
||||
>
|
||||
You
|
||||
<div class="flex items-start space-x-4">
|
||||
<div class="flex-shrink-0">
|
||||
<div
|
||||
class="w-10 h-10 bg-blue-500 flex items-center justify-center text-white font-semibold"
|
||||
>
|
||||
You
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<textarea
|
||||
id="content"
|
||||
name="content"
|
||||
placeholder="What's on your mind?"
|
||||
class="w-full px-0 py-2 text-gray-900 placeholder-gray-500 border-0 resize-none focus:outline-none focus:ring-0"
|
||||
rows="3"
|
||||
required
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex items-center justify-between pt-4 border-t border-gray-100"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<label
|
||||
for="image"
|
||||
class="cursor-pointer flex items-center space-x-2 text-blue-500 hover:text-blue-600"
|
||||
>
|
||||
<svg
|
||||
class="w-6 h-6"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
></path>
|
||||
</svg>
|
||||
<span class="text-sm font-medium">Photo</span>
|
||||
</label>
|
||||
<input
|
||||
id="image"
|
||||
type="file"
|
||||
name="image"
|
||||
accept="image/*"
|
||||
class="hidden"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="bg-blue-500 hover:bg-blue-600 text-white px-6 py-2 text-sm font-semibold transition-colors disabled:opacity-50"
|
||||
>
|
||||
Post
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Image preview -->
|
||||
<div id="imagePreview" class="hidden">
|
||||
<img
|
||||
id="previewImg"
|
||||
class="w-full h-64 object-cover border"
|
||||
alt="Preview"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
id="removeImage"
|
||||
class="mt-2 text-red-500 text-sm hover:text-red-600"
|
||||
>
|
||||
Remove image
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Posts Feed -->
|
||||
<div class="space-y-0">
|
||||
{{range .Posts}}
|
||||
<article class="bg-white border-b border-gray-200">
|
||||
<!-- Post Header -->
|
||||
<div class="flex items-center px-6 py-4">
|
||||
<div class="flex-shrink-0">
|
||||
<div
|
||||
class="w-10 h-10 bg-blue-500 flex items-center justify-center text-white font-semibold"
|
||||
>
|
||||
{{slice .AuthorName 0 1}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="text-sm font-semibold text-gray-900">
|
||||
{{.AuthorName}}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500">
|
||||
{{.CreatedAt.Format "Jan 2, 2006"}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Post Image -->
|
||||
{{if .ImageURL}}
|
||||
<div class="w-full">
|
||||
<img
|
||||
src="{{.ImageURL}}"
|
||||
alt="Post image"
|
||||
class="w-full max-h-96 object-cover"
|
||||
onerror="this.parentElement.style.display='none'"
|
||||
/>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<!-- Post Content -->
|
||||
{{if .Content}}
|
||||
<div class="px-6 pt-2 pb-4">
|
||||
<p class="text-gray-900 leading-relaxed">
|
||||
<span class="font-semibold">{{.AuthorName}}</span>
|
||||
{{.Content}}
|
||||
</p>
|
||||
</div>
|
||||
{{end}}
|
||||
</article>
|
||||
{{else}}
|
||||
<div class="bg-white p-12 text-center">
|
||||
<div class="max-w-sm mx-auto">
|
||||
<svg
|
||||
class="w-16 h-16 mx-auto text-gray-300 mb-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
></path>
|
||||
</svg>
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">
|
||||
No posts yet
|
||||
</h3>
|
||||
<p class="text-gray-500">
|
||||
Be the first to share something with the community!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<textarea
|
||||
id="content"
|
||||
name="content"
|
||||
placeholder="What's on your mind?"
|
||||
class="w-full px-0 py-2 text-gray-900 placeholder-gray-500 border-0 resize-none focus:outline-none focus:ring-0"
|
||||
rows="3"
|
||||
required
|
||||
></textarea>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex items-center justify-between pt-4 border-t border-gray-100"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<label
|
||||
for="image"
|
||||
class="cursor-pointer flex items-center space-x-2 text-blue-500 hover:text-blue-600"
|
||||
>
|
||||
<svg
|
||||
class="w-6 h-6"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
></path>
|
||||
</svg>
|
||||
<span class="text-sm font-medium">Photo</span>
|
||||
</label>
|
||||
<input
|
||||
id="image"
|
||||
type="file"
|
||||
name="image"
|
||||
accept="image/*"
|
||||
class="hidden"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="bg-blue-500 hover:bg-blue-600 text-white px-6 py-2 text-sm font-semibold transition-colors disabled:opacity-50"
|
||||
>
|
||||
Post
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Image preview -->
|
||||
<div id="imagePreview" class="hidden">
|
||||
<img
|
||||
id="previewImg"
|
||||
class="w-full h-64 object-cover border"
|
||||
alt="Preview"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
id="removeImage"
|
||||
class="mt-2 text-red-500 text-sm hover:text-red-600"
|
||||
>
|
||||
Remove image
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Posts Feed -->
|
||||
<div class="space-y-0">
|
||||
{{range .Posts}}
|
||||
<article class="bg-white border-b border-gray-200">
|
||||
<!-- Post Header -->
|
||||
<div class="flex items-center px-6 py-4">
|
||||
<div class="flex-shrink-0">
|
||||
<div
|
||||
class="w-10 h-10 bg-blue-500 flex items-center justify-center text-white font-semibold"
|
||||
>
|
||||
{{slice .AuthorName 0 1}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="text-sm font-semibold text-gray-900">{{.AuthorName}}</p>
|
||||
<p class="text-xs text-gray-500">
|
||||
{{.CreatedAt.Format "Jan 2, 2006"}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Post Image -->
|
||||
{{if .ImageURL}}
|
||||
<div class="w-full">
|
||||
<img
|
||||
src="{{.ImageURL}}"
|
||||
alt="Post image"
|
||||
class="w-full max-h-96 object-cover"
|
||||
onerror="this.parentElement.style.display='none'"
|
||||
/>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<!-- Post Content -->
|
||||
{{if .Content}}
|
||||
<div class="px-6 pt-2 pb-4">
|
||||
<p class="text-gray-900 leading-relaxed">
|
||||
<span class="font-semibold">{{.AuthorName}}</span> {{.Content}}
|
||||
</p>
|
||||
</div>
|
||||
{{end}}
|
||||
</article>
|
||||
{{else}}
|
||||
<div class="bg-white p-12 text-center">
|
||||
<div class="max-w-sm mx-auto">
|
||||
<svg
|
||||
class="w-16 h-16 mx-auto text-gray-300 mb-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
></path>
|
||||
</svg>
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">No posts yet</h3>
|
||||
<p class="text-gray-500">
|
||||
Be the first to share something with the community!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* Custom styles for Instagram-like feel */
|
||||
.reaction-btn.active {
|
||||
color: #3b82f6 !important;
|
||||
}
|
||||
/* Custom styles for Instagram-like feel */
|
||||
.reaction-btn.active {
|
||||
color: #3b82f6 !important;
|
||||
}
|
||||
|
||||
.reaction-btn.active svg {
|
||||
fill: currentColor;
|
||||
}
|
||||
.reaction-btn.active svg {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.reaction-btn.dislike-active {
|
||||
color: #ef4444 !important;
|
||||
}
|
||||
.reaction-btn.dislike-active {
|
||||
color: #ef4444 !important;
|
||||
}
|
||||
|
||||
/* Smooth transitions */
|
||||
.reaction-btn {
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
/* Smooth transitions */
|
||||
.reaction-btn {
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.reaction-btn:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
.reaction-btn:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
/* Focus styles */
|
||||
button:focus {
|
||||
outline: 2px solid #3b82f6;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
/* Focus styles */
|
||||
button:focus {
|
||||
outline: 2px solid #3b82f6;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
input:focus,
|
||||
textarea:focus {
|
||||
outline: 2px solid #3b82f6;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
input:focus,
|
||||
textarea:focus {
|
||||
outline: 2px solid #3b82f6;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const fileInput = document.getElementById("image");
|
||||
const imagePreview = document.getElementById("imagePreview");
|
||||
const previewImg = document.getElementById("previewImg");
|
||||
const removeImageBtn = document.getElementById("removeImage");
|
||||
const form = document.querySelector("form");
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const fileInput = document.getElementById("image");
|
||||
const imagePreview = document.getElementById("imagePreview");
|
||||
const previewImg = document.getElementById("previewImg");
|
||||
const removeImageBtn = document.getElementById("removeImage");
|
||||
const form = document.querySelector("form");
|
||||
|
||||
// Image upload preview
|
||||
if (fileInput) {
|
||||
fileInput.addEventListener("change", function (e) {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
console.log(
|
||||
"Selected file:",
|
||||
file.name,
|
||||
"Size:",
|
||||
file.size,
|
||||
"Type:",
|
||||
file.type
|
||||
);
|
||||
// Image upload preview
|
||||
if (fileInput) {
|
||||
fileInput.addEventListener("change", function (e) {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
console.log(
|
||||
"Selected file:",
|
||||
file.name,
|
||||
"Size:",
|
||||
file.size,
|
||||
"Type:",
|
||||
file.type,
|
||||
);
|
||||
|
||||
// Validate file size (10MB max)
|
||||
if (file.size > 10 * 1024 * 1024) {
|
||||
alert("File is too large. Maximum size is 10MB.");
|
||||
this.value = "";
|
||||
return;
|
||||
}
|
||||
// Validate file size (10MB max)
|
||||
if (file.size > 10 * 1024 * 1024) {
|
||||
alert("File is too large. Maximum size is 10MB.");
|
||||
this.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate file type
|
||||
const allowedTypes = [
|
||||
"image/jpeg",
|
||||
"image/jpg",
|
||||
"image/png",
|
||||
"image/gif",
|
||||
"image/webp",
|
||||
];
|
||||
if (!allowedTypes.includes(file.type)) {
|
||||
alert("Invalid file type. Please select a valid image file.");
|
||||
this.value = "";
|
||||
return;
|
||||
}
|
||||
// Validate file type
|
||||
const allowedTypes = [
|
||||
"image/jpeg",
|
||||
"image/jpg",
|
||||
"image/png",
|
||||
"image/gif",
|
||||
"image/webp",
|
||||
];
|
||||
if (!allowedTypes.includes(file.type)) {
|
||||
alert(
|
||||
"Invalid file type. Please select a valid image file.",
|
||||
);
|
||||
this.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
// Show preview
|
||||
const reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
previewImg.src = e.target.result;
|
||||
imagePreview.classList.remove("hidden");
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Remove image preview
|
||||
if (removeImageBtn) {
|
||||
removeImageBtn.addEventListener("click", function () {
|
||||
fileInput.value = "";
|
||||
imagePreview.classList.add("hidden");
|
||||
previewImg.src = "";
|
||||
});
|
||||
}
|
||||
|
||||
// Reaction buttons
|
||||
const reactionBtns = document.querySelectorAll(".reaction-btn");
|
||||
reactionBtns.forEach(function (btn) {
|
||||
btn.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
const postId = this.dataset.postId;
|
||||
const reaction = this.dataset.reaction;
|
||||
|
||||
// Toggle active state
|
||||
if (reaction === "like") {
|
||||
this.classList.toggle("active");
|
||||
// Remove dislike active state from sibling
|
||||
const dislikeBtn = this.parentElement.querySelector(
|
||||
'[data-reaction="dislike"]'
|
||||
);
|
||||
dislikeBtn.classList.remove("dislike-active");
|
||||
} else {
|
||||
this.classList.toggle("dislike-active");
|
||||
// Remove like active state from sibling
|
||||
const likeBtn = this.parentElement.querySelector(
|
||||
'[data-reaction="like"]'
|
||||
);
|
||||
likeBtn.classList.remove("active");
|
||||
// Show preview
|
||||
const reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
previewImg.src = e.target.result;
|
||||
imagePreview.classList.remove("hidden");
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update count (mock implementation)
|
||||
const countSpan = this.querySelector("span");
|
||||
const currentCount = parseInt(countSpan.textContent);
|
||||
const isActive =
|
||||
this.classList.contains("active") ||
|
||||
this.classList.contains("dislike-active");
|
||||
countSpan.textContent = isActive
|
||||
? currentCount + 1
|
||||
: Math.max(0, currentCount - 1);
|
||||
// Remove image preview
|
||||
if (removeImageBtn) {
|
||||
removeImageBtn.addEventListener("click", function () {
|
||||
fileInput.value = "";
|
||||
imagePreview.classList.add("hidden");
|
||||
previewImg.src = "";
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`${reaction} clicked for post ${postId}`);
|
||||
// Reaction buttons
|
||||
const reactionBtns = document.querySelectorAll(".reaction-btn");
|
||||
reactionBtns.forEach(function (btn) {
|
||||
btn.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
const postId = this.dataset.postId;
|
||||
const reaction = this.dataset.reaction;
|
||||
|
||||
// Here you would typically send an AJAX request to update the backend
|
||||
// fetch(`/posts/${postId}/react`, {
|
||||
// method: 'POST',
|
||||
// headers: { 'Content-Type': 'application/json' },
|
||||
// body: JSON.stringify({ reaction: reaction })
|
||||
// });
|
||||
});
|
||||
// Toggle active state
|
||||
if (reaction === "like") {
|
||||
this.classList.toggle("active");
|
||||
// Remove dislike active state from sibling
|
||||
const dislikeBtn = this.parentElement.querySelector(
|
||||
'[data-reaction="dislike"]',
|
||||
);
|
||||
dislikeBtn.classList.remove("dislike-active");
|
||||
} else {
|
||||
this.classList.toggle("dislike-active");
|
||||
// Remove like active state from sibling
|
||||
const likeBtn = this.parentElement.querySelector(
|
||||
'[data-reaction="like"]',
|
||||
);
|
||||
likeBtn.classList.remove("active");
|
||||
}
|
||||
|
||||
// Update count (mock implementation)
|
||||
const countSpan = this.querySelector("span");
|
||||
const currentCount = parseInt(countSpan.textContent);
|
||||
const isActive =
|
||||
this.classList.contains("active") ||
|
||||
this.classList.contains("dislike-active");
|
||||
countSpan.textContent = isActive
|
||||
? currentCount + 1
|
||||
: Math.max(0, currentCount - 1);
|
||||
|
||||
console.log(`${reaction} clicked for post ${postId}`);
|
||||
|
||||
// Here you would typically send an AJAX request to update the backend
|
||||
// fetch(`/posts/${postId}/react`, {
|
||||
// method: 'POST',
|
||||
// headers: { 'Content-Type': 'application/json' },
|
||||
// body: JSON.stringify({ reaction: reaction })
|
||||
// });
|
||||
});
|
||||
});
|
||||
|
||||
// Form submission
|
||||
if (form) {
|
||||
form.addEventListener("submit", function (e) {
|
||||
const content = document.getElementById("content").value.trim();
|
||||
const hasImage = fileInput.files.length > 0;
|
||||
|
||||
if (!content && !hasImage) {
|
||||
e.preventDefault();
|
||||
alert("Please add some content or an image to your post.");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Form being submitted...");
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-resize textarea
|
||||
const textarea = document.getElementById("content");
|
||||
if (textarea) {
|
||||
textarea.addEventListener("input", function () {
|
||||
this.style.height = "auto";
|
||||
this.style.height = this.scrollHeight + "px";
|
||||
});
|
||||
}
|
||||
|
||||
// Smooth scroll to top when clicking header
|
||||
const header = document.querySelector("h1");
|
||||
if (header) {
|
||||
header.addEventListener("click", function () {
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Form submission
|
||||
if (form) {
|
||||
form.addEventListener("submit", function (e) {
|
||||
const content = document.getElementById("content").value.trim();
|
||||
const hasImage = fileInput.files.length > 0;
|
||||
|
||||
if (!content && !hasImage) {
|
||||
e.preventDefault();
|
||||
alert("Please add some content or an image to your post.");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Form being submitted...");
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-resize textarea
|
||||
const textarea = document.getElementById("content");
|
||||
if (textarea) {
|
||||
textarea.addEventListener("input", function () {
|
||||
this.style.height = "auto";
|
||||
this.style.height = this.scrollHeight + "px";
|
||||
});
|
||||
}
|
||||
|
||||
// Smooth scroll to top when clicking header
|
||||
const header = document.querySelector("h1");
|
||||
if (header) {
|
||||
header.addEventListener("click", function () {
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{{ end }}
|
||||
|
||||
Reference in New Issue
Block a user