edwh-editorjs 2.3.1__tar.gz → 2.4.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.
Files changed (31) hide show
  1. {edwh_editorjs-2.3.1 → edwh_editorjs-2.4.0}/.gitignore +1 -0
  2. {edwh_editorjs-2.3.1 → edwh_editorjs-2.4.0}/CHANGELOG.md +16 -0
  3. {edwh_editorjs-2.3.1 → edwh_editorjs-2.4.0}/PKG-INFO +1 -1
  4. edwh_editorjs-2.4.0/editorjs/__about__.py +1 -0
  5. {edwh_editorjs-2.3.1 → edwh_editorjs-2.4.0}/editorjs/blocks.py +57 -28
  6. {edwh_editorjs-2.3.1 → edwh_editorjs-2.4.0}/editorjs/core.py +38 -10
  7. edwh_editorjs-2.4.0/editorjs/exceptions.py +8 -0
  8. {edwh_editorjs-2.3.1 → edwh_editorjs-2.4.0}/tests/test_core.py +10 -0
  9. edwh_editorjs-2.3.1/.github/workflows/build_documentation.yml +0 -36
  10. edwh_editorjs-2.3.1/.github/workflows/publish_to_pypi.yml +0 -40
  11. edwh_editorjs-2.3.1/.github/workflows/pytest.yml +0 -30
  12. edwh_editorjs-2.3.1/editorjs/__about__.py +0 -1
  13. edwh_editorjs-2.3.1/editorjs/exceptions.py +0 -3
  14. edwh_editorjs-2.3.1/htmlcov/.gitignore +0 -2
  15. edwh_editorjs-2.3.1/htmlcov/class_index.html +0 -259
  16. edwh_editorjs-2.3.1/htmlcov/favicon_32_cb_58284776.png +0 -0
  17. edwh_editorjs-2.3.1/htmlcov/function_index.html +0 -395
  18. edwh_editorjs-2.3.1/htmlcov/index.html +0 -132
  19. edwh_editorjs-2.3.1/htmlcov/keybd_closed_cb_ce680311.png +0 -0
  20. edwh_editorjs-2.3.1/htmlcov/status.json +0 -1
  21. edwh_editorjs-2.3.1/htmlcov/style_cb_8e611ae1.css +0 -337
  22. edwh_editorjs-2.3.1/htmlcov/z_a93c8aeb4b8fa1f9___init___py.html +0 -125
  23. edwh_editorjs-2.3.1/htmlcov/z_a93c8aeb4b8fa1f9_blocks_py.html +0 -406
  24. edwh_editorjs-2.3.1/htmlcov/z_a93c8aeb4b8fa1f9_exceptions_py.html +0 -116
  25. edwh_editorjs-2.3.1/htmlcov/z_a93c8aeb4b8fa1f9_parser_py.html +0 -172
  26. {edwh_editorjs-2.3.1 → edwh_editorjs-2.4.0}/README.md +0 -0
  27. {edwh_editorjs-2.3.1 → edwh_editorjs-2.4.0}/editorjs/__init__.py +0 -0
  28. {edwh_editorjs-2.3.1 → edwh_editorjs-2.4.0}/editorjs/helpers.py +0 -0
  29. {edwh_editorjs-2.3.1 → edwh_editorjs-2.4.0}/editorjs/types.py +0 -0
  30. {edwh_editorjs-2.3.1 → edwh_editorjs-2.4.0}/pyproject.toml +0 -0
  31. {edwh_editorjs-2.3.1 → edwh_editorjs-2.4.0}/tests/__init__.py +0 -0
@@ -11,6 +11,7 @@ dist/*
11
11
  .env
12
12
  venv*
13
13
  *coverage*
14
+ htmlcov/*
14
15
  dist/
15
16
  build/
16
17
  example.*
@@ -2,6 +2,22 @@
2
2
 
3
3
  <!--next-version-placeholder-->
4
4
 
5
+ ## v2.4.0 (2024-12-02)
6
+
7
+ ### Feature
8
+
9
+ * **image:** Support more options for the image block ([`c74dcce`](https://github.com/educationwarehouse/edwh-editorjs/commit/c74dccef5c1115038f6de0f08250dfa6cff18796))
10
+
11
+ ### Fix
12
+
13
+ * Less exceptions, more warnings for better ease-of-use ([`891fca1`](https://github.com/educationwarehouse/edwh-editorjs/commit/891fca198e774c53a8b6365b927a080f61c0b2c8))
14
+
15
+ ## v2.3.2 (2024-11-27)
16
+
17
+ ### Fix
18
+
19
+ * (refactor) clean up todo's, add `Unreachable` for when some branch/function should never be used ([`3226642`](https://github.com/educationwarehouse/edwh-editorjs/commit/3226642cfc8adb9a3cad6052d02ea6fc6c048058))
20
+
5
21
  ## v2.3.1 (2024-11-27)
6
22
 
7
23
  ### Fix
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: edwh-editorjs
3
- Version: 2.3.1
3
+ Version: 2.4.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>
@@ -0,0 +1 @@
1
+ __version__ = "2.4.0"
@@ -4,7 +4,6 @@ mdast to editorjs
4
4
 
5
5
  import abc
6
6
  import re
7
- import traceback
8
7
  import typing as t
9
8
  from html.parser import HTMLParser
10
9
  from urllib.parse import urlparse
@@ -12,7 +11,7 @@ from urllib.parse import urlparse
12
11
  import humanize
13
12
  import markdown2
14
13
 
15
- from .exceptions import TODO
14
+ from .exceptions import TODO, Unreachable
16
15
  from .types import EditorChildData, MDChildNode
17
16
 
18
17
 
@@ -62,7 +61,6 @@ def process_styled_content(item: MDChildNode, strict: bool = True) -> str:
62
61
  "strongEmphasis": "<b><i>{value}</i></b>",
63
62
  "link": '<a href="{url}">{value}</a>',
64
63
  "inlineCode": '<code class="inline-code">{value}</code>',
65
- # todo: <mark>, linktool
66
64
  }
67
65
 
68
66
  if _type in BLOCKS:
@@ -314,12 +312,6 @@ class ListBlock(EditorJSBlock):
314
312
  }
315
313
  )
316
314
 
317
- # todo: detect 'checklist':
318
- """
319
- type: checklist
320
- data: {items: [{text: "a", checked: false}, {text: "b", checked: false}, {text: "c", checked: true},…]}
321
- """
322
-
323
315
  if could_be_checklist:
324
316
  return [
325
317
  {
@@ -414,7 +406,20 @@ class ImageBlock(EditorJSBlock):
414
406
  def to_markdown(cls, data: EditorChildData) -> str:
415
407
  url = data.get("url", "") or data.get("file", {}).get("url", "")
416
408
  caption = data.get("caption", "")
417
- return f"""![{caption}]({url} "{caption}")\n\n"""
409
+
410
+ with_border = "1" if data.get("withBorder") else ""
411
+ with_background = "1" if data.get("withBackground") else ""
412
+ stretched = "1" if data.get("stretched") else ""
413
+
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"""
419
+
420
+ @classmethod
421
+ def _caption(cls, node: MDChildNode):
422
+ return node.get("alt") or node.get("caption") or ""
418
423
 
419
424
  @classmethod
420
425
  def to_json(cls, node: MDChildNode) -> list[dict]:
@@ -422,15 +427,34 @@ class ImageBlock(EditorJSBlock):
422
427
  {
423
428
  "type": "image",
424
429
  "data": {
425
- "caption": cls.to_text(node),
426
430
  "file": {"url": node.get("url")},
431
+ "caption": cls._caption(node),
432
+ "withBorder": bool(node.get("border", False)),
433
+ "stretched": bool(node.get("stretched", False)),
434
+ "withBackground": bool(node.get("background", False)),
427
435
  },
428
436
  }
429
437
  ]
430
438
 
431
439
  @classmethod
432
440
  def to_text(cls, node: MDChildNode) -> str:
433
- return node.get("alt") or node.get("caption") or ""
441
+ caption = cls._caption(node)
442
+ url = node.get("url")
443
+
444
+ background = node.get("background") or ""
445
+ stretched = node.get("stretched") or ""
446
+ border = node.get("border") or ""
447
+
448
+ return f"""
449
+ <div class="ce-block {stretched and 'ce-block--stretched'}">
450
+ <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'}">
452
+ <div class="image-tool__image">
453
+ <img class="image-tool__image-picture" src="{url}" title="{caption}" alt="{caption}">
454
+ </div>
455
+ </div>
456
+ </div>
457
+ """
434
458
 
435
459
 
436
460
  @block("blockquote", "quote")
@@ -482,10 +506,10 @@ class RawBlock(EditorJSBlock):
482
506
 
483
507
  @classmethod
484
508
  def to_json(cls, node: MDChildNode) -> list[dict]:
485
- # todo: apply same logic as paragraph block to find <editorjs/> items!!!
486
509
  raw = cls.to_text(node)
487
510
 
488
511
  if raw.startswith("<editorjs"):
512
+ # not a raw block but (probably) a self-closing editorjs block
489
513
  return EditorJSCustom.to_json({"children": [node]})
490
514
  else:
491
515
  return [raw_block(raw)]
@@ -559,7 +583,8 @@ class TableBlock(EditorJSBlock):
559
583
 
560
584
  @classmethod
561
585
  def to_text(cls, node: MDChildNode) -> str:
562
- raise TODO(node)
586
+ # I think this might be triggered if there is a table (deeply) within a paragraph block?
587
+ raise TODO(["TableBlock.to_text", node])
563
588
 
564
589
 
565
590
  @block("linkTool")
@@ -712,7 +737,7 @@ class AlignmentBlock(EditorJSBlock):
712
737
  data["level"] = int(tag.removeprefix("h"))
713
738
  else:
714
739
  # doesn't support alignment
715
- raise TODO(f"Unsupported tag for alignment: {tag}")
740
+ raise NotImplementedError(f"Unsupported tag for alignment: {tag}")
716
741
 
717
742
  return [
718
743
  {
@@ -777,7 +802,9 @@ class AttributeParser(HTMLParser):
777
802
 
778
803
  class EditorJSCustom(EditorJSBlock, markdown2.Extra):
779
804
  """
780
- Special type of block to deal with custom attributes
805
+ Special type of block to deal with custom attributes.
806
+
807
+ This is both a special editorjs block as well as a markdown2 plugin!
781
808
  """
782
809
 
783
810
  name = "editorjs"
@@ -792,23 +819,31 @@ class EditorJSCustom(EditorJSBlock, markdown2.Extra):
792
819
 
793
820
  @classmethod
794
821
  def to_markdown(cls, data: EditorChildData) -> str:
795
- raise TODO()
822
+ raise Unreachable("Custom Blocks have their own to_markdown logic.")
796
823
 
797
824
  @classmethod
798
- def to_json(cls, node: MDChildNode) -> list[dict]:
799
- html = "".join(_["value"] for _ in node.get("children", []))
825
+ def _find_right_block(cls, html: str) -> tuple[EditorJSBlock, dict]:
800
826
  attrs, body = cls.parse_html(html)
801
827
  _type = attrs.get("type", "")
802
828
  attrs.setdefault("body", body) # only if there is no such attribute yet
803
829
 
804
- if not (handler := BLOCKS.get(_type)):
830
+ handler = BLOCKS.get(_type)
831
+
832
+ if not handler:
805
833
  raise ValueError(f"Unknown custom type {_type}")
806
834
 
835
+ return handler, attrs
836
+
837
+ @classmethod
838
+ def to_json(cls, node: MDChildNode) -> list[dict]:
839
+ html = "".join(_["value"] for _ in node.get("children", []))
840
+ handler, attrs = cls._find_right_block(html)
807
841
  return handler.to_json(attrs)
808
842
 
809
843
  @classmethod
810
844
  def to_text(cls, node: MDChildNode) -> str:
811
- raise TODO()
845
+ handler, attrs = cls._find_right_block(node.get("value", ""))
846
+ return handler.to_text(attrs)
812
847
 
813
848
  # markdown2:
814
849
  re_short = re.compile(r"<editorjs.*?/>")
@@ -816,13 +851,7 @@ class EditorJSCustom(EditorJSBlock, markdown2.Extra):
816
851
 
817
852
  def run(self, text: str) -> str:
818
853
  def replace_html(match):
819
- attrs, body = self.parse_html(match.group())
820
- _type = attrs.get("type", "")
821
- attrs.setdefault("body", body) # only if there is no such attribute yet
822
-
823
- if not (handler := BLOCKS.get(_type)):
824
- raise ValueError(f"Unknown custom type {_type}")
825
-
854
+ handler, attrs = self._find_right_block(match.group())
826
855
  return handler.to_text(attrs)
827
856
 
828
857
  # Substitute using the replacement functions
@@ -1,11 +1,13 @@
1
1
  import json
2
2
  import typing as t
3
+ import warnings
3
4
 
4
5
  import markdown2
5
6
  import mdast
6
7
  from typing_extensions import Self
7
8
 
8
9
  from .blocks import BLOCKS
10
+ from .exceptions import TODO
9
11
  from .helpers import unix_timestamp
10
12
  from .types import MDRootNode
11
13
 
@@ -41,13 +43,26 @@ class EditorJS:
41
43
  for child in blocks:
42
44
  _type = child["type"]
43
45
  if not (block := BLOCKS.get(_type)):
44
- raise TypeError(f"from_json: Unsupported block type `{_type}`")
45
-
46
- data = child.get("data", {})
47
- # forward any 'tunes' via data:
48
- data["tunes"] = data.get("tunes") or child.get("tunes") or {}
49
-
50
- markdown_items.append(block.to_markdown(data))
46
+ warnings.warn(
47
+ f"from_json: Unsupported block type `{_type}`",
48
+ category=RuntimeWarning,
49
+ )
50
+ continue
51
+
52
+ try:
53
+ data = child.get("data", {})
54
+ # forward any 'tunes' via data:
55
+ data["tunes"] = data.get("tunes") or child.get("tunes") or {}
56
+
57
+ markdown_items.append(block.to_markdown(data))
58
+ except Exception as e:
59
+ warnings.warn(
60
+ "from_json: Oh oh, unexpected block failure!",
61
+ category=RuntimeWarning,
62
+ source=e,
63
+ )
64
+ # if isinstance(e, TODO):
65
+ # raise e
51
66
 
52
67
  markdown = "".join(markdown_items)
53
68
  return cls.from_markdown(markdown)
@@ -76,9 +91,22 @@ class EditorJS:
76
91
  for child in self._mdast["children"]:
77
92
  _type = child["type"]
78
93
  if not (block := BLOCKS.get(_type)):
79
- raise TypeError(f"to_json: Unsupported block type `{_type}`")
80
-
81
- blocks.extend(block.to_json(child))
94
+ warnings.warn(
95
+ f"to_json: Unsupported block type `{_type}`",
96
+ category=RuntimeWarning,
97
+ )
98
+ continue
99
+
100
+ try:
101
+ blocks.extend(block.to_json(child))
102
+ except Exception as e:
103
+ warnings.warn(
104
+ "to_json: Oh oh, unexpected block failure!",
105
+ category=RuntimeWarning,
106
+ source=e,
107
+ )
108
+ # if isinstance(e, TODO):
109
+ # raise e
82
110
 
83
111
  data = {"time": unix_timestamp(), "blocks": blocks, "version": EDITORJS_VERSION}
84
112
 
@@ -0,0 +1,8 @@
1
+ class TODO(NotImplementedError):
2
+ def __init__(self, msg: str = "todo"):
3
+ super().__init__(msg)
4
+
5
+
6
+ class Unreachable(NotImplementedError):
7
+ def __init__(self, msg: str = "unimplemented"):
8
+ super().__init__(msg)
@@ -246,3 +246,13 @@ def test_embed():
246
246
  print(e.to_markdown())
247
247
  print(e.to_html())
248
248
  print(e.to_json())
249
+
250
+
251
+ def test_image_options():
252
+ json_blocks = r"""{"time":1733155142016,"blocks":[{"id":"e7_WBThzLQ","type":"image","data":{"caption":"border","withBorder":true,"withBackground":false,"stretched":false,"file":{"url":"https://py4web.leiden.dockers.local/img/upload/5.jpg?hash=b39755c8a568cbf45d329e3a3128fb43065b1d1b","name":"kat.jpg","title":"kat","extension":"jpg","size":3682051}}},{"id":"B5qVcjqBuB","type":"image","data":{"caption":"stretch","withBorder":false,"withBackground":false,"stretched":true,"file":{"url":"https://py4web.leiden.dockers.local/img/upload/6.jpg?hash=b39755c8a568cbf45d329e3a3128fb43065b1d1b","name":"kat.jpg","title":"kat","extension":"jpg","size":3682051}}},{"id":"ft32yP2_cv","type":"image","data":{"caption":"background","withBorder":false,"withBackground":true,"stretched":false,"file":{"url":"https://py4web.leiden.dockers.local/img/upload/7.jpg?hash=b39755c8a568cbf45d329e3a3128fb43065b1d1b","name":"kat.jpg","title":"kat","extension":"jpg","size":3682051}}}],"version":"2.30.7"}"""
253
+
254
+ e = EditorJS.from_json(json_blocks)
255
+
256
+ print(e.to_markdown())
257
+ print(e.to_html())
258
+ print(e.to_json())
@@ -1,36 +0,0 @@
1
- name: Build documentation
2
-
3
- on:
4
- workflow_dispatch:
5
- release:
6
- types: [published]
7
-
8
- jobs:
9
- build:
10
- runs-on: ubuntu-latest
11
- strategy:
12
- matrix:
13
- python-version: ['3.10']
14
-
15
- steps:
16
- - uses: actions/checkout@v2
17
-
18
- - name: Set up Python ${{ matrix.python-version }}
19
- uses: actions/setup-python@v2
20
- with:
21
- python-version: ${{ matrix.python-version }}
22
-
23
- - name: Install dependencies
24
- run: |
25
- python -m pip install --upgrade pip
26
- pip install pdoc
27
- if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
28
- - name: Build docs
29
- run: |
30
- chmod +x ./scripts/build_documentation.sh
31
- bash ./scripts/build_documentation.sh
32
- - name: Deploy
33
- uses: JamesIves/github-pages-deploy-action@4.1.4
34
- with:
35
- branch: documentation
36
- folder: ./documentation
@@ -1,40 +0,0 @@
1
- name: Upload Python Package to PyPI
2
-
3
- on:
4
- release:
5
- types: [published]
6
-
7
- jobs:
8
- deploy:
9
- runs-on: ubuntu-latest
10
- strategy:
11
- matrix:
12
- python-version: ['3.10']
13
-
14
- steps:
15
- - uses: actions/checkout@v2
16
-
17
- - name: Set up Python ${{ matrix.python-version }}
18
- uses: actions/setup-python@v2
19
- with:
20
- python-version: ${{ matrix.python-version }}
21
-
22
- - name: Install dependencies
23
- run: |
24
- python -m pip install --upgrade pip
25
- python -m pip install pytest
26
- if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
27
-
28
- - name: Test with pyTest
29
- run: |
30
- python -m pytest -v
31
-
32
- - name: Build package
33
- run: |
34
- bash ./scripts/compile_for_pypi.sh
35
-
36
- - name: Publish package
37
- uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
38
- with:
39
- user: __token__
40
- password: ${{ secrets.PYPI_API_TOKEN }}
@@ -1,30 +0,0 @@
1
- name: Test with pyTest
2
-
3
- on: [workflow_dispatch, push, pull_request]
4
-
5
- jobs:
6
- deploy:
7
- runs-on: ubuntu-latest
8
- strategy:
9
- matrix:
10
- python-version: ['3.10']
11
- fail-fast: false
12
-
13
- steps:
14
- - uses: actions/checkout@v2
15
-
16
- - name: Set up Python
17
- uses: actions/setup-python@v2
18
- with:
19
- python-version: ${{ matrix.python-version }}
20
- cache: 'pip'
21
-
22
- - name: Install dependencies
23
- run: |
24
- python -m pip install --upgrade pip
25
- python -m pip install pytest
26
- if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
27
-
28
- - name: Test with pyTest
29
- run: |
30
- python -m pytest -v
@@ -1 +0,0 @@
1
- __version__ = "2.3.1"
@@ -1,3 +0,0 @@
1
- class TODO(NotImplementedError):
2
- def __init__(self, msg: str = "todo"):
3
- super().__init__(msg)
@@ -1,2 +0,0 @@
1
- # Created by coverage.py
2
- *