Skip to content

Commit 5f18d7a

Browse files
jevinjojomariobehling
authored andcommitted
Auto-expanding textarea for frontpage text in event settings (fossasia#1340)
* Add auto-expanding textarea * guard to prevent duplication * inline JS to separate static file and other modifications * initial text area & resize * viewport-size * initial height & scrollbar appears only inside textarea not on page --------- Co-authored-by: Mario Behling <mb@mariobehling.de>
1 parent a5bd92f commit 5f18d7a

File tree

4 files changed

+309
-198
lines changed

4 files changed

+309
-198
lines changed

app/eventyay/base/configurations/default_setting.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from django.utils.translation import gettext_lazy as _
1616
from django.utils.translation import gettext_noop, pgettext, pgettext_lazy
1717
from i18nfield.forms import I18nFormField, I18nTextarea, I18nTextInput
18+
from eventyay.base.forms import I18nAutoExpandingTextarea
1819
from i18nfield.strings import LazyI18nString
1920
from rest_framework import serializers
2021

@@ -2229,7 +2230,7 @@ def primary_font_kwargs():
22292230
'type': LazyI18nString,
22302231
'serializer_class': I18nField,
22312232
'form_class': I18nFormField,
2232-
'form_kwargs': dict(label=_('Frontpage text'), widget=I18nTextarea),
2233+
'form_kwargs': dict(label=_('Frontpage text'), widget=I18nAutoExpandingTextarea),
22332234
},
22342235
'event_info_text': {
22352236
'default': '',

app/eventyay/base/forms/__init__.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,29 @@ def format_output(self, rendered_widgets, id_) -> str:
167167
return super().format_output(rendered_widgets, id_)
168168

169169

170+
class I18nAutoExpandingTextarea(i18nfield.forms.I18nTextarea):
171+
172+
def __init__(self, attrs=None, **kwargs):
173+
default_attrs = {
174+
'class': 'form-control auto-expanding-textarea',
175+
'data-auto-expand': 'true',
176+
'style': 'min-height: 320px; max-height: 400px; overflow-y: auto; resize: vertical; transition: height 0.2s ease-in-out; box-sizing: border-box;'
177+
}
178+
if attrs:
179+
if 'class' in attrs:
180+
default_attrs['class'] = default_attrs['class'] + ' ' + attrs['class']
181+
if 'style' in attrs:
182+
default_attrs['style'] = default_attrs['style'] + '; ' + attrs['style']
183+
attrs_copy = attrs.copy()
184+
attrs_copy.pop('class', None)
185+
attrs_copy.pop('style', None)
186+
default_attrs.update(attrs_copy)
187+
super().__init__(attrs=default_attrs, **kwargs)
188+
189+
class Media:
190+
js = ('eventyay-common/js/auto-expanding-textarea.js',)
191+
192+
170193
class I18nURLFormField(i18nfield.forms.I18nFormField):
171194
"""
172195
Custom form field to handle internationalized URL inputs. It extends the I18nFormField
@@ -202,5 +225,4 @@ def clean(self, value) -> LazyI18nString:
202225
url_validator(val)
203226
else:
204227
url_validator(value.data)
205-
206-
return value
228+
return value
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
(function () {
2+
'use strict';
3+
4+
if (window.autoExpandTextareaInitialized) {
5+
return;
6+
}
7+
window.autoExpandTextareaInitialized = true;
8+
9+
function autoExpandTextarea(textarea) {
10+
if (!textarea || textarea.hasAttribute('data-auto-expand-init')) return;
11+
textarea.setAttribute('data-auto-expand-init', 'true');
12+
13+
function adjustHeight() {
14+
textarea.style.height = 'auto';
15+
requestAnimationFrame(function () {
16+
var computedStyle = window.getComputedStyle(textarea);
17+
var minHeight = parseInt(computedStyle.minHeight, 10) || 320;
18+
var viewportHeight = window.innerHeight;
19+
var computedMaxHeight = parseInt(computedStyle.maxHeight, 10);
20+
21+
// Calculate offset based on viewport to prevent page scrollbar
22+
var offset = 500;
23+
if (viewportHeight < 600) {
24+
offset = 300;
25+
} else if (viewportHeight < 800) {
26+
offset = 400;
27+
} else if (viewportHeight < 1000) {
28+
offset = 450;
29+
} else {
30+
offset = 550;
31+
}
32+
33+
var viewportBasedMax = Math.max(200, viewportHeight - offset);
34+
var maxHeight = Math.min(computedMaxHeight || 400, viewportBasedMax);
35+
var contentHeight = textarea.scrollHeight;
36+
var newHeight = Math.max(minHeight, Math.min(contentHeight, maxHeight));
37+
38+
textarea.style.height = newHeight + 'px';
39+
textarea.style.overflowY = (contentHeight > maxHeight) ? 'auto' : 'hidden';
40+
});
41+
}
42+
43+
textarea.addEventListener('input', adjustHeight);
44+
textarea.addEventListener('paste', function () { setTimeout(adjustHeight, 10); });
45+
46+
adjustHeight();
47+
}
48+
49+
function initializeAutoExpandTextareas() {
50+
var textareas = document.querySelectorAll('textarea[data-auto-expand="true"]');
51+
textareas.forEach(autoExpandTextarea);
52+
}
53+
54+
// Single global resize listener to recalculate heights on viewport change
55+
var globalResizeTimeout;
56+
window.addEventListener('resize', function () {
57+
clearTimeout(globalResizeTimeout);
58+
globalResizeTimeout = setTimeout(initializeAutoExpandTextareas, 150);
59+
});
60+
61+
if (document.readyState === 'loading') {
62+
document.addEventListener('DOMContentLoaded', initializeAutoExpandTextareas);
63+
} else {
64+
initializeAutoExpandTextareas();
65+
}
66+
67+
// Watch for dynamically added textareas (e.g., form fields added via AJAX)
68+
var formContainer = document.querySelector('form') || document.body;
69+
var observer = new MutationObserver(function (mutations) {
70+
mutations.forEach(function (mutation) {
71+
mutation.addedNodes.forEach(function (node) {
72+
if (node.nodeType === 1) {
73+
if (node.matches && node.matches('textarea[data-auto-expand="true"]')) {
74+
autoExpandTextarea(node);
75+
}
76+
if (node.querySelectorAll) {
77+
var textareas = node.querySelectorAll('textarea[data-auto-expand="true"]');
78+
textareas.forEach(autoExpandTextarea);
79+
}
80+
}
81+
});
82+
});
83+
});
84+
observer.observe(formContainer, { childList: true, subtree: true });
85+
})();

0 commit comments

Comments
 (0)