1- From b16e4d99ebb8519b4d8abf5a1d7b4beb3356aa59 Mon Sep 17 00:00:00 2001
1+ From 0a5d6eeb66cbece3a1df154d126536dfab28b324 Mon Sep 17 00:00:00 2001
22From: Bart van der Braak <
[email protected] >
33Date: 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
55
66---
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(-)
1011
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">
1175diff --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
1377--- a/web_src/js/components/PullRequestMergeForm.vue
1478+++ 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);
1680 const showMergeStyleMenu = ref(false);
1781 const showActionForm = ref(false);
1882
1983+ const mergeMessageTextarea = ref<HTMLTextAreaElement | null>(null);
2084+ const cursorLine = ref(1);
2185+ const cursorColumn = ref(1);
86+ + const wordCount = ref(1);
87+ + const lineCount = ref(1);
2288+
2389 const mergeButtonStyleClass = computed(() => {
2490 if (mergeForm.value.allOverridableChecksOk) return 'primary';
2591 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) {
2793 function clearMergeMessage() {
2894 mergeMessageFieldValue.value = mergeForm.value.defaultMergeMessage;
2995 }
@@ -38,12 +104,16 @@ index bafeec6c97..c409c12c6f 100644
38104+ const lines = value.substring(0, pos).split('\n');
39105+ cursorLine.value = lines.length;
40106+ 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;
41111+ }
42112+
43113 </script>
44114
45115 <template>
46- @@ -105,7 +122,19 @@ function clearMergeMessage() {
116+ @@ -105,7 +128,22 @@ function clearMergeMessage() {
47117 <input type="text" name="merge_title_field" v-model="mergeTitleFieldValue">
48118 </div>
49119 <div class="field">
@@ -58,41 +128,99 @@ index bafeec6c97..c409c12c6f 100644
58128+ @keyup="updateCursorPosition"
59129+ @input="updateCursorPosition"
60130+ />
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>
63136+ </div>
64137 <template v-if="mergeMessageFieldValue !== mergeForm.defaultMergeMessage">
65138 <button @click.prevent="clearMergeMessage" class="btn tw-mt-1 tw-p-1 interact-fg" :data-tooltip-content="mergeForm.textClearMergeMessageHint">
66139 {{ mergeForm.textClearMergeMessage }}
67140diff --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
69142--- a/web_src/js/features/comp/ComboMarkdownEditor.ts
70143+++ 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 {
72176
73177 initTextareaMarkdown(this.textarea);
74178 initTextareaEvents(this.textarea, this.dropzone);
75179+
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';
80189+
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 = () => {
82204+ const value = this.textarea.value;
83205+ const pos = this.textarea.selectionStart;
84206+
85207+ const lines = value.substr(0, pos).split('\n');
86208+ const line = lines.length;
87209+ const column = lines[lines.length - 1].length + 1;
88210+
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}`;
90217+ };
91218+
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+ +
96224 }
97225
98226 async setupDropzone() {
0 commit comments