mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 19:06:18 +01:00 
			
		
		
		
	Add global setting how timestamps should be rendered (#28657)
- Resolves https://github.com/go-gitea/gitea/issues/22493 - Related to https://github.com/go-gitea/gitea/issues/4520 Some admins prefer all timestamps to display the full date instead of relative time. They can do that now by setting ```ini [ui] PREFERRED_TIMESTAMP_TENSE = absolute ``` This setting is set to `mixed` by default, allowing dates to render as "5 hours ago". Here are some screenshots of the UI with this setting set to `absolute`:    --------- Signed-off-by: Yarden Shoham <git@yardenshoham.com> Co-authored-by: delvh <dev.lh@web.de>
This commit is contained in:
		| @@ -1244,6 +1244,10 @@ LEVEL = Info | ||||
| ;; Change the sort type of the explore pages. | ||||
| ;; Default is "recentupdate", but you also have "alphabetically", "reverselastlogin", "newest", "oldest". | ||||
| ;EXPLORE_PAGING_DEFAULT_SORT = recentupdate | ||||
| ;; | ||||
| ;; The tense all timestamps should be rendered in. Possible values are `absolute` time (i.e. 1970-01-01, 11:59) and `mixed`. | ||||
| ;; `mixed` means most timestamps are rendered in relative time (i.e. 2 days ago). | ||||
| ;PREFERRED_TIMESTAMP_TENSE = mixed | ||||
|  | ||||
| ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||||
| ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||||
|   | ||||
| @@ -231,6 +231,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a | ||||
| - `ONLY_SHOW_RELEVANT_REPOS`: **false**: Whether to only show relevant repos on the explore page when no keyword is specified and default sorting is used. | ||||
|     A repo is considered irrelevant if it's a fork or if it has no metadata (no description, no icon, no topic). | ||||
| - `EXPLORE_PAGING_DEFAULT_SORT`: **recentupdate**: Change the sort type of the explore pages. Valid values are "recentupdate", "alphabetically", "reverselastlogin", "newest" and "oldest" | ||||
| - `PREFERRED_TIMESTAMP_TENSE`: **mixed**: The tense all timestamps should be rendered in. Possible values are `absolute` time (i.e. 1970-01-01, 11:59) and `mixed`. `mixed` means most timestamps are rendered in relative time (i.e. 2 days ago). | ||||
|  | ||||
| ### UI - Admin (`ui.admin`) | ||||
|  | ||||
|   | ||||
| @@ -7,33 +7,35 @@ import ( | ||||
| 	"time" | ||||
|  | ||||
| 	"code.gitea.io/gitea/modules/container" | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
| ) | ||||
|  | ||||
| // UI settings | ||||
| var UI = struct { | ||||
| 	ExplorePagingNum      int | ||||
| 	SitemapPagingNum      int | ||||
| 	IssuePagingNum        int | ||||
| 	RepoSearchPagingNum   int | ||||
| 	MembersPagingNum      int | ||||
| 	FeedMaxCommitNum      int | ||||
| 	FeedPagingNum         int | ||||
| 	PackagesPagingNum     int | ||||
| 	GraphMaxCommitNum     int | ||||
| 	CodeCommentLines      int | ||||
| 	ReactionMaxUserNum    int | ||||
| 	MaxDisplayFileSize    int64 | ||||
| 	ShowUserEmail         bool | ||||
| 	DefaultShowFullName   bool | ||||
| 	DefaultTheme          string | ||||
| 	Themes                []string | ||||
| 	Reactions             []string | ||||
| 	ReactionsLookup       container.Set[string] `ini:"-"` | ||||
| 	CustomEmojis          []string | ||||
| 	CustomEmojisMap       map[string]string `ini:"-"` | ||||
| 	SearchRepoDescription bool | ||||
| 	OnlyShowRelevantRepos bool | ||||
| 	ExploreDefaultSort    string `ini:"EXPLORE_PAGING_DEFAULT_SORT"` | ||||
| 	ExplorePagingNum        int | ||||
| 	SitemapPagingNum        int | ||||
| 	IssuePagingNum          int | ||||
| 	RepoSearchPagingNum     int | ||||
| 	MembersPagingNum        int | ||||
| 	FeedMaxCommitNum        int | ||||
| 	FeedPagingNum           int | ||||
| 	PackagesPagingNum       int | ||||
| 	GraphMaxCommitNum       int | ||||
| 	CodeCommentLines        int | ||||
| 	ReactionMaxUserNum      int | ||||
| 	MaxDisplayFileSize      int64 | ||||
| 	ShowUserEmail           bool | ||||
| 	DefaultShowFullName     bool | ||||
| 	DefaultTheme            string | ||||
| 	Themes                  []string | ||||
| 	Reactions               []string | ||||
| 	ReactionsLookup         container.Set[string] `ini:"-"` | ||||
| 	CustomEmojis            []string | ||||
| 	CustomEmojisMap         map[string]string `ini:"-"` | ||||
| 	SearchRepoDescription   bool | ||||
| 	OnlyShowRelevantRepos   bool | ||||
| 	ExploreDefaultSort      string `ini:"EXPLORE_PAGING_DEFAULT_SORT"` | ||||
| 	PreferredTimestampTense string | ||||
|  | ||||
| 	AmbiguousUnicodeDetection bool | ||||
|  | ||||
| @@ -67,23 +69,24 @@ var UI = struct { | ||||
| 		Keywords    string | ||||
| 	} `ini:"ui.meta"` | ||||
| }{ | ||||
| 	ExplorePagingNum:    20, | ||||
| 	SitemapPagingNum:    20, | ||||
| 	IssuePagingNum:      20, | ||||
| 	RepoSearchPagingNum: 20, | ||||
| 	MembersPagingNum:    20, | ||||
| 	FeedMaxCommitNum:    5, | ||||
| 	FeedPagingNum:       20, | ||||
| 	PackagesPagingNum:   20, | ||||
| 	GraphMaxCommitNum:   100, | ||||
| 	CodeCommentLines:    4, | ||||
| 	ReactionMaxUserNum:  10, | ||||
| 	MaxDisplayFileSize:  8388608, | ||||
| 	DefaultTheme:        `gitea-auto`, | ||||
| 	Themes:              []string{`gitea-auto`, `gitea-light`, `gitea-dark`}, | ||||
| 	Reactions:           []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`}, | ||||
| 	CustomEmojis:        []string{`git`, `gitea`, `codeberg`, `gitlab`, `github`, `gogs`}, | ||||
| 	CustomEmojisMap:     map[string]string{"git": ":git:", "gitea": ":gitea:", "codeberg": ":codeberg:", "gitlab": ":gitlab:", "github": ":github:", "gogs": ":gogs:"}, | ||||
| 	ExplorePagingNum:        20, | ||||
| 	SitemapPagingNum:        20, | ||||
| 	IssuePagingNum:          20, | ||||
| 	RepoSearchPagingNum:     20, | ||||
| 	MembersPagingNum:        20, | ||||
| 	FeedMaxCommitNum:        5, | ||||
| 	FeedPagingNum:           20, | ||||
| 	PackagesPagingNum:       20, | ||||
| 	GraphMaxCommitNum:       100, | ||||
| 	CodeCommentLines:        4, | ||||
| 	ReactionMaxUserNum:      10, | ||||
| 	MaxDisplayFileSize:      8388608, | ||||
| 	DefaultTheme:            `gitea-auto`, | ||||
| 	Themes:                  []string{`gitea-auto`, `gitea-light`, `gitea-dark`}, | ||||
| 	Reactions:               []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`}, | ||||
| 	CustomEmojis:            []string{`git`, `gitea`, `codeberg`, `gitlab`, `github`, `gogs`}, | ||||
| 	CustomEmojisMap:         map[string]string{"git": ":git:", "gitea": ":gitea:", "codeberg": ":codeberg:", "gitlab": ":gitlab:", "github": ":github:", "gogs": ":gogs:"}, | ||||
| 	PreferredTimestampTense: "mixed", | ||||
|  | ||||
| 	AmbiguousUnicodeDetection: true, | ||||
|  | ||||
| @@ -142,6 +145,10 @@ func loadUIFrom(rootCfg ConfigProvider) { | ||||
| 	UI.DefaultShowFullName = sec.Key("DEFAULT_SHOW_FULL_NAME").MustBool(false) | ||||
| 	UI.SearchRepoDescription = sec.Key("SEARCH_REPO_DESCRIPTION").MustBool(true) | ||||
|  | ||||
| 	if UI.PreferredTimestampTense != "mixed" && UI.PreferredTimestampTense != "absolute" { | ||||
| 		log.Fatal("ui.PREFERRED_TIMESTAMP_TENSE must be either 'mixed' or 'absolute'") | ||||
| 	} | ||||
|  | ||||
| 	// OnlyShowRelevantRepos=false is important for many private/enterprise instances, | ||||
| 	// because many private repositories do not have "description/topic", users just want to search by their names. | ||||
| 	UI.OnlyShowRelevantRepos = sec.Key("ONLY_SHOW_RELEVANT_REPOS").MustBool(false) | ||||
|   | ||||
| @@ -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 | ||||
| 	} | ||||
| @@ -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)) | ||||
| } | ||||
|   | ||||
| @@ -29,17 +29,17 @@ func TestDateTime(t *testing.T) { | ||||
| 	assert.EqualValues(t, "-", DateTime("short", TimeStamp(0))) | ||||
|  | ||||
| 	actual := DateTime("short", "invalid") | ||||
| 	assert.EqualValues(t, `<relative-time format="datetime" year="numeric" month="short" day="numeric" weekday="" datetime="invalid">invalid</relative-time>`, actual) | ||||
| 	assert.EqualValues(t, `<relative-time  format="datetime" year="numeric" month="short" day="numeric" weekday="" datetime="invalid">invalid</relative-time>`, actual) | ||||
|  | ||||
| 	actual = DateTime("short", refTimeStr) | ||||
| 	assert.EqualValues(t, `<relative-time format="datetime" year="numeric" month="short" day="numeric" weekday="" datetime="2018-01-01T00:00:00Z">2018-01-01T00:00:00Z</relative-time>`, actual) | ||||
| 	assert.EqualValues(t, `<relative-time  format="datetime" year="numeric" month="short" day="numeric" weekday="" datetime="2018-01-01T00:00:00Z">2018-01-01T00:00:00Z</relative-time>`, actual) | ||||
|  | ||||
| 	actual = DateTime("short", refTime) | ||||
| 	assert.EqualValues(t, `<relative-time format="datetime" year="numeric" month="short" day="numeric" weekday="" datetime="2018-01-01T00:00:00Z">2018-01-01</relative-time>`, actual) | ||||
| 	assert.EqualValues(t, `<relative-time  format="datetime" year="numeric" month="short" day="numeric" weekday="" datetime="2018-01-01T00:00:00Z">2018-01-01</relative-time>`, actual) | ||||
|  | ||||
| 	actual = DateTime("short", refTimeStamp) | ||||
| 	assert.EqualValues(t, `<relative-time format="datetime" year="numeric" month="short" day="numeric" weekday="" datetime="2017-12-31T19:00:00-05:00">2017-12-31</relative-time>`, actual) | ||||
| 	assert.EqualValues(t, `<relative-time  format="datetime" year="numeric" month="short" day="numeric" weekday="" datetime="2017-12-31T19:00:00-05:00">2017-12-31</relative-time>`, actual) | ||||
|  | ||||
| 	actual = DateTime("full", refTimeStamp) | ||||
| 	assert.EqualValues(t, `<relative-time format="datetime" weekday="" year="numeric" month="short" day="numeric" hour="numeric" minute="numeric" second="numeric" datetime="2017-12-31T19:00:00-05:00">2017-12-31 19:00:00 -05:00</relative-time>`, actual) | ||||
| 	assert.EqualValues(t, `<relative-time  format="datetime" weekday="" year="numeric" month="short" day="numeric" hour="numeric" minute="numeric" second="numeric" datetime="2017-12-31T19:00:00-05:00">2017-12-31 19:00:00 -05:00</relative-time>`, actual) | ||||
| } | ||||
|   | ||||
| @@ -9,6 +9,7 @@ import ( | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 	"code.gitea.io/gitea/modules/translation" | ||||
| ) | ||||
|  | ||||
| @@ -132,6 +133,9 @@ 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 { | ||||
| 	if setting.UI.PreferredTimestampTense == "absolute" { | ||||
| 		return DateTime("full", then, `class="time-since"`) | ||||
| 	} | ||||
| 	return timeSinceUnix(then, time.Now(), lang) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -247,6 +247,7 @@ export default { | ||||
|             <div class="gt-ellipsis text light-2"> | ||||
|               {{ commit.committer_or_author_name }} | ||||
|               <span class="text right"> | ||||
|                 <!-- TODO: make this respect the PreferredTimestampTense setting --> | ||||
|                 <relative-time class="time-since" prefix="" :datetime="commit.time" data-tooltip-content data-tooltip-interactive="true">{{ commit.time }}</relative-time> | ||||
|               </span> | ||||
|             </div> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user