Created
September 12, 2024 14:40
-
-
Save d0rc/90330f4659ce0ad9f7badc7060ad2d92 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bytes" | |
"fmt" | |
"html/template" | |
"io/ioutil" | |
"log" | |
"os" | |
"path/filepath" | |
"strings" | |
"github.com/russross/blackfriday/v2" | |
) | |
type Section struct { | |
ID string | |
Title string | |
Content template.HTML | |
SubSections []Section | |
} | |
type PageData struct { | |
Title string | |
Sections []Section | |
} | |
const templateHTML = ` | |
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>{{.Title}}</title> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/themes/prism-okaidia.min.css"> | |
<style> | |
/* CSS styles from the previous template */ | |
body, html { | |
margin: 0; | |
padding: 0; | |
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; | |
height: 100%; | |
line-height: 1.6; | |
} | |
.container { | |
display: flex; | |
height: 100%; | |
} | |
.column { | |
height: 100%; | |
overflow-y: auto; | |
} | |
.left-column { | |
width: 230px; | |
background-color: #2E3336; | |
color: #fff; | |
position: fixed; | |
top: 0; | |
left: 0; | |
height: 100%; | |
overflow-y: auto; | |
} | |
.middle-column { | |
width: calc(100% - 230px - 40%); | |
background-color: #F3F7F9; | |
border-right: 1px solid #f0f4f7; | |
margin-left: 230px; | |
} | |
.right-column { | |
width: 40%; | |
background-color: #2E3336; | |
color: #fff; | |
position: fixed; | |
top: 0; | |
right: 0; | |
height: 100%; | |
overflow-y: auto; | |
} | |
.content-wrapper { | |
padding: 28px; | |
} | |
h1, h2, h3, h4 { | |
margin-top: 2em; | |
margin-bottom: 0.8em; | |
} | |
h1 { | |
font-size: 25px; | |
padding-top: 0.5em; | |
padding-bottom: 0.5em; | |
margin-bottom: 21px; | |
margin-top: 2em; | |
border-top: 1px solid #ccc; | |
border-bottom: 1px solid #ccc; | |
} | |
h2 { | |
font-size: 19px; | |
} | |
h3 { | |
font-size: 15px; | |
} | |
a { | |
color: #fff; | |
text-decoration: none; | |
} | |
.left-column ul { | |
list-style-type: none; | |
padding-left: 20px; | |
} | |
.left-column li { | |
margin-bottom: 10px; | |
transition: all 0.3s ease; | |
} | |
.left-column li.active > a { | |
color: #007bff; | |
} | |
pre[class*="language-"] { | |
background-color: #1E2224; | |
margin: 0; | |
border-radius: 5px; | |
} | |
code[class*="language-"] { | |
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; | |
font-size: 12px; | |
line-height: 1.5; | |
} | |
.language-switcher { | |
display: flex; | |
justify-content: flex-start; | |
margin-bottom: 20px; | |
} | |
.language-button { | |
padding: 5px 10px; | |
background-color: #1E2224; | |
border: none; | |
color: #fff; | |
cursor: pointer; | |
margin-right: 10px; | |
} | |
.language-button.active { | |
background-color: #007bff; | |
} | |
.code-block { | |
display: none; | |
} | |
.code-block.active { | |
display: block; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="container"> | |
<div class="column left-column"> | |
<div class="content-wrapper"> | |
<h2>API Reference</h2> | |
<ul id="menu"> | |
{{range .Sections}} | |
<li><a href="#{{.ID}}">{{.Title}}</a> | |
{{if .SubSections}} | |
<ul> | |
{{range .SubSections}} | |
<li><a href="#{{.ID}}">{{.Title}}</a></li> | |
{{end}} | |
</ul> | |
{{end}} | |
</li> | |
{{end}} | |
</ul> | |
</div> | |
</div> | |
<div class="column middle-column"> | |
<div class="content-wrapper"> | |
<h1>{{.Title}}</h1> | |
{{range .Sections}} | |
<section id="{{.ID}}"> | |
<h2>{{.Title}}</h2> | |
{{.Content}} | |
{{range .SubSections}} | |
<section id="{{.ID}}"> | |
<h3>{{.Title}}</h3> | |
{{.Content}} | |
</section> | |
{{end}} | |
</section> | |
{{end}} | |
</div> | |
</div> | |
<div class="column right-column"> | |
<div class="content-wrapper"> | |
<div class="language-switcher"> | |
<button class="language-button active" data-language="php">PHP</button> | |
<button class="language-button" data-language="javascript">JavaScript</button> | |
<button class="language-button" data-language="go">Go</button> | |
</div> | |
<!-- Code examples will be populated here --> | |
</div> | |
</div> | |
</div> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/components/prism-core.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/plugins/autoloader/prism-autoloader.min.js"></script> | |
<script> | |
// Language switcher | |
document.querySelectorAll('.language-button').forEach(button => { | |
button.addEventListener('click', () => { | |
const language = button.dataset.language; | |
// Update button states | |
document.querySelectorAll('.language-button').forEach(btn => { | |
btn.classList.remove('active'); | |
}); | |
button.classList.add('active'); | |
// Update code block visibility | |
document.querySelectorAll('.code-block').forEach(block => { | |
block.classList.remove('active'); | |
if (block.dataset.language === language) { | |
block.classList.add('active'); | |
} | |
}); | |
}); | |
}); | |
// Menu animation on scroll | |
const menu = document.getElementById('menu'); | |
const menuItems = menu.getElementsByTagName('a'); | |
const sections = document.querySelectorAll('section'); | |
function isInViewport(element) { | |
const rect = element.getBoundingClientRect(); | |
return ( | |
rect.top >= 0 && | |
rect.left >= 0 && | |
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && | |
rect.right <= (window.innerWidth || document.documentElement.clientWidth) | |
); | |
} | |
function setActiveMenuItem() { | |
let currentSection = ''; | |
sections.forEach(section => { | |
if (isInViewport(section)) { | |
currentSection = section.id; | |
} | |
}); | |
Array.from(menuItems).forEach(item => { | |
item.parentElement.classList.remove('active'); | |
if (item.getAttribute('href').slice(1) === currentSection) { | |
item.parentElement.classList.add('active'); | |
} | |
}); | |
} | |
window.addEventListener('scroll', setActiveMenuItem); | |
setActiveMenuItem(); // Call once to set initial state | |
</script> | |
</body> | |
</html> | |
` | |
func main() { | |
if len(os.Args) != 3 { | |
fmt.Println("Usage: go run main.go input.md output.html") | |
os.Exit(1) | |
} | |
inputFile := os.Args[1] | |
outputFile := os.Args[2] | |
// Read the Markdown file | |
mdContent, err := ioutil.ReadFile(inputFile) | |
if err != nil { | |
log.Fatalf("Error reading Markdown file: %v", err) | |
} | |
// Convert Markdown to HTML | |
html := blackfriday.Run(mdContent) | |
// Parse the HTML and extract sections | |
pageData := parseHTML(html) | |
// Set the title to the input filename without extension | |
pageData.Title = strings.TrimSuffix(filepath.Base(inputFile), filepath.Ext(inputFile)) | |
// Create a new template and parse the template string | |
tmpl, err := template.New("apiDocs").Parse(templateHTML) | |
if err != nil { | |
log.Fatalf("Error parsing template: %v", err) | |
} | |
// Create the output file | |
out, err := os.Create(outputFile) | |
if err != nil { | |
log.Fatalf("Error creating output file: %v", err) | |
} | |
defer out.Close() | |
// Execute the template and write to the output file | |
err = tmpl.Execute(out, pageData) | |
if err != nil { | |
log.Fatalf("Error executing template: %v", err) | |
} | |
fmt.Printf("Successfully generated %s\n", outputFile) | |
} | |
func parseHTML(html []byte) PageData { | |
var pageData PageData | |
var currentSection *Section | |
var currentSubSection *Section | |
reader := bytes.NewReader(html) | |
doc, err := blackfriday.Parse(reader.Bytes(), &blackfriday.Renderer{}) | |
if err != nil { | |
log.Fatalf("Error parsing HTML: %v", err) | |
} | |
doc.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus { | |
if !entering { | |
return blackfriday.GoToNext | |
} | |
switch node.Type { | |
case blackfriday.Heading: | |
title := string(node.FirstChild.Literal) | |
id := strings.ToLower(strings.ReplaceAll(title, " ", "-")) | |
if node.Level == 2 { | |
currentSection = &Section{ | |
ID: id, | |
Title: title, | |
} | |
pageData.Sections = append(pageData.Sections, *currentSection) | |
currentSubSection = nil | |
} else if node.Level == 3 && currentSection != nil { | |
currentSubSection = &Section{ | |
ID: id, | |
Title: title, | |
} | |
currentSection.SubSections = append(currentSection.SubSections, *currentSubSection) | |
} | |
default: | |
if currentSubSection != nil { | |
currentSubSection.Content += template.HTML(blackfriday.Run(node.Literal)) | |
} else if currentSection != nil { | |
currentSection.Content += template.HTML(blackfriday.Run(node.Literal)) | |
} | |
} | |
return blackfriday.GoToNext | |
}) | |
return pageData | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment