diff --git a/cmd/web/handlers.go b/cmd/web/handlers.go index 37f8c1f..7acf2f1 100644 --- a/cmd/web/handlers.go +++ b/cmd/web/handlers.go @@ -1,8 +1,7 @@ package main import ( - "fmt" - "html" + // "html" "net/http" "strings" @@ -15,7 +14,7 @@ func (app *application) home(w http.ResponseWriter, r *http.Request) { return } - app.render(w, http.StatusOK, "home.tmpl.html", nil) + app.renderPage(w, http.StatusOK, "home.tmpl.html", nil) } func (app *application) generateRss(w http.ResponseWriter, r *http.Request) { @@ -29,12 +28,14 @@ func (app *application) generateRss(w http.ResponseWriter, r *http.Request) { pages[i] = strings.TrimSpace(pages[i]) } - feed, err := feed.GenerateRss(siteUrl, siteName, siteDesc, pages...) + feedInfo, err := feed.NewFeedInfo(siteName, siteUrl, siteDesc, "", pages...) if err != nil { - w.Write([]byte(fmt.Sprintf("

Error generating feed: %s

", err.Error()))) - app.infoLog.Printf("Error generating feed: %s\n", err.Error()) - } - for _, line := range strings.Split(feed, "\n") { - w.Write([]byte(html.EscapeString(line) + "\n")) + app.errorLog.Printf("Error generating feed: %s\n", err.Error()) + return } + + feed := feedInfo.GenerateRSS() + data := newTemplateData(r) + data.Feeds = append(data.Feeds, feed) + app.renderElem(w, http.StatusOK, "feed-output.tmpl.html", data) } diff --git a/cmd/web/helpers.go b/cmd/web/helpers.go index 96af52d..6c281ac 100644 --- a/cmd/web/helpers.go +++ b/cmd/web/helpers.go @@ -32,7 +32,7 @@ func (app *application) cleanUrl(url string) string { return s } -func (app *application) render(w http.ResponseWriter, status int, page string, data *templateData) { +func (app *application) renderPage(w http.ResponseWriter, status int, page string, data *templateData) { ts, ok := app.templateCache[page] if !ok { err := fmt.Errorf("the template %s does not exist", page) @@ -54,3 +54,26 @@ func (app *application) render(w http.ResponseWriter, status int, page string, d buf.WriteTo(w) } + +func (app *application) renderElem(w http.ResponseWriter, status int, elem string, data *templateData) { + ts, ok := app.templateCache[elem] + if !ok { + err := fmt.Errorf("the template %s does not exist", elem) + app.serverError(w, err) + return + } + + // a buffer to attempt to write the template to + // before writing it to the ResponseWriter w + buf := new(bytes.Buffer) + + err := ts.Execute(buf, data) + if err != nil { + app.serverError(w, err) + return + } + + w.WriteHeader(status) + + buf.WriteTo(w) +} diff --git a/cmd/web/templates.go b/cmd/web/templates.go index e994bfe..2e6429c 100644 --- a/cmd/web/templates.go +++ b/cmd/web/templates.go @@ -1,11 +1,15 @@ package main import ( + "net/http" "path/filepath" "text/template" + "time" ) type templateData struct { + CurrentYear int + Feeds []string } func newTemplateCache() (map[string]*template.Template, error) { @@ -15,7 +19,6 @@ func newTemplateCache() (map[string]*template.Template, error) { if err != nil { return nil, err } - for _, page := range pages { name := filepath.Base(page) @@ -39,5 +42,27 @@ func newTemplateCache() (map[string]*template.Template, error) { cache[name] = ts } + // htmx elements should just be raw html, so we parse those separately + hxElems, err := filepath.Glob("./ui/html/htmx/*.tmpl.html") + if err != nil { + return nil, err + } + for _, hxElem := range hxElems { + name := filepath.Base(hxElem) + + ts, err := template.ParseFiles(hxElem) + if err != nil { + return nil, err + } + + cache[name] = ts + } + return cache, nil } + +func newTemplateData(r *http.Request) *templateData { + return &templateData{ + CurrentYear: time.Now().Year(), + } +} diff --git a/feed/feed.go b/feed/feed.go index c625f59..aa08611 100644 --- a/feed/feed.go +++ b/feed/feed.go @@ -10,15 +10,32 @@ import ( "golang.org/x/net/html" ) -type FeedBuilder interface { - GenerateFeed() string -} +const feedfmtopen = ` + + +%s +%s +%s` + +const feedfmtclose = ` +` + +const itemfmt = ` +%s +%s +%s +%s + +%s + +` type FeedInfo struct { SiteName string SiteUrl string SiteDesc string PageUrls []string + Items []*FeedItem errors map[string]error } @@ -26,7 +43,6 @@ type FeedItem struct { Url string Title string Author string - EscapedText string PubTime time.Time RawText string } @@ -97,9 +113,15 @@ func (f *FeedItem) ParseContent(content string) error { if err != nil { return err } - var builder strings.Builder - html.Render(&builder, earticle) - f.RawText = builder.String() + etitle, err := getHtmlElement(doc, "title") + if err != nil { + return err + } + f.Title = etitle.FirstChild.Data + + var articleBuilder strings.Builder + html.Render(&articleBuilder, earticle) + f.RawText = articleBuilder.String() etime, err := getHtmlElement(earticle, "time") if err != nil { @@ -133,37 +155,75 @@ func NewFeedItem(url string) (*FeedItem, error) { return &item, nil } -// parseArticle returns an error if it could not parse the HTML or if it could not parse a time -// if a time could not be parsed, the parsed html article will still be returned -func parseArticle(content string) (string, *time.Time, error) { - doc, err := html.Parse(strings.NewReader(content)) - if err != nil { - return "", nil, fmt.Errorf("Error parsing HTML: %w", err) +func NewFeedInfo(name, base_url, desc, author string, page_urls...string) (*FeedInfo, error) { + info := FeedInfo{ + SiteName: name, + SiteUrl: base_url, + SiteDesc: desc, + PageUrls: page_urls, } - var f func(*html.Node, string) - var element *html.Node - var pagetime time.Time - f = func(n *html.Node, tag string) { - if n.Type == html.ElementNode && n.Data == tag { - element = n - return - } - for c := n.FirstChild; c != nil; c = c.NextSibling { - f(c, tag) + for _,url := range info.PageUrls { + item, err := NewFeedItem(url) + if err != nil { + info.errors[url] = err + } else { + info.Items = append(info.Items, item) } } - - f(doc, "article") - var builder strings.Builder - html.Render(&builder, element) - - f(element, "time") - for _, d := range element.Attr { - if d.Key == "datetime" { - pagetime, err = parseTime(d.Val) - } - } - - return builder.String(), &pagetime, nil + return &info, nil } +func (info *FeedInfo) format(raw string) string { + var formatBuilder strings.Builder + depth := 0 + oldDepth := 0 + for _,line := range strings.Split(raw, "\n") { + tmp := strings.TrimSpace(line) + if tmp == "" { + continue + } + oldDepth = depth + for i,s := range line { + if i < len(line) - 1 { + t := line[i + 1] + if s == '<' && t != '?' && t != '/' { + depth += 1 + } + if s == '<' && t == '/' { + depth -= 1 + } + if s == '/' && t == '>' { + depth -= 1 + } + } + } + for i := 0; i < depth; i++ { + if (i == depth - 1 && oldDepth < depth) { + continue + } + formatBuilder.WriteString(" ") + } + formatBuilder.WriteString(html.EscapeString(tmp)) + formatBuilder.WriteString("\n") + } + return formatBuilder.String() +} + +func (info *FeedInfo) GenerateRSS() string { + var outputBuilder strings.Builder + outputBuilder.WriteString(fmt.Sprintf(feedfmtopen, info.SiteName, info.SiteUrl, info.SiteDesc)) + outputBuilder.WriteString("\n") + for _, item := range info.Items { + outputBuilder.WriteString(fmt.Sprintf( + itemfmt, + item.Title, + item.Url, + item.Url, + item.PubTime.Format("Mon, 2 Jan 2006 15:04:05 MST"), + item.RawText, + )) + outputBuilder.WriteString("\n") + } + outputBuilder.WriteString(feedfmtclose) + return info.format(outputBuilder.String()) +} diff --git a/feed/rss.go b/feed/rss.go deleted file mode 100644 index a088669..0000000 --- a/feed/rss.go +++ /dev/null @@ -1,64 +0,0 @@ -package feed - -import ( - "fmt" - "strings" - "time" - - "golang.org/x/net/html" -) - -const feedfmt = ` - - - %s - %s - %s%s - - ` - -const itemfmt = ` - - Content Title - %s - %s - %s - - %s - - ` - -type RSSBuilder struct { - Info FeedInfo - Items []FeedItem -} - -func GenerateRss(siteUrl, siteTitle, siteDesc string, pageUrls ...string) (string, error) { - var items strings.Builder - var errs strings.Builder - var err error - - for _, u := range pageUrls { - var formattedArticle strings.Builder - var err error - page, err := fetchPage(u) - if err != nil { - continue - } - article, atime, err := parseArticle(page) - if err != nil && article == "" { - errs.WriteString(fmt.Sprintf("error parsing article %s: %s", u, err.Error())) - continue - } - for _, line := range strings.Split(article, "\n") { - formattedArticle.WriteString(fmt.Sprintf("\t\t%s\n", html.EscapeString(line))) - } - if atime != nil { - items.WriteString(fmt.Sprintf(itemfmt, u, u, atime.Format("Mon, 2 Jan 2006 15:04:05 MST"), formattedArticle.String())) - } else { - items.WriteString(fmt.Sprintf(itemfmt, u, u, time.Now().Format("Mon, 2 Jan 2006 15:04:05 MST"), formattedArticle.String())) - } - } - - return fmt.Sprintf(feedfmt, siteTitle, siteUrl, siteDesc, items.String()), err -} diff --git a/ui/html/htmx/feed-output.tmpl.html b/ui/html/htmx/feed-output.tmpl.html new file mode 100644 index 0000000..7fa5506 --- /dev/null +++ b/ui/html/htmx/feed-output.tmpl.html @@ -0,0 +1,7 @@ +{{ range .Feeds }} +
+    
+{{ . }}
+    
+
+{{ end }} diff --git a/ui/html/pages/home.tmpl.html b/ui/html/pages/home.tmpl.html index 2083339..0edc05c 100644 --- a/ui/html/pages/home.tmpl.html +++ b/ui/html/pages/home.tmpl.html @@ -28,8 +28,6 @@

-
- - +
{{end}}