- Add custom publish time field to article create/edit forms - Support datetime-local input for manual time setting - Auto-set on first publish if left blank - Works for both admin and user article forms - Display last updated time on article detail page - Show both published time and last updated time - Auto-maintained by GORM on each update - Format: YYYY-MM-DD HH:MM - Add i18n support for new fields - article_published_at: Published Time / 发布时间 - article_published_at_hint: Leave blank to auto-set on publish / 留空则在发布时自动设置 - article_last_updated: Last Updated / 最后更新 - Update handlers and templates - handlers/article.go: Add parsePublishedAt/formatPublishedAt functions - handlers/home.go: Add formatUpdateTime function - handlers/my_articles.go: Support custom publish time in user articles - templates: Add datetime-local inputs and time display Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
178 lines
4.8 KiB
Go
178 lines
4.8 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/gin-contrib/sessions"
|
|
"github.com/gin-gonic/gin"
|
|
"gorm.io/gorm"
|
|
|
|
"go_blog/models"
|
|
)
|
|
|
|
// MyArticlesPage renders the logged-in user's article management page.
|
|
func MyArticlesPage(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
tr := getTr(c)
|
|
session := sessions.Default(c)
|
|
userID := session.Get("user_id")
|
|
|
|
var articles []models.Article
|
|
db.Where("author_id = ?", userID).Order("created_at DESC").Find(&articles)
|
|
|
|
data := DefaultData(c)
|
|
data["Title"] = tr["my_articles_title"]
|
|
data["Articles"] = articles
|
|
c.HTML(http.StatusOK, "my_articles", data)
|
|
}
|
|
}
|
|
|
|
// MyArticleCreatePage renders the article creation form for regular users.
|
|
func MyArticleCreatePage(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
tr := getTr(c)
|
|
token := randomToken()
|
|
session := sessions.Default(c)
|
|
session.Set("pending_token", token)
|
|
session.Save()
|
|
|
|
renderMyArticleForm(c, db, articleForm{
|
|
Action: "/my/articles/new",
|
|
TitleText: tr["article_create_title"],
|
|
SessionToken: token,
|
|
}, "")
|
|
}
|
|
}
|
|
|
|
// MyArticleCreate handles the POST request to create a new article for regular users.
|
|
func MyArticleCreate(db *gorm.DB) gin.HandlerFunc {
|
|
return ArticleCreate(db) // Reuse the same logic
|
|
}
|
|
|
|
// MyArticleEditPage renders the article edit form for the logged-in user's own articles.
|
|
func MyArticleEditPage(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
tr := getTr(c)
|
|
id := c.Param("id")
|
|
session := sessions.Default(c)
|
|
userID := session.Get("user_id")
|
|
|
|
var article models.Article
|
|
if err := db.First(&article, "id = ? AND author_id = ?", id, userID).Error; err != nil {
|
|
c.Redirect(http.StatusFound, "/my/articles")
|
|
return
|
|
}
|
|
|
|
renderMyArticleForm(c, db, articleForm{
|
|
Title: article.Title,
|
|
Slug: article.Slug,
|
|
Summary: article.Summary,
|
|
Content: article.Content,
|
|
Cover: article.Cover,
|
|
StatusStr: strconv.Itoa(article.Status),
|
|
IsTop: article.IsTop,
|
|
PublishedAt: formatPublishedAt(article.PublishedAt),
|
|
Action: "/my/articles/" + id + "/edit",
|
|
TitleText: tr["article_edit_title"],
|
|
ArticleID: article.ID,
|
|
}, "")
|
|
}
|
|
}
|
|
|
|
// MyArticleUpdate handles the POST request to update the user's own article.
|
|
func MyArticleUpdate(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
tr := getTr(c)
|
|
id := c.Param("id")
|
|
session := sessions.Default(c)
|
|
userID := session.Get("user_id")
|
|
|
|
var article models.Article
|
|
if err := db.First(&article, "id = ? AND author_id = ?", id, userID).Error; err != nil {
|
|
c.Redirect(http.StatusFound, "/my/articles")
|
|
return
|
|
}
|
|
|
|
f := parseArticleForm(c)
|
|
f.Action = "/my/articles/" + id + "/edit"
|
|
f.TitleText = tr["article_edit_title"]
|
|
f.ArticleID = article.ID
|
|
|
|
if f.Title == "" {
|
|
renderMyArticleForm(c, db, f, tr["article_title_required"])
|
|
return
|
|
}
|
|
if f.Content == "" {
|
|
renderMyArticleForm(c, db, f, tr["article_content_required"])
|
|
return
|
|
}
|
|
|
|
if f.Slug == "" {
|
|
f.Slug = generateSlug(f.Title)
|
|
if f.Slug == "" {
|
|
f.Slug = fallbackSlug(article.ID)
|
|
}
|
|
}
|
|
|
|
newStatus := statusFromForm(f.StatusStr)
|
|
|
|
// Handle published_at: use form value if provided, otherwise auto-stamp on first publish.
|
|
var publishedAt *time.Time
|
|
if f.PublishedAt != "" {
|
|
// User provided a custom published time
|
|
publishedAt = parsePublishedAt(f.PublishedAt)
|
|
} else {
|
|
// Stamp the publish time the first time an article is published.
|
|
wasPublished := article.Status == models.ArticlePublished
|
|
publishedAt = article.PublishedAt
|
|
if newStatus == models.ArticlePublished && !wasPublished && publishedAt == nil {
|
|
now := time.Now()
|
|
publishedAt = &now
|
|
}
|
|
}
|
|
|
|
updates := map[string]interface{}{
|
|
"title": f.Title,
|
|
"slug": f.Slug,
|
|
"summary": f.Summary,
|
|
"content": f.Content,
|
|
"cover": f.Cover,
|
|
"status": newStatus,
|
|
"is_top": f.IsTop,
|
|
"published_at": publishedAt,
|
|
}
|
|
|
|
if err := db.Model(&article).Updates(updates).Error; err != nil {
|
|
renderMyArticleForm(c, db, f, tr["article_error"])
|
|
return
|
|
}
|
|
|
|
c.Redirect(http.StatusFound, "/my/articles")
|
|
}
|
|
}
|
|
|
|
// MyArticleDelete soft-deletes the user's own article.
|
|
func MyArticleDelete(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
id := c.Param("id")
|
|
session := sessions.Default(c)
|
|
userID := session.Get("user_id")
|
|
|
|
db.Where("id = ? AND author_id = ?", id, userID).Delete(&models.Article{})
|
|
c.Redirect(http.StatusFound, "/my/articles")
|
|
}
|
|
}
|
|
|
|
// renderMyArticleForm renders the article form for regular users.
|
|
func renderMyArticleForm(c *gin.Context, db *gorm.DB, f articleForm, errMsg string) {
|
|
data := DefaultData(c)
|
|
data["Title"] = f.TitleText
|
|
if errMsg != "" {
|
|
data["Error"] = errMsg
|
|
}
|
|
applyFormToData(data, f)
|
|
c.HTML(http.StatusOK, "my_article_form", data)
|
|
}
|