diff --git a/docs/plugins/assets/banner.png b/docs/plugins/assets/banner.png new file mode 100644 index 0000000..56f5d1b Binary files /dev/null and b/docs/plugins/assets/banner.png differ diff --git a/docs/plugins/assets/banner.psd b/docs/plugins/assets/banner.psd new file mode 100644 index 0000000..7e30760 Binary files /dev/null and b/docs/plugins/assets/banner.psd differ diff --git a/docs/plugins/assets/logo.png b/docs/plugins/assets/logo.png new file mode 100644 index 0000000..083342a Binary files /dev/null and b/docs/plugins/assets/logo.png differ diff --git a/docs/plugins/assets/logo_white.png b/docs/plugins/assets/logo_white.png new file mode 100644 index 0000000..9557852 Binary files /dev/null and b/docs/plugins/assets/logo_white.png differ diff --git a/docs/plugins/assets/theme.js b/docs/plugins/assets/theme.js new file mode 100644 index 0000000..4338f19 --- /dev/null +++ b/docs/plugins/assets/theme.js @@ -0,0 +1,51 @@ +/* Things to do before body loads */ +function restoreDarkMode(){ + if (localStorage.getItem("darkMode") === "enabled") { + $("html").addClass("is-dark"); + $("html").removeClass("is-white"); + } else { + $("html").removeClass("is-dark"); + $("html").addClass("is-white"); + } +} +restoreDarkMode(); + +function updateElementToTheme(isDarkTheme=false){ + if (!isDarkTheme){ + let whiteSrc = $("#sysicon").attr("white_src"); + $("#sysicon").attr("src", whiteSrc); + $("#darkModeToggle").html(``); + + // Update the rendering text color in the garphs + if (typeof(changeScaleTextColor) != "undefined"){ + changeScaleTextColor("black"); + } + + }else{ + let darkSrc = $("#sysicon").attr("dark_src"); + $("#sysicon").attr("src", darkSrc); + $("#darkModeToggle").html(``); + + // Update the rendering text color in the garphs + if (typeof(changeScaleTextColor) != "undefined"){ + changeScaleTextColor("white"); + } + } +} + +/* Things to do after body loads */ +$(document).ready(function(){ + $("#darkModeToggle").on("click", function() { + $("html").toggleClass("is-dark"); + $("html").toggleClass("is-white"); + if ($("html").hasClass("is-dark")) { + localStorage.setItem("darkMode", "enabled"); + updateElementToTheme(true); + } else { + localStorage.setItem("darkMode", "disabled"); + updateElementToTheme(false); + } + }); + + updateElementToTheme(localStorage.getItem("darkMode") === "enabled"); +}); \ No newline at end of file diff --git a/docs/plugins/build.go b/docs/plugins/build.go new file mode 100644 index 0000000..d69bf13 --- /dev/null +++ b/docs/plugins/build.go @@ -0,0 +1,280 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/fs" + "os" + "path/filepath" + "strings" + + "github.com/gomarkdown/markdown" + "github.com/gomarkdown/markdown/html" + "github.com/gomarkdown/markdown/parser" + "github.com/yosssi/gohtml" +) + +type FileInfo struct { + Filename string `json:"filename"` + Title string `json:"title"` + Type string `json:"type"` +} + +func build() { + rootDir := "./docs" + outputFile := "./index.json" + + type Folder struct { + Title string `json:"title"` + Path string `json:"path"` + Type string `json:"type"` + Files []interface{} `json:"files,omitempty"` + } + + var buildTree func(path string, d fs.DirEntry) interface{} + buildTree = func(path string, d fs.DirEntry) interface{} { + relativePath, _ := filepath.Rel(rootDir, path) + relativePath = filepath.ToSlash(relativePath) + var title string + if d.IsDir() { + title = filepath.Base(relativePath) + } else { + title = strings.TrimSuffix(filepath.Base(relativePath), filepath.Ext(relativePath)) + } + + //Strip the leader numbers from the title, e.g. 1. Introduction -> Introduction + if strings.Contains(title, ".") { + parts := strings.SplitN(title, ".", 2) + if len(parts) > 1 { + title = strings.TrimSpace(parts[1]) + } + } + // Remove leading numbers and dots + title = strings.TrimLeft(title, "0123456789. ") + // Remove leading spaces + title = strings.TrimLeft(title, " ") + + if d.IsDir() { + folder := Folder{ + Title: title, + Path: relativePath, + Type: "folder", + } + + entries, err := os.ReadDir(path) + if err != nil { + panic(err) + } + + for _, entry := range entries { + if entry.Name() == "img" || entry.Name() == "assets" { + continue + } + if strings.Contains(filepath.ToSlash(filepath.Join(relativePath, entry.Name())), "/img/") || strings.Contains(filepath.ToSlash(filepath.Join(relativePath, entry.Name())), "/assets/") { + continue + } + child := buildTree(filepath.Join(path, entry.Name()), entry) + if child != nil { + folder.Files = append(folder.Files, child) + } + } + return folder + } else { + ext := filepath.Ext(relativePath) + if ext != ".md" && ext != ".html" && ext != ".txt" { + return nil + } + return FileInfo{ + Filename: relativePath, + Title: title, + Type: "file", + } + } + } + + rootInfo, err := os.Stat(rootDir) + if err != nil { + panic(err) + } + rootFolder := buildTree(rootDir, fs.FileInfoToDirEntry(rootInfo)) + jsonData, err := json.MarshalIndent(rootFolder, "", " ") + if err != nil { + panic(err) + } + + /* For debug purposes, print the JSON structure */ + err = os.WriteFile(outputFile, jsonData, 0644) + if err != nil { + panic(err) + } + + /* For each file in the folder structure, convert markdown to HTML */ + htmlOutputDir := "./html" + os.RemoveAll(htmlOutputDir) // Clear previous HTML output + err = os.MkdirAll(htmlOutputDir, os.ModePerm) + if err != nil { + panic(err) + } + + var processFiles func(interface{}) + processFiles = func(node interface{}) { + switch n := node.(type) { + case FileInfo: + if filepath.Ext(n.Filename) == ".md" { + inputPath := filepath.Join(rootDir, n.Filename) + outputPath := filepath.Join(htmlOutputDir, strings.TrimSuffix(n.Filename, ".md")+".html") + + // Ensure the output directory exists + err := os.MkdirAll(filepath.Dir(outputPath), os.ModePerm) + if err != nil { + panic(err) + } + + // Read the markdown file + mdContent, err := os.ReadFile(inputPath) + if err != nil { + panic(err) + } + + // Convert markdown to HTML + docContent := mdToHTML(mdContent) + docContent, err = optimizeCss(docContent) + if err != nil { + panic(err) + } + + // Load the HTML template + templateBytes, err := os.ReadFile("template/documents.html") + if err != nil { + panic(err) + } + + // Generate the side menu HTML + sideMenuHTML, err := generateSideMenu(string(jsonData), n.Title) + if err != nil { + panic(err) + } + + templateBody := string(templateBytes) + // Replace placeholders in the template + htmlContent := strings.ReplaceAll(templateBody, "{{title}}", n.Title+" | Zoraxy Documentation") + htmlContent = strings.ReplaceAll(htmlContent, "{{content}}", string(docContent)) + htmlContent = strings.ReplaceAll(htmlContent, "{{sideMenu}}", sideMenuHTML) + htmlContent = strings.ReplaceAll(htmlContent, "{{root_url}}", *root_url) + //Add more if needed + + //Beautify the HTML content + htmlContent = gohtml.Format(htmlContent) + + // Write the HTML file + err = os.WriteFile(outputPath, []byte(htmlContent), 0644) + if err != nil { + panic(err) + } + + //Check if the .md file directory have an ./img or ./assets folder. If yes, copy the contents to the output directory + imgDir := filepath.Join(rootDir, filepath.Dir(n.Filename), "img") + assetsDir := filepath.Join(rootDir, filepath.Dir(n.Filename), "assets") + if _, err := os.Stat(imgDir); !os.IsNotExist(err) { + err = filepath.Walk(imgDir, func(srcPath string, info os.FileInfo, err error) error { + if err != nil { + return err + } + relPath, err := filepath.Rel(imgDir, srcPath) + if err != nil { + return err + } + destPath := filepath.Join(filepath.Dir(outputPath), "img", relPath) + if info.IsDir() { + return os.MkdirAll(destPath, os.ModePerm) + } + data, err := os.ReadFile(srcPath) + if err != nil { + return err + } + return os.WriteFile(destPath, data, 0644) + }) + if err != nil { + panic(err) + } + } + if _, err := os.Stat(assetsDir); !os.IsNotExist(err) { + err = filepath.Walk(assetsDir, func(srcPath string, info os.FileInfo, err error) error { + if err != nil { + return err + } + relPath, err := filepath.Rel(assetsDir, srcPath) + if err != nil { + return err + } + destPath := filepath.Join(filepath.Dir(outputPath), "assets", relPath) + if info.IsDir() { + return os.MkdirAll(destPath, os.ModePerm) + } + data, err := os.ReadFile(srcPath) + if err != nil { + return err + } + return os.WriteFile(destPath, data, 0644) + }) + if err != nil { + panic(err) + } + } + + fmt.Println("Generated HTML:", outputPath) + } + case Folder: + for _, child := range n.Files { + processFiles(child) + } + } + } + + processFiles(rootFolder) + copyOtherRes() +} + +func copyOtherRes() { + srcDir := "./assets" + destDir := "./html/assets" + + err := filepath.Walk(srcDir, func(srcPath string, info os.FileInfo, err error) error { + if err != nil { + return err + } + relPath, err := filepath.Rel(srcDir, srcPath) + if err != nil { + return err + } + destPath := filepath.Join(destDir, relPath) + if info.IsDir() { + return os.MkdirAll(destPath, os.ModePerm) + } + data, err := os.ReadFile(srcPath) + if err != nil { + return err + } + return os.WriteFile(destPath, data, 0644) + }) + if err != nil { + panic(err) + } + +} + +func mdToHTML(md []byte) []byte { + // create markdown parser with extensions + extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock + p := parser.NewWithExtensions(extensions) + doc := p.Parse(md) + + // create HTML renderer with extensions + htmlFlags := html.CommonFlags | html.HrefTargetBlank + opts := html.RendererOptions{ + Flags: htmlFlags, + } + renderer := html.NewRenderer(opts) + + return markdown.Render(doc, renderer) +} diff --git a/docs/plugins/build.sh b/docs/plugins/build.sh new file mode 100644 index 0000000..f78c1ae --- /dev/null +++ b/docs/plugins/build.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +# Run the Go program with the specified arguments +./docs.exe -m=build -root=plugins/html/ \ No newline at end of file diff --git a/docs/plugins/cssOptimizer.go b/docs/plugins/cssOptimizer.go new file mode 100644 index 0000000..f73701e --- /dev/null +++ b/docs/plugins/cssOptimizer.go @@ -0,0 +1,130 @@ +package main + +import ( + "fmt" + "net/url" + "strings" + + "github.com/PuerkitoBio/goquery" +) + +func optimizeCss(htmlContent []byte) ([]byte, error) { + doc, err := goquery.NewDocumentFromReader(strings.NewReader(string(htmlContent))) + if err != nil { + return nil, err + } + + originalHTMLContent := string(htmlContent) + // Replace img elements + + doc.Find("img").Each(func(i int, s *goquery.Selection) { + //For each of the image element, replace the parent from p to div + originalParent, err := s.Parent().Html() + if err != nil { + fmt.Println("Error getting parent HTML:", err) + return + } + + src, exists := s.Attr("src") + if !exists { + fmt.Println("No src attribute found for img element") + return + } + encodedSrc := (&url.URL{Path: src}).String() + + //Patch the bug in the parser that converts " />" to "/>" + originalParent = strings.ReplaceAll(originalParent, "/>", " />") + fmt.Println("
") + //Replace the img with ts-image + originalHTMLContent = strings.Replace(originalHTMLContent, originalParent, "
"+originalParent+"
", 1) + }) + + // Add "ts-text" class to each p element + doc.Find("p").Each(func(i int, s *goquery.Selection) { + class, exists := s.Attr("class") + var newClass string + if exists { + newClass = fmt.Sprintf("%s ts-text", class) + } else { + newClass = "ts-text" + } + + originalParagraph, _ := s.Html() + originalHTMLContent = strings.ReplaceAll(originalHTMLContent, originalParagraph, fmt.Sprintf("

%s

", newClass, originalParagraph)) + }) + + //Replace hr with ts-divider + // Replace hr elements outside of code blocks + doc.Find("hr").Each(func(i int, s *goquery.Selection) { + parent := s.Parent() + if parent.Is("code") { + // Skip
inside blocks + return + } + + // Replace
with
+ originalHTMLContent = strings.ReplaceAll(originalHTMLContent, "
", "
") + }) + + // Add ts-table to all table elements + doc.Find("table").Each(func(i int, s *goquery.Selection) { + class, exists := s.Attr("class") + var newClass string + if exists { + newClass = fmt.Sprintf("%s ts-table", class) + } else { + newClass = "ts-table" + } + + originalTable, _ := s.Html() + originalHTMLContent = strings.ReplaceAll(originalHTMLContent, originalTable, fmt.Sprintf("%s
", newClass, originalTable)) + }) + + // Replace