1
- From b16e4d99ebb8519b4d8abf5a1d7b4beb3356aa59 Mon Sep 17 00:00:00 2001
1
+ From 0a5d6eeb66cbece3a1df154d126536dfab28b324 Mon Sep 17 00:00:00 2001
2
2
From: Bart van der Braak <bart@blender.org>
3
3
Date: Wed, 30 Apr 2025 18:09:40 +0200
4
- Subject: [PATCH 18/18 ] BLENDER: Add Line Length indicator for cursor
4
+ Subject: [PATCH] BLENDER: Add Line Length indicator for cursor
5
5
6
6
---
7
- .../js/components/PullRequestMergeForm.vue | 31 ++++++++++++++++++-
8
- .../js/features/comp/ComboMarkdownEditor.ts | 21 +++++++++++++
9
- 2 files changed, 51 insertions(+), 1 deletion(-)
7
+ templates/repo/editor/commit_form.tmpl | 52 ++++++++++++++++++
8
+ .../js/components/PullRequestMergeForm.vue | 40 +++++++++++++-
9
+ .../js/features/comp/ComboMarkdownEditor.ts | 54 +++++++++++++++++--
10
+ 3 files changed, 142 insertions(+), 4 deletions(-)
10
11
12
+ diff --git a/templates/repo/editor/commit_form.tmpl b/templates/repo/editor/commit_form.tmpl
13
+ index c050324e93..2bb9395645 100644
14
+ --- a/templates/repo/editor/commit_form.tmpl
15
+ +++ b/templates/repo/editor/commit_form.tmpl
16
+ @@ -13,6 +13,58 @@
17
+ </div>
18
+ <div class="field">
19
+ <textarea name="commit_message" placeholder="{{ctx.Locale.Tr "repo.editor.commit_message_desc"}}" rows="5">{{.commit_message}}</textarea>
20
+ + <script>
21
+ + (function () {
22
+ + const textarea = document.querySelector('.commit-form textarea[name="commit_message"]');
23
+ + if (!textarea) return;
24
+ +
25
+ + const statusbar = document.createElement('div');
26
+ + statusbar.className = 'editor-statusbar';
27
+ +
28
+ + const autosave = document.createElement('span');
29
+ + autosave.className = 'autosave';
30
+ +
31
+ + const lines = document.createElement('span');
32
+ + lines.className = 'lines';
33
+ + lines.textContent = '1';
34
+ +
35
+ + const words = document.createElement('span');
36
+ + words.className = 'words';
37
+ + words.textContent = '1';
38
+ +
39
+ + const cursor = document.createElement('span');
40
+ + cursor.className = 'cursor';
41
+ + cursor.textContent = '1:1';
42
+ +
43
+ + statusbar.appendChild(autosave);
44
+ + statusbar.appendChild(lines);
45
+ + statusbar.appendChild(words);
46
+ + statusbar.appendChild(cursor);
47
+ + textarea.parentElement.appendChild(statusbar);
48
+ +
49
+ + function updateStatus() {
50
+ + const value = textarea.value;
51
+ + const pos = textarea.selectionStart;
52
+ +
53
+ + const linesArray = value.substr(0, pos).split('\n');
54
+ + const line = linesArray.length;
55
+ + const column = linesArray[linesArray.length - 1].length + 1;
56
+ +
57
+ + const totalLines = value.split('\n').length;
58
+ + const totalWords = (value.match(/\b\w+\b/g) || []).length;
59
+ +
60
+ + lines.textContent = totalLines.toString();
61
+ + words.textContent = totalWords.toString();
62
+ + cursor.textContent = `${line}:${column}`;
63
+ + }
64
+ +
65
+ + textarea.addEventListener('input', updateStatus);
66
+ + textarea.addEventListener('click', updateStatus);
67
+ + textarea.addEventListener('keyup', updateStatus);
68
+ + updateStatus(); // Initial render
69
+ + })();
70
+ + </script>
71
+ +
72
+ </div>
73
+ <div class="inline field">
74
+ <div class="ui checkbox">
11
75
diff --git a/web_src/js/components/PullRequestMergeForm.vue b/web_src/js/components/PullRequestMergeForm.vue
12
- index bafeec6c97..c409c12c6f 100644
76
+ index bafeec6c97..6f8bcaa6cc 100644
13
77
--- a/web_src/js/components/PullRequestMergeForm.vue
14
78
+++ b/web_src/js/components/PullRequestMergeForm.vue
15
- @@ -26,6 +26,10 @@ const mergeStyleAllowedCount = ref(0);
79
+ @@ -26,6 +26,12 @@ const mergeStyleAllowedCount = ref(0);
16
80
const showMergeStyleMenu = ref(false);
17
81
const showActionForm = ref(false);
18
82
19
83
+ const mergeMessageTextarea = ref<HTMLTextAreaElement | null>(null);
20
84
+ const cursorLine = ref(1);
21
85
+ const cursorColumn = ref(1);
86
+ + const wordCount = ref(1);
87
+ + const lineCount = ref(1);
22
88
+
23
89
const mergeButtonStyleClass = computed(() => {
24
90
if (mergeForm.value.allOverridableChecksOk) return 'primary';
25
91
return autoMergeWhenSucceed.value ? 'primary' : 'red';
26
- @@ -76,6 +80,19 @@ function switchMergeStyle(name, autoMerge = false) {
92
+ @@ -76,6 +82,23 @@ function switchMergeStyle(name, autoMerge = false) {
27
93
function clearMergeMessage() {
28
94
mergeMessageFieldValue.value = mergeForm.value.defaultMergeMessage;
29
95
}
@@ -38,12 +104,16 @@ index bafeec6c97..c409c12c6f 100644
38
104
+ const lines = value.substring(0, pos).split('\n');
39
105
+ cursorLine.value = lines.length;
40
106
+ cursorColumn.value = lines[lines.length - 1].length + 1;
107
+ +
108
+ + // Full content stats
109
+ + lineCount.value = textarea.value.split('\n').length;
110
+ + wordCount.value = textarea.value.trim().split(/\s+/).filter(Boolean).length;
41
111
+ }
42
112
+
43
113
</script>
44
114
45
115
<template>
46
- @@ -105,7 +122,19 @@ function clearMergeMessage() {
116
+ @@ -105,7 +128,22 @@ function clearMergeMessage() {
47
117
<input type="text" name="merge_title_field" v-model="mergeTitleFieldValue">
48
118
</div>
49
119
<div class="field">
@@ -58,41 +128,99 @@ index bafeec6c97..c409c12c6f 100644
58
128
+ @keyup="updateCursorPosition"
59
129
+ @input="updateCursorPosition"
60
130
+ />
61
- + <div class="tw-mt-2 tw-text-sm tw-text-gray-500">
62
- + Line: {{ cursorLine }}, Column: {{ cursorColumn }}
131
+ + <div class="editor-statusbar">
132
+ + <span class="autosave"></span>
133
+ + <span class="lines">{{ lineCount }}</span>
134
+ + <span class="words">{{ wordCount }}</span>
135
+ + <span class="cursor">{{ cursorLine }}:{{ cursorColumn }}</span>
63
136
+ </div>
64
137
<template v-if="mergeMessageFieldValue !== mergeForm.defaultMergeMessage">
65
138
<button @click.prevent="clearMergeMessage" class="btn tw-mt-1 tw-p-1 interact-fg" :data-tooltip-content="mergeForm.textClearMergeMessageHint">
66
139
{{ mergeForm.textClearMergeMessage }}
67
140
diff --git a/web_src/js/features/comp/ComboMarkdownEditor.ts b/web_src/js/features/comp/ComboMarkdownEditor.ts
68
- index bba50a1296..0df15ca1b4 100644
141
+ index bba50a1296..3252e68402 100644
69
142
--- a/web_src/js/features/comp/ComboMarkdownEditor.ts
70
143
+++ b/web_src/js/features/comp/ComboMarkdownEditor.ts
71
- @@ -161,6 +161,27 @@ export class ComboMarkdownEditor {
144
+ @@ -69,6 +69,7 @@ export class ComboMarkdownEditor {
145
+ easyMDE: any;
146
+ easyMDEToolbarActions: any;
147
+ easyMDEToolbarDefault: any;
148
+ + statusbarEl?: HTMLDivElement;
149
+
150
+ textarea: HTMLTextAreaElement & {_giteaComboMarkdownEditor: any};
151
+ textareaMarkdownToolbar: HTMLElement;
152
+ @@ -127,10 +128,10 @@ export class ComboMarkdownEditor {
153
+ this.textareaMarkdownToolbar = this.container.querySelector('markdown-toolbar');
154
+ this.textareaMarkdownToolbar.setAttribute('for', this.textarea.id);
155
+ for (const el of this.textareaMarkdownToolbar.querySelectorAll('.markdown-toolbar-button')) {
156
+ - // upstream bug: The role code is never executed in base MarkdownButtonElement https://github.com/github/markdown-toolbar-element/issues/70
157
+ el.setAttribute('role', 'button');
158
+ - // the editor usually is in a form, so the buttons should have "type=button", avoiding conflicting with the form's submit.
159
+ - if (el.nodeName === 'BUTTON' && !el.getAttribute('type')) el.setAttribute('type', 'button');
160
+ + if (el.nodeName === 'BUTTON' && !el.getAttribute('type')) {
161
+ + el.setAttribute('type', 'button');
162
+ + }
163
+ }
164
+
165
+ const monospaceButton = this.container.querySelector('.markdown-switch-monospace');
166
+ @@ -153,6 +154,8 @@ export class ComboMarkdownEditor {
167
+ easymdeButton.addEventListener('click', async (e) => {
168
+ e.preventDefault();
169
+ this.userPreferredEditor = 'easymde';
170
+ + // Clear statusbar if switching to EasyMDE
171
+ + if (this.statusbarEl) this.statusbarEl.remove();
172
+ await this.switchToEasyMDE();
173
+ });
174
+ }
175
+ @@ -161,6 +164,51 @@ export class ComboMarkdownEditor {
72
176
73
177
initTextareaMarkdown(this.textarea);
74
178
initTextareaEvents(this.textarea, this.dropzone);
75
179
+
76
- + // Cursor Position Tracker
77
- + const positionDisplay = document.createElement('div');
78
- + positionDisplay.className = 'tw-mt-2 tw-text-sm tw-text-gray-500';
79
- + this.container.appendChild(positionDisplay);
180
+ + // === Status bar setup ===
181
+ + const statusbar = document.createElement('div');
182
+ + statusbar.className = 'editor-statusbar';
183
+ +
184
+ + const autosave = document.createElement('span');
185
+ + autosave.className = 'autosave';
186
+ +
187
+ + const linesEl = document.createElement('span');
188
+ + linesEl.className = 'lines';
80
189
+
81
- + const updateCursorPosition = () => {
190
+ + const wordsEl = document.createElement('span');
191
+ + wordsEl.className = 'words';
192
+ +
193
+ + const cursorEl = document.createElement('span');
194
+ + cursorEl.className = 'cursor';
195
+ +
196
+ + statusbar.appendChild(autosave);
197
+ + statusbar.appendChild(linesEl);
198
+ + statusbar.appendChild(wordsEl);
199
+ + statusbar.appendChild(cursorEl);
200
+ + this.container.appendChild(statusbar);
201
+ + this.statusbarEl = statusbar; // So we can remove it when switching to EasyMDE
202
+ +
203
+ + const updateStatus = () => {
82
204
+ const value = this.textarea.value;
83
205
+ const pos = this.textarea.selectionStart;
84
206
+
85
207
+ const lines = value.substr(0, pos).split('\n');
86
208
+ const line = lines.length;
87
209
+ const column = lines[lines.length - 1].length + 1;
88
210
+
89
- + positionDisplay.textContent = `Line: ${line}, Column: ${column}`;
211
+ + const totalLines = value.split('\n').length;
212
+ + const totalWords = (value.match(/\b\w+\b/g) || []).length;
213
+ +
214
+ + linesEl.textContent = totalLines.toString();
215
+ + wordsEl.textContent = totalWords.toString();
216
+ + cursorEl.textContent = `${line}:${column}`;
90
217
+ };
91
218
+
92
- + this.textarea.addEventListener('input', updateCursorPosition);
93
- + this.textarea.addEventListener('click', updateCursorPosition);
94
- + this.textarea.addEventListener('keyup', updateCursorPosition);
95
- + updateCursorPosition();
219
+ + this.textarea.addEventListener('input', updateStatus);
220
+ + this.textarea.addEventListener('click', updateStatus);
221
+ + this.textarea.addEventListener('keyup', updateStatus);
222
+ + updateStatus();
223
+ +
96
224
}
97
225
98
226
async setupDropzone() {
0 commit comments