edwh-editorjs 2.5.0a4__tar.gz → 2.6.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,18 @@
2
2
 
3
3
  <!--next-version-placeholder-->
4
4
 
5
+ ## v2.6.0 (2026-02-05)
6
+
7
+ ### Fix
8
+
9
+ * **custom:** Replace recursive html parser with lxml to only parse outer layer and keep inner HTML intact ([`8a8908c`](https://github.com/educationwarehouse/edwh-editorjs/commit/8a8908c30708573df11f2011b24fda8c4984de21))
10
+
11
+ ## v2.5.0 (2025-03-20)
12
+
13
+ ### Feature
14
+
15
+ * Improve markdown conversion and image handling in EditorJS blocks ([`80d0fc0`](https://github.com/educationwarehouse/edwh-editorjs/commit/80d0fc00a4e39b279de35998d1a85a37282c58a7))
16
+
5
17
  ## v2.4.0 (2024-12-02)
6
18
 
7
19
  ### Feature
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: edwh-editorjs
3
- Version: 2.5.0a4
3
+ Version: 2.6.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>
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.13
15
15
  Requires-Python: >=3.10
16
16
  Requires-Dist: html2markdown
17
17
  Requires-Dist: humanize
18
+ Requires-Dist: lxml
18
19
  Requires-Dist: markdown2
19
20
  Requires-Dist: mdast
20
21
  Provides-Extra: dev
@@ -0,0 +1,86 @@
1
+ Via de pilot **‘Ambtenaar in de Praktijk’** doen Leidse ambtenaren gerichte praktijkervaring op in de stad. Zo loopt Lies Timmering, projectleider van de **Leidse Gelijke Kansen Aanpak**, elke woensdagmorgen mee bij **BuZz**, waar zij te vinden is bij de buurtsoep en meewerkt met bewoners en vrijwilligers. In deze blog deelt Lies haar persoonlijke reflectie op wat deze ervaringen voor haar betekenen en hoe ze helpen om beleid beter te laten aansluiten op wat er écht speelt in de stad.
2
+
3
+ <editorjs type="image" caption="Foto: BuZz Leiden&nbsp;" border="" background="" stretched="1" url="https://py4web.onderwijsindeleidseregio.nl/thumb/upload/af1ac9a1-fd7a-416e-a5dc-6ee97266397d.jpg?hash=075d9622bd25e284eb23ea34a22537dd707d8437" />
4
+
5
+ ***
6
+
7
+ Vandaag pakte ik mijn eed en belofte er nog eens bij. Ik beloofde als ambtenaar bij de start van mijn functie ‘plechtig’ het volgende:  
8
+
9
+ 1. Ik zal de gerechtigheid dienen. 
10
+ 2. Ik zal trouw zijn aan de grondwet en de overige wetten van het rijk.
11
+ 3. Ik zal mij inzetten voor het welzijn en de rechten van alle burgers van Leiden.
12
+ 4. Ik zal onpartijdig handelen en de democratische beginselen en procedures respecteren.
13
+ 5. Ik ben loyaal ten opzichte van de bestuursorganen van de stad en het door hen vastgestelde beleid.
14
+ 6. Ik zal van de overheidsmacht die mij is toevertrouwd geen misbruik maken.
15
+ 7. Ik zal zorgvuldig omgaan met informatie.
16
+ 8. Ik zal de geloofwaardigheid van het ambt niet schaden.
17
+ 9. Ik zal het vertrouwen, dat de burger in mij mag stellen, niet beschamen.
18
+ 10. Ik zal mij een zelfstandig oordeel vormen over de morele juistheid van mijn handelen.
19
+
20
+ Tevens verklaar ik dat ik bekend ben met de gedragscode voor ambtenaren van de gemeente Leiden en deze als leidraad zal hanteren. 
21
+
22
+ **Dat verklaar en beloof ik!**
23
+
24
+ Toen ik mocht starten bij de gemeente had ik weinig beeld bij wat een ambtenaar nou precies deed. Wat het betekende om te werken voor een gemeente. Mensen zeiden me: “Bij de gemeente, jij? Dat gaat jou toch allemaal veel te traag, dat is niks voor jou?!” Maar de inhoud van de functie sprak me ontzettend aan, het team waarin ik werd ontvangen was ongelofelijk warm en de rest zou ik nog ontdekken.  
25
+
26
+ <editorjs type='alignment' tag='p' alignment='center'> <b>Werk dat ertoe doet </b> </editorjs>
27
+
28
+ Toen ik mocht starten bij de gemeente had ik weinig beeld bij wat een ambtenaar nou precies deed. Wat het betekende om te werken voor een gemeente. Mensen zeiden me: “Bij de gemeente, jij? Dat gaat jou toch allemaal veel te traag, dat is niks voor jou?!” Maar de inhoud van de functie sprak me ontzettend aan, het team waarin ik werd ontvangen was ongelofelijk warm en de rest zou ik nog ontdekken. 
29
+
30
+ Later kon ik al deze mensen vertellen dat ik een geweldige baan had gevonden. Een baan waarvan mijn hart sneller ging kloppen. Ik mocht werk doen dat ertoe deed. Niks ging langzaam, eerder te snel. Nog nooit werkte ik in zo’n dynamische omgeving als een gemeente, geen dag is hetzelfde!  
31
+
32
+ Iedereen die mij spreekt, die weet: ik geniet van mijn werk. Ik mag met ontzettend fijne mensen werken en ik mag werk doen dat ertoe doet. Ik mag mij inzetten voor het welzijn en de rechten van burgers in Leiden. Net zoals ik heb beloofd.  
33
+
34
+ Wat ik wel merkte toen ik vanuit mijn functie met zoveel mogelijk partners en burgers in de stad ging praten, was dat er een flinke kloof was tussen de beleidswereld (gemeente, overheid) en de leefwereld van mensen, de wijken, de scholen, de plekken waarvoor het beleid werd gemaakt. Daar ervaarden mensen soms afstand, onbegrip en frustratie doordat de realiteit weerbarstig is en het beleid zwart-wit. Dit is natuurlijk niet iets Leids. Dit is landelijk zo, dit is internationaal zo. De afstand tussen overheid en ‘burgers’ lijkt groot, en bovendien ook steeds groter te worden. Regels, procedures, systemen. Maar ook simpelweg taal. Alsof het verschillende talen zijn, maar het zijn zeker verschillende belevingen...  
35
+
36
+ Wat in mijn eed en belofte niet echt scherp wordt, is het waartoe van mijn werk. Waartoe beloof ik te doen wat ik als ambtenaar ga doen? Zeker, trouw aan de wet, zorgvuldigheid, inzetten voor het welzijn, loyaal aan het stadsbestuur. Ja. Maar waartoe doe ik dit dan eigenlijk?  
37
+
38
+ Volgens AI bestaan “\[...] ambtenaren om het functioneren van de samenleving te waarborgen door het uitvoeren van overheidstaken, het ontwikkelen en uitvoeren van beleid, en het leveren van publieke diensten. Zij zorgen voor de handhaving van wetten, innen belastingen, verlenen vergunningen en ondersteunen politieke bestuurders.”  
39
+
40
+ Herkenbaar en zeker, dat doen we. Maar nogmaals, waartoe doen we dit? Anno 2026 is een ambtenaar toch zeker iemand die zich bewust is van het feit dat de functie bestaat bij de gratie van deze vorm van democratie? Dat het doel is om de inwoners te dienen, en dus ook nabij te zijn om zelf te ervaren dat je werk goed uitpakt, en dus dienend is aan het welzijn en de rechten van de burgers? Soms raak ik zelf zo overweldigd door de systemen waarin we allen ons werk proberen uit te voeren dat het eerder voelt alsof je trouw hebt beloofd te zijn aan Excel-sheets en processen van besluitvorming, dan aan je inzetten voor het welzijn van de inwoners. De praktijk van een ambtenaar is ook best weerbarstig…   
41
+
42
+ Tim ‘s Jongers  <a href="https://decorrespondent.nl/15957/beleid-wordt-beter-als-ambtenaren-achter-hun-laptop-vandaan-komen-daarom-een-pleidooi-voor-een-maatschappelijke-diensttijd/91ead8ad-ffb7-0a2a-15bf-316fe8e4578a" target="_blank">schreef eens dat beleid beter wordt als ambtenaren achter hun laptops vandaan komen </a> en onderdeel worden van de leefwereld waarvoor zij beleid maken. In Leiden kennen wij gelukkig verschillende werkprincipes die ons hierop wijzen, zo ook: wij werken van buiten (wat leeft in de stad) naar binnen (we maken beleid dat aansluit op wat leeft in de stad).  
43
+
44
+ <editorjs type='alignment' tag='h2' alignment='center'>Leidse Ambtenaren in de Praktijk  </editorjs>
45
+
46
+ Eén van de projecten die vanuit de Leidse Gelijke Kansen Aanpak is opgestart is de pilot ‘Ambtenaar in de Praktijk’. Via Ambtenaar in de Praktijk, doen ambtenaren van gemeente Leiden gerichte praktijkervaring op. Op die manier ervaren zij direct hoe beleid uitpakt in de praktijk en die kennis en ervaring brengen zij mee terug naar hun beleidsafdelingen.  
47
+
48
+ “Ik krijg door het meewerken in de praktijk van een brugfunctionaris meer inzicht in de problematieken die scholen binnenkomen. Ik realiseerde me dat problemen zichtbaar worden via het kind maar dat de problemen altijd verder reiken.”   —  Ambtenaar in de praktijk 
49
+
50
+ <editorjs type='alignment' tag='p' alignment='center'> <b>Gerichte praktijkervaring via partners in de stad  </b> </editorjs>
51
+
52
+ In aanloop naar deze pilot hebben we de standpunten van onze samenwerkingspartners geïnventariseerd en hen gevraagd: ‘stel dat we dit gaan doen, wat zou je dan waardevolle praktijkervaring vinden voor een ambtenaar en hoe kun je dat faciliteren?’  
53
+
54
+ Op die manier zijn we gekomen tot een soort menukaart met daarop min of meer kant-en-klare ideeën voor het opdoen van praktijkervaringen. Denk aan het meewerken in een buurthuis of buurtontmoetingsplek tijdens een inloop voor inwoners. Of denk aan het meewerken bij een speelgroep van JES Rijnland waar ouders met zeer jonge kinderen komen. Of misschien het meewerken bij een spreekuur rondom schulden. Of meewerken op een Leidse basisschool om zo meer mee te krijgen van wat er zoals speelt in en rond het onderwijs.   
55
+
56
+ “Bij de speelgroep merk ik dat er vaak sprake is van een taalbarrière. Ouders gebruiken de ontmoetingen bij een speelgroep dus ook om onderling informatie uit te wisselen. Het is een soort vindplaats. Ouders en de professional(s) van de speelgroep delen kennis en helpen elkaar dus via deze plek.” — Ambtenaar in de praktijk
57
+
58
+ <editorjs type='alignment' tag='p' alignment='center'> <b>De gemeente organiseert en faciliteert  </b> </editorjs>
59
+
60
+ Het is geweldig fijn dat gemeente Leiden haar ambtenaren hiervoor 32u op jaarbasis ruimte biedt. Op die manier geven we echt uitvoering aan ‘werken van buiten naar binnen’. De coördinatie van de pilot gebeurt vanuit de Leidse Gelijke Kansen Aanpak. Het is natuurlijk niet de bedoeling dat dit een verzwaring oplevert voor de partners in de stad. Het moet elkaar gaan versterken.  
61
+
62
+ “Het is alleen al prachtig dat we nu zo nabij kunnen zijn. De partners voelen zich gezien en gehoord in hun werk. En andersom kun je ook een inkijkje geven in hoe het dan binnen de gemeente loopt, waarom dingen soms wat tijd vragen. Of gewoon door te zeggen, “Ja, wij balen er ook enorm van dat dit nog wat rommelig loopt. We werken er hard aan om dit beter te maken.” Het vertrouwen waarmee de partners deze pilot in de stad hebben ontvangen is geweldig. En nu op naar verdere uitbreiding!”  — Ambtenaar in de praktijk
63
+
64
+ <editorjs type='alignment' tag='p' alignment='center'> <b>Ongelofelijk leerzame weken  </b> </editorjs>
65
+
66
+ De reacties in de stad zijn dan ook positief; de partnerorganisaties benadrukken het belang van praktijkervaring voor ambtenaren. Het is zeer leerzaam om in de praktijk te ervaren dat jouw beeld van hoe iets uitwerkt, misschien moet worden bijgesteld omdat de praktijk nu eenmaal weerbarstig is.  
67
+
68
+ “Op papier staat het mooi, maar in de praktijk valt het me op hoeveel je vraagt van partijen op het gebied van samenwerking en uitwisseling. Als je je handen dagelijks vol hebt aan het ondersteunen van inwoners, dan is het niet realistisch om te verwachten dat men ook nog zelf op zoek gaat naar allerlei samenwerkingen. Zonde, want je ziet wel hoeveel men onderling van die kennis zou kunnen profiteren. Zowel de professionals als inwoners.” — Ambtenaar in de praktijk  
69
+
70
+ <editorjs type='alignment' tag='p' alignment='center'> <b>De volgende ronde gaat in maart van start  </b> </editorjs>
71
+
72
+ De eerste ronde van deze mooie pilot is bijna afgerond. De betrokken ambtenaren ronden de pilot met elkaar af door de geleerde en ervaren lessen met elkaar om te zetten naar advies terug naar de beleidswereld. Onderling worden deze natuurlijk ook vast gedeeld met de volgende groep ambtenaren en met de betrokken partijen. Het is een prachtig leerproces. Wat leren wij van hen? Wat leren zij van ons? Wat betekent dit en hoe kunnen we dit leren omzetten in het daadwerkelijk verkleinen van die kloof tussen praktijk en beleid?   
73
+
74
+ Zo geeft een collega aan dat ze ziet hoe brieven in duidelijke taal vanuit de gemeente, toch vaak niet duidelijk genoeg lijken te zijn voor inwoners. Dat kun je niet weten, tenzij je spreekt met de inwoners zelf én met de professionals die hen ondersteunen.  
75
+
76
+ “Bij het Goede Buur spreekuur werk ik met gepassioneerde hulpverleners die de inwoner zo goed mogelijk willen helpen.” — Ambtenaar in de praktijk
77
+
78
+ Eén ding is zeker: dit zou onderdeel moeten kunnen zijn van je werk. Gemeente Leiden werkt graag kennisgericht, en via deze praktijkervaring maken we daar ook praktijkervaringskennisgericht werken van. Minimaal ieder jaar een week meewerken in de praktijk. De mouwen opstropen, doen, ondergaan en ervaren. Dit zou toch voor eenieder moeten gelden in elke positie waarbij jouw beleid invloed heeft op de ander? 
79
+
80
+ <editorjs type='alignment' tag='p' alignment='center'> <b>College in de Praktijk?  </b> </editorjs>
81
+
82
+ Pasgeleden opperde een collega dat we misschien ook moeten denken aan een uitbreiding van de pilot; ‘College in de Praktijk’, zodat na de verkiezingen het college van wethouders en burgemeester ook de Leidse praktijk echt kan ervaren. Een werkbezoek is namelijk echt iets heel anders als gewoon even ergens zelf de koffie zetten, stoelen uitklappen voor de vele inwoners die binnenkomen, samen soep koken, luisteren naar de verhalen van zij die even steun nodig hebben of vragen komen stellen. Zij die even warmte opzoeken omdat het thuis koud is. Of even stoom komen afblazen omdat ze niet als volwaardig worden gezien... 
83
+
84
+ “Heel eerlijk? Ik had geen idee. Ik wist het wel, maar ik realiseerde me het niet. Zelf voelen hoe het moet zijn om geen netwerk te hebben, om geen idee te hebben van de wereld om je heen. Zoveel te bieden, zo slim, getalenteerd. Zo betrokken en sociaal. Maar toch vast blijven zitten omdat je nu eenmaal de weg niet kent. Je denkt, dat hebben we allemaal goed ingeregeld! Maar in de praktijk zie je dat het niet zo makkelijk is...”  — Ambtenaar in de praktijk
85
+
86
+ ***
@@ -0,0 +1,18 @@
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()
@@ -0,0 +1 @@
1
+ __version__ = "2.6.0"
@@ -5,15 +5,15 @@ mdast to editorjs
5
5
  import abc
6
6
  import re
7
7
  import typing as t
8
- from html.parser import HTMLParser
9
8
  from urllib.parse import urlparse
10
9
 
10
+ import html2markdown
11
11
  import humanize
12
+ import lxml.html
12
13
  import markdown2
13
14
 
14
15
  from .exceptions import TODO, Unreachable
15
16
  from .types import EditorChildData, MDChildNode
16
- import html2markdown
17
17
 
18
18
 
19
19
  class EditorJSBlock(abc.ABC):
@@ -55,8 +55,6 @@ def process_styled_content(item: MDChildNode, strict: bool = True) -> str:
55
55
  """
56
56
  _type = item.get("type")
57
57
 
58
- print('process_styled_content', _type)
59
-
60
58
  html_wrappers = {
61
59
  "text": "{value}",
62
60
  "html": "{value}",
@@ -110,9 +108,9 @@ class HeadingBlock(EditorJSBlock):
110
108
  raise ValueError("Header level must be between 1 and 6.")
111
109
 
112
110
  if (
113
- tunes.get("alignmentTune")
114
- and (alignment := tunes["alignmentTune"].get("alignment"))
115
- and (alignment != "left")
111
+ tunes.get("alignmentTune")
112
+ and (alignment := tunes["alignmentTune"].get("alignment"))
113
+ and (alignment != "left")
116
114
  ):
117
115
  # can't just return regular HTML because then it will turn into a raw block
118
116
  return AlignmentBlock.to_markdown(
@@ -172,9 +170,9 @@ class ParagraphBlock(EditorJSBlock):
172
170
  tunes = data.get("tunes", {})
173
171
 
174
172
  if (
175
- tunes.get("alignmentTune")
176
- and (alignment := tunes["alignmentTune"].get("alignment"))
177
- and (alignment != "left")
173
+ tunes.get("alignmentTune")
174
+ and (alignment := tunes["alignmentTune"].get("alignment"))
175
+ and (alignment != "left")
178
176
  ):
179
177
  return AlignmentBlock.to_markdown(
180
178
  {
@@ -216,7 +214,7 @@ class ParagraphBlock(EditorJSBlock):
216
214
  else:
217
215
  # <editorjs>something</editorjs> = 3 children
218
216
  result.extend(
219
- EditorJSCustom.to_json({"children": nodes[idx: idx + 2]})
217
+ EditorJSCustom.to_json({"children": nodes[idx : idx + 2]})
220
218
  )
221
219
 
222
220
  skip = 2
@@ -398,7 +396,7 @@ class CodeBlock(EditorJSBlock):
398
396
  @classmethod
399
397
  def to_markdown(cls, data: EditorChildData) -> str:
400
398
  code = data.get("code", "")
401
- return f"```\n" f"{code}" f"\n```\n"
399
+ return f"```\n{code}\n```\n"
402
400
 
403
401
  @classmethod
404
402
  def to_json(cls, node: MDChildNode) -> list[dict]:
@@ -457,9 +455,9 @@ class ImageBlock(EditorJSBlock):
457
455
  border = node.get("border") or ""
458
456
 
459
457
  return f"""
460
- <div class="ce-block {stretched and 'ce-block--stretched'}">
458
+ <div class="ce-block {stretched and "ce-block--stretched"}">
461
459
  <div class="ce-block__content">
462
- <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"}">
463
461
  <div class="image-tool__image">
464
462
  <figure>
465
463
  <img class="image-tool__image-picture" src="{url}" title="{caption}" alt="{caption}">
@@ -514,7 +512,6 @@ class QuoteBlock(EditorJSBlock):
514
512
 
515
513
  @block("raw", "html")
516
514
  class RawBlock(EditorJSBlock):
517
-
518
515
  @classmethod
519
516
  def to_markdown(cls, data: EditorChildData) -> str:
520
517
  text = data.get("html", "")
@@ -537,7 +534,6 @@ class RawBlock(EditorJSBlock):
537
534
 
538
535
  @block("table")
539
536
  class TableBlock(EditorJSBlock):
540
-
541
537
  @classmethod
542
538
  def to_markdown(cls, data: EditorChildData) -> str:
543
539
  """
@@ -656,7 +652,6 @@ class LinkBlock(EditorJSBlock):
656
652
 
657
653
  @block("attaches")
658
654
  class AttachmentBlock(EditorJSBlock):
659
-
660
655
  @classmethod
661
656
  def to_markdown(cls, data: EditorChildData) -> str:
662
657
  title = data.get("title", "")
@@ -721,7 +716,7 @@ class AttachmentBlock(EditorJSBlock):
721
716
  </div>
722
717
  {file_size}
723
718
  </div>
724
- <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", "")}">
725
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>
726
721
  </a>
727
722
  </div>
@@ -773,7 +768,6 @@ class AlignmentBlock(EditorJSBlock):
773
768
 
774
769
  @block("embed")
775
770
  class EmbedBlock(EditorJSBlock):
776
-
777
771
  @classmethod
778
772
  def to_markdown(cls, data: EditorChildData) -> str:
779
773
  service = data.get("service", "")
@@ -802,20 +796,6 @@ class EmbedBlock(EditorJSBlock):
802
796
  ### end blocks
803
797
 
804
798
 
805
- class AttributeParser(HTMLParser):
806
- def __init__(self):
807
- super().__init__()
808
- self.attributes = {}
809
- self.data = None
810
-
811
- def handle_starttag(self, tag, attrs):
812
- # Collect attributes when the tag is encountered
813
- self.attributes = dict(attrs)
814
-
815
- def handle_data(self, data):
816
- self.data = data
817
-
818
-
819
799
  class EditorJSCustom(EditorJSBlock, markdown2.Extra):
820
800
  """
821
801
  Special type of block to deal with custom attributes.
@@ -828,10 +808,29 @@ class EditorJSCustom(EditorJSBlock, markdown2.Extra):
828
808
 
829
809
  @classmethod
830
810
  def parse_html(cls, html: str):
831
- parser = AttributeParser()
832
- parser.feed(html)
811
+ """
812
+ Extract attributes from the outermost HTML element and return its inner HTML.
813
+
814
+ This function parses the provided markup, identifies the root element,
815
+ returns its attributes as a dictionary, and serializes all direct child
816
+ nodes back into an HTML string.
817
+
818
+ Args:
819
+ html: A string containing a single root HTML element.
820
+
821
+ Returns:
822
+ A tuple of:
823
+ - dict[str, str]: Attributes of the root element
824
+ - str: Inner HTML of the root element
825
+ """
826
+ root = lxml.html.fromstring(html)
827
+
828
+ attributes = dict(root.attrib)
829
+ inner_html = "".join(
830
+ lxml.html.tostring(child, encoding="unicode") for child in root
831
+ )
833
832
 
834
- return parser.attributes, parser.data
833
+ return attributes, inner_html
835
834
 
836
835
  @classmethod
837
836
  def to_markdown(cls, data: EditorChildData) -> str:
@@ -846,6 +845,7 @@ class EditorJSCustom(EditorJSBlock, markdown2.Extra):
846
845
  handler = BLOCKS.get(_type)
847
846
 
848
847
  if not handler:
848
+ raise ValueError(f"debug: {attrs = } {body = }") # fixme
849
849
  raise ValueError(f"Unknown custom type {_type}")
850
850
 
851
851
  return handler, attrs
@@ -100,13 +100,11 @@ class EditorJS:
100
100
  try:
101
101
  blocks.extend(block.to_json(child))
102
102
  except Exception as e:
103
-
104
103
  warnings.warn(
105
104
  "to_json: Oh oh, unexpected block failure!",
106
105
  category=RuntimeWarning,
107
106
  source=e,
108
107
  )
109
- raise e
110
108
  # if isinstance(e, TODO):
111
109
  # raise e
112
110
 
@@ -31,6 +31,7 @@ dependencies = [
31
31
  "markdown2", # markdown -> html
32
32
  "html2markdown", # html -> markdown
33
33
  "humanize",
34
+ "lxml",
34
35
  ]
35
36
 
36
37
  [project.optional-dependencies]
@@ -2,6 +2,7 @@ import json
2
2
  import textwrap
3
3
 
4
4
  from editorjs import EditorJS
5
+ from editorjs.blocks import EditorJSCustom
5
6
 
6
7
  EXAMPLE_MD = textwrap.dedent("""
7
8
  # Heading
@@ -185,7 +186,8 @@ asdfsdajgdsjaklgkjds
185
186
 
186
187
 
187
188
  def test_code():
188
- e = EditorJS.from_markdown(textwrap.dedent("""
189
+ e = EditorJS.from_markdown(
190
+ textwrap.dedent("""
189
191
  Read code:
190
192
 
191
193
  ```
@@ -193,7 +195,8 @@ def test_code():
193
195
  ```
194
196
 
195
197
  End of code
196
- """))
198
+ """)
199
+ )
197
200
 
198
201
  blocks = json.loads(e.to_json())
199
202
 
@@ -266,6 +269,7 @@ def test_figcaption():
266
269
 
267
270
  assert "figcaption" in html
268
271
 
272
+
269
273
  def test_bold():
270
274
  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"}"""
271
275
 
@@ -291,3 +295,36 @@ def test_quotes():
291
295
  e.to_markdown(),
292
296
  )
293
297
 
298
+
299
+ # test case based on issue HETNIEUWELEIDEN-019c28cd-56b8-7778-be0c-60fb9e930638
300
+
301
+
302
+ def test_nonrecusrive_attr_parser():
303
+ html = "<div type='outer'><div type='inner'>contents</div></div>"
304
+
305
+ attributes, data = EditorJSCustom.parse_html(html)
306
+
307
+ assert attributes == {"type": "outer"}
308
+ assert data == '<div type="inner">contents</div>'
309
+
310
+
311
+ def test_editorjs_alignment_tag():
312
+ """Test that editorjs alignment tags with nested HTML are parsed correctly."""
313
+ md_input = "<editorjs type='alignment' tag='p' alignment='center'> <b>Werk dat ertoe doet </b> </editorjs>"
314
+
315
+ e = EditorJS.from_markdown(md_input)
316
+
317
+ print("MDAST:", e.to_mdast())
318
+ print("Markdown:", e.to_markdown())
319
+ print("JSON:", e.to_json())
320
+
321
+ html = e.to_html()
322
+ print("HTML:", html)
323
+
324
+ # The type should be detected as 'alignment' (or mapped to 'paragraph' with alignment tune)
325
+ blocks = json.loads(e.to_json())
326
+
327
+ assert blocks
328
+
329
+ assert "<b>Werk dat ertoe doet" in html
330
+ assert "<editorjs" not in html
@@ -1 +0,0 @@
1
- __version__ = "2.5.0a4"
File without changes