package themes
// Functions directly related to HTML, CSS, Amber or GCSS
import (
"bytes"
"fmt"
"html"
"net/url"
"strings"
"github.com/microcosm-cc/bluemonday"
)
const (
// DefaultCSSFilename is the default CSS stylesheet
DefaultCSSFilename = "style.css"
// DefaultGCSSFilename is the default GCSS stylesheet
DefaultGCSSFilename = "style.gcss"
// DefaultTheme is the default theme when rendering Markdown and for error pages
DefaultTheme = "default"
)
// MetaKeywords contains a selection of allowed keywords for the HTML meta tag
var MetaKeywords = []string{"application-name", "author", "description", "generator", "keywords", "robots", "language", "googlebot", "Slurp", "bingbot", "geo.position", "geo.placename", "geo.region", "ICBM", "viewport"}
// MessagePage is an easy way to output a HTML page only given a title, the body
// (will be placed between the
tags) and the name of one of the
// built-in themes. Does not close and .
// Deprecated
func MessagePage(title, body, theme string) string {
return fmt.Sprintf("%s%s
%s
%s", title, StyleHead(theme), title, body)
}
var policy = bluemonday.UGCPolicy()
// StyleHead returns contents that goes in "", as bytes.
// This is either CSS wrapped in a "")
buf.WriteString("")
} else {
buf.WriteString("")
}
return buf.Bytes()
}
// MessagePageBytes provides the same functionalityt as MessagePage,
// but with []byte instead of string, and without closing
func MessagePageBytes(title string, body []byte, theme string) []byte {
// Sanitize the theme and title
theme = policy.Sanitize(theme)
title = policy.Sanitize(title)
var buf bytes.Buffer
buf.WriteString("")
buf.WriteString(title)
buf.WriteString("")
buf.Write(StyleHead(theme))
buf.WriteString("
")
buf.WriteString(title)
buf.WriteString("
")
buf.Write(body)
return buf.Bytes()
}
// SimpleHTMLPage provides a quick way to build a HTML page
func SimpleHTMLPage(title, headline, inhead, body, language []byte) []byte {
// Sanitize the title, headline and language
titleString := policy.Sanitize(string(title))
headlineString := policy.Sanitize(string(headline))
languageString := policy.Sanitize(string(language))
var buf bytes.Buffer
if len(languageString) > 0 {
buf.WriteString("")
} else {
buf.WriteString("")
}
if len(titleString) > 0 {
buf.WriteString("")
buf.WriteString(titleString)
buf.WriteString("")
}
buf.Write(inhead)
buf.WriteString("")
if len(headlineString) > 0 {
buf.WriteString("
")
}
buf.Write(body)
return buf.Bytes()
}
// HTMLLink builds an HTML link given the link text, the URL to a file/directory
// and a boolean that is true if the given URL is to a directory.
func HTMLLink(text, urlPath string, isDirectory bool) string {
return HTMLLinkWithBase(text, "", urlPath, isDirectory)
}
// HTMLLinkWithBase builds an HTML link, just like HTMLLink, but with the given
// base URL in front of the URL path. The base URL can be a path, like "/files",
// or an absolute URL. An empty base URL links relative to the server root.
func HTMLLinkWithBase(text, baseURL, urlPath string, isDirectory bool) string {
// Sanitize the link text
text = policy.Sanitize(text)
// Add a final slash, if needed
if isDirectory {
text += "/"
urlPath += "/"
}
// Percent-encode the URL path so filenames with newlines, spaces or
// other special bytes produce a valid href. See issue #144.
href := strings.TrimSuffix(baseURL, "/") + (&url.URL{Path: "/" + urlPath}).EscapedPath()
return "" + text + " "
}
// StyleAmber modifies Amber source code so that a link to the given stylesheet URL is added
func StyleAmber(amberdata []byte, url string) []byte {
// Sanitize the URL
url = policy.Sanitize(url)
// If the given url is not already mentioned and the data contains "body"
if !bytes.Contains(amberdata, []byte(url)) && bytes.Contains(amberdata, []byte("html")) && bytes.Contains(amberdata, []byte("body")) {
// Extract one level of indendation
whitespace := OneLevelOfIndentation(&amberdata, "body")
// Check if there already is a head section
if bytes.Contains(amberdata, []byte("head")) {
// Add a link to the stylesheet
return bytes.Replace(amberdata, []byte("head\n"), []byte("head\n"+whitespace+whitespace+`link[href="`+url+`"][rel="stylesheet"][type="text/css"]`+"\n"), 1)
} else if bytes.Contains(amberdata, []byte("body")) {
// Add a link to the stylesheet
return bytes.Replace(amberdata, []byte("html\n"), []byte("html\n"+whitespace+"head\n"+whitespace+whitespace+`link[href="`+url+`"][rel="stylesheet"][type="text/css"]`+"\n"), 1)
}
}
return amberdata
}
// StyleHTML modifies HTML source code so that a link to the given stylesheet URL is added
func StyleHTML(htmldata []byte, url string) []byte {
// Sanitize the URL
url = policy.Sanitize(url)
// If the given url is not already mentioned and the data contains "body"
if !bytes.Contains(htmldata, []byte(url)) && bytes.Contains(htmldata, []byte("body")) {
if bytes.Contains(htmldata, []byte("")) {
return bytes.Replace(htmldata, []byte(""), []byte(" \n "), 1)
} else if bytes.Contains(htmldata, []byte("")) {
return bytes.Replace(htmldata, []byte(""), []byte(" \n \n \n "), 1)
}
}
return htmldata
}
// InsertDoctype inserts to the HTML, if missing.
// Does not check if the given data is HTML. Assumes it to be HTML.
func InsertDoctype(htmldata []byte) []byte {
// If there are more than two lines
if bytes.Count(htmldata, []byte("\n")) > 2 {
fields := bytes.SplitN(htmldata, []byte("\n"), 3)
line1 := strings.ToLower(string(fields[0]))
line2 := strings.ToLower(string(fields[1]))
if strings.Contains(line1, "doctype") || strings.Contains(line2, "doctype") {
return htmldata
}
// Doctype is missing from the first two lines, add it
return []byte("\n" + string(htmldata))
}
return htmldata
}
// NoPage generates a HTML page for when a file is not found
func NoPage(filename, theme string) []byte {
// Sanitize the filename and the theme name
filename = policy.Sanitize(filename)
theme = policy.Sanitize(theme)
// Return a HTML page
return MessagePageBytes("Not found", []byte("File not found: "+filename), theme)
}