diff --git a/modules/templates/helper.go b/modules/templates/helper.go
index f071353bfad..ae7f3636a6d 100644
--- a/modules/templates/helper.go
+++ b/modules/templates/helper.go
@@ -96,9 +96,6 @@ func newFuncMapWebPage() template.FuncMap {
"AllowedReactions": func() []string {
return setting.UI.Reactions
},
- "CustomEmojis": func() map[string]string {
- return setting.UI.CustomEmojisMap
- },
"MetaAuthor": func() string {
return setting.UI.Meta.Author
},
@@ -114,16 +111,6 @@ func newFuncMapWebPage() template.FuncMap {
"DisableWebhooks": func() bool {
return setting.DisableWebhooks
},
- "NotificationSettings": func() map[string]any {
- return map[string]any{
- "MinTimeout": int(setting.UI.Notification.MinTimeout / time.Millisecond),
- "TimeoutStep": int(setting.UI.Notification.TimeoutStep / time.Millisecond),
- "MaxTimeout": int(setting.UI.Notification.MaxTimeout / time.Millisecond),
- }
- },
- "MermaidMaxSourceCharacters": func() int {
- return setting.MermaidMaxSourceCharacters
- },
// -----------------------------------------------------------------
// render
diff --git a/services/context/context.go b/services/context/context.go
index f13d51c55f1..99e40ea72e6 100644
--- a/services/context/context.go
+++ b/services/context/context.go
@@ -46,8 +46,11 @@ type Context struct {
TemplateContext TemplateContext
- Render Render
- PageData map[string]any // data used by JavaScript modules in one page, it's `window.config.pageData`
+ Render Render
+
+ // PageData is used by JavaScript modules, it is `window.config.pageData`.
+ // Deprecated: it was introduced for refactoring some legacy JS code, it should not be used in new code anymore.
+ PageData map[string]any
Cache cache.StringCache
Flash *middleware.Flash
diff --git a/services/context/context_template.go b/services/context/context_template.go
index b4775171eb9..2d990b44add 100644
--- a/services/context/context_template.go
+++ b/services/context/context_template.go
@@ -13,11 +13,11 @@ import (
"time"
user_model "gitea.dev/models/user"
- "gitea.dev/modules/htmlutil"
"gitea.dev/modules/httplib"
"gitea.dev/modules/public"
"gitea.dev/modules/reqctx"
"gitea.dev/modules/setting"
+ "gitea.dev/modules/translation"
"gitea.dev/modules/web/middleware"
"gitea.dev/services/webtheme"
)
@@ -148,5 +148,41 @@ func (c TemplateContext) HeadMetaContentSecurityPolicy() template.HTML {
if csp == "" {
return ""
}
- return htmlutil.HTMLFormat(``, csp)
+ return template.HTML(``)
+}
+
+func (c TemplateContext) WindowConfig() map[string]any {
+ locale := c["Locale"].(translation.Locale) //nolint:forcetypeassert // must exist
+ return map[string]any{
+ "appUrl": c.AppFullLink("/"),
+ "appSubUrl": setting.AppSubURL,
+ "assetUrlPrefix": setting.StaticURLPrefix + "/assets",
+ "runModeIsProd": setting.IsProd,
+ "customEmojis": setting.UI.CustomEmojisMap,
+ "pageData": c.parentContext().GetData()["PageData"],
+ "enableTimeTracking": setting.Service.EnableTimetracking,
+ "mermaidMaxSourceCharacters": setting.MermaidMaxSourceCharacters,
+ "sharedWorkerUri": public.AssetURI("web_src/js/user-events.sharedworker.ts"),
+ "notificationSettings": map[string]any{
+ "MinTimeout": int(setting.UI.Notification.MinTimeout / time.Millisecond),
+ "TimeoutStep": int(setting.UI.Notification.TimeoutStep / time.Millisecond),
+ "MaxTimeout": int(setting.UI.Notification.MaxTimeout / time.Millisecond),
+ },
+ // This global i18n object should only contain general texts.
+ // for specialized texts, it should be provided inside the related modules by:
+ // (1) API response (2) HTML data-attribute (3) PageData
+ //
+ // Maybe (if really needed) in the future we can introduce versioned frontend i18n data,
+ // make frontend cache i18n data in local storage and only update when the version is changed,
+ // then we can fill more keys here.
+ "i18n": map[string]any{
+ "error_occurred": locale.Tr("error.occurred"),
+ "remove_label_str": locale.Tr("remove_label_str"),
+ "modal_confirm": locale.Tr("modal.confirm"),
+ "modal_cancel": locale.Tr("modal.cancel"),
+ "more_items": locale.Tr("more_items"),
+ "copy_success": locale.Tr("copy_success"),
+ "copy_error": locale.Tr("copy_error"),
+ },
+ }
}
diff --git a/services/context/context_test.go b/services/context/context_test.go
index f6e34619761..8e56007347c 100644
--- a/services/context/context_test.go
+++ b/services/context/context_test.go
@@ -7,6 +7,7 @@ import (
"net/http"
"net/http/httptest"
"net/url"
+ "strings"
"testing"
"gitea.dev/modules/reqctx"
@@ -64,3 +65,12 @@ func TestAppFullLink(t *testing.T) {
assert.Equal(t, "https://gitea.example.com/sub/user/repo", string(tmplCtx.AppFullLink("user/repo")))
assert.Equal(t, "https://gitea.example.com/sub/user/repo", string(tmplCtx.AppFullLink("/user/repo")))
}
+
+func TestHeadMetaContentSecurityPolicy(t *testing.T) {
+ tmplCtx := NewTemplateContext(reqctx.NewRequestContextForTest(t), nil)
+ nonce := tmplCtx.CspScriptNonce()
+ assert.Equal(t, ``, string(tmplCtx.HeadMetaContentSecurityPolicy()))
+ assert.False(t, strings.ContainsAny(WebContentSecurityPolicy(nonce), `"<>&`))
+ defer test.MockVariableValue(&setting.Security.ContentSecurityPolicyGeneral, "unset")()
+ assert.Empty(t, tmplCtx.HeadMetaContentSecurityPolicy())
+}
diff --git a/templates/base/head_script.tmpl b/templates/base/head_script.tmpl
index 7a918d8bdb1..d1605e97e72 100644
--- a/templates/base/head_script.tmpl
+++ b/templates/base/head_script.tmpl
@@ -6,29 +6,6 @@ If you introduce mistakes in it, Gitea JavaScript code wouldn't run correctly.
{{/* before our JS code gets loaded, use arrays to store errors, then the arrays will be switched to our error handler later */}}
window.addEventListener('error', function(e) {window._globalHandlerErrors=window._globalHandlerErrors||[]; window._globalHandlerErrors.push(e);});
window.addEventListener('unhandledrejection', function(e) {window._globalHandlerErrors=window._globalHandlerErrors||[]; window._globalHandlerErrors.push(e);});
- window.config = {
- appUrl: '{{ctx.AppFullLink "/"}}',
- appSubUrl: '{{AppSubUrl}}',
- assetUrlPrefix: '{{AssetUrlPrefix}}',
- runModeIsProd: {{.RunModeIsProd}},
- customEmojis: {{CustomEmojis}},
- pageData: {{.PageData}},
- notificationSettings: {{NotificationSettings}}, {{/*a map provided by NewFuncMap in helper.go*/}}
- enableTimeTracking: {{EnableTimetracking}},
- mermaidMaxSourceCharacters: {{MermaidMaxSourceCharacters}},
- sharedWorkerUri: '{{AssetURI "web_src/js/user-events.sharedworker.ts"}}',
- {{/* this global i18n object should only contain general texts. for specialized texts, it should be provided inside the related modules by: (1) API response (2) HTML data-attribute (3) PageData */}}
- i18n: {
- error_occurred: {{ctx.Locale.Tr "error.occurred"}},
- remove_label_str: {{ctx.Locale.Tr "remove_label_str"}},
- modal_confirm: {{ctx.Locale.Tr "modal.confirm"}},
- modal_cancel: {{ctx.Locale.Tr "modal.cancel"}},
- more_items: {{ctx.Locale.Tr "more_items"}},
- copy_success: {{ctx.Locale.Tr "copy_success"}},
- copy_error: {{ctx.Locale.Tr "copy_error"}},
- },
- };
- {{/* in case some pages don't render the pageData, we make sure it is an object to prevent null access */}}
- window.config.pageData = window.config.pageData || {};
+
{{ctx.ScriptImport "web_src/js/iife.ts"}}
diff --git a/web_src/js/bootstrap.ts b/web_src/js/bootstrap.ts
index f88f4900637..191279685db 100644
--- a/web_src/js/bootstrap.ts
+++ b/web_src/js/bootstrap.ts
@@ -1,15 +1,18 @@
-// DO NOT IMPORT window.config HERE!
-// to make sure the error handler always works, we should never import `window.config`, because
-// some user's custom template breaks it.
import {showGlobalErrorMessage, processWindowErrorEvent} from './modules/errors.ts';
+// window.config is initialized here
+try {
+ window.config = JSON.parse(document.querySelector('#global-window-config')!.textContent);
+ // in case some pages don't render the pageData, we make sure it is an object to prevent null access
+ window.config.pageData ??= {};
+} catch {
+ showGlobalErrorMessage(`Gitea JavaScript code couldn't run correctly, please check your custom templates`);
+}
+
// A module should not be imported twice, otherwise there will be bugs when a module has its internal states.
// A real example is "generateElemId" in "utils/dom.ts", if it is imported twice in different module scopes,
// It will generate duplicate IDs (ps: don't try to use "random" to fix, it is just a real example to show the importance of "do not import a module twice")
if (!window._globalHandlerErrors?._inited) {
- if (!window.config) {
- showGlobalErrorMessage(`Gitea JavaScript code couldn't run correctly, please check your custom templates`);
- }
// we added an event handler for window error at the very beginning of