Skip to content

Commit 54b30ae

Browse files
authored
Merge pull request #16 from 2ik/dev
Inline Editing support
2 parents 79126e5 + 97c063f commit 54b30ae

File tree

13 files changed

+283
-128
lines changed

13 files changed

+283
-128
lines changed

README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ INSTALLED_APPS = [
2626
]
2727
```
2828

29+
## Upgrade
30+
31+
```bash
32+
pip install django-editorjs-fields --upgrade
33+
python manage.py collectstatic # upgrade js and css files
34+
```
35+
36+
2937
## Usage
3038

3139
Add code in your model
@@ -282,8 +290,8 @@ file.
282290
| `EDITORJS_IMAGE_UPLOAD_PATH` | Path uploads images | `uploads/images/` | `str` |
283291
| `EDITORJS_IMAGE_UPLOAD_PATH_DATE` | Subdirectories | `%Y/%m/` | `str` |
284292
| `EDITORJS_IMAGE_NAME_ORIGINAL` | To use the original name of the image file? | `False` | `bool` |
285-
| `EDITORJS_IMAGE_NAME` | Image file name. Ignored when `EDITORJS_IMAGE_NAME_ORIGINAL` is `True` | `token_urlsafe(8)` | `callable(filename: str, file: django.core.files.uploadedfile.InMemoryUploadedFile)` ([docs](https://docs.djangoproject.com/en/3.0/ref/files/uploads/)) |
286-
| `EDITORJS_VERSION` | Version Editor.js | `2.22.2` | `str` |
293+
| `EDITORJS_IMAGE_NAME` | Image file name. Ignored when `EDITORJS_IMAGE_NAME_ORIGINAL` is `True` | `token_urlsafe(8)` | `callable(filename: str, file: InMemoryUploadedFile)` ([docs](https://docs.djangoproject.com/en/3.0/ref/files/uploads/)) |
294+
| `EDITORJS_VERSION` | Version Editor.js | `2.22.3` | `str` |
287295

288296
For `EDITORJS_IMAGE_NAME` was used `from secrets import token_urlsafe`
289297

django_editorjs_fields/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "0.2.2"
1+
__version__ = "0.2.3"
22

33
from .fields import EditorJsJSONField, EditorJsTextField
44
from .widgets import EditorJsWidget

django_editorjs_fields/fields.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ class EditorJsTextField(EditorJsFieldMixin, FieldMixin):
6767
def __init__(self, plugins=None, tools=None, **kwargs):
6868
super().__init__(plugins, tools, **kwargs)
6969

70+
def clean(self, value, model_instance):
71+
if value == 'null':
72+
value = None
73+
74+
return super().clean(value, model_instance)
75+
7076

7177
class EditorJsJSONField(EditorJsFieldMixin, JSONField if HAS_JSONFIELD else FieldMixin):
7278
# pylint: disable=useless-super-delegation
Lines changed: 139 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,118 +1,173 @@
1-
function initEditorJsField(field_id, config) {
2-
var pluginName = "django_editorjs_fields - " + field_id
1+
;(function () {
2+
var pluginName = "django_editorjs_fields"
33
var pluginHelp =
44
"Write about the issue here: https://github.com/2ik/django-editorjs-fields/issues"
5-
var textarea = document.getElementById(field_id)
6-
var holder = textarea.nextElementSibling
7-
var text = textarea.value.trim()
85

9-
if (text) {
6+
function initEditorJsPlugin() {
7+
var fields = document.querySelectorAll("[data-editorjs-textarea]")
8+
9+
for (let i = 0; i < fields.length; i++) {
10+
initEditorJsField(fields[i])
11+
}
12+
}
13+
14+
function initEditorJsField(textarea) {
15+
if (!textarea) {
16+
logError("bad textarea")
17+
return false
18+
}
19+
20+
var id = textarea.getAttribute("id")
21+
22+
if (!id) {
23+
logError("empty field 'id'")
24+
holder.remove()
25+
return false
26+
}
27+
28+
var holder = document.getElementById(id + "_editorjs_holder")
29+
30+
if (!holder) {
31+
logError("holder not found")
32+
holder.remove()
33+
return false
34+
}
35+
36+
if (id.indexOf("__prefix__") !== -1) return
37+
1038
try {
11-
text = JSON.parse(text)
39+
var config = JSON.parse(textarea.getAttribute("data-config"))
1240
} catch (error) {
1341
console.error(error)
14-
console.error(
15-
pluginName +
16-
" - invalid json data from the database. Clear the field manually. " +
17-
pluginHelp
42+
logError(
43+
"invalid 'data-config' on field: " + id + " . Clear the field manually"
1844
)
1945
holder.remove()
2046
return false
2147
}
22-
}
2348

24-
textarea.style.display = "none" // remove old textarea
49+
var text = textarea.value.trim()
50+
51+
if (text) {
52+
try {
53+
text = JSON.parse(text)
54+
} catch (error) {
55+
console.error(error)
56+
logError(
57+
"invalid json data from the database. Clear the field manually"
58+
)
59+
holder.remove()
60+
return false
61+
}
62+
}
2563

26-
var editorConfig = {
27-
id: field_id,
28-
holder: holder,
29-
data: text,
30-
}
64+
textarea.style.display = "none" // remove old textarea
65+
66+
var editorConfig = {
67+
id: id,
68+
holder: holder,
69+
data: text,
70+
}
71+
72+
if ("tools" in config) {
73+
// set config
74+
var tools = config.tools
3175

32-
if ("tools" in config) {
33-
// set config
34-
var tools = config.tools
76+
for (var plugin in tools) {
77+
var cls = tools[plugin].class
3578

36-
for (var plugin in tools) {
37-
var cls = tools[plugin].class
79+
if (cls && window[cls] != undefined) {
80+
tools[plugin].class = eval(cls)
81+
continue
82+
}
3883

39-
if (cls && window[cls] != undefined) {
40-
tools[plugin].class = eval(cls)
41-
continue
84+
delete tools[plugin]
85+
logError("[" + plugin + "] Class " + cls + " Not Found")
4286
}
4387

44-
delete tools[plugin]
45-
console.error(
46-
pluginName +
47-
" - [" +
48-
plugin +
49-
"] Class " +
50-
cls +
51-
" Not Found. " +
52-
pluginHelp
53-
)
88+
editorConfig.tools = tools
5489
}
5590

56-
editorConfig.tools = tools
57-
}
91+
if ("autofocus" in config) {
92+
editorConfig.autofocus = !!config.autofocus
93+
}
5894

59-
if ("autofocus" in config) {
60-
editorConfig.autofocus = !!config.autofocus
61-
}
95+
if ("hideToolbar" in config) {
96+
editorConfig.hideToolbar = !!config.hideToolbar
97+
}
6298

63-
if ("hideToolbar" in config) {
64-
editorConfig.hideToolbar = !!config.hideToolbar
65-
}
99+
if ("inlineToolbar" in config) {
100+
editorConfig.inlineToolbar = config.inlineToolbar
101+
}
66102

67-
if ("inlineToolbar" in config) {
68-
editorConfig.inlineToolbar = config.inlineToolbar
69-
}
103+
if ("readOnly" in config) {
104+
editorConfig.readOnly = config.readOnly
105+
}
70106

71-
if ("readOnly" in config) {
72-
editorConfig.readOnly = config.readOnly
73-
}
107+
if ("minHeight" in config) {
108+
editorConfig.minHeight = config.minHeight || 300
109+
}
74110

75-
if ("minHeight" in config) {
76-
editorConfig.minHeight = config.minHeight || 300
77-
}
111+
if ("logLevel" in config) {
112+
editorConfig.logLevel = config.logLevel || "VERBOSE"
113+
} else {
114+
editorConfig.logLevel = "ERROR"
115+
}
78116

79-
if ("logLevel" in config) {
80-
editorConfig.logLevel = config.logLevel || "VERBOSE"
81-
} else {
82-
editorConfig.logLevel = "ERROR"
83-
}
117+
if ("placeholder" in config) {
118+
editorConfig.placeholder = config.placeholder || "Type text..."
119+
} else {
120+
editorConfig.placeholder = "Type text..."
121+
}
84122

85-
if ("placeholder" in config) {
86-
editorConfig.placeholder = config.placeholder || "Type text..."
87-
} else {
88-
editorConfig.placeholder = "Type text..."
89-
}
123+
if ("defaultBlock" in config) {
124+
editorConfig.defaultBlock = config.defaultBlock || "paragraph"
125+
}
90126

91-
if ("defaultBlock" in config) {
92-
editorConfig.defaultBlock = config.defaultBlock || "paragraph"
93-
}
127+
if ("sanitizer" in config) {
128+
editorConfig.sanitizer = config.sanitizer || {
129+
p: true,
130+
b: true,
131+
a: true,
132+
}
133+
}
134+
135+
if ("i18n" in config) {
136+
editorConfig.i18n = config.i18n || {}
137+
}
94138

95-
if ("sanitizer" in config) {
96-
editorConfig.sanitizer = config.sanitizer || {
97-
p: true,
98-
b: true,
99-
a: true,
139+
editorConfig.onChange = function () {
140+
editor
141+
.save()
142+
.then(function (data) {
143+
if (data.blocks.length) {
144+
textarea.value = JSON.stringify(data)
145+
} else {
146+
textarea.value = 'null'
147+
}
148+
})
149+
.catch(function (error) {
150+
console.log("save error: ", error)
151+
})
100152
}
153+
var editor = new EditorJS(editorConfig)
154+
holder.setAttribute("data-processed", 1)
101155
}
102156

103-
if ("i18n" in config) {
104-
editorConfig.i18n = config.i18n || {}
157+
function logError(msg) {
158+
console.error(pluginName + " - " + msg + ". " + pluginHelp)
105159
}
106160

107-
editorConfig.onChange = function () {
108-
editor
109-
.save()
110-
.then(function (data) {
111-
textarea.value = JSON.stringify(data)
112-
})
113-
.catch(function (error) {
114-
console.log("save error: ", error)
115-
})
161+
addEventListener("DOMContentLoaded", initEditorJsPlugin)
162+
163+
// Event
164+
if (typeof django === "object" && django.jQuery) {
165+
django.jQuery(document).on("formset:added", function (event, $row) {
166+
var textarea = $row.find("[data-editorjs-textarea]").get(0)
167+
168+
if (textarea) {
169+
initEditorJsField(textarea)
170+
}
171+
})
116172
}
117-
var editor = new EditorJS(editorConfig)
118-
}
173+
})()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<textarea data-editorjs-textarea name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %} data-config='{{ widget.config }}'>{% if widget.value %}{{ widget.value }}{% endif %}</textarea>
2+
<div data-editorjs-holder id="{{ widget.attrs.id }}_editorjs_holder" class="editorjs-holder"></div>

django_editorjs_fields/templatetags/editorjs.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def generate_quote(data):
8383

8484
def generate_code(data):
8585
code = data.get('code')
86-
return f'<code class="code">{code}</div>'
86+
return f'<code class="code">{code}</code>'
8787

8888

8989
def generate_raw(data):
@@ -101,6 +101,9 @@ def generate_embed(data):
101101

102102
@register.filter(is_safe=True)
103103
def editorjs(value):
104+
if not value or value == 'null':
105+
return ""
106+
104107
if not isinstance(value, dict):
105108
try:
106109
value = json.loads(value)

django_editorjs_fields/widgets.py

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,27 @@
11
import json
22

3+
from django.core.serializers.json import DjangoJSONEncoder
34
from django.forms import Media, widgets
4-
from django.utils.functional import cached_property
5+
from django.forms.renderers import get_default_renderer
6+
from django.forms.utils import flatatt
7+
from django.utils.encoding import force_str
8+
from django.utils.functional import Promise, cached_property
9+
from django.utils.html import conditional_escape
510
from django.utils.safestring import mark_safe
611

712
from .config import CONFIG_TOOLS, PLUGINS, PLUGINS_KEYS, VERSION
813

914

15+
class LazyEncoder(DjangoJSONEncoder):
16+
def default(self, obj):
17+
if isinstance(obj, Promise):
18+
return force_str(obj)
19+
return super().default(obj)
20+
21+
22+
json_encode = LazyEncoder().encode
23+
24+
1025
class EditorJsWidget(widgets.Textarea):
1126
def __init__(self, plugins=None, tools=None, config=None, **kwargs):
1227
self.plugins = PLUGINS if plugins is None else plugins
@@ -69,17 +84,17 @@ def media(self):
6984
)
7085

7186
def render(self, name, value, attrs=None, renderer=None):
72-
html = super().render(name, value, attrs)
73-
74-
html += '''
75-
<div data-editorjs-holder id="%(id)s_editorjs_holder" class="editorjs-holder"></div>
76-
<script defer>
77-
addEventListener('DOMContentLoaded', function () {
78-
initEditorJsField('%(id)s', %(config)s);
79-
})
80-
</script>''' % {
81-
'id': attrs.get('id'),
82-
'config': json.dumps(self.configuration()),
83-
}
84-
85-
return mark_safe(html)
87+
if value is None:
88+
value = ''
89+
90+
if renderer is None:
91+
renderer = get_default_renderer()
92+
93+
return mark_safe(renderer.render("django-editorjs-fields/widget.html", {
94+
'widget': {
95+
'name': name,
96+
'value': conditional_escape(force_str(value)),
97+
'attrs': self.build_attrs(self.attrs, attrs),
98+
'config': json_encode(self.configuration()),
99+
}
100+
}))

0 commit comments

Comments
 (0)