syntaxmatrix 2.5.5.4__tar.gz → 2.5.6__tar.gz
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.
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/PKG-INFO +1 -1
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/SyntaxMatrix.egg-info/PKG-INFO +1 -1
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/SyntaxMatrix.egg-info/SOURCES.txt +2 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/setup.py +1 -1
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/__init__.py +3 -2
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/auth.py +142 -5
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/core.py +9 -5
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/generate_page.py +17 -7
- syntaxmatrix-2.5.6/syntaxmatrix/preface.py +549 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/routes.py +238 -177
- syntaxmatrix-2.5.6/syntaxmatrix/templates/change_password.html +124 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/templates/dashboard.html +7 -5
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/utils.py +301 -456
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/LICENSE.txt +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/README.md +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/SyntaxMatrix.egg-info/dependency_links.txt +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/SyntaxMatrix.egg-info/requires.txt +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/SyntaxMatrix.egg-info/top_level.txt +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/pyproject.toml +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/setup.cfg +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/agentic/__init__.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/agentic/agent_tools.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/agentic/agents.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/agentic/code_tools_registry.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/agentic/model_templates.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/bootstrap.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/commentary.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/dataset_preprocessing.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/db.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/display.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/emailer.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/file_processor.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/gpt_models_latest.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/history_store.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/kernel_manager.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/llm_store.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/models.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/plottings.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/profiles.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/project_root.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/session.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/settings/__init__.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/settings/default.yaml +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/settings/logging.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/settings/model_map.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/settings/prompts.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/settings/string_navbar.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/smiv.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/smpv.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/static/css/style.css +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/static/docs.md +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/static/icons/favicon.png +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/static/icons/hero_bg.jpg +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/static/icons/logo.png +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/static/icons/svg_497526.svg +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/static/icons/svg_497528.svg +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/static/js/chat.js +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/static/js/sidebar.js +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/static/js/widgets.js +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/templates/code_cell.html +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/templates/docs.html +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/templates/error.html +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/templates/login.html +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/templates/register.html +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/themes.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/ui_modes.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/vector_db.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/vectordb/__init__.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/vectordb/adapters/__init__.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/vectordb/adapters/milvus_adapter.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/vectordb/adapters/pgvector_adapter.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/vectordb/adapters/sqlite_adapter.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/vectordb/base.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/vectordb/registry.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/vectorizer.py +0 -0
- {syntaxmatrix-2.5.5.4 → syntaxmatrix-2.5.6}/syntaxmatrix/workspace_db.py +0 -0
|
@@ -24,6 +24,7 @@ syntaxmatrix/kernel_manager.py
|
|
|
24
24
|
syntaxmatrix/llm_store.py
|
|
25
25
|
syntaxmatrix/models.py
|
|
26
26
|
syntaxmatrix/plottings.py
|
|
27
|
+
syntaxmatrix/preface.py
|
|
27
28
|
syntaxmatrix/profiles.py
|
|
28
29
|
syntaxmatrix/project_root.py
|
|
29
30
|
syntaxmatrix/routes.py
|
|
@@ -62,6 +63,7 @@ syntaxmatrix/static/icons/svg_497528.svg
|
|
|
62
63
|
syntaxmatrix/static/js/chat.js
|
|
63
64
|
syntaxmatrix/static/js/sidebar.js
|
|
64
65
|
syntaxmatrix/static/js/widgets.js
|
|
66
|
+
syntaxmatrix/templates/change_password.html
|
|
65
67
|
syntaxmatrix/templates/code_cell.html
|
|
66
68
|
syntaxmatrix/templates/dashboard.html
|
|
67
69
|
syntaxmatrix/templates/docs.html
|
|
@@ -8,7 +8,7 @@ with open(os.path.join(this_directory, "README.md"), encoding="utf-8") as f:
|
|
|
8
8
|
|
|
9
9
|
setup(
|
|
10
10
|
name="syntaxmatrix",
|
|
11
|
-
version="2.5.
|
|
11
|
+
version="2.5.6",
|
|
12
12
|
author="Bob Nti",
|
|
13
13
|
author_email="bob.nti@syntaxmatrix.net",
|
|
14
14
|
description="SyntaxMUI: A customizable framework for Python AI Assistant Projects.",
|
|
@@ -44,8 +44,8 @@ get_widget_value = _app_instance.get_widget_value
|
|
|
44
44
|
save_embed_model = _app_instance.save_embed_model
|
|
45
45
|
load_embed_model = _app_instance.load_embed_model
|
|
46
46
|
delete_embed_key = _app_instance.delete_embed_key
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
set_smxai_identity = _app_instance.set_smxai_identity
|
|
48
|
+
set_smxai_instructions = _app_instance.set_smxai_instructions
|
|
49
49
|
set_website_description = _app_instance.set_website_description
|
|
50
50
|
smiv_index = _app_instance.smiv_index
|
|
51
51
|
smpv_search = _app_instance.smpv_search
|
|
@@ -54,6 +54,7 @@ process_query_stream = _app_instance.process_query_stream
|
|
|
54
54
|
process_query = _app_instance.process_query
|
|
55
55
|
embed_query = _app_instance.embed_query
|
|
56
56
|
enable_user_files = _app_instance.enable_user_files
|
|
57
|
+
enable_registration = _app_instance.enable_registration
|
|
57
58
|
stream_write = _app_instance.stream_write
|
|
58
59
|
enable_stream = _app_instance.enable_stream
|
|
59
60
|
stream = _app_instance.stream
|
|
@@ -41,6 +41,19 @@ def init_auth_db():
|
|
|
41
41
|
);
|
|
42
42
|
""")
|
|
43
43
|
|
|
44
|
+
# Ensure new must_reset_password flag exists for mandatory first-login reset
|
|
45
|
+
try:
|
|
46
|
+
cur = conn.execute("PRAGMA table_info(users)")
|
|
47
|
+
cols = [row[1] for row in cur.fetchall()]
|
|
48
|
+
if "must_reset_password" not in cols:
|
|
49
|
+
conn.execute(
|
|
50
|
+
"ALTER TABLE users "
|
|
51
|
+
"ADD COLUMN must_reset_password INTEGER NOT NULL DEFAULT 0"
|
|
52
|
+
)
|
|
53
|
+
except Exception:
|
|
54
|
+
# Best-effort migration; if this fails we still let the app start
|
|
55
|
+
pass
|
|
56
|
+
|
|
44
57
|
# --- Roles table ---
|
|
45
58
|
conn.execute("""
|
|
46
59
|
CREATE TABLE IF NOT EXISTS roles (
|
|
@@ -302,6 +315,60 @@ def register_user(email:str, username:str, password:str, role:str = "user") -> b
|
|
|
302
315
|
finally:
|
|
303
316
|
conn.close()
|
|
304
317
|
|
|
318
|
+
def set_must_reset_by_email(email: str, must_reset: bool = True) -> None:
|
|
319
|
+
"""
|
|
320
|
+
Mark a user account as requiring a password reset (or clear the flag) by email.
|
|
321
|
+
Used when an admin creates a user with a temporary password.
|
|
322
|
+
"""
|
|
323
|
+
if not email:
|
|
324
|
+
return
|
|
325
|
+
conn = _get_conn()
|
|
326
|
+
try:
|
|
327
|
+
conn.execute(
|
|
328
|
+
"UPDATE users SET must_reset_password = ? WHERE email = ?",
|
|
329
|
+
(1 if must_reset else 0, email),
|
|
330
|
+
)
|
|
331
|
+
conn.commit()
|
|
332
|
+
finally:
|
|
333
|
+
conn.close()
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def user_must_reset_password(user_id: int) -> bool:
|
|
337
|
+
"""
|
|
338
|
+
Check whether this user is currently forced to change their password.
|
|
339
|
+
"""
|
|
340
|
+
if not user_id:
|
|
341
|
+
return False
|
|
342
|
+
conn = _get_conn()
|
|
343
|
+
try:
|
|
344
|
+
cur = conn.execute(
|
|
345
|
+
"SELECT must_reset_password FROM users WHERE id = ?",
|
|
346
|
+
(user_id,),
|
|
347
|
+
)
|
|
348
|
+
row = cur.fetchone()
|
|
349
|
+
finally:
|
|
350
|
+
conn.close()
|
|
351
|
+
if not row:
|
|
352
|
+
return False
|
|
353
|
+
return bool(row[0])
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def clear_must_reset(user_id: int) -> None:
|
|
357
|
+
"""
|
|
358
|
+
Clear the mandatory-reset flag (called after the user has changed their password).
|
|
359
|
+
"""
|
|
360
|
+
if not user_id:
|
|
361
|
+
return
|
|
362
|
+
conn = _get_conn()
|
|
363
|
+
try:
|
|
364
|
+
conn.execute(
|
|
365
|
+
"UPDATE users SET must_reset_password = 0 WHERE id = ?",
|
|
366
|
+
(user_id,),
|
|
367
|
+
)
|
|
368
|
+
conn.commit()
|
|
369
|
+
finally:
|
|
370
|
+
conn.close()
|
|
371
|
+
|
|
305
372
|
def authenticate(email:str, password:str) -> Optional[Dict]:
|
|
306
373
|
"""Return user dict if creds match, else None."""
|
|
307
374
|
conn = _get_conn()
|
|
@@ -315,15 +382,85 @@ def authenticate(email:str, password:str) -> Optional[Dict]:
|
|
|
315
382
|
return {"id": row[0], "email":row[1], "username": row[2], "role": row[4]}
|
|
316
383
|
return None
|
|
317
384
|
|
|
385
|
+
def verify_password(user_id: int, candidate: str) -> bool:
|
|
386
|
+
"""
|
|
387
|
+
Check whether `candidate` matches the current password of the user.
|
|
388
|
+
Used by the change-password flow.
|
|
389
|
+
"""
|
|
390
|
+
if not user_id or not candidate:
|
|
391
|
+
return False
|
|
392
|
+
|
|
393
|
+
conn = _get_conn()
|
|
394
|
+
try:
|
|
395
|
+
cur = conn.execute(
|
|
396
|
+
"SELECT password FROM users WHERE id = ?",
|
|
397
|
+
(user_id,),
|
|
398
|
+
)
|
|
399
|
+
row = cur.fetchone()
|
|
400
|
+
finally:
|
|
401
|
+
conn.close()
|
|
402
|
+
|
|
403
|
+
if not row:
|
|
404
|
+
return False
|
|
405
|
+
return check_password_hash(row[0], candidate)
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def update_password(user_id: int, new_password: str) -> None:
|
|
409
|
+
"""
|
|
410
|
+
Overwrite the user's password with a new hash.
|
|
411
|
+
"""
|
|
412
|
+
if not user_id or not new_password:
|
|
413
|
+
return
|
|
414
|
+
|
|
415
|
+
hashed = generate_password_hash(new_password)
|
|
416
|
+
conn = _get_conn()
|
|
417
|
+
try:
|
|
418
|
+
conn.execute(
|
|
419
|
+
"UPDATE users SET password = ? WHERE id = ?",
|
|
420
|
+
(hashed, user_id),
|
|
421
|
+
)
|
|
422
|
+
conn.commit()
|
|
423
|
+
finally:
|
|
424
|
+
conn.close()
|
|
425
|
+
|
|
426
|
+
def update_password(user_id: int, new_password: str) -> bool:
|
|
427
|
+
"""
|
|
428
|
+
Update the stored password hash for a given user id.
|
|
429
|
+
Returns True on success, False if something goes wrong.
|
|
430
|
+
"""
|
|
431
|
+
hashed = generate_password_hash(new_password)
|
|
432
|
+
conn = _get_conn()
|
|
433
|
+
try:
|
|
434
|
+
conn.execute(
|
|
435
|
+
"UPDATE users SET password = ? WHERE id = ?",
|
|
436
|
+
(hashed, user_id),
|
|
437
|
+
)
|
|
438
|
+
conn.commit()
|
|
439
|
+
return True
|
|
440
|
+
except Exception:
|
|
441
|
+
# We do not raise inside auth; caller shows a friendly message instead.
|
|
442
|
+
return False
|
|
443
|
+
finally:
|
|
444
|
+
conn.close()
|
|
445
|
+
|
|
318
446
|
def login_required(f):
|
|
319
447
|
@wraps(f)
|
|
320
|
-
def
|
|
321
|
-
if not session
|
|
322
|
-
flash("Please log in
|
|
448
|
+
def wrapper(*args, **kwargs):
|
|
449
|
+
if "user_id" not in session:
|
|
450
|
+
flash("Please log in.")
|
|
323
451
|
return redirect(url_for("login", next=request.path))
|
|
324
|
-
return f(*args, **kwargs)
|
|
325
|
-
return decorated
|
|
326
452
|
|
|
453
|
+
# If the account is flagged for a mandatory reset, force the user
|
|
454
|
+
# to the change-password screen before allowing anything else.
|
|
455
|
+
if session.get("must_reset_password") and request.endpoint not in (
|
|
456
|
+
"change_password",
|
|
457
|
+
"logout",
|
|
458
|
+
):
|
|
459
|
+
flash("Please set a new password before continuing.")
|
|
460
|
+
return redirect(url_for("change_password"))
|
|
461
|
+
|
|
462
|
+
return f(*args, **kwargs)
|
|
463
|
+
return wrapper
|
|
327
464
|
|
|
328
465
|
def admin_required(view):
|
|
329
466
|
@wraps(view)
|
|
@@ -54,7 +54,7 @@ class SyntaxMUI:
|
|
|
54
54
|
port="5080",
|
|
55
55
|
user_icon="👩🏿🦲",
|
|
56
56
|
bot_icon="<img src='/static/icons/favicon.png' width=20' alt='bot'/>",
|
|
57
|
-
favicon="/static/icons/favicon.png",
|
|
57
|
+
favicon="", # /static/icons/favicon.png",
|
|
58
58
|
site_logo="<img src='/static/icons/logo.png' width='30' alt='logo'/>",
|
|
59
59
|
site_title="SyntaxMatrix",
|
|
60
60
|
project_name="smxAI",
|
|
@@ -75,6 +75,7 @@ class SyntaxMUI:
|
|
|
75
75
|
self.ui_mode = ui_mode
|
|
76
76
|
self.theme_toggle_enabled = False
|
|
77
77
|
self.user_files_enabled = False
|
|
78
|
+
self.registration_enabled = False
|
|
78
79
|
self.smxai_identity = SMXAI_CHAT_ID
|
|
79
80
|
self.smxai_instructions = SMXAI_CHAT_INSTRUCTIONS
|
|
80
81
|
self.website_description = SMXAI_WEBSITE_DESCRIPTION
|
|
@@ -311,6 +312,9 @@ class SyntaxMUI:
|
|
|
311
312
|
|
|
312
313
|
def enable_user_files(self):
|
|
313
314
|
self.user_files_enabled = True
|
|
315
|
+
|
|
316
|
+
def enable_registration(self):
|
|
317
|
+
self.registration_enabled = True
|
|
314
318
|
|
|
315
319
|
@staticmethod
|
|
316
320
|
def columns(components):
|
|
@@ -514,12 +518,12 @@ class SyntaxMUI:
|
|
|
514
518
|
# ──────────────────────────────────────────────────────────────
|
|
515
519
|
# *********** LLM CLIENT HELPERS **********************
|
|
516
520
|
# ──────────────────────────────────────────────────────────────
|
|
517
|
-
def
|
|
518
|
-
self.
|
|
521
|
+
def set_smxai_identity(self, profile):
|
|
522
|
+
self.set_smxai_identity = profile
|
|
519
523
|
|
|
520
524
|
|
|
521
|
-
def
|
|
522
|
-
self.
|
|
525
|
+
def set_smxai_instructions(self, instructions):
|
|
526
|
+
self.set_smxai_instructions = instructions
|
|
523
527
|
|
|
524
528
|
|
|
525
529
|
def set_website_description(self, desc):
|
|
@@ -35,15 +35,25 @@ _SMX_FADEIN_CSS = '''
|
|
|
35
35
|
</style>
|
|
36
36
|
'''.strip()
|
|
37
37
|
|
|
38
|
-
_SMX_LAYOUT_CSS =
|
|
38
|
+
_SMX_LAYOUT_CSS = """
|
|
39
39
|
<style>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
#smx-root,
|
|
41
|
+
[id^="smx-"]{
|
|
42
|
+
margin-top: 0;
|
|
43
|
+
margin-bottom: 40px;
|
|
44
|
+
/* No side padding on desktop */
|
|
45
|
+
padding-inline: 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* Keep a bit of breathing room on small screens */
|
|
49
|
+
@media (max-width: 768px) {
|
|
50
|
+
#smx-root,
|
|
51
|
+
[id^="smx-"]{
|
|
52
|
+
padding-inline: 12px;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
45
55
|
</style>
|
|
46
|
-
|
|
56
|
+
""".strip()
|
|
47
57
|
|
|
48
58
|
def smx_strip_fences(html: str) -> str:
|
|
49
59
|
s = (html or '').strip()
|