vyasa 0.3.8__py3-none-any.whl → 0.3.10__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.
- vyasa/__init__.py +1 -1
- vyasa/core.py +94 -4
- {vyasa-0.3.8.dist-info → vyasa-0.3.10.dist-info}/METADATA +1 -1
- vyasa-0.3.10.dist-info/RECORD +16 -0
- vyasa-0.3.8.dist-info/RECORD +0 -16
- {vyasa-0.3.8.dist-info → vyasa-0.3.10.dist-info}/WHEEL +0 -0
- {vyasa-0.3.8.dist-info → vyasa-0.3.10.dist-info}/entry_points.txt +0 -0
- {vyasa-0.3.8.dist-info → vyasa-0.3.10.dist-info}/licenses/LICENSE +0 -0
- {vyasa-0.3.8.dist-info → vyasa-0.3.10.dist-info}/top_level.txt +0 -0
vyasa/__init__.py
CHANGED
vyasa/core.py
CHANGED
|
@@ -69,6 +69,8 @@ def span_token(name, pat, attr, prec=5):
|
|
|
69
69
|
self.option = match.group(2) if match.group(2) else None
|
|
70
70
|
elif name == 'IframeEmbed':
|
|
71
71
|
self.options = match.group(2) if match.group(2) else None
|
|
72
|
+
elif name == 'DownloadEmbed':
|
|
73
|
+
self.label = match.group(2) if match.group(2) else None
|
|
72
74
|
T.__name__ = name
|
|
73
75
|
return T
|
|
74
76
|
|
|
@@ -85,6 +87,12 @@ IframeEmbed = span_token(
|
|
|
85
87
|
'src',
|
|
86
88
|
6
|
|
87
89
|
)
|
|
90
|
+
DownloadEmbed = span_token(
|
|
91
|
+
'DownloadEmbed',
|
|
92
|
+
r'\[download:([^\|\]]+)(?:\|(.+))?\]',
|
|
93
|
+
'path',
|
|
94
|
+
6
|
|
95
|
+
)
|
|
88
96
|
|
|
89
97
|
# Superscript and Subscript tokens with higher precedence
|
|
90
98
|
class Superscript(mst.span_token.SpanToken):
|
|
@@ -295,6 +303,7 @@ class ContentRenderer(FrankenRenderer):
|
|
|
295
303
|
allowfullscreen = True
|
|
296
304
|
caption = None
|
|
297
305
|
popup = False
|
|
306
|
+
border = 'default'
|
|
298
307
|
|
|
299
308
|
# Parse options: key=value;key=value
|
|
300
309
|
if options_raw:
|
|
@@ -318,6 +327,8 @@ class ContentRenderer(FrankenRenderer):
|
|
|
318
327
|
caption = value
|
|
319
328
|
elif key == 'popup':
|
|
320
329
|
popup = value.lower() in ('1', 'true', 'yes', 'on')
|
|
330
|
+
elif key == 'border':
|
|
331
|
+
border = value.lower()
|
|
321
332
|
|
|
322
333
|
# Break out of normal content flow for viewport widths
|
|
323
334
|
break_out = 'vw' in str(width).lower()
|
|
@@ -344,8 +355,15 @@ class ContentRenderer(FrankenRenderer):
|
|
|
344
355
|
'</div>'
|
|
345
356
|
)
|
|
346
357
|
|
|
358
|
+
if border in ('black', 'dark'):
|
|
359
|
+
border_classes = 'border border-black'
|
|
360
|
+
elif border in ('none', 'false', '0', 'off'):
|
|
361
|
+
border_classes = 'border border-transparent'
|
|
362
|
+
else:
|
|
363
|
+
border_classes = 'border border-slate-200 dark:border-slate-800'
|
|
364
|
+
|
|
347
365
|
iframe = f'''
|
|
348
|
-
<div class="relative my-6 rounded-lg overflow-hidden
|
|
366
|
+
<div class="relative my-6 rounded-lg overflow-hidden {border_classes}" style="{container_style}">
|
|
349
367
|
{fullscreen_button}
|
|
350
368
|
<iframe
|
|
351
369
|
id="{iframe_id}"
|
|
@@ -362,6 +380,34 @@ class ContentRenderer(FrankenRenderer):
|
|
|
362
380
|
if caption:
|
|
363
381
|
return iframe + f'<p class="text-sm text-slate-500 dark:text-slate-400 text-center mt-2">{caption}</p>'
|
|
364
382
|
return iframe
|
|
383
|
+
|
|
384
|
+
def render_download_embed(self, token):
|
|
385
|
+
from pathlib import Path
|
|
386
|
+
raw_path = token.path.strip()
|
|
387
|
+
label = getattr(token, 'label', None)
|
|
388
|
+
|
|
389
|
+
# Resolve relative paths against current_path like normal links
|
|
390
|
+
if self.current_path:
|
|
391
|
+
root = get_root_folder().resolve()
|
|
392
|
+
current_file_full = root / self.current_path
|
|
393
|
+
current_dir = current_file_full.parent
|
|
394
|
+
resolved = (current_dir / raw_path).resolve()
|
|
395
|
+
try:
|
|
396
|
+
rel_path = resolved.relative_to(root).as_posix()
|
|
397
|
+
download_path = f"/download/{rel_path}"
|
|
398
|
+
except ValueError:
|
|
399
|
+
download_path = raw_path
|
|
400
|
+
else:
|
|
401
|
+
download_path = f"/download/{raw_path}"
|
|
402
|
+
|
|
403
|
+
if not label:
|
|
404
|
+
label = Path(raw_path).name
|
|
405
|
+
|
|
406
|
+
link_class = (
|
|
407
|
+
"text-amber-600 dark:text-amber-400 underline underline-offset-2 "
|
|
408
|
+
"hover:text-amber-800 dark:hover:text-amber-200 font-medium transition-colors"
|
|
409
|
+
)
|
|
410
|
+
return f'<a href="{download_path}" class="{link_class}" download>{label}</a>'
|
|
365
411
|
|
|
366
412
|
def render_footnote_ref(self, token):
|
|
367
413
|
self.fn_counter += 1
|
|
@@ -562,6 +608,12 @@ class ContentRenderer(FrankenRenderer):
|
|
|
562
608
|
is_external = href.startswith(('http://', 'https://', 'mailto:', 'tel:', '//'))
|
|
563
609
|
is_absolute_internal = href.startswith('/') and not href.startswith('//')
|
|
564
610
|
is_relative = not is_external and not is_absolute_internal
|
|
611
|
+
download_flag = False
|
|
612
|
+
if token.title and 'download=true' in token.title.lower():
|
|
613
|
+
download_flag = True
|
|
614
|
+
if '?download=true' in href.lower():
|
|
615
|
+
href = re.sub(r'\?download=true', '', href, flags=re.IGNORECASE)
|
|
616
|
+
download_flag = True
|
|
565
617
|
if is_hash:
|
|
566
618
|
link_class = (
|
|
567
619
|
"text-amber-600 dark:text-amber-400 underline underline-offset-2 "
|
|
@@ -593,12 +645,23 @@ class ContentRenderer(FrankenRenderer):
|
|
|
593
645
|
is_internal = is_absolute_internal and '.' not in href.split('/')[-1]
|
|
594
646
|
hx = f' hx-get="{href}" hx-target="#main-content" hx-push-url="true" hx-swap="innerHTML show:window:top"' if is_internal else ''
|
|
595
647
|
ext = '' if (is_internal or is_absolute_internal or is_hash) else ' target="_blank" rel="noopener noreferrer"'
|
|
648
|
+
download_attr = ''
|
|
649
|
+
if download_flag:
|
|
650
|
+
download_attr = ' download'
|
|
651
|
+
if href.startswith('/posts/'):
|
|
652
|
+
download_target = href[len('/posts/'):]
|
|
653
|
+
elif href.startswith('/'):
|
|
654
|
+
download_target = href.lstrip('/')
|
|
655
|
+
else:
|
|
656
|
+
download_target = href
|
|
657
|
+
href = f'/download/{download_target}'
|
|
658
|
+
hx = ''
|
|
596
659
|
# Amber/gold link styling, stands out and is accessible
|
|
597
660
|
link_class = (
|
|
598
661
|
"text-amber-600 dark:text-amber-400 underline underline-offset-2 "
|
|
599
662
|
"hover:text-amber-800 dark:hover:text-amber-200 font-medium transition-colors"
|
|
600
663
|
)
|
|
601
|
-
return f'<a href="{href}"{hx}{ext} class="{link_class}"{title}>{inner}</a>'
|
|
664
|
+
return f'<a href="{href}"{hx}{ext}{download_attr} class="{link_class}"{title}>{inner}</a>'
|
|
602
665
|
|
|
603
666
|
|
|
604
667
|
def postprocess_tabs(html, tab_data_store, img_dir, current_path, footnotes):
|
|
@@ -621,7 +684,7 @@ def postprocess_tabs(html, tab_data_store, img_dir, current_path, footnotes):
|
|
|
621
684
|
for i, (_, tab_content) in enumerate(tabs):
|
|
622
685
|
active = 'active' if i == 0 else ''
|
|
623
686
|
# Render each tab's content as fresh markdown
|
|
624
|
-
with ContentRenderer(YoutubeEmbed, InlineCodeAttr, Strikethrough, FootnoteRef, Superscript, Subscript, img_dir=img_dir, footnotes=footnotes, current_path=current_path) as renderer:
|
|
687
|
+
with ContentRenderer(YoutubeEmbed, IframeEmbed, DownloadEmbed, InlineCodeAttr, Strikethrough, FootnoteRef, Superscript, Subscript, img_dir=img_dir, footnotes=footnotes, current_path=current_path) as renderer:
|
|
625
688
|
doc = mst.Document(tab_content)
|
|
626
689
|
rendered = renderer.render(doc)
|
|
627
690
|
html_parts.append(f'<div class="tab-panel {active}" data-tab-index="{i}">{rendered}</div>')
|
|
@@ -702,7 +765,7 @@ def from_md(content, img_dir=None, current_path=None):
|
|
|
702
765
|
'table': 'uk-table uk-table-striped uk-table-hover uk-table-divider uk-table-middle my-6'}
|
|
703
766
|
|
|
704
767
|
# Register custom tokens with renderer context manager
|
|
705
|
-
with ContentRenderer(YoutubeEmbed, IframeEmbed, InlineCodeAttr, Strikethrough, FootnoteRef, Superscript, Subscript, img_dir=img_dir, footnotes=footnotes, current_path=current_path) as renderer:
|
|
768
|
+
with ContentRenderer(YoutubeEmbed, IframeEmbed, DownloadEmbed, InlineCodeAttr, Strikethrough, FootnoteRef, Superscript, Subscript, img_dir=img_dir, footnotes=footnotes, current_path=current_path) as renderer:
|
|
706
769
|
doc = mst.Document(content)
|
|
707
770
|
html = renderer.render(doc)
|
|
708
771
|
|
|
@@ -1924,6 +1987,33 @@ def serve_post_static(path: str, ext: str):
|
|
|
1924
1987
|
return FileResponse(file_path)
|
|
1925
1988
|
return Response(status_code=404)
|
|
1926
1989
|
|
|
1990
|
+
# Serve JSON attachments from blog posts (not included in fasthtml static exts)
|
|
1991
|
+
@rt("/posts/{path:path}.json")
|
|
1992
|
+
def serve_post_json(path: str):
|
|
1993
|
+
from starlette.responses import FileResponse
|
|
1994
|
+
file_path = get_root_folder() / f'{path}.json'
|
|
1995
|
+
if file_path.exists():
|
|
1996
|
+
return FileResponse(
|
|
1997
|
+
file_path,
|
|
1998
|
+
headers={"Content-Disposition": f'attachment; filename="{file_path.name}"'}
|
|
1999
|
+
)
|
|
2000
|
+
return Response(status_code=404)
|
|
2001
|
+
|
|
2002
|
+
# Generic download route for any file under the blog root
|
|
2003
|
+
@rt("/download/{path:path}")
|
|
2004
|
+
def download_file(path: str):
|
|
2005
|
+
from starlette.responses import FileResponse
|
|
2006
|
+
root = get_root_folder().resolve()
|
|
2007
|
+
file_path = (root / path).resolve()
|
|
2008
|
+
if not str(file_path).startswith(str(root) + os.sep):
|
|
2009
|
+
return Response(status_code=403)
|
|
2010
|
+
if file_path.exists() and file_path.is_file():
|
|
2011
|
+
return FileResponse(
|
|
2012
|
+
file_path,
|
|
2013
|
+
headers={"Content-Disposition": f'attachment; filename="{file_path.name}"'}
|
|
2014
|
+
)
|
|
2015
|
+
return Response(status_code=404)
|
|
2016
|
+
|
|
1927
2017
|
def theme_toggle():
|
|
1928
2018
|
theme_script = """on load set franken to (localStorage's __FRANKEN__ or '{}') as Object
|
|
1929
2019
|
if franken's mode is 'dark' then add .dark to <html/> end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
vyasa/__init__.py,sha256=T0ZRRHOvcleFaTybYJyhb7pRgPUBQjeQHftxbLgmLj4,158
|
|
2
|
+
vyasa/agent.py,sha256=XD4V6rM-JrVtqTVa3pZqPy_BUmcQgKdidQaY4il632Q,3196
|
|
3
|
+
vyasa/build.py,sha256=USqeZsXGPB8COzdWRPLgSgkVg6DIzQwL0qGGJaMM7Ec,26548
|
|
4
|
+
vyasa/config.py,sha256=7mjBTnv7MlBF4CUHA-2IZDt9l9_EMjgOLmQ00I2LQZA,7768
|
|
5
|
+
vyasa/core.py,sha256=79HAv7f-cIF3jMbU6A6wTh7wyUDSSosMnsVrZaI4p4s,134134
|
|
6
|
+
vyasa/helpers.py,sha256=VgOzAIoL3X8RYLvKQRd-S4jM-sjmlolyxFlyOiP7bmM,12489
|
|
7
|
+
vyasa/layout_helpers.py,sha256=uodzhyrGoqC6egbGuzuAPJP2yiWYM8krxb64dE4nwpc,1082
|
|
8
|
+
vyasa/main.py,sha256=xcneOe3Vw3vxfxSii2Y7C5DJTFLz71JDCY_CDZKVwxc,4073
|
|
9
|
+
vyasa/static/scripts.js,sha256=ofLQZWt4QHkuR6RyNvkDxQaEoMBop5vqYLaUBn1TDlE,47954
|
|
10
|
+
vyasa/static/sidenote.css,sha256=9YEl111Qs6kGnrgcVQtC1eiccMGY3-lu05l_NnwXVME,1045
|
|
11
|
+
vyasa-0.3.10.dist-info/licenses/LICENSE,sha256=xV8xoN4VOL0uw9X8RSs2IMuD_Ss_a9yAbtGNeBWZwnw,11337
|
|
12
|
+
vyasa-0.3.10.dist-info/METADATA,sha256=5ZqmrcpkHAsXxfKnIC7COk_rDcEQeX_BBfGMLUH4qYs,9792
|
|
13
|
+
vyasa-0.3.10.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
14
|
+
vyasa-0.3.10.dist-info/entry_points.txt,sha256=ypyjtxRlygocldmIxOIijoME2BIfxzU0uuNfdi3_1us,41
|
|
15
|
+
vyasa-0.3.10.dist-info/top_level.txt,sha256=Pwy2VqkeYm2zJ5zQ-6XsJHNPsAU4xwscZn-8siQz9Pg,6
|
|
16
|
+
vyasa-0.3.10.dist-info/RECORD,,
|
vyasa-0.3.8.dist-info/RECORD
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
vyasa/__init__.py,sha256=89B_FfWXTlbCEDljhZnJIJO4yim9LM6xT01hwd2pWuA,158
|
|
2
|
-
vyasa/agent.py,sha256=XD4V6rM-JrVtqTVa3pZqPy_BUmcQgKdidQaY4il632Q,3196
|
|
3
|
-
vyasa/build.py,sha256=USqeZsXGPB8COzdWRPLgSgkVg6DIzQwL0qGGJaMM7Ec,26548
|
|
4
|
-
vyasa/config.py,sha256=7mjBTnv7MlBF4CUHA-2IZDt9l9_EMjgOLmQ00I2LQZA,7768
|
|
5
|
-
vyasa/core.py,sha256=PwxX-8DGYLEwXGulMQpwO-y_GSU9L7MDsQaHuXQ_LfU,130627
|
|
6
|
-
vyasa/helpers.py,sha256=VgOzAIoL3X8RYLvKQRd-S4jM-sjmlolyxFlyOiP7bmM,12489
|
|
7
|
-
vyasa/layout_helpers.py,sha256=uodzhyrGoqC6egbGuzuAPJP2yiWYM8krxb64dE4nwpc,1082
|
|
8
|
-
vyasa/main.py,sha256=xcneOe3Vw3vxfxSii2Y7C5DJTFLz71JDCY_CDZKVwxc,4073
|
|
9
|
-
vyasa/static/scripts.js,sha256=ofLQZWt4QHkuR6RyNvkDxQaEoMBop5vqYLaUBn1TDlE,47954
|
|
10
|
-
vyasa/static/sidenote.css,sha256=9YEl111Qs6kGnrgcVQtC1eiccMGY3-lu05l_NnwXVME,1045
|
|
11
|
-
vyasa-0.3.8.dist-info/licenses/LICENSE,sha256=xV8xoN4VOL0uw9X8RSs2IMuD_Ss_a9yAbtGNeBWZwnw,11337
|
|
12
|
-
vyasa-0.3.8.dist-info/METADATA,sha256=h_eBtbWMEUHNfiJGpgBgdGEf9cDy3HxrTvwS2ZNut9A,9791
|
|
13
|
-
vyasa-0.3.8.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
14
|
-
vyasa-0.3.8.dist-info/entry_points.txt,sha256=ypyjtxRlygocldmIxOIijoME2BIfxzU0uuNfdi3_1us,41
|
|
15
|
-
vyasa-0.3.8.dist-info/top_level.txt,sha256=Pwy2VqkeYm2zJ5zQ-6XsJHNPsAU4xwscZn-8siQz9Pg,6
|
|
16
|
-
vyasa-0.3.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|