mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 02:46:04 +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. | ;; Change the sort type of the explore pages. | ||||||
| ;; Default is "recentupdate", but you also have "alphabetically", "reverselastlogin", "newest", "oldest". | ;; Default is "recentupdate", but you also have "alphabetically", "reverselastlogin", "newest", "oldest". | ||||||
| ;EXPLORE_PAGING_DEFAULT_SORT = recentupdate | ;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. | - `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). |     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" | - `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`) | ### UI - Admin (`ui.admin`) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ import ( | |||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/modules/container" | 	"code.gitea.io/gitea/modules/container" | ||||||
|  | 	"code.gitea.io/gitea/modules/log" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // UI settings | // UI settings | ||||||
| @@ -34,6 +35,7 @@ var UI = struct { | |||||||
| 	SearchRepoDescription   bool | 	SearchRepoDescription   bool | ||||||
| 	OnlyShowRelevantRepos   bool | 	OnlyShowRelevantRepos   bool | ||||||
| 	ExploreDefaultSort      string `ini:"EXPLORE_PAGING_DEFAULT_SORT"` | 	ExploreDefaultSort      string `ini:"EXPLORE_PAGING_DEFAULT_SORT"` | ||||||
|  | 	PreferredTimestampTense string | ||||||
|  |  | ||||||
| 	AmbiguousUnicodeDetection bool | 	AmbiguousUnicodeDetection bool | ||||||
|  |  | ||||||
| @@ -84,6 +86,7 @@ var UI = struct { | |||||||
| 	Reactions:               []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`}, | 	Reactions:               []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`}, | ||||||
| 	CustomEmojis:            []string{`git`, `gitea`, `codeberg`, `gitlab`, `github`, `gogs`}, | 	CustomEmojis:            []string{`git`, `gitea`, `codeberg`, `gitlab`, `github`, `gogs`}, | ||||||
| 	CustomEmojisMap:         map[string]string{"git": ":git:", "gitea": ":gitea:", "codeberg": ":codeberg:", "gitlab": ":gitlab:", "github": ":github:", "gogs": ":gogs:"}, | 	CustomEmojisMap:         map[string]string{"git": ":git:", "gitea": ":gitea:", "codeberg": ":codeberg:", "gitlab": ":gitlab:", "github": ":github:", "gogs": ":gogs:"}, | ||||||
|  | 	PreferredTimestampTense: "mixed", | ||||||
|  |  | ||||||
| 	AmbiguousUnicodeDetection: true, | 	AmbiguousUnicodeDetection: true, | ||||||
|  |  | ||||||
| @@ -142,6 +145,10 @@ func loadUIFrom(rootCfg ConfigProvider) { | |||||||
| 	UI.DefaultShowFullName = sec.Key("DEFAULT_SHOW_FULL_NAME").MustBool(false) | 	UI.DefaultShowFullName = sec.Key("DEFAULT_SHOW_FULL_NAME").MustBool(false) | ||||||
| 	UI.SearchRepoDescription = sec.Key("SEARCH_REPO_DESCRIPTION").MustBool(true) | 	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, | 	// 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. | 	// 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) | 	UI.OnlyShowRelevantRepos = sec.Key("ONLY_SHOW_RELEVANT_REPOS").MustBool(false) | ||||||
|   | |||||||
| @@ -7,11 +7,12 @@ import ( | |||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"html" | 	"html" | ||||||
| 	"html/template" | 	"html/template" | ||||||
|  | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // DateTime renders an absolute time HTML element by datetime. | // 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 { | 	if p, ok := datetime.(*time.Time); ok { | ||||||
| 		datetime = *p | 		datetime = *p | ||||||
| 	} | 	} | ||||||
| @@ -48,13 +49,15 @@ func DateTime(format string, datetime any) template.HTML { | |||||||
| 		panic(fmt.Sprintf("Unsupported time type %T", datetime)) | 		panic(fmt.Sprintf("Unsupported time type %T", datetime)) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	extraAttrs := strings.Join(attrs, " ") | ||||||
|  |  | ||||||
| 	switch format { | 	switch format { | ||||||
| 	case "short": | 	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": | 	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": | 	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)) | 	panic(fmt.Sprintf("Unsupported format %s", format)) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/translation" | 	"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 | // TimeSince renders relative time HTML given a time.Time | ||||||
| func TimeSince(then time.Time, lang translation.Locale) template.HTML { | 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) | 	return timeSinceUnix(then, time.Now(), lang) | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -247,6 +247,7 @@ export default { | |||||||
|             <div class="gt-ellipsis text light-2"> |             <div class="gt-ellipsis text light-2"> | ||||||
|               {{ commit.committer_or_author_name }} |               {{ commit.committer_or_author_name }} | ||||||
|               <span class="text right"> |               <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> |                 <relative-time class="time-since" prefix="" :datetime="commit.time" data-tooltip-content data-tooltip-interactive="true">{{ commit.time }}</relative-time> | ||||||
|               </span> |               </span> | ||||||
|             </div> |             </div> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user