edwh-editorjs 2.6.0__tar.gz → 2.6.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,12 @@
2
2
 
3
3
  <!--next-version-placeholder-->
4
4
 
5
+ ## v2.6.1 (2026-02-05)
6
+
7
+ ### Fix
8
+
9
+ * **paragraph:** Handle nested HTML in alignment tag closing detection ([`7f910e1`](https://github.com/educationwarehouse/edwh-editorjs/commit/7f910e15103fe25ae0e4d1158deecc15d267d926))
10
+
5
11
  ## v2.6.0 (2026-02-05)
6
12
 
7
13
  ### Fix
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: edwh-editorjs
3
- Version: 2.6.0
3
+ Version: 2.6.1
4
4
  Summary: EditorJS.py
5
5
  Project-URL: Homepage, https://github.com/educationwarehouse/edwh-EditorJS
6
6
  Author-email: SKevo <skevo.cw@gmail.com>, Robin van der Noord <robin.vdn@educationwarehouse.nl>
@@ -0,0 +1,42 @@
1
+ diff --git a/editorjs/blocks.py b/editorjs/blocks.py
2
+ index 9627a82..bc93cee 100644
3
+ --- a/editorjs/blocks.py
4
+ +++ b/editorjs/blocks.py
5
+ @@ -210,14 +210,30 @@ class ParagraphBlock(EditorJSBlock):
6
+
7
+ if child.get("value", "").endswith("/>"):
8
+ # self-closing
9
+ - result.append(EditorJSCustom.to_json(node))
10
+ + result.append(EditorJSCustom.to_json({"children": [child]}))
11
+ else:
12
+ - # <editorjs>something</editorjs> = 3 children
13
+ - result.extend(
14
+ - EditorJSCustom.to_json({"children": nodes[idx : idx + 2]})
15
+ - )
16
+ -
17
+ - skip = 2
18
+ + # <editorjs>...</editorjs> may include nested HTML; find the closing tag
19
+ + end_idx = None
20
+ + for j, next_child in enumerate(nodes[idx + 1 :], start=idx + 1):
21
+ + if next_child.get("type") == "html" and next_child.get(
22
+ + "value", ""
23
+ + ).strip() == "</editorjs>":
24
+ + end_idx = j
25
+ + break
26
+ +
27
+ + if end_idx is None:
28
+ + # fallback to previous behavior if tag is malformed
29
+ + result.extend(
30
+ + EditorJSCustom.to_json({"children": nodes[idx : idx + 2]})
31
+ + )
32
+ + skip = 2
33
+ + else:
34
+ + result.extend(
35
+ + EditorJSCustom.to_json(
36
+ + {"children": nodes[idx : end_idx + 1]}
37
+ + )
38
+ + )
39
+ + skip = end_idx - idx
40
+
41
+ continue
42
+
@@ -0,0 +1,22 @@
1
+ from pathlib import Path
2
+
3
+ from editorjs import EditorJS
4
+
5
+
6
+ def main():
7
+ # blog = Path("blog.md").read_text()
8
+
9
+ ejs = EditorJS.from_json(
10
+ '{"time":1770292823347,"blocks":[{"id":"QKelxSHz2Y","type":"paragraph","data":{"text":"rechts basis"},"tunes":{"alignmentTune":{"alignment":"right"}}},{"id":"IUX70yigzz","type":"paragraph","data":{"text":"links"},"tunes":{"alignmentTune":{"alignment":"left"}}},{"id":"SaWthD_Vlr","type":"paragraph","data":{"text":"<b>rechts duur</b>"},"tunes":{"alignmentTune":{"alignment":"right"}}}],"version":"2.30.7"}'
11
+ )
12
+
13
+ # if html = <editorjs type='alignment' tag='p' alignment='center'> <b>Werk dat ertoe doet </b> </editorjs>
14
+ # assert parse_html(html).type == 'alignment'
15
+
16
+ print(ejs.to_html()) # good
17
+ print(ejs.to_json()) # bad, fixme
18
+ # {"time": 1770292880600, "blocks": [{"type": "paragraph", "data": {"text": ""}, "tunes": {"alignmentTune": {"alignment": "right"}}}, {"type": "paragraph", "data": {"text": "links"}}, {"type": "paragraph", "data": {"text": "<b></b>"}, "tunes": {"alignmentTune": {"alignment": "right"}}}, {"type": "raw", "data": {"html": "</b></editorjs>"}}], "version": "2.30.6"}
19
+
20
+
21
+ if __name__ == "__main__":
22
+ main()
@@ -0,0 +1 @@
1
+ __version__ = "2.6.1"
@@ -187,6 +187,18 @@ class ParagraphBlock(EditorJSBlock):
187
187
 
188
188
  return f"{text}\n\n"
189
189
 
190
+ @staticmethod
191
+ def _find_closing_editorjs_tag(nodes: list, start_idx: int) -> int | None:
192
+ """Find index of closing </editorjs> tag."""
193
+ for idx, node in enumerate(nodes[start_idx:], start=start_idx):
194
+ if (
195
+ node.get("type") == "html"
196
+ and node.get("value", "").strip() == "</editorjs>"
197
+ ):
198
+ return idx
199
+
200
+ return None
201
+
190
202
  @classmethod
191
203
  def to_json(cls, node: MDChildNode) -> list[dict]:
192
204
  result = []
@@ -210,14 +222,17 @@ class ParagraphBlock(EditorJSBlock):
210
222
 
211
223
  if child.get("value", "").endswith("/>"):
212
224
  # self-closing
213
- result.append(EditorJSCustom.to_json(node))
225
+ result.append(EditorJSCustom.to_json({"children": [child]}))
214
226
  else:
215
- # <editorjs>something</editorjs> = 3 children
216
- result.extend(
217
- EditorJSCustom.to_json({"children": nodes[idx : idx + 2]})
227
+ # <editorjs>...</editorjs> may include nested HTML; find the closing tag
228
+ end_idx = cls._find_closing_editorjs_tag(nodes, idx + 1)
229
+ children_slice = (
230
+ nodes[idx : idx + 2]
231
+ if end_idx is None
232
+ else nodes[idx : end_idx + 1]
218
233
  )
219
-
220
- skip = 2
234
+ skip = 2 if end_idx is None else end_idx - idx
235
+ result.extend(EditorJSCustom.to_json({"children": children_slice}))
221
236
 
222
237
  continue
223
238
 
@@ -328,3 +328,48 @@ def test_editorjs_alignment_tag():
328
328
 
329
329
  assert "<b>Werk dat ertoe doet" in html
330
330
  assert "<editorjs" not in html
331
+ assert "<code" not in html
332
+
333
+
334
+ def test_alignment_with_nested_bold_roundtrip():
335
+ """
336
+ Test that alignment tags preserve nested bold HTML through JSON roundtrip.
337
+
338
+ Regression test for issue where nested HTML in alignment tags causes:
339
+ - Text content to disappear
340
+ - Malformed closing tags to appear as separate raw blocks
341
+ """
342
+ # Input: three paragraphs with different alignments, one with nested bold
343
+ input_json = r"""{"time":1770292823347,"blocks":[{"id":"QKelxSHz2Y","type":"paragraph","data":{"text":"rechts basis"},"tunes":{"alignmentTune":{"alignment":"right"}}},{"id":"IUX70yigzz","type":"paragraph","data":{"text":"links"},"tunes":{"alignmentTune":{"alignment":"left"}}},{"id":"SaWthD_Vlr","type":"paragraph","data":{"text":"<b>rechts duur</b>"},"tunes":{"alignmentTune":{"alignment":"right"}}}],"version":"2.30.7"}"""
344
+ # faulty output: {"time": 1770292880600, "blocks": [{"type": "paragraph", "data": {"text": ""}, "tunes": {"alignmentTune": {"alignment": "right"}}}, {"type": "paragraph", "data": {"text": "links"}}, {"type": "paragraph", "data": {"text": "<b></b>"}, "tunes": {"alignmentTune": {"alignment": "right"}}}, {"type": "raw", "data": {"html": "</b></editorjs>"}}], "version": "2.30.6"}
345
+
346
+ e = EditorJS.from_json(input_json)
347
+
348
+ # Convert through markdown and back to JSON
349
+ md = e.to_markdown()
350
+ print("Markdown output:", md)
351
+
352
+ e2 = EditorJS.from_markdown(md)
353
+ output_json = json.loads(e2.to_json())
354
+
355
+ output_str = json.dumps(output_json, indent=2)
356
+ print("Output JSON:", output_str)
357
+
358
+ # Should have exactly 3 blocks (not 4 with a malformed raw block)
359
+ assert len(output_json["blocks"]) == 3
360
+
361
+ # All should be paragraphs (not raw blocks)
362
+ for block in output_json["blocks"]:
363
+ assert block["type"] == "paragraph"
364
+
365
+ # Third block should preserve the bold content
366
+ assert (
367
+ "<b>rechts duur</b>" in output_json["blocks"][2]["data"]["text"]
368
+ or "<b>rechts duur </b>" in output_json["blocks"][2]["data"]["text"]
369
+ )
370
+
371
+ # Third block should preserve alignment
372
+ assert output_json["blocks"][2]["tunes"]["alignmentTune"]["alignment"] == "right"
373
+
374
+ assert "raw" not in output_str
375
+ assert "html" not in output_str
@@ -1,18 +0,0 @@
1
- from pathlib import Path
2
-
3
- from editorjs import EditorJS
4
-
5
-
6
- def main():
7
- blog = Path("blog.md").read_text()
8
-
9
- ejs = EditorJS.from_markdown(blog)
10
-
11
- # if html = <editorjs type='alignment' tag='p' alignment='center'> <b>Werk dat ertoe doet </b> </editorjs>
12
- # assert parse_html(html).type == 'alignment'
13
-
14
- print(ejs.to_html())
15
-
16
-
17
- if __name__ == "__main__":
18
- main()
@@ -1 +0,0 @@
1
- __version__ = "2.6.0"
File without changes
File without changes
File without changes