edwh-editorjs 2.5.0a3__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.
- {edwh_editorjs-2.5.0a3 → edwh_editorjs-2.6.0}/CHANGELOG.md +12 -0
- {edwh_editorjs-2.5.0a3 → edwh_editorjs-2.6.0}/PKG-INFO +3 -2
- edwh_editorjs-2.6.0/blog.md +86 -0
- edwh_editorjs-2.6.0/debug.py +18 -0
- edwh_editorjs-2.6.0/editorjs/__about__.py +1 -0
- {edwh_editorjs-2.5.0a3 → edwh_editorjs-2.6.0}/editorjs/blocks.py +46 -34
- {edwh_editorjs-2.5.0a3 → edwh_editorjs-2.6.0}/pyproject.toml +1 -0
- {edwh_editorjs-2.5.0a3 → edwh_editorjs-2.6.0}/tests/test_core.py +53 -2
- edwh_editorjs-2.5.0a3/editorjs/__about__.py +0 -1
- {edwh_editorjs-2.5.0a3 → edwh_editorjs-2.6.0}/.gitignore +0 -0
- {edwh_editorjs-2.5.0a3 → edwh_editorjs-2.6.0}/README.md +0 -0
- {edwh_editorjs-2.5.0a3 → edwh_editorjs-2.6.0}/editorjs/__init__.py +0 -0
- {edwh_editorjs-2.5.0a3 → edwh_editorjs-2.6.0}/editorjs/core.py +0 -0
- {edwh_editorjs-2.5.0a3 → edwh_editorjs-2.6.0}/editorjs/exceptions.py +0 -0
- {edwh_editorjs-2.5.0a3 → edwh_editorjs-2.6.0}/editorjs/helpers.py +0 -0
- {edwh_editorjs-2.5.0a3 → edwh_editorjs-2.6.0}/editorjs/types.py +0 -0
- {edwh_editorjs-2.5.0a3 → edwh_editorjs-2.6.0}/tests/__init__.py +0 -0
|
@@ -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.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: edwh-editorjs
|
|
3
|
-
Version: 2.
|
|
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 " 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,16 @@ 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
|
-
|
|
17
|
+
|
|
17
18
|
|
|
18
19
|
class EditorJSBlock(abc.ABC):
|
|
19
20
|
@classmethod
|
|
@@ -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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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")
|
|
@@ -248,8 +257,7 @@ class ParagraphBlock(EditorJSBlock):
|
|
|
248
257
|
|
|
249
258
|
@classmethod
|
|
250
259
|
def to_text(cls, node: MDChildNode) -> str:
|
|
251
|
-
return
|
|
252
|
-
# return default_to_text(node)
|
|
260
|
+
return default_to_text(node)
|
|
253
261
|
|
|
254
262
|
|
|
255
263
|
@block("list")
|
|
@@ -388,7 +396,7 @@ class CodeBlock(EditorJSBlock):
|
|
|
388
396
|
@classmethod
|
|
389
397
|
def to_markdown(cls, data: EditorChildData) -> str:
|
|
390
398
|
code = data.get("code", "")
|
|
391
|
-
return f"```\n
|
|
399
|
+
return f"```\n{code}\n```\n"
|
|
392
400
|
|
|
393
401
|
@classmethod
|
|
394
402
|
def to_json(cls, node: MDChildNode) -> list[dict]:
|
|
@@ -447,9 +455,9 @@ class ImageBlock(EditorJSBlock):
|
|
|
447
455
|
border = node.get("border") or ""
|
|
448
456
|
|
|
449
457
|
return f"""
|
|
450
|
-
<div class="ce-block {stretched and
|
|
458
|
+
<div class="ce-block {stretched and "ce-block--stretched"}">
|
|
451
459
|
<div class="ce-block__content">
|
|
452
|
-
<div class="cdx-block image-tool image-tool--filled {background and
|
|
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"}">
|
|
453
461
|
<div class="image-tool__image">
|
|
454
462
|
<figure>
|
|
455
463
|
<img class="image-tool__image-picture" src="{url}" title="{caption}" alt="{caption}">
|
|
@@ -471,7 +479,7 @@ class QuoteBlock(EditorJSBlock):
|
|
|
471
479
|
result = f"> {text}\n"
|
|
472
480
|
if caption := data.get("caption", ""):
|
|
473
481
|
result += f"> <cite>{caption}</cite>\n"
|
|
474
|
-
return result
|
|
482
|
+
return result + "\n"
|
|
475
483
|
|
|
476
484
|
@classmethod
|
|
477
485
|
def to_json(cls, node: MDChildNode) -> list[dict]:
|
|
@@ -497,12 +505,13 @@ class QuoteBlock(EditorJSBlock):
|
|
|
497
505
|
|
|
498
506
|
@classmethod
|
|
499
507
|
def to_text(cls, node: MDChildNode) -> str:
|
|
500
|
-
return
|
|
508
|
+
return "".join(
|
|
509
|
+
process_styled_content(child) for child in node.get("children", [])
|
|
510
|
+
)
|
|
501
511
|
|
|
502
512
|
|
|
503
513
|
@block("raw", "html")
|
|
504
514
|
class RawBlock(EditorJSBlock):
|
|
505
|
-
|
|
506
515
|
@classmethod
|
|
507
516
|
def to_markdown(cls, data: EditorChildData) -> str:
|
|
508
517
|
text = data.get("html", "")
|
|
@@ -525,7 +534,6 @@ class RawBlock(EditorJSBlock):
|
|
|
525
534
|
|
|
526
535
|
@block("table")
|
|
527
536
|
class TableBlock(EditorJSBlock):
|
|
528
|
-
|
|
529
537
|
@classmethod
|
|
530
538
|
def to_markdown(cls, data: EditorChildData) -> str:
|
|
531
539
|
"""
|
|
@@ -644,7 +652,6 @@ class LinkBlock(EditorJSBlock):
|
|
|
644
652
|
|
|
645
653
|
@block("attaches")
|
|
646
654
|
class AttachmentBlock(EditorJSBlock):
|
|
647
|
-
|
|
648
655
|
@classmethod
|
|
649
656
|
def to_markdown(cls, data: EditorChildData) -> str:
|
|
650
657
|
title = data.get("title", "")
|
|
@@ -709,7 +716,7 @@ class AttachmentBlock(EditorJSBlock):
|
|
|
709
716
|
</div>
|
|
710
717
|
{file_size}
|
|
711
718
|
</div>
|
|
712
|
-
<a class="cdx-attaches__download-button" href="{node.get(
|
|
719
|
+
<a class="cdx-attaches__download-button" href="{node.get("file", "")}" target="_blank" rel="nofollow noindex noreferrer" title="{node.get("name", "")}">
|
|
713
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>
|
|
714
721
|
</a>
|
|
715
722
|
</div>
|
|
@@ -761,7 +768,6 @@ class AlignmentBlock(EditorJSBlock):
|
|
|
761
768
|
|
|
762
769
|
@block("embed")
|
|
763
770
|
class EmbedBlock(EditorJSBlock):
|
|
764
|
-
|
|
765
771
|
@classmethod
|
|
766
772
|
def to_markdown(cls, data: EditorChildData) -> str:
|
|
767
773
|
service = data.get("service", "")
|
|
@@ -790,20 +796,6 @@ class EmbedBlock(EditorJSBlock):
|
|
|
790
796
|
### end blocks
|
|
791
797
|
|
|
792
798
|
|
|
793
|
-
class AttributeParser(HTMLParser):
|
|
794
|
-
def __init__(self):
|
|
795
|
-
super().__init__()
|
|
796
|
-
self.attributes = {}
|
|
797
|
-
self.data = None
|
|
798
|
-
|
|
799
|
-
def handle_starttag(self, tag, attrs):
|
|
800
|
-
# Collect attributes when the tag is encountered
|
|
801
|
-
self.attributes = dict(attrs)
|
|
802
|
-
|
|
803
|
-
def handle_data(self, data):
|
|
804
|
-
self.data = data
|
|
805
|
-
|
|
806
|
-
|
|
807
799
|
class EditorJSCustom(EditorJSBlock, markdown2.Extra):
|
|
808
800
|
"""
|
|
809
801
|
Special type of block to deal with custom attributes.
|
|
@@ -816,10 +808,29 @@ class EditorJSCustom(EditorJSBlock, markdown2.Extra):
|
|
|
816
808
|
|
|
817
809
|
@classmethod
|
|
818
810
|
def parse_html(cls, html: str):
|
|
819
|
-
|
|
820
|
-
|
|
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
|
+
)
|
|
821
832
|
|
|
822
|
-
return
|
|
833
|
+
return attributes, inner_html
|
|
823
834
|
|
|
824
835
|
@classmethod
|
|
825
836
|
def to_markdown(cls, data: EditorChildData) -> str:
|
|
@@ -834,6 +845,7 @@ class EditorJSCustom(EditorJSBlock, markdown2.Extra):
|
|
|
834
845
|
handler = BLOCKS.get(_type)
|
|
835
846
|
|
|
836
847
|
if not handler:
|
|
848
|
+
raise ValueError(f"debug: {attrs = } {body = }") # fixme
|
|
837
849
|
raise ValueError(f"Unknown custom type {_type}")
|
|
838
850
|
|
|
839
851
|
return handler, attrs
|
|
@@ -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(
|
|
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
|
|
|
@@ -277,3 +281,50 @@ def test_bold():
|
|
|
277
281
|
e.to_html(),
|
|
278
282
|
e.to_markdown(),
|
|
279
283
|
)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def test_quotes():
|
|
287
|
+
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"}"""
|
|
288
|
+
|
|
289
|
+
e = EditorJS.from_json(js)
|
|
290
|
+
|
|
291
|
+
print(
|
|
292
|
+
e._mdast,
|
|
293
|
+
e.to_json(),
|
|
294
|
+
e.to_html(),
|
|
295
|
+
e.to_markdown(),
|
|
296
|
+
)
|
|
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.0a3"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|