diff --git a/docs/changelog.md b/docs/changelog.md index 11d60f5c..c6b18438 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,6 +1,13 @@ ITables ChangeLog ================= +2.5.0-dev (unreleased) +------------------ + +**Fixed** +- The offline mode now allows the init cell to be rendered after the table cells. It should work more reliably in VS Code ([#424](https://github.com/mwouts/itables/issues/424)) + + 2.4.5 (2025-08-23) ------------------ diff --git a/src/itables/html/datatables_template_offline.html b/src/itables/html/datatables_template_offline.html new file mode 100644 index 00000000..77229047 --- /dev/null +++ b/src/itables/html/datatables_template_offline.html @@ -0,0 +1,25 @@ + +
+ diff --git a/src/itables/html/init_notebook_offline.html b/src/itables/html/init_notebook_offline.html new file mode 100644 index 00000000..bc5024d2 --- /dev/null +++ b/src/itables/html/init_notebook_offline.html @@ -0,0 +1,25 @@ + diff --git a/src/itables/javascript.py b/src/itables/javascript.py index c98008af..6679929b 100644 --- a/src/itables/javascript.py +++ b/src/itables/javascript.py @@ -50,9 +50,10 @@ from .downsample import downsample from .utils import read_package_file -DATATABLES_SRC_FOR_ITABLES = ( - f"_datatables_src_for_itables_{itables_version.replace('.','_').replace('-','_')}" +_ITABLES_UNDERSCORE_VERSION = ( + f"_itables_{itables_version.replace('.','_').replace('-','_')}" ) +_ITABLES_READY_EVENT = f"itables-{itables_version}-ready" _OPTIONS_NOT_AVAILABLE_IN_APP_MODE = { "connected", "dt_url", @@ -179,7 +180,7 @@ def init_notebook_mode( ) local_import = ( "const { set_or_remove_dark_class } = await import(window." - + DATATABLES_SRC_FOR_ITABLES + + _ITABLES_UNDERSCORE_VERSION + ");" ) init_datatables = replace_value(init_datatables, connected_import, local_import) @@ -201,13 +202,26 @@ def generate_init_offline_itables_html(dt_bundle: Union[Path, str]) -> str: assert dt_bundle.suffix == ".js" dt_src = dt_bundle.read_text(encoding="utf-8") dt_css = dt_bundle.with_suffix(".css").read_text(encoding="utf-8") - dt64 = b64encode(dt_src.encode("utf-8")).decode("ascii") + dt_src_b64 = b64encode(dt_src.encode("utf-8")).decode("ascii") + dt_css_b64 = b64encode(dt_css.encode("utf-8")).decode("ascii") + + init_notebook_mode = read_package_file("html/init_notebook_offline.html") + init_notebook_mode = replace_value( + init_notebook_mode, + "_itables_underscore_version", + _ITABLES_UNDERSCORE_VERSION, + expected_count=3, + ) + init_notebook_mode = replace_value( + init_notebook_mode, "itables-version-ready", _ITABLES_READY_EVENT + ) + init_notebook_mode = replace_value(init_notebook_mode, "dt_src_b64", dt_src_b64) + init_notebook_mode = replace_value(init_notebook_mode, "dt_css_b64", dt_css_b64) - return f""" + return ( + init_notebook_mode + + f"""
-
""" + ) def _table_header( @@ -297,17 +312,15 @@ def get_keys_to_be_evaluated(data: Any) -> list[list[Union[int, str]]]: return keys_to_be_evaluated -def replace_value(template: str, pattern: str, value: str) -> str: +def replace_value( + template: str, pattern: str, value: str, expected_count: int = 1 +) -> str: """Set the given pattern to the desired value in the template, after making sure that the pattern is found exactly once.""" count = template.count(pattern) - if not count: - raise ValueError("pattern={} was not found in template".format(pattern)) - elif count > 1: + if count != expected_count: raise ValueError( - "pattern={} was found multiple times ({}) in template".format( - pattern, count - ) + f"{pattern=} was found {count} times in template, expected {expected_count}." ) return template.replace(pattern, value) @@ -702,8 +715,8 @@ def html_table_from_template( ) # Load the HTML template - output = read_package_file("html/datatables_template.html") if connected: + output = read_package_file("html/datatables_template.html") assert dt_url.endswith(".js") output = replace_value(output, UNPKG_DT_BUNDLE_URL_NO_VERSION, dt_url) output = replace_value( @@ -712,21 +725,14 @@ def html_table_from_template( dt_url[:-3] + ".css", ) else: - connected_style = ( - f'\n' - ) - output = replace_value(output, connected_style, "") - connected_import = ( - "import { ITable, jQuery as $ } from '" - + UNPKG_DT_BUNDLE_URL_NO_VERSION - + "';" - ) - local_import = ( - "const { ITable, jQuery: $ } = await import(window." - + DATATABLES_SRC_FOR_ITABLES - + ");" + output = read_package_file("html/datatables_template_offline.html") + output = replace_value( + output, + "_itables_underscore_version", + _ITABLES_UNDERSCORE_VERSION, + expected_count=2, ) - output = replace_value(output, connected_import, local_import) + output = replace_value(output, "itables-version-ready", _ITABLES_READY_EVENT) itables_source = ( "the internet" if connected else "the init_notebook_mode cell" diff --git a/src/itables/version.py b/src/itables/version.py index 5689dd5c..bc0001e7 100644 --- a/src/itables/version.py +++ b/src/itables/version.py @@ -1,3 +1,3 @@ """ITables' version number""" -__version__ = "2.4.5" +__version__ = "2.5.0-dev" diff --git a/tests/test_connected_notebook_is_small.py b/tests/test_connected_notebook_is_small.py index bfab24a2..993ddad9 100644 --- a/tests/test_connected_notebook_is_small.py +++ b/tests/test_connected_notebook_is_small.py @@ -40,4 +40,4 @@ def test_offline_notebook_is_not_too_large(tmp_path): nb_py.write_text(text_notebook(connected=False)) jupytext([str(nb_py), "--to", "ipynb", "--set-kernel", "itables", "--execute"]) assert nb_ipynb.exists() - assert 750000 < nb_ipynb.stat().st_size < 850000 + assert 825000 < nb_ipynb.stat().st_size < 875000 diff --git a/tests/test_javascript.py b/tests/test_javascript.py index f0e587de..88bcbd1e 100644 --- a/tests/test_javascript.py +++ b/tests/test_javascript.py @@ -73,14 +73,23 @@ def test_replace_value( def test_replace_value_not_found( template="line1\nline2\nline3\n", pattern="line4", value="new line4" ): - with pytest.raises(ValueError, match="not found"): + with pytest.raises(ValueError, match="was found 0 times in template"): assert replace_value(template, pattern, value) +def test_replace_value_multiple_expected( + template="line1\nline2\nline2\n", pattern="line2", value="new line2" +): + assert ( + replace_value(template, pattern, value, expected_count=2) + == "line1\nnew line2\nnew line2\n" + ) + + def test_replace_value_multiple( template="line1\nline2\nline2\n", pattern="line2", value="new line2" ): - with pytest.raises(ValueError, match="found multiple times"): + with pytest.raises(ValueError, match="was found 2 times in template, expected 1."): assert replace_value(template, pattern, value)