kash-shell 0.3.10__py3-none-any.whl → 0.3.11__py3-none-any.whl
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.
- kash/actions/core/format_markdown_template.py +2 -5
- kash/actions/core/markdownify.py +2 -4
- kash/actions/core/readability.py +2 -4
- kash/actions/core/render_as_html.py +30 -11
- kash/actions/core/show_webpage.py +6 -11
- kash/actions/core/strip_html.py +2 -6
- kash/actions/core/{webpage_config.py → tabbed_webpage_config.py} +5 -3
- kash/actions/core/{webpage_generate.py → tabbed_webpage_generate.py} +5 -4
- kash/commands/base/files_command.py +28 -10
- kash/commands/workspace/workspace_commands.py +1 -2
- kash/config/colors.py +2 -2
- kash/exec/action_decorators.py +6 -6
- kash/exec/llm_transforms.py +6 -3
- kash/exec/preconditions.py +6 -0
- kash/exec/resolve_args.py +4 -0
- kash/file_storage/file_store.py +20 -18
- kash/help/function_param_info.py +1 -1
- kash/local_server/local_server_routes.py +1 -7
- kash/model/items_model.py +74 -28
- kash/shell/utils/shell_function_wrapper.py +15 -15
- kash/text_handling/doc_normalization.py +1 -1
- kash/text_handling/markdown_render.py +1 -0
- kash/text_handling/markdown_utils.py +22 -0
- kash/utils/common/function_inspect.py +360 -110
- kash/utils/file_utils/file_ext.py +4 -0
- kash/utils/file_utils/file_formats_model.py +17 -1
- kash/web_gen/__init__.py +0 -4
- kash/web_gen/simple_webpage.py +52 -0
- kash/web_gen/tabbed_webpage.py +23 -16
- kash/web_gen/template_render.py +37 -2
- kash/web_gen/templates/base_styles.css.jinja +76 -56
- kash/web_gen/templates/base_webpage.html.jinja +85 -67
- kash/web_gen/templates/item_view.html.jinja +47 -37
- kash/web_gen/templates/simple_webpage.html.jinja +24 -0
- kash/web_gen/templates/tabbed_webpage.html.jinja +42 -32
- {kash_shell-0.3.10.dist-info → kash_shell-0.3.11.dist-info}/METADATA +5 -5
- {kash_shell-0.3.10.dist-info → kash_shell-0.3.11.dist-info}/RECORD +40 -38
- {kash_shell-0.3.10.dist-info → kash_shell-0.3.11.dist-info}/WHEEL +0 -0
- {kash_shell-0.3.10.dist-info → kash_shell-0.3.11.dist-info}/entry_points.txt +0 -0
- {kash_shell-0.3.10.dist-info → kash_shell-0.3.11.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from kash.model.items_model import Item
|
|
2
|
+
from kash.utils.file_utils.file_formats_model import Format
|
|
3
|
+
from kash.web_gen.template_render import render_web_template
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def simple_webpage_render(
|
|
7
|
+
item: Item,
|
|
8
|
+
page_template: str = "simple_webpage.html.jinja",
|
|
9
|
+
add_title_h1: bool = True,
|
|
10
|
+
) -> str:
|
|
11
|
+
"""
|
|
12
|
+
Generate a simple web page from a single item.
|
|
13
|
+
If `add_title_h1` is True, the title will be inserted as an h1 heading above the body.
|
|
14
|
+
"""
|
|
15
|
+
return render_web_template(
|
|
16
|
+
template_filename=page_template,
|
|
17
|
+
data={
|
|
18
|
+
"title": item.title,
|
|
19
|
+
"add_title_h1": add_title_h1,
|
|
20
|
+
"content_html": item.body_as_html(),
|
|
21
|
+
"thumbnail_url": item.thumbnail_url,
|
|
22
|
+
},
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
## Tests
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_render():
|
|
30
|
+
import os
|
|
31
|
+
|
|
32
|
+
from kash.model.items_model import ItemType
|
|
33
|
+
|
|
34
|
+
# Create a test item
|
|
35
|
+
item = Item(
|
|
36
|
+
type=ItemType.doc,
|
|
37
|
+
format=Format.html,
|
|
38
|
+
title="A Simple Web Page",
|
|
39
|
+
body="<p>This is a simple web page with <b>HTML content</b>.</p>",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Generate HTML
|
|
43
|
+
html = simple_webpage_render(item)
|
|
44
|
+
|
|
45
|
+
os.makedirs("tmp", exist_ok=True)
|
|
46
|
+
with open("tmp/simple_webpage.html", "w") as f:
|
|
47
|
+
f.write(html)
|
|
48
|
+
print("Rendered simple webpage to tmp/simple_webpage.html")
|
|
49
|
+
|
|
50
|
+
# Basic validation
|
|
51
|
+
assert item.title and item.title in html
|
|
52
|
+
assert "<b>HTML content</b>" in html
|
kash/web_gen/tabbed_webpage.py
CHANGED
|
@@ -2,7 +2,7 @@ import os
|
|
|
2
2
|
from dataclasses import asdict, dataclass
|
|
3
3
|
|
|
4
4
|
from frontmatter_format import read_yaml_file, to_yaml_string, write_yaml_file
|
|
5
|
-
from prettyfmt import sanitize_title
|
|
5
|
+
from prettyfmt import abbrev_on_words, sanitize_title
|
|
6
6
|
|
|
7
7
|
from kash.config.logger import get_logger
|
|
8
8
|
from kash.exec.preconditions import has_thumbnail_url
|
|
@@ -12,7 +12,6 @@ from kash.model.paths_model import StorePath
|
|
|
12
12
|
from kash.utils.common.type_utils import as_dataclass, not_none
|
|
13
13
|
from kash.utils.errors import NoMatch
|
|
14
14
|
from kash.utils.file_utils.file_formats_model import Format
|
|
15
|
-
from kash.web_gen import base_templates_dir
|
|
16
15
|
from kash.web_gen.template_render import render_web_template
|
|
17
16
|
from kash.workspaces import current_ws
|
|
18
17
|
from kash.workspaces.source_items import find_upstream_item
|
|
@@ -34,6 +33,7 @@ class TabbedWebpage:
|
|
|
34
33
|
title: str
|
|
35
34
|
tabs: list[TabInfo]
|
|
36
35
|
show_tabs: bool = True
|
|
36
|
+
add_title_h1: bool = True
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
def _fill_in_ids(tabs: list[TabInfo]):
|
|
@@ -42,7 +42,9 @@ def _fill_in_ids(tabs: list[TabInfo]):
|
|
|
42
42
|
tab.id = f"tab_{i}"
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
def
|
|
45
|
+
def tabbed_webpage_config(
|
|
46
|
+
items: list[Item], clean_headings: bool = False, add_title_h1: bool = True
|
|
47
|
+
) -> Item:
|
|
46
48
|
"""
|
|
47
49
|
Get an item with the config for a tabbed web page.
|
|
48
50
|
"""
|
|
@@ -58,11 +60,15 @@ def webpage_config(items: list[Item], clean_headings: bool = False) -> Item:
|
|
|
58
60
|
log.warning("Item has no thumbnail URL: %s", item)
|
|
59
61
|
return None
|
|
60
62
|
|
|
61
|
-
|
|
63
|
+
def clean_label(label: str) -> str:
|
|
64
|
+
if clean_headings:
|
|
65
|
+
return clean_heading(label)
|
|
66
|
+
else:
|
|
67
|
+
return abbrev_on_words(sanitize_title(label), max_len=40)
|
|
62
68
|
|
|
63
69
|
tabs = [
|
|
64
70
|
TabInfo(
|
|
65
|
-
label=
|
|
71
|
+
label=clean_label(item.abbrev_title()),
|
|
66
72
|
store_path=item.store_path,
|
|
67
73
|
thumbnail_url=get_thumbnail_url(item),
|
|
68
74
|
)
|
|
@@ -70,7 +76,9 @@ def webpage_config(items: list[Item], clean_headings: bool = False) -> Item:
|
|
|
70
76
|
]
|
|
71
77
|
_fill_in_ids(tabs)
|
|
72
78
|
title = summary_heading([item.abbrev_title() for item in items])
|
|
73
|
-
config = TabbedWebpage(
|
|
79
|
+
config = TabbedWebpage(
|
|
80
|
+
title=title, tabs=tabs, show_tabs=len(tabs) > 1, add_title_h1=add_title_h1
|
|
81
|
+
)
|
|
74
82
|
|
|
75
83
|
config_item = Item(
|
|
76
84
|
title=f"{title} (config)",
|
|
@@ -91,7 +99,9 @@ def _load_tab_content(config: TabbedWebpage):
|
|
|
91
99
|
tab.content_html = html
|
|
92
100
|
|
|
93
101
|
|
|
94
|
-
def
|
|
102
|
+
def tabbed_webpage_generate(
|
|
103
|
+
config_item: Item, page_template: str = "base_webpage.html.jinja", add_title_h1: bool = True
|
|
104
|
+
) -> str:
|
|
95
105
|
"""
|
|
96
106
|
Generate a web page using the supplied config.
|
|
97
107
|
"""
|
|
@@ -101,14 +111,15 @@ def webpage_generate(config_item: Item) -> str:
|
|
|
101
111
|
_load_tab_content(tabbed_webpage)
|
|
102
112
|
|
|
103
113
|
content = render_web_template(
|
|
104
|
-
|
|
114
|
+
template_filename="tabbed_webpage.html.jinja",
|
|
115
|
+
data=asdict(tabbed_webpage),
|
|
105
116
|
)
|
|
106
117
|
|
|
107
118
|
return render_web_template(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
{
|
|
119
|
+
page_template,
|
|
120
|
+
data={
|
|
111
121
|
"title": tabbed_webpage.title,
|
|
122
|
+
"add_title_h1": add_title_h1,
|
|
112
123
|
"content": content,
|
|
113
124
|
},
|
|
114
125
|
)
|
|
@@ -138,11 +149,7 @@ def test_render():
|
|
|
138
149
|
new_config = as_dataclass(read_yaml_file("tmp/webpage_config.yaml"), TabbedWebpage)
|
|
139
150
|
assert new_config == config
|
|
140
151
|
|
|
141
|
-
html = render_web_template(
|
|
142
|
-
base_templates_dir,
|
|
143
|
-
"tabbed_webpage.html.jinja",
|
|
144
|
-
asdict(config),
|
|
145
|
-
)
|
|
152
|
+
html = render_web_template(template_filename="tabbed_webpage.html.jinja", data=asdict(config))
|
|
146
153
|
with open("tmp/webpage.html", "w") as f:
|
|
147
154
|
f.write(html)
|
|
148
155
|
print("Rendered tabbed webpage to tmp/webpage.html")
|
kash/web_gen/template_render.py
CHANGED
|
@@ -1,12 +1,45 @@
|
|
|
1
|
+
from collections.abc import Iterator
|
|
2
|
+
from contextlib import contextmanager
|
|
3
|
+
from contextvars import ContextVar
|
|
1
4
|
from pathlib import Path
|
|
2
5
|
|
|
3
6
|
from jinja2 import Environment, FileSystemLoader
|
|
4
7
|
|
|
5
8
|
from kash.config import colors
|
|
6
9
|
|
|
10
|
+
_base_templates_dir = Path(__file__).parent / "templates"
|
|
11
|
+
"""Common base web page templates."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_additional_template_dirs: ContextVar[list[Path] | None] = ContextVar(
|
|
15
|
+
"_additional_template_dirs", default=None
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_template_dirs(*dirs: Path) -> list[Path]:
|
|
20
|
+
"""
|
|
21
|
+
Returns template directories currently in context along with any
|
|
22
|
+
additional template directories.
|
|
23
|
+
"""
|
|
24
|
+
additional = _additional_template_dirs.get() or []
|
|
25
|
+
return list(dirs) + [*additional, _base_templates_dir]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@contextmanager
|
|
29
|
+
def additional_template_dirs(*dirs: Path) -> Iterator[None]:
|
|
30
|
+
"""
|
|
31
|
+
Context manager for temporarily adding template directories to the search path.
|
|
32
|
+
"""
|
|
33
|
+
original = _additional_template_dirs.get()
|
|
34
|
+
current = [] if not original else original.copy()
|
|
35
|
+
token = _additional_template_dirs.set(current + list(dirs))
|
|
36
|
+
try:
|
|
37
|
+
yield
|
|
38
|
+
finally:
|
|
39
|
+
_additional_template_dirs.reset(token)
|
|
40
|
+
|
|
7
41
|
|
|
8
42
|
def render_web_template(
|
|
9
|
-
templates_dir: Path,
|
|
10
43
|
template_filename: str,
|
|
11
44
|
data: dict,
|
|
12
45
|
autoescape: bool = True,
|
|
@@ -14,11 +47,13 @@ def render_web_template(
|
|
|
14
47
|
) -> str:
|
|
15
48
|
"""
|
|
16
49
|
Render a Jinja2 template file with the given data, returning an HTML string.
|
|
50
|
+
Uses template directories from the base directory and any added via context manager.
|
|
17
51
|
"""
|
|
18
52
|
if css_overrides is None:
|
|
19
53
|
css_overrides = {}
|
|
20
54
|
|
|
21
|
-
|
|
55
|
+
search_paths = get_template_dirs()
|
|
56
|
+
env = Environment(loader=FileSystemLoader(search_paths), autoescape=autoescape)
|
|
22
57
|
|
|
23
58
|
# Load and render the template.
|
|
24
59
|
template = env.get_template(template_filename)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
:root {
|
|
2
|
+
{% block root_variables %}
|
|
2
3
|
font-size: 16px;
|
|
3
4
|
/* Adding Hack Nerd Font to all fonts for icon support, if it is installed. */
|
|
4
5
|
--font-sans: "Source Sans 3 Variable", sans-serif, "Hack Nerd Font";
|
|
@@ -16,14 +17,17 @@
|
|
|
16
17
|
|
|
17
18
|
--console-char-width: 88;
|
|
18
19
|
--console-width: calc(var(--console-char-width) + 2rem);
|
|
20
|
+
{% endblock root_variables %}
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
{{ color_defs|safe }}
|
|
22
24
|
|
|
25
|
+
{% block selection_styles %}
|
|
23
26
|
::selection {
|
|
24
27
|
background-color: var(--color-selection);
|
|
25
28
|
color: inherit;
|
|
26
29
|
}
|
|
30
|
+
{% endblock selection_styles %}
|
|
27
31
|
|
|
28
32
|
{# TODO: Fix PDF issues and re-enable for prettier emoji. #}
|
|
29
33
|
{# @font-face {
|
|
@@ -39,6 +43,7 @@
|
|
|
39
43
|
U+1F900-1F9FF; /* Supplemental Symbols and Pictographs */
|
|
40
44
|
} #}
|
|
41
45
|
|
|
46
|
+
{% block scrollbar_styles %}
|
|
42
47
|
/* Scrollbar coloring. */
|
|
43
48
|
/* For Webkit browsers (Chrome, Safari) */
|
|
44
49
|
::-webkit-scrollbar {
|
|
@@ -60,7 +65,9 @@
|
|
|
60
65
|
scrollbar-width: thin;
|
|
61
66
|
scrollbar-color: var(--color-scrollbar) var(--color-bg);
|
|
62
67
|
}
|
|
68
|
+
{% endblock scrollbar_styles %}
|
|
63
69
|
|
|
70
|
+
{% block body_styles %}
|
|
64
71
|
body {
|
|
65
72
|
font-family: var(--font-serif);
|
|
66
73
|
color: var(--color-text);
|
|
@@ -70,7 +77,9 @@ body {
|
|
|
70
77
|
background-color: var(--color-bg);
|
|
71
78
|
overflow-wrap: break-word; /* Don't let long words/URLs break layout. */
|
|
72
79
|
}
|
|
80
|
+
{% endblock body_styles %}
|
|
73
81
|
|
|
82
|
+
{% block typography %}
|
|
74
83
|
p {
|
|
75
84
|
margin-bottom: 1rem;
|
|
76
85
|
}
|
|
@@ -120,37 +129,6 @@ h4 {
|
|
|
120
129
|
margin-bottom: 0.7rem;
|
|
121
130
|
}
|
|
122
131
|
|
|
123
|
-
/* Long text stylings, for nicely formatting blog post length or longer texts. */
|
|
124
|
-
|
|
125
|
-
.long-text h1 {
|
|
126
|
-
font-family: var(--font-serif);
|
|
127
|
-
font-weight: 400;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
.long-text h2 {
|
|
131
|
-
font-family: var(--font-serif);
|
|
132
|
-
font-weight: 400;
|
|
133
|
-
font-style: italic;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
.long-text h3 {
|
|
137
|
-
font-family: var(--font-sans);
|
|
138
|
-
font-weight: var(--font-weight-sans-bold);
|
|
139
|
-
text-transform: uppercase;
|
|
140
|
-
letter-spacing: 0.02em;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
.long-text h4 {
|
|
144
|
-
font-family: var(--font-serif);
|
|
145
|
-
font-weight: 700;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
.subtitle {
|
|
149
|
-
font-family: var(--font-serif);
|
|
150
|
-
font-style: italic;
|
|
151
|
-
font-size: 1rem;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
132
|
ul {
|
|
155
133
|
list-style-type: none;
|
|
156
134
|
margin-left: 2rem;
|
|
@@ -207,7 +185,42 @@ pre {
|
|
|
207
185
|
letter-spacing: -0.025em;
|
|
208
186
|
{# overflow-x: auto; #}
|
|
209
187
|
}
|
|
188
|
+
{% endblock typography %}
|
|
189
|
+
|
|
190
|
+
{% block long_text_styles %}
|
|
191
|
+
/* Long text stylings, for nicely formatting blog post length or longer texts. */
|
|
192
|
+
|
|
193
|
+
.long-text h1 {
|
|
194
|
+
font-family: var(--font-serif);
|
|
195
|
+
font-weight: 400;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.long-text h2 {
|
|
199
|
+
font-family: var(--font-serif);
|
|
200
|
+
font-weight: 400;
|
|
201
|
+
font-style: italic;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.long-text h3 {
|
|
205
|
+
font-family: var(--font-sans);
|
|
206
|
+
font-weight: var(--font-weight-sans-bold);
|
|
207
|
+
text-transform: uppercase;
|
|
208
|
+
letter-spacing: 0.02em;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.long-text h4 {
|
|
212
|
+
font-family: var(--font-serif);
|
|
213
|
+
font-weight: 700;
|
|
214
|
+
}
|
|
210
215
|
|
|
216
|
+
.subtitle {
|
|
217
|
+
font-family: var(--font-serif);
|
|
218
|
+
font-style: italic;
|
|
219
|
+
font-size: 1rem;
|
|
220
|
+
}
|
|
221
|
+
{% endblock long_text_styles %}
|
|
222
|
+
|
|
223
|
+
{% block table_styles %}
|
|
211
224
|
table {
|
|
212
225
|
font-family: var(--font-sans);
|
|
213
226
|
font-size: var(--font-size-small);
|
|
@@ -239,16 +252,6 @@ tbody tr:nth-child(even) {
|
|
|
239
252
|
background-color: var(--color-bg-alt-solid);
|
|
240
253
|
}
|
|
241
254
|
|
|
242
|
-
nav {
|
|
243
|
-
display: flex;
|
|
244
|
-
flex-wrap: wrap;
|
|
245
|
-
/* Allow wrapping */
|
|
246
|
-
justify-content: center;
|
|
247
|
-
/* Center the content */
|
|
248
|
-
gap: 1rem;
|
|
249
|
-
/* Add some space between the buttons */
|
|
250
|
-
}
|
|
251
|
-
|
|
252
255
|
/* Container for wide tables to allow tables to break out of parent width. */
|
|
253
256
|
.table-container {
|
|
254
257
|
{# max-width: calc(100vw - 6rem); #}
|
|
@@ -258,26 +261,24 @@ nav {
|
|
|
258
261
|
box-sizing: border-box;
|
|
259
262
|
margin-bottom: 1rem;
|
|
260
263
|
background-color: var(--color-bg-solid);
|
|
261
|
-
|
|
262
264
|
}
|
|
265
|
+
{% endblock table_styles %}
|
|
263
266
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
267
|
+
{% block nav_styles %}
|
|
268
|
+
nav {
|
|
269
|
+
display: flex;
|
|
270
|
+
flex-wrap: wrap;
|
|
271
|
+
/* Allow wrapping */
|
|
272
|
+
justify-content: center;
|
|
273
|
+
/* Center the content */
|
|
274
|
+
gap: 1rem;
|
|
275
|
+
/* Add some space between the buttons */
|
|
273
276
|
}
|
|
277
|
+
{% endblock nav_styles %}
|
|
274
278
|
|
|
275
|
-
@media (max-width: 768px) {
|
|
276
|
-
table {
|
|
277
|
-
font-size: var(--font-size-smaller);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
279
|
|
|
280
|
+
|
|
281
|
+
{% block footnote_styles %}
|
|
281
282
|
/* Footnotes. */
|
|
282
283
|
sup {
|
|
283
284
|
font-size: 80%;
|
|
@@ -295,4 +296,23 @@ sup {
|
|
|
295
296
|
color: var(--color-primary-light);
|
|
296
297
|
text-decoration: none;
|
|
297
298
|
}
|
|
299
|
+
{% endblock footnote_styles %}
|
|
300
|
+
|
|
301
|
+
{% block responsive_styles %}
|
|
302
|
+
/* Bleed wide on larger screens. */
|
|
303
|
+
/* TODO: Don't make so wide if table itself isn't large? */
|
|
304
|
+
@media (min-width: 768px) {
|
|
305
|
+
table {
|
|
306
|
+
width: calc(100vw - 6rem);
|
|
307
|
+
}
|
|
308
|
+
.table-container {
|
|
309
|
+
width: calc(100vw - 6rem);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
298
312
|
|
|
313
|
+
@media (max-width: 768px) {
|
|
314
|
+
table {
|
|
315
|
+
font-size: var(--font-size-smaller);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
{% endblock responsive_styles %}
|
|
@@ -2,10 +2,14 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
|
|
4
4
|
<head>
|
|
5
|
+
{% block meta %}
|
|
5
6
|
<meta charset="UTF-8" />
|
|
6
7
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
-
|
|
8
|
+
{% endblock meta %}
|
|
8
9
|
|
|
10
|
+
{% block title %}<title>{{ title }}</title>{% endblock title %}
|
|
11
|
+
|
|
12
|
+
{% block head_basic %}
|
|
9
13
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
10
14
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
11
15
|
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
|
|
@@ -26,12 +30,12 @@
|
|
|
26
30
|
|
|
27
31
|
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet" />
|
|
28
32
|
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js" defer></script>
|
|
33
|
+
{% endblock head_basic %}
|
|
29
34
|
|
|
30
|
-
{%
|
|
31
|
-
{{ extra_head|safe }}
|
|
32
|
-
{% endif %}
|
|
35
|
+
{% block head_extra %}{% endblock head_extra %}
|
|
33
36
|
|
|
34
37
|
<style>
|
|
38
|
+
{% block font_faces %}
|
|
35
39
|
/* https://fontsource.org/fonts/pt-serif/cdn */
|
|
36
40
|
/* pt-serif-latin-400-normal */
|
|
37
41
|
@font-face {
|
|
@@ -88,84 +92,98 @@
|
|
|
88
92
|
src: url(https://cdn.jsdelivr.net/fontsource/fonts/source-sans-3:vf@latest/latin-wght-italic.woff2) format('woff2-variations');
|
|
89
93
|
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
|
|
90
94
|
}
|
|
91
|
-
|
|
92
95
|
{# Other decent sans serif options: Work Sans Variable, Nunito Sans Variable #}
|
|
96
|
+
{% endblock font_faces %}
|
|
93
97
|
|
|
98
|
+
{% block base_styles %}
|
|
94
99
|
{% include "base_styles.css.jinja" %}
|
|
100
|
+
{% endblock base_styles %}
|
|
101
|
+
|
|
102
|
+
{% block content_styles %}
|
|
95
103
|
{% include "content_styles.css.jinja" %}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
{{
|
|
99
|
-
{% endif %}
|
|
104
|
+
{% endblock content_styles %}
|
|
105
|
+
|
|
106
|
+
{% block custom_styles %}{% endblock custom_styles %}
|
|
100
107
|
</style>
|
|
101
|
-
|
|
102
108
|
</head>
|
|
103
109
|
|
|
104
110
|
<body>
|
|
105
|
-
{{
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
// Wrap tables within the main content area for horizontal scrolling.
|
|
123
|
-
const containers = [];
|
|
124
|
-
document.querySelectorAll('.long-text').forEach(el => {
|
|
125
|
-
const pane = el.querySelector('.tab-pane');
|
|
126
|
-
containers.push(pane || el);
|
|
127
|
-
});
|
|
128
|
-
containers.forEach(container => {
|
|
129
|
-
// Grab all tables, then only wrap the ones whose parent is this container.
|
|
130
|
-
container.querySelectorAll('table').forEach(table => {
|
|
131
|
-
if (table.parentElement !== container) {
|
|
132
|
-
return; // Only direct children.
|
|
133
|
-
}
|
|
134
|
-
if (table.parentNode.classList.contains('table-container')) {
|
|
135
|
-
return; // Already wrapped.
|
|
136
|
-
}
|
|
137
|
-
const wrapper = document.createElement('div');
|
|
138
|
-
wrapper.className = 'table-container';
|
|
139
|
-
container.insertBefore(wrapper, table);
|
|
140
|
-
wrapper.appendChild(table);
|
|
141
|
-
});
|
|
142
|
-
});
|
|
111
|
+
{% block body_header %}{% endblock body_header %}
|
|
112
|
+
|
|
113
|
+
{% block main_content %}
|
|
114
|
+
{{ content|safe }}
|
|
115
|
+
{% endblock main_content %}
|
|
116
|
+
|
|
117
|
+
{% block body_footer %}{% endblock body_footer %}
|
|
118
|
+
|
|
119
|
+
{% block scripts %}
|
|
120
|
+
<script>
|
|
121
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
122
|
+
// Send messages to the parent window, in case we are in a viewport where that matters
|
|
123
|
+
// (e.g. an iframe tooltip).
|
|
124
|
+
// Request a resize of the parent viewport. This iframe size message format isn't
|
|
125
|
+
// standardized by ResizeObserver, but is common. It is supported by Kerm.
|
|
126
|
+
const content = document.body;
|
|
127
|
+
console.log("Suggesting resize to parent:", content.offsetWidth, content.offsetHeight);
|
|
143
128
|
|
|
144
|
-
|
|
129
|
+
window.parent.postMessage({
|
|
130
|
+
type: 'resize',
|
|
131
|
+
width: Math.max(content.offsetWidth, 600),
|
|
132
|
+
height: Math.max(content.offsetHeight, 100)
|
|
133
|
+
}, '*');
|
|
145
134
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
135
|
+
// Wrap tables within the main content area for horizontal scrolling.
|
|
136
|
+
const containers = [];
|
|
137
|
+
document.querySelectorAll('.long-text').forEach(el => {
|
|
138
|
+
const pane = el.querySelector('.tab-pane');
|
|
139
|
+
containers.push(pane || el);
|
|
140
|
+
});
|
|
141
|
+
containers.forEach(container => {
|
|
142
|
+
// Find all tables within the container.
|
|
143
|
+
const tables = Array.from(container.querySelectorAll('table'));
|
|
144
|
+
tables.forEach(table => {
|
|
145
|
+
// Skip tables already in table-container divs
|
|
146
|
+
if (table.parentNode.classList.contains('table-container')) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
// Create the wrapper.
|
|
151
|
+
const wrapper = document.createElement('div');
|
|
152
|
+
wrapper.className = 'table-container';
|
|
153
|
+
// Get the parent and insert the wrapper where the table is, then move the table into the wrapper.
|
|
154
|
+
const parent = table.parentNode;
|
|
155
|
+
parent.insertBefore(wrapper, table);
|
|
156
|
+
wrapper.appendChild(table);
|
|
157
|
+
} catch (e) {
|
|
158
|
+
console.error("Error wrapping table:", e);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
});
|
|
153
163
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
console.log("Sending close message to parent");
|
|
164
|
+
// Double-click to expand (e.g. expand tooltip to popover).
|
|
165
|
+
document.addEventListener('dblclick', () => {
|
|
166
|
+
console.log("Sending expand message to parent");
|
|
158
167
|
window.parent.postMessage({
|
|
159
|
-
type: '
|
|
168
|
+
type: 'expand'
|
|
160
169
|
}, '*');
|
|
161
|
-
}
|
|
162
|
-
});
|
|
170
|
+
});
|
|
163
171
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
172
|
+
// Escape to close tooltip or popover.
|
|
173
|
+
document.addEventListener('keydown', (event) => {
|
|
174
|
+
if (event.key === 'Escape') {
|
|
175
|
+
console.log("Sending close message to parent");
|
|
176
|
+
window.parent.postMessage({
|
|
177
|
+
type: 'close'
|
|
178
|
+
}, '*');
|
|
179
|
+
}
|
|
180
|
+
});
|
|
168
181
|
|
|
182
|
+
{% block scripts_extra %}{% endblock scripts_extra %}
|
|
183
|
+
</script>
|
|
184
|
+
{% endblock scripts %}
|
|
185
|
+
|
|
186
|
+
{% block analytics %}{% endblock analytics %}
|
|
169
187
|
</body>
|
|
170
188
|
|
|
171
189
|
</html>
|