From 9ee0798006c50fe25c649525b59fe35a540486ac Mon Sep 17 00:00:00 2001 From: Oracle Date: Mon, 11 Aug 2025 18:35:09 +0200 Subject: [PATCH] Codearea: Added tab-size and tab insertion to the widget --- asyncron/base/admin.py | 2 +- .../templates/asyncron/codearea-widget.html | 61 ++++++++++++++++++- asyncron/widgets.py | 53 +++++++++++++++- 3 files changed, 109 insertions(+), 7 deletions(-) diff --git a/asyncron/base/admin.py b/asyncron/base/admin.py index 847a899..0385e8f 100644 --- a/asyncron/base/admin.py +++ b/asyncron/base/admin.py @@ -11,7 +11,7 @@ class BaseModelAdmin( admin.ModelAdmin ): def formfield_for_dbfield( self, db_field, **kwargs ): formfield = super().formfield_for_dbfield(db_field, **kwargs) if isinstance( formfield, JSONField ): - formfield.widget = Codearea( language = "json" ) + formfield.widget = Codearea( language = "json-pretty" ) custom_widget = getattr(self, "field_widgets", {}).get(db_field.name, None) if custom_widget: diff --git a/asyncron/templates/asyncron/codearea-widget.html b/asyncron/templates/asyncron/codearea-widget.html index a511afb..41fc37c 100644 --- a/asyncron/templates/asyncron/codearea-widget.html +++ b/asyncron/templates/asyncron/codearea-widget.html @@ -35,6 +35,7 @@ padding: 5px; font-size: 1em; border-radius: 3px; + tab-size: {{ tab_size }}; } @@ -44,11 +45,65 @@ const textarea = codearea.querySelector('textarea'); const code = codearea.querySelector('code'); - textarea.addEventListener('input', (e) => { - code.innerHTML = textarea.value; + //function encode(e){return e.replace(/[^]/g,function(e){return"&#"+e.charCodeAt(0)+";"})} + function sync(){ + code.innerHTML = new Option(textarea.value).innerHTML; //encode(textarea.value); delete code.dataset.highlighted; hljs.highlightElement( code ) - }); + } hljs.highlightElement( code ) + + textarea.addEventListener('keydown', function o(e){ + if (e.key != 'Tab') return; + + e.preventDefault(); + + //Largly From: https://stackoverflow.com/a/45396754 + if (this.selectionStart == this.selectionEnd){ + document.execCommand('insertText', false, "\t"); + return; + } + + // Block indent/unindent trashes undo stack. + // Select whole lines + var selStart = this.selectionStart; + var selEnd = this.selectionEnd; + var text = this.value; + while (selStart > 0 && text[selStart-1] != '\n') + selStart--; + while (selEnd > 0 && text[selEnd-1]!='\n' && selEnd < text.length) + selEnd++; + + // Get selected text + var lines = text.substr(selStart, selEnd - selStart).split('\n'); + + // Insert tabs + for (var i=0; i sync() ); + })() diff --git a/asyncron/widgets.py b/asyncron/widgets.py index 4387d15..eeaeed0 100644 --- a/asyncron/widgets.py +++ b/asyncron/widgets.py @@ -1,18 +1,65 @@ - from django.template import loader from django.forms import Textarea +import json class Codearea( Textarea ): - def __init__( self, *args, language = "auto", **kwargs ): + def __init__( self, + *args, + language = "auto", + spell_check = False, + extra_rows = 2, extra_cols = 10, + min_rows = 10, min_cols = 40, + tab_size = 4, + **kwargs + ): self.language = language + self.spell_check = spell_check + self.extra_rows = extra_rows + self.extra_cols = extra_cols + self.min_rows = min_rows + self.min_cols = min_cols + self.tab_size = tab_size + super().__init__( *args, **kwargs ) def render( self, name, value, attrs = None, renderer = None ): + if not self.spell_check: + attrs.update({ + "autocomplete": "off", + "autocorrect": "off", + "autocapitalize": "off", + "spellcheck": "false" + }) + + value = value or "" + + if self.language == "json-pretty": + self.language = "json" + value = json.dumps(json.loads(value), indent = 4 ) + + + if self.extra_rows is not False: + attrs.update( + rows = max( value.count("\n") + self.extra_rows, self.min_rows ) + ) + + if self.extra_cols is not False: + try: + max_line_chars = max( len(line) + line.count("\t") * (self.tab_size - 1) for line in value.split("\n") ) + except ValueError: + max_line_chars = 0 + + attrs.update( + cols = max( max_line_chars + self.extra_cols, self.min_cols ) + ) + + return loader.get_template("asyncron/codearea-widget.html").render({ "textarea": super().render( name, value, attrs, renderer ), "value": value, "target": name, - "language": self.language + "tab_size": self.tab_size, + "language": self.language, })