mirror of
https://github.com/dwmkerr/hacker-laws.git
synced 2025-12-17 12:45:20 +01:00
wip: looking better, almost ready for final cleanup
This commit is contained in:
37
.github/website/generate.py
vendored
37
.github/website/generate.py
vendored
@@ -1,8 +1,9 @@
|
|||||||
|
"""Generate the Hacker Laws website from the Hacker Laws README"""
|
||||||
import argparse
|
import argparse
|
||||||
import markdown
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
from jinja2 import Environment, FileSystemLoader
|
from jinja2 import Environment, FileSystemLoader
|
||||||
|
import markdown
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
|
||||||
@@ -43,19 +44,31 @@ def prepare_markdown(path: str) -> str:
|
|||||||
|
|
||||||
def parse_markdown(markdown_content: str):
|
def parse_markdown(markdown_content: str):
|
||||||
(_, remains) = bisect_text(markdown_content, "<!-- vim-markdown-toc GFM -->")
|
(_, remains) = bisect_text(markdown_content, "<!-- vim-markdown-toc GFM -->")
|
||||||
(toc, content) = bisect_text(remains, "<!-- vim-markdown-toc -->")
|
(_, content) = bisect_text(remains, "<!-- vim-markdown-toc -->")
|
||||||
|
|
||||||
sections = content.split("\n## ") # Split by Markdown headings
|
md = markdown.Markdown(extensions=['toc'])
|
||||||
|
md.convert(content)
|
||||||
|
toc = md.toc
|
||||||
|
|
||||||
|
markdown_sections = content.split("\n#") # Split by Markdown headings
|
||||||
|
sections = []
|
||||||
laws = []
|
laws = []
|
||||||
for section in sections:
|
for markdown_section in markdown_sections:
|
||||||
if section.strip():
|
if markdown_section.strip():
|
||||||
lines = section.split("\n", 1)
|
lines = markdown_section.split("\n", 1)
|
||||||
title = lines[0].strip("# ").strip()
|
title = lines[0].strip("# ").strip()
|
||||||
content = markdown.markdown(lines[1] if len(lines) > 1 else "")
|
content = md.convert(lines[1] if len(lines) > 1 else "")
|
||||||
law_id = title.lower().replace(" ", "-")
|
full_content = md.convert(markdown_section)
|
||||||
laws.append({"title": title, "content": content, "id": law_id})
|
id = title.lower().replace(" ", "-")
|
||||||
|
laws.append({"title": title, "content": content, "id": id})
|
||||||
|
sections.append({
|
||||||
|
"title": title,
|
||||||
|
"content": content,
|
||||||
|
"id": id,
|
||||||
|
"full_content": full_content
|
||||||
|
})
|
||||||
|
|
||||||
return (markdown.markdown(toc), laws)
|
return (sections, toc)
|
||||||
|
|
||||||
|
|
||||||
def extract_static_files(html_content, output_dir):
|
def extract_static_files(html_content, output_dir):
|
||||||
@@ -103,13 +116,13 @@ def generate_site(markdown_content: str, output_dir: str):
|
|||||||
"""Generate the static HTML file from Markdown and Jinja2 template."""
|
"""Generate the static HTML file from Markdown and Jinja2 template."""
|
||||||
|
|
||||||
template = load_template()
|
template = load_template()
|
||||||
(toc, laws) = parse_markdown(markdown_content)
|
(sections, toc) = parse_markdown(markdown_content)
|
||||||
|
|
||||||
# Ensure output directory exists
|
# Ensure output directory exists
|
||||||
os.makedirs(output_dir, exist_ok=True)
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
# Render HTML
|
# Render HTML
|
||||||
html_output = template.render(toc=toc, laws=laws)
|
html_output = template.render(toc=toc, sections=sections)
|
||||||
|
|
||||||
# Save HTML to output directory
|
# Save HTML to output directory
|
||||||
output_file = os.path.join(output_dir, "index.html")
|
output_file = os.path.join(output_dir, "index.html")
|
||||||
|
|||||||
4
.github/website/makefile
vendored
4
.github/website/makefile
vendored
@@ -27,9 +27,9 @@ serve: # 🚀 start local server
|
|||||||
python3 -m http.server 8000
|
python3 -m http.server 8000
|
||||||
|
|
||||||
.PHONY: watch
|
.PHONY: watch
|
||||||
watch: # 👀 Watch for changes...
|
watch: build # 👀 Watch for changes...
|
||||||
@echo "👀 Watching for changes..."
|
@echo "👀 Watching for changes..."
|
||||||
watchmedo shell-command --patterns="$(MARKDOWN_FILE);*.py;./src/*.*" --command="make build" .
|
watchmedo shell-command --patterns="$(MARKDOWN_FILE);*.py;src/*" --command="make build" .
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean: #🧹 Clean up generated files
|
clean: #🧹 Clean up generated files
|
||||||
|
|||||||
24
.github/website/src/index.html.jinja
vendored
24
.github/website/src/index.html.jinja
vendored
@@ -44,21 +44,18 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="container">
|
<main class="container">
|
||||||
<!-- The table of contents. -->
|
|
||||||
{{ toc }}
|
{{ toc }}
|
||||||
|
|
||||||
<!-- Each of the laws. -->
|
<!-- The table of contents. -->
|
||||||
{% for law in laws %}
|
|
||||||
<section id="{{ law.id }}" class="law-section">
|
<!-- Each of the sections - most of which are laws. -->
|
||||||
<h2>
|
{% for section in sections %}
|
||||||
{{ law.title }}
|
<section id="{{ section.id }}" class="law-section">
|
||||||
<a href="#{{ law.id }}" class="anchor"><i class="bi bi-link-45deg"></i></a>
|
{{ section.full_content | safe }}
|
||||||
</h2>
|
|
||||||
{{ law.content | safe }}
|
|
||||||
<div class="social-sharing">
|
<div class="social-sharing">
|
||||||
<a href="https://twitter.com/share?url=#{{ law.id }}" title="Share on Twitter"><i class="bi bi-twitter"></i></a>
|
<a href="https://twitter.com/share?url=#{{ section.id }}" title="Share on Twitter"><i class="bi bi-twitter"></i></a>
|
||||||
<a href="https://facebook.com/share?url=#{{ law.id }}" title="Share on Facebook"><i class="bi bi-facebook"></i></a>
|
<a href="https://facebook.com/share?url=#{{ section.id }}" title="Share on Facebook"><i class="bi bi-facebook"></i></a>
|
||||||
<a href="#" onclick="navigator.clipboard.writeText(window.location.href + '#{{ law.id }}'); alert('Copied!');" title="Copy Link"><i class="bi bi-clipboard"></i></a>
|
<a href="#" onclick="navigator.clipboard.writeText(window.location.href + '#{{ section.id }}'); alert('Copied!');" title="Copy Link"><i class="bi bi-clipboard"></i></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="back-to-top"><a href="#top">↑ Back to Top</a></div>
|
<div class="back-to-top"><a href="#top">↑ Back to Top</a></div>
|
||||||
</section>
|
</section>
|
||||||
@@ -69,5 +66,8 @@
|
|||||||
<footer class="container text-center my-4">
|
<footer class="container text-center my-4">
|
||||||
<p>© 2025 Hacker Laws</p>
|
<p>© 2025 Hacker Laws</p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="script.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
18
.github/website/src/script.js
vendored
Normal file
18
.github/website/src/script.js
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
$(document).ready(function() {
|
||||||
|
$("h1, h2, h3, h4, h5, h6").each(function() {
|
||||||
|
var $heading = $(this);
|
||||||
|
var headingId = $heading.attr("id") || $heading.text().trim().toLowerCase().replace(/\s+/g, "-");
|
||||||
|
|
||||||
|
// Ensure a unique ID
|
||||||
|
$heading.attr("id", headingId);
|
||||||
|
|
||||||
|
// Create the anchor link
|
||||||
|
var $anchor = $('<a>')
|
||||||
|
.attr("href", "#" + headingId)
|
||||||
|
.addClass("header-link")
|
||||||
|
.html("#");
|
||||||
|
|
||||||
|
// Append to the heading
|
||||||
|
$heading.append($anchor);
|
||||||
|
});
|
||||||
|
});
|
||||||
145
.github/website/src/styles.css
vendored
145
.github/website/src/styles.css
vendored
@@ -1,67 +1,82 @@
|
|||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: 'Inter', sans-serif;
|
font-family: 'Inter', sans-serif;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
color: #333;
|
color: #333;
|
||||||
padding-top: 70px;
|
padding-top: 70px;
|
||||||
}
|
}
|
||||||
.container {
|
.container {
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
}
|
}
|
||||||
.navbar-custom {
|
.navbar-custom {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-bottom: 1px solid #e5e5e5;
|
border-bottom: 1px solid #e5e5e5;
|
||||||
}
|
}
|
||||||
.navbar-brand, .nav-link {
|
.navbar-brand, .nav-link {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
header {
|
header {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
padding: 2rem 0;
|
padding: 2rem 0;
|
||||||
border-bottom: 1px solid #e5e5e5;
|
border-bottom: 1px solid #e5e5e5;
|
||||||
}
|
}
|
||||||
h1, h2 {
|
h1, h2 {
|
||||||
font-family: 'Libre Baskerville', serif;
|
font-family: 'Libre Baskerville', serif;
|
||||||
}
|
}
|
||||||
.law-section {
|
.law-section {
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-bottom: 1px solid #e5e5e5;
|
border-bottom: 1px solid #e5e5e5;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
.law-section h2 {
|
.law-section h2 {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.law-section h2 a.anchor {
|
.law-section h2 a.anchor {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #999;
|
color: #999;
|
||||||
margin-left: 0.5rem;
|
margin-left: 0.5rem;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
.law-section:hover h2 a.anchor {
|
.law-section:hover h2 a.anchor {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
a {
|
a {
|
||||||
color: #0056b3;
|
color: #0056b3;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
a:hover {
|
a:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
.social-sharing a {
|
.social-sharing a {
|
||||||
margin-right: 0.75rem;
|
margin-right: 0.75rem;
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
color: #555;
|
color: #555;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
.social-sharing a:hover {
|
.social-sharing a:hover {
|
||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
.back-to-top {
|
.back-to-top {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Initially hide the hash link */
|
||||||
|
.header-link {
|
||||||
|
text-decoration: none;
|
||||||
|
margin-left: 12px; /* Increased left padding */
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
font-size: inherit; /* Matches the heading size */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Only show the hash when the whole section is hovered */
|
||||||
|
section:hover .header-link,
|
||||||
|
article:hover .header-link,
|
||||||
|
div:hover .header-link {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user