vyasa 0.3.6__py3-none-any.whl → 0.3.8__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 CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "0.3.5"
1
+ __version__ = "0.3.6"
2
2
 
3
3
  from .core import app, rt, get_root_folder, get_blog_title
4
4
 
vyasa/core.py CHANGED
@@ -67,6 +67,8 @@ def span_token(name, pat, attr, prec=5):
67
67
  self.caption = match.group(2) if match.group(2) else None
68
68
  elif name == 'MermaidEmbed':
69
69
  self.option = match.group(2) if match.group(2) else None
70
+ elif name == 'IframeEmbed':
71
+ self.options = match.group(2) if match.group(2) else None
70
72
  T.__name__ = name
71
73
  return T
72
74
 
@@ -77,6 +79,12 @@ YoutubeEmbed = span_token(
77
79
  'video_id',
78
80
  6
79
81
  )
82
+ IframeEmbed = span_token(
83
+ 'IframeEmbed',
84
+ r'\[iframe:([^\|\]]+)(?:\|(.+))?\]',
85
+ 'src',
86
+ 6
87
+ )
80
88
 
81
89
  # Superscript and Subscript tokens with higher precedence
82
90
  class Superscript(mst.span_token.SpanToken):
@@ -221,6 +229,7 @@ class ContentRenderer(FrankenRenderer):
221
229
  self.current_path = current_path # Current post path for resolving relative links and images
222
230
  self.heading_counts = {}
223
231
  self.mermaid_counter = 0
232
+ self.iframe_counter = 0
224
233
 
225
234
  def render_list_item(self, token):
226
235
  """Render list items with task list checkbox support"""
@@ -273,6 +282,86 @@ class ContentRenderer(FrankenRenderer):
273
282
  if caption:
274
283
  return iframe + f'<p class="text-sm text-slate-500 dark:text-slate-400 text-center mt-2">{caption}</p>'
275
284
  return iframe
285
+
286
+ def render_iframe_embed(self, token):
287
+ src = token.src.strip()
288
+ options_raw = getattr(token, 'options', None)
289
+
290
+ # Defaults
291
+ width = '65vw'
292
+ height = '400px'
293
+ title = 'Embedded content'
294
+ allow = 'clipboard-read; clipboard-write; fullscreen'
295
+ allowfullscreen = True
296
+ caption = None
297
+ popup = False
298
+
299
+ # Parse options: key=value;key=value
300
+ if options_raw:
301
+ for part in options_raw.split(';'):
302
+ if not part.strip() or '=' not in part:
303
+ continue
304
+ key, value = part.split('=', 1)
305
+ key = key.strip().lower()
306
+ value = value.strip()
307
+ if key == 'width':
308
+ width = value
309
+ elif key == 'height':
310
+ height = value
311
+ elif key == 'title':
312
+ title = value
313
+ elif key == 'allow':
314
+ allow = value
315
+ elif key == 'fullscreen':
316
+ allowfullscreen = value.lower() in ('1', 'true', 'yes', 'on')
317
+ elif key == 'caption':
318
+ caption = value
319
+ elif key == 'popup':
320
+ popup = value.lower() in ('1', 'true', 'yes', 'on')
321
+
322
+ # Break out of normal content flow for viewport widths
323
+ break_out = 'vw' in str(width).lower()
324
+ if break_out:
325
+ container_style = f"width: {width}; position: relative; left: 50%; transform: translateX(-50%);"
326
+ else:
327
+ container_style = f"width: {width};"
328
+
329
+ self.iframe_counter += 1
330
+ iframe_id = f"iframe-{abs(hash(src)) & 0xFFFFFF}-{self.iframe_counter}"
331
+
332
+ fullscreen_button = ''
333
+ if popup:
334
+ fullscreen_button = (
335
+ '<div class="iframe-controls absolute top-2 right-2 z-10 flex gap-1 '
336
+ 'bg-white/80 dark:bg-slate-800/80 backdrop-blur-sm rounded">'
337
+ f'<button data-iframe-fullscreen-toggle="true" '
338
+ f'data-iframe-src="{src}" '
339
+ f'data-iframe-title="{title}" '
340
+ f'data-iframe-allow="{allow}" '
341
+ f'data-iframe-allowfullscreen="{str(allowfullscreen).lower()}" '
342
+ 'class="px-2 py-1 text-xs border rounded hover:bg-slate-100 '
343
+ 'dark:hover:bg-slate-700" title="Fullscreen">⛶</button>'
344
+ '</div>'
345
+ )
346
+
347
+ iframe = f'''
348
+ <div class="relative my-6 rounded-lg overflow-hidden border border-slate-200 dark:border-slate-800" style="{container_style}">
349
+ {fullscreen_button}
350
+ <iframe
351
+ id="{iframe_id}"
352
+ src="{src}"
353
+ title="{title}"
354
+ frameborder="0"
355
+ allow="{allow}"
356
+ {'allowfullscreen' if allowfullscreen else ''}
357
+ style="width: 100%; height: {height};">
358
+ </iframe>
359
+ </div>
360
+ '''
361
+
362
+ if caption:
363
+ return iframe + f'<p class="text-sm text-slate-500 dark:text-slate-400 text-center mt-2">{caption}</p>'
364
+ return iframe
276
365
 
277
366
  def render_footnote_ref(self, token):
278
367
  self.fn_counter += 1
@@ -613,7 +702,7 @@ def from_md(content, img_dir=None, current_path=None):
613
702
  'table': 'uk-table uk-table-striped uk-table-hover uk-table-divider uk-table-middle my-6'}
614
703
 
615
704
  # Register custom tokens with renderer context manager
616
- with ContentRenderer(YoutubeEmbed, InlineCodeAttr, Strikethrough, FootnoteRef, Superscript, Subscript, img_dir=img_dir, footnotes=footnotes, current_path=current_path) as renderer:
705
+ with ContentRenderer(YoutubeEmbed, IframeEmbed, InlineCodeAttr, Strikethrough, FootnoteRef, Superscript, Subscript, img_dir=img_dir, footnotes=footnotes, current_path=current_path) as renderer:
617
706
  doc = mst.Document(content)
618
707
  html = renderer.render(doc)
619
708
 
@@ -818,6 +907,41 @@ hdrs = (
818
907
  height: calc(100vh - 6rem) !important;
819
908
  }
820
909
 
910
+ /* Iframe fullscreen overlay */
911
+ body.iframe-fullscreen-open {
912
+ overflow: hidden;
913
+ }
914
+ .iframe-fullscreen-overlay {
915
+ position: fixed;
916
+ inset: 0;
917
+ z-index: 1000;
918
+ background: rgba(2, 6, 23, 0.85);
919
+ display: flex;
920
+ flex-direction: column;
921
+ }
922
+ .iframe-fullscreen-header {
923
+ display: flex;
924
+ align-items: center;
925
+ justify-content: space-between;
926
+ gap: 1rem;
927
+ padding: 0.75rem 1rem;
928
+ color: #e2e8f0;
929
+ font-size: 0.9rem;
930
+ background: rgba(15, 23, 42, 0.9);
931
+ border-bottom: 1px solid rgba(148, 163, 184, 0.3);
932
+ }
933
+ .iframe-fullscreen-body {
934
+ flex: 1;
935
+ padding: 0.75rem;
936
+ }
937
+ .iframe-fullscreen-frame {
938
+ width: 100%;
939
+ height: 100%;
940
+ border: 0;
941
+ border-radius: 0.5rem;
942
+ background: #0f172a;
943
+ }
944
+
821
945
  .layout-fluid {
822
946
  --layout-breakpoint: 1280px;
823
947
  --layout-blend: 240px;
vyasa/static/scripts.js CHANGED
@@ -1174,6 +1174,86 @@ function initPdfFocusToggle() {
1174
1174
  });
1175
1175
  }
1176
1176
 
1177
+ function openIframeFullscreen(button) {
1178
+ const src = button.getAttribute('data-iframe-src');
1179
+ const title = button.getAttribute('data-iframe-title') || 'Embedded content';
1180
+ const allow = button.getAttribute('data-iframe-allow') || '';
1181
+ const allowfullscreen = button.getAttribute('data-iframe-allowfullscreen') === 'true';
1182
+
1183
+ let overlay = document.querySelector('.iframe-fullscreen-overlay');
1184
+ if (!overlay) {
1185
+ overlay = document.createElement('div');
1186
+ overlay.className = 'iframe-fullscreen-overlay';
1187
+ overlay.innerHTML = `
1188
+ <div class="iframe-fullscreen-header">
1189
+ <div class="iframe-fullscreen-title"></div>
1190
+ <button type="button" class="iframe-fullscreen-close px-2 py-1 text-xs border rounded hover:bg-slate-700">
1191
+ Close
1192
+ </button>
1193
+ </div>
1194
+ <div class="iframe-fullscreen-body">
1195
+ <iframe class="iframe-fullscreen-frame" frameborder="0"></iframe>
1196
+ </div>
1197
+ `;
1198
+ document.body.appendChild(overlay);
1199
+
1200
+ overlay.addEventListener('click', (event) => {
1201
+ if (event.target.classList.contains('iframe-fullscreen-overlay')) {
1202
+ closeIframeFullscreen();
1203
+ }
1204
+ });
1205
+
1206
+ overlay.querySelector('.iframe-fullscreen-close').addEventListener('click', () => {
1207
+ closeIframeFullscreen();
1208
+ });
1209
+ }
1210
+
1211
+ overlay.querySelector('.iframe-fullscreen-title').textContent = title;
1212
+ const frame = overlay.querySelector('.iframe-fullscreen-frame');
1213
+ frame.setAttribute('src', src);
1214
+ frame.setAttribute('title', title);
1215
+ frame.setAttribute('allow', allow);
1216
+ if (allowfullscreen) {
1217
+ frame.setAttribute('allowfullscreen', '');
1218
+ } else {
1219
+ frame.removeAttribute('allowfullscreen');
1220
+ }
1221
+
1222
+ document.body.classList.add('iframe-fullscreen-open');
1223
+ overlay.style.display = 'flex';
1224
+ }
1225
+
1226
+ function closeIframeFullscreen() {
1227
+ const overlay = document.querySelector('.iframe-fullscreen-overlay');
1228
+ if (!overlay) {
1229
+ return;
1230
+ }
1231
+ const frame = overlay.querySelector('.iframe-fullscreen-frame');
1232
+ if (frame) {
1233
+ frame.setAttribute('src', 'about:blank');
1234
+ }
1235
+ overlay.style.display = 'none';
1236
+ document.body.classList.remove('iframe-fullscreen-open');
1237
+ }
1238
+
1239
+ function initIframeFullscreenToggle() {
1240
+ document.addEventListener('click', (event) => {
1241
+ const button = event.target.closest('[data-iframe-fullscreen-toggle]');
1242
+ if (!button) {
1243
+ return;
1244
+ }
1245
+ event.preventDefault();
1246
+ openIframeFullscreen(button);
1247
+ });
1248
+
1249
+ document.addEventListener('keydown', (event) => {
1250
+ if (event.key !== 'Escape') {
1251
+ return;
1252
+ }
1253
+ closeIframeFullscreen();
1254
+ });
1255
+ }
1256
+
1177
1257
  // Initialize on page load
1178
1258
  document.addEventListener('DOMContentLoaded', () => {
1179
1259
  updateActivePostLink();
@@ -1183,6 +1263,7 @@ document.addEventListener('DOMContentLoaded', () => {
1183
1263
  initFolderChevronState();
1184
1264
  initKeyboardShortcuts();
1185
1265
  initPdfFocusToggle();
1266
+ initIframeFullscreenToggle();
1186
1267
  initSearchPlaceholderCycle(document);
1187
1268
  initPostsSearchPersistence(document);
1188
1269
  initCodeBlockCopyButtons(document);
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vyasa
3
- Version: 0.3.6
3
+ Version: 0.3.8
4
4
  Summary: A lightweight, elegant blogging platform built with FastHTML
5
5
  Home-page: https://github.com/yeshwanth/vyasa
6
6
  Author: Yeshwanth
@@ -1,16 +1,16 @@
1
- vyasa/__init__.py,sha256=l6KpjZx1i1_wMJSbuQIqteDjUJ_h9MM1W8xpm-5ISPA,158
1
+ vyasa/__init__.py,sha256=89B_FfWXTlbCEDljhZnJIJO4yim9LM6xT01hwd2pWuA,158
2
2
  vyasa/agent.py,sha256=XD4V6rM-JrVtqTVa3pZqPy_BUmcQgKdidQaY4il632Q,3196
3
3
  vyasa/build.py,sha256=USqeZsXGPB8COzdWRPLgSgkVg6DIzQwL0qGGJaMM7Ec,26548
4
4
  vyasa/config.py,sha256=7mjBTnv7MlBF4CUHA-2IZDt9l9_EMjgOLmQ00I2LQZA,7768
5
- vyasa/core.py,sha256=3p_HMM5wfpHLCbtWEDW-gdluZCVj3tFV3N6DBXO4PGQ,126211
5
+ vyasa/core.py,sha256=PwxX-8DGYLEwXGulMQpwO-y_GSU9L7MDsQaHuXQ_LfU,130627
6
6
  vyasa/helpers.py,sha256=VgOzAIoL3X8RYLvKQRd-S4jM-sjmlolyxFlyOiP7bmM,12489
7
7
  vyasa/layout_helpers.py,sha256=uodzhyrGoqC6egbGuzuAPJP2yiWYM8krxb64dE4nwpc,1082
8
8
  vyasa/main.py,sha256=xcneOe3Vw3vxfxSii2Y7C5DJTFLz71JDCY_CDZKVwxc,4073
9
- vyasa/static/scripts.js,sha256=SBFM8klicaI77-GsozUVxdMJwTuaJWoVrV6FsY4Kuws,45124
9
+ vyasa/static/scripts.js,sha256=ofLQZWt4QHkuR6RyNvkDxQaEoMBop5vqYLaUBn1TDlE,47954
10
10
  vyasa/static/sidenote.css,sha256=9YEl111Qs6kGnrgcVQtC1eiccMGY3-lu05l_NnwXVME,1045
11
- vyasa-0.3.6.dist-info/licenses/LICENSE,sha256=xV8xoN4VOL0uw9X8RSs2IMuD_Ss_a9yAbtGNeBWZwnw,11337
12
- vyasa-0.3.6.dist-info/METADATA,sha256=sMt4Yt11hsTD6w1i47BKCo4bhGQsYPoEjI65aWEUMFw,9791
13
- vyasa-0.3.6.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
14
- vyasa-0.3.6.dist-info/entry_points.txt,sha256=ypyjtxRlygocldmIxOIijoME2BIfxzU0uuNfdi3_1us,41
15
- vyasa-0.3.6.dist-info/top_level.txt,sha256=Pwy2VqkeYm2zJ5zQ-6XsJHNPsAU4xwscZn-8siQz9Pg,6
16
- vyasa-0.3.6.dist-info/RECORD,,
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