From 1bdb4e8725a12f8fcbfb9dcf12fc5ccc88d3557d Mon Sep 17 00:00:00 2001
From: yp05327 <576951401@qq.com>
Date: Fri, 28 Apr 2023 08:23:29 +0000
Subject: [PATCH 01/14] wip

---
 modules/issue/template/template.go              |  6 ++++++
 modules/structs/issue.go                        | 11 ++++++-----
 templates/repo/issue/fields/markdowneditor.tmpl |  4 ++++
 templates/repo/issue/new_form.tmpl              |  2 ++
 4 files changed, 18 insertions(+), 5 deletions(-)
 create mode 100644 templates/repo/issue/fields/markdowneditor.tmpl

diff --git a/modules/issue/template/template.go b/modules/issue/template/template.go
index 0f19d87e8d5dc..117aef5619b79 100644
--- a/modules/issue/template/template.go
+++ b/modules/issue/template/template.go
@@ -98,6 +98,12 @@ func validateYaml(template *api.IssueTemplate) error {
 			if err := validateOptions(field, idx); err != nil {
 				return err
 			}
+		case api.IssueFormFieldTypeMarkdownEditor:
+			if err := validateStringItem(position, field.Attributes, false,
+				"description",
+			); err != nil {
+				return err
+			}
 		default:
 			return position.Errorf("unknown type")
 		}
diff --git a/modules/structs/issue.go b/modules/structs/issue.go
index 04e169df84197..26fe5044780d8 100644
--- a/modules/structs/issue.go
+++ b/modules/structs/issue.go
@@ -128,11 +128,12 @@ type IssueDeadline struct {
 type IssueFormFieldType string
 
 const (
-	IssueFormFieldTypeMarkdown   IssueFormFieldType = "markdown"
-	IssueFormFieldTypeTextarea   IssueFormFieldType = "textarea"
-	IssueFormFieldTypeInput      IssueFormFieldType = "input"
-	IssueFormFieldTypeDropdown   IssueFormFieldType = "dropdown"
-	IssueFormFieldTypeCheckboxes IssueFormFieldType = "checkboxes"
+	IssueFormFieldTypeMarkdown       IssueFormFieldType = "markdown"
+	IssueFormFieldTypeTextarea       IssueFormFieldType = "textarea"
+	IssueFormFieldTypeInput          IssueFormFieldType = "input"
+	IssueFormFieldTypeDropdown       IssueFormFieldType = "dropdown"
+	IssueFormFieldTypeCheckboxes     IssueFormFieldType = "checkboxes"
+	IssueFormFieldTypeMarkdownEditor IssueFormFieldType = "markdowneditor"
 )
 
 // IssueFormField represents a form field
diff --git a/templates/repo/issue/fields/markdowneditor.tmpl b/templates/repo/issue/fields/markdowneditor.tmpl
new file mode 100644
index 0000000000000..85b1f00a4c178
--- /dev/null
+++ b/templates/repo/issue/fields/markdowneditor.tmpl
@@ -0,0 +1,4 @@
+<div class="field">
+	{{template "repo/issue/fields/header" .}}
+	{{template "repo/issue/comment_tab" $}}
+</div>
diff --git a/templates/repo/issue/new_form.tmpl b/templates/repo/issue/new_form.tmpl
index d00a4813d250f..f603fd92a47e1 100644
--- a/templates/repo/issue/new_form.tmpl
+++ b/templates/repo/issue/new_form.tmpl
@@ -29,6 +29,8 @@
 								{{template "repo/issue/fields/dropdown" dict "Context" $.Context "item" .}}
 							{{else if eq .Type "checkboxes"}}
 								{{template "repo/issue/fields/checkboxes" dict "Context" $.Context "item" .}}
+							{{else if eq .Type "markdowneditor"}}
+								{{template "repo/issue/fields/markdowneditor" dict "Context" $.Context "item" $}}
 							{{end}}
 						{{end}}
 						{{if .IsAttachmentEnabled}}

From df498c7334babd8f890fbe02d84810a72b203e8c Mon Sep 17 00:00:00 2001
From: yp05327 <576951401@qq.com>
Date: Mon, 1 May 2023 02:49:25 +0000
Subject: [PATCH 02/14] fix tmpl

---
 templates/repo/issue/fields/markdowneditor.tmpl | 2 +-
 templates/repo/issue/new_form.tmpl              | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/templates/repo/issue/fields/markdowneditor.tmpl b/templates/repo/issue/fields/markdowneditor.tmpl
index 85b1f00a4c178..3c8bea1327ed7 100644
--- a/templates/repo/issue/fields/markdowneditor.tmpl
+++ b/templates/repo/issue/fields/markdowneditor.tmpl
@@ -1,4 +1,4 @@
 <div class="field">
 	{{template "repo/issue/fields/header" .}}
-	{{template "repo/issue/comment_tab" $}}
+	{{template "repo/issue/comment_tab" .Context.Data}}
 </div>
diff --git a/templates/repo/issue/new_form.tmpl b/templates/repo/issue/new_form.tmpl
index f603fd92a47e1..4e1980902723b 100644
--- a/templates/repo/issue/new_form.tmpl
+++ b/templates/repo/issue/new_form.tmpl
@@ -30,7 +30,7 @@
 							{{else if eq .Type "checkboxes"}}
 								{{template "repo/issue/fields/checkboxes" dict "Context" $.Context "item" .}}
 							{{else if eq .Type "markdowneditor"}}
-								{{template "repo/issue/fields/markdowneditor" dict "Context" $.Context "item" $}}
+								{{template "repo/issue/fields/markdowneditor" dict "Context" $.Context "item" .}}
 							{{end}}
 						{{end}}
 						{{if .IsAttachmentEnabled}}

From 759c9f7bd0d754b4793c6349a5c1464bdca361a9 Mon Sep 17 00:00:00 2001
From: yp05327 <576951401@qq.com>
Date: Mon, 1 May 2023 03:01:05 +0000
Subject: [PATCH 03/14] remove upload

---
 templates/repo/issue/new_form.tmpl | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/templates/repo/issue/new_form.tmpl b/templates/repo/issue/new_form.tmpl
index 4e1980902723b..187285258ea17 100644
--- a/templates/repo/issue/new_form.tmpl
+++ b/templates/repo/issue/new_form.tmpl
@@ -33,11 +33,6 @@
 								{{template "repo/issue/fields/markdowneditor" dict "Context" $.Context "item" .}}
 							{{end}}
 						{{end}}
-						{{if .IsAttachmentEnabled}}
-						<div class="field">
-							{{template "repo/upload" .}}
-						</div>
-						{{end}}
 					{{else}}
 						{{template "repo/issue/comment_tab" .}}
 					{{end}}

From ce9ce245fcbd2e87c901465d7b640b2a23a395a0 Mon Sep 17 00:00:00 2001
From: yp05327 <576951401@qq.com>
Date: Mon, 1 May 2023 06:44:05 +0000
Subject: [PATCH 04/14] reuse textarea

---
 modules/issue/template/template.go            |  6 ---
 modules/structs/issue.go                      | 11 ++---
 .../repo/issue/fields/markdowneditor.tmpl     |  4 --
 templates/repo/issue/fields/textarea.tmpl     | 21 +++++++--
 templates/repo/issue/new_form.tmpl            |  2 -
 .../js/features/comp/ComboMarkdownEditor.js   | 20 ++++----
 web_src/js/features/repo-issue.js             | 46 +++++++++++++++++--
 web_src/js/features/repo-legacy.js            |  3 +-
 web_src/js/features/repo-wiki.js              |  2 +-
 web_src/js/index.js                           |  2 +
 10 files changed, 80 insertions(+), 37 deletions(-)
 delete mode 100644 templates/repo/issue/fields/markdowneditor.tmpl

diff --git a/modules/issue/template/template.go b/modules/issue/template/template.go
index 117aef5619b79..0f19d87e8d5dc 100644
--- a/modules/issue/template/template.go
+++ b/modules/issue/template/template.go
@@ -98,12 +98,6 @@ func validateYaml(template *api.IssueTemplate) error {
 			if err := validateOptions(field, idx); err != nil {
 				return err
 			}
-		case api.IssueFormFieldTypeMarkdownEditor:
-			if err := validateStringItem(position, field.Attributes, false,
-				"description",
-			); err != nil {
-				return err
-			}
 		default:
 			return position.Errorf("unknown type")
 		}
diff --git a/modules/structs/issue.go b/modules/structs/issue.go
index 26fe5044780d8..04e169df84197 100644
--- a/modules/structs/issue.go
+++ b/modules/structs/issue.go
@@ -128,12 +128,11 @@ type IssueDeadline struct {
 type IssueFormFieldType string
 
 const (
-	IssueFormFieldTypeMarkdown       IssueFormFieldType = "markdown"
-	IssueFormFieldTypeTextarea       IssueFormFieldType = "textarea"
-	IssueFormFieldTypeInput          IssueFormFieldType = "input"
-	IssueFormFieldTypeDropdown       IssueFormFieldType = "dropdown"
-	IssueFormFieldTypeCheckboxes     IssueFormFieldType = "checkboxes"
-	IssueFormFieldTypeMarkdownEditor IssueFormFieldType = "markdowneditor"
+	IssueFormFieldTypeMarkdown   IssueFormFieldType = "markdown"
+	IssueFormFieldTypeTextarea   IssueFormFieldType = "textarea"
+	IssueFormFieldTypeInput      IssueFormFieldType = "input"
+	IssueFormFieldTypeDropdown   IssueFormFieldType = "dropdown"
+	IssueFormFieldTypeCheckboxes IssueFormFieldType = "checkboxes"
 )
 
 // IssueFormField represents a form field
diff --git a/templates/repo/issue/fields/markdowneditor.tmpl b/templates/repo/issue/fields/markdowneditor.tmpl
deleted file mode 100644
index 3c8bea1327ed7..0000000000000
--- a/templates/repo/issue/fields/markdowneditor.tmpl
+++ /dev/null
@@ -1,4 +0,0 @@
-<div class="field">
-	{{template "repo/issue/fields/header" .}}
-	{{template "repo/issue/comment_tab" .Context.Data}}
-</div>
diff --git a/templates/repo/issue/fields/textarea.tmpl b/templates/repo/issue/fields/textarea.tmpl
index ad3c5efa045c4..99d0fead84eb7 100644
--- a/templates/repo/issue/fields/textarea.tmpl
+++ b/templates/repo/issue/fields/textarea.tmpl
@@ -1,6 +1,21 @@
-<div class="field">
+<div class="field markdown-textarea">
 	{{template "repo/issue/fields/header" .}}
 	{{/* FIXME: preview markdown result */}}
-	{{/* FIXME: required validation for markdown editor */}}
-	<textarea name="form-field-{{.item.ID}}" placeholder="{{.item.Attributes.placeholder}}" {{if and .item.Validations.required .item.Attributes.render}}required{{end}}>{{.item.Attributes.value}}</textarea>
+	<textarea class="textarea" name="form-field-{{.item.ID}}" placeholder="{{.item.Attributes.placeholder}}" {{if and .item.Validations.required .item.Attributes.render}}required{{end}}>{{.item.Attributes.value}}</textarea>
+	
+	{{$ := .Context.Data}}
+	{{template "shared/combomarkdowneditor" (dict
+		"locale" $.locale
+		"MarkdownPreviewUrl" (print $.Repository.Link "/markup")
+		"MarkdownPreviewContext" $.RepoLink
+		"TextareaName" "content"
+		"TextareaPlaceholder"  ($.locale.Tr "repo.diff.comment.placeholder")
+		"DropzoneParentContainer" "form, .ui.form"
+	)}}
 </div>
+
+{{if $.IsAttachmentEnabled}}
+<div class="field markdown-textarea">
+	{{template "repo/upload" $}}
+</div>
+{{end}}
\ No newline at end of file
diff --git a/templates/repo/issue/new_form.tmpl b/templates/repo/issue/new_form.tmpl
index 187285258ea17..782663154a19e 100644
--- a/templates/repo/issue/new_form.tmpl
+++ b/templates/repo/issue/new_form.tmpl
@@ -29,8 +29,6 @@
 								{{template "repo/issue/fields/dropdown" dict "Context" $.Context "item" .}}
 							{{else if eq .Type "checkboxes"}}
 								{{template "repo/issue/fields/checkboxes" dict "Context" $.Context "item" .}}
-							{{else if eq .Type "markdowneditor"}}
-								{{template "repo/issue/fields/markdowneditor" dict "Context" $.Context "item" .}}
 							{{end}}
 						{{end}}
 					{{else}}
diff --git a/web_src/js/features/comp/ComboMarkdownEditor.js b/web_src/js/features/comp/ComboMarkdownEditor.js
index 9995033e8978e..525bb1d679887 100644
--- a/web_src/js/features/comp/ComboMarkdownEditor.js
+++ b/web_src/js/features/comp/ComboMarkdownEditor.js
@@ -370,17 +370,15 @@ export function getComboMarkdownEditor(el) {
   return el?._giteaComboMarkdownEditor;
 }
 
-export async function initComboMarkdownEditor(container, options = {}) {
-  if (container instanceof $) {
-    if (container.length !== 1) {
-      throw new Error('initComboMarkdownEditor: container must be a single element');
-    }
-    container = container[0];
+export async function initComboMarkdownEditor(containers, options = {}) {
+  if (!containers) {
+    throw new Error('initComboMarkdownEditor: container list is null');
   }
-  if (!container) {
-    throw new Error('initComboMarkdownEditor: container is null');
+  const editors = [];
+  for (const container of containers) {
+    const editor = new ComboMarkdownEditor(container, options);
+    await editor.init();
+    editors.push(editor);
   }
-  const editor = new ComboMarkdownEditor(container, options);
-  await editor.init();
-  return editor;
+  return editors;
 }
diff --git a/web_src/js/features/repo-issue.js b/web_src/js/features/repo-issue.js
index 63fae3a37cb47..118e2fc3ee262 100644
--- a/web_src/js/features/repo-issue.js
+++ b/web_src/js/features/repo-issue.js
@@ -392,7 +392,8 @@ export async function handleReply($el) {
   const $textarea = form.find('textarea');
   let editor = getComboMarkdownEditor($textarea);
   if (!editor) {
-    editor = await initComboMarkdownEditor(form.find('.combo-markdown-editor'));
+    const editors = await initComboMarkdownEditor(form.find('.combo-markdown-editor'));
+    editor = editors[0];
   }
   editor.focus();
   return editor;
@@ -522,8 +523,8 @@ export function initRepoPullRequestReview() {
       td.find("input[name='side']").val(side === 'left' ? 'previous' : 'proposed');
       td.find("input[name='path']").val(path);
 
-      const editor = await initComboMarkdownEditor(td.find('.combo-markdown-editor'));
-      editor.focus();
+      const editors = await initComboMarkdownEditor(td.find('.combo-markdown-editor'));
+      editors[0].focus();
     }
   });
 }
@@ -634,3 +635,42 @@ export function initRepoIssueBranchSelect() {
   };
   $('#branch-select > .item').on('click', changeBranchSelect);
 }
+
+export function initRepoIssueMarkdownTextarea() {
+  const hiddenTextarea = function (target) {
+    const comboMarkdownEditor = $(target).parent().find('.combo-markdown-editor');
+    const markdownTextEditor = comboMarkdownEditor.find('.markdown-text-editor');
+    const dropzone = $(target).parent().next().find('.dropzone');
+    // show combo markdown editor
+    comboMarkdownEditor.removeClass('gt-hidden');
+    markdownTextEditor.trigger('focus');
+    if (dropzone) {
+      dropzone.removeClass('gt-hidden');
+    }
+    // hidden textarea
+    $(target).addClass('gt-hidden');
+  };
+
+  const showTextarea = function (target) {
+    const comboMarkdownEditor = $(target).parent().find('.combo-markdown-editor');
+    const markdownTextEditor = comboMarkdownEditor.find('.markdown-text-editor');
+    const dropzone = $(target).parent().next().find('.dropzone');
+    // hidden combo markdown editor
+    $(comboMarkdownEditor).addClass('gt-hidden');
+    if (dropzone) {
+      $(dropzone).addClass('gt-hidden');
+    }
+    // show textarea
+    $(target).removeClass('gt-hidden');
+    // sync textarea content
+    $(target).val($(markdownTextEditor).val());
+  };
+
+  // default display textarea
+  $('.markdown-textarea .textarea').each((_, target) => showTextarea(target));
+
+  $('.markdown-textarea .textarea').on('click', function() {
+    hiddenTextarea(this);
+    $('.markdown-textarea .textarea').not(this).each((_, target) => showTextarea(target));
+  });
+}
diff --git a/web_src/js/features/repo-legacy.js b/web_src/js/features/repo-legacy.js
index 851a9b855e6b3..31000a3884113 100644
--- a/web_src/js/features/repo-legacy.js
+++ b/web_src/js/features/repo-legacy.js
@@ -439,7 +439,8 @@ async function onEditContent(event) {
 
   if (!$editContentZone.html()) {
     $editContentZone.html($('#issue-comment-editor-template').html());
-    comboMarkdownEditor = await initComboMarkdownEditor($editContentZone.find('.combo-markdown-editor'));
+    const comboMarkdownEditors = await initComboMarkdownEditor($editContentZone.find('.combo-markdown-editor'));
+    comboMarkdownEditor = comboMarkdownEditors[0];
 
     const $dropzone = $editContentZone.find('.dropzone');
     const dz = await setupDropzone($dropzone);
diff --git a/web_src/js/features/repo-wiki.js b/web_src/js/features/repo-wiki.js
index 09202a303ce86..bf5fe40902876 100644
--- a/web_src/js/features/repo-wiki.js
+++ b/web_src/js/features/repo-wiki.js
@@ -61,7 +61,7 @@ async function initRepoWikiFormEditor() {
         'clean-block', 'preview', 'fullscreen', 'side-by-side', '|', 'gitea-switch-to-textarea'
       ],
     },
-  });
+  })[0];
 
   $form.on('submit', () => {
     if (!validateTextareaNonEmpty($editArea)) {
diff --git a/web_src/js/index.js b/web_src/js/index.js
index f7cbb24e8562e..7d4b5f2e34b23 100644
--- a/web_src/js/index.js
+++ b/web_src/js/index.js
@@ -25,6 +25,7 @@ import {initCommentContent, initMarkupContent} from './markup/content.js';
 import {initUserAuthLinkAccountView, initUserAuthOauth2} from './features/user-auth.js';
 import {
   initRepoIssueDue,
+  initRepoIssueMarkdownTextarea,
   initRepoIssueReferenceRepositorySearch,
   initRepoIssueTimeTracking,
   initRepoIssueWipTitle,
@@ -146,6 +147,7 @@ onDomReady(() => {
   initRepoIssueDue();
   initRepoIssueList();
   initRepoIssueSidebarList();
+  initRepoIssueMarkdownTextarea();
   initRepoIssueReferenceRepositorySearch();
   initRepoIssueTimeTracking();
   initRepoIssueWipTitle();

From ff6146a9a99a33175f6b62088f5a23d5fd6b8457 Mon Sep 17 00:00:00 2001
From: yp05327 <576951401@qq.com>
Date: Mon, 1 May 2023 06:58:36 +0000
Subject: [PATCH 05/14] convert to listen focus event

---
 web_src/js/features/repo-issue.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/web_src/js/features/repo-issue.js b/web_src/js/features/repo-issue.js
index 118e2fc3ee262..55f30c27c5e1a 100644
--- a/web_src/js/features/repo-issue.js
+++ b/web_src/js/features/repo-issue.js
@@ -669,7 +669,7 @@ export function initRepoIssueMarkdownTextarea() {
   // default display textarea
   $('.markdown-textarea .textarea').each((_, target) => showTextarea(target));
 
-  $('.markdown-textarea .textarea').on('click', function() {
+  $('.markdown-textarea .textarea').on('focus', function() {
     hiddenTextarea(this);
     $('.markdown-textarea .textarea').not(this).each((_, target) => showTextarea(target));
   });

From 2db0fb4637783455756f39c959fe605eee30a0bf Mon Sep 17 00:00:00 2001
From: yp05327 <576951401@qq.com>
Date: Mon, 1 May 2023 07:10:51 +0000
Subject: [PATCH 06/14] add default value

---
 templates/repo/issue/fields/textarea.tmpl | 1 +
 1 file changed, 1 insertion(+)

diff --git a/templates/repo/issue/fields/textarea.tmpl b/templates/repo/issue/fields/textarea.tmpl
index 99d0fead84eb7..8b7d2f05b94f2 100644
--- a/templates/repo/issue/fields/textarea.tmpl
+++ b/templates/repo/issue/fields/textarea.tmpl
@@ -9,6 +9,7 @@
 		"MarkdownPreviewUrl" (print $.Repository.Link "/markup")
 		"MarkdownPreviewContext" $.RepoLink
 		"TextareaName" "content"
+		"TextareaContent" .item.Attributes.value
 		"TextareaPlaceholder"  ($.locale.Tr "repo.diff.comment.placeholder")
 		"DropzoneParentContainer" "form, .ui.form"
 	)}}

From f39e26f87b405c2efa0aede9267198a425705bf3 Mon Sep 17 00:00:00 2001
From: yp05327 <576951401@qq.com>
Date: Mon, 1 May 2023 07:12:36 +0000
Subject: [PATCH 07/14] fix lint

---
 templates/repo/issue/fields/textarea.tmpl | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/templates/repo/issue/fields/textarea.tmpl b/templates/repo/issue/fields/textarea.tmpl
index 8b7d2f05b94f2..b42bd087115f5 100644
--- a/templates/repo/issue/fields/textarea.tmpl
+++ b/templates/repo/issue/fields/textarea.tmpl
@@ -2,7 +2,7 @@
 	{{template "repo/issue/fields/header" .}}
 	{{/* FIXME: preview markdown result */}}
 	<textarea class="textarea" name="form-field-{{.item.ID}}" placeholder="{{.item.Attributes.placeholder}}" {{if and .item.Validations.required .item.Attributes.render}}required{{end}}>{{.item.Attributes.value}}</textarea>
-	
+
 	{{$ := .Context.Data}}
 	{{template "shared/combomarkdowneditor" (dict
 		"locale" $.locale
@@ -19,4 +19,4 @@
 <div class="field markdown-textarea">
 	{{template "repo/upload" $}}
 </div>
-{{end}}
\ No newline at end of file
+{{end}}

From 3bb05571a0b62db1bc1360660b61e6d89607f089 Mon Sep 17 00:00:00 2001
From: yp05327 <576951401@qq.com>
Date: Mon, 1 May 2023 07:16:58 +0000
Subject: [PATCH 08/14] fix placeholder

---
 templates/repo/issue/fields/textarea.tmpl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/templates/repo/issue/fields/textarea.tmpl b/templates/repo/issue/fields/textarea.tmpl
index b42bd087115f5..6131f25c485b6 100644
--- a/templates/repo/issue/fields/textarea.tmpl
+++ b/templates/repo/issue/fields/textarea.tmpl
@@ -10,7 +10,7 @@
 		"MarkdownPreviewContext" $.RepoLink
 		"TextareaName" "content"
 		"TextareaContent" .item.Attributes.value
-		"TextareaPlaceholder"  ($.locale.Tr "repo.diff.comment.placeholder")
+		"TextareaPlaceholder"  .item.Attributes.placeholder
 		"DropzoneParentContainer" "form, .ui.form"
 	)}}
 </div>

From ff83b3b1cdbe56f68fece8a786a22a27f761dabb Mon Sep 17 00:00:00 2001
From: yp05327 <576951401@qq.com>
Date: Mon, 1 May 2023 08:08:48 +0000
Subject: [PATCH 09/14] improve

---
 templates/repo/issue/fields/textarea.tmpl | 7 +++----
 templates/repo/issue/new_form.tmpl        | 2 +-
 2 files changed, 4 insertions(+), 5 deletions(-)

diff --git a/templates/repo/issue/fields/textarea.tmpl b/templates/repo/issue/fields/textarea.tmpl
index 6131f25c485b6..7a7f1075962c7 100644
--- a/templates/repo/issue/fields/textarea.tmpl
+++ b/templates/repo/issue/fields/textarea.tmpl
@@ -3,11 +3,10 @@
 	{{/* FIXME: preview markdown result */}}
 	<textarea class="textarea" name="form-field-{{.item.ID}}" placeholder="{{.item.Attributes.placeholder}}" {{if and .item.Validations.required .item.Attributes.render}}required{{end}}>{{.item.Attributes.value}}</textarea>
 
-	{{$ := .Context.Data}}
 	{{template "shared/combomarkdowneditor" (dict
-		"locale" $.locale
-		"MarkdownPreviewUrl" (print $.Repository.Link "/markup")
-		"MarkdownPreviewContext" $.RepoLink
+		"locale" .Locale
+		"MarkdownPreviewUrl" (print .RepoLink "/markup")
+		"MarkdownPreviewContext" .RepoLink
 		"TextareaName" "content"
 		"TextareaContent" .item.Attributes.value
 		"TextareaPlaceholder"  .item.Attributes.placeholder
diff --git a/templates/repo/issue/new_form.tmpl b/templates/repo/issue/new_form.tmpl
index 782663154a19e..a2abd2f001398 100644
--- a/templates/repo/issue/new_form.tmpl
+++ b/templates/repo/issue/new_form.tmpl
@@ -24,7 +24,7 @@
 							{{else if eq .Type "markdown"}}
 								{{template "repo/issue/fields/markdown" dict "Context" $.Context "item" .}}
 							{{else if eq .Type "textarea"}}
-								{{template "repo/issue/fields/textarea" dict "Context" $.Context "item" .}}
+								{{template "repo/issue/fields/textarea" dict "Context" $.Context "item" . "RepoLink" $.RepoLink "Locale" $.locale}}
 							{{else if eq .Type "dropdown"}}
 								{{template "repo/issue/fields/dropdown" dict "Context" $.Context "item" .}}
 							{{else if eq .Type "checkboxes"}}

From f26e1c9b776c54757c5ef6cdc623d0795739cbd1 Mon Sep 17 00:00:00 2001
From: yp05327 <576951401@qq.com>
Date: Tue, 2 May 2023 04:48:25 +0000
Subject: [PATCH 10/14] improve

---
 templates/repo/issue/comment_tab.tmpl         |  8 ++++----
 templates/repo/issue/fields/textarea.tmpl     | 10 +++++-----
 templates/repo/issue/new_form.tmpl            |  2 +-
 .../js/features/comp/ComboMarkdownEditor.js   | 20 ++++++++++---------
 web_src/js/features/repo-issue.js             |  8 ++++----
 web_src/js/features/repo-legacy.js            | 19 ++++++++++++------
 web_src/js/features/repo-wiki.js              |  2 +-
 7 files changed, 39 insertions(+), 30 deletions(-)

diff --git a/templates/repo/issue/comment_tab.tmpl b/templates/repo/issue/comment_tab.tmpl
index c40e6ddf32e87..bcb1786692b54 100644
--- a/templates/repo/issue/comment_tab.tmpl
+++ b/templates/repo/issue/comment_tab.tmpl
@@ -3,7 +3,7 @@
 {{if not $textareaContent}}{{$textareaContent = .PullRequestTemplate}}{{end}}
 {{if not $textareaContent}}{{$textareaContent = .content}}{{end}}
 
-<div class="field">
+<div class="field markdown">
 	{{template "shared/combomarkdowneditor" (dict
 		"locale" $.locale
 		"MarkdownPreviewUrl" (print .Repository.Link "/markup")
@@ -16,7 +16,7 @@
 </div>
 
 {{if .IsAttachmentEnabled}}
-	<div class="field">
-		{{template "repo/upload" .}}
-	</div>
+<div class="field">
+	{{template "repo/upload" .}}
+</div>
 {{end}}
diff --git a/templates/repo/issue/fields/textarea.tmpl b/templates/repo/issue/fields/textarea.tmpl
index 7a7f1075962c7..6127a32d46de6 100644
--- a/templates/repo/issue/fields/textarea.tmpl
+++ b/templates/repo/issue/fields/textarea.tmpl
@@ -1,7 +1,7 @@
-<div class="field markdown-textarea">
+<div class="field textarea">
 	{{template "repo/issue/fields/header" .}}
 	{{/* FIXME: preview markdown result */}}
-	<textarea class="textarea" name="form-field-{{.item.ID}}" placeholder="{{.item.Attributes.placeholder}}" {{if and .item.Validations.required .item.Attributes.render}}required{{end}}>{{.item.Attributes.value}}</textarea>
+	<textarea class="fake" name="form-field-{{.item.ID}}" placeholder="{{.item.Attributes.placeholder}}" {{if and .item.Validations.required .item.Attributes.render}}required{{end}}>{{.item.Attributes.value}}</textarea>
 
 	{{template "shared/combomarkdowneditor" (dict
 		"locale" .Locale
@@ -14,8 +14,8 @@
 	)}}
 </div>
 
-{{if $.IsAttachmentEnabled}}
-<div class="field markdown-textarea">
-	{{template "repo/upload" $}}
+{{if .IsAttachmentEnabled}}
+<div class="field">
+	{{template "repo/upload" .Context.Data}}
 </div>
 {{end}}
diff --git a/templates/repo/issue/new_form.tmpl b/templates/repo/issue/new_form.tmpl
index a2abd2f001398..936acc7755ca7 100644
--- a/templates/repo/issue/new_form.tmpl
+++ b/templates/repo/issue/new_form.tmpl
@@ -24,7 +24,7 @@
 							{{else if eq .Type "markdown"}}
 								{{template "repo/issue/fields/markdown" dict "Context" $.Context "item" .}}
 							{{else if eq .Type "textarea"}}
-								{{template "repo/issue/fields/textarea" dict "Context" $.Context "item" . "RepoLink" $.RepoLink "Locale" $.locale}}
+								{{template "repo/issue/fields/textarea" dict "Context" $.Context "item" . "RepoLink" $.RepoLink "Locale" $.locale "IsAttachmentEnabled" $.IsAttachmentEnabled}}
 							{{else if eq .Type "dropdown"}}
 								{{template "repo/issue/fields/dropdown" dict "Context" $.Context "item" .}}
 							{{else if eq .Type "checkboxes"}}
diff --git a/web_src/js/features/comp/ComboMarkdownEditor.js b/web_src/js/features/comp/ComboMarkdownEditor.js
index 525bb1d679887..9995033e8978e 100644
--- a/web_src/js/features/comp/ComboMarkdownEditor.js
+++ b/web_src/js/features/comp/ComboMarkdownEditor.js
@@ -370,15 +370,17 @@ export function getComboMarkdownEditor(el) {
   return el?._giteaComboMarkdownEditor;
 }
 
-export async function initComboMarkdownEditor(containers, options = {}) {
-  if (!containers) {
-    throw new Error('initComboMarkdownEditor: container list is null');
+export async function initComboMarkdownEditor(container, options = {}) {
+  if (container instanceof $) {
+    if (container.length !== 1) {
+      throw new Error('initComboMarkdownEditor: container must be a single element');
+    }
+    container = container[0];
   }
-  const editors = [];
-  for (const container of containers) {
-    const editor = new ComboMarkdownEditor(container, options);
-    await editor.init();
-    editors.push(editor);
+  if (!container) {
+    throw new Error('initComboMarkdownEditor: container is null');
   }
-  return editors;
+  const editor = new ComboMarkdownEditor(container, options);
+  await editor.init();
+  return editor;
 }
diff --git a/web_src/js/features/repo-issue.js b/web_src/js/features/repo-issue.js
index 55f30c27c5e1a..ec0742bca50f2 100644
--- a/web_src/js/features/repo-issue.js
+++ b/web_src/js/features/repo-issue.js
@@ -666,11 +666,11 @@ export function initRepoIssueMarkdownTextarea() {
     $(target).val($(markdownTextEditor).val());
   };
 
-  // default display textarea
-  $('.markdown-textarea .textarea').each((_, target) => showTextarea(target));
+  // default display all textarea
+  $('.field.textarea .fake').each((_, target) => showTextarea(target));
 
-  $('.markdown-textarea .textarea').on('focus', function() {
+  $('.field.textarea .fake').on('focus', function() {
     hiddenTextarea(this);
-    $('.markdown-textarea .textarea').not(this).each((_, target) => showTextarea(target));
+    $('.field.textarea .fake').not(this).each((_, target) => showTextarea(target));
   });
 }
diff --git a/web_src/js/features/repo-legacy.js b/web_src/js/features/repo-legacy.js
index 31000a3884113..d381250b2e709 100644
--- a/web_src/js/features/repo-legacy.js
+++ b/web_src/js/features/repo-legacy.js
@@ -89,10 +89,18 @@ export function initRepoCommentForm() {
     $('#comment-form').trigger('submit');
   });
 
-  const _promise = initComboMarkdownEditor($commentForm.find('.combo-markdown-editor'), {
-    onContentChanged(editor) {
-      $statusButton.text($statusButton.attr(editor.value().trim() ? 'data-status-and-comment' : 'data-status'));
-    },
+  // default markdown editor should have no more than one
+  $commentForm.find('.field.markdown .combo-markdown-editor').each((_, comboMarkdownEditor) => {
+    const _promise = initComboMarkdownEditor(comboMarkdownEditor, {
+      onContentChanged(editor) {
+        $statusButton.text($statusButton.attr(editor.value().trim() ? 'data-status-and-comment' : 'data-status'));
+      },
+    });
+  });
+
+  // issue template textarea
+  $commentForm.find('.field.textarea .combo-markdown-editor').each((_, comboMarkdownEditor) => {
+    initComboMarkdownEditor(comboMarkdownEditor);
   });
 
   initBranchSelector();
@@ -439,8 +447,7 @@ async function onEditContent(event) {
 
   if (!$editContentZone.html()) {
     $editContentZone.html($('#issue-comment-editor-template').html());
-    const comboMarkdownEditors = await initComboMarkdownEditor($editContentZone.find('.combo-markdown-editor'));
-    comboMarkdownEditor = comboMarkdownEditors[0];
+    comboMarkdownEditor = await initComboMarkdownEditor($editContentZone.find('.combo-markdown-editor'));
 
     const $dropzone = $editContentZone.find('.dropzone');
     const dz = await setupDropzone($dropzone);
diff --git a/web_src/js/features/repo-wiki.js b/web_src/js/features/repo-wiki.js
index bf5fe40902876..09202a303ce86 100644
--- a/web_src/js/features/repo-wiki.js
+++ b/web_src/js/features/repo-wiki.js
@@ -61,7 +61,7 @@ async function initRepoWikiFormEditor() {
         'clean-block', 'preview', 'fullscreen', 'side-by-side', '|', 'gitea-switch-to-textarea'
       ],
     },
-  })[0];
+  });
 
   $form.on('submit', () => {
     if (!validateTextareaNonEmpty($editArea)) {

From ce5604002828f91f48a965f452ae89e5a1d60e41 Mon Sep 17 00:00:00 2001
From: wxiaoguang <wxiaoguang@gmail.com>
Date: Tue, 2 May 2023 17:51:21 +0800
Subject: [PATCH 11/14] fix

---
 templates/repo/issue/comment_tab.tmpl         |  8 +-
 templates/repo/issue/fields/textarea.tmpl     | 38 ++++----
 templates/repo/issue/new_form.tmpl            |  2 +-
 .../js/features/comp/ComboMarkdownEditor.js   | 83 ++++-------------
 web_src/js/features/comp/ImagePaste.js        |  8 ++
 web_src/js/features/comp/QuickSubmit.js       |  4 +-
 web_src/js/features/comp/TributeExpander.js   | 59 ++++++++++++
 web_src/js/features/repo-issue.js             | 92 +++++++++++--------
 web_src/js/features/repo-legacy.js            | 30 ++----
 web_src/js/index.js                           |  2 -
 10 files changed, 177 insertions(+), 149 deletions(-)
 create mode 100644 web_src/js/features/comp/TributeExpander.js

diff --git a/templates/repo/issue/comment_tab.tmpl b/templates/repo/issue/comment_tab.tmpl
index bcb1786692b54..c40e6ddf32e87 100644
--- a/templates/repo/issue/comment_tab.tmpl
+++ b/templates/repo/issue/comment_tab.tmpl
@@ -3,7 +3,7 @@
 {{if not $textareaContent}}{{$textareaContent = .PullRequestTemplate}}{{end}}
 {{if not $textareaContent}}{{$textareaContent = .content}}{{end}}
 
-<div class="field markdown">
+<div class="field">
 	{{template "shared/combomarkdowneditor" (dict
 		"locale" $.locale
 		"MarkdownPreviewUrl" (print .Repository.Link "/markup")
@@ -16,7 +16,7 @@
 </div>
 
 {{if .IsAttachmentEnabled}}
-<div class="field">
-	{{template "repo/upload" .}}
-</div>
+	<div class="field">
+		{{template "repo/upload" .}}
+	</div>
 {{end}}
diff --git a/templates/repo/issue/fields/textarea.tmpl b/templates/repo/issue/fields/textarea.tmpl
index 6127a32d46de6..8e9eacf17c24c 100644
--- a/templates/repo/issue/fields/textarea.tmpl
+++ b/templates/repo/issue/fields/textarea.tmpl
@@ -1,21 +1,25 @@
-<div class="field textarea">
+{{$useMarkdownEditor := not .item.Validations.render}}
+<div class="field {{if $useMarkdownEditor}}combo-editor-dropzone{{end}}">
 	{{template "repo/issue/fields/header" .}}
-	{{/* FIXME: preview markdown result */}}
-	<textarea class="fake" name="form-field-{{.item.ID}}" placeholder="{{.item.Attributes.placeholder}}" {{if and .item.Validations.required .item.Attributes.render}}required{{end}}>{{.item.Attributes.value}}</textarea>
 
-	{{template "shared/combomarkdowneditor" (dict
-		"locale" .Locale
-		"MarkdownPreviewUrl" (print .RepoLink "/markup")
-		"MarkdownPreviewContext" .RepoLink
-		"TextareaName" "content"
-		"TextareaContent" .item.Attributes.value
-		"TextareaPlaceholder"  .item.Attributes.placeholder
-		"DropzoneParentContainer" "form, .ui.form"
-	)}}
-</div>
+	{{/* the real form element to provide the value */}}
+	<textarea class="form-field-real" name="form-field-{{.item.ID}}" placeholder="{{.item.Attributes.placeholder}}" {{if and .item.Validations.required}}required{{end}}>{{.item.Attributes.value}}</textarea>
+
+	{{if $useMarkdownEditor}}
+		{{template "shared/combomarkdowneditor" (dict
+			"locale" .root.locale
+			"ContainerClasses" "gt-hidden"
+			"MarkdownPreviewUrl" (print .root.RepoLink "/markup")
+			"MarkdownPreviewContext" .root.RepoLink
+			"TextareaContent" .item.Attributes.value
+			"TextareaPlaceholder"  .item.Attributes.placeholder
+			"DropzoneParentContainer" ".combo-editor-dropzone"
+		)}}
 
-{{if .IsAttachmentEnabled}}
-<div class="field">
-	{{template "repo/upload" .Context.Data}}
+		{{if .root.IsAttachmentEnabled}}
+		<div class="gt-mt-4 form-field-dropzone gt-hidden">
+			{{template "repo/upload" .root}}
+		</div>
+		{{end}}
+	{{end}}
 </div>
-{{end}}
diff --git a/templates/repo/issue/new_form.tmpl b/templates/repo/issue/new_form.tmpl
index dfe795e137533..c12b8149b0864 100644
--- a/templates/repo/issue/new_form.tmpl
+++ b/templates/repo/issue/new_form.tmpl
@@ -24,7 +24,7 @@
 							{{else if eq .Type "markdown"}}
 								{{template "repo/issue/fields/markdown" dict "Context" $.Context "item" .}}
 							{{else if eq .Type "textarea"}}
-								{{template "repo/issue/fields/textarea" dict "Context" $.Context "item" . "RepoLink" $.RepoLink "Locale" $.locale "IsAttachmentEnabled" $.IsAttachmentEnabled}}
+								{{template "repo/issue/fields/textarea" dict "Context" $.Context "item" . "root" $}}
 							{{else if eq .Type "dropdown"}}
 								{{template "repo/issue/fields/dropdown" dict "Context" $.Context "item" .}}
 							{{else if eq .Type "checkboxes"}}
diff --git a/web_src/js/features/comp/ComboMarkdownEditor.js b/web_src/js/features/comp/ComboMarkdownEditor.js
index 42c10e664eaa6..97b0de0216a0d 100644
--- a/web_src/js/features/comp/ComboMarkdownEditor.js
+++ b/web_src/js/features/comp/ComboMarkdownEditor.js
@@ -5,10 +5,9 @@ import {attachTribute} from '../tribute.js';
 import {hideElem, showElem, autosize} from '../../utils/dom.js';
 import {initEasyMDEImagePaste, initTextareaImagePaste} from './ImagePaste.js';
 import {handleGlobalEnterQuickSubmit} from './QuickSubmit.js';
-import {emojiString} from '../emoji.js';
 import {renderPreviewPanelContent} from '../repo-editor.js';
-import {matchEmoji, matchMention} from '../../utils/match.js';
 import {svg} from '../../svg.js';
+import {initTributeExpander} from './TributeExpander.js';
 
 let elementIdCounter = 0;
 
@@ -43,14 +42,12 @@ class ComboMarkdownEditor {
 
   async init() {
     this.prepareEasyMDEToolbarActions();
+    this.setupContainer();
     this.setupTab();
     this.setupDropzone();
     this.setupTextarea();
-    this.setupExpander();
 
-    if (this.userPreferredEditor === 'easymde') {
-      await this.switchToEasyMDE();
-    }
+    await this.switchToUserPreference();
   }
 
   applyEditorHeights(el, heights) {
@@ -60,6 +57,11 @@ class ComboMarkdownEditor {
     if (heights.maxHeight) el.style.maxHeight = heights.maxHeight;
   }
 
+  setupContainer() {
+    initTributeExpander(this.container.querySelector('text-expander'));
+    this.container.addEventListener('ce-editor-content-changed', (e) => this.options?.onContentChanged?.(this, e));
+  }
+
   setupTextarea() {
     this.textarea = this.container.querySelector('.markdown-text-editor');
     this.textarea._giteaComboMarkdownEditor = this;
@@ -103,64 +105,6 @@ class ComboMarkdownEditor {
     }
   }
 
-  setupExpander() {
-    const expander = this.container.querySelector('text-expander');
-    expander?.addEventListener('text-expander-change', ({detail: {key, provide, text}}) => {
-      if (key === ':') {
-        const matches = matchEmoji(text);
-        if (!matches.length) return provide({matched: false});
-
-        const ul = document.createElement('ul');
-        ul.classList.add('suggestions');
-        for (const name of matches) {
-          const emoji = emojiString(name);
-          const li = document.createElement('li');
-          li.setAttribute('role', 'option');
-          li.setAttribute('data-value', emoji);
-          li.textContent = `${emoji} ${name}`;
-          ul.append(li);
-        }
-
-        provide({matched: true, fragment: ul});
-      } else if (key === '@') {
-        const matches = matchMention(text);
-        if (!matches.length) return provide({matched: false});
-
-        const ul = document.createElement('ul');
-        ul.classList.add('suggestions');
-        for (const {value, name, fullname, avatar} of matches) {
-          const li = document.createElement('li');
-          li.setAttribute('role', 'option');
-          li.setAttribute('data-value', `${key}${value}`);
-
-          const img = document.createElement('img');
-          img.src = avatar;
-          li.append(img);
-
-          const nameSpan = document.createElement('span');
-          nameSpan.textContent = name;
-          li.append(nameSpan);
-
-          if (fullname && fullname.toLowerCase() !== name) {
-            const fullnameSpan = document.createElement('span');
-            fullnameSpan.classList.add('fullname');
-            fullnameSpan.textContent = fullname;
-            li.append(fullnameSpan);
-          }
-
-          ul.append(li);
-        }
-
-        provide({matched: true, fragment: ul});
-      }
-    });
-    expander?.addEventListener('text-expander-value', ({detail}) => {
-      if (detail?.item) {
-        detail.value = detail.item.getAttribute('data-value');
-      }
-    });
-  }
-
   setupDropzone() {
     const dropzoneParentContainer = this.container.getAttribute('data-dropzone-parent-container');
     if (dropzoneParentContainer) {
@@ -270,7 +214,16 @@ class ComboMarkdownEditor {
     return processed;
   }
 
+  async switchToUserPreference() {
+    if (this.userPreferredEditor === 'easymde') {
+      await this.switchToEasyMDE();
+    } else {
+      this.switchToTextarea();
+    }
+  }
+
   switchToTextarea() {
+    if (!this.easyMDE) return;
     showElem(this.textareaMarkdownToolbar);
     if (this.easyMDE) {
       this.easyMDE.toTextArea();
@@ -279,6 +232,8 @@ class ComboMarkdownEditor {
   }
 
   async switchToEasyMDE() {
+    if (this.easyMDE) return;
+
     // EasyMDE's CSS should be loaded via webpack config, otherwise our own styles can not overwrite the default styles.
     const {default: EasyMDE} = await import(/* webpackChunkName: "easymde" */'easymde');
     const easyMDEOpt = {
diff --git a/web_src/js/features/comp/ImagePaste.js b/web_src/js/features/comp/ImagePaste.js
index 9145b24062f26..dc335495a310f 100644
--- a/web_src/js/features/comp/ImagePaste.js
+++ b/web_src/js/features/comp/ImagePaste.js
@@ -25,6 +25,10 @@ function clipboardPastedImages(e) {
   return files;
 }
 
+function triggerEditorContentChanged(target) {
+  target.dispatchEvent(new CustomEvent('ce-editor-content-changed', {bubbles: true}));
+}
+
 class TextareaEditor {
   constructor(editor) {
     this.editor = editor;
@@ -38,6 +42,7 @@ class TextareaEditor {
     editor.selectionStart = startPos;
     editor.selectionEnd = startPos + value.length;
     editor.focus();
+    triggerEditorContentChanged(editor);
   }
 
   replacePlaceholder(oldVal, newVal) {
@@ -54,6 +59,7 @@ class TextareaEditor {
     }
     editor.selectionStart = editor.selectionEnd;
     editor.focus();
+    triggerEditorContentChanged(editor);
   }
 }
 
@@ -70,6 +76,7 @@ class CodeMirrorEditor {
     endPoint.ch = startPoint.ch + value.length;
     editor.setSelection(startPoint, endPoint);
     editor.focus();
+    triggerEditorContentChanged(editor.getTextArea());
   }
 
   replacePlaceholder(oldVal, newVal) {
@@ -84,6 +91,7 @@ class CodeMirrorEditor {
     endPoint.ch += newVal.length;
     editor.setSelection(endPoint, endPoint);
     editor.focus();
+    triggerEditorContentChanged(editor.getTextArea());
   }
 }
 
diff --git a/web_src/js/features/comp/QuickSubmit.js b/web_src/js/features/comp/QuickSubmit.js
index 43424a949f1ec..d598a59655308 100644
--- a/web_src/js/features/comp/QuickSubmit.js
+++ b/web_src/js/features/comp/QuickSubmit.js
@@ -6,7 +6,9 @@ export function handleGlobalEnterQuickSubmit(target) {
   if ($form.length) {
     // here use the event to trigger the submit event (instead of calling `submit()` method directly)
     // otherwise the `areYouSure` handler won't be executed, then there will be an annoying "confirm to leave" dialog
-    $form.trigger('submit');
+    if ($form[0].checkValidity()) {
+      $form.trigger('submit');
+    }
   } else {
     // if no form, then the editor is for an AJAX request, dispatch an event to the target, let the target's event handler to do the AJAX request.
     // the 'ce-' prefix means this is a CustomEvent
diff --git a/web_src/js/features/comp/TributeExpander.js b/web_src/js/features/comp/TributeExpander.js
new file mode 100644
index 0000000000000..815c8e55457a0
--- /dev/null
+++ b/web_src/js/features/comp/TributeExpander.js
@@ -0,0 +1,59 @@
+import {matchEmoji, matchMention} from '../../utils/match.js';
+import {emojiString} from '../emoji.js';
+
+export function initTributeExpander(expander) {
+  expander?.addEventListener('text-expander-change', ({detail: {key, provide, text}}) => {
+    if (key === ':') {
+      const matches = matchEmoji(text);
+      if (!matches.length) return provide({matched: false});
+
+      const ul = document.createElement('ul');
+      ul.classList.add('suggestions');
+      for (const name of matches) {
+        const emoji = emojiString(name);
+        const li = document.createElement('li');
+        li.setAttribute('role', 'option');
+        li.setAttribute('data-value', emoji);
+        li.textContent = `${emoji} ${name}`;
+        ul.append(li);
+      }
+
+      provide({matched: true, fragment: ul});
+    } else if (key === '@') {
+      const matches = matchMention(text);
+      if (!matches.length) return provide({matched: false});
+
+      const ul = document.createElement('ul');
+      ul.classList.add('suggestions');
+      for (const {value, name, fullname, avatar} of matches) {
+        const li = document.createElement('li');
+        li.setAttribute('role', 'option');
+        li.setAttribute('data-value', `${key}${value}`);
+
+        const img = document.createElement('img');
+        img.src = avatar;
+        li.append(img);
+
+        const nameSpan = document.createElement('span');
+        nameSpan.textContent = name;
+        li.append(nameSpan);
+
+        if (fullname && fullname.toLowerCase() !== name) {
+          const fullnameSpan = document.createElement('span');
+          fullnameSpan.classList.add('fullname');
+          fullnameSpan.textContent = fullname;
+          li.append(fullnameSpan);
+        }
+
+        ul.append(li);
+      }
+
+      provide({matched: true, fragment: ul});
+    }
+  });
+  expander?.addEventListener('text-expander-value', ({detail}) => {
+    if (detail?.item) {
+      detail.value = detail.item.getAttribute('data-value');
+    }
+  });
+}
diff --git a/web_src/js/features/repo-issue.js b/web_src/js/features/repo-issue.js
index ec0742bca50f2..1fd91b87a1193 100644
--- a/web_src/js/features/repo-issue.js
+++ b/web_src/js/features/repo-issue.js
@@ -392,8 +392,7 @@ export async function handleReply($el) {
   const $textarea = form.find('textarea');
   let editor = getComboMarkdownEditor($textarea);
   if (!editor) {
-    const editors = await initComboMarkdownEditor(form.find('.combo-markdown-editor'));
-    editor = editors[0];
+    editor = await initComboMarkdownEditor(form.find('.combo-markdown-editor'));
   }
   editor.focus();
   return editor;
@@ -523,8 +522,8 @@ export function initRepoPullRequestReview() {
       td.find("input[name='side']").val(side === 'left' ? 'previous' : 'proposed');
       td.find("input[name='path']").val(path);
 
-      const editors = await initComboMarkdownEditor(td.find('.combo-markdown-editor'));
-      editors[0].focus();
+      const editor = await initComboMarkdownEditor(td.find('.combo-markdown-editor'));
+      editor.focus();
     }
   });
 }
@@ -636,41 +635,58 @@ export function initRepoIssueBranchSelect() {
   $('#branch-select > .item').on('click', changeBranchSelect);
 }
 
-export function initRepoIssueMarkdownTextarea() {
-  const hiddenTextarea = function (target) {
-    const comboMarkdownEditor = $(target).parent().find('.combo-markdown-editor');
-    const markdownTextEditor = comboMarkdownEditor.find('.markdown-text-editor');
-    const dropzone = $(target).parent().next().find('.dropzone');
-    // show combo markdown editor
-    comboMarkdownEditor.removeClass('gt-hidden');
-    markdownTextEditor.trigger('focus');
-    if (dropzone) {
-      dropzone.removeClass('gt-hidden');
-    }
-    // hidden textarea
-    $(target).addClass('gt-hidden');
-  };
+export function initSingleCommentEditor($commentForm) {
+  // pages:
+  // * normal new issue/pr page, no status-button
+  // * issue/pr view page, with comment form, has status-button
+  const opts = {};
+  const $statusButton = $('#status-button');
+  if ($statusButton.length) {
+    $statusButton.on('click', (e) => {
+      e.preventDefault();
+      $('#status').val($statusButton.data('status-val'));
+      $('#comment-form').trigger('submit');
+    });
+    opts.onContentChanged = (editor) => {
+      $statusButton.text($statusButton.attr(editor.value().trim() ? 'data-status-and-comment' : 'data-status'));
+    };
+  }
+  initComboMarkdownEditor($commentForm.find('.combo-markdown-editor'), opts);
+}
 
-  const showTextarea = function (target) {
-    const comboMarkdownEditor = $(target).parent().find('.combo-markdown-editor');
-    const markdownTextEditor = comboMarkdownEditor.find('.markdown-text-editor');
-    const dropzone = $(target).parent().next().find('.dropzone');
-    // hidden combo markdown editor
-    $(comboMarkdownEditor).addClass('gt-hidden');
-    if (dropzone) {
-      $(dropzone).addClass('gt-hidden');
-    }
-    // show textarea
-    $(target).removeClass('gt-hidden');
-    // sync textarea content
-    $(target).val($(markdownTextEditor).val());
-  };
+export function initIssueTemplateCommentEditors($commentForm) {
+  // pages:
+  // * new issue with issue template
+  const $comboFields = $commentForm.find('.combo-editor-dropzone');
 
-  // default display all textarea
-  $('.field.textarea .fake').each((_, target) => showTextarea(target));
+  const initCombo = async ($combo) => {
+    const $dropzoneContainer = $combo.find('.form-field-dropzone');
+    const $formField = $combo.find('.form-field-real');
+    const $markdownEditor = $combo.find('.combo-markdown-editor');
 
-  $('.field.textarea .fake').on('focus', function() {
-    hiddenTextarea(this);
-    $('.field.textarea .fake').not(this).each((_, target) => showTextarea(target));
-  });
+    const editor = await initComboMarkdownEditor($markdownEditor, {
+      onContentChanged: (editor) => {
+        $formField.val(editor.value());
+      }
+    });
+
+    $formField.on('focus', async () => {
+      // deactivate all markdown editors
+      showElem($commentForm.find('.combo-editor-dropzone .form-field-real'));
+      hideElem($commentForm.find('.combo-editor-dropzone .combo-markdown-editor'));
+      hideElem($commentForm.find('.combo-editor-dropzone .form-field-dropzone'));
+
+      // activate this markdown editor
+      hideElem($formField);
+      showElem($markdownEditor);
+      showElem($dropzoneContainer);
+
+      await editor.switchToUserPreference();
+      editor.focus();
+    });
+  };
+
+  for (const el of $comboFields) {
+    initCombo($(el));
+  }
 }
diff --git a/web_src/js/features/repo-legacy.js b/web_src/js/features/repo-legacy.js
index d381250b2e709..964ec16d2b008 100644
--- a/web_src/js/features/repo-legacy.js
+++ b/web_src/js/features/repo-legacy.js
@@ -3,7 +3,7 @@ import {
   initRepoIssueBranchSelect, initRepoIssueCodeCommentCancel, initRepoIssueCommentDelete,
   initRepoIssueComments, initRepoIssueDependencyDelete, initRepoIssueReferenceIssue,
   initRepoIssueTitleEdit, initRepoIssueWipToggle,
-  initRepoPullRequestUpdate, updateIssuesMeta, handleReply
+  initRepoPullRequestUpdate, updateIssuesMeta, handleReply, initIssueTemplateCommentEditors, initSingleCommentEditor,
 } from './repo-issue.js';
 import {initUnicodeEscapeButton} from './repo-unicode-escape.js';
 import {svg} from '../svg.js';
@@ -53,6 +53,13 @@ export function initRepoCommentForm() {
     return;
   }
 
+  if ($commentForm.find('.field.combo-editor-dropzone').length) {
+    // at the moment, if a form has multiple combo-markdown-editors, it must be a issue template form
+    initIssueTemplateCommentEditors($commentForm);
+  } else {
+    initSingleCommentEditor($commentForm);
+  }
+
   function initBranchSelector() {
     const $selectBranch = $('.ui.select-branch');
     const $branchMenu = $selectBranch.find('.reference-list-menu');
@@ -82,27 +89,6 @@ export function initRepoCommentForm() {
     });
   }
 
-  const $statusButton = $('#status-button');
-  $statusButton.on('click', (e) => {
-    e.preventDefault();
-    $('#status').val($statusButton.data('status-val'));
-    $('#comment-form').trigger('submit');
-  });
-
-  // default markdown editor should have no more than one
-  $commentForm.find('.field.markdown .combo-markdown-editor').each((_, comboMarkdownEditor) => {
-    const _promise = initComboMarkdownEditor(comboMarkdownEditor, {
-      onContentChanged(editor) {
-        $statusButton.text($statusButton.attr(editor.value().trim() ? 'data-status-and-comment' : 'data-status'));
-      },
-    });
-  });
-
-  // issue template textarea
-  $commentForm.find('.field.textarea .combo-markdown-editor').each((_, comboMarkdownEditor) => {
-    initComboMarkdownEditor(comboMarkdownEditor);
-  });
-
   initBranchSelector();
 
   // List submits
diff --git a/web_src/js/index.js b/web_src/js/index.js
index 7d4b5f2e34b23..f7cbb24e8562e 100644
--- a/web_src/js/index.js
+++ b/web_src/js/index.js
@@ -25,7 +25,6 @@ import {initCommentContent, initMarkupContent} from './markup/content.js';
 import {initUserAuthLinkAccountView, initUserAuthOauth2} from './features/user-auth.js';
 import {
   initRepoIssueDue,
-  initRepoIssueMarkdownTextarea,
   initRepoIssueReferenceRepositorySearch,
   initRepoIssueTimeTracking,
   initRepoIssueWipTitle,
@@ -147,7 +146,6 @@ onDomReady(() => {
   initRepoIssueDue();
   initRepoIssueList();
   initRepoIssueSidebarList();
-  initRepoIssueMarkdownTextarea();
   initRepoIssueReferenceRepositorySearch();
   initRepoIssueTimeTracking();
   initRepoIssueWipTitle();

From 143dcadbc228dd128c3f01692975f1931fdea535 Mon Sep 17 00:00:00 2001
From: wxiaoguang <wxiaoguang@gmail.com>
Date: Wed, 3 May 2023 00:53:02 +0800
Subject: [PATCH 12/14] fix

---
 templates/repo/issue/fields/textarea.tmpl       | 2 +-
 web_src/js/features/comp/ComboMarkdownEditor.js | 1 -
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/templates/repo/issue/fields/textarea.tmpl b/templates/repo/issue/fields/textarea.tmpl
index 8e9eacf17c24c..49d51eb5a8ba2 100644
--- a/templates/repo/issue/fields/textarea.tmpl
+++ b/templates/repo/issue/fields/textarea.tmpl
@@ -1,4 +1,4 @@
-{{$useMarkdownEditor := not .item.Validations.render}}
+{{$useMarkdownEditor := not .item.Attributes.render}}
 <div class="field {{if $useMarkdownEditor}}combo-editor-dropzone{{end}}">
 	{{template "repo/issue/fields/header" .}}
 
diff --git a/web_src/js/features/comp/ComboMarkdownEditor.js b/web_src/js/features/comp/ComboMarkdownEditor.js
index 97b0de0216a0d..d683988f7d76e 100644
--- a/web_src/js/features/comp/ComboMarkdownEditor.js
+++ b/web_src/js/features/comp/ComboMarkdownEditor.js
@@ -233,7 +233,6 @@ class ComboMarkdownEditor {
 
   async switchToEasyMDE() {
     if (this.easyMDE) return;
-
     // EasyMDE's CSS should be loaded via webpack config, otherwise our own styles can not overwrite the default styles.
     const {default: EasyMDE} = await import(/* webpackChunkName: "easymde" */'easymde');
     const easyMDEOpt = {

From 49b004611e25f6be10f73d10f10a35ea74cf71cf Mon Sep 17 00:00:00 2001
From: yp05327 <576951401@qq.com>
Date: Mon, 8 May 2023 05:13:18 +0000
Subject: [PATCH 13/14] fix

---
 web_src/js/features/repo-issue.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/web_src/js/features/repo-issue.js b/web_src/js/features/repo-issue.js
index 3b29e30be14e4..8ecc7aa4ca2af 100644
--- a/web_src/js/features/repo-issue.js
+++ b/web_src/js/features/repo-issue.js
@@ -664,6 +664,7 @@ export function initRepoIssueGotoID() {
       hideElem($('#hashtag-button'));
     }
   });
+}
 
 export function initSingleCommentEditor($commentForm) {
   // pages:

From d22b77f576f89dacc0d04d257c4cbff96c81e693 Mon Sep 17 00:00:00 2001
From: silverwind <me@silverwind.io>
Date: Mon, 8 May 2023 23:54:05 +0200
Subject: [PATCH 14/14] rename TributeExpander to TextExpander

---
 web_src/js/features/comp/ComboMarkdownEditor.js               | 4 ++--
 .../js/features/comp/{TributeExpander.js => TextExpander.js}  | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)
 rename web_src/js/features/comp/{TributeExpander.js => TextExpander.js} (97%)

diff --git a/web_src/js/features/comp/ComboMarkdownEditor.js b/web_src/js/features/comp/ComboMarkdownEditor.js
index 9e83ba6ea4fe2..103e71daae473 100644
--- a/web_src/js/features/comp/ComboMarkdownEditor.js
+++ b/web_src/js/features/comp/ComboMarkdownEditor.js
@@ -7,7 +7,7 @@ import {initEasyMDEImagePaste, initTextareaImagePaste} from './ImagePaste.js';
 import {handleGlobalEnterQuickSubmit} from './QuickSubmit.js';
 import {renderPreviewPanelContent} from '../repo-editor.js';
 import {easyMDEToolbarActions} from './EasyMDEToolbarActions.js';
-import {initTributeExpander} from './TributeExpander.js';
+import {initTextExpander} from './TextExpander.js';
 
 let elementIdCounter = 0;
 
@@ -58,7 +58,7 @@ class ComboMarkdownEditor {
   }
 
   setupContainer() {
-    initTributeExpander(this.container.querySelector('text-expander'));
+    initTextExpander(this.container.querySelector('text-expander'));
     this.container.addEventListener('ce-editor-content-changed', (e) => this.options?.onContentChanged?.(this, e));
   }
 
diff --git a/web_src/js/features/comp/TributeExpander.js b/web_src/js/features/comp/TextExpander.js
similarity index 97%
rename from web_src/js/features/comp/TributeExpander.js
rename to web_src/js/features/comp/TextExpander.js
index 815c8e55457a0..e2840610dfd8f 100644
--- a/web_src/js/features/comp/TributeExpander.js
+++ b/web_src/js/features/comp/TextExpander.js
@@ -1,7 +1,7 @@
 import {matchEmoji, matchMention} from '../../utils/match.js';
 import {emojiString} from '../emoji.js';
 
-export function initTributeExpander(expander) {
+export function initTextExpander(expander) {
   expander?.addEventListener('text-expander-change', ({detail: {key, provide, text}}) => {
     if (key === ':') {
       const matches = matchEmoji(text);