summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--templates/base/footer.tmpl2
-rw-r--r--templates/base/head.tmpl45
-rw-r--r--templates/base/head_script.tmpl49
-rw-r--r--web_src/js/bootstrap.js41
-rw-r--r--web_src/js/features/common-global.js20
-rw-r--r--web_src/js/index.js7
-rw-r--r--web_src/js/publicpath.js6
7 files changed, 118 insertions, 52 deletions
diff --git a/templates/base/footer.tmpl b/templates/base/footer.tmpl
index 1aabfa2f5c..9bf16f8aa5 100644
--- a/templates/base/footer.tmpl
+++ b/templates/base/footer.tmpl
@@ -22,7 +22,7 @@
<script src='https://hcaptcha.com/1/api.js' async></script>
{{end}}
{{end}}
- <script src="{{AssetUrlPrefix}}/js/index.js?v={{MD5 AppVer}}"></script>
+ <script src="{{AssetUrlPrefix}}/js/index.js?v={{MD5 AppVer}}" onerror="alert('Failed to load asset files from ' + this.src + ', please make sure the asset files can be accessed and the ROOT_URL setting in app.ini is correct.')"></script>
{{template "custom/footer" .}}
</body>
</html>
diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl
index 2a9c24255d..35157e9b95 100644
--- a/templates/base/head.tmpl
+++ b/templates/base/head.tmpl
@@ -19,51 +19,12 @@
<link rel="alternate" type="application/atom+xml" title="" href="{{.FeedURL}}.atom">
<link rel="alternate" type="application/rss+xml" title="" href="{{.FeedURL}}.rss">
{{end}}
- <script>
- <!-- /* eslint-disable */ -->
- window.config = {
- appVer: '{{AppVer}}',
- appSubUrl: '{{AppSubUrl}}',
- assetUrlPrefix: '{{AssetUrlPrefix}}',
- runModeIsProd: {{.RunModeIsProd}},
- customEmojis: {{CustomEmojis}},
- useServiceWorker: {{UseServiceWorker}},
- csrfToken: '{{.CsrfToken}}',
- pageData: {{.PageData}},
- requireTribute: {{.RequireTribute}},
- notificationSettings: {{NotificationSettings}}, {{/*a map provided by NewFuncMap in helper.go*/}}
- enableTimeTracking: {{EnableTimetracking}},
- {{if .RequireTribute}}
- tributeValues: Array.from(new Map([
- {{ range .Participants }}
- ['{{.Name}}', {key: '{{.Name}} {{.FullName}}', value: '{{.Name}}',
- name: '{{.Name}}', fullname: '{{.FullName}}', avatar: '{{.AvatarLink}}'}],
- {{ end }}
- {{ range .Assignees }}
- ['{{.Name}}', {key: '{{.Name}} {{.FullName}}', value: '{{.Name}}',
- name: '{{.Name}}', fullname: '{{.FullName}}', avatar: '{{.AvatarLink}}'}],
- {{ end }}
- {{ range .MentionableTeams }}
- ['{{$.MentionableTeamsOrg}}/{{.Name}}', {key: '{{$.MentionableTeamsOrg}}/{{.Name}}', value: '{{$.MentionableTeamsOrg}}/{{.Name}}',
- name: '{{$.MentionableTeamsOrg}}/{{.Name}}', avatar: '{{$.MentionableTeamsOrgAvatar}}'}],
- {{ end }}
- ]).values()),
- {{end}}
- mermaidMaxSourceCharacters: {{MermaidMaxSourceCharacters}},
- {{/* 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: {
- copy_success: '{{.i18n.Tr "copy_success"}}',
- copy_error: '{{.i18n.Tr "copy_error"}}',
- error_occurred: '{{.i18n.Tr "error.occurred"}}',
- network_error: '{{.i18n.Tr "error.network_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 || {};
- </script>
<link rel="icon" href="{{AssetUrlPrefix}}/img/logo.svg" type="image/svg+xml">
<link rel="alternate icon" href="{{AssetUrlPrefix}}/img/favicon.png" type="image/png">
<link rel="stylesheet" href="{{AssetUrlPrefix}}/css/index.css?v={{MD5 AppVer}}">
+
+ {{template "base/head_script" .}}
+
<noscript>
<style>
.dropdown:hover > .menu { display: block; }
diff --git a/templates/base/head_script.tmpl b/templates/base/head_script.tmpl
new file mode 100644
index 0000000000..e6a8060a16
--- /dev/null
+++ b/templates/base/head_script.tmpl
@@ -0,0 +1,49 @@
+{{/*
+==== DO NOT EDIT ====
+If you are customizing Gitea, please do not change this file.
+If you introduce mistakes in it, Gitea JavaScript code wouldn't run correctly.
+*/}}
+<script>
+ <!-- /* eslint-disable */ -->
+ window.addEventListener('error', function(e) {window._globalHandlerErrors=window._globalHandlerErrors||[]; window._globalHandlerErrors.push(e);});
+ window.config = {
+ appVer: '{{AppVer}}',
+ appUrl: '{{AppUrl}}',
+ appSubUrl: '{{AppSubUrl}}',
+ assetUrlPrefix: '{{AssetUrlPrefix}}',
+ runModeIsProd: {{.RunModeIsProd}},
+ customEmojis: {{CustomEmojis}},
+ useServiceWorker: {{UseServiceWorker}},
+ csrfToken: '{{.CsrfToken}}',
+ pageData: {{.PageData}},
+ requireTribute: {{.RequireTribute}},
+ notificationSettings: {{NotificationSettings}}, {{/*a map provided by NewFuncMap in helper.go*/}}
+ enableTimeTracking: {{EnableTimetracking}},
+ {{if .RequireTribute}}
+ tributeValues: Array.from(new Map([
+ {{ range .Participants }}
+ ['{{.Name}}', {key: '{{.Name}} {{.FullName}}', value: '{{.Name}}',
+ name: '{{.Name}}', fullname: '{{.FullName}}', avatar: '{{.AvatarLink}}'}],
+ {{ end }}
+ {{ range .Assignees }}
+ ['{{.Name}}', {key: '{{.Name}} {{.FullName}}', value: '{{.Name}}',
+ name: '{{.Name}}', fullname: '{{.FullName}}', avatar: '{{.AvatarLink}}'}],
+ {{ end }}
+ {{ range .MentionableTeams }}
+ ['{{$.MentionableTeamsOrg}}/{{.Name}}', {key: '{{$.MentionableTeamsOrg}}/{{.Name}}', value: '{{$.MentionableTeamsOrg}}/{{.Name}}',
+ name: '{{$.MentionableTeamsOrg}}/{{.Name}}', avatar: '{{$.MentionableTeamsOrgAvatar}}'}],
+ {{ end }}
+ ]).values()),
+ {{end}}
+ mermaidMaxSourceCharacters: {{MermaidMaxSourceCharacters}},
+ {{/* 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: {
+ copy_success: '{{.i18n.Tr "copy_success"}}',
+ copy_error: '{{.i18n.Tr "copy_error"}}',
+ error_occurred: '{{.i18n.Tr "error.occurred"}}',
+ network_error: '{{.i18n.Tr "error.network_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 || {};
+</script>
diff --git a/web_src/js/bootstrap.js b/web_src/js/bootstrap.js
new file mode 100644
index 0000000000..cf13b2a559
--- /dev/null
+++ b/web_src/js/bootstrap.js
@@ -0,0 +1,41 @@
+import {joinPaths} from './utils.js';
+
+// 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.
+
+// This sets up the URL prefix used in webpack's chunk loading.
+// This file must be imported before any lazy-loading is being attempted.
+__webpack_public_path__ = joinPaths(window?.config?.assetUrlPrefix ?? '/', '/');
+
+export function showGlobalErrorMessage(msg) {
+ const pageContent = document.querySelector('.page-content');
+ if (!pageContent) return;
+ const el = document.createElement('div');
+ el.innerHTML = `<div class="ui container negative message center aligned js-global-error" style="white-space: pre-line;"></div>`;
+ el.childNodes[0].textContent = msg;
+ pageContent.prepend(el.childNodes[0]);
+}
+
+/**
+ * @param {ErrorEvent} e
+ */
+function processWindowErrorEvent(e) {
+ showGlobalErrorMessage(`JavaScript error: ${e.message} (${e.filename} @ ${e.lineno}:${e.colno}). Open browser console to see more details.`);
+}
+
+function initGlobalErrorHandler() {
+ 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 <script> of page head
+ // the handler calls `_globalHandlerErrors.push` (array method) to record all errors occur before this init
+ // then in this init, we can collect all error events and show them
+ for (const e of window._globalHandlerErrors || []) {
+ processWindowErrorEvent(e);
+ }
+ // then, change _globalHandlerErrors to an object with push method, to process further error events directly
+ window._globalHandlerErrors = {'push': (e) => processWindowErrorEvent(e)};
+}
+
+initGlobalErrorHandler();
diff --git a/web_src/js/features/common-global.js b/web_src/js/features/common-global.js
index a9baf9be0c..60af4d0d67 100644
--- a/web_src/js/features/common-global.js
+++ b/web_src/js/features/common-global.js
@@ -3,8 +3,9 @@ import 'jquery.are-you-sure';
import {mqBinarySearch} from '../utils.js';
import createDropzone from './dropzone.js';
import {initCompColorPicker} from './comp/ColorPicker.js';
+import {showGlobalErrorMessage} from '../bootstrap.js';
-const {csrfToken} = window.config;
+const {appUrl, csrfToken} = window.config;
export function initGlobalFormDirtyLeaveConfirm() {
// Warn users that try to leave a page after entering data into a form.
@@ -343,3 +344,20 @@ export function initGlobalButtons() {
});
});
}
+
+/**
+ * Too many users set their ROOT_URL to wrong value, and it causes a lot of problems:
+ * * Cross-origin API request without correct cookie
+ * * Incorrect href in <a>
+ * * ...
+ * So we check whether current URL starts with AppUrl(ROOT_URL).
+ * If they don't match, show a warning to users.
+ */
+export function checkAppUrl() {
+ const curUrl = window.location.href;
+ if (curUrl.startsWith(appUrl)) {
+ return;
+ }
+ showGlobalErrorMessage(`Your ROOT_URL in app.ini is ${appUrl} but you are visiting ${curUrl}
+You should set ROOT_URL correctly, otherwise the web may not work correctly.`);
+}
diff --git a/web_src/js/index.js b/web_src/js/index.js
index b7eba5e664..18b949e4e6 100644
--- a/web_src/js/index.js
+++ b/web_src/js/index.js
@@ -1,4 +1,5 @@
-import './publicpath.js';
+// bootstrap module must be the first one to be imported, it handles webpack lazy-loading and global errors
+import './bootstrap.js';
import $ from 'jquery';
import {initVueEnv} from './components/VueComponentLoader.js';
@@ -39,6 +40,7 @@ import {
} from './features/repo-issue.js';
import {initRepoEllipsisButton, initRepoCommitLastCommitLoader} from './features/repo-commit.js';
import {
+ checkAppUrl,
initFootLanguageMenu,
initGlobalButtonClickOnEnter,
initGlobalButtons,
@@ -82,7 +84,6 @@ $.fn.tab.settings.silent = true;
$.fn.checkbox.settings.enableEnterKey = false;
initVueEnv();
-
$(document).ready(() => {
initGlobalCommon();
@@ -169,4 +170,6 @@ $(document).ready(() => {
initUserAuthWebAuthn();
initUserAuthWebAuthnRegister();
initUserSettings();
+
+ checkAppUrl();
});
diff --git a/web_src/js/publicpath.js b/web_src/js/publicpath.js
deleted file mode 100644
index 44448a8447..0000000000
--- a/web_src/js/publicpath.js
+++ /dev/null
@@ -1,6 +0,0 @@
-// This sets up the URL prefix used in webpack's chunk loading.
-// This file must be imported before any lazy-loading is being attempted.
-import {joinPaths} from './utils.js';
-const {assetUrlPrefix} = window.config;
-
-__webpack_public_path__ = joinPaths(assetUrlPrefix, '/');