Skip to content
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions models/user/setting_keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ const (
UserActivityPubPrivPem = "activitypub.priv_pem"
// UserActivityPubPubPem is user's public key
UserActivityPubPubPem = "activitypub.pub_pem"
// SettingsForceAbsoluteTimestamps is the setting key for hidden comment types
SettingsForceAbsoluteTimestamps = "timestamps.force_absolute"
)
11 changes: 7 additions & 4 deletions modules/timeutil/datetime.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import (
"fmt"
"html"
"html/template"
"strings"
"time"
)

// DateTime renders an absolute time HTML element by datetime.
func DateTime(format string, datetime any) template.HTML {
func DateTime(format string, datetime any, attrs ...string) template.HTML {
if p, ok := datetime.(*time.Time); ok {
datetime = *p
}
Expand Down Expand Up @@ -48,13 +49,15 @@ func DateTime(format string, datetime any) template.HTML {
panic(fmt.Sprintf("Unsupported time type %T", datetime))
}

extraAttrs := strings.Join(attrs, " ")

switch format {
case "short":
return template.HTML(fmt.Sprintf(`<relative-time format="datetime" year="numeric" month="short" day="numeric" weekday="" datetime="%s">%s</relative-time>`, datetimeEscaped, textEscaped))
return template.HTML(fmt.Sprintf(`<relative-time %s format="datetime" year="numeric" month="short" day="numeric" weekday="" datetime="%s">%s</relative-time>`, extraAttrs, datetimeEscaped, textEscaped))
case "long":
return template.HTML(fmt.Sprintf(`<relative-time format="datetime" year="numeric" month="long" day="numeric" weekday="" datetime="%s">%s</relative-time>`, datetimeEscaped, textEscaped))
return template.HTML(fmt.Sprintf(`<relative-time %s format="datetime" year="numeric" month="long" day="numeric" weekday="" datetime="%s">%s</relative-time>`, extraAttrs, datetimeEscaped, textEscaped))
case "full":
return template.HTML(fmt.Sprintf(`<relative-time format="datetime" weekday="" year="numeric" month="short" day="numeric" hour="numeric" minute="numeric" second="numeric" datetime="%s">%s</relative-time>`, datetimeEscaped, textEscaped))
return template.HTML(fmt.Sprintf(`<relative-time %s format="datetime" weekday="" year="numeric" month="short" day="numeric" hour="numeric" minute="numeric" second="numeric" datetime="%s">%s</relative-time>`, extraAttrs, datetimeEscaped, textEscaped))
}
panic(fmt.Sprintf("Unsupported format %s", format))
}
32 changes: 29 additions & 3 deletions modules/timeutil/since.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,30 @@
package timeutil

import (
"context"
"fmt"
"html/template"
"strconv"
"strings"
"time"

"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/translation"
)

type PreferenceHelper struct {
GetSetting func(ctx context.Context, key string, def ...string) (string, error)
SettingsForceAbsoluteTimestamps string
}

var preferenceHelper PreferenceHelper

func Init(ph *PreferenceHelper) {
if ph != nil {
preferenceHelper = *ph
}
}

// Seconds-based time units
const (
Minute = 60
Expand Down Expand Up @@ -131,11 +147,21 @@ func timeSinceUnix(then, now time.Time, lang translation.Locale) template.HTML {
}

// TimeSince renders relative time HTML given a time.Time
func TimeSince(then time.Time, lang translation.Locale) template.HTML {
func TimeSince(ctx context.Context, then time.Time, lang translation.Locale) template.HTML {
// if user forces absolute timestamps, use the full time
val, err := preferenceHelper.GetSetting(ctx, preferenceHelper.SettingsForceAbsoluteTimestamps, "false")
if err != nil {
log.Error("GetSetting %w", err)
}
forceAbsoluteTimestamps, _ := strconv.ParseBool(val) // we can safely ignore the failed conversion here
if forceAbsoluteTimestamps {
return DateTime("full", then, `class="time-since"`)
}

return timeSinceUnix(then, time.Now(), lang)
}

// TimeSinceUnix renders relative time HTML given a TimeStamp
func TimeSinceUnix(then TimeStamp, lang translation.Locale) template.HTML {
return TimeSince(then.AsLocalTime(), lang)
func TimeSinceUnix(ctx context.Context, then TimeStamp, lang translation.Locale) template.HTML {
return TimeSince(ctx, then.AsLocalTime(), lang)
}
3 changes: 3 additions & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,9 @@ saved_successfully = Your settings were saved successfully.
privacy = Privacy
keep_activity_private = Hide the activity from the profile page
keep_activity_private_popup = Makes the activity visible only for you and the admins
timestamps = Timestamps
force_absolute_timestamps = Force absolute timestamps
force_absolute_timestamps_popup = Always render all timestamps as dates, do not allow relative form (e.g. 5 days ago)

lookup_avatar_by_mail = Look Up Avatar by Email Address
federated_avatar_lookup = Federated Avatar Lookup
Expand Down
2 changes: 1 addition & 1 deletion routers/web/repo/blame.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames m
commitCnt++

// User avatar image
commitSince := timeutil.TimeSinceUnix(timeutil.TimeStamp(commit.Author.When.Unix()), ctx.Locale)
commitSince := timeutil.TimeSinceUnix(ctx, timeutil.TimeStamp(commit.Author.When.Unix()), ctx.Locale)

var avatar string
if commit.User != nil {
Expand Down
2 changes: 1 addition & 1 deletion routers/web/repo/issue_content_history.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func GetContentHistoryList(ctx *context.Context) {
class := avatars.DefaultAvatarClass + " gt-mr-3"
name := html.EscapeString(username)
avatarHTML := string(templates.AvatarHTML(src, 28, class, username))
timeSinceText := string(timeutil.TimeSinceUnix(item.EditedUnix, ctx.Locale))
timeSinceText := string(timeutil.TimeSinceUnix(ctx, item.EditedUnix, ctx.Locale))

results = append(results, map[string]interface{}{
"name": avatarHTML + "<strong>" + name + "</strong> " + actionText + " " + timeSinceText,
Expand Down
22 changes: 22 additions & 0 deletions routers/web/user/setting/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"net/http"
"os"
"path/filepath"
"strconv"
"strings"

"code.gitea.io/gitea/models/db"
Expand Down Expand Up @@ -350,6 +351,14 @@ func Appearance(ctx *context.Context) {
return forms.IsUserHiddenCommentTypeGroupChecked(commentTypeGroup, hiddenCommentTypes)
}

val, err = user_model.GetUserSetting(ctx.Doer.ID, user_model.SettingsForceAbsoluteTimestamps, "false")
if err != nil {
ctx.ServerError("GetUserSetting", err)
return
}
forceAbsoluteTimestamps, _ := strconv.ParseBool(val) // we can safely ignore the failed conversion here
ctx.Data["ForceAbsoluteTimestamps"] = forceAbsoluteTimestamps

ctx.HTML(http.StatusOK, tplSettingsAppearance)
}

Expand Down Expand Up @@ -421,3 +430,16 @@ func UpdateUserHiddenComments(ctx *context.Context) {
ctx.Flash.Success(ctx.Tr("settings.saved_successfully"))
ctx.Redirect(setting.AppSubURL + "/user/settings/appearance")
}

// UpdateUserTimestamps update a user's timestamp preferences
func UpdateUserTimestamps(ctx *context.Context) {
err := user_model.SetUserSetting(ctx.Doer.ID, user_model.SettingsForceAbsoluteTimestamps, strconv.FormatBool(forms.UserTimestampsFromRequest(ctx).ForceAbsoluteTimestamps))
if err != nil {
ctx.ServerError("SetUserSetting", err)
return
}

log.Trace("User settings updated: %s", ctx.Doer.Name)
ctx.Flash.Success(ctx.Tr("settings.saved_successfully"))
ctx.Redirect(setting.AppSubURL + "/user/settings/appearance")
}
3 changes: 3 additions & 0 deletions routers/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import (
context_service "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/lfs"
user_service "code.gitea.io/gitea/services/user"

_ "code.gitea.io/gitea/modules/session" // to registers all internal adapters

Expand Down Expand Up @@ -397,6 +398,7 @@ func registerRoutes(m *web.Route) {
m.Get("/login/oauth/keys", ignSignInAndCsrf, auth.OIDCKeys)
m.Post("/login/oauth/introspect", CorsHandler(), web.Bind(forms.IntrospectTokenForm{}), ignSignInAndCsrf, auth.IntrospectOAuth)

user_service.Init()
m.Group("/user/settings", func() {
m.Get("", user_setting.Profile)
m.Post("", web.Bind(forms.UpdateProfileForm{}), user_setting.ProfilePost)
Expand All @@ -414,6 +416,7 @@ func registerRoutes(m *web.Route) {
m.Get("", user_setting.Appearance)
m.Post("/language", web.Bind(forms.UpdateLanguageForm{}), user_setting.UpdateUserLang)
m.Post("/hidden_comments", user_setting.UpdateUserHiddenComments)
m.Post("/timestamps", user_setting.UpdateUserTimestamps)
m.Post("/theme", web.Bind(forms.UpdateThemeForm{}), user_setting.UpdateUIThemePost)
})
m.Group("/security", func() {
Expand Down
5 changes: 5 additions & 0 deletions services/forms/user_form.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,11 @@ type UpdateLanguageForm struct {
Language string
}

// UpdateTimestampsForm form for updating profile
type UpdateTimestampsForm struct {
ForceAbsoluteTimestamps bool
}

// Validate validates the fields
func (f *UpdateLanguageForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
ctx := context.GetContext(req)
Expand Down
14 changes: 14 additions & 0 deletions services/forms/user_form_timestamps.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package forms

import (
"code.gitea.io/gitea/modules/context"
)

// UserTimestampsFromRequest parse the form to hidden comment types bitset
func UserTimestampsFromRequest(ctx *context.Context) *UpdateTimestampsForm {
timestampsForm := &UpdateTimestampsForm{ForceAbsoluteTimestamps: ctx.FormBool("force_absolute_timestamps")}
return timestampsForm
}
17 changes: 17 additions & 0 deletions services/user/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ import (
system_model "code.gitea.io/gitea/models/system"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/avatar"
gitea_context "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/eventsource"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/packages"
)
Expand Down Expand Up @@ -290,3 +292,18 @@ func DeleteAvatar(u *user_model.User) error {
}
return nil
}

func Init() {
timeutil.Init(&timeutil.PreferenceHelper{
GetSetting: func(ctx context.Context, key string, def ...string) (string, error) {
giteaCtx, ok := ctx.(*gitea_context.Context)

// this casting should always be ok but if it fails we have to provide a fallback
if !ok {
return "false", nil
}
return user_model.GetUserSetting(giteaCtx.Doer.ID, key, def...)
},
SettingsForceAbsoluteTimestamps: user_model.SettingsForceAbsoluteTimestamps,
})
}
2 changes: 1 addition & 1 deletion templates/admin/process-row.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<div class="icon gt-ml-3 gt-mr-3">{{if eq .Process.Type "request"}}{{svg "octicon-globe" 16}}{{else if eq .Process.Type "system"}}{{svg "octicon-cpu" 16}}{{else}}{{svg "octicon-terminal" 16}}{{end}}</div>
<div class="content gt-f1">
<div class="header">{{.Process.Description}}</div>
<div class="description">{{TimeSince .Process.Start .root.locale}}</div>
<div class="description">{{TimeSince $.Context .Process.Start .root.locale}}</div>
</div>
<div>
{{if ne .Process.Type "system"}}
Expand Down
2 changes: 1 addition & 1 deletion templates/admin/stacktrace-row.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
</div>
<div class="content gt-f1">
<div class="header">{{.Process.Description}}</div>
<div class="description">{{if ne .Process.Type "none"}}{{TimeSince .Process.Start .root.locale}}{{end}}</div>
<div class="description">{{if ne .Process.Type "none"}}{{TimeSince .root.Context .Process.Start .root.locale}}{{end}}</div>
</div>
<div>
{{if or (eq .Process.Type "request") (eq .Process.Type "normal")}}
Expand Down
14 changes: 7 additions & 7 deletions templates/devtest/gitea-ui.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@

<div>
<h1>TimeSince</h1>
<div>Now: {{TimeSince .TimeNow $.locale}}</div>
<div>5s past: {{TimeSince .TimePast5s $.locale}}</div>
<div>5s future: {{TimeSince .TimeFuture5s $.locale}}</div>
<div>2m past: {{TimeSince .TimePast2m $.locale}}</div>
<div>2m future: {{TimeSince .TimeFuture2m $.locale}}</div>
<div>1y past: {{TimeSince .TimePast1y $.locale}}</div>
<div>1y future: {{TimeSince .TimeFuture1y $.locale}}</div>
<div>Now: {{TimeSince $.Context .TimeNow $.locale}}</div>
<div>5s past: {{TimeSince $.Context .TimePast5s $.locale}}</div>
<div>5s future: {{TimeSince $.Context .TimeFuture5s $.locale}}</div>
<div>2m past: {{TimeSince $.Context .TimePast2m $.locale}}</div>
<div>2m future: {{TimeSince $.Context .TimeFuture2m $.locale}}</div>
<div>1y past: {{TimeSince $.Context .TimePast1y $.locale}}</div>
<div>1y future: {{TimeSince $.Context .TimeFuture1y $.locale}}</div>
</div>

<div>
Expand Down
2 changes: 1 addition & 1 deletion templates/explore/repo_list.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
{{end}}
</div>
{{end}}
<p class="time">{{$.locale.Tr "org.repo_updated"}} {{TimeSinceUnix .UpdatedUnix $.locale}}</p>
<p class="time">{{$.locale.Tr "org.repo_updated"}} {{TimeSinceUnix $.Context .UpdatedUnix $.locale}}</p>
</div>
</div>
{{else}}
Expand Down
2 changes: 1 addition & 1 deletion templates/package/shared/list.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<span class="ui label">{{svg .Package.Type.SVGName 16}} {{.Package.Type.Name}}</span>
</div>
<div class="desc issue-item-bottom-row gt-df gt-ac gt-fw gt-my-1">
{{$timeStr := TimeSinceUnix .Version.CreatedUnix $.locale}}
{{$timeStr := TimeSinceUnix $.Context .Version.CreatedUnix $.locale}}
{{$hasRepositoryAccess := false}}
{{if .Repository}}
{{$hasRepositoryAccess = index $.RepositoryAccessMap .Repository.ID}}
Expand Down
2 changes: 1 addition & 1 deletion templates/package/shared/versionlist.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
<a class="title" href="{{.FullWebLink}}">{{.Version.LowerVersion}}</a>
</div>
<div class="desc issue-item-bottom-row gt-df gt-ac gt-fw gt-my-1">
{{$.locale.Tr "packages.published_by" (TimeSinceUnix .Version.CreatedUnix $.locale) .Creator.HomeLink (.Creator.GetDisplayName | Escape) | Safe}}
{{$.locale.Tr "packages.published_by" (TimeSinceUnix $.Context .Version.CreatedUnix $.locale) .Creator.HomeLink (.Creator.GetDisplayName | Escape) | Safe}}
</div>
</div>
</li>
Expand Down
4 changes: 2 additions & 2 deletions templates/package/view.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<h1>{{.PackageDescriptor.Package.Name}} ({{.PackageDescriptor.Version.Version}})</h1>
</div>
<div>
{{$timeStr := TimeSinceUnix .PackageDescriptor.Version.CreatedUnix $.locale}}
{{$timeStr := TimeSinceUnix $.Context .PackageDescriptor.Version.CreatedUnix $.locale}}
{{if .HasRepositoryAccess}}
{{.locale.Tr "packages.published_by_in" $timeStr .PackageDescriptor.Creator.HomeLink (.PackageDescriptor.Creator.GetDisplayName | Escape) .PackageDescriptor.Repository.Link (.PackageDescriptor.Repository.FullName | Escape) | Safe}}
{{else}}
Expand Down Expand Up @@ -44,7 +44,7 @@
{{if .HasRepositoryAccess}}
<div class="item">{{svg "octicon-repo" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Repository.Link}}">{{.PackageDescriptor.Repository.FullName}}</a></div>
{{end}}
<div class="item">{{svg "octicon-calendar" 16 "gt-mr-3"}} {{TimeSinceUnix .PackageDescriptor.Version.CreatedUnix $.locale}}</div>
<div class="item">{{svg "octicon-calendar" 16 "gt-mr-3"}} {{TimeSinceUnix $.Context .PackageDescriptor.Version.CreatedUnix $.locale}}</div>
<div class="item">{{svg "octicon-download" 16 "gt-mr-3"}} {{.PackageDescriptor.Version.DownloadCount}}</div>
{{template "package/metadata/cargo" .}}
{{template "package/metadata/chef" .}}
Expand Down
2 changes: 1 addition & 1 deletion templates/projects/list.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
<li class="item">
{{svg .IconName}} <a href="{{.Link}}">{{.Title}}</a>
<div class="meta">
{{$closedDate:= TimeSinceUnix .ClosedDateUnix $.locale}}
{{$closedDate:= TimeSinceUnix $.Context .ClosedDateUnix $.locale}}
{{if .IsClosed}}
{{svg "octicon-clock"}} {{$.locale.Tr "repo.milestones.closed" $closedDate | Safe}}
{{end}}
Expand Down
2 changes: 1 addition & 1 deletion templates/projects/view.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@
<div class="meta gt-my-2">
<span class="text light grey">
{{.Repo.FullName}}#{{.Index}}
{{$timeStr := TimeSinceUnix .GetLastEventTimestamp $.locale}}
{{$timeStr := TimeSinceUnix $.Context .GetLastEventTimestamp $.locale}}
{{if .OriginalAuthor}}
{{$.locale.Tr .GetLastEventLabelFake $timeStr (.OriginalAuthor|Escape) | Safe}}
{{else if gt .Poster.ID 0}}
Expand Down
2 changes: 1 addition & 1 deletion templates/repo/actions/runs_list.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
</div>
</div>
<div class="issue-item-right">
<div>{{TimeSinceUnix .Updated $.locale}}</div>
<div>{{TimeSinceUnix $.Context .Updated $.locale}}</div>
<div>{{.Duration}}</div>
</div>
</li>
Expand Down
Loading