Add slogan config (#3752)

This is a PR for #3616

Currently added a new optional config `SLOGAN`  in ini file. When this config is set title page is modified in APP_NAME [ - SLOGAN]

Example in image below

![Selezione_075.png](/attachments/7a72171e-e730-4e57-8c97-ffc94258e00f)

Add the new config value in the admin settings page (readonly)

![Screenshot 2024-05-13 at 18-04-13 My Forgejo.png](/attachments/dad00fc2-29fa-4371-a7b9-5233eadeac13)

## TODO

* [x] Add the possibility to add the `SLOGAN` config from the installation form
* [ ] Update https://forgejo.org/docs/next/admin/config-cheat-sheet

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/3752
Reviewed-by: 0ko <0ko@noreply.codeberg.org>
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Co-authored-by: mirko <mirko.perillo@gmail.com>
Co-committed-by: mirko <mirko.perillo@gmail.com>
This commit is contained in:
mirko 2024-06-07 17:12:48 +00:00 committed by Earl Warren
parent dedcd6c647
commit f015846c11
19 changed files with 138 additions and 13 deletions

View file

@ -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) ;; Copy required sections to your own app.ini (default is custom/conf/app.ini)
;; and modify as needed. ;; and modify as needed.
;; Do not copy the whole file as-is, as it contains some invalid sections for illustrative purposes. ;; 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. ;; 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 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 will automatically detect the current user - but you can set it here change it if you run locally
RUN_USER = ; git RUN_USER = ; git

View file

@ -45,6 +45,14 @@ var (
// AppName is the Application name, used in the page title. // AppName is the Application name, used in the page title.
// It maps to ini:"APP_NAME" // It maps to ini:"APP_NAME"
AppName string 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 // AppURL is the Application ROOT_URL. It always has a '/' suffix
// It maps to ini:"ROOT_URL" // It maps to ini:"ROOT_URL"
AppURL string AppURL string
@ -164,10 +172,21 @@ func MakeAbsoluteAssetURL(appURL, staticURLPrefix string) string {
return strings.TrimSuffix(staticURLPrefix, "/") 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) { func loadServerFrom(rootCfg ConfigProvider) {
sec := rootCfg.Section("server") sec := rootCfg.Section("server")
AppName = rootCfg.Section("").Key("APP_NAME").MustString("Forgejo: Beyond coding. We Forge.") 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") Domain = sec.Key("DOMAIN").MustString("localhost")
HTTPAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0") HTTPAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0")
HTTPPort = sec.Key("HTTP_PORT").MustString("3000") HTTPPort = sec.Key("HTTP_PORT").MustString("3000")

View file

@ -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)
}

View file

@ -79,6 +79,12 @@ func NewFuncMap() template.FuncMap {
"AppName": func() string { "AppName": func() string {
return setting.AppName return setting.AppName
}, },
"AppSlogan": func() string {
return setting.AppSlogan
},
"AppDisplayName": func() string {
return setting.AppDisplayName
},
"AppSubUrl": func() string { "AppSubUrl": func() string {
return setting.AppSubURL return setting.AppSubURL
}, },

View file

@ -28,6 +28,12 @@ func mailSubjectTextFuncMap() texttmpl.FuncMap {
"AppName": func() string { "AppName": func() string {
return setting.AppName return setting.AppName
}, },
"AppSlogan": func() string {
return setting.AppSlogan
},
"AppDisplayName": func() string {
return setting.AppDisplayName
},
"AppDomain": func() string { // documented in mail-templates.md "AppDomain": func() string { // documented in mail-templates.md
return setting.Domain return setting.Domain
}, },

View file

@ -267,6 +267,8 @@ err_admin_name_is_invalid = Administrator Username is invalid
general_title = General settings general_title = General settings
app_name = Instance title app_name = Instance title
app_name_helper = You can enter your company name here. 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 = Repository root path
repo_path_helper = Remote Git repositories will be saved to this directory. repo_path_helper = Remote Git repositories will be saved to this directory.
lfs_path = Git LFS root path 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.server_config = Server configuration
config.app_name = Instance title config.app_name = Instance title
config.app_slogan = Instance slogan
config.app_ver = Forgejo version config.app_ver = Forgejo version
config.app_url = Base URL config.app_url = Base URL
config.custom_conf = Configuration file path config.custom_conf = Configuration file path

View file

@ -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.

View file

@ -115,7 +115,8 @@ func Install(ctx *context.Context) {
ctx.Data["CurDbType"] = curDBType ctx.Data["CurDbType"] = curDBType
// Application general settings // Application general settings
form.AppName = setting.AppName form.AppName = "Forgejo"
form.AppSlogan = "Beyond coding. We Forge."
form.RepoRootPath = setting.RepoRootPath form.RepoRootPath = setting.RepoRootPath
form.LFSRootPath = setting.LFS.Storage.Path 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_NAME").SetValue(form.AppName)
cfg.Section("").Key("APP_SLOGAN").SetValue(form.AppSlogan)
cfg.Section("").Key("RUN_USER").SetValue(form.RunUser) cfg.Section("").Key("RUN_USER").SetValue(form.RunUser)
cfg.Section("").Key("WORK_PATH").SetValue(setting.AppWorkPath) cfg.Section("").Key("WORK_PATH").SetValue(setting.AppWorkPath)
cfg.Section("").Key("RUN_MODE").SetValue("prod") cfg.Section("").Key("RUN_MODE").SetValue("prod")

View file

@ -31,6 +31,7 @@ type InstallForm struct {
DbSchema string DbSchema string
AppName string `binding:"Required" locale:"install.app_name"` AppName string `binding:"Required" locale:"install.app_name"`
AppSlogan string
RepoRootPath string `binding:"Required"` RepoRootPath string `binding:"Required"`
LFSRootPath string LFSRootPath string
RunUser string `binding:"Required"` RunUser string `binding:"Required"`

View file

@ -7,6 +7,8 @@
<dl class="admin-dl-horizontal"> <dl class="admin-dl-horizontal">
<dt>{{ctx.Locale.Tr "admin.config.app_name"}}</dt> <dt>{{ctx.Locale.Tr "admin.config.app_name"}}</dt>
<dd>{{AppName}}</dd> <dd>{{AppName}}</dd>
<dt>{{ctx.Locale.Tr "admin.config.app_slogan"}}</dt>
<dd>{{AppSlogan}}</dd>
<dt>{{ctx.Locale.Tr "admin.config.app_ver"}}</dt> <dt>{{ctx.Locale.Tr "admin.config.app_ver"}}</dt>
<dd>{{AppVer}}{{.AppBuiltWith}}</dd> <dd>{{AppVer}}{{.AppBuiltWith}}</dd>
<dt>{{ctx.Locale.Tr "admin.config.custom_conf"}}</dt> <dt>{{ctx.Locale.Tr "admin.config.custom_conf"}}</dt>

View file

@ -3,7 +3,7 @@
<head> <head>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
{{/* Display `- .Repository.FullName` only if `.Title` does not already start with that. */}} {{/* 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}} {{if .ManifestData}}<link rel="manifest" href="data:{{.ManifestData}}">{{end}}
<meta name="author" content="{{if .Repository}}{{.Owner.Name}}{{else}}{{MetaAuthor}}{{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}}"> <meta name="description" content="{{if .Repository}}{{.Repository.Name}}{{if .Repository.Description}} - {{.Repository.Description}}{{end}}{{else}}{{MetaDescription}}{{end}}">

View file

@ -38,10 +38,10 @@
<meta property="og:image" content="{{.Repository.Owner.AvatarLink ctx}}"> <meta property="og:image" content="{{.Repository.Owner.AvatarLink ctx}}">
{{end}} {{end}}
{{else}} {{else}}
<meta property="og:title" content="{{AppName}}"> <meta property="og:title" content="{{AppDisplayName}}">
<meta property="og:type" content="website"> <meta property="og:type" content="website">
<meta property="og:image" content="{{AssetUrlPrefix}}/img/logo.png"> <meta property="og:image" content="{{AssetUrlPrefix}}/img/logo.png">
<meta property="og:url" content="{{AppUrl}}"> <meta property="og:url" content="{{AppUrl}}">
<meta property="og:description" content="{{MetaDescription}}"> <meta property="og:description" content="{{MetaDescription}}">
{{end}} {{end}}
<meta property="og:site_name" content="{{AppName}}"> <meta property="og:site_name" content="{{AppDisplayName}}">

View file

@ -5,7 +5,7 @@
<img class="logo" width="220" height="220" src="{{AssetUrlPrefix}}/img/logo.svg" alt="{{ctx.Locale.Tr "logo"}}"> <img class="logo" width="220" height="220" src="{{AssetUrlPrefix}}/img/logo.svg" alt="{{ctx.Locale.Tr "logo"}}">
<div class="hero"> <div class="hero">
<h1 class="ui icon header title"> <h1 class="ui icon header title">
{{AppName}} {{AppDisplayName}}
</h1> </h1>
<h2>{{ctx.Locale.Tr "startpage.app_desc"}}</h2> <h2>{{ctx.Locale.Tr "startpage.app_desc"}}</h2>
</div> </div>

View file

@ -107,6 +107,11 @@
<input id="app_name" name="app_name" value="{{.app_name}}" required> <input id="app_name" name="app_name" value="{{.app_name}}" required>
<span class="help">{{ctx.Locale.Tr "install.app_name_helper"}}</span> <span class="help">{{ctx.Locale.Tr "install.app_name_helper"}}</span>
</div> </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}}"> <div class="inline required field {{if .Err_RepoRootPath}}error{{end}}">
<label for="repo_root_path">{{ctx.Locale.Tr "install.repo_path"}}</label> <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> <input id="repo_root_path" name="repo_root_path" value="{{.repo_root_path}}" required>

View file

@ -9,7 +9,7 @@
<html lang="{{ctx.Locale.Lang}}" data-theme="{{ThemeName .SignedUser}}"> <html lang="{{ctx.Locale.Lang}}" data-theme="{{ThemeName .SignedUser}}">
<head> <head>
<meta name="viewport" content="width=device-width, initial-scale=1"> <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="icon" href="{{AssetUrlPrefix}}/img/favicon.svg" type="image/svg+xml">
<link rel="alternate icon" href="{{AssetUrlPrefix}}/img/favicon.png" type="image/png"> <link rel="alternate icon" href="{{AssetUrlPrefix}}/img/favicon.png" type="image/png">
{{template "base/head_style" .}} {{template "base/head_style" .}}

View file

@ -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)
}

View file

@ -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 RUN_MODE = prod
[database] [database]

View file

@ -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 RUN_MODE = prod
[database] [database]

View file

@ -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 RUN_MODE = prod
[database] [database]