diff --git a/main.go b/main.go
index d6c89ef..5853e3b 100644
--- a/main.go
+++ b/main.go
@@ -21,7 +21,6 @@ import (
"io/ioutil"
"github.com/sevlyar/go-daemon"
- gfm "github.com/shurcooL/github_flavored_markdown"
"github.com/shurcooL/github_flavored_markdown/gfmstyle"
//blackfriday "gopkg.in/russross/blackfriday.v2"
)
@@ -34,7 +33,7 @@ var (
devmode = flag.Bool("dev_mode", false, "whether this server should run in developer mode or not")
)
-const DEBUG = true
+const DEBUG = false
const DOMAIN_NAME = "threefortiethofonehamster.com"
@@ -42,6 +41,7 @@ const HTML_HEADER = `
+
%s | %s
@@ -89,9 +89,15 @@ func serveMarkdown(w http.ResponseWriter, r *http.Request, paths ...string) {
}
}
w.Write([]byte(fmt.Sprintf(HTML_HEADER, string(title), r.Host)))
- for _, b := range bs {
- html := gfm.Markdown(b)
- // find images in markdown, replace with device-responsive images
+ for i, b := range bs {
+ pathDir := paths[i][len("static/"):]
+ lastSlash := strings.LastIndex(pathDir, "/")
+ if lastSlash != -1 {
+ pathDir = pathDir[:lastSlash]
+ }
+ // Markdown uses the path to generate the correct paths for resized images
+ log.Print(paths[i], "->", pathDir)
+ html := Markdown(b, pathDir)
w.Write(html)
}
w.Write([]byte(HTML_FOOTER))
@@ -240,7 +246,7 @@ func startServer(srv *http.Server) {
//serveMux.Handle("/certbot/", http.StripPrefix("/certbot/", http.FileServer(http.Dir("./certbot-tmp"))))
serveMux.Handle("/gfm/", http.StripPrefix("/gfm", http.FileServer(gfmstyle.Assets)))
serveMux.Handle("/resume/", http.StripPrefix("/resume", http.FileServer(http.Dir("resume/"))))
- serveMux.Handle("/thumbnail/", Cache(Resize(1024, http.StripPrefix("/thumbnail", http.FileServer(http.Dir("static/"))))))
+ serveMux.Handle("/resize/", Cache(Resize(640, http.StripPrefix("/resize", http.FileServer(http.Dir("static/"))))))
serveMux.HandleFunc("/main.css", func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "main.css") })
if webhookKey != nil {
log.Print("web hook found")
diff --git a/markdown.go b/markdown.go
new file mode 100644
index 0000000..48ab165
--- /dev/null
+++ b/markdown.go
@@ -0,0 +1,345 @@
+// this code is a fork of GitHub Flavored Markdown (https://github.com/shurcooL/github_flavored_markdown)
+// with some minor alterations to support media-responsive images
+/*
+Package github_flavored_markdown provides a GitHub Flavored Markdown renderer
+with fenced code block highlighting, clickable heading anchor links.
+
+The functionality should be equivalent to the GitHub Markdown API endpoint specified at
+https://developer.github.com/v3/markdown/#render-a-markdown-document-in-raw-mode, except
+the rendering is performed locally.
+
+See examples for how to generate a complete HTML page, including CSS styles.
+*/
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "log"
+ "regexp"
+ "sort"
+ "strings"
+ "text/template"
+
+ "github.com/microcosm-cc/bluemonday"
+ "github.com/russross/blackfriday"
+ "github.com/shurcooL/highlight_diff"
+ "github.com/shurcooL/highlight_go"
+ "github.com/shurcooL/sanitized_anchor_name"
+ "github.com/sourcegraph/annotate"
+ "github.com/sourcegraph/syntaxhighlight"
+ "golang.org/x/net/html"
+)
+
+// Markdown renders GitHub Flavored Markdown text.
+func Markdown(text []byte, path string) []byte {
+ log.Print("markdown " + path)
+ const htmlFlags = 0
+ renderer := &renderer{
+ Html: blackfriday.HtmlRenderer(htmlFlags, "", "").(*blackfriday.Html), path: path}
+ unsanitized := blackfriday.Markdown(text, renderer, extensions)
+ return unsanitized
+}
+
+// extensions for GitHub Flavored Markdown-like parsing.
+const extensions = blackfriday.EXTENSION_NO_INTRA_EMPHASIS |
+ blackfriday.EXTENSION_TABLES |
+ blackfriday.EXTENSION_FENCED_CODE |
+ blackfriday.EXTENSION_AUTOLINK |
+ blackfriday.EXTENSION_STRIKETHROUGH |
+ blackfriday.EXTENSION_SPACE_HEADERS |
+ blackfriday.EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK
+
+// policy for GitHub Flavored Markdown-like sanitization.
+var policy = func() *bluemonday.Policy {
+ p := bluemonday.UGCPolicy()
+ p.AllowAttrs("class").Matching(bluemonday.SpaceSeparatedTokens).OnElements("div", "span")
+ p.AllowAttrs("class", "name").Matching(bluemonday.SpaceSeparatedTokens).OnElements("a")
+ p.AllowAttrs("rel").Matching(regexp.MustCompile(`^nofollow$`)).OnElements("a")
+ p.AllowAttrs("aria-hidden").Matching(regexp.MustCompile(`^true$`)).OnElements("a")
+ p.AllowAttrs("type").Matching(regexp.MustCompile(`^checkbox$`)).OnElements("input")
+ p.AllowAttrs("checked", "disabled").Matching(regexp.MustCompile(`^$`)).OnElements("input")
+ p.AllowDataURIImages()
+ return p
+}()
+
+type renderer struct {
+ *blackfriday.Html
+ path string
+}
+
+// GitHub Flavored Markdown heading with clickable and hidden anchor.
+func (*renderer) Header(out *bytes.Buffer, text func() bool, level int, _ string) {
+ marker := out.Len()
+ doubleSpace(out)
+
+ if !text() {
+ out.Truncate(marker)
+ return
+ }
+
+ textHTML := out.String()[marker:]
+ out.Truncate(marker)
+
+ // Extract text content of the heading.
+ var textContent string
+ if node, err := html.Parse(strings.NewReader(textHTML)); err == nil {
+ textContent = extractText(node)
+ } else {
+ // Failed to parse HTML (probably can never happen), so just use the whole thing.
+ textContent = html.UnescapeString(textHTML)
+ }
+ anchorName := sanitized_anchor_name.Create(textContent)
+
+ out.WriteString(fmt.Sprintf(``, level, anchorName, anchorName))
+ out.WriteString(textHTML)
+ out.WriteString(fmt.Sprintf("\n", level))
+}
+
+func (r *renderer) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
+ writeImg := func() {
+ out.WriteString("
0 {
+ attrEscape(out, alt)
+ }
+ if len(title) > 0 {
+ out.WriteString("\" title=\"")
+ attrEscape(out, title)
+ }
+
+ out.WriteString("\" />")
+ }
+
+ writeSource := func() {
+ out.WriteString("")
+ }
+ // link to outside of this website
+ if bytes.HasPrefix(link, []byte("http")) {
+ writeImg()
+ } else {
+ // local link; we can use the resized images to support phones
+ out.WriteString("")
+ writeSource()
+ writeImg()
+ out.WriteString("")
+ }
+}
+
+// extractText returns the recursive concatenation of the text content of an html node.
+func extractText(n *html.Node) string {
+ var out string
+ for c := n.FirstChild; c != nil; c = c.NextSibling {
+ if c.Type == html.TextNode {
+ out += c.Data
+ } else {
+ out += extractText(c)
+ }
+ }
+ return out
+}
+
+// TODO: Clean up and improve this code.
+// GitHub Flavored Markdown fenced code block with highlighting.
+func (*renderer) BlockCode(out *bytes.Buffer, text []byte, lang string) {
+ doubleSpace(out)
+
+ // parse out the language name
+ count := 0
+ for _, elt := range strings.Fields(lang) {
+ if elt[0] == '.' {
+ elt = elt[1:]
+ }
+ if len(elt) == 0 {
+ continue
+ }
+ out.WriteString(``)
+ count++
+ break
+ }
+
+ if count == 0 {
+ out.WriteString("")
+ }
+
+ if highlightedCode, ok := highlightCode(text, lang); ok {
+ out.Write(highlightedCode)
+ } else {
+ attrEscape(out, text)
+ }
+
+ if count == 0 {
+ out.WriteString("
\n")
+ } else {
+ out.WriteString("
\n")
+ }
+}
+
+// Task List support.
+func (r *renderer) ListItem(out *bytes.Buffer, text []byte, flags int) {
+ switch {
+ case bytes.HasPrefix(text, []byte("[ ] ")):
+ text = append([]byte(``), text[3:]...)
+ case bytes.HasPrefix(text, []byte("[x] ")) || bytes.HasPrefix(text, []byte("[X] ")):
+ text = append([]byte(``), text[3:]...)
+ }
+ r.Html.ListItem(out, text, flags)
+}
+
+var gfmHTMLConfig = syntaxhighlight.HTMLConfig{
+ String: "s",
+ Keyword: "k",
+ Comment: "c",
+ Type: "n",
+ Literal: "o",
+ Punctuation: "p",
+ Plaintext: "n",
+ Tag: "tag",
+ HTMLTag: "htm",
+ HTMLAttrName: "atn",
+ HTMLAttrValue: "atv",
+ Decimal: "m",
+}
+
+func highlightCode(src []byte, lang string) (highlightedCode []byte, ok bool) {
+ switch lang {
+ case "Go", "Go-unformatted":
+ var buf bytes.Buffer
+ err := highlight_go.Print(src, &buf, syntaxhighlight.HTMLPrinter(gfmHTMLConfig))
+ if err != nil {
+ return nil, false
+ }
+ return buf.Bytes(), true
+ case "diff":
+ anns, err := highlight_diff.Annotate(src)
+ if err != nil {
+ return nil, false
+ }
+
+ lines := bytes.Split(src, []byte("\n"))
+ lineStarts := make([]int, len(lines))
+ var offset int
+ for lineIndex := 0; lineIndex < len(lines); lineIndex++ {
+ lineStarts[lineIndex] = offset
+ offset += len(lines[lineIndex]) + 1
+ }
+
+ lastDel, lastIns := -1, -1
+ for lineIndex := 0; lineIndex < len(lines); lineIndex++ {
+ var lineFirstChar byte
+ if len(lines[lineIndex]) > 0 {
+ lineFirstChar = lines[lineIndex][0]
+ }
+ switch lineFirstChar {
+ case '+':
+ if lastIns == -1 {
+ lastIns = lineIndex
+ }
+ case '-':
+ if lastDel == -1 {
+ lastDel = lineIndex
+ }
+ default:
+ if lastDel != -1 || lastIns != -1 {
+ if lastDel == -1 {
+ lastDel = lastIns
+ } else if lastIns == -1 {
+ lastIns = lineIndex
+ }
+
+ beginOffsetLeft := lineStarts[lastDel]
+ endOffsetLeft := lineStarts[lastIns]
+ beginOffsetRight := lineStarts[lastIns]
+ endOffsetRight := lineStarts[lineIndex]
+
+ anns = append(anns, &annotate.Annotation{Start: beginOffsetLeft, End: endOffsetLeft, Left: []byte(``), Right: []byte(``), WantInner: 0})
+ anns = append(anns, &annotate.Annotation{Start: beginOffsetRight, End: endOffsetRight, Left: []byte(``), Right: []byte(``), WantInner: 0})
+
+ if '@' != lineFirstChar {
+ //leftContent := string(src[beginOffsetLeft:endOffsetLeft])
+ //rightContent := string(src[beginOffsetRight:endOffsetRight])
+ // This is needed to filter out the "-" and "+" at the beginning of each line from being highlighted.
+ // TODO: Still not completely filtered out.
+ leftContent := ""
+ for line := lastDel; line < lastIns; line++ {
+ leftContent += "\x00" + string(lines[line][1:]) + "\n"
+ }
+ rightContent := ""
+ for line := lastIns; line < lineIndex; line++ {
+ rightContent += "\x00" + string(lines[line][1:]) + "\n"
+ }
+
+ var sectionSegments [2][]*annotate.Annotation
+ highlight_diff.HighlightedDiffFunc(leftContent, rightContent, §ionSegments, [2]int{beginOffsetLeft, beginOffsetRight})
+
+ anns = append(anns, sectionSegments[0]...)
+ anns = append(anns, sectionSegments[1]...)
+ }
+ }
+ lastDel, lastIns = -1, -1
+ }
+ }
+
+ sort.Sort(anns)
+
+ out, err := annotate.Annotate(src, anns, template.HTMLEscape)
+ if err != nil {
+ return nil, false
+ }
+ return out, true
+ default:
+ return nil, false
+ }
+}
+
+// Unexported blackfriday helpers.
+
+func doubleSpace(out *bytes.Buffer) {
+ if out.Len() > 0 {
+ out.WriteByte('\n')
+ }
+}
+
+func escapeSingleChar(char byte) (string, bool) {
+ if char == '"' {
+ return """, true
+ }
+ if char == '&' {
+ return "&", true
+ }
+ if char == '<' {
+ return "<", true
+ }
+ if char == '>' {
+ return ">", true
+ }
+ return "", false
+}
+
+func attrEscape(out *bytes.Buffer, src []byte) {
+ org := 0
+ for i, ch := range src {
+ if entity, ok := escapeSingleChar(ch); ok {
+ if i > org {
+ // copy all the normal characters since the last escape
+ out.Write(src[org:i])
+ }
+ org = i + 1
+ out.WriteString(entity)
+ }
+ }
+ if org < len(src) {
+ out.Write(src[org:])
+ }
+}