diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index d825e47a76..451742ac06 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1,10 +1,10 @@ -; This file lists the default values used by Gitea +; This file lists the default values used by Forgejo ;; Copy required sections to your own app.ini (default is custom/conf/app.ini) ;; and modify as needed. ;; Do not copy the whole file as-is, as it contains some invalid sections for illustrative purposes. ;; If you don't know what a setting is you should not set it. ;; -;; see https://docs.gitea.com/administration/config-cheat-sheet for additional documentation. +;; see https://forgejo.org/docs/next/admin/config-cheat-sheet for additional documentation. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -41,7 +41,14 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; App name that shows in every page title -APP_NAME = ; Gitea: Git with a cup of tea +APP_NAME = ; Forgejo: Beyond coding. We Forge. +;; +;; APP_SLOGAN shows a slogan near the App name in every page title. +;APP_SLOGAN = +;; +;; APP_DISPLAY_NAME_FORMAT defines how the AppDisplayName should be presented +;; It is used only if APP_SLOGAN is set. +;APP_DISPLAY_NAME_FORMAT = {APP_NAME}: {APP_SLOGAN} ;; ;; RUN_USER will automatically detect the current user - but you can set it here change it if you run locally RUN_USER = ; git diff --git a/modules/setting/server.go b/modules/setting/server.go index c20dd1949d..5cc33f6fc4 100644 --- a/modules/setting/server.go +++ b/modules/setting/server.go @@ -45,6 +45,14 @@ var ( // AppName is the Application name, used in the page title. // It maps to ini:"APP_NAME" AppName string + // AppSlogan is the Application slogan. + // It maps to ini:"APP_SLOGAN" + AppSlogan string + // AppDisplayNameFormat defines how the AppDisplayName should be presented + // It maps to ini:"APP_DISPLAY_NAME_FORMAT" + AppDisplayNameFormat string + // AppDisplayName is the display name for the application, defined following AppDisplayNameFormat + AppDisplayName string // AppURL is the Application ROOT_URL. It always has a '/' suffix // It maps to ini:"ROOT_URL" AppURL string @@ -164,10 +172,21 @@ func MakeAbsoluteAssetURL(appURL, staticURLPrefix string) string { return strings.TrimSuffix(staticURLPrefix, "/") } +func generateDisplayName() string { + appDisplayName := AppName + if AppSlogan != "" { + appDisplayName = strings.Replace(AppDisplayNameFormat, "{APP_NAME}", AppName, 1) + appDisplayName = strings.Replace(appDisplayName, "{APP_SLOGAN}", AppSlogan, 1) + } + return appDisplayName +} + func loadServerFrom(rootCfg ConfigProvider) { sec := rootCfg.Section("server") AppName = rootCfg.Section("").Key("APP_NAME").MustString("Forgejo: Beyond coding. We Forge.") - + AppSlogan = rootCfg.Section("").Key("APP_SLOGAN").MustString("") + AppDisplayNameFormat = rootCfg.Section("").Key("APP_DISPLAY_NAME_FORMAT").MustString("{APP_NAME}: {APP_SLOGAN}") + AppDisplayName = generateDisplayName() Domain = sec.Key("DOMAIN").MustString("localhost") HTTPAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0") HTTPPort = sec.Key("HTTP_PORT").MustString("3000") diff --git a/modules/setting/server_test.go b/modules/setting/server_test.go new file mode 100644 index 0000000000..8db8168854 --- /dev/null +++ b/modules/setting/server_test.go @@ -0,0 +1,36 @@ +// Copyright 2024 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "testing" + + "code.gitea.io/gitea/modules/test" + + "github.com/stretchr/testify/assert" +) + +func TestDisplayNameDefault(t *testing.T) { + defer test.MockVariableValue(&AppName, "Forgejo")() + defer test.MockVariableValue(&AppSlogan, "Beyond coding. We Forge.")() + defer test.MockVariableValue(&AppDisplayNameFormat, "{APP_NAME}: {APP_SLOGAN}")() + displayName := generateDisplayName() + assert.Equal(t, "Forgejo: Beyond coding. We Forge.", displayName) +} + +func TestDisplayNameEmptySlogan(t *testing.T) { + defer test.MockVariableValue(&AppName, "Forgejo")() + defer test.MockVariableValue(&AppSlogan, "")() + defer test.MockVariableValue(&AppDisplayNameFormat, "{APP_NAME}: {APP_SLOGAN}")() + displayName := generateDisplayName() + assert.Equal(t, "Forgejo", displayName) +} + +func TestDisplayNameCustomFormat(t *testing.T) { + defer test.MockVariableValue(&AppName, "Forgejo")() + defer test.MockVariableValue(&AppSlogan, "Beyond coding. We Forge.")() + defer test.MockVariableValue(&AppDisplayNameFormat, "{APP_NAME} - {APP_SLOGAN}")() + displayName := generateDisplayName() + assert.Equal(t, "Forgejo - Beyond coding. We Forge.", displayName) +} diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 4dc1f1938c..f1ae1c8bb1 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -79,6 +79,12 @@ func NewFuncMap() template.FuncMap { "AppName": func() string { return setting.AppName }, + "AppSlogan": func() string { + return setting.AppSlogan + }, + "AppDisplayName": func() string { + return setting.AppDisplayName + }, "AppSubUrl": func() string { return setting.AppSubURL }, diff --git a/modules/templates/mailer.go b/modules/templates/mailer.go index 7c97e1ea89..ee79755dbb 100644 --- a/modules/templates/mailer.go +++ b/modules/templates/mailer.go @@ -28,6 +28,12 @@ func mailSubjectTextFuncMap() texttmpl.FuncMap { "AppName": func() string { return setting.AppName }, + "AppSlogan": func() string { + return setting.AppSlogan + }, + "AppDisplayName": func() string { + return setting.AppDisplayName + }, "AppDomain": func() string { // documented in mail-templates.md return setting.Domain }, diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 13adb33a55..39f7886c54 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -267,6 +267,8 @@ err_admin_name_is_invalid = Administrator Username is invalid general_title = General settings app_name = Instance title app_name_helper = You can enter your company name here. +app_slogan = Instance slogan +app_slogan_helper = Enter your instance slogan here. Leave empty to disable. repo_path = Repository root path repo_path_helper = Remote Git repositories will be saved to this directory. lfs_path = Git LFS root path @@ -3207,6 +3209,7 @@ auths.invalid_openIdConnectAutoDiscoveryURL = Invalid Auto Discovery URL (this m config.server_config = Server configuration config.app_name = Instance title +config.app_slogan = Instance slogan config.app_ver = Forgejo version config.app_url = Base URL config.custom_conf = Configuration file path diff --git a/release-notes/8.0.0/3616.md b/release-notes/8.0.0/3616.md new file mode 100644 index 0000000000..fdeb470cf0 --- /dev/null +++ b/release-notes/8.0.0/3616.md @@ -0,0 +1,10 @@ +There are a couple of new configs to define the name of the instance. +The more important is `APP_SLOGAN`. It permits to configure a slogan for the site and it is optional. +The other is `APP_DISPLAY_NAME_FORMAT` and permits to customize the aspect of the full display name for the instance used in some parts of the UI as: + +- Title page. +- Homepage head title. +- Open Graph site and title meta tags. + +Its default value is `APP_NAME: APP_SLOGAN`. +The config `APP_DISPLAY_NAME_FORMAT` is used only if `APP_SLOGAN` is set otherwise the full display name shows only `APP_NAME` value. diff --git a/routers/install/install.go b/routers/install/install.go index 8f4fafa6f5..dfd8047d04 100644 --- a/routers/install/install.go +++ b/routers/install/install.go @@ -115,7 +115,8 @@ func Install(ctx *context.Context) { ctx.Data["CurDbType"] = curDBType // Application general settings - form.AppName = setting.AppName + form.AppName = "Forgejo" + form.AppSlogan = "Beyond coding. We Forge." form.RepoRootPath = setting.RepoRootPath form.LFSRootPath = setting.LFS.Storage.Path @@ -383,6 +384,7 @@ func SubmitInstall(ctx *context.Context) { } cfg.Section("").Key("APP_NAME").SetValue(form.AppName) + cfg.Section("").Key("APP_SLOGAN").SetValue(form.AppSlogan) cfg.Section("").Key("RUN_USER").SetValue(form.RunUser) cfg.Section("").Key("WORK_PATH").SetValue(setting.AppWorkPath) cfg.Section("").Key("RUN_MODE").SetValue("prod") diff --git a/services/forms/user_form.go b/services/forms/user_form.go index 0b7bea4638..cc93b27e2a 100644 --- a/services/forms/user_form.go +++ b/services/forms/user_form.go @@ -31,6 +31,7 @@ type InstallForm struct { DbSchema string AppName string `binding:"Required" locale:"install.app_name"` + AppSlogan string RepoRootPath string `binding:"Required"` LFSRootPath string RunUser string `binding:"Required"` diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl index 1e94935a16..d4e5b326fd 100644 --- a/templates/admin/config.tmpl +++ b/templates/admin/config.tmpl @@ -7,6 +7,8 @@ <dl class="admin-dl-horizontal"> <dt>{{ctx.Locale.Tr "admin.config.app_name"}}</dt> <dd>{{AppName}}</dd> + <dt>{{ctx.Locale.Tr "admin.config.app_slogan"}}</dt> + <dd>{{AppSlogan}}</dd> <dt>{{ctx.Locale.Tr "admin.config.app_ver"}}</dt> <dd>{{AppVer}}{{.AppBuiltWith}}</dd> <dt>{{ctx.Locale.Tr "admin.config.custom_conf"}}</dt> diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl index c0caf34d53..7753f49243 100644 --- a/templates/base/head.tmpl +++ b/templates/base/head.tmpl @@ -3,7 +3,7 @@ <head> <meta name="viewport" content="width=device-width, initial-scale=1"> {{/* Display `- .Repository.FullName` only if `.Title` does not already start with that. */}} - <title>{{if .Title}}{{.Title}} - {{end}}{{if and (.Repository.Name) (not (StringUtils.HasPrefix .Title .Repository.FullName))}}{{.Repository.FullName}} - {{end}}{{AppName}}</title> + <title>{{if .Title}}{{.Title}} - {{end}}{{if and (.Repository.Name) (not (StringUtils.HasPrefix .Title .Repository.FullName))}}{{.Repository.FullName}} - {{end}}{{AppDisplayName}}</title> {{if .ManifestData}}<link rel="manifest" href="data:{{.ManifestData}}">{{end}} <meta name="author" content="{{if .Repository}}{{.Owner.Name}}{{else}}{{MetaAuthor}}{{end}}"> <meta name="description" content="{{if .Repository}}{{.Repository.Name}}{{if .Repository.Description}} - {{.Repository.Description}}{{end}}{{else}}{{MetaDescription}}{{end}}"> diff --git a/templates/base/head_opengraph.tmpl b/templates/base/head_opengraph.tmpl index f1f38999d2..c02adabaab 100644 --- a/templates/base/head_opengraph.tmpl +++ b/templates/base/head_opengraph.tmpl @@ -38,10 +38,10 @@ <meta property="og:image" content="{{.Repository.Owner.AvatarLink ctx}}"> {{end}} {{else}} - <meta property="og:title" content="{{AppName}}"> + <meta property="og:title" content="{{AppDisplayName}}"> <meta property="og:type" content="website"> <meta property="og:image" content="{{AssetUrlPrefix}}/img/logo.png"> <meta property="og:url" content="{{AppUrl}}"> <meta property="og:description" content="{{MetaDescription}}"> {{end}} -<meta property="og:site_name" content="{{AppName}}"> +<meta property="og:site_name" content="{{AppDisplayName}}"> diff --git a/templates/home.tmpl b/templates/home.tmpl index e6fd4ef020..23b1feae21 100644 --- a/templates/home.tmpl +++ b/templates/home.tmpl @@ -5,7 +5,7 @@ <img class="logo" width="220" height="220" src="{{AssetUrlPrefix}}/img/logo.svg" alt="{{ctx.Locale.Tr "logo"}}"> <div class="hero"> <h1 class="ui icon header title"> - {{AppName}} + {{AppDisplayName}} </h1> <h2>{{ctx.Locale.Tr "startpage.app_desc"}}</h2> </div> diff --git a/templates/install.tmpl b/templates/install.tmpl index f027b47922..29eb44526a 100644 --- a/templates/install.tmpl +++ b/templates/install.tmpl @@ -107,6 +107,11 @@ <input id="app_name" name="app_name" value="{{.app_name}}" required> <span class="help">{{ctx.Locale.Tr "install.app_name_helper"}}</span> </div> + <div class="inline field"> + <label for="app_slogan">{{ctx.Locale.Tr "install.app_slogan"}}</label> + <input id="app_slogan" name="app_slogan" value="{{.app_slogan}}"> + <span class="help">{{ctx.Locale.Tr "install.app_slogan_helper"}}</span> + </div> <div class="inline required field {{if .Err_RepoRootPath}}error{{end}}"> <label for="repo_root_path">{{ctx.Locale.Tr "install.repo_path"}}</label> <input id="repo_root_path" name="repo_root_path" value="{{.repo_root_path}}" required> diff --git a/templates/status/500.tmpl b/templates/status/500.tmpl index 8e250abe27..e5fdaec0cf 100644 --- a/templates/status/500.tmpl +++ b/templates/status/500.tmpl @@ -9,7 +9,7 @@ <html lang="{{ctx.Locale.Lang}}" data-theme="{{ThemeName .SignedUser}}"> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> - <title>{{ctx.Locale.Tr "error.server_internal"}} - {{AppName}}</title> + <title>{{ctx.Locale.Tr "error.server_internal"}} - {{AppDisplayName}}</title> <link rel="icon" href="{{AssetUrlPrefix}}/img/favicon.svg" type="image/svg+xml"> <link rel="alternate icon" href="{{AssetUrlPrefix}}/img/favicon.png" type="image/png"> {{template "base/head_style" .}} diff --git a/tests/integration/view_test.go b/tests/integration/view_test.go index e77cc382e9..d7225101d7 100644 --- a/tests/integration/view_test.go +++ b/tests/integration/view_test.go @@ -185,3 +185,28 @@ func TestInHistoryButton(t *testing.T) { }) }) } + +func TestTitleDisplayName(t *testing.T) { + session := emptyTestSession(t) + title := GetHTMLTitle(t, session, "/") + assert.Equal(t, "Gitea: Git with a cup of tea", title) +} + +func TestHomeDisplayName(t *testing.T) { + session := emptyTestSession(t) + req := NewRequest(t, "GET", "/") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + assert.Equal(t, "Gitea: Git with a cup of tea", strings.TrimSpace(htmlDoc.Find("h1.title").Text())) +} + +func TestOpenGraphDisplayName(t *testing.T) { + session := emptyTestSession(t) + req := NewRequest(t, "GET", "/") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + ogTitle, _ := htmlDoc.Find("meta[property='og:title']").Attr("content") + assert.Equal(t, "Gitea: Git with a cup of tea", ogTitle) + ogSiteName, _ := htmlDoc.Find("meta[property='og:site_name']").Attr("content") + assert.Equal(t, "Gitea: Git with a cup of tea", ogSiteName) +} diff --git a/tests/mysql.ini.tmpl b/tests/mysql.ini.tmpl index ab2e91ebdb..1bfc797ba5 100644 --- a/tests/mysql.ini.tmpl +++ b/tests/mysql.ini.tmpl @@ -1,4 +1,5 @@ -APP_NAME = Gitea: Git with a cup of tea +APP_NAME = Gitea +APP_SLOGAN = Git with a cup of tea RUN_MODE = prod [database] diff --git a/tests/pgsql.ini.tmpl b/tests/pgsql.ini.tmpl index 300d36eb71..760826280b 100644 --- a/tests/pgsql.ini.tmpl +++ b/tests/pgsql.ini.tmpl @@ -1,4 +1,5 @@ -APP_NAME = Gitea: Git with a cup of tea +APP_NAME = Gitea +APP_SLOGAN = Git with a cup of tea RUN_MODE = prod [database] diff --git a/tests/sqlite.ini.tmpl b/tests/sqlite.ini.tmpl index bc7227fcc9..48ee8d4e4a 100644 --- a/tests/sqlite.ini.tmpl +++ b/tests/sqlite.ini.tmpl @@ -1,4 +1,5 @@ -APP_NAME = Gitea: Git with a cup of tea +APP_NAME = Gitea +APP_SLOGAN = Git with a cup of tea RUN_MODE = prod [database]