diff --git a/CHANGELOG.md b/CHANGELOG.md index 5225638..14d3385 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,22 @@ is the error. Tags carry no `v` prefix. ## [Unreleased] +## [0.4.2] - 2026-10-07 + +A patch release of the content writer: a link written into a memo opens in a +new window. Reading is unchanged, and so is everything outside +`easyvista_python_client.content`. + +### Changed + +- **Every link `to_transport` writes carries `target="_blank" + rel="noopener noreferrer"`**, as a link written in EasyVista's own editor + does. Without it, EasyVista's memo view opens the link in place, inside its + own window, so a link to another application seemed to do nothing (measured + on a live memo, 2026-10-07: links with and without the attributes, side by + side). Reading ignores both attributes, so `from_transport(to_transport(m))` + is unchanged for every link. + ## [0.4.1] - 2026-10-02 A patch release of the content reader. A ``, `` or `` round a @@ -1645,7 +1661,8 @@ Initial public release. status/error code, with non-retryable validation errors (HTTP 590, code 2013). - `py.typed` marker — the package ships inline type information. -[Unreleased]: https://github.com/baraline/easyvista_python_client/compare/0.4.1...HEAD +[Unreleased]: https://github.com/baraline/easyvista_python_client/compare/0.4.2...HEAD +[0.4.2]: https://github.com/baraline/easyvista_python_client/compare/0.4.1...0.4.2 [0.4.1]: https://github.com/baraline/easyvista_python_client/compare/0.4.0...0.4.1 [0.4.0]: https://github.com/baraline/easyvista_python_client/compare/0.3.0...0.4.0 [0.3.0]: https://github.com/baraline/easyvista_python_client/compare/0.2.0...0.3.0 diff --git a/docs/content.rst b/docs/content.rst index 82e81ef..89312ad 100644 --- a/docs/content.rst +++ b/docs/content.rst @@ -214,6 +214,11 @@ options, ``HARDBREAKS`` and ``UNSAFE``: * a fence becomes ``
``, with the info string as a
   ``class="language-..."``;
 * a table becomes a ```` with a ````;
+* a link opens in a new window: each ```` the renderer writes carries
+  ``target="_blank" rel="noopener noreferrer"``, as a link written in
+  EasyVista's editor does. Without it the memo view opens the link in place,
+  inside EasyVista's window (measured 2026-10-07). Reading ignores both
+  attributes, so a round trip is unchanged;
 * raw HTML, inline or as a block, is passed through as written;
 * ``""``, or only whitespace, stays ``""`` rather than becoming ``

``. diff --git a/easyvista_python_client/_version.py b/easyvista_python_client/_version.py index f45571a..3ce3d4c 100644 --- a/easyvista_python_client/_version.py +++ b/easyvista_python_client/_version.py @@ -5,4 +5,4 @@ live only in ``__init__``. """ -__version__ = "0.4.1" +__version__ = "0.4.2" diff --git a/easyvista_python_client/content/conversion.py b/easyvista_python_client/content/conversion.py index a60efc4..45a4960 100644 --- a/easyvista_python_client/content/conversion.py +++ b/easyvista_python_client/content/conversion.py @@ -748,12 +748,26 @@ def html_to_markdown(html: str) -> str: return str(_FORMATTER.render(_CONVERTER.convert_soup(soup))).strip() +#: A link as cmark-gfm writes one: an ``href``, an optional ``title``, nothing else. +_RENDERED_LINK = re.compile(r'
') + +#: What EasyVista's own editor writes on a link: without it the memo view opens the +#: link in place, inside EasyVista's window, rather than in a new one. +_NEW_WINDOW = ' target="_blank" rel="noopener noreferrer"' + + def markdown_to_html(markdown: str) -> str: - """Render Markdown as HTML: CommonMark with GFM tables, through cmark-gfm.""" + """Render Markdown as HTML: CommonMark with GFM tables, through cmark-gfm. + + Every link opens in a new window, the way a link written in EasyVista's + editor does: ``target="_blank" rel="noopener noreferrer"`` is added to each + link cmark-gfm writes. Reading ignores both attributes. + """ html: str = cmarkgfm.markdown_to_html_with_extensions( markdown, options=_RENDER_OPTIONS, extensions=["table"] ) + html = _RENDERED_LINK.sub(lambda link: link.group(0)[:-1] + _NEW_WINDOW + ">", html) return html.strip() diff --git a/easyvista_python_client/content/tests/test_conversion.py b/easyvista_python_client/content/tests/test_conversion.py index 7205bdb..0804cfa 100644 --- a/easyvista_python_client/content/tests/test_conversion.py +++ b/easyvista_python_client/content/tests/test_conversion.py @@ -29,6 +29,9 @@ read = EasyvistaContentConverter.from_transport render = EasyvistaContentConverter.to_transport +#: What every rendered link carries: EasyVista's editor writes it on a link it makes. +NEW_WINDOW = ' target="_blank" rel="noopener noreferrer"' + def test_content_is_markdown_in_python_and_html_for_easyvista() -> None: assert read("

The printer is offline.

") == ( @@ -233,12 +236,12 @@ def test_deeply_nested_markdown_renders() -> None: ), pytest.param( "[x](javascript:alert(1))", - '

x

', + f'

x

', id="link-target", ), pytest.param( # python-markdown, before 0.4.0, left this as raw markup "", - '

javascript:alert(1)

', + f'

javascript:alert(1)

', id="autolink", ), ], @@ -258,3 +261,62 @@ def test_markup_a_memo_displays_as_text_stays_text_both_ways() -> None: assert markdown == "\\