A clean, Markdown-based publishing platform made for writers. Write together, and build a community. https://writefreely.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

209 lines
5.9 KiB

  1. /*
  2. * Copyright © 2018 A Bunch Tell LLC.
  3. *
  4. * This file is part of WriteFreely.
  5. *
  6. * WriteFreely is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Affero General Public License, included
  8. * in the LICENSE file in this source code package.
  9. */
  10. package writefreely
  11. import (
  12. "html/template"
  13. "io"
  14. "io/ioutil"
  15. "net/http"
  16. "os"
  17. "path/filepath"
  18. "strings"
  19. "github.com/dustin/go-humanize"
  20. "github.com/writeas/web-core/l10n"
  21. "github.com/writeas/web-core/log"
  22. "github.com/writeas/writefreely/config"
  23. )
  24. var (
  25. templates = map[string]*template.Template{}
  26. pages = map[string]*template.Template{}
  27. userPages = map[string]*template.Template{}
  28. funcMap = template.FuncMap{
  29. "largeNumFmt": largeNumFmt,
  30. "pluralize": pluralize,
  31. "isRTL": isRTL,
  32. "isLTR": isLTR,
  33. "localstr": localStr,
  34. "localhtml": localHTML,
  35. "tolower": strings.ToLower,
  36. "title": strings.Title,
  37. }
  38. )
  39. const (
  40. templatesDir = "templates"
  41. pagesDir = "pages"
  42. )
  43. func showUserPage(w http.ResponseWriter, name string, obj interface{}) {
  44. if obj == nil {
  45. log.Error("showUserPage: data is nil!")
  46. return
  47. }
  48. if err := userPages[filepath.Join("user", name+".tmpl")].ExecuteTemplate(w, name, obj); err != nil {
  49. log.Error("Error parsing %s: %v", name, err)
  50. }
  51. }
  52. func initTemplate(parentDir, name string) {
  53. if debugging {
  54. log.Info(" " + filepath.Join(parentDir, templatesDir, name+".tmpl"))
  55. }
  56. files := []string{
  57. filepath.Join(parentDir, templatesDir, name+".tmpl"),
  58. filepath.Join(parentDir, templatesDir, "include", "footer.tmpl"),
  59. filepath.Join(parentDir, templatesDir, "base.tmpl"),
  60. filepath.Join(parentDir, templatesDir, "user", "include", "silenced.tmpl"),
  61. }
  62. if name == "collection" || name == "collection-tags" || name == "chorus-collection" {
  63. // These pages list out collection posts, so we also parse templatesDir + "include/posts.tmpl"
  64. files = append(files, filepath.Join(parentDir, templatesDir, "include", "posts.tmpl"))
  65. }
  66. if name == "chorus-collection" || name == "chorus-collection-post" {
  67. files = append(files, filepath.Join(parentDir, templatesDir, "user", "include", "header.tmpl"))
  68. }
  69. if name == "collection" || name == "collection-tags" || name == "collection-post" || name == "post" || name == "chorus-collection" || name == "chorus-collection-post" {
  70. files = append(files, filepath.Join(parentDir, templatesDir, "include", "post-render.tmpl"))
  71. }
  72. templates[name] = template.Must(template.New("").Funcs(funcMap).ParseFiles(files...))
  73. }
  74. func initPage(parentDir, path, key string) {
  75. if debugging {
  76. log.Info(" [%s] %s", key, path)
  77. }
  78. files := []string{
  79. path,
  80. filepath.Join(parentDir, templatesDir, "include", "footer.tmpl"),
  81. filepath.Join(parentDir, templatesDir, "base.tmpl"),
  82. filepath.Join(parentDir, templatesDir, "user", "include", "silenced.tmpl"),
  83. }
  84. if key == "login.tmpl" || key == "landing.tmpl" {
  85. files = append(files, filepath.Join(parentDir, templatesDir, "include", "oauth.tmpl"))
  86. }
  87. pages[key] = template.Must(template.New("").Funcs(funcMap).ParseFiles(files...))
  88. }
  89. func initUserPage(parentDir, path, key string) {
  90. if debugging {
  91. log.Info(" [%s] %s", key, path)
  92. }
  93. userPages[key] = template.Must(template.New(key).Funcs(funcMap).ParseFiles(
  94. path,
  95. filepath.Join(parentDir, templatesDir, "user", "include", "header.tmpl"),
  96. filepath.Join(parentDir, templatesDir, "user", "include", "footer.tmpl"),
  97. filepath.Join(parentDir, templatesDir, "user", "include", "silenced.tmpl"),
  98. ))
  99. }
  100. // InitTemplates loads all template files from the configured parent dir.
  101. func InitTemplates(cfg *config.Config) error {
  102. log.Info("Loading templates...")
  103. tmplFiles, err := ioutil.ReadDir(filepath.Join(cfg.Server.TemplatesParentDir, templatesDir))
  104. if err != nil {
  105. return err
  106. }
  107. for _, f := range tmplFiles {
  108. if !f.IsDir() && !strings.HasPrefix(f.Name(), ".") {
  109. parts := strings.Split(f.Name(), ".")
  110. key := parts[0]
  111. initTemplate(cfg.Server.TemplatesParentDir, key)
  112. }
  113. }
  114. log.Info("Loading pages...")
  115. // Initialize all static pages that use the base template
  116. filepath.Walk(filepath.Join(cfg.Server.PagesParentDir, pagesDir), func(path string, i os.FileInfo, err error) error {
  117. if !i.IsDir() && !strings.HasPrefix(i.Name(), ".") {
  118. key := i.Name()
  119. initPage(cfg.Server.PagesParentDir, path, key)
  120. }
  121. return nil
  122. })
  123. log.Info("Loading user pages...")
  124. // Initialize all user pages that use base templates
  125. filepath.Walk(filepath.Join(cfg.Server.TemplatesParentDir, templatesDir, "user"), func(path string, f os.FileInfo, err error) error {
  126. if !f.IsDir() && !strings.HasPrefix(f.Name(), ".") {
  127. corePath := path
  128. if cfg.Server.TemplatesParentDir != "" {
  129. corePath = corePath[len(cfg.Server.TemplatesParentDir)+1:]
  130. }
  131. parts := strings.Split(corePath, string(filepath.Separator))
  132. key := f.Name()
  133. if len(parts) > 2 {
  134. key = filepath.Join(parts[1], f.Name())
  135. }
  136. initUserPage(cfg.Server.TemplatesParentDir, path, key)
  137. }
  138. return nil
  139. })
  140. return nil
  141. }
  142. // renderPage retrieves the given template and renders it to the given io.Writer.
  143. // If something goes wrong, the error is logged and returned.
  144. func renderPage(w io.Writer, tmpl string, data interface{}) error {
  145. err := pages[tmpl].ExecuteTemplate(w, "base", data)
  146. if err != nil {
  147. log.Error("%v", err)
  148. }
  149. return err
  150. }
  151. func largeNumFmt(n int64) string {
  152. return humanize.Comma(n)
  153. }
  154. func pluralize(singular, plural string, n int64) string {
  155. if n == 1 {
  156. return singular
  157. }
  158. return plural
  159. }
  160. func isRTL(d string) bool {
  161. return d == "rtl"
  162. }
  163. func isLTR(d string) bool {
  164. return d == "ltr" || d == "auto"
  165. }
  166. func localStr(term, lang string) string {
  167. s := l10n.Strings(lang)[term]
  168. if s == "" {
  169. s = l10n.Strings("")[term]
  170. }
  171. return s
  172. }
  173. func localHTML(term, lang string) template.HTML {
  174. s := l10n.Strings(lang)[term]
  175. if s == "" {
  176. s = l10n.Strings("")[term]
  177. }
  178. s = strings.Replace(s, "write.as", "<a href=\"https://writefreely.org\">writefreely</a>", 1)
  179. return template.HTML(s)
  180. }