diff --git a/routers/web/repo/issue_watch.go b/routers/web/repo/issue_watch.go
index 1cb5cc7162d89..fc04c4e40b3cd 100644
--- a/routers/web/repo/issue_watch.go
+++ b/routers/web/repo/issue_watch.go
@@ -41,7 +41,7 @@ func IssueWatch(ctx *context.Context) {
return
}
- watch, err := strconv.ParseBool(ctx.Req.PostForm.Get("watch"))
+ watch, err := strconv.ParseBool(ctx.FormString("watch"))
if err != nil {
ctx.ServerError("watch is not bool", err)
return
@@ -52,5 +52,5 @@ func IssueWatch(ctx *context.Context) {
return
}
- ctx.Redirect(issue.Link())
+ ctx.JSONOK()
}
diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl
index 4334e4bcbdc21..f46cc0d630e58 100644
--- a/templates/repo/issue/view_content/sidebar.tmpl
+++ b/templates/repo/issue/view_content/sidebar.tmpl
@@ -267,23 +267,13 @@
{{if and $.IssueWatch (not .Repository.IsArchived)}}
-
{{ctx.Locale.Tr "notification.notifications"}}
-
-
-
+
{{end}}
{{if .Repository.IsTimetrackerEnabled $.Context}}
diff --git a/web_src/js/components/issue/Subscribe.vue b/web_src/js/components/issue/Subscribe.vue
new file mode 100644
index 0000000000000..75b2a05b6585f
--- /dev/null
+++ b/web_src/js/components/issue/Subscribe.vue
@@ -0,0 +1,70 @@
+
+
+
+
{{ locale.notifications }}
+
+
+
+
+
diff --git a/web_src/js/index.js b/web_src/js/index.js
index 4713618506b0c..227950038245d 100644
--- a/web_src/js/index.js
+++ b/web_src/js/index.js
@@ -85,6 +85,7 @@ import {initRepoIssueList} from './features/repo-issue-list.js';
import {initCommonIssueListQuickGoto} from './features/common-issue-list.js';
import {initRepoDiffCommitBranchesAndTags} from './features/repo-diff-commit.js';
import {initDirAuto} from './modules/dirauto.js';
+import {initIssueSubsribe} from './components/issue/Subscribe.vue';
// Init Gitea's Fomantic settings
initGiteaFomantic();
@@ -184,4 +185,6 @@ onDomReady(() => {
initRepoDiffView();
initPdfViewer();
initScopedAccessTokenCategories();
+
+ initIssueSubsribe();
});
diff --git a/web_src/js/init.js b/web_src/js/init.js
new file mode 100644
index 0000000000000..c70d9ea602104
--- /dev/null
+++ b/web_src/js/init.js
@@ -0,0 +1,37 @@
+import {createApp} from 'vue';
+
+// convertName convert the html tag a-b to aB
+export function convertName(o) {
+ return o.replace(/-(\w)/g, (_, c) => {
+ return c ? c.toUpperCase() : '';
+ });
+}
+
+// initComponent will mount the component with tag id named id and vue sfc
+// it will also assign all attributes of the tag with the prefix data-locale- and data-
+// to the component as props
+export function initComponent(id, sfc) {
+ const el = document.getElementById(id);
+ if (!el) return;
+
+ const data = {};
+
+ for (const attr of el.getAttributeNames()) {
+ if (attr.startsWith('data-locale-')) {
+ data.locale = data.locale || {};
+ data.locale[convertName(attr.slice(12))] = el.getAttribute(attr);
+ } else if (attr.startsWith('data-')) {
+ data[convertName(attr.slice(5))] = el.getAttribute(attr);
+ }
+ }
+
+ if (!sfc.props.locale) {
+ sfc.props.locale = {
+ type: Object,
+ default: () => {},
+ };
+ }
+
+ const view = createApp(sfc, data);
+ view.mount(el);
+}
diff --git a/web_src/js/init.test.js b/web_src/js/init.test.js
new file mode 100644
index 0000000000000..356b63bb694e5
--- /dev/null
+++ b/web_src/js/init.test.js
@@ -0,0 +1,7 @@
+import {convertName} from './init.js';
+
+test('init', () => {
+ expect(convertName('abc')).toEqual('abc');
+ expect(convertName('abc-repo')).toEqual('abcRepo');
+ expect(convertName('abc-repo-issue')).toEqual('abcRepoIssue');
+});
diff --git a/web_src/js/svg.js b/web_src/js/svg.js
index c2a96fba3f040..048b8362d885e 100644
--- a/web_src/js/svg.js
+++ b/web_src/js/svg.js
@@ -69,6 +69,8 @@ import octiconTag from '../../public/assets/img/svg/octicon-tag.svg';
import octiconTriangleDown from '../../public/assets/img/svg/octicon-triangle-down.svg';
import octiconX from '../../public/assets/img/svg/octicon-x.svg';
import octiconXCircleFill from '../../public/assets/img/svg/octicon-x-circle-fill.svg';
+import octiconMute from '../../public/assets/img/svg/octicon-mute.svg';
+import octiconUnmute from '../../public/assets/img/svg/octicon-unmute.svg';
const svgs = {
'gitea-double-chevron-left': giteaDoubleChevronLeft,
@@ -140,6 +142,8 @@ const svgs = {
'octicon-triangle-down': octiconTriangleDown,
'octicon-x': octiconX,
'octicon-x-circle-fill': octiconXCircleFill,
+ 'octicon-mute': octiconMute,
+ 'octicon-unmute': octiconUnmute,
};
// TODO: use a more general approach to access SVG icons.