Skip to content

Commit 8b16903

Browse files
Copilotmarcarl
andauthored
Extract inline JavaScript to external file with defer loading and configurable navbar URL (#16)
* Initial plan * Extract inline JavaScript to external selex-init.js file with defer loading Co-authored-by: marcarl <536209+marcarl@users.noreply.github.com> * Move navbar script URL to top of selex-init.js for easy configuration Co-authored-by: marcarl <536209+marcarl@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: marcarl <536209+marcarl@users.noreply.github.com>
1 parent bcc374d commit 8b16903

3 files changed

Lines changed: 182 additions & 46 deletions

File tree

exporters/html/html_export.py

Lines changed: 51 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,39 @@ def generate_css_file(css_dir: Path) -> None:
144144
print(f"Generated CSS file: {css_file_path}")
145145

146146

147+
def generate_js_file(js_dir: Path) -> None:
148+
"""Generate the shared JavaScript file for HTML documents.
149+
150+
Creates a selex-init.js file in the specified directory by copying
151+
the source JavaScript file from the html exporter directory.
152+
153+
Args:
154+
js_dir: Directory where the JS file should be created
155+
"""
156+
import shutil
157+
158+
js_file_path = js_dir / "selex-init.js"
159+
160+
# Only generate if it doesn't exist to avoid regenerating on every document
161+
if js_file_path.exists():
162+
return
163+
164+
# Get the source JS file path (same directory as this module)
165+
source_js_path = Path(__file__).parent / "selex-init.js"
166+
167+
if not source_js_path.exists():
168+
print(f"Warning: Source JS file not found: {source_js_path}")
169+
return
170+
171+
# Copy the JS file
172+
try:
173+
shutil.copy2(source_js_path, js_file_path)
174+
print(f"Generated JS file: {js_file_path}")
175+
except Exception as e:
176+
print(f"Error copying JS file: {e}")
177+
178+
179+
147180
def convert_to_html(data: Dict[str, Any], apply_amendments: bool = False, up_to_amendment: int = None) -> str:
148181
"""Convert JSON data to HTML format with ELI structure.
149182
@@ -221,7 +254,7 @@ def convert_to_html(data: Dict[str, Any], apply_amendments: bool = False, up_to_
221254

222255
# Create HTML document with navbar integration and ELI format
223256
html_doc = create_html_head(rubrik_original, beteckning)
224-
html_doc += "\n<body>"
257+
html_doc += f"\n<body data-beteckning=\"{html.escape(beteckning)}\">"
225258

226259
# Build metadata in two columns
227260
column1_items = []
@@ -475,36 +508,18 @@ def extract_content_from_html(html_content: str) -> str:
475508
<meta charset="UTF-8">
476509
<meta name="viewport" content="width=device-width, initial-scale=1.0">
477510
<title>{html.escape(title)} - Med ändringar</title>
478-
<script src="https://swebar.netlify.app/navbar.js"></script>
511+
<script>
512+
window.NAVBAR_CONFIG = {{
513+
logoUrl: "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 70'><text x='0' y='60' font-family='Inter, sans-serif' font-size='70' fill='%23f1c40f'>SE-Lex</text></svg>",
514+
drawerEnabled: false,
515+
}};
516+
</script>
517+
<script src="../../selex-init.js" defer></script>
479518
<style>{get_common_styles()}
480519
{get_amendment_styles()}
481520
</style>
482-
<script>
483-
function showTab(tabName) {{
484-
// Hide all tab contents
485-
const contents = document.querySelectorAll('.tab-content');
486-
contents.forEach(content => content.classList.remove('active'));
487-
488-
// Remove active class from all buttons
489-
const buttons = document.querySelectorAll('.tab-button');
490-
buttons.forEach(button => button.classList.remove('active'));
491-
492-
// Show selected tab content
493-
document.getElementById(tabName).classList.add('active');
494-
495-
// Mark button as active
496-
document.querySelector(`[onclick="showTab('${{tabName}}')"]`).classList.add('active');
497-
}}
498-
499-
document.addEventListener('DOMContentLoaded', function() {{
500-
if (typeof SwedacNavbar !== 'undefined') {{
501-
SwedacNavbar.setHeader("SFS");
502-
SwedacNavbar.setHeaderChild("{html.escape(beteckning)}");
503-
}}
504-
}});
505-
</script>
506521
</head>
507-
<body>
522+
<body data-beteckning="{html.escape(beteckning)}">
508523
{metadata_section}
509524
510525
<h1>{html.escape(title)}</h1>
@@ -518,8 +533,8 @@ def extract_content_from_html(html_content: str) -> str:
518533
519534
<div class="tab-container">
520535
<div class="tab-buttons">
521-
<button class="tab-button active" onclick="showTab('diff')">Visa ändringar</button>
522-
<button class="tab-button" onclick="showTab('final')">Slutlig version</button>
536+
<button class="tab-button active" data-tab="diff">Visa ändringar</button>
537+
<button class="tab-button" data-tab="final">Slutlig version</button>
523538
</div>
524539
525540
<div id="diff" class="tab-content active">
@@ -554,7 +569,7 @@ def extract_content_from_html(html_content: str) -> str:
554569
return combined_html
555570

556571

557-
def create_html_head(title: str, beteckning: str, additional_styles: str = "", additional_scripts: str = "", use_external_css: bool = True, css_relative_path: str = "../../styles.css") -> str:
572+
def create_html_head(title: str, beteckning: str, additional_styles: str = "", additional_scripts: str = "", use_external_css: bool = True, css_relative_path: str = "../../styles.css", js_relative_path: str = "../../selex-init.js") -> str:
558573
"""Create HTML head section with navbar integration.
559574
560575
Args:
@@ -564,6 +579,7 @@ def create_html_head(title: str, beteckning: str, additional_styles: str = "", a
564579
additional_scripts: Additional JavaScript to include
565580
use_external_css: Whether to use external CSS file (default: True)
566581
css_relative_path: Relative path to CSS file from HTML document (default: "../../styles.css")
582+
js_relative_path: Relative path to JS file from HTML document (default: "../../selex-init.js")
567583
568584
Returns:
569585
str: Complete HTML head section
@@ -595,25 +611,19 @@ def create_html_head(title: str, beteckning: str, additional_styles: str = "", a
595611
base_styles += """
596612
</style>"""
597613

598-
# Navbar initialization script
614+
# Navbar configuration and external scripts
599615
navbar_script = f"""
600616
<script>
601617
window.NAVBAR_CONFIG = {{
602618
logoUrl: "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 70'><text x='0' y='60' font-family='Inter, sans-serif' font-size='70' fill='%23f1c40f'>SE-Lex</text></svg>",
603619
drawerEnabled: false,
604620
}};
605621
</script>
606-
<script src=\"https://swebar.netlify.app/navbar.js\"></script>
607-
<script>
608-
document.addEventListener('DOMContentLoaded', function() {{
609-
if (typeof SweNavbar !== 'undefined') {{
610-
SweNavbar.setHeader(\"SFS\");
611-
SweNavbar.setHeaderChild(\"{html.escape(beteckning)}\");
612-
}}
613-
}});"""
622+
<script src=\"{js_relative_path}\" defer></script>"""
614623
if additional_scripts:
615-
navbar_script += additional_scripts
616-
navbar_script += """
624+
navbar_script += f"""
625+
<script>
626+
{additional_scripts}
617627
</script>"""
618628

619629
# Generate ELI canonical URL and metadata

exporters/html/selex-init.js

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/**
2+
* SE-Lex HTML Page Initialization
3+
*
4+
* This file initializes and configures the navbar and other JavaScript functionality
5+
* for SE-Lex HTML pages. It is loaded with "defer" so pages work even without JavaScript.
6+
*/
7+
8+
(function() {
9+
'use strict';
10+
11+
// ============================================================================
12+
// NAVBAR SCRIPT URL - Configure the navbar library location here
13+
// ============================================================================
14+
const NAVBAR_SCRIPT_URL = 'https://swebar.netlify.app/navbar.js';
15+
16+
/**
17+
* Dynamically load the navbar script
18+
*/
19+
function loadNavbarScript() {
20+
return new Promise((resolve, reject) => {
21+
// Check if navbar is already loaded
22+
if (typeof SweNavbar !== 'undefined') {
23+
resolve();
24+
return;
25+
}
26+
27+
// Create script element
28+
const script = document.createElement('script');
29+
script.src = NAVBAR_SCRIPT_URL;
30+
script.onload = resolve;
31+
script.onerror = reject;
32+
33+
// Insert as first script in head
34+
document.head.insertBefore(script, document.head.firstChild);
35+
});
36+
}
37+
38+
/**
39+
* Initialize the navbar with SE-Lex configuration
40+
*/
41+
function initNavbar() {
42+
// Load navbar script first
43+
loadNavbarScript().then(() => {
44+
// Check if navbar library is loaded
45+
if (typeof SweNavbar === 'undefined') {
46+
return;
47+
}
48+
49+
// Get beteckning from data attribute if available
50+
const bodyElement = document.body;
51+
const beteckning = bodyElement.getAttribute('data-beteckning');
52+
53+
// Configure navbar
54+
if (beteckning) {
55+
SweNavbar.setHeader("SFS");
56+
SweNavbar.setHeaderChild(beteckning);
57+
} else {
58+
SweNavbar.setHeader("SFS");
59+
}
60+
}).catch((error) => {
61+
console.warn('Failed to load navbar script:', error);
62+
});
63+
}
64+
65+
/**
66+
* Tab switching functionality for amendment pages
67+
*/
68+
function showTab(tabName) {
69+
// Hide all tab contents
70+
const contents = document.querySelectorAll('.tab-content');
71+
contents.forEach(content => content.classList.remove('active'));
72+
73+
// Remove active class from all buttons
74+
const buttons = document.querySelectorAll('.tab-button');
75+
buttons.forEach(button => button.classList.remove('active'));
76+
77+
// Show selected tab content
78+
const selectedTab = document.getElementById(tabName);
79+
if (selectedTab) {
80+
selectedTab.classList.add('active');
81+
}
82+
83+
// Mark button as active
84+
const activeButton = document.querySelector(`[data-tab="${tabName}"]`);
85+
if (activeButton) {
86+
activeButton.classList.add('active');
87+
}
88+
}
89+
90+
/**
91+
* Initialize tab functionality if tabs are present
92+
*/
93+
function initTabs() {
94+
const tabButtons = document.querySelectorAll('.tab-button[data-tab]');
95+
96+
tabButtons.forEach(button => {
97+
button.addEventListener('click', function() {
98+
const tabName = this.getAttribute('data-tab');
99+
showTab(tabName);
100+
});
101+
});
102+
}
103+
104+
/**
105+
* Main initialization function
106+
*/
107+
function init() {
108+
// Initialize navbar
109+
initNavbar();
110+
111+
// Initialize tabs if present
112+
initTabs();
113+
}
114+
115+
// Run initialization when DOM is ready
116+
if (document.readyState === 'loading') {
117+
document.addEventListener('DOMContentLoaded', init);
118+
} else {
119+
// DOM is already ready
120+
init();
121+
}
122+
123+
// Expose showTab function globally for backwards compatibility
124+
window.showTab = showTab;
125+
})();

sfs_processor.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -636,12 +636,13 @@ def main():
636636
print(f"Hittade {len(json_files)} JSON-fil(er) att konvertera från {json_dir}")
637637
print(f"Utdata kommer att sparas i {output_dir}")
638638

639-
# Generate CSS file once for HTML/HTMLDIFF formats
639+
# Generate CSS and JS files once for HTML/HTMLDIFF formats
640640
if "html" in output_modes or "htmldiff" in output_modes:
641-
from exporters.html.html_export import generate_css_file
642-
css_dir = output_dir / "eli" / "sfs"
643-
css_dir.mkdir(parents=True, exist_ok=True)
644-
generate_css_file(css_dir)
641+
from exporters.html.html_export import generate_css_file, generate_js_file
642+
css_js_dir = output_dir / "eli" / "sfs"
643+
css_js_dir.mkdir(parents=True, exist_ok=True)
644+
generate_css_file(css_js_dir)
645+
generate_js_file(css_js_dir)
645646

646647
# Handle git mode with batch processing
647648
if "git" in output_modes:

0 commit comments

Comments
 (0)