edwh-editorjs 2.4.0__tar.gz → 2.5.0__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.5.0 (2025-03-20)
6
+
7
+ ### Feature
8
+
9
+ * Improve markdown conversion and image handling in EditorJS blocks ([`80d0fc0`](https://github.com/educationwarehouse/edwh-editorjs/commit/80d0fc00a4e39b279de35998d1a85a37282c58a7))
10
+
5
11
  ## v2.4.0 (2024-12-02)
6
12
 
7
13
  ### Feature
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: edwh-editorjs
3
- Version: 2.4.0
3
+ Version: 2.5.0
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>
@@ -13,6 +13,7 @@ Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
14
  Classifier: Programming Language :: Python :: 3.13
15
15
  Requires-Python: >=3.10
16
+ Requires-Dist: html2markdown
16
17
  Requires-Dist: humanize
17
18
  Requires-Dist: markdown2
18
19
  Requires-Dist: mdast
@@ -0,0 +1 @@
1
+ __version__ = "2.5.0"
@@ -8,6 +8,7 @@ import typing as t
8
8
  from html.parser import HTMLParser
9
9
  from urllib.parse import urlparse
10
10
 
11
+ import html2markdown
11
12
  import humanize
12
13
  import markdown2
13
14
 
@@ -53,6 +54,7 @@ def process_styled_content(item: MDChildNode, strict: bool = True) -> str:
53
54
  A formatted HTML string based on the item type.
54
55
  """
55
56
  _type = item.get("type")
57
+
56
58
  html_wrappers = {
57
59
  "text": "{value}",
58
60
  "html": "{value}",
@@ -82,9 +84,16 @@ def process_styled_content(item: MDChildNode, strict: bool = True) -> str:
82
84
 
83
85
 
84
86
  def default_to_text(node: MDChildNode):
85
- return "".join(
86
- process_styled_content(child) for child in node.get("children", [])
87
- ) or process_styled_content(node)
87
+ if node["type"] == "paragraph":
88
+ return "".join(
89
+ process_styled_content(child) for child in node.get("children", [])
90
+ )
91
+ else:
92
+ return process_styled_content(node)
93
+
94
+ # return "".join(
95
+ # process_styled_content(child) for child in node.get("children", [])
96
+ # ) or process_styled_content(node)
88
97
 
89
98
 
90
99
  @block("heading", "header")
@@ -173,6 +182,9 @@ class ParagraphBlock(EditorJSBlock):
173
182
  }
174
183
  )
175
184
 
185
+ # deal with bold etc:
186
+ text = html2markdown.convert(text)
187
+
176
188
  return f"{text}\n\n"
177
189
 
178
190
  @classmethod
@@ -384,7 +396,7 @@ class CodeBlock(EditorJSBlock):
384
396
  @classmethod
385
397
  def to_markdown(cls, data: EditorChildData) -> str:
386
398
  code = data.get("code", "")
387
- return f"```\n" f"{code}" f"\n```\n"
399
+ return f"```\n{code}\n```\n"
388
400
 
389
401
  @classmethod
390
402
  def to_json(cls, node: MDChildNode) -> list[dict]:
@@ -411,11 +423,8 @@ class ImageBlock(EditorJSBlock):
411
423
  with_background = "1" if data.get("withBackground") else ""
412
424
  stretched = "1" if data.get("stretched") else ""
413
425
 
414
- if any((with_border, with_background, stretched)):
415
- # custom type to support custom options:
416
- return f"""<editorjs type="image" caption="{caption}" border="{with_border}" background="{with_background}" stretched="{stretched}" url="{url}" />\n\n"""
417
- else:
418
- return f"""![{caption}]({url} "{caption}")\n\n"""
426
+ # always custom type so we can render as <figure> instead of markdown2's default (simple <img>)
427
+ return f"""<editorjs type="image" caption="{caption}" border="{with_border}" background="{with_background}" stretched="{stretched}" url="{url}" />\n\n"""
419
428
 
420
429
  @classmethod
421
430
  def _caption(cls, node: MDChildNode):
@@ -446,11 +455,14 @@ class ImageBlock(EditorJSBlock):
446
455
  border = node.get("border") or ""
447
456
 
448
457
  return f"""
449
- <div class="ce-block {stretched and 'ce-block--stretched'}">
458
+ <div class="ce-block {stretched and "ce-block--stretched"}">
450
459
  <div class="ce-block__content">
451
- <div class="cdx-block image-tool image-tool--filled {background and 'image-tool--withBackground'} {stretched and 'image-tool--stretched'} {border and 'image-tool--withBorder'}">
460
+ <div class="cdx-block image-tool image-tool--filled {background and "image-tool--withBackground"} {stretched and "image-tool--stretched"} {border and "image-tool--withBorder"}">
452
461
  <div class="image-tool__image">
453
- <img class="image-tool__image-picture" src="{url}" title="{caption}" alt="{caption}">
462
+ <figure>
463
+ <img class="image-tool__image-picture" src="{url}" title="{caption}" alt="{caption}">
464
+ <figcaption>{caption}</figcaption>
465
+ </figure>
454
466
  </div>
455
467
  </div>
456
468
  </div>
@@ -467,7 +479,7 @@ class QuoteBlock(EditorJSBlock):
467
479
  result = f"> {text}\n"
468
480
  if caption := data.get("caption", ""):
469
481
  result += f"> <cite>{caption}</cite>\n"
470
- return result
482
+ return result + "\n"
471
483
 
472
484
  @classmethod
473
485
  def to_json(cls, node: MDChildNode) -> list[dict]:
@@ -493,12 +505,13 @@ class QuoteBlock(EditorJSBlock):
493
505
 
494
506
  @classmethod
495
507
  def to_text(cls, node: MDChildNode) -> str:
496
- return default_to_text(node)
508
+ return "".join(
509
+ process_styled_content(child) for child in node.get("children", [])
510
+ )
497
511
 
498
512
 
499
513
  @block("raw", "html")
500
514
  class RawBlock(EditorJSBlock):
501
-
502
515
  @classmethod
503
516
  def to_markdown(cls, data: EditorChildData) -> str:
504
517
  text = data.get("html", "")
@@ -521,7 +534,6 @@ class RawBlock(EditorJSBlock):
521
534
 
522
535
  @block("table")
523
536
  class TableBlock(EditorJSBlock):
524
-
525
537
  @classmethod
526
538
  def to_markdown(cls, data: EditorChildData) -> str:
527
539
  """
@@ -640,7 +652,6 @@ class LinkBlock(EditorJSBlock):
640
652
 
641
653
  @block("attaches")
642
654
  class AttachmentBlock(EditorJSBlock):
643
-
644
655
  @classmethod
645
656
  def to_markdown(cls, data: EditorChildData) -> str:
646
657
  title = data.get("title", "")
@@ -705,7 +716,7 @@ class AttachmentBlock(EditorJSBlock):
705
716
  </div>
706
717
  {file_size}
707
718
  </div>
708
- <a class="cdx-attaches__download-button" href="{node.get('file', '')}" target="_blank" rel="nofollow noindex noreferrer" title="{node.get('name', '')}">
719
+ <a class="cdx-attaches__download-button" href="{node.get("file", "")}" target="_blank" rel="nofollow noindex noreferrer" title="{node.get("name", "")}">
709
720
  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="M7 10L11.8586 14.8586C11.9367 14.9367 12.0633 14.9367 12.1414 14.8586L17 10"></path></svg>
710
721
  </a>
711
722
  </div>
@@ -757,7 +768,6 @@ class AlignmentBlock(EditorJSBlock):
757
768
 
758
769
  @block("embed")
759
770
  class EmbedBlock(EditorJSBlock):
760
-
761
771
  @classmethod
762
772
  def to_markdown(cls, data: EditorChildData) -> str:
763
773
  service = data.get("service", "")
@@ -135,7 +135,6 @@ class EditorJS:
135
135
  Export HTML string
136
136
  """
137
137
  md = self.to_markdown()
138
- # todo: deal with custom elements like linktool, attaches
139
138
  return self._md.convert(md)
140
139
 
141
140
  def __repr__(self):
@@ -27,8 +27,9 @@ requires-python = ">=3.10"
27
27
 
28
28
  dependencies = [
29
29
  # "bleach",
30
- "mdast",
31
- "markdown2",
30
+ "mdast", # markdown -> ast
31
+ "markdown2", # markdown -> html
32
+ "html2markdown", # html -> markdown
32
33
  "humanize",
33
34
  ]
34
35
 
@@ -185,7 +185,8 @@ asdfsdajgdsjaklgkjds
185
185
 
186
186
 
187
187
  def test_code():
188
- e = EditorJS.from_markdown(textwrap.dedent("""
188
+ e = EditorJS.from_markdown(
189
+ textwrap.dedent("""
189
190
  Read code:
190
191
 
191
192
  ```
@@ -193,7 +194,8 @@ def test_code():
193
194
  ```
194
195
 
195
196
  End of code
196
- """))
197
+ """)
198
+ )
197
199
 
198
200
  blocks = json.loads(e.to_json())
199
201
 
@@ -256,3 +258,38 @@ def test_image_options():
256
258
  print(e.to_markdown())
257
259
  print(e.to_html())
258
260
  print(e.to_json())
261
+
262
+
263
+ def test_figcaption():
264
+ js = """{"time":1742471258492,"blocks":[{"id":"ZoA3rbc05C","type":"image","data":{"caption":"Party Time!","withBorder":false,"withBackground":false,"stretched":false,"file":{"url":"https://py4web.leiden.dockers.local/img/upload/23.png?hash=979795a433fc15cb94eccb3159f1f1e4054b1664"}}}],"version":"2.30.7"}"""
265
+ e = EditorJS.from_json(js)
266
+
267
+ html = e.to_html()
268
+
269
+ assert "figcaption" in html
270
+
271
+
272
+ def test_bold():
273
+ js = """{"time":1742475802066,"blocks":[{"id":"v_Kc51dnJH","type":"paragraph","data":{"text":"Deze tekst is <b>half bold</b> en half niet"},"tunes":{"alignmentTune":{"alignment":"left"}}},{"id":"q_fkuEFcY5","type":"paragraph","data":{"text":"<b>Deze tekst is heel bold</b>"},"tunes":{"alignmentTune":{"alignment":"left"}}}],"version":"2.30.7"}"""
274
+
275
+ e = EditorJS.from_json(js)
276
+
277
+ print(
278
+ e._mdast,
279
+ e.to_json(),
280
+ e.to_html(),
281
+ e.to_markdown(),
282
+ )
283
+
284
+
285
+ def test_quotes():
286
+ js = """{"time":1742477286420,"blocks":[{"id":"PpmEaxeSkq","type":"quote","data":{"text":"To baldly go where no bald man has ever gone before","caption":"a bald guy","alignment":"left"}},{"id":"26ERFQ6U3V","type":"quote","data":{"text":"Einstein was een sukkel","caption":"Einstein's ex vrouw","alignment":"left"}},{"id":"RB8AdaCd86","type":"quote","data":{"text":"Asdf","caption":"fsda-man","alignment":"left"}},{"id":"BCSus2rhUr","type":"paragraph","data":{"text":"groetjes"},"tunes":{"alignmentTune":{"alignment":"left"}}}],"version":"2.30.7"}"""
287
+
288
+ e = EditorJS.from_json(js)
289
+
290
+ print(
291
+ e._mdast,
292
+ e.to_json(),
293
+ e.to_html(),
294
+ e.to_markdown(),
295
+ )
@@ -1 +0,0 @@
1
- __version__ = "2.4.0"
File without changes
File without changes