edwh-editorjs 2.0.0__tar.gz → 2.0.0b2__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.0.0 → edwh_editorjs-2.0.0b2}/CHANGELOG.md +0 -24
- {edwh_editorjs-2.0.0 → edwh_editorjs-2.0.0b2}/PKG-INFO +1 -1
- edwh_editorjs-2.0.0b2/editorjs/__about__.py +1 -0
- {edwh_editorjs-2.0.0 → edwh_editorjs-2.0.0b2}/editorjs/blocks.py +7 -295
- {edwh_editorjs-2.0.0 → edwh_editorjs-2.0.0b2}/editorjs/core.py +4 -14
- edwh_editorjs-2.0.0b2/tests/test_core.py +74 -0
- edwh_editorjs-2.0.0/editorjs/__about__.py +0 -1
- edwh_editorjs-2.0.0/htmlcov/.gitignore +0 -2
- edwh_editorjs-2.0.0/htmlcov/class_index.html +0 -259
- edwh_editorjs-2.0.0/htmlcov/favicon_32_cb_58284776.png +0 -0
- edwh_editorjs-2.0.0/htmlcov/function_index.html +0 -395
- edwh_editorjs-2.0.0/htmlcov/index.html +0 -132
- edwh_editorjs-2.0.0/htmlcov/keybd_closed_cb_ce680311.png +0 -0
- edwh_editorjs-2.0.0/htmlcov/status.json +0 -1
- edwh_editorjs-2.0.0/htmlcov/style_cb_8e611ae1.css +0 -337
- edwh_editorjs-2.0.0/htmlcov/z_a93c8aeb4b8fa1f9___init___py.html +0 -125
- edwh_editorjs-2.0.0/htmlcov/z_a93c8aeb4b8fa1f9_blocks_py.html +0 -406
- edwh_editorjs-2.0.0/htmlcov/z_a93c8aeb4b8fa1f9_exceptions_py.html +0 -116
- edwh_editorjs-2.0.0/htmlcov/z_a93c8aeb4b8fa1f9_parser_py.html +0 -172
- edwh_editorjs-2.0.0/tests/test_core.py +0 -112
- {edwh_editorjs-2.0.0 → edwh_editorjs-2.0.0b2}/.github/workflows/build_documentation.yml +0 -0
- {edwh_editorjs-2.0.0 → edwh_editorjs-2.0.0b2}/.github/workflows/publish_to_pypi.yml +0 -0
- {edwh_editorjs-2.0.0 → edwh_editorjs-2.0.0b2}/.github/workflows/pytest.yml +0 -0
- {edwh_editorjs-2.0.0 → edwh_editorjs-2.0.0b2}/.gitignore +0 -0
- {edwh_editorjs-2.0.0 → edwh_editorjs-2.0.0b2}/LICENSE +0 -0
- {edwh_editorjs-2.0.0 → edwh_editorjs-2.0.0b2}/README.md +0 -0
- {edwh_editorjs-2.0.0 → edwh_editorjs-2.0.0b2}/editorjs/__init__.py +0 -0
- {edwh_editorjs-2.0.0 → edwh_editorjs-2.0.0b2}/editorjs/exceptions.py +0 -0
- {edwh_editorjs-2.0.0 → edwh_editorjs-2.0.0b2}/editorjs/helpers.py +0 -0
- {edwh_editorjs-2.0.0 → edwh_editorjs-2.0.0b2}/editorjs/types.py +0 -0
- {edwh_editorjs-2.0.0 → edwh_editorjs-2.0.0b2}/pyproject.toml +0 -0
- {edwh_editorjs-2.0.0 → edwh_editorjs-2.0.0b2}/tests/__init__.py +0 -0
|
@@ -2,30 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
<!--next-version-placeholder-->
|
|
4
4
|
|
|
5
|
-
## v2.0.0 (2024-11-18)
|
|
6
|
-
|
|
7
|
-
### Fix
|
|
8
|
-
|
|
9
|
-
* Rendered attachment should not be content-editable ([`58ab562`](https://github.com/educationwarehouse/edwh-editorjs/commit/58ab562daaca233455b3fe66b773af61f1abb0ad))
|
|
10
|
-
|
|
11
|
-
## v2.0.0-beta.5 (2024-11-07)
|
|
12
|
-
|
|
13
|
-
### Feature
|
|
14
|
-
|
|
15
|
-
* Add markdown2 extra to deal with custom `<editorjs />` blocks ([`31d8647`](https://github.com/educationwarehouse/edwh-editorjs/commit/31d8647b7275e245dabf27a99c43d400217705be))
|
|
16
|
-
|
|
17
|
-
## v2.0.0-beta.4 (2024-11-07)
|
|
18
|
-
|
|
19
|
-
### Feature
|
|
20
|
-
|
|
21
|
-
* Support (basic) tables, custom editorjs blocks like linkTool via <editorjs> ([`a6dfadf`](https://github.com/educationwarehouse/edwh-editorjs/commit/a6dfadf21ec008fe714704a056b9ffec751d731c))
|
|
22
|
-
|
|
23
|
-
## v2.0.0-beta.3 (2024-11-06)
|
|
24
|
-
|
|
25
|
-
### Fix
|
|
26
|
-
|
|
27
|
-
* Various fixes with nested lists ([`52a7773`](https://github.com/educationwarehouse/edwh-editorjs/commit/52a7773470dce3eee6a2d17d46b594551ed043a5))
|
|
28
|
-
|
|
29
5
|
## v2.0.0-beta.2 (2024-11-06)
|
|
30
6
|
|
|
31
7
|
### Feature
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: edwh-editorjs
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.0b2
|
|
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 @@
|
|
|
1
|
+
__version__ = "2.0.0-beta.2"
|
|
@@ -5,10 +5,6 @@ mdast to editorjs
|
|
|
5
5
|
import abc
|
|
6
6
|
import re
|
|
7
7
|
import typing as t
|
|
8
|
-
from html.parser import HTMLParser
|
|
9
|
-
from urllib.parse import urlparse
|
|
10
|
-
|
|
11
|
-
import markdown2
|
|
12
8
|
|
|
13
9
|
from .exceptions import TODO
|
|
14
10
|
from .types import EditorChildData, MDChildNode
|
|
@@ -60,7 +56,7 @@ def process_styled_content(item: MDChildNode, strict: bool = True) -> str:
|
|
|
60
56
|
"strongEmphasis": "<b><i>{value}</i></b>",
|
|
61
57
|
"link": '<a href="{url}">{value}</a>',
|
|
62
58
|
"inlineCode": '<code class="inline-code">{value}</code>',
|
|
63
|
-
# todo: <mark
|
|
59
|
+
# todo: <mark>
|
|
64
60
|
}
|
|
65
61
|
|
|
66
62
|
if _type in BLOCKS:
|
|
@@ -142,52 +138,16 @@ class ParagraphBlock(EditorJSBlock):
|
|
|
142
138
|
result = []
|
|
143
139
|
current_text = ""
|
|
144
140
|
|
|
145
|
-
|
|
146
|
-
nodes = node.get("children", [])
|
|
147
|
-
|
|
148
|
-
for idx, child in enumerate(nodes):
|
|
149
|
-
if skip:
|
|
150
|
-
skip -= 1
|
|
151
|
-
continue
|
|
152
|
-
|
|
141
|
+
for child in node.get("children"):
|
|
153
142
|
_type = child.get("type")
|
|
154
|
-
|
|
155
|
-
# deal with custom types
|
|
156
|
-
if _type == "html" and child.get("value", "").startswith("<editorjs"):
|
|
157
|
-
# special type, e.g. <editorjs type="linkTool" href=...>...</editorjs>
|
|
158
|
-
|
|
159
|
-
if child.get("value", "").endswith("/>"):
|
|
160
|
-
# self-closing
|
|
161
|
-
result.append(EditorJSCustom.to_json(node))
|
|
162
|
-
continue
|
|
163
|
-
else:
|
|
164
|
-
# <editorjs>something</editorjs> = 3 children
|
|
165
|
-
result.extend(
|
|
166
|
-
EditorJSCustom.to_json({"children": nodes[idx : idx + 2]})
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
skip = 2
|
|
170
|
-
continue
|
|
171
|
-
|
|
172
|
-
elif _type == "image":
|
|
143
|
+
if _type == "image":
|
|
173
144
|
if current_text:
|
|
174
145
|
result.append({"data": {"text": current_text}, "type": "paragraph"})
|
|
175
146
|
current_text = ""
|
|
176
147
|
|
|
177
148
|
result.extend(ImageBlock.to_json(child))
|
|
178
149
|
else:
|
|
179
|
-
|
|
180
|
-
_child_text = child_text.strip()
|
|
181
|
-
if _child_text.startswith("|") and _child_text.endswith("|"):
|
|
182
|
-
# note: this just supports text-only tables.
|
|
183
|
-
# tables with more complex elements break into multiple children.
|
|
184
|
-
# and mdast DOES support converting into a Table/TableCell structure
|
|
185
|
-
# via the GFM exttension
|
|
186
|
-
# but their default mdast->md converter does NOT implement these functionalities.
|
|
187
|
-
result.extend(TableBlock.to_json(child))
|
|
188
|
-
continue
|
|
189
|
-
|
|
190
|
-
current_text += child_text
|
|
150
|
+
current_text += cls.to_text(child)
|
|
191
151
|
|
|
192
152
|
# final text after image:
|
|
193
153
|
if current_text:
|
|
@@ -211,7 +171,7 @@ class ListBlock(EditorJSBlock):
|
|
|
211
171
|
markdown_items = []
|
|
212
172
|
for index, item in enumerate(subitems):
|
|
213
173
|
prefix = f"{index + 1}." if style == "ordered" else "-"
|
|
214
|
-
line = f"{'
|
|
174
|
+
line = f"{' ' * depth}{prefix} {item['content']}"
|
|
215
175
|
markdown_items.append(line)
|
|
216
176
|
|
|
217
177
|
# Recurse if there are nested items
|
|
@@ -220,7 +180,7 @@ class ListBlock(EditorJSBlock):
|
|
|
220
180
|
|
|
221
181
|
return "\n".join(markdown_items)
|
|
222
182
|
|
|
223
|
-
return "\n" + parse_items(items) + "\n
|
|
183
|
+
return "\n" + parse_items(items) + "\n"
|
|
224
184
|
|
|
225
185
|
@classmethod
|
|
226
186
|
def to_json(cls, node: MDChildNode) -> list[dict]:
|
|
@@ -314,7 +274,7 @@ class ChecklistBlock(ListBlock):
|
|
|
314
274
|
char = "x" if item.get("checked", False) else " "
|
|
315
275
|
markdown_items.append(f"- [{char}] {text}")
|
|
316
276
|
|
|
317
|
-
return "\n" + "\n".join(markdown_items) + "\n
|
|
277
|
+
return "\n" + "\n".join(markdown_items) + "\n"
|
|
318
278
|
|
|
319
279
|
|
|
320
280
|
@block("thematicBreak", "delimiter")
|
|
@@ -420,251 +380,3 @@ class QuoteBlock(EditorJSBlock):
|
|
|
420
380
|
@classmethod
|
|
421
381
|
def to_text(cls, node: MDChildNode) -> str:
|
|
422
382
|
return default_to_text(node)
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
@block("raw")
|
|
426
|
-
class RawBlock(EditorJSBlock):
|
|
427
|
-
|
|
428
|
-
@classmethod
|
|
429
|
-
def to_markdown(cls, data: EditorChildData) -> str:
|
|
430
|
-
return data.get("html", "")
|
|
431
|
-
|
|
432
|
-
@classmethod
|
|
433
|
-
def to_json(cls, node: MDChildNode) -> list[dict]:
|
|
434
|
-
raise TODO(node)
|
|
435
|
-
|
|
436
|
-
@classmethod
|
|
437
|
-
def to_text(cls, node: MDChildNode) -> str:
|
|
438
|
-
raise TODO(node)
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
@block("table")
|
|
442
|
-
class TableBlock(EditorJSBlock):
|
|
443
|
-
|
|
444
|
-
@classmethod
|
|
445
|
-
def to_markdown(cls, data: EditorChildData) -> str:
|
|
446
|
-
"""
|
|
447
|
-
| Script | Interpreter | User | System | |
|
|
448
|
-
|--------|-------------|------|--------|---|
|
|
449
|
-
| | | | | |
|
|
450
|
-
| | | | | |
|
|
451
|
-
| | | | | |
|
|
452
|
-
"""
|
|
453
|
-
|
|
454
|
-
table = ""
|
|
455
|
-
rows = data.get("content", [])
|
|
456
|
-
|
|
457
|
-
# Add an empty header row if no headings are provided
|
|
458
|
-
if not data.get("withHeadings", False) and rows:
|
|
459
|
-
table += "| " + " | ".join([""] * len(rows[0])) + " |\n"
|
|
460
|
-
table += "|" + " - |" * len(rows[0]) + "\n"
|
|
461
|
-
|
|
462
|
-
# Populate rows
|
|
463
|
-
for idx, tr in enumerate(rows):
|
|
464
|
-
table += "| " + " | ".join(tr) + " |\n"
|
|
465
|
-
|
|
466
|
-
# Add separator if headings are enabled and it's the first row
|
|
467
|
-
if not idx and data.get("withHeadings", False):
|
|
468
|
-
table += "|" + " - |" * len(tr) + "\n"
|
|
469
|
-
|
|
470
|
-
return f"\n{table}\n"
|
|
471
|
-
|
|
472
|
-
@classmethod
|
|
473
|
-
def to_json(cls, node: MDChildNode) -> list[dict]:
|
|
474
|
-
# content":[["Yeah","Okay"],["<i>1</i>","<code class=\"inline-code\">2</code>"]]}}]
|
|
475
|
-
table = []
|
|
476
|
-
with_headings = False
|
|
477
|
-
|
|
478
|
-
# first row is headings or empty. If not empty, withHeadings is True
|
|
479
|
-
# second row must be ignored
|
|
480
|
-
for idx, row in enumerate(node.get("value", "").strip().split("\n")):
|
|
481
|
-
tr = [_.strip() for _ in row.split("|")[1:-1]]
|
|
482
|
-
if not idx:
|
|
483
|
-
# first
|
|
484
|
-
if any(tr):
|
|
485
|
-
with_headings = True
|
|
486
|
-
table.append(tr)
|
|
487
|
-
|
|
488
|
-
elif idx == 1:
|
|
489
|
-
continue
|
|
490
|
-
else:
|
|
491
|
-
table.append(tr)
|
|
492
|
-
|
|
493
|
-
return [
|
|
494
|
-
{
|
|
495
|
-
"type": "table",
|
|
496
|
-
"content": table,
|
|
497
|
-
"withHeadings": with_headings,
|
|
498
|
-
}
|
|
499
|
-
]
|
|
500
|
-
|
|
501
|
-
@classmethod
|
|
502
|
-
def to_text(cls, node: MDChildNode) -> str:
|
|
503
|
-
raise TODO(node)
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
@block("linkTool")
|
|
507
|
-
class LinkBlock(EditorJSBlock):
|
|
508
|
-
@classmethod
|
|
509
|
-
def to_markdown(cls, data: EditorChildData) -> str:
|
|
510
|
-
link = data.get("link", "")
|
|
511
|
-
meta = data.get("meta", {})
|
|
512
|
-
title = meta.get("title", "")
|
|
513
|
-
description = meta.get("description", "")
|
|
514
|
-
image = meta.get("image", {}).get("url", "")
|
|
515
|
-
return f"""<editorjs type="linkTool" href="{link}" title="{title}" image="{image}">{description}</editorjs>"""
|
|
516
|
-
|
|
517
|
-
@classmethod
|
|
518
|
-
def to_json(cls, node: MDChildNode) -> list[dict]:
|
|
519
|
-
return [
|
|
520
|
-
{
|
|
521
|
-
"type": "linkTool",
|
|
522
|
-
"data": {
|
|
523
|
-
"link": node.get("href", ""),
|
|
524
|
-
"meta": {
|
|
525
|
-
"title": node.get("title", ""),
|
|
526
|
-
"description": node.get("body", ""),
|
|
527
|
-
"image": {
|
|
528
|
-
"url": node.get("image", ""),
|
|
529
|
-
},
|
|
530
|
-
},
|
|
531
|
-
},
|
|
532
|
-
}
|
|
533
|
-
]
|
|
534
|
-
|
|
535
|
-
@classmethod
|
|
536
|
-
def to_text(cls, node: MDChildNode) -> str:
|
|
537
|
-
url = node.get("href", "")
|
|
538
|
-
image = node.get("image", "")
|
|
539
|
-
title = node.get("title", "")
|
|
540
|
-
body = node.get("body", "")
|
|
541
|
-
domain = urlparse(url).netloc
|
|
542
|
-
|
|
543
|
-
return f"""
|
|
544
|
-
<div class="link-tool">
|
|
545
|
-
<a class="link-tool__content link-tool__content--rendered" target="_blank"
|
|
546
|
-
rel="nofollow noindex noreferrer" href="{url}">
|
|
547
|
-
<div class="link-tool__image"
|
|
548
|
-
style="background-image: url("{image}");"></div>
|
|
549
|
-
<div class="link-tool__title">{title}</div>
|
|
550
|
-
<p class="link-tool__description">{body}</p>
|
|
551
|
-
<span class="link-tool__anchor">{domain}</span>
|
|
552
|
-
</a>
|
|
553
|
-
</div>
|
|
554
|
-
"""
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
@block("attaches")
|
|
558
|
-
class AttachmentBlock(EditorJSBlock):
|
|
559
|
-
|
|
560
|
-
@classmethod
|
|
561
|
-
def to_markdown(cls, data: EditorChildData) -> str:
|
|
562
|
-
file = data.get("file", {}).get("url", "")
|
|
563
|
-
title = data.get("title", "")
|
|
564
|
-
return f"""<editorjs type="attaches" file="{file}">{title}</editorjs>"""
|
|
565
|
-
|
|
566
|
-
@classmethod
|
|
567
|
-
def to_json(cls, node: MDChildNode) -> list[dict]:
|
|
568
|
-
return [
|
|
569
|
-
{
|
|
570
|
-
"type": "attaches",
|
|
571
|
-
"data": {
|
|
572
|
-
"file": {"url": node.get("file", "")},
|
|
573
|
-
"title": node.get("body", ""),
|
|
574
|
-
},
|
|
575
|
-
}
|
|
576
|
-
]
|
|
577
|
-
|
|
578
|
-
@classmethod
|
|
579
|
-
def to_text(cls, node: MDChildNode) -> str:
|
|
580
|
-
return f"""
|
|
581
|
-
<div class="cdx-attaches cdx-attaches--with-file">
|
|
582
|
-
<div class="cdx-attaches__file-icon">
|
|
583
|
-
<div class="cdx-attaches__file-icon-background">
|
|
584
|
-
<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-linejoin="round" stroke-width="2" d="M13.3236 8.43554L9.49533 12.1908C9.13119 12.5505 8.93118 13.043 8.9393 13.5598C8.94741 14.0767 9.163 14.5757 9.53862 14.947C9.91424 15.3182 10.4191 15.5314 10.9422 15.5397C11.4653 15.5479 11.9637 15.3504 12.3279 14.9908L16.1562 11.2355C16.8845 10.5161 17.2845 9.53123 17.2682 8.4975C17.252 7.46376 16.8208 6.46583 16.0696 5.72324C15.3184 4.98066 14.3086 4.55425 13.2624 4.53782C12.2162 4.52138 11.2193 4.91627 10.4911 5.63562L6.66277 9.39093C5.57035 10.4699 4.97032 11.9473 4.99467 13.4979C5.01903 15.0485 5.66578 16.5454 6.79264 17.6592C7.9195 18.7731 9.43417 19.4127 11.0034 19.4374C12.5727 19.462 14.068 18.8697 15.1604 17.7907L18.9887 14.0354"></path></svg>
|
|
585
|
-
</div>
|
|
586
|
-
</div>
|
|
587
|
-
<div class="cdx-attaches__file-info">
|
|
588
|
-
<div class="cdx-attaches__title" data-placeholder="File title" data-empty="false">
|
|
589
|
-
{node.get("body", "")}
|
|
590
|
-
</div>
|
|
591
|
-
</div>
|
|
592
|
-
<a class="cdx-attaches__download-button" href="{node.get('file', '')}" target="_blank" rel="nofollow noindex noreferrer">
|
|
593
|
-
<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>
|
|
594
|
-
</a>
|
|
595
|
-
</div>
|
|
596
|
-
"""
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
class AttributeParser(HTMLParser):
|
|
600
|
-
def __init__(self):
|
|
601
|
-
super().__init__()
|
|
602
|
-
self.attributes = {}
|
|
603
|
-
self.data = None
|
|
604
|
-
|
|
605
|
-
def handle_starttag(self, tag, attrs):
|
|
606
|
-
# Collect attributes when the tag is encountered
|
|
607
|
-
self.attributes = dict(attrs)
|
|
608
|
-
|
|
609
|
-
def handle_data(self, data):
|
|
610
|
-
self.data = data
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
class EditorJSCustom(EditorJSBlock, markdown2.Extra):
|
|
614
|
-
"""
|
|
615
|
-
Special type of block to deal with custom attributes
|
|
616
|
-
"""
|
|
617
|
-
|
|
618
|
-
name = "editorjs"
|
|
619
|
-
order = (), (markdown2.Stage.POSTPROCESS,)
|
|
620
|
-
|
|
621
|
-
@classmethod
|
|
622
|
-
def parse_html(cls, html: str):
|
|
623
|
-
parser = AttributeParser()
|
|
624
|
-
parser.feed(html)
|
|
625
|
-
|
|
626
|
-
return parser.attributes, parser.data
|
|
627
|
-
|
|
628
|
-
@classmethod
|
|
629
|
-
def to_markdown(cls, data: EditorChildData) -> str:
|
|
630
|
-
raise TODO()
|
|
631
|
-
|
|
632
|
-
@classmethod
|
|
633
|
-
def to_json(cls, node: MDChildNode) -> list[dict]:
|
|
634
|
-
html = "".join(_["value"] for _ in node.get("children", []))
|
|
635
|
-
attrs, body = cls.parse_html(html)
|
|
636
|
-
_type = attrs.get("type", "")
|
|
637
|
-
attrs.setdefault("body", body) # only if there is no such attribute yet
|
|
638
|
-
|
|
639
|
-
if not (handler := BLOCKS.get(_type)):
|
|
640
|
-
raise ValueError(f"Unknown custom type {_type}")
|
|
641
|
-
|
|
642
|
-
return handler.to_json(attrs)
|
|
643
|
-
|
|
644
|
-
@classmethod
|
|
645
|
-
def to_text(cls, node: MDChildNode) -> str:
|
|
646
|
-
raise TODO()
|
|
647
|
-
|
|
648
|
-
# markdown2:
|
|
649
|
-
re_short = re.compile(r"<editorjs.*?/>")
|
|
650
|
-
re_long = re.compile(r"<editorjs.*?>.*?</editorjs>")
|
|
651
|
-
|
|
652
|
-
def run(self, text: str) -> str:
|
|
653
|
-
def replace_html(match):
|
|
654
|
-
attrs, body = self.parse_html(match.group())
|
|
655
|
-
_type = attrs.get("type", "")
|
|
656
|
-
attrs.setdefault("body", body) # only if there is no such attribute yet
|
|
657
|
-
|
|
658
|
-
if not (handler := BLOCKS.get(_type)):
|
|
659
|
-
raise ValueError(f"Unknown custom type {_type}")
|
|
660
|
-
|
|
661
|
-
return handler.to_text(attrs)
|
|
662
|
-
|
|
663
|
-
# Substitute using the replacement functions
|
|
664
|
-
text = self.re_long.sub(replace_html, text)
|
|
665
|
-
text = self.re_short.sub(replace_html, text)
|
|
666
|
-
|
|
667
|
-
return text
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
EditorJSCustom.register()
|
|
@@ -6,6 +6,7 @@ import mdast
|
|
|
6
6
|
from typing_extensions import Self
|
|
7
7
|
|
|
8
8
|
from .blocks import BLOCKS
|
|
9
|
+
from .exceptions import TODO
|
|
9
10
|
from .helpers import unix_timestamp
|
|
10
11
|
from .types import MDRootNode
|
|
11
12
|
|
|
@@ -15,12 +16,9 @@ EDITORJS_VERSION = "2.30.6"
|
|
|
15
16
|
class EditorJS:
|
|
16
17
|
# internal representation is mdast, because we can convert to other types
|
|
17
18
|
_mdast: MDRootNode
|
|
19
|
+
_md = markdown2.Markdown(extras=["task_list"]) # todo: striketrough, table, ...
|
|
18
20
|
|
|
19
|
-
def __init__(
|
|
20
|
-
self,
|
|
21
|
-
_mdast: str | dict,
|
|
22
|
-
extras: list = ("task_list", "fenced-code-blocks", "tables", "editorjs"),
|
|
23
|
-
):
|
|
21
|
+
def __init__(self, _mdast: str | dict):
|
|
24
22
|
if not isinstance(_mdast, str | dict):
|
|
25
23
|
raise TypeError("Only `str` or `dict` is supported!")
|
|
26
24
|
|
|
@@ -28,8 +26,6 @@ class EditorJS:
|
|
|
28
26
|
MDRootNode, json.loads(_mdast) if isinstance(_mdast, str) else _mdast
|
|
29
27
|
)
|
|
30
28
|
|
|
31
|
-
self._md = markdown2.Markdown(extras=extras) # todo: striketrough, ?
|
|
32
|
-
|
|
33
29
|
@classmethod
|
|
34
30
|
def from_json(cls, data: str | dict) -> Self:
|
|
35
31
|
"""
|
|
@@ -52,7 +48,6 @@ class EditorJS:
|
|
|
52
48
|
"""
|
|
53
49
|
Load from markdown string
|
|
54
50
|
"""
|
|
55
|
-
|
|
56
51
|
return cls(mdast.md_to_json(data))
|
|
57
52
|
|
|
58
53
|
@classmethod
|
|
@@ -83,11 +78,7 @@ class EditorJS:
|
|
|
83
78
|
"""
|
|
84
79
|
Export Markdown string
|
|
85
80
|
"""
|
|
86
|
-
|
|
87
|
-
# idk why this happens:
|
|
88
|
-
md = md.replace(r"\[ ]", "[ ]")
|
|
89
|
-
md = md.replace(r"\[x]", "[x]")
|
|
90
|
-
return md
|
|
81
|
+
return mdast.json_to_md(self.to_mdast())
|
|
91
82
|
|
|
92
83
|
def to_mdast(self) -> str:
|
|
93
84
|
"""
|
|
@@ -100,7 +91,6 @@ class EditorJS:
|
|
|
100
91
|
Export HTML string
|
|
101
92
|
"""
|
|
102
93
|
md = self.to_markdown()
|
|
103
|
-
# todo: deal with custom elements like linktool, attaches
|
|
104
94
|
return self._md.convert(md)
|
|
105
95
|
|
|
106
96
|
def __repr__(self):
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import textwrap
|
|
2
|
+
|
|
3
|
+
from editorjs import EditorJS
|
|
4
|
+
|
|
5
|
+
EXAMPLE_MD = textwrap.dedent("""
|
|
6
|
+
# Heading
|
|
7
|
+
|
|
8
|
+
Paragraph with *special* content __yeah__ such as *[links](https://g.co)!*
|
|
9
|
+
|
|
10
|
+
- list (unordered)
|
|
11
|
+
- with
|
|
12
|
+
- *nested* lvl 1.0
|
|
13
|
+
- __nested__ lvl 1.1
|
|
14
|
+
- nested lvl 2.0
|
|
15
|
+
- [items](https://g.co)
|
|
16
|
+
|
|
17
|
+
1. another
|
|
18
|
+
2. list (ordered)
|
|
19
|
+
2.1. with
|
|
20
|
+
2.2. nesting
|
|
21
|
+
3. *and styling*
|
|
22
|
+
|
|
23
|
+
***
|
|
24
|
+
|
|
25
|
+
Checkbox:
|
|
26
|
+
- [ ] unchecked
|
|
27
|
+
- [ ] *unchecked*
|
|
28
|
+
- [x] checked
|
|
29
|
+
- [x] *checked*
|
|
30
|
+
|
|
31
|
+
Inline `code`
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
def real_code(example: str):
|
|
35
|
+
pass
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Image: ; post image
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
> The secret to creativity is knowing how to hide your sources.
|
|
42
|
+
> Regel 2
|
|
43
|
+
> <cite>Albert Einstein</cite>
|
|
44
|
+
""")
|
|
45
|
+
|
|
46
|
+
EXAMPLE_JSON = r"""{"time":1730905327104,"blocks":[{"id":"1pY2hYfB1A","type":"header","data":{"text":"Heading","level":1}},{"id":"akvQa8k7ne","type":"paragraph","data":{"text":"Paragraph with special content yeah such as <a href=\"https://g.co\">links</a>!"}},{"id":"mtXTKYuP8k","type":"list","data":{"style":"unordered","items":[{"content":"list (unordered)","items":[]},{"content":"with","items":[{"content":"<i>nested</i> lvl 1.0","items":[]},{"content":"<b>nested</b> lvl 1.1","items":[{"content":"nested lvl 2.0","items":[]}]}]},{"content":"<a href=\"https://g.co\">items</a>","items":[]}]}},{"id":"CwdqcNPGp2","type":"list","data":{"style":"ordered","items":[{"content":"another","items":[]},{"content":"list (ordered)\n2.1. with\n2.2. nesting","items":[]},{"content":"<i>and styling</i>","items":[]}]}},{"id":"4aavopqblu","type":"delimiter","data":{}},{"id":"GgTUixlCYO","type":"paragraph","data":{"text":"Checkbox:"}},{"id":"E12TkYu5E_","type":"checklist","data":{"items":[{"text":"unchecked","checked":false},{"text":"<i>unchecked</i>","checked":false},{"text":"checked","checked":true},{"text":"<i>checked</i>","checked":true}]}},{"id":"I-HNbkhXE-","type":"paragraph","data":{"text":"Inline <code class=\"inline-code\">code</code>"}},{"id":"fNKmTrZvXn","type":"code","data":{"code":"def real_code(example: str): \n pass"}},{"id":"bfHSwu6hjy","type":"paragraph","data":{"text":"Image: "}},{"id":"gClWIHSnxn","type":"image","data":{"caption":"Caption","withBorder":false,"withBackground":false,"stretched":false,"file":{"url":"https://py4web.leiden.dockers.local/img/upload/16.txt?hash=3f4b66f9be3306fa7f42c0bcf6238d1101d9c75f"}}},{"id":"NZ_ruz2mXL","type":"paragraph","data":{"text":"; post image"}},{"id":"Nn7p_3Whpy","type":"quote","data":{"text":"The secret to creativity is knowing how to hide your sources.<br>\nRegel 2<br>\n","caption":"Albert Einstein","alignment":"left"}}],"version":"2.30.6"}"""
|
|
47
|
+
|
|
48
|
+
def test_md():
|
|
49
|
+
e = EditorJS.from_markdown(EXAMPLE_MD)
|
|
50
|
+
|
|
51
|
+
print(e.to_mdast())
|
|
52
|
+
print(e.to_markdown())
|
|
53
|
+
print(e.to_json())
|
|
54
|
+
print(e.to_html())
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_json():
|
|
58
|
+
e = EditorJS.from_json(EXAMPLE_JSON)
|
|
59
|
+
|
|
60
|
+
print(e.to_mdast())
|
|
61
|
+
print(e.to_json())
|
|
62
|
+
print(e.to_markdown())
|
|
63
|
+
print(e.to_html())
|
|
64
|
+
|
|
65
|
+
# def test_lossless():
|
|
66
|
+
# e = EditorJS.from_markdown(EXAMPLE_MD)
|
|
67
|
+
# assert e == EditorJS.from_mdast(e.to_mdast())
|
|
68
|
+
# assert e == EditorJS.from_json(e.to_json())
|
|
69
|
+
# assert e == EditorJS.from_markdown(e.to_markdown())
|
|
70
|
+
#
|
|
71
|
+
# e = EditorJS.from_json(EXAMPLE_JSON)
|
|
72
|
+
# assert e == EditorJS.from_mdast(e.to_mdast())
|
|
73
|
+
# assert e == EditorJS.from_json(e.to_json())
|
|
74
|
+
# assert e == EditorJS.from_markdown(e.to_markdown())
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2.0.0"
|