Skip to content

Commit ce56040

Browse files
committed
fix
1 parent 30e814b commit ce56040

10 files changed

+177
-149
lines changed

templates/repo/issue/comment_tab.tmpl

+4-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
{{if not $textareaContent}}{{$textareaContent = .PullRequestTemplate}}{{end}}
44
{{if not $textareaContent}}{{$textareaContent = .content}}{{end}}
55

6-
<div class="field markdown">
6+
<div class="field">
77
{{template "shared/combomarkdowneditor" (dict
88
"locale" $.locale
99
"MarkdownPreviewUrl" (print .Repository.Link "/markup")
@@ -16,7 +16,7 @@
1616
</div>
1717

1818
{{if .IsAttachmentEnabled}}
19-
<div class="field">
20-
{{template "repo/upload" .}}
21-
</div>
19+
<div class="field">
20+
{{template "repo/upload" .}}
21+
</div>
2222
{{end}}

templates/repo/issue/fields/textarea.tmpl

+21-17
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
1-
<div class="field textarea">
1+
{{$useMarkdownEditor := not .item.Validations.render}}
2+
<div class="field {{if $useMarkdownEditor}}combo-editor-dropzone{{end}}">
23
{{template "repo/issue/fields/header" .}}
3-
{{/* FIXME: preview markdown result */}}
4-
<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>
54

6-
{{template "shared/combomarkdowneditor" (dict
7-
"locale" .Locale
8-
"MarkdownPreviewUrl" (print .RepoLink "/markup")
9-
"MarkdownPreviewContext" .RepoLink
10-
"TextareaName" "content"
11-
"TextareaContent" .item.Attributes.value
12-
"TextareaPlaceholder" .item.Attributes.placeholder
13-
"DropzoneParentContainer" "form, .ui.form"
14-
)}}
15-
</div>
5+
{{/* the real form element to provide the value */}}
6+
<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>
7+
8+
{{if $useMarkdownEditor}}
9+
{{template "shared/combomarkdowneditor" (dict
10+
"locale" .root.locale
11+
"ContainerClasses" "gt-hidden"
12+
"MarkdownPreviewUrl" (print .root.RepoLink "/markup")
13+
"MarkdownPreviewContext" .root.RepoLink
14+
"TextareaContent" .item.Attributes.value
15+
"TextareaPlaceholder" .item.Attributes.placeholder
16+
"DropzoneParentContainer" ".combo-editor-dropzone"
17+
)}}
1618

17-
{{if .IsAttachmentEnabled}}
18-
<div class="field">
19-
{{template "repo/upload" .Context.Data}}
19+
{{if .root.IsAttachmentEnabled}}
20+
<div class="gt-mt-4 form-field-dropzone gt-hidden">
21+
{{template "repo/upload" .root}}
22+
</div>
23+
{{end}}
24+
{{end}}
2025
</div>
21-
{{end}}

templates/repo/issue/new_form.tmpl

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
{{else if eq .Type "markdown"}}
2525
{{template "repo/issue/fields/markdown" dict "Context" $.Context "item" .}}
2626
{{else if eq .Type "textarea"}}
27-
{{template "repo/issue/fields/textarea" dict "Context" $.Context "item" . "RepoLink" $.RepoLink "Locale" $.locale "IsAttachmentEnabled" $.IsAttachmentEnabled}}
27+
{{template "repo/issue/fields/textarea" dict "Context" $.Context "item" . "root" $}}
2828
{{else if eq .Type "dropdown"}}
2929
{{template "repo/issue/fields/dropdown" dict "Context" $.Context "item" .}}
3030
{{else if eq .Type "checkboxes"}}

web_src/js/features/comp/ComboMarkdownEditor.js

+19-64
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@ import {attachTribute} from '../tribute.js';
55
import {hideElem, showElem, autosize} from '../../utils/dom.js';
66
import {initEasyMDEImagePaste, initTextareaImagePaste} from './ImagePaste.js';
77
import {handleGlobalEnterQuickSubmit} from './QuickSubmit.js';
8-
import {emojiString} from '../emoji.js';
98
import {renderPreviewPanelContent} from '../repo-editor.js';
10-
import {matchEmoji, matchMention} from '../../utils/match.js';
119
import {svg} from '../../svg.js';
10+
import {initTributeExpander} from './TributeExpander.js';
1211

1312
let elementIdCounter = 0;
1413

@@ -43,14 +42,12 @@ class ComboMarkdownEditor {
4342

4443
async init() {
4544
this.prepareEasyMDEToolbarActions();
45+
this.setupContainer();
4646
this.setupTab();
4747
this.setupDropzone();
4848
this.setupTextarea();
49-
this.setupExpander();
5049

51-
if (this.userPreferredEditor === 'easymde') {
52-
await this.switchToEasyMDE();
53-
}
50+
await this.switchToUserPreference();
5451
}
5552

5653
applyEditorHeights(el, heights) {
@@ -60,6 +57,11 @@ class ComboMarkdownEditor {
6057
if (heights.maxHeight) el.style.maxHeight = heights.maxHeight;
6158
}
6259

60+
setupContainer() {
61+
initTributeExpander(this.container.querySelector('text-expander'));
62+
this.container.addEventListener('ce-editor-content-changed', (e) => this.options?.onContentChanged?.(this, e));
63+
}
64+
6365
setupTextarea() {
6466
this.textarea = this.container.querySelector('.markdown-text-editor');
6567
this.textarea._giteaComboMarkdownEditor = this;
@@ -103,64 +105,6 @@ class ComboMarkdownEditor {
103105
}
104106
}
105107

106-
setupExpander() {
107-
const expander = this.container.querySelector('text-expander');
108-
expander?.addEventListener('text-expander-change', ({detail: {key, provide, text}}) => {
109-
if (key === ':') {
110-
const matches = matchEmoji(text);
111-
if (!matches.length) return provide({matched: false});
112-
113-
const ul = document.createElement('ul');
114-
ul.classList.add('suggestions');
115-
for (const name of matches) {
116-
const emoji = emojiString(name);
117-
const li = document.createElement('li');
118-
li.setAttribute('role', 'option');
119-
li.setAttribute('data-value', emoji);
120-
li.textContent = `${emoji} ${name}`;
121-
ul.append(li);
122-
}
123-
124-
provide({matched: true, fragment: ul});
125-
} else if (key === '@') {
126-
const matches = matchMention(text);
127-
if (!matches.length) return provide({matched: false});
128-
129-
const ul = document.createElement('ul');
130-
ul.classList.add('suggestions');
131-
for (const {value, name, fullname, avatar} of matches) {
132-
const li = document.createElement('li');
133-
li.setAttribute('role', 'option');
134-
li.setAttribute('data-value', `${key}${value}`);
135-
136-
const img = document.createElement('img');
137-
img.src = avatar;
138-
li.append(img);
139-
140-
const nameSpan = document.createElement('span');
141-
nameSpan.textContent = name;
142-
li.append(nameSpan);
143-
144-
if (fullname && fullname.toLowerCase() !== name) {
145-
const fullnameSpan = document.createElement('span');
146-
fullnameSpan.classList.add('fullname');
147-
fullnameSpan.textContent = fullname;
148-
li.append(fullnameSpan);
149-
}
150-
151-
ul.append(li);
152-
}
153-
154-
provide({matched: true, fragment: ul});
155-
}
156-
});
157-
expander?.addEventListener('text-expander-value', ({detail}) => {
158-
if (detail?.item) {
159-
detail.value = detail.item.getAttribute('data-value');
160-
}
161-
});
162-
}
163-
164108
setupDropzone() {
165109
const dropzoneParentContainer = this.container.getAttribute('data-dropzone-parent-container');
166110
if (dropzoneParentContainer) {
@@ -270,7 +214,16 @@ class ComboMarkdownEditor {
270214
return processed;
271215
}
272216

217+
async switchToUserPreference() {
218+
if (this.userPreferredEditor === 'easymde') {
219+
await this.switchToEasyMDE();
220+
} else {
221+
this.switchToTextarea();
222+
}
223+
}
224+
273225
switchToTextarea() {
226+
if (!this.easyMDE) return;
274227
showElem(this.textareaMarkdownToolbar);
275228
if (this.easyMDE) {
276229
this.easyMDE.toTextArea();
@@ -279,6 +232,8 @@ class ComboMarkdownEditor {
279232
}
280233

281234
async switchToEasyMDE() {
235+
if (this.easyMDE) return;
236+
282237
// EasyMDE's CSS should be loaded via webpack config, otherwise our own styles can not overwrite the default styles.
283238
const {default: EasyMDE} = await import(/* webpackChunkName: "easymde" */'easymde');
284239
const easyMDEOpt = {

web_src/js/features/comp/ImagePaste.js

+8
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ function clipboardPastedImages(e) {
2525
return files;
2626
}
2727

28+
function triggerEditorContentChanged(target) {
29+
target.dispatchEvent(new CustomEvent('ce-editor-content-changed', {bubbles: true}));
30+
}
31+
2832
class TextareaEditor {
2933
constructor(editor) {
3034
this.editor = editor;
@@ -38,6 +42,7 @@ class TextareaEditor {
3842
editor.selectionStart = startPos;
3943
editor.selectionEnd = startPos + value.length;
4044
editor.focus();
45+
triggerEditorContentChanged(editor);
4146
}
4247

4348
replacePlaceholder(oldVal, newVal) {
@@ -54,6 +59,7 @@ class TextareaEditor {
5459
}
5560
editor.selectionStart = editor.selectionEnd;
5661
editor.focus();
62+
triggerEditorContentChanged(editor);
5763
}
5864
}
5965

@@ -70,6 +76,7 @@ class CodeMirrorEditor {
7076
endPoint.ch = startPoint.ch + value.length;
7177
editor.setSelection(startPoint, endPoint);
7278
editor.focus();
79+
triggerEditorContentChanged(editor.getTextArea());
7380
}
7481

7582
replacePlaceholder(oldVal, newVal) {
@@ -84,6 +91,7 @@ class CodeMirrorEditor {
8491
endPoint.ch += newVal.length;
8592
editor.setSelection(endPoint, endPoint);
8693
editor.focus();
94+
triggerEditorContentChanged(editor.getTextArea());
8795
}
8896
}
8997

web_src/js/features/comp/QuickSubmit.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ export function handleGlobalEnterQuickSubmit(target) {
66
if ($form.length) {
77
// here use the event to trigger the submit event (instead of calling `submit()` method directly)
88
// otherwise the `areYouSure` handler won't be executed, then there will be an annoying "confirm to leave" dialog
9-
$form.trigger('submit');
9+
if ($form[0].checkValidity()) {
10+
$form.trigger('submit');
11+
}
1012
} else {
1113
// 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.
1214
// the 'ce-' prefix means this is a CustomEvent
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import {matchEmoji, matchMention} from '../../utils/match.js';
2+
import {emojiString} from '../emoji.js';
3+
4+
export function initTributeExpander(expander) {
5+
expander?.addEventListener('text-expander-change', ({detail: {key, provide, text}}) => {
6+
if (key === ':') {
7+
const matches = matchEmoji(text);
8+
if (!matches.length) return provide({matched: false});
9+
10+
const ul = document.createElement('ul');
11+
ul.classList.add('suggestions');
12+
for (const name of matches) {
13+
const emoji = emojiString(name);
14+
const li = document.createElement('li');
15+
li.setAttribute('role', 'option');
16+
li.setAttribute('data-value', emoji);
17+
li.textContent = `${emoji} ${name}`;
18+
ul.append(li);
19+
}
20+
21+
provide({matched: true, fragment: ul});
22+
} else if (key === '@') {
23+
const matches = matchMention(text);
24+
if (!matches.length) return provide({matched: false});
25+
26+
const ul = document.createElement('ul');
27+
ul.classList.add('suggestions');
28+
for (const {value, name, fullname, avatar} of matches) {
29+
const li = document.createElement('li');
30+
li.setAttribute('role', 'option');
31+
li.setAttribute('data-value', `${key}${value}`);
32+
33+
const img = document.createElement('img');
34+
img.src = avatar;
35+
li.append(img);
36+
37+
const nameSpan = document.createElement('span');
38+
nameSpan.textContent = name;
39+
li.append(nameSpan);
40+
41+
if (fullname && fullname.toLowerCase() !== name) {
42+
const fullnameSpan = document.createElement('span');
43+
fullnameSpan.classList.add('fullname');
44+
fullnameSpan.textContent = fullname;
45+
li.append(fullnameSpan);
46+
}
47+
48+
ul.append(li);
49+
}
50+
51+
provide({matched: true, fragment: ul});
52+
}
53+
});
54+
expander?.addEventListener('text-expander-value', ({detail}) => {
55+
if (detail?.item) {
56+
detail.value = detail.item.getAttribute('data-value');
57+
}
58+
});
59+
}

0 commit comments

Comments
 (0)