diff --git a/hyphae/backlinks/backlinks.go b/backlinks/backlinks.go similarity index 95% rename from hyphae/backlinks/backlinks.go rename to backlinks/backlinks.go index 6ed43b0..605d8bd 100644 --- a/hyphae/backlinks/backlinks.go +++ b/backlinks/backlinks.go @@ -9,8 +9,8 @@ import ( "github.com/bouncepaw/mycorrhiza/util" ) -// YieldHyphaBacklinks gets backlinks for the desired hypha, sorts and yields them one by one. -func YieldHyphaBacklinks(hyphaName string) <-chan string { +// yieldHyphaBacklinks gets backlinks for the desired hypha, sorts and yields them one by one. +func yieldHyphaBacklinks(hyphaName string) <-chan string { hyphaName = util.CanonicalName(hyphaName) out := make(chan string) sorted := hyphae.PathographicSort(out) @@ -147,7 +147,7 @@ type backlinkIndexRenaming struct { links []string } -// Apply changes backlink index respective to the operation data +// apply changes backlink index respective to the operation data func (op backlinkIndexRenaming) apply() { for _, link := range op.links { if lSet, exists := backlinkIndex[link]; exists { diff --git a/hyphae/backlinks/hooks.go b/backlinks/hooks.go similarity index 83% rename from hyphae/backlinks/hooks.go rename to backlinks/hooks.go index 9cc5237..1ad7ebe 100644 --- a/hyphae/backlinks/hooks.go +++ b/backlinks/hooks.go @@ -1,10 +1,11 @@ package backlinks import ( - "github.com/bouncepaw/mycomarkup/v3" - "github.com/bouncepaw/mycomarkup/v3/links" - "github.com/bouncepaw/mycomarkup/v3/mycocontext" - "github.com/bouncepaw/mycomarkup/v3/tools" + "github.com/bouncepaw/mycomarkup/v4" + "github.com/bouncepaw/mycomarkup/v4/links" + "github.com/bouncepaw/mycomarkup/v4/mycocontext" + "github.com/bouncepaw/mycomarkup/v4/options" + "github.com/bouncepaw/mycomarkup/v4/tools" "github.com/bouncepaw/mycorrhiza/hyphae" ) @@ -34,7 +35,7 @@ func extractHyphaLinks(h hyphae.Hypha) []string { // extractHyphaLinksFromContent extracts local hypha links from the provided text. func extractHyphaLinksFromContent(hyphaName string, contents string) []string { - ctx, _ := mycocontext.ContextFromStringInput(hyphaName, contents) + ctx, _ := mycocontext.ContextFromStringInput(contents, options.Options{HyphaName: hyphaName}.FillTheRest()) linkVisitor, getLinks := tools.LinkVisitor(ctx) // Ignore the result of BlockTree because we call it for linkVisitor. _ = mycomarkup.BlockTree(ctx, linkVisitor) diff --git a/backlinks/view_backlinks.html b/backlinks/view_backlinks.html new file mode 100644 index 0000000..597fe32 --- /dev/null +++ b/backlinks/view_backlinks.html @@ -0,0 +1,17 @@ +{{define "backlinks to text"}}Backlinks to {{.}}{{end}} +{{define "title"}}{{template "backlinks to text" .HyphaName}}{{end}} +{{define "body"}} +
+
+

{{block "backlinks to link" .HyphaName}}Backlinks to {{beautifulName .}}{{end}}

+

{{block "description" .}}Hyphae which have a link to this hypha, embed it as an image or transclude it are listed below.{{end}}

+ +
+
+{{end}} \ No newline at end of file diff --git a/backlinks/web.go b/backlinks/web.go new file mode 100644 index 0000000..278b936 --- /dev/null +++ b/backlinks/web.go @@ -0,0 +1,62 @@ +package backlinks + +import ( + "embed" + "github.com/bouncepaw/mycorrhiza/cfg" + "github.com/bouncepaw/mycorrhiza/util" + "github.com/bouncepaw/mycorrhiza/viewutil" + "github.com/gorilla/mux" + "log" + "net/http" + "text/template" +) + +func InitHandlers(rtr *mux.Router) { + rtr.PathPrefix("/backlinks/").HandlerFunc(handlerBacklinks) + chain = viewutil. + En(viewutil.CopyEnWith(fs, "view_backlinks.html")). + Ru(template.Must(viewutil.CopyRuWith(fs, "view_backlinks.html").Parse(ruTranslation))) +} + +// handlerBacklinks lists all backlinks to a hypha. +func handlerBacklinks(w http.ResponseWriter, rq *http.Request) { + var ( + hyphaName = util.HyphaNameFromRq(rq, "backlinks") + backlinks []string + ) + for b := range yieldHyphaBacklinks(hyphaName) { + backlinks = append(backlinks, b) + } + viewBacklinks(viewutil.MetaFrom(w, rq), hyphaName, backlinks) +} + +var ( + //go:embed *.html + fs embed.FS + ruTranslation = ` +{{define "backlinks to text"}}Обратные ссылки на {{.}}{{end}} +{{define "backlinks to link"}}Обратные ссылки на {{beautifulName .}}{{end}} +{{define "description"}}Ниже перечислены гифы, на которых есть ссылка на эту гифу, трансклюзия этой гифы или эта гифа вставлена как изображение.{{end}} +` + chain viewutil.Chain +) + +type backlinksData struct { + viewutil.BaseData + HyphaName string + Backlinks []string +} + +func viewBacklinks(meta viewutil.Meta, hyphaName string, backlinks []string) { + if err := chain.Get(meta).ExecuteTemplate(meta.W, "page", backlinksData{ + BaseData: viewutil.BaseData{ + Meta: meta, + HeaderLinks: cfg.HeaderLinks, + CommonScripts: cfg.CommonScripts, + }, + HyphaName: hyphaName, + Backlinks: backlinks, + }); err != nil { + log.Println(err) + } +} diff --git a/hyphae/categories/categories.go b/categories/categories.go similarity index 69% rename from hyphae/categories/categories.go rename to categories/categories.go index 4e26fa5..50ab12b 100644 --- a/hyphae/categories/categories.go +++ b/categories/categories.go @@ -1,4 +1,4 @@ -// Package categories provides category management. All operations in this package are mutexed. +// Package categories provides category management. // // As per the long pondering, this is how categories (cats for short) // work in Mycorrhiza: @@ -12,12 +12,19 @@ // cat operations are not mentioned on the recent changes page. // - For cat A, if there are 0 hyphae in the cat, cat A does not // exist. If there are 1 or more hyphae in the cat, cat A exists. +// +// List of things to do with categories later: +// +// - Forbid / in cat names. +// - Rename categories. +// - Delete categories. +// - Bind hyphae. package categories import "sync" -// List returns names of all categories. -func List() (categoryList []string) { +// listOfCategories returns names of all categories. +func listOfCategories() (categoryList []string) { mutex.RLock() for cat, _ := range categoryToHyphae { categoryList = append(categoryList, cat) @@ -26,8 +33,8 @@ func List() (categoryList []string) { return categoryList } -// WithHypha returns what categories have the given hypha. The hypha name must be canonical. -func WithHypha(hyphaName string) (categoryList []string) { +// categoriesWithHypha returns what categories have the given hypha. The hypha name must be canonical. +func categoriesWithHypha(hyphaName string) (categoryList []string) { mutex.RLock() defer mutex.RUnlock() if node, ok := hyphaToCategories[hyphaName]; ok { @@ -37,8 +44,8 @@ func WithHypha(hyphaName string) (categoryList []string) { } } -// Contents returns what hyphae are in the category. If the returned slice is empty, the category does not exist, and vice versa. The category name must be canonical. -func Contents(catName string) (hyphaList []string) { +// hyphaeInCategory returns what hyphae are in the category. If the returned slice is empty, the category does not exist, and vice versa. The category name must be canonical. +func hyphaeInCategory(catName string) (hyphaList []string) { mutex.RLock() defer mutex.RUnlock() if node, ok := categoryToHyphae[catName]; ok { @@ -50,8 +57,8 @@ func Contents(catName string) (hyphaList []string) { var mutex sync.RWMutex -// AddHyphaToCategory adds the hypha to the category and updates the records on the disk. If the hypha is already in the category, nothing happens. Pass canonical names. -func AddHyphaToCategory(hyphaName, catName string) { +// addHyphaToCategory adds the hypha to the category and updates the records on the disk. If the hypha is already in the category, nothing happens. Pass canonical names. +func addHyphaToCategory(hyphaName, catName string) { mutex.Lock() if node, ok := hyphaToCategories[hyphaName]; ok { node.storeCategory(catName) @@ -68,8 +75,8 @@ func AddHyphaToCategory(hyphaName, catName string) { go saveToDisk() } -// RemoveHyphaFromCategory removes the hypha from the category and updates the records on the disk. If the hypha is not in the category, nothing happens. Pass canonical names. -func RemoveHyphaFromCategory(hyphaName, catName string) { +// removeHyphaFromCategory removes the hypha from the category and updates the records on the disk. If the hypha is not in the category, nothing happens. Pass canonical names. +func removeHyphaFromCategory(hyphaName, catName string) { mutex.Lock() if node, ok := hyphaToCategories[hyphaName]; ok { node.removeCategory(catName) diff --git a/hyphae/categories/files.go b/categories/files.go similarity index 93% rename from hyphae/categories/files.go rename to categories/files.go index fd5c3df..7c51322 100644 --- a/hyphae/categories/files.go +++ b/categories/files.go @@ -12,8 +12,8 @@ import ( var categoryToHyphae = map[string]*categoryNode{} var hyphaToCategories = map[string]*hyphaNode{} -// InitCategories initializes the category system. Call it after the Structure is initialized. This function might terminate the program in case of a bad mood or filesystem faults. -func InitCategories() { +// Init initializes the category system. Call it after the Structure is initialized. This function might terminate the program in case of a bad mood or filesystem faults. +func Init() { var ( record, err = readCategoriesFromDisk() ) diff --git a/web/categories.go b/categories/handlers.go similarity index 79% rename from web/categories.go rename to categories/handlers.go index fc42444..3c30319 100644 --- a/web/categories.go +++ b/categories/handlers.go @@ -1,10 +1,9 @@ -package web +package categories import ( - "github.com/bouncepaw/mycorrhiza/hyphae/categories" "github.com/bouncepaw/mycorrhiza/user" "github.com/bouncepaw/mycorrhiza/util" - "github.com/bouncepaw/mycorrhiza/views" + "github.com/bouncepaw/mycorrhiza/viewutil" "github.com/gorilla/mux" "io" "log" @@ -12,16 +11,18 @@ import ( "strings" ) -func initCategories(r *mux.Router) { +// InitHandlers initializes HTTP handlers for the given router. Call somewhere in package web. +func InitHandlers(r *mux.Router) { r.PathPrefix("/add-to-category").HandlerFunc(handlerAddToCategory).Methods("POST") r.PathPrefix("/remove-from-category").HandlerFunc(handlerRemoveFromCategory).Methods("POST") r.PathPrefix("/category/").HandlerFunc(handlerCategory).Methods("GET") r.PathPrefix("/category").HandlerFunc(handlerListCategory).Methods("GET") + prepareViews() } func handlerListCategory(w http.ResponseWriter, rq *http.Request) { log.Println("Viewing list of categories") - views.CategoryList(views.MetaFrom(w, rq)) + categoryList(viewutil.MetaFrom(w, rq)) } func handlerCategory(w http.ResponseWriter, rq *http.Request) { @@ -32,7 +33,7 @@ func handlerCategory(w http.ResponseWriter, rq *http.Request) { return } log.Println("Viewing category", catName) - views.CategoryPage(views.MetaFrom(w, rq), catName) + categoryPage(viewutil.MetaFrom(w, rq), catName) } func handlerRemoveFromCategory(w http.ResponseWriter, rq *http.Request) { @@ -51,7 +52,8 @@ func handlerRemoveFromCategory(w http.ResponseWriter, rq *http.Request) { http.Redirect(w, rq, redirectTo, http.StatusSeeOther) return } - categories.RemoveHyphaFromCategory(hyphaName, catName) + log.Println(user.FromRequest(rq).Name, "removed", hyphaName, "from", catName) + removeHyphaFromCategory(hyphaName, catName) http.Redirect(w, rq, redirectTo, http.StatusSeeOther) } @@ -71,6 +73,7 @@ func handlerAddToCategory(w http.ResponseWriter, rq *http.Request) { http.Redirect(w, rq, redirectTo, http.StatusSeeOther) return } - categories.AddHyphaToCategory(hyphaName, catName) + log.Println(user.FromRequest(rq).Name, "added", hyphaName, "to", catName) + addHyphaToCategory(hyphaName, catName) http.Redirect(w, rq, redirectTo, http.StatusSeeOther) } diff --git a/categories/view_card.html b/categories/view_card.html new file mode 100644 index 0000000..1f145c2 --- /dev/null +++ b/categories/view_card.html @@ -0,0 +1,35 @@ +{{define "category card"}} + {{$hyphaName := .HyphaName}} + {{$givenPermission := .GivenPermissionToModify}} + +{{end}} \ No newline at end of file diff --git a/categories/view_list.html b/categories/view_list.html new file mode 100644 index 0000000..53754b3 --- /dev/null +++ b/categories/view_list.html @@ -0,0 +1,18 @@ +{{define "category list"}}Category list{{end}} +{{define "title"}}{{template "category list"}}{{end}} +{{define "body"}} +
+

{{template "title"}}

+ {{if len .Categories}} + + {{else}} +

{{block `no categories` .}}This wiki has no categories.{{end}}

+ {{end}} +
+{{end}} \ No newline at end of file diff --git a/categories/view_page.html b/categories/view_page.html new file mode 100644 index 0000000..a250d38 --- /dev/null +++ b/categories/view_page.html @@ -0,0 +1,29 @@ +{{define "category x"}}Category {{. | beautifulName}}{{end}} +{{define "title"}}{{template "category x" .CatName}}{{end}} +{{define "body"}} + {{$catName := .CatName}} +
+

{{block "cat" .}}Category{{end}} {{beautifulName $catName}}

+ {{if len .Hyphae | not}} +

{{block "empty cat" .}}This category is empty{{end}}

+ {{end}} + +
+{{end}} \ No newline at end of file diff --git a/categories/views.go b/categories/views.go new file mode 100644 index 0000000..3401f2d --- /dev/null +++ b/categories/views.go @@ -0,0 +1,104 @@ +package categories + +import ( + "embed" + "github.com/bouncepaw/mycorrhiza/cfg" + "github.com/bouncepaw/mycorrhiza/viewutil" + "log" + "strings" + "text/template" // TODO: Fight +) + +const ruTranslation = ` +{{define "empty cat"}}Эта категория пуста.{{end}} +{{define "add hypha"}}Добавить в категорию{{end}} +{{define "cat"}}Категория{{end}} +{{define "hypha name"}}Имя гифы{{end}} +{{define "categories"}}Категории{{end}} +{{define "placeholder"}}Имя категории...{{end}} +{{define "remove from category title"}}Убрать гифу из этой категории{{end}} +{{define "add to category title"}}Добавить гифу в эту категорию{{end}} +{{define "category list"}}Список категорий{{end}} +{{define "no categories"}}В этой вики нет категорий.{{end}} +{{define "category x"}}Категория {{. | beautifulName}}{{end}} +` + +var ( + //go:embed *.html + fs embed.FS + viewListChain, viewPageChain, viewCardChain viewutil.Chain +) + +func prepareViews() { + m := template.Must + + viewCardChain = viewutil. + En(viewutil.CopyEnWith(fs, "view_card.html")). + Ru(m(viewutil.CopyRuWith(fs, "view_card.html").Parse(ruTranslation))) + viewListChain = viewutil. + En(viewutil.CopyEnWith(fs, "view_list.html")). + Ru(m(viewutil.CopyRuWith(fs, "view_list.html").Parse(ruTranslation))) + viewPageChain = viewutil. + En(viewutil.CopyEnWith(fs, "view_page.html")). + Ru(m(viewutil.CopyRuWith(fs, "view_page.html").Parse(ruTranslation))) +} + +type cardData struct { + HyphaName string + Categories []string + GivenPermissionToModify bool +} + +// CategoryCard is the little sidebar that is shown nearby the hypha view. +func CategoryCard(meta viewutil.Meta, hyphaName string) string { + var buf strings.Builder + err := viewCardChain.Get(meta).ExecuteTemplate(&buf, "category card", cardData{ + hyphaName, + categoriesWithHypha(hyphaName), + meta.U.CanProceed("add-to-category"), + }) + if err != nil { + log.Println(err) + } + return buf.String() +} + +type pageData struct { + viewutil.BaseData + CatName string + Hyphae []string + GivenPermissionToModify bool +} + +func categoryPage(meta viewutil.Meta, catName string) { + if err := viewPageChain.Get(meta).ExecuteTemplate(meta.W, "page", pageData{ + BaseData: viewutil.BaseData{ + Meta: meta, + HeaderLinks: cfg.HeaderLinks, + CommonScripts: cfg.CommonScripts, + }, + CatName: catName, + Hyphae: hyphaeInCategory(catName), + GivenPermissionToModify: meta.U.CanProceed("add-to-category"), + }); err != nil { + log.Println(err) + } +} + +type listData struct { + viewutil.BaseData + Categories []string +} + +func categoryList(meta viewutil.Meta) { + if err := viewListChain.Get(meta).ExecuteTemplate(meta.W, "page", listData{ + BaseData: viewutil.BaseData{ + Meta: meta, + HeaderLinks: cfg.HeaderLinks, + CommonScripts: cfg.CommonScripts, + }, + Categories: listOfCategories(), + }); err != nil { + log.Println(err) + } +} diff --git a/cfg/config.go b/cfg/config.go index c13f00c..4267068 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -193,7 +193,7 @@ func ReadConfigFile(path string) error { TelegramEnabled = (TelegramBotToken != "") && (TelegramBotName != "") // This URL makes much more sense. If no URL is set or the protocol is forgotten, assume HTTP. - if (URL == "") || (strings.Index(URL, ":") == -1) { + if (URL == "") || !strings.Contains(URL, ":") { URL = "http://" + ListenAddr } diff --git a/cfg/header_links.go b/cfg/header_links.go index 3d284aa..fcb51c8 100644 --- a/cfg/header_links.go +++ b/cfg/header_links.go @@ -1,10 +1,10 @@ package cfg -// See https://mycorrhiza.wiki/hypha/configuration/header import ( - "github.com/bouncepaw/mycomarkup/v3" - "github.com/bouncepaw/mycomarkup/v3/blocks" - "github.com/bouncepaw/mycomarkup/v3/mycocontext" + "github.com/bouncepaw/mycomarkup/v4" + "github.com/bouncepaw/mycomarkup/v4/blocks" + "github.com/bouncepaw/mycomarkup/v4/mycocontext" + "github.com/bouncepaw/mycomarkup/v4/options" ) // HeaderLinks is a list off current header links. Feel free to iterate it directly but do not modify it by yourself. Call ParseHeaderLinks if you need to set new header links. @@ -24,7 +24,7 @@ func SetDefaultHeaderLinks() { // ParseHeaderLinks extracts all rocketlinks from the given text and saves them as header links. func ParseHeaderLinks(text string) { HeaderLinks = []HeaderLink{} - ctx, _ := mycocontext.ContextFromStringInput("", text) + ctx, _ := mycocontext.ContextFromStringInput(text, options.Options{}.FillTheRest()) // We call for side-effects _ = mycomarkup.BlockTree(ctx, func(block blocks.Block) { switch launchpad := block.(type) { diff --git a/go.mod b/go.mod index a57cbd3..6d1974e 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/bouncepaw/mycorrhiza go 1.18 require ( - github.com/bouncepaw/mycomarkup/v3 v3.6.3 + github.com/bouncepaw/mycomarkup/v4 v4.0.0 github.com/go-ini/ini v1.63.2 github.com/gorilla/feeds v1.1.1 github.com/gorilla/mux v1.8.0 @@ -22,7 +22,8 @@ require ( // Use this trick to test local Mycomarkup changes, replace the path with yours, // but do not commit the change to the path: -// replace github.com/bouncepaw/mycomarkup/v3 v3.6.3 => "/Users/bouncepaw/GolandProjects/mycomarkup" +// replace github.com/bouncepaw/mycomarkup/v4 v4.0.0 => "/Users/bouncepaw/GolandProjects/mycomarkup" + // Use this utility every time Mycomarkup gets a major update: // https://github.com/marwan-at-work/mod diff --git a/go.sum b/go.sum index 85d030c..8896013 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,9 @@ github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/bouncepaw/mycomarkup/v3 v3.6.3 h1:FQzzCxrHAEFBjPKFF/7R9gamyeU/8Cn+cFZEgngYtjE= -github.com/bouncepaw/mycomarkup/v3 v3.6.3/go.mod h1:BpiGUVsYCgRZCDxF0iIdc08LJokm/Ab36S/Hif0J6D0= +github.com/bouncepaw/mycomarkup/v4 v3.6.2 h1:5zqb12aOw19xg8/0QIvgoA8oEW2doSdWqCbXltPEaPQ= +github.com/bouncepaw/mycomarkup/v4 v3.6.2/go.mod h1:BpiGUVsYCgRZCDxF0iIdc08LJokm/Ab36S/Hif0J6D0= +github.com/bouncepaw/mycomarkup/v4 v4.0.0 h1:qokseZ+otcFuQ5vARdvxKqjlEZFMvsjFJ7YpJ4sUr8c= +github.com/bouncepaw/mycomarkup/v4 v4.0.0/go.mod h1:y0b8U6Xfnh3KfNUpG3QuAXRJwqFPPpmS2kYvLzaf688= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-ini/ini v1.63.2 h1:kwN3umicd2HF3Tgvap4um1ZG52/WyKT9GGdPx0CJk6Y= diff --git a/help/en/top_bar.myco b/help/en/top_bar.myco index 523f684..65214c0 100644 --- a/help/en/top_bar.myco +++ b/help/en/top_bar.myco @@ -15,6 +15,7 @@ On big screens, the top bar is spread onto two lines. ** All hyphae ** Random ** Help +** Categories On small screens, the authorization section and the most-used-links section are hidden behind a menu. Click the button to see them. If your browser does not support JavaScript, they are always shown. @@ -40,9 +41,11 @@ Reload the wiki. ---- -Edit the hypha. You can put any markup there. Rocket links will be used for generating the top bar: +Edit the hypha. You can put any markup there. Only rocket links will be used for generating the top bar: ```myco +This paragraph is unused. + => /recent-changes | Recent changes => Highlights => Philosophy | Our views on life diff --git a/help/help.go b/help/help.go index bc12477..057d3c1 100644 --- a/help/help.go +++ b/help/help.go @@ -5,7 +5,7 @@ import ( "embed" ) -//go:embed en en.myco +//go:embed en en.myco *.html var fs embed.FS // Get determines what help text you need and returns it. The path is a substring from URL, it follows this form: diff --git a/help/view_help.html b/help/view_help.html new file mode 100644 index 0000000..ed426b7 --- /dev/null +++ b/help/view_help.html @@ -0,0 +1,50 @@ +{{define "title"}}Help{{end}} +{{define "body"}} +
+
+
+ {{if .ContentsHTML}} + {{.ContentsHTML}} + {{else}} +

{{block "entry not found" .}}Entry not found{{end}}

+

{{block "entry not found invitation" .}}If you want to write this entry by yourself, consider contributing it directly.{{end}}

+ {{end}} +
+
+ +
+{{end}} + diff --git a/help/web.go b/help/web.go new file mode 100644 index 0000000..a717435 --- /dev/null +++ b/help/web.go @@ -0,0 +1,111 @@ +package help + +// stuff.go is used for meta stuff about the wiki or all hyphae at once. +import ( + "github.com/bouncepaw/mycomarkup/v4" + "github.com/bouncepaw/mycorrhiza/cfg" + "github.com/bouncepaw/mycorrhiza/shroom" + "github.com/bouncepaw/mycorrhiza/viewutil" + "io" + "log" + "net/http" + "strings" + "text/template" + + "github.com/gorilla/mux" + + "github.com/bouncepaw/mycomarkup/v4/mycocontext" +) + +var ( + chain viewutil.Chain + ruTranslation = ` +{{define "title"}}Справка{{end}} +{{define "entry not found"}}Статья не найдена{{end}} +{{define "entry not found invitation"}}Если вы хотите написать эту статью сами, то будем рады вашим правкам в репозитории Миокризы.{{end}} + +{{define "topics"}}Темы справки{{end}} +{{define "main"}}Введение{{end}} +{{define "hypha"}}Гифа{{end}} +{{define "media"}}Медиа{{end}} +{{define "mycomarkup"}}Микоразметка{{end}} +{{define "category"}}Категории{{end}} +{{define "interface"}}Интерфейс{{end}} +{{define "prevnext"}}Пред/след{{end}} +{{define "top_bar"}}Верхняя панель{{end}} +{{define "sibling_hyphae"}}Гифы-сиблинги{{end}} +{{define "special pages"}}Специальные страницы{{end}} +{{define "recent_changes"}}Недавние изменения{{end}} +{{define "feeds"}}Ленты{{end}} +{{define "configuration"}}Конфигурация (для администраторов){{end}} +{{define "config_file"}}Файл конфигурации{{end}} +{{define "lock"}}Замок{{end}} +{{define "whitelist"}}Белый список{{end}} +{{define "telegram"}}Вход через Телеграм{{end}} +` +) + +func InitHandlers(r *mux.Router) { + r.PathPrefix("/help").HandlerFunc(handlerHelp) + chain = viewutil. + En(viewutil.CopyEnWith(fs, "view_help.html")). + Ru(template.Must(viewutil.CopyRuWith(fs, "view_help.html").Parse(ruTranslation))) +} + +// handlerHelp gets the appropriate documentation or tells you where you (personally) have failed. +func handlerHelp(w http.ResponseWriter, rq *http.Request) { + // See the history of this file to resurrect the old algorithm that supported multiple languages + var ( + meta = viewutil.MetaFrom(w, rq) + articlePath = strings.TrimPrefix(strings.TrimPrefix(rq.URL.Path, "/help/"), "/help") + lang = "en" + ) + if articlePath == "" { + articlePath = "en" + } + + if !strings.HasPrefix(articlePath, "en") { + w.WriteHeader(http.StatusNotFound) + _, _ = io.WriteString(w, "404 Not found") + return + } + + content, err := Get(articlePath) + if err != nil && strings.HasPrefix(err.Error(), "open") { + w.WriteHeader(http.StatusNotFound) + viewHelp(meta, lang, "") + return + } + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + viewHelp(meta, lang, err.Error()) + return + } + + // TODO: change for the function that uses byte array when there is such function in mycomarkup. + ctx, _ := mycocontext.ContextFromStringInput(string(content), shroom.MarkupOptions(articlePath)) + ast := mycomarkup.BlockTree(ctx) + result := mycomarkup.BlocksToHTML(ctx, ast) + w.WriteHeader(http.StatusOK) + viewHelp(meta, lang, result) +} + +type helpData struct { + viewutil.BaseData + ContentsHTML string + Lang string +} + +func viewHelp(meta viewutil.Meta, lang, contentsHTML string) { + if err := chain.Get(meta).ExecuteTemplate(meta.W, "page", helpData{ + BaseData: viewutil.BaseData{ + Meta: meta, + HeaderLinks: cfg.HeaderLinks, + CommonScripts: cfg.CommonScripts, + }, + ContentsHTML: contentsHTML, + Lang: lang, + }); err != nil { + log.Println(err) + } +} diff --git a/history/revision.go b/history/revision.go index fd9293a..4817aa1 100644 --- a/history/revision.go +++ b/history/revision.go @@ -175,7 +175,7 @@ func (rev *Revision) hyphaeAffected() (hyphae []string) { filesAffected = rev.filesAffected() ) for _, filename := range filesAffected { - if strings.IndexRune(filename, '.') >= 0 { + if strings.ContainsRune(filename, '.') { dotPos := strings.LastIndexByte(filename, '.') hyphaName := string([]byte(filename)[0:dotPos]) // is it safe? if isNewName(hyphaName) { diff --git a/l18n/en/ui.json b/l18n/en/ui.json index 28a1de6..485429a 100644 --- a/l18n/en/ui.json +++ b/l18n/en/ui.json @@ -3,20 +3,6 @@ "register": "Register", "title_search": "Search by title", "admin_panel": "Admin panel", - - "search_results_title": "Search: {{.query}}", - "search_results_query": "Search results for ‘{{.query}}’", - "search_results_desc": "Every hypha name has been compared with the query. Hyphae that have matched the query are listed below.", - - "backlinks_title": "Backlinks to {{.hypha_name}}", - "backlinks_heading": "Backlinks to {{.hypha_link}}", - "backlinks_desc": "Hyphae which have a link to this hypha, embed it as an image or transclude it are listed below.", - - "list_title": "List of pages", - "list_heading": "List of hyphae", - "list_desc": "This wiki has {{.n}} %s.", - "list_desc+one": "hypha", - "list_desc+other": "hyphae", "edit_link": "Edit text", "logout_link": "Log out", diff --git a/l18n/l18n.go b/l18n/l18n.go index 67b347b..b557de0 100644 --- a/l18n/l18n.go +++ b/l18n/l18n.go @@ -118,7 +118,7 @@ func (t Localizer) GetWithLocale(locale, key string, replacements ...*Replacemen // If the str doesn't have any substitutions, no need to // template.Execute. - if strings.Index(str, "}}") == -1 { + if !strings.Contains(str, "}}") { return str } @@ -145,7 +145,7 @@ func (t Localizer) GetPlural(key string, n int, replacements ...*Replacements) s // As in the original, we skip templating if have nothing to replace // (however, it's strange case for plurals) - if strings.Index(str, "}}") == -1 { + if !strings.Contains(str, "}}") { return str } @@ -164,7 +164,7 @@ func (t Localizer) GetPlural64(key string, n int64, replacements ...*Replacement // As in the original, we skip templating if have nothing to replace // (however, it's strange case for plurals) - if strings.Index(str, "}}") == -1 { + if !strings.Contains(str, "}}") { return str } diff --git a/l18n/ru/ui.json b/l18n/ru/ui.json index 43285ac..83ae6ae 100644 --- a/l18n/ru/ui.json +++ b/l18n/ru/ui.json @@ -3,21 +3,10 @@ "register": "Регистрация", "title_search": "Поиск по названию", "admin_panel": "Администрирование", - - "search_results_title": "Поиск: {{.query}}", - "search_results_query": "Результаты поиска для «{{.query}}»", - "search_results_desc": "Название каждой из существующих гиф сопоставлено с запросом. Подходящие гифы приведены ниже.", - + "backlinks_title": "Обратные ссылки на {{.hypha_name}}", "backlinks_heading": "Обратные ссылки на {{.hypha_link}}", "backlinks_desc": "Ниже перечислены гифы, на которых есть ссылка на эту гифу, трансклюзия этой гифы или эта гифа вставлена как изображение.", - - "list_title": "Список страниц", - "list_heading": "Список гиф", - "list_desc": "В этой вики {{.n}} %s.", - "list_desc+one": "гифа", - "list_desc+few": "гифы", - "list_desc+many": "гиф", "edit_link": "Редактировать", "logout_link": "Выйти", diff --git a/main.go b/main.go index bfa549c..a67ac4c 100644 --- a/main.go +++ b/main.go @@ -5,13 +5,13 @@ package main import ( - "github.com/bouncepaw/mycorrhiza/hyphae/categories" + "github.com/bouncepaw/mycorrhiza/backlinks" + "github.com/bouncepaw/mycorrhiza/categories" "github.com/bouncepaw/mycorrhiza/migration" + "github.com/bouncepaw/mycorrhiza/viewutil" "log" "os" - "github.com/bouncepaw/mycorrhiza/hyphae/backlinks" - "github.com/bouncepaw/mycorrhiza/cfg" "github.com/bouncepaw/mycorrhiza/files" "github.com/bouncepaw/mycorrhiza/history" @@ -41,6 +41,7 @@ func main() { log.Println("Using Git storage at", files.HyphaeDir()) // Init the subsystems: + viewutil.Init() hyphae.Index(files.HyphaeDir()) backlinks.IndexBacklinks() go backlinks.RunBacklinksConveyor() @@ -49,7 +50,7 @@ func main() { history.InitGitRepo() migration.MigrateRocketsMaybe() shroom.SetHeaderLinks() - categories.InitCategories() + categories.Init() // Static files: static.InitFS(files.StaticFiles()) diff --git a/migration/rockets.go b/migration/rockets.go index f68ce97..3895aec 100644 --- a/migration/rockets.go +++ b/migration/rockets.go @@ -4,7 +4,7 @@ package migration import ( - "github.com/bouncepaw/mycomarkup/v3/tools" + "github.com/bouncepaw/mycomarkup/v4/tools" "io" "io/ioutil" "log" diff --git a/misc/handlers.go b/misc/handlers.go new file mode 100644 index 0000000..f15a129 --- /dev/null +++ b/misc/handlers.go @@ -0,0 +1,159 @@ +// Package misc provides miscellaneous informative views. +package misc + +import ( + "github.com/bouncepaw/mycorrhiza/backlinks" + "github.com/bouncepaw/mycorrhiza/cfg" + "github.com/bouncepaw/mycorrhiza/files" + "github.com/bouncepaw/mycorrhiza/hyphae" + "github.com/bouncepaw/mycorrhiza/l18n" + "github.com/bouncepaw/mycorrhiza/shroom" + "github.com/bouncepaw/mycorrhiza/static" + "github.com/bouncepaw/mycorrhiza/user" + "github.com/bouncepaw/mycorrhiza/util" + "github.com/bouncepaw/mycorrhiza/views" + "github.com/bouncepaw/mycorrhiza/viewutil" + "github.com/gorilla/mux" + "io" + "log" + "math/rand" + "mime" + "net/http" +) + +func InitHandlers(rtr *mux.Router) { + rtr.HandleFunc("/robots.txt", handlerRobotsTxt) + rtr.HandleFunc("/static/style.css", handlerStyle) + rtr.PathPrefix("/static/"). + Handler(http.StripPrefix("/static/", http.FileServer(http.FS(static.FS)))) + rtr.HandleFunc("/list", handlerList) + rtr.HandleFunc("/reindex", handlerReindex) + rtr.HandleFunc("/update-header-links", handlerUpdateHeaderLinks) + rtr.HandleFunc("/random", handlerRandom) + rtr.HandleFunc("/about", handlerAbout) + rtr.HandleFunc("/favicon.ico", func(w http.ResponseWriter, rq *http.Request) { + http.Redirect(w, rq, "/static/favicon.ico", http.StatusSeeOther) + }) + rtr.HandleFunc("/title-search/", handlerTitleSearch) + initViews() +} + +// handlerList shows a list of all hyphae in the wiki in random order. +func handlerList(w http.ResponseWriter, rq *http.Request) { + util.PrepareRq(rq) + viewList(viewutil.MetaFrom(w, rq)) +} + +// handlerReindex reindexes all hyphae by checking the wiki storage directory anew. +func handlerReindex(w http.ResponseWriter, rq *http.Request) { + util.PrepareRq(rq) + if ok := user.CanProceed(rq, "reindex"); !ok { + var lc = l18n.FromRequest(rq) + viewutil.HttpErr(viewutil.MetaFrom(w, rq), http.StatusForbidden, cfg.HomeHypha, lc.Get("ui.reindex_no_rights")) + log.Println("Rejected", rq.URL) + return + } + hyphae.ResetCount() + log.Println("Reindexing hyphae in", files.HyphaeDir()) + hyphae.Index(files.HyphaeDir()) + backlinks.IndexBacklinks() + http.Redirect(w, rq, "/", http.StatusSeeOther) +} + +// handlerUpdateHeaderLinks updates header links by reading the configured hypha, if there is any, or resorting to default values. +func handlerUpdateHeaderLinks(w http.ResponseWriter, rq *http.Request) { + util.PrepareRq(rq) + if ok := user.CanProceed(rq, "update-header-links"); !ok { + var lc = l18n.FromRequest(rq) + viewutil.HttpErr(viewutil.MetaFrom(w, rq), http.StatusForbidden, cfg.HomeHypha, lc.Get("ui.header_no_rights")) + log.Println("Rejected", rq.URL) + return + } + shroom.SetHeaderLinks() + http.Redirect(w, rq, "/", http.StatusSeeOther) +} + +// handlerRandom redirects to a random hypha. +func handlerRandom(w http.ResponseWriter, rq *http.Request) { + util.PrepareRq(rq) + var ( + randomHyphaName string + amountOfHyphae = hyphae.Count() + ) + if amountOfHyphae == 0 { + var lc = l18n.FromRequest(rq) + viewutil.HttpErr(viewutil.MetaFrom(w, rq), http.StatusNotFound, cfg.HomeHypha, lc.Get("ui.random_no_hyphae_tip")) + return + } + i := rand.Intn(amountOfHyphae) + for h := range hyphae.YieldExistingHyphae() { + if i == 0 { + randomHyphaName = h.CanonicalName() + } + i-- + } + http.Redirect(w, rq, "/hypha/"+randomHyphaName, http.StatusSeeOther) +} + +// handlerAbout shows a summary of wiki's software. +func handlerAbout(w http.ResponseWriter, rq *http.Request) { + w.Header().Set("Content-Type", "text/html;charset=utf-8") + w.WriteHeader(http.StatusOK) + var ( + lc = l18n.FromRequest(rq) + title = lc.Get("ui.about_title", &l18n.Replacements{"name": cfg.WikiName}) + ) + _, err := io.WriteString(w, views.Base( + viewutil.MetaFrom(w, rq), + title, + views.AboutHTML(lc), + )) + if err != nil { + log.Println(err) + } +} + +var stylesheets = []string{"default.css", "custom.css"} + +func handlerStyle(w http.ResponseWriter, rq *http.Request) { + w.Header().Set("Content-Type", mime.TypeByExtension(".css")) + for _, name := range stylesheets { + file, err := static.FS.Open(name) + if err != nil { + continue + } + _, err = io.Copy(w, file) + if err != nil { + log.Println(err) + } + _ = file.Close() + } +} + +func handlerRobotsTxt(w http.ResponseWriter, rq *http.Request) { + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + + file, err := static.FS.Open("robots.txt") + if err != nil { + return + } + _, err = io.Copy(w, file) + if err != nil { + log.Println() + } + _ = file.Close() +} + +func handlerTitleSearch(w http.ResponseWriter, rq *http.Request) { + util.PrepareRq(rq) + _ = rq.ParseForm() + var ( + query = rq.FormValue("q") + results []string + ) + for hyphaName := range shroom.YieldHyphaNamesContainingString(query) { + results = append(results, hyphaName) + } + w.WriteHeader(http.StatusOK) + viewTitleSearch(viewutil.MetaFrom(w, rq), query, results) +} diff --git a/misc/view_list.html b/misc/view_list.html new file mode 100644 index 0000000..0b99b5b --- /dev/null +++ b/misc/view_list.html @@ -0,0 +1,21 @@ +{{define "list of hyphae"}}List of hyphae{{end}} +{{define "title"}}{{template "list of hyphae"}}{{end}} +{{define "body"}} +
+
+

{{template "list of hyphae"}}

+
    + {{range .Entries}} +
  1. + + {{beautifulName .Name}} + + {{if .Ext}} + {{.Ext}} + {{end}} +
  2. + {{end}} +
+
+
+{{end}} \ No newline at end of file diff --git a/misc/view_title_search.html b/misc/view_title_search.html new file mode 100644 index 0000000..0ce4872 --- /dev/null +++ b/misc/view_title_search.html @@ -0,0 +1,16 @@ +{{define "search:"}}Search: {{.}}{{end}} +{{define "title"}}{{template "search:" .Query}}{{end}} +{{define "body"}} +
+

{{block "search results for" .Query}}Search results for ‘{{.}}’{{end}}

+

{{block "search desc" .}}Every hypha name has been compared with the query. Hyphae that have matched the query are listed below.{{end}}

+ +
+
+{{end}} \ No newline at end of file diff --git a/misc/views.go b/misc/views.go new file mode 100644 index 0000000..e288067 --- /dev/null +++ b/misc/views.go @@ -0,0 +1,95 @@ +package misc + +import ( + "embed" + "github.com/bouncepaw/mycorrhiza/cfg" + "github.com/bouncepaw/mycorrhiza/hyphae" + "github.com/bouncepaw/mycorrhiza/viewutil" + "log" + "path/filepath" + "text/template" +) + +var ( + //go:embed *html + fs embed.FS + chainList, chainTitleSearch viewutil.Chain + ruTranslation = ` +{{define "list of hyphae"}}Список гиф{{end}} +{{define "search:"}}Поиск:{{end}} +{{define "search results for"}}Результаты поиска для «{{.}}»{{end}} +{{define "search desc"}}Название каждой из существующих гиф сопоставлено с запросом. Подходящие гифы приведены ниже.{{end}} +` +) + +func initViews() { + m := template.Must + chainList = viewutil. + En(viewutil.CopyEnWith(fs, "view_list.html")). + Ru(m(viewutil.CopyRuWith(fs, "view_list.html").Parse(ruTranslation))) + chainTitleSearch = viewutil. + En(viewutil.CopyEnWith(fs, "view_title_search.html")). + Ru(m(viewutil.CopyRuWith(fs, "view_title_search.html").Parse(ruTranslation))) +} + +type listDatum struct { + Name string + Ext string +} + +type listData struct { + viewutil.BaseData + Entries []listDatum +} + +func viewList(meta viewutil.Meta) { + // TODO: make this more effective, there are too many loops and vars + var ( + hyphaNames = make(chan string) + sortedHypha = hyphae.PathographicSort(hyphaNames) + data []listDatum + ) + for hypha := range hyphae.YieldExistingHyphae() { + hyphaNames <- hypha.CanonicalName() + } + close(hyphaNames) + for hyphaName := range sortedHypha { + switch h := hyphae.ByName(hyphaName).(type) { + case *hyphae.TextualHypha: + data = append(data, listDatum{h.CanonicalName(), ""}) + case *hyphae.MediaHypha: + data = append(data, listDatum{h.CanonicalName(), filepath.Ext(h.MediaFilePath())[1:]}) + } + } + + if err := chainList.Get(meta).ExecuteTemplate(meta.W, "page", listData{ + BaseData: viewutil.BaseData{ + Meta: meta, + HeaderLinks: cfg.HeaderLinks, + CommonScripts: cfg.CommonScripts, + }, + Entries: data, + }); err != nil { + log.Println(err) + } +} + +type titleSearchData struct { + viewutil.BaseData + Query string + Results []string +} + +func viewTitleSearch(meta viewutil.Meta, query string, results []string) { + if err := chainTitleSearch.Get(meta).ExecuteTemplate(meta.W, "page", titleSearchData{ + BaseData: viewutil.BaseData{ + Meta: meta, + HeaderLinks: cfg.HeaderLinks, + CommonScripts: cfg.CommonScripts, + }, + Query: query, + Results: results, + }); err != nil { + log.Println(err) + } +} diff --git a/shroom/delete.go b/shroom/delete.go index 1227dcb..958262e 100644 --- a/shroom/delete.go +++ b/shroom/delete.go @@ -2,8 +2,7 @@ package shroom import ( "fmt" - "github.com/bouncepaw/mycorrhiza/hyphae/backlinks" - + "github.com/bouncepaw/mycorrhiza/backlinks" "github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/user" diff --git a/shroom/log.go b/shroom/log.go index ecda380..2be9a70 100644 --- a/shroom/log.go +++ b/shroom/log.go @@ -7,9 +7,6 @@ import ( "github.com/bouncepaw/mycorrhiza/user" ) -func rejectDeleteLog(h hyphae.Hypha, u *user.User, errmsg string) { - log.Printf("Reject delete ‘%s’ by @%s: %s\n", h.CanonicalName(), u.Name, errmsg) -} func rejectRenameLog(h hyphae.Hypha, u *user.User, errmsg string) { log.Printf("Reject rename ‘%s’ by @%s: %s\n", h.CanonicalName(), u.Name, errmsg) } diff --git a/shroom/mycomarkup_options.go b/shroom/mycomarkup_options.go new file mode 100644 index 0000000..05595e0 --- /dev/null +++ b/shroom/mycomarkup_options.go @@ -0,0 +1,46 @@ +package shroom + +import ( + "errors" + "github.com/bouncepaw/mycomarkup/v4/options" + "github.com/bouncepaw/mycorrhiza/cfg" + "github.com/bouncepaw/mycorrhiza/hyphae" + "github.com/bouncepaw/mycorrhiza/views" +) + +func MarkupOptions(hyphaName string) options.Options { + return fillMycomarkupOptions(options.Options{ + HyphaName: hyphaName, + WebSiteURL: cfg.URL, + TransclusionSupported: true, + }) +} + +func fillMycomarkupOptions(opts options.Options) options.Options { + opts.HyphaExists = func(hyphaName string) bool { + switch hyphae.ByName(hyphaName).(type) { + case *hyphae.EmptyHypha: + return false + default: + return true + } + } + opts.HyphaHTMLData = func(hyphaName string) (rawText, binaryBlock string, err error) { + switch h := hyphae.ByName(hyphaName).(type) { + case *hyphae.EmptyHypha: + err = errors.New("Hypha " + hyphaName + " does not exist") + case *hyphae.TextualHypha: + rawText, err = FetchTextFile(h) + case *hyphae.MediaHypha: + rawText, err = FetchTextFile(h) + binaryBlock = views.MediaRaw(h) + } + return + } + opts.IterateHyphaNamesWith = func(λ func(string)) { + for h := range hyphae.YieldExistingHyphae() { + λ(h.CanonicalName()) + } + } + return opts.FillTheRest() +} diff --git a/shroom/rename.go b/shroom/rename.go index b232b63..71b4e9c 100644 --- a/shroom/rename.go +++ b/shroom/rename.go @@ -3,7 +3,7 @@ package shroom import ( "errors" "fmt" - "github.com/bouncepaw/mycorrhiza/hyphae/backlinks" + "github.com/bouncepaw/mycorrhiza/backlinks" "regexp" "github.com/bouncepaw/mycorrhiza/history" diff --git a/shroom/search.go b/shroom/search.go index 6f6e902..4387abf 100644 --- a/shroom/search.go +++ b/shroom/search.go @@ -7,7 +7,7 @@ import ( "github.com/bouncepaw/mycorrhiza/util" ) -// YieldHyphaNamesContainingString picks hyphae with have a string in their title, sorts and iterates over them. +// YieldHyphaNamesContainingString picks hyphae with have a string in their title, sorts and iterates over them in alphabetical order. func YieldHyphaNamesContainingString(query string) <-chan string { query = util.CanonicalName(strings.TrimSpace(query)) out := make(chan string) diff --git a/shroom/shroom.go b/shroom/shroom.go index 6269694..469307c 100644 --- a/shroom/shroom.go +++ b/shroom/shroom.go @@ -2,41 +2,3 @@ // // Some of them are wrappers around functions provided by package hyphae. They manage history for you. package shroom - -import ( - "errors" - - "github.com/bouncepaw/mycorrhiza/hyphae" - "github.com/bouncepaw/mycorrhiza/views" - - "github.com/bouncepaw/mycomarkup/v3/globals" -) - -func init() { - // TODO: clean this complete and utter mess - globals.HyphaExists = func(hyphaName string) bool { - switch hyphae.ByName(hyphaName).(type) { - case *hyphae.EmptyHypha: - return false - default: - return true - } - } - globals.HyphaAccess = func(hyphaName string) (rawText, binaryBlock string, err error) { - switch h := hyphae.ByName(hyphaName).(type) { - case *hyphae.EmptyHypha: - err = errors.New("Hypha " + hyphaName + " does not exist") - case *hyphae.TextualHypha: - rawText, err = FetchTextFile(h) - case *hyphae.MediaHypha: - rawText, err = FetchTextFile(h) - binaryBlock = views.MediaRaw(h) - } - return - } - globals.HyphaIterate = func(λ func(string)) { - for h := range hyphae.YieldExistingHyphae() { - λ(h.CanonicalName()) - } - } -} diff --git a/shroom/upload.go b/shroom/upload.go index 74e2ba5..54d34cf 100644 --- a/shroom/upload.go +++ b/shroom/upload.go @@ -4,10 +4,10 @@ import ( "bytes" "errors" "fmt" + "github.com/bouncepaw/mycorrhiza/backlinks" "github.com/bouncepaw/mycorrhiza/files" "github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/hyphae" - "github.com/bouncepaw/mycorrhiza/hyphae/backlinks" "github.com/bouncepaw/mycorrhiza/mimetype" "github.com/bouncepaw/mycorrhiza/user" "io" @@ -97,7 +97,7 @@ func UploadText(h hyphae.Hypha, data []byte, userMessage string, u *user.User) e } // TODO: that []byte(...) part should be removed - if bytes.Compare(data, []byte(oldText)) == 0 { + if bytes.Equal(data, []byte(oldText)) { // No changes! Just like cancel button hop.Abort() return nil @@ -118,7 +118,7 @@ func UploadText(h hyphae.Hypha, data []byte, userMessage string, u *user.User) e } // TODO: that []byte(...) part should be removed - if bytes.Compare(data, []byte(oldText)) == 0 { + if bytes.Equal(data, []byte(oldText)) { // No changes! Just like cancel button hop.Abort() return nil diff --git a/util/util.go b/util/util.go index 66fedd0..9af0361 100644 --- a/util/util.go +++ b/util/util.go @@ -8,7 +8,7 @@ import ( "net/http" "strings" - "github.com/bouncepaw/mycomarkup/v3/util" + "github.com/bouncepaw/mycomarkup/v4/util" "github.com/bouncepaw/mycorrhiza/cfg" ) diff --git a/views/about.go b/views/about.go index e5c2793..7aa29ce 100644 --- a/views/about.go +++ b/views/about.go @@ -47,7 +47,7 @@ const aboutTemplateString = `
  • {{ get .L.Version }} 1.9.0
  • {{ if .Cfg.UseAuth }}
  • {{ get .L.UserCount }} {{ .UserCount }}
  • -
  • {{ get .L.HomePage }} {{ .Cfg.HomeHypha }}
  • +
  • {{ get .L.HomeHypha }} {{ .Cfg.HomeHypha }}
  • {{ get .L.Admins }} {{$cfg := .Cfg}}{{ range $i, $username := .Admins }} {{ if gt $i 0 }}{{ end }} {{ $username }} @@ -71,7 +71,7 @@ var aboutData = struct { "Title": e().en("About %s").ru("О %s"), "Version": e().en("Mycorrhiza Wiki version:").ru("Версия Микоризы:"), "UserCount": e().en("User count:").ru("Число пользователей:"), - "HomePage": e().en("Home page:").ru("Домашняя гифа:"), + "HomeHypha": e().en("Home hypha:").ru("Домашняя гифа:"), "Admins": e().en("Administrators:").ru("Администраторы:"), "NoAuth": e().en("This wiki does not use authorization").ru("На этой вики не используется авторизация"), "AboutHyphae": e().en("See /list for information about hyphae on this wiki.").ru("См. /list, чтобы узнать о гифах в этой вики."), diff --git a/views/admin.go b/views/admin.go index dd89e2b..f0d9993 100644 --- a/views/admin.go +++ b/views/admin.go @@ -2,6 +2,7 @@ package views import ( "github.com/bouncepaw/mycorrhiza/util" + "github.com/bouncepaw/mycorrhiza/viewutil" "html/template" "io" "log" @@ -25,7 +26,7 @@ var ( adminTemplatesRu *template.Template ) -func localizedAdminTemplates(meta Meta) *template.Template { +func localizedAdminTemplates(meta viewutil.Meta) *template.Template { if meta.Lc.Locale == "ru" { return adminTemplatesRu } @@ -59,16 +60,18 @@ func init() { template.Must(adminTemplatesEn.Clone()).Parse(adminTranslationRu)) } -func AdminPanel(meta Meta) { +func AdminPanel(meta viewutil.Meta) { var buf strings.Builder err := localizedAdminTemplates(meta).ExecuteTemplate(&buf, "panel", nil) if err != nil { log.Println(err) } _, err = io.WriteString(meta.W, Base( + meta, templateAsString(localizedAdminTemplates(meta), "panel title"), buf.String(), - meta.Lc, - meta.U, )) + if err != nil { + log.Println(err) + } } diff --git a/views/auth.qtpl b/views/auth.qtpl index 619d2fd..fc94ec5 100644 --- a/views/auth.qtpl +++ b/views/auth.qtpl @@ -1,6 +1,8 @@ {% import "net/http" %} +{% import "sort" %} {% import "github.com/bouncepaw/mycorrhiza/cfg" %} {% import "github.com/bouncepaw/mycorrhiza/l18n" %} +{% import "github.com/bouncepaw/mycorrhiza/user" %} {% func Register(rq *http.Request) %} {% code @@ -148,3 +150,63 @@ Telegram auth widget was requested by Yogurt. As you can see, we don't offer use {% endfunc %} + +{% code +var userListL10n = map[string]l10nEntry{ + "heading": en("List of users").ru("Список пользователей"), + "administrators": en("Administrators").ru("Администраторы"), + "moderators": en("Moderators").ru("Модераторы"), + "editors": en("Editors").ru("Редакторы"), +} +%} + +{% func UserList(lc *l18n.Localizer) %} +
    +
    +{% code +var get = func(key string) string { + return userListL10n[key].get(lc.Locale) +} + +var ( + admins = make([]string, 0) + moderators = make([]string, 0) + editors = make([]string, 0) +) +for u := range user.YieldUsers() { + switch u.Group { + // What if we place the users into sorted slices? + case "admin": + admins = append(admins, u.Name) + case "moderator": + moderators = append(moderators, u.Name) + case "editor", "trusted": + editors = append(editors, u.Name) + } +} +sort.Strings(admins) +sort.Strings(moderators) +sort.Strings(editors) +%} +

    {%s get("heading") %}

    +
    +

    {%s get("administrators") %}

    +
      {% for _, name := range admins %} +
    1. {%s name %}
    2. + {% endfor %}
    +
    +
    +

    {%s get("moderators") %}

    +
      {% for _, name := range moderators %} +
    1. {%s name %}
    2. + {% endfor %}
    +
    +
    +

    {%s get("editors") %}

    +
      {% for _, name := range editors %} +
    1. {%s name %}
    2. + {% endfor %}
    +
    +
    +
    +{% endfunc %} \ No newline at end of file diff --git a/views/auth.qtpl.go b/views/auth.qtpl.go index 8018252..8f54d26 100644 --- a/views/auth.qtpl.go +++ b/views/auth.qtpl.go @@ -8,517 +8,523 @@ package views import "net/http" //line views/auth.qtpl:2 -import "github.com/bouncepaw/mycorrhiza/cfg" +import "sort" //line views/auth.qtpl:3 +import "github.com/bouncepaw/mycorrhiza/cfg" + +//line views/auth.qtpl:4 import "github.com/bouncepaw/mycorrhiza/l18n" //line views/auth.qtpl:5 +import "github.com/bouncepaw/mycorrhiza/user" + +//line views/auth.qtpl:7 import ( qtio422016 "io" qt422016 "github.com/valyala/quicktemplate" ) -//line views/auth.qtpl:5 +//line views/auth.qtpl:7 var ( _ = qtio422016.Copy _ = qt422016.AcquireByteBuffer ) -//line views/auth.qtpl:5 +//line views/auth.qtpl:7 func StreamRegister(qw422016 *qt422016.Writer, rq *http.Request) { -//line views/auth.qtpl:5 +//line views/auth.qtpl:7 qw422016.N().S(` `) -//line views/auth.qtpl:7 +//line views/auth.qtpl:9 lc := l18n.FromRequest(rq) -//line views/auth.qtpl:8 +//line views/auth.qtpl:10 qw422016.N().S(`
    `) -//line views/auth.qtpl:12 +//line views/auth.qtpl:14 if cfg.AllowRegistration { -//line views/auth.qtpl:12 +//line views/auth.qtpl:14 qw422016.N().S(` `) -//line views/auth.qtpl:30 +//line views/auth.qtpl:32 streamtelegramWidget(qw422016, lc) -//line views/auth.qtpl:30 +//line views/auth.qtpl:32 qw422016.N().S(` `) -//line views/auth.qtpl:31 +//line views/auth.qtpl:33 } else if cfg.UseAuth { -//line views/auth.qtpl:31 +//line views/auth.qtpl:33 qw422016.N().S(`

    `) -//line views/auth.qtpl:32 +//line views/auth.qtpl:34 qw422016.E().S(lc.Get("auth.noregister")) -//line views/auth.qtpl:32 +//line views/auth.qtpl:34 qw422016.N().S(`

    ← `) -//line views/auth.qtpl:33 +//line views/auth.qtpl:35 qw422016.E().S(lc.Get("auth.go_back")) -//line views/auth.qtpl:33 +//line views/auth.qtpl:35 qw422016.N().S(`

    `) -//line views/auth.qtpl:34 +//line views/auth.qtpl:36 } else { -//line views/auth.qtpl:34 +//line views/auth.qtpl:36 qw422016.N().S(`

    `) -//line views/auth.qtpl:35 +//line views/auth.qtpl:37 qw422016.E().S(lc.Get("auth.noauth")) -//line views/auth.qtpl:35 +//line views/auth.qtpl:37 qw422016.N().S(`

    ← `) -//line views/auth.qtpl:36 +//line views/auth.qtpl:38 qw422016.E().S(lc.Get("auth.go_back")) -//line views/auth.qtpl:36 +//line views/auth.qtpl:38 qw422016.N().S(`

    `) -//line views/auth.qtpl:37 +//line views/auth.qtpl:39 } -//line views/auth.qtpl:37 +//line views/auth.qtpl:39 qw422016.N().S(`
    `) -//line views/auth.qtpl:41 +//line views/auth.qtpl:43 } -//line views/auth.qtpl:41 +//line views/auth.qtpl:43 func WriteRegister(qq422016 qtio422016.Writer, rq *http.Request) { -//line views/auth.qtpl:41 +//line views/auth.qtpl:43 qw422016 := qt422016.AcquireWriter(qq422016) -//line views/auth.qtpl:41 +//line views/auth.qtpl:43 StreamRegister(qw422016, rq) -//line views/auth.qtpl:41 +//line views/auth.qtpl:43 qt422016.ReleaseWriter(qw422016) -//line views/auth.qtpl:41 +//line views/auth.qtpl:43 } -//line views/auth.qtpl:41 +//line views/auth.qtpl:43 func Register(rq *http.Request) string { -//line views/auth.qtpl:41 +//line views/auth.qtpl:43 qb422016 := qt422016.AcquireByteBuffer() -//line views/auth.qtpl:41 +//line views/auth.qtpl:43 WriteRegister(qb422016, rq) -//line views/auth.qtpl:41 +//line views/auth.qtpl:43 qs422016 := string(qb422016.B) -//line views/auth.qtpl:41 +//line views/auth.qtpl:43 qt422016.ReleaseByteBuffer(qb422016) -//line views/auth.qtpl:41 +//line views/auth.qtpl:43 return qs422016 -//line views/auth.qtpl:41 +//line views/auth.qtpl:43 } -//line views/auth.qtpl:43 +//line views/auth.qtpl:45 func StreamLogin(qw422016 *qt422016.Writer, lc *l18n.Localizer) { -//line views/auth.qtpl:43 +//line views/auth.qtpl:45 qw422016.N().S(`
    `) -//line views/auth.qtpl:47 +//line views/auth.qtpl:49 if cfg.UseAuth { -//line views/auth.qtpl:47 +//line views/auth.qtpl:49 qw422016.N().S(` `) -//line views/auth.qtpl:63 +//line views/auth.qtpl:65 streamtelegramWidget(qw422016, lc) -//line views/auth.qtpl:63 +//line views/auth.qtpl:65 qw422016.N().S(` `) -//line views/auth.qtpl:64 +//line views/auth.qtpl:66 } else { -//line views/auth.qtpl:64 +//line views/auth.qtpl:66 qw422016.N().S(`

    `) -//line views/auth.qtpl:65 +//line views/auth.qtpl:67 qw422016.E().S(lc.Get("auth.noauth")) -//line views/auth.qtpl:65 +//line views/auth.qtpl:67 qw422016.N().S(`

    ← `) -//line views/auth.qtpl:66 +//line views/auth.qtpl:68 qw422016.E().S(lc.Get("auth.go_home")) -//line views/auth.qtpl:66 +//line views/auth.qtpl:68 qw422016.N().S(`

    `) -//line views/auth.qtpl:67 +//line views/auth.qtpl:69 } -//line views/auth.qtpl:67 +//line views/auth.qtpl:69 qw422016.N().S(`
    `) -//line views/auth.qtpl:71 +//line views/auth.qtpl:73 } -//line views/auth.qtpl:71 +//line views/auth.qtpl:73 func WriteLogin(qq422016 qtio422016.Writer, lc *l18n.Localizer) { -//line views/auth.qtpl:71 +//line views/auth.qtpl:73 qw422016 := qt422016.AcquireWriter(qq422016) -//line views/auth.qtpl:71 +//line views/auth.qtpl:73 StreamLogin(qw422016, lc) -//line views/auth.qtpl:71 +//line views/auth.qtpl:73 qt422016.ReleaseWriter(qw422016) -//line views/auth.qtpl:71 +//line views/auth.qtpl:73 } -//line views/auth.qtpl:71 +//line views/auth.qtpl:73 func Login(lc *l18n.Localizer) string { -//line views/auth.qtpl:71 +//line views/auth.qtpl:73 qb422016 := qt422016.AcquireByteBuffer() -//line views/auth.qtpl:71 +//line views/auth.qtpl:73 WriteLogin(qb422016, lc) -//line views/auth.qtpl:71 +//line views/auth.qtpl:73 qs422016 := string(qb422016.B) -//line views/auth.qtpl:71 +//line views/auth.qtpl:73 qt422016.ReleaseByteBuffer(qb422016) -//line views/auth.qtpl:71 +//line views/auth.qtpl:73 return qs422016 -//line views/auth.qtpl:71 +//line views/auth.qtpl:73 } // Telegram auth widget was requested by Yogurt. As you can see, we don't offer user administrators control over it. Of course we don't. -//line views/auth.qtpl:74 +//line views/auth.qtpl:76 func streamtelegramWidget(qw422016 *qt422016.Writer, lc *l18n.Localizer) { -//line views/auth.qtpl:74 +//line views/auth.qtpl:76 qw422016.N().S(` `) -//line views/auth.qtpl:75 +//line views/auth.qtpl:77 if cfg.TelegramEnabled { -//line views/auth.qtpl:75 +//line views/auth.qtpl:77 qw422016.N().S(`

    `) -//line views/auth.qtpl:76 +//line views/auth.qtpl:78 qw422016.E().S(lc.Get("auth.telegram_tip")) -//line views/auth.qtpl:76 +//line views/auth.qtpl:78 qw422016.N().S(`

    `) -//line views/auth.qtpl:78 +//line views/auth.qtpl:80 } -//line views/auth.qtpl:78 +//line views/auth.qtpl:80 qw422016.N().S(` `) -//line views/auth.qtpl:79 +//line views/auth.qtpl:81 } -//line views/auth.qtpl:79 +//line views/auth.qtpl:81 func writetelegramWidget(qq422016 qtio422016.Writer, lc *l18n.Localizer) { -//line views/auth.qtpl:79 +//line views/auth.qtpl:81 qw422016 := qt422016.AcquireWriter(qq422016) -//line views/auth.qtpl:79 +//line views/auth.qtpl:81 streamtelegramWidget(qw422016, lc) -//line views/auth.qtpl:79 +//line views/auth.qtpl:81 qt422016.ReleaseWriter(qw422016) -//line views/auth.qtpl:79 +//line views/auth.qtpl:81 } -//line views/auth.qtpl:79 +//line views/auth.qtpl:81 func telegramWidget(lc *l18n.Localizer) string { -//line views/auth.qtpl:79 +//line views/auth.qtpl:81 qb422016 := qt422016.AcquireByteBuffer() -//line views/auth.qtpl:79 +//line views/auth.qtpl:81 writetelegramWidget(qb422016, lc) -//line views/auth.qtpl:79 +//line views/auth.qtpl:81 qs422016 := string(qb422016.B) -//line views/auth.qtpl:79 +//line views/auth.qtpl:81 qt422016.ReleaseByteBuffer(qb422016) -//line views/auth.qtpl:79 +//line views/auth.qtpl:81 return qs422016 -//line views/auth.qtpl:79 +//line views/auth.qtpl:81 } -//line views/auth.qtpl:81 +//line views/auth.qtpl:83 func StreamLoginError(qw422016 *qt422016.Writer, err string, lc *l18n.Localizer) { -//line views/auth.qtpl:81 +//line views/auth.qtpl:83 qw422016.N().S(`
    `) -//line views/auth.qtpl:85 +//line views/auth.qtpl:87 switch err { -//line views/auth.qtpl:86 +//line views/auth.qtpl:88 case "unknown username": -//line views/auth.qtpl:86 +//line views/auth.qtpl:88 qw422016.N().S(`

    `) -//line views/auth.qtpl:87 +//line views/auth.qtpl:89 qw422016.E().S(lc.Get("auth.error_username")) -//line views/auth.qtpl:87 +//line views/auth.qtpl:89 qw422016.N().S(`

    `) -//line views/auth.qtpl:88 +//line views/auth.qtpl:90 case "wrong password": -//line views/auth.qtpl:88 +//line views/auth.qtpl:90 qw422016.N().S(`

    `) -//line views/auth.qtpl:89 +//line views/auth.qtpl:91 qw422016.E().S(lc.Get("auth.error_password")) -//line views/auth.qtpl:89 +//line views/auth.qtpl:91 qw422016.N().S(`

    `) -//line views/auth.qtpl:90 +//line views/auth.qtpl:92 default: -//line views/auth.qtpl:90 +//line views/auth.qtpl:92 qw422016.N().S(`

    `) -//line views/auth.qtpl:91 +//line views/auth.qtpl:93 qw422016.E().S(err) -//line views/auth.qtpl:91 +//line views/auth.qtpl:93 qw422016.N().S(`

    `) -//line views/auth.qtpl:92 +//line views/auth.qtpl:94 } -//line views/auth.qtpl:92 +//line views/auth.qtpl:94 qw422016.N().S(`

    ← `) -//line views/auth.qtpl:93 +//line views/auth.qtpl:95 qw422016.E().S(lc.Get("auth.try_again")) -//line views/auth.qtpl:93 +//line views/auth.qtpl:95 qw422016.N().S(`

    `) -//line views/auth.qtpl:97 +//line views/auth.qtpl:99 } -//line views/auth.qtpl:97 +//line views/auth.qtpl:99 func WriteLoginError(qq422016 qtio422016.Writer, err string, lc *l18n.Localizer) { -//line views/auth.qtpl:97 +//line views/auth.qtpl:99 qw422016 := qt422016.AcquireWriter(qq422016) -//line views/auth.qtpl:97 +//line views/auth.qtpl:99 StreamLoginError(qw422016, err, lc) -//line views/auth.qtpl:97 +//line views/auth.qtpl:99 qt422016.ReleaseWriter(qw422016) -//line views/auth.qtpl:97 +//line views/auth.qtpl:99 } -//line views/auth.qtpl:97 +//line views/auth.qtpl:99 func LoginError(err string, lc *l18n.Localizer) string { -//line views/auth.qtpl:97 +//line views/auth.qtpl:99 qb422016 := qt422016.AcquireByteBuffer() -//line views/auth.qtpl:97 +//line views/auth.qtpl:99 WriteLoginError(qb422016, err, lc) -//line views/auth.qtpl:97 +//line views/auth.qtpl:99 qs422016 := string(qb422016.B) -//line views/auth.qtpl:97 +//line views/auth.qtpl:99 qt422016.ReleaseByteBuffer(qb422016) -//line views/auth.qtpl:97 +//line views/auth.qtpl:99 return qs422016 -//line views/auth.qtpl:97 +//line views/auth.qtpl:99 } -//line views/auth.qtpl:99 +//line views/auth.qtpl:101 func StreamLogout(qw422016 *qt422016.Writer, can bool, lc *l18n.Localizer) { -//line views/auth.qtpl:99 +//line views/auth.qtpl:101 qw422016.N().S(`
    `) -//line views/auth.qtpl:103 +//line views/auth.qtpl:105 if can { -//line views/auth.qtpl:103 +//line views/auth.qtpl:105 qw422016.N().S(`

    `) -//line views/auth.qtpl:104 +//line views/auth.qtpl:106 qw422016.E().S(lc.Get("auth.logout_header")) -//line views/auth.qtpl:104 +//line views/auth.qtpl:106 qw422016.N().S(`

    `) -//line views/auth.qtpl:107 +//line views/auth.qtpl:109 qw422016.E().S(lc.Get("auth.go_home")) -//line views/auth.qtpl:107 +//line views/auth.qtpl:109 qw422016.N().S(`
    `) -//line views/auth.qtpl:109 +//line views/auth.qtpl:111 } else { -//line views/auth.qtpl:109 +//line views/auth.qtpl:111 qw422016.N().S(`

    `) -//line views/auth.qtpl:110 +//line views/auth.qtpl:112 qw422016.E().S(lc.Get("auth.logout_anon")) -//line views/auth.qtpl:110 +//line views/auth.qtpl:112 qw422016.N().S(`

    `) -//line views/auth.qtpl:111 +//line views/auth.qtpl:113 qw422016.E().S(lc.Get("auth.login_title")) -//line views/auth.qtpl:111 +//line views/auth.qtpl:113 qw422016.N().S(`

    ← `) -//line views/auth.qtpl:112 +//line views/auth.qtpl:114 qw422016.E().S(lc.Get("auth.go_home")) -//line views/auth.qtpl:112 +//line views/auth.qtpl:114 qw422016.N().S(`

    `) -//line views/auth.qtpl:113 +//line views/auth.qtpl:115 } -//line views/auth.qtpl:113 +//line views/auth.qtpl:115 qw422016.N().S(`
    `) -//line views/auth.qtpl:117 +//line views/auth.qtpl:119 } -//line views/auth.qtpl:117 +//line views/auth.qtpl:119 func WriteLogout(qq422016 qtio422016.Writer, can bool, lc *l18n.Localizer) { -//line views/auth.qtpl:117 +//line views/auth.qtpl:119 qw422016 := qt422016.AcquireWriter(qq422016) -//line views/auth.qtpl:117 +//line views/auth.qtpl:119 StreamLogout(qw422016, can, lc) -//line views/auth.qtpl:117 +//line views/auth.qtpl:119 qt422016.ReleaseWriter(qw422016) -//line views/auth.qtpl:117 +//line views/auth.qtpl:119 } -//line views/auth.qtpl:117 +//line views/auth.qtpl:119 func Logout(can bool, lc *l18n.Localizer) string { -//line views/auth.qtpl:117 +//line views/auth.qtpl:119 qb422016 := qt422016.AcquireByteBuffer() -//line views/auth.qtpl:117 +//line views/auth.qtpl:119 WriteLogout(qb422016, can, lc) -//line views/auth.qtpl:117 +//line views/auth.qtpl:119 qs422016 := string(qb422016.B) -//line views/auth.qtpl:117 +//line views/auth.qtpl:119 qt422016.ReleaseByteBuffer(qb422016) -//line views/auth.qtpl:117 +//line views/auth.qtpl:119 return qs422016 -//line views/auth.qtpl:117 +//line views/auth.qtpl:119 } -//line views/auth.qtpl:119 +//line views/auth.qtpl:121 func StreamLock(qw422016 *qt422016.Writer, lc *l18n.Localizer) { -//line views/auth.qtpl:119 +//line views/auth.qtpl:121 qw422016.N().S(` @@ -526,9 +532,9 @@ func StreamLock(qw422016 *qt422016.Writer, lc *l18n.Localizer) { 🔒 `) -//line views/auth.qtpl:125 +//line views/auth.qtpl:127 qw422016.E().S(lc.Get("auth.lock_title")) -//line views/auth.qtpl:125 +//line views/auth.qtpl:127 qw422016.N().S(` @@ -538,68 +544,237 @@ func StreamLock(qw422016 *qt422016.Writer, lc *l18n.Localizer) {

    🔒

    `) -//line views/auth.qtpl:133 +//line views/auth.qtpl:135 qw422016.E().S(lc.Get("auth.lock_title")) -//line views/auth.qtpl:133 +//line views/auth.qtpl:135 qw422016.N().S(`

    `) -//line views/auth.qtpl:145 +//line views/auth.qtpl:147 streamtelegramWidget(qw422016, lc) -//line views/auth.qtpl:145 +//line views/auth.qtpl:147 qw422016.N().S(`
    `) -//line views/auth.qtpl:150 +//line views/auth.qtpl:152 } -//line views/auth.qtpl:150 +//line views/auth.qtpl:152 func WriteLock(qq422016 qtio422016.Writer, lc *l18n.Localizer) { -//line views/auth.qtpl:150 +//line views/auth.qtpl:152 qw422016 := qt422016.AcquireWriter(qq422016) -//line views/auth.qtpl:150 +//line views/auth.qtpl:152 StreamLock(qw422016, lc) -//line views/auth.qtpl:150 +//line views/auth.qtpl:152 qt422016.ReleaseWriter(qw422016) -//line views/auth.qtpl:150 +//line views/auth.qtpl:152 } -//line views/auth.qtpl:150 +//line views/auth.qtpl:152 func Lock(lc *l18n.Localizer) string { -//line views/auth.qtpl:150 +//line views/auth.qtpl:152 qb422016 := qt422016.AcquireByteBuffer() -//line views/auth.qtpl:150 +//line views/auth.qtpl:152 WriteLock(qb422016, lc) -//line views/auth.qtpl:150 +//line views/auth.qtpl:152 qs422016 := string(qb422016.B) -//line views/auth.qtpl:150 +//line views/auth.qtpl:152 qt422016.ReleaseByteBuffer(qb422016) -//line views/auth.qtpl:150 +//line views/auth.qtpl:152 return qs422016 -//line views/auth.qtpl:150 +//line views/auth.qtpl:152 +} + +//line views/auth.qtpl:155 +var userListL10n = map[string]l10nEntry{ + "heading": en("List of users").ru("Список пользователей"), + "administrators": en("Administrators").ru("Администраторы"), + "moderators": en("Moderators").ru("Модераторы"), + "editors": en("Editors").ru("Редакторы"), +} + +//line views/auth.qtpl:163 +func StreamUserList(qw422016 *qt422016.Writer, lc *l18n.Localizer) { +//line views/auth.qtpl:163 + qw422016.N().S(` +
    +
    +`) +//line views/auth.qtpl:167 + var get = func(key string) string { + return userListL10n[key].get(lc.Locale) + } + + var ( + admins = make([]string, 0) + moderators = make([]string, 0) + editors = make([]string, 0) + ) + for u := range user.YieldUsers() { + switch u.Group { + // What if we place the users into sorted slices? + case "admin": + admins = append(admins, u.Name) + case "moderator": + moderators = append(moderators, u.Name) + case "editor", "trusted": + editors = append(editors, u.Name) + } + } + sort.Strings(admins) + sort.Strings(moderators) + sort.Strings(editors) + +//line views/auth.qtpl:190 + qw422016.N().S(` +

    `) +//line views/auth.qtpl:191 + qw422016.E().S(get("heading")) +//line views/auth.qtpl:191 + qw422016.N().S(`

    +
    +

    `) +//line views/auth.qtpl:193 + qw422016.E().S(get("administrators")) +//line views/auth.qtpl:193 + qw422016.N().S(`

    +
      `) +//line views/auth.qtpl:194 + for _, name := range admins { +//line views/auth.qtpl:194 + qw422016.N().S(` +
    1. `) +//line views/auth.qtpl:195 + qw422016.E().S(name) +//line views/auth.qtpl:195 + qw422016.N().S(`
    2. + `) +//line views/auth.qtpl:196 + } +//line views/auth.qtpl:196 + qw422016.N().S(`
    +
    +
    +

    `) +//line views/auth.qtpl:199 + qw422016.E().S(get("moderators")) +//line views/auth.qtpl:199 + qw422016.N().S(`

    +
      `) +//line views/auth.qtpl:200 + for _, name := range moderators { +//line views/auth.qtpl:200 + qw422016.N().S(` +
    1. `) +//line views/auth.qtpl:201 + qw422016.E().S(name) +//line views/auth.qtpl:201 + qw422016.N().S(`
    2. + `) +//line views/auth.qtpl:202 + } +//line views/auth.qtpl:202 + qw422016.N().S(`
    +
    +
    +

    `) +//line views/auth.qtpl:205 + qw422016.E().S(get("editors")) +//line views/auth.qtpl:205 + qw422016.N().S(`

    +
      `) +//line views/auth.qtpl:206 + for _, name := range editors { +//line views/auth.qtpl:206 + qw422016.N().S(` +
    1. `) +//line views/auth.qtpl:207 + qw422016.E().S(name) +//line views/auth.qtpl:207 + qw422016.N().S(`
    2. + `) +//line views/auth.qtpl:208 + } +//line views/auth.qtpl:208 + qw422016.N().S(`
    +
    +
    +
    +`) +//line views/auth.qtpl:212 +} + +//line views/auth.qtpl:212 +func WriteUserList(qq422016 qtio422016.Writer, lc *l18n.Localizer) { +//line views/auth.qtpl:212 + qw422016 := qt422016.AcquireWriter(qq422016) +//line views/auth.qtpl:212 + StreamUserList(qw422016, lc) +//line views/auth.qtpl:212 + qt422016.ReleaseWriter(qw422016) +//line views/auth.qtpl:212 +} + +//line views/auth.qtpl:212 +func UserList(lc *l18n.Localizer) string { +//line views/auth.qtpl:212 + qb422016 := qt422016.AcquireByteBuffer() +//line views/auth.qtpl:212 + WriteUserList(qb422016, lc) +//line views/auth.qtpl:212 + qs422016 := string(qb422016.B) +//line views/auth.qtpl:212 + qt422016.ReleaseByteBuffer(qb422016) +//line views/auth.qtpl:212 + return qs422016 +//line views/auth.qtpl:212 } diff --git a/views/base.go b/views/base.go index df5464c..4b7e50b 100644 --- a/views/base.go +++ b/views/base.go @@ -2,28 +2,11 @@ package views import ( "embed" - "github.com/bouncepaw/mycorrhiza/l18n" - "github.com/bouncepaw/mycorrhiza/user" - "io" - "net/http" + "github.com/bouncepaw/mycorrhiza/viewutil" ) -// Meta is a bundle of common stuffs used by views, templates. -type Meta struct { - Lc *l18n.Localizer - U *user.User - W io.Writer - PageTitle string -} - -// MetaFrom makes a Meta from the given data. You are meant to further modify it. -func MetaFrom(w http.ResponseWriter, rq *http.Request) Meta { - return Meta{ - Lc: l18n.FromRequest(rq), - U: user.FromRequest(rq), - W: w, - } -} - -//go:embed *.html -var fs embed.FS +var ( + //go:embed *.html + fs embed.FS + Base = viewutil.Base +) diff --git a/views/categories.go b/views/categories.go deleted file mode 100644 index f377a73..0000000 --- a/views/categories.go +++ /dev/null @@ -1,125 +0,0 @@ -package views - -import ( - "github.com/bouncepaw/mycorrhiza/hyphae/categories" - "github.com/bouncepaw/mycorrhiza/util" - "html/template" - "io" - "log" - "strings" -) - -const categoriesRu = ` -{{define "empty cat"}}Эта категория пуста.{{end}} -{{define "add hypha"}}Добавить в категорию{{end}} -{{define "cat"}}Категория{{end}} -{{define "hypha name"}}Имя гифы{{end}} -{{define "categories"}}Категории{{end}} -{{define "placeholder"}}Имя категории...{{end}} -{{define "remove from category title"}}Убрать гифу из этой категории{{end}} -{{define "add to category title"}}Добавить гифу в эту категорию{{end}} -{{define "category list heading"}}Список категорий{{end}} -{{define "no categories"}}В этой вики нет категорий.{{end}} -{{define "category x"}}Категория {{. | beautifulName}}{{end}} -` - -var ( - categoryTemplatesEn *template.Template - categoryTemplatesRu *template.Template -) - -func init() { - categoryTemplatesEn = template.Must(template. - New("category"). - Funcs( - template.FuncMap{ - "beautifulName": util.BeautifulName, - }). - ParseFS(fs, "categories.html")) - categoryTemplatesRu = template.Must(template.Must(categoryTemplatesEn.Clone()).Parse(categoriesRu)) -} - -func localizedCatTemplates(meta Meta) *template.Template { - if meta.Lc.Locale == "ru" { - return categoryTemplatesRu - } - return categoryTemplatesEn -} - -func localizedCatTemplateAsString(meta Meta, name string, datum ...interface{}) string { - var buf strings.Builder - var err error - if len(datum) == 1 { - err = localizedCatTemplates(meta).ExecuteTemplate(&buf, name, datum[0]) - } else { - err = localizedCatTemplates(meta).ExecuteTemplate(&buf, name, nil) - } - if err != nil { - log.Println(err) - return "" - } - return buf.String() -} - -func categoryCard(meta Meta, hyphaName string) string { - var buf strings.Builder - err := localizedCatTemplates(meta).ExecuteTemplate(&buf, "category card", struct { - HyphaName string - Categories []string - GivenPermissionToModify bool - }{ - hyphaName, - categories.WithHypha(hyphaName), - meta.U.CanProceed("add-to-category"), - }) - if err != nil { - log.Println(err) - } - return buf.String() -} - -func CategoryPage(meta Meta, catName string) { - var buf strings.Builder - err := localizedCatTemplates(meta).ExecuteTemplate(&buf, "category page", struct { - CatName string - Hyphae []string - GivenPermissionToModify bool - }{ - catName, - categories.Contents(catName), - meta.U.CanProceed("add-to-category"), - }) - if err != nil { - log.Println(err) - } - _, err = io.WriteString(meta.W, Base( - localizedCatTemplateAsString(meta, "category x", catName), - buf.String(), - meta.Lc, - meta.U, - )) - if err != nil { - log.Println(err) - } -} - -func CategoryList(meta Meta) { - var buf strings.Builder - err := localizedCatTemplates(meta).ExecuteTemplate(&buf, "category list", struct { - Categories []string - }{ - categories.List(), - }) - if err != nil { - log.Println(err) - } - _, err = io.WriteString(meta.W, Base( - localizedCatTemplateAsString(meta, "category list heading"), - buf.String(), - meta.Lc, - meta.U, - )) - if err != nil { - log.Println(err) - } -} diff --git a/views/categories.html b/views/categories.html deleted file mode 100644 index 41b2594..0000000 --- a/views/categories.html +++ /dev/null @@ -1,82 +0,0 @@ -{{define "category x"}}Category {{. | beautifulName}}{{end}} - -{{define "category card"}} -{{$hyphaName := .HyphaName}} -{{$givenPermission := .GivenPermissionToModify}} - -{{end}} - -{{define "category page"}} -{{$catName := .CatName}} -
    -

    {{block "cat" .}}Category{{end}} {{beautifulName $catName}}

    - {{if len .Hyphae | not}} -

    {{block "empty cat" .}}This category is empty{{end}}

    - {{end}} -
      - {{range .Hyphae}} -
    • - {{beautifulName .}} -
    • - {{end}} - {{if .GivenPermissionToModify}} -
    • -
      - - - - -
      -
    • - {{end}} -
    -
    -{{end}} - -{{define "category list"}} -
    -

    {{block `category list heading` .}}Category list{{end}}

    - {{if len .Categories}} - - {{else}} -

    {{block `no categories` .}}This wiki has no categories.{{end}}

    - {{end}} -
    -{{end}} \ No newline at end of file diff --git a/views/help.go b/views/help.go deleted file mode 100644 index e637c88..0000000 --- a/views/help.go +++ /dev/null @@ -1,101 +0,0 @@ -package views - -import ( - "fmt" - "github.com/bouncepaw/mycorrhiza/l18n" - "log" - "strings" - "text/template" -) - -var helpTopicsL10n = map[string][]string{ - "topics": {"Help topics", "Темы справки"}, - "main": {"Main", "Введение"}, - "hypha": {"Hypha", "Гифа"}, - "media": {"Media", "Медиа"}, - "mycomarkup": {"Mycomarkup", "Микоразметка"}, - "interface": {"Interface", "Интерфейс"}, - "prevnext": {"Previous/next", "Назад/далее"}, // пред след? - "top_bar": {"Top bar", "Верхняя панель"}, - "sibling_hyphae": {"Sibling hyphae", "Гифы-сиблинги"}, - "special_pages": {"Special pages", "Специальные страницы"}, - "recent_changes": {"Recent changes", "Недавние изменения"}, // так ли? В медиавики свежие правки - "feeds": {"Feeds", "Ленты"}, - "configuration": {"Configuration (for administrators)", "Конфигурация (для администраторов)"}, - "config_file": {"Configuration file", "Файл конфигурации"}, - "lock": {"Lock", "Блокировка"}, // Не Замок ли? - "whitelist": {"Whitelist", "Белый список"}, - "telegram": {"Telegram authentication", "Вход через Телеграм"}, - "category": {"Categories", "Категории"}, -} - -const helpTopicTemplate = `` - -// helpTopicsLinkWrapper wraps in -func helpTopicsLinkWrapper(lang string) func(string, string) string { - return func(path, contents string) string { - return fmt.Sprintf(`%s`, lang, path, contents) - } -} - -func helpTopicsLocalizedTopic(lang string) func(string) string { - pos := 0 - if lang == "ru" { - pos = 1 - } - return func(topic string) string { - return helpTopicsL10n[topic][pos] - } -} - -func helpTopics(lang string, lc *l18n.Localizer) string { - temp, err := template. - New("help topics"). - Funcs(template.FuncMap{ - "a": helpTopicsLinkWrapper(lang), - "l": helpTopicsLocalizedTopic(lc.Locale), - }). - Parse(helpTopicTemplate) - if err != nil { - log.Println(err) - return "" - } - - // TODO: one day, it should write to a better place - var out strings.Builder - _ = temp.Execute(&out, nil) // Shall not fail! - return out.String() -} diff --git a/views/nav.qtpl b/views/nav.qtpl index 097c7f1..32353e8 100644 --- a/views/nav.qtpl +++ b/views/nav.qtpl @@ -1,9 +1,10 @@ {% import "strings" %} {% import "github.com/bouncepaw/mycorrhiza/cfg" %} -{% import "github.com/bouncepaw/mycorrhiza/hyphae/backlinks" %} +{% import "github.com/bouncepaw/mycorrhiza/backlinks" %} {% import "github.com/bouncepaw/mycorrhiza/l18n" %} {% import "github.com/bouncepaw/mycorrhiza/user" %} {% import "github.com/bouncepaw/mycorrhiza/hyphae" %} +{% import "github.com/bouncepaw/mycorrhiza/viewutil" %} {% func hyphaInfoEntry(h hyphae.Hypha, u *user.User, action, displayText string) %} {% if u.CanProceed(action) %} @@ -13,7 +14,7 @@ {% endif %} {% endfunc %} -{% func hyphaInfo(meta Meta, h hyphae.Hypha) %} +{% func hyphaInfo(meta viewutil.Meta, h hyphae.Hypha) %} {% code u := meta.U lc := meta.Lc @@ -52,3 +53,9 @@ {% endif %} {% endfunc %} + +{% func commonScripts() %} +{% for _, scriptPath := range cfg.CommonScripts %} + +{% endfor %} +{% endfunc %} diff --git a/views/nav.qtpl.go b/views/nav.qtpl.go index ee546a7..de41b2d 100644 --- a/views/nav.qtpl.go +++ b/views/nav.qtpl.go @@ -11,7 +11,7 @@ import "strings" import "github.com/bouncepaw/mycorrhiza/cfg" //line views/nav.qtpl:3 -import "github.com/bouncepaw/mycorrhiza/hyphae/backlinks" +import "github.com/bouncepaw/mycorrhiza/backlinks" //line views/nav.qtpl:4 import "github.com/bouncepaw/mycorrhiza/l18n" @@ -22,270 +22,322 @@ import "github.com/bouncepaw/mycorrhiza/user" //line views/nav.qtpl:6 import "github.com/bouncepaw/mycorrhiza/hyphae" -//line views/nav.qtpl:8 +//line views/nav.qtpl:7 +import "github.com/bouncepaw/mycorrhiza/viewutil" + +//line views/nav.qtpl:9 import ( qtio422016 "io" qt422016 "github.com/valyala/quicktemplate" ) -//line views/nav.qtpl:8 +//line views/nav.qtpl:9 var ( _ = qtio422016.Copy _ = qt422016.AcquireByteBuffer ) -//line views/nav.qtpl:8 +//line views/nav.qtpl:9 func streamhyphaInfoEntry(qw422016 *qt422016.Writer, h hyphae.Hypha, u *user.User, action, displayText string) { -//line views/nav.qtpl:8 +//line views/nav.qtpl:9 qw422016.N().S(` `) -//line views/nav.qtpl:9 +//line views/nav.qtpl:10 if u.CanProceed(action) { -//line views/nav.qtpl:9 +//line views/nav.qtpl:10 qw422016.N().S(`
  • `) -//line views/nav.qtpl:11 +//line views/nav.qtpl:12 qw422016.E().S(displayText) -//line views/nav.qtpl:11 +//line views/nav.qtpl:12 qw422016.N().S(`
  • `) -//line views/nav.qtpl:13 +//line views/nav.qtpl:14 } -//line views/nav.qtpl:13 +//line views/nav.qtpl:14 qw422016.N().S(` `) -//line views/nav.qtpl:14 +//line views/nav.qtpl:15 } -//line views/nav.qtpl:14 +//line views/nav.qtpl:15 func writehyphaInfoEntry(qq422016 qtio422016.Writer, h hyphae.Hypha, u *user.User, action, displayText string) { -//line views/nav.qtpl:14 +//line views/nav.qtpl:15 qw422016 := qt422016.AcquireWriter(qq422016) -//line views/nav.qtpl:14 +//line views/nav.qtpl:15 streamhyphaInfoEntry(qw422016, h, u, action, displayText) -//line views/nav.qtpl:14 +//line views/nav.qtpl:15 qt422016.ReleaseWriter(qw422016) -//line views/nav.qtpl:14 +//line views/nav.qtpl:15 } -//line views/nav.qtpl:14 +//line views/nav.qtpl:15 func hyphaInfoEntry(h hyphae.Hypha, u *user.User, action, displayText string) string { -//line views/nav.qtpl:14 +//line views/nav.qtpl:15 qb422016 := qt422016.AcquireByteBuffer() -//line views/nav.qtpl:14 +//line views/nav.qtpl:15 writehyphaInfoEntry(qb422016, h, u, action, displayText) -//line views/nav.qtpl:14 +//line views/nav.qtpl:15 qs422016 := string(qb422016.B) -//line views/nav.qtpl:14 +//line views/nav.qtpl:15 qt422016.ReleaseByteBuffer(qb422016) -//line views/nav.qtpl:14 +//line views/nav.qtpl:15 return qs422016 -//line views/nav.qtpl:14 +//line views/nav.qtpl:15 } -//line views/nav.qtpl:16 -func streamhyphaInfo(qw422016 *qt422016.Writer, meta Meta, h hyphae.Hypha) { -//line views/nav.qtpl:16 +//line views/nav.qtpl:17 +func streamhyphaInfo(qw422016 *qt422016.Writer, meta viewutil.Meta, h hyphae.Hypha) { +//line views/nav.qtpl:17 qw422016.N().S(` `) -//line views/nav.qtpl:18 +//line views/nav.qtpl:19 u := meta.U lc := meta.Lc backs := backlinks.BacklinksCount(h) -//line views/nav.qtpl:21 +//line views/nav.qtpl:22 qw422016.N().S(` `) -//line views/nav.qtpl:32 +//line views/nav.qtpl:33 } -//line views/nav.qtpl:32 -func writehyphaInfo(qq422016 qtio422016.Writer, meta Meta, h hyphae.Hypha) { -//line views/nav.qtpl:32 +//line views/nav.qtpl:33 +func writehyphaInfo(qq422016 qtio422016.Writer, meta viewutil.Meta, h hyphae.Hypha) { +//line views/nav.qtpl:33 qw422016 := qt422016.AcquireWriter(qq422016) -//line views/nav.qtpl:32 +//line views/nav.qtpl:33 streamhyphaInfo(qw422016, meta, h) -//line views/nav.qtpl:32 +//line views/nav.qtpl:33 qt422016.ReleaseWriter(qw422016) -//line views/nav.qtpl:32 +//line views/nav.qtpl:33 } -//line views/nav.qtpl:32 -func hyphaInfo(meta Meta, h hyphae.Hypha) string { -//line views/nav.qtpl:32 +//line views/nav.qtpl:33 +func hyphaInfo(meta viewutil.Meta, h hyphae.Hypha) string { +//line views/nav.qtpl:33 qb422016 := qt422016.AcquireByteBuffer() -//line views/nav.qtpl:32 +//line views/nav.qtpl:33 writehyphaInfo(qb422016, meta, h) -//line views/nav.qtpl:32 +//line views/nav.qtpl:33 qs422016 := string(qb422016.B) -//line views/nav.qtpl:32 +//line views/nav.qtpl:33 qt422016.ReleaseByteBuffer(qb422016) -//line views/nav.qtpl:32 +//line views/nav.qtpl:33 return qs422016 -//line views/nav.qtpl:32 +//line views/nav.qtpl:33 } -//line views/nav.qtpl:34 +//line views/nav.qtpl:35 func streamsiblingHyphae(qw422016 *qt422016.Writer, siblings string, lc *l18n.Localizer) { -//line views/nav.qtpl:34 +//line views/nav.qtpl:35 qw422016.N().S(` `) -//line views/nav.qtpl:35 +//line views/nav.qtpl:36 if cfg.UseSiblingHyphaeSidebar { -//line views/nav.qtpl:35 +//line views/nav.qtpl:36 qw422016.N().S(` `) -//line views/nav.qtpl:40 +//line views/nav.qtpl:41 } -//line views/nav.qtpl:40 +//line views/nav.qtpl:41 qw422016.N().S(` `) -//line views/nav.qtpl:41 +//line views/nav.qtpl:42 } -//line views/nav.qtpl:41 +//line views/nav.qtpl:42 func writesiblingHyphae(qq422016 qtio422016.Writer, siblings string, lc *l18n.Localizer) { -//line views/nav.qtpl:41 +//line views/nav.qtpl:42 qw422016 := qt422016.AcquireWriter(qq422016) -//line views/nav.qtpl:41 +//line views/nav.qtpl:42 streamsiblingHyphae(qw422016, siblings, lc) -//line views/nav.qtpl:41 +//line views/nav.qtpl:42 qt422016.ReleaseWriter(qw422016) -//line views/nav.qtpl:41 +//line views/nav.qtpl:42 } -//line views/nav.qtpl:41 +//line views/nav.qtpl:42 func siblingHyphae(siblings string, lc *l18n.Localizer) string { -//line views/nav.qtpl:41 +//line views/nav.qtpl:42 qb422016 := qt422016.AcquireByteBuffer() -//line views/nav.qtpl:41 +//line views/nav.qtpl:42 writesiblingHyphae(qb422016, siblings, lc) -//line views/nav.qtpl:41 +//line views/nav.qtpl:42 qs422016 := string(qb422016.B) -//line views/nav.qtpl:41 +//line views/nav.qtpl:42 qt422016.ReleaseByteBuffer(qb422016) -//line views/nav.qtpl:41 +//line views/nav.qtpl:42 return qs422016 -//line views/nav.qtpl:41 +//line views/nav.qtpl:42 } -//line views/nav.qtpl:43 +//line views/nav.qtpl:44 func StreamSubhyphae(qw422016 *qt422016.Writer, subhyphae string, lc *l18n.Localizer) { -//line views/nav.qtpl:43 +//line views/nav.qtpl:44 qw422016.N().S(` `) -//line views/nav.qtpl:44 +//line views/nav.qtpl:45 if strings.TrimSpace(subhyphae) != "" { -//line views/nav.qtpl:44 +//line views/nav.qtpl:45 qw422016.N().S(`

    `) -//line views/nav.qtpl:46 +//line views/nav.qtpl:47 qw422016.E().S(lc.Get("ui.subhyphae")) -//line views/nav.qtpl:46 +//line views/nav.qtpl:47 qw422016.N().S(`

    `) -//line views/nav.qtpl:53 +//line views/nav.qtpl:54 } -//line views/nav.qtpl:53 +//line views/nav.qtpl:54 qw422016.N().S(` `) -//line views/nav.qtpl:54 +//line views/nav.qtpl:55 } -//line views/nav.qtpl:54 +//line views/nav.qtpl:55 func WriteSubhyphae(qq422016 qtio422016.Writer, subhyphae string, lc *l18n.Localizer) { -//line views/nav.qtpl:54 +//line views/nav.qtpl:55 qw422016 := qt422016.AcquireWriter(qq422016) -//line views/nav.qtpl:54 +//line views/nav.qtpl:55 StreamSubhyphae(qw422016, subhyphae, lc) -//line views/nav.qtpl:54 +//line views/nav.qtpl:55 qt422016.ReleaseWriter(qw422016) -//line views/nav.qtpl:54 +//line views/nav.qtpl:55 } -//line views/nav.qtpl:54 +//line views/nav.qtpl:55 func Subhyphae(subhyphae string, lc *l18n.Localizer) string { -//line views/nav.qtpl:54 +//line views/nav.qtpl:55 qb422016 := qt422016.AcquireByteBuffer() -//line views/nav.qtpl:54 +//line views/nav.qtpl:55 WriteSubhyphae(qb422016, subhyphae, lc) -//line views/nav.qtpl:54 +//line views/nav.qtpl:55 qs422016 := string(qb422016.B) -//line views/nav.qtpl:54 +//line views/nav.qtpl:55 qt422016.ReleaseByteBuffer(qb422016) -//line views/nav.qtpl:54 +//line views/nav.qtpl:55 return qs422016 -//line views/nav.qtpl:54 +//line views/nav.qtpl:55 +} + +//line views/nav.qtpl:57 +func streamcommonScripts(qw422016 *qt422016.Writer) { +//line views/nav.qtpl:57 + qw422016.N().S(` +`) +//line views/nav.qtpl:58 + for _, scriptPath := range cfg.CommonScripts { +//line views/nav.qtpl:58 + qw422016.N().S(` + +`) +//line views/nav.qtpl:60 + } +//line views/nav.qtpl:60 + qw422016.N().S(` +`) +//line views/nav.qtpl:61 +} + +//line views/nav.qtpl:61 +func writecommonScripts(qq422016 qtio422016.Writer) { +//line views/nav.qtpl:61 + qw422016 := qt422016.AcquireWriter(qq422016) +//line views/nav.qtpl:61 + streamcommonScripts(qw422016) +//line views/nav.qtpl:61 + qt422016.ReleaseWriter(qw422016) +//line views/nav.qtpl:61 +} + +//line views/nav.qtpl:61 +func commonScripts() string { +//line views/nav.qtpl:61 + qb422016 := qt422016.AcquireByteBuffer() +//line views/nav.qtpl:61 + writecommonScripts(qb422016) +//line views/nav.qtpl:61 + qs422016 := string(qb422016.B) +//line views/nav.qtpl:61 + qt422016.ReleaseByteBuffer(qb422016) +//line views/nav.qtpl:61 + return qs422016 +//line views/nav.qtpl:61 } diff --git a/views/readers.qtpl b/views/readers.qtpl index 116f0e9..76de41e 100644 --- a/views/readers.qtpl +++ b/views/readers.qtpl @@ -5,11 +5,13 @@ {% import "github.com/bouncepaw/mycorrhiza/cfg" %} {% import "github.com/bouncepaw/mycorrhiza/hyphae" %} +{% import "github.com/bouncepaw/mycorrhiza/categories" %} {% import "github.com/bouncepaw/mycorrhiza/l18n" %} {% import "github.com/bouncepaw/mycorrhiza/mimetype" %} {% import "github.com/bouncepaw/mycorrhiza/tree" %} {% import "github.com/bouncepaw/mycorrhiza/user" %} {% import "github.com/bouncepaw/mycorrhiza/util" %} +{% import "github.com/bouncepaw/mycorrhiza/viewutil" %} {% func MediaMenu(rq *http.Request, h hyphae.Hypha, u *user.User) %} {% code @@ -85,7 +87,7 @@ If `contents` == "", a helpful message is shown instead. If you rename .prevnext, change the docs too. -{% func Hypha(meta Meta, h hyphae.Hypha, contents string) %} +{% func Hypha(meta viewutil.Meta, h hyphae.Hypha, contents string) %} {% code siblings, subhyphae, prevHyphaName, nextHyphaName := tree.Tree(h.CanonicalName()) lc := meta.Lc @@ -131,7 +133,7 @@ If you rename .prevnext, change the docs too. {%= hyphaInfo(meta, h) %} -{%s= categoryCard(meta, h.CanonicalName()) %} +{%s= categories.CategoryCard(meta, h.CanonicalName()) %} {%= siblingHyphae(siblings, meta.Lc) %}
    {%= viewScripts() %} diff --git a/views/readers.qtpl.go b/views/readers.qtpl.go index 06ffd59..fac57cb 100644 --- a/views/readers.qtpl.go +++ b/views/readers.qtpl.go @@ -23,613 +23,619 @@ import "github.com/bouncepaw/mycorrhiza/cfg" import "github.com/bouncepaw/mycorrhiza/hyphae" //line views/readers.qtpl:8 -import "github.com/bouncepaw/mycorrhiza/l18n" +import "github.com/bouncepaw/mycorrhiza/categories" //line views/readers.qtpl:9 -import "github.com/bouncepaw/mycorrhiza/mimetype" +import "github.com/bouncepaw/mycorrhiza/l18n" //line views/readers.qtpl:10 -import "github.com/bouncepaw/mycorrhiza/tree" +import "github.com/bouncepaw/mycorrhiza/mimetype" //line views/readers.qtpl:11 -import "github.com/bouncepaw/mycorrhiza/user" +import "github.com/bouncepaw/mycorrhiza/tree" //line views/readers.qtpl:12 +import "github.com/bouncepaw/mycorrhiza/user" + +//line views/readers.qtpl:13 import "github.com/bouncepaw/mycorrhiza/util" //line views/readers.qtpl:14 +import "github.com/bouncepaw/mycorrhiza/viewutil" + +//line views/readers.qtpl:16 import ( qtio422016 "io" qt422016 "github.com/valyala/quicktemplate" ) -//line views/readers.qtpl:14 +//line views/readers.qtpl:16 var ( _ = qtio422016.Copy _ = qt422016.AcquireByteBuffer ) -//line views/readers.qtpl:14 +//line views/readers.qtpl:16 func StreamMediaMenu(qw422016 *qt422016.Writer, rq *http.Request, h hyphae.Hypha, u *user.User) { -//line views/readers.qtpl:14 +//line views/readers.qtpl:16 qw422016.N().S(` `) -//line views/readers.qtpl:16 +//line views/readers.qtpl:18 lc := l18n.FromRequest(rq) -//line views/readers.qtpl:17 +//line views/readers.qtpl:19 qw422016.N().S(`

    `) -//line views/readers.qtpl:20 +//line views/readers.qtpl:22 qw422016.N().S(lc.Get("ui.media_title", &l18n.Replacements{"name": beautifulLink(h.CanonicalName())})) -//line views/readers.qtpl:20 +//line views/readers.qtpl:22 qw422016.N().S(`

    `) -//line views/readers.qtpl:21 +//line views/readers.qtpl:23 switch h.(type) { -//line views/readers.qtpl:22 +//line views/readers.qtpl:24 case *hyphae.MediaHypha: -//line views/readers.qtpl:22 +//line views/readers.qtpl:24 qw422016.N().S(`

    `) -//line views/readers.qtpl:23 +//line views/readers.qtpl:25 qw422016.E().S(lc.Get("ui.media_tip")) -//line views/readers.qtpl:23 +//line views/readers.qtpl:25 qw422016.N().S(` `) -//line views/readers.qtpl:23 +//line views/readers.qtpl:25 qw422016.E().S(lc.Get("ui.media_what_is")) -//line views/readers.qtpl:23 +//line views/readers.qtpl:25 qw422016.N().S(`

    `) -//line views/readers.qtpl:24 +//line views/readers.qtpl:26 default: -//line views/readers.qtpl:24 +//line views/readers.qtpl:26 qw422016.N().S(`

    `) -//line views/readers.qtpl:25 +//line views/readers.qtpl:27 qw422016.E().S(lc.Get("ui.media_empty")) -//line views/readers.qtpl:25 +//line views/readers.qtpl:27 qw422016.N().S(` `) -//line views/readers.qtpl:25 +//line views/readers.qtpl:27 qw422016.E().S(lc.Get("ui.media_what_is")) -//line views/readers.qtpl:25 +//line views/readers.qtpl:27 qw422016.N().S(`

    `) -//line views/readers.qtpl:26 +//line views/readers.qtpl:28 } -//line views/readers.qtpl:26 +//line views/readers.qtpl:28 qw422016.N().S(`
    `) -//line views/readers.qtpl:29 +//line views/readers.qtpl:31 switch h := h.(type) { -//line views/readers.qtpl:30 +//line views/readers.qtpl:32 case *hyphae.MediaHypha: -//line views/readers.qtpl:30 +//line views/readers.qtpl:32 qw422016.N().S(` `) -//line views/readers.qtpl:32 +//line views/readers.qtpl:34 mime := mimetype.FromExtension(path.Ext(h.MediaFilePath())) fileinfo, err := os.Stat(h.MediaFilePath()) -//line views/readers.qtpl:33 +//line views/readers.qtpl:35 qw422016.N().S(` `) -//line views/readers.qtpl:34 +//line views/readers.qtpl:36 if err == nil { -//line views/readers.qtpl:34 +//line views/readers.qtpl:36 qw422016.N().S(`
    `) -//line views/readers.qtpl:36 +//line views/readers.qtpl:38 qw422016.E().S(lc.Get("ui.media_stat")) -//line views/readers.qtpl:36 +//line views/readers.qtpl:38 qw422016.N().S(`

    `) -//line views/readers.qtpl:38 +//line views/readers.qtpl:40 qw422016.E().S(lc.Get("ui.media_stat_mime")) -//line views/readers.qtpl:38 +//line views/readers.qtpl:40 qw422016.N().S(` `) -//line views/readers.qtpl:38 +//line views/readers.qtpl:40 qw422016.E().S(mime) -//line views/readers.qtpl:38 +//line views/readers.qtpl:40 qw422016.N().S(`

    `) -//line views/readers.qtpl:40 +//line views/readers.qtpl:42 } -//line views/readers.qtpl:40 +//line views/readers.qtpl:42 qw422016.N().S(` `) -//line views/readers.qtpl:42 +//line views/readers.qtpl:44 if strings.HasPrefix(mime, "image/") { -//line views/readers.qtpl:42 +//line views/readers.qtpl:44 qw422016.N().S(`
    `) -//line views/readers.qtpl:44 +//line views/readers.qtpl:46 qw422016.E().S(lc.Get("ui.media_include")) -//line views/readers.qtpl:44 +//line views/readers.qtpl:46 qw422016.N().S(`
    img { `)
    -//line views/readers.qtpl:46
    +//line views/readers.qtpl:48
     			qw422016.E().S(h.CanonicalName())
    -//line views/readers.qtpl:46
    +//line views/readers.qtpl:48
     			qw422016.N().S(` }
    `) -//line views/readers.qtpl:48 +//line views/readers.qtpl:50 } -//line views/readers.qtpl:48 +//line views/readers.qtpl:50 qw422016.N().S(` `) -//line views/readers.qtpl:49 +//line views/readers.qtpl:51 } -//line views/readers.qtpl:49 +//line views/readers.qtpl:51 qw422016.N().S(` `) -//line views/readers.qtpl:51 +//line views/readers.qtpl:53 if u.CanProceed("upload-binary") { -//line views/readers.qtpl:51 +//line views/readers.qtpl:53 qw422016.N().S(` `) -//line views/readers.qtpl:64 +//line views/readers.qtpl:66 } -//line views/readers.qtpl:64 +//line views/readers.qtpl:66 qw422016.N().S(` `) -//line views/readers.qtpl:67 +//line views/readers.qtpl:69 switch h := h.(type) { -//line views/readers.qtpl:68 +//line views/readers.qtpl:70 case *hyphae.MediaHypha: -//line views/readers.qtpl:68 +//line views/readers.qtpl:70 qw422016.N().S(` `) -//line views/readers.qtpl:69 +//line views/readers.qtpl:71 if u.CanProceed("remove-media") { -//line views/readers.qtpl:69 +//line views/readers.qtpl:71 qw422016.N().S(` `) -//line views/readers.qtpl:77 +//line views/readers.qtpl:79 } -//line views/readers.qtpl:77 +//line views/readers.qtpl:79 qw422016.N().S(` `) -//line views/readers.qtpl:78 +//line views/readers.qtpl:80 } -//line views/readers.qtpl:78 +//line views/readers.qtpl:80 qw422016.N().S(`
    `) -//line views/readers.qtpl:83 +//line views/readers.qtpl:85 } -//line views/readers.qtpl:83 +//line views/readers.qtpl:85 func WriteMediaMenu(qq422016 qtio422016.Writer, rq *http.Request, h hyphae.Hypha, u *user.User) { -//line views/readers.qtpl:83 +//line views/readers.qtpl:85 qw422016 := qt422016.AcquireWriter(qq422016) -//line views/readers.qtpl:83 +//line views/readers.qtpl:85 StreamMediaMenu(qw422016, rq, h, u) -//line views/readers.qtpl:83 +//line views/readers.qtpl:85 qt422016.ReleaseWriter(qw422016) -//line views/readers.qtpl:83 +//line views/readers.qtpl:85 } -//line views/readers.qtpl:83 +//line views/readers.qtpl:85 func MediaMenu(rq *http.Request, h hyphae.Hypha, u *user.User) string { -//line views/readers.qtpl:83 +//line views/readers.qtpl:85 qb422016 := qt422016.AcquireByteBuffer() -//line views/readers.qtpl:83 +//line views/readers.qtpl:85 WriteMediaMenu(qb422016, rq, h, u) -//line views/readers.qtpl:83 +//line views/readers.qtpl:85 qs422016 := string(qb422016.B) -//line views/readers.qtpl:83 +//line views/readers.qtpl:85 qt422016.ReleaseByteBuffer(qb422016) -//line views/readers.qtpl:83 +//line views/readers.qtpl:85 return qs422016 -//line views/readers.qtpl:83 +//line views/readers.qtpl:85 } // If `contents` == "", a helpful message is shown instead. // // If you rename .prevnext, change the docs too. -//line views/readers.qtpl:88 -func StreamHypha(qw422016 *qt422016.Writer, meta Meta, h hyphae.Hypha, contents string) { -//line views/readers.qtpl:88 +//line views/readers.qtpl:90 +func StreamHypha(qw422016 *qt422016.Writer, meta viewutil.Meta, h hyphae.Hypha, contents string) { +//line views/readers.qtpl:90 qw422016.N().S(` `) -//line views/readers.qtpl:90 +//line views/readers.qtpl:92 siblings, subhyphae, prevHyphaName, nextHyphaName := tree.Tree(h.CanonicalName()) lc := meta.Lc -//line views/readers.qtpl:92 +//line views/readers.qtpl:94 qw422016.N().S(`
    `) -//line views/readers.qtpl:96 +//line views/readers.qtpl:98 if meta.U.CanProceed("edit") { -//line views/readers.qtpl:96 +//line views/readers.qtpl:98 qw422016.N().S(` `) -//line views/readers.qtpl:100 +//line views/readers.qtpl:102 } -//line views/readers.qtpl:100 +//line views/readers.qtpl:102 qw422016.N().S(` `) -//line views/readers.qtpl:102 +//line views/readers.qtpl:104 if cfg.UseAuth && util.IsProfileName(h.CanonicalName()) && meta.U.Name == strings.TrimPrefix(h.CanonicalName(), cfg.UserHypha+"/") { -//line views/readers.qtpl:102 +//line views/readers.qtpl:104 qw422016.N().S(` `) -//line views/readers.qtpl:106 +//line views/readers.qtpl:108 if meta.U.Group == "admin" { -//line views/readers.qtpl:106 +//line views/readers.qtpl:108 qw422016.N().S(` `) -//line views/readers.qtpl:110 +//line views/readers.qtpl:112 } -//line views/readers.qtpl:110 +//line views/readers.qtpl:112 qw422016.N().S(` `) -//line views/readers.qtpl:111 +//line views/readers.qtpl:113 } -//line views/readers.qtpl:111 +//line views/readers.qtpl:113 qw422016.N().S(` `) -//line views/readers.qtpl:113 +//line views/readers.qtpl:115 qw422016.N().S(NaviTitle(h)) -//line views/readers.qtpl:113 +//line views/readers.qtpl:115 qw422016.N().S(` `) -//line views/readers.qtpl:114 +//line views/readers.qtpl:116 switch h.(type) { -//line views/readers.qtpl:115 +//line views/readers.qtpl:117 case *hyphae.EmptyHypha: -//line views/readers.qtpl:115 +//line views/readers.qtpl:117 qw422016.N().S(` `) -//line views/readers.qtpl:116 +//line views/readers.qtpl:118 streamnonExistentHyphaNotice(qw422016, h, meta.U, meta.Lc) -//line views/readers.qtpl:116 +//line views/readers.qtpl:118 qw422016.N().S(` `) -//line views/readers.qtpl:117 +//line views/readers.qtpl:119 default: -//line views/readers.qtpl:117 +//line views/readers.qtpl:119 qw422016.N().S(` `) -//line views/readers.qtpl:118 +//line views/readers.qtpl:120 qw422016.N().S(contents) -//line views/readers.qtpl:118 +//line views/readers.qtpl:120 qw422016.N().S(` `) -//line views/readers.qtpl:119 +//line views/readers.qtpl:121 } -//line views/readers.qtpl:119 +//line views/readers.qtpl:121 qw422016.N().S(`
    `) -//line views/readers.qtpl:122 +//line views/readers.qtpl:124 if prevHyphaName != "" { -//line views/readers.qtpl:122 +//line views/readers.qtpl:124 qw422016.N().S(` `) -//line views/readers.qtpl:124 +//line views/readers.qtpl:126 } -//line views/readers.qtpl:124 +//line views/readers.qtpl:126 qw422016.N().S(` `) -//line views/readers.qtpl:125 +//line views/readers.qtpl:127 if nextHyphaName != "" { -//line views/readers.qtpl:125 +//line views/readers.qtpl:127 qw422016.N().S(` `) -//line views/readers.qtpl:127 +//line views/readers.qtpl:129 } -//line views/readers.qtpl:127 +//line views/readers.qtpl:129 qw422016.N().S(`
    `) -//line views/readers.qtpl:129 +//line views/readers.qtpl:131 StreamSubhyphae(qw422016, subhyphae, meta.Lc) -//line views/readers.qtpl:129 +//line views/readers.qtpl:131 qw422016.N().S(`
    `) -//line views/readers.qtpl:131 +//line views/readers.qtpl:133 streamhyphaInfo(qw422016, meta, h) -//line views/readers.qtpl:131 +//line views/readers.qtpl:133 qw422016.N().S(`
    `) -//line views/readers.qtpl:134 - qw422016.N().S(categoryCard(meta, h.CanonicalName())) -//line views/readers.qtpl:134 +//line views/readers.qtpl:136 + qw422016.N().S(categories.CategoryCard(meta, h.CanonicalName())) +//line views/readers.qtpl:136 qw422016.N().S(` `) -//line views/readers.qtpl:135 +//line views/readers.qtpl:137 streamsiblingHyphae(qw422016, siblings, meta.Lc) -//line views/readers.qtpl:135 +//line views/readers.qtpl:137 qw422016.N().S(`
    `) -//line views/readers.qtpl:137 +//line views/readers.qtpl:139 streamviewScripts(qw422016) -//line views/readers.qtpl:137 +//line views/readers.qtpl:139 qw422016.N().S(` `) -//line views/readers.qtpl:138 +//line views/readers.qtpl:140 } -//line views/readers.qtpl:138 -func WriteHypha(qq422016 qtio422016.Writer, meta Meta, h hyphae.Hypha, contents string) { -//line views/readers.qtpl:138 +//line views/readers.qtpl:140 +func WriteHypha(qq422016 qtio422016.Writer, meta viewutil.Meta, h hyphae.Hypha, contents string) { +//line views/readers.qtpl:140 qw422016 := qt422016.AcquireWriter(qq422016) -//line views/readers.qtpl:138 +//line views/readers.qtpl:140 StreamHypha(qw422016, meta, h, contents) -//line views/readers.qtpl:138 +//line views/readers.qtpl:140 qt422016.ReleaseWriter(qw422016) -//line views/readers.qtpl:138 +//line views/readers.qtpl:140 } -//line views/readers.qtpl:138 -func Hypha(meta Meta, h hyphae.Hypha, contents string) string { -//line views/readers.qtpl:138 +//line views/readers.qtpl:140 +func Hypha(meta viewutil.Meta, h hyphae.Hypha, contents string) string { +//line views/readers.qtpl:140 qb422016 := qt422016.AcquireByteBuffer() -//line views/readers.qtpl:138 +//line views/readers.qtpl:140 WriteHypha(qb422016, meta, h, contents) -//line views/readers.qtpl:138 +//line views/readers.qtpl:140 qs422016 := string(qb422016.B) -//line views/readers.qtpl:138 +//line views/readers.qtpl:140 qt422016.ReleaseByteBuffer(qb422016) -//line views/readers.qtpl:138 +//line views/readers.qtpl:140 return qs422016 -//line views/readers.qtpl:138 +//line views/readers.qtpl:140 } -//line views/readers.qtpl:140 +//line views/readers.qtpl:142 func StreamRevision(qw422016 *qt422016.Writer, rq *http.Request, lc *l18n.Localizer, h hyphae.Hypha, contents, revHash string) { -//line views/readers.qtpl:140 +//line views/readers.qtpl:142 qw422016.N().S(`

    `) -//line views/readers.qtpl:144 +//line views/readers.qtpl:146 qw422016.E().S(lc.Get("ui.revision_warning")) -//line views/readers.qtpl:144 +//line views/readers.qtpl:146 qw422016.N().S(` `) -//line views/readers.qtpl:144 +//line views/readers.qtpl:146 qw422016.E().S(lc.Get("ui.revision_link")) -//line views/readers.qtpl:144 +//line views/readers.qtpl:146 qw422016.N().S(`

    `) -//line views/readers.qtpl:145 +//line views/readers.qtpl:147 qw422016.N().S(NaviTitle(h)) -//line views/readers.qtpl:145 +//line views/readers.qtpl:147 qw422016.N().S(` `) -//line views/readers.qtpl:146 +//line views/readers.qtpl:148 qw422016.N().S(contents) -//line views/readers.qtpl:146 +//line views/readers.qtpl:148 qw422016.N().S(`
    `) -//line views/readers.qtpl:150 +//line views/readers.qtpl:152 streamviewScripts(qw422016) -//line views/readers.qtpl:150 +//line views/readers.qtpl:152 qw422016.N().S(` `) -//line views/readers.qtpl:151 +//line views/readers.qtpl:153 } -//line views/readers.qtpl:151 +//line views/readers.qtpl:153 func WriteRevision(qq422016 qtio422016.Writer, rq *http.Request, lc *l18n.Localizer, h hyphae.Hypha, contents, revHash string) { -//line views/readers.qtpl:151 +//line views/readers.qtpl:153 qw422016 := qt422016.AcquireWriter(qq422016) -//line views/readers.qtpl:151 +//line views/readers.qtpl:153 StreamRevision(qw422016, rq, lc, h, contents, revHash) -//line views/readers.qtpl:151 +//line views/readers.qtpl:153 qt422016.ReleaseWriter(qw422016) -//line views/readers.qtpl:151 +//line views/readers.qtpl:153 } -//line views/readers.qtpl:151 +//line views/readers.qtpl:153 func Revision(rq *http.Request, lc *l18n.Localizer, h hyphae.Hypha, contents, revHash string) string { -//line views/readers.qtpl:151 +//line views/readers.qtpl:153 qb422016 := qt422016.AcquireByteBuffer() -//line views/readers.qtpl:151 +//line views/readers.qtpl:153 WriteRevision(qb422016, rq, lc, h, contents, revHash) -//line views/readers.qtpl:151 +//line views/readers.qtpl:153 qs422016 := string(qb422016.B) -//line views/readers.qtpl:151 +//line views/readers.qtpl:153 qt422016.ReleaseByteBuffer(qb422016) -//line views/readers.qtpl:151 +//line views/readers.qtpl:153 return qs422016 -//line views/readers.qtpl:151 +//line views/readers.qtpl:153 } -//line views/readers.qtpl:153 +//line views/readers.qtpl:155 func streamviewScripts(qw422016 *qt422016.Writer) { -//line views/readers.qtpl:153 +//line views/readers.qtpl:155 qw422016.N().S(` `) -//line views/readers.qtpl:154 +//line views/readers.qtpl:156 for _, scriptPath := range cfg.ViewScripts { -//line views/readers.qtpl:154 +//line views/readers.qtpl:156 qw422016.N().S(` `) -//line views/readers.qtpl:156 +//line views/readers.qtpl:158 } -//line views/readers.qtpl:156 +//line views/readers.qtpl:158 qw422016.N().S(` `) -//line views/readers.qtpl:157 +//line views/readers.qtpl:159 } -//line views/readers.qtpl:157 +//line views/readers.qtpl:159 func writeviewScripts(qq422016 qtio422016.Writer) { -//line views/readers.qtpl:157 +//line views/readers.qtpl:159 qw422016 := qt422016.AcquireWriter(qq422016) -//line views/readers.qtpl:157 +//line views/readers.qtpl:159 streamviewScripts(qw422016) -//line views/readers.qtpl:157 +//line views/readers.qtpl:159 qt422016.ReleaseWriter(qw422016) -//line views/readers.qtpl:157 +//line views/readers.qtpl:159 } -//line views/readers.qtpl:157 +//line views/readers.qtpl:159 func viewScripts() string { -//line views/readers.qtpl:157 +//line views/readers.qtpl:159 qb422016 := qt422016.AcquireByteBuffer() -//line views/readers.qtpl:157 +//line views/readers.qtpl:159 writeviewScripts(qb422016) -//line views/readers.qtpl:157 +//line views/readers.qtpl:159 qs422016 := string(qb422016.B) -//line views/readers.qtpl:157 +//line views/readers.qtpl:159 qt422016.ReleaseByteBuffer(qb422016) -//line views/readers.qtpl:157 +//line views/readers.qtpl:159 return qs422016 -//line views/readers.qtpl:157 +//line views/readers.qtpl:159 } diff --git a/views/stuff.qtpl b/views/stuff.qtpl deleted file mode 100644 index 3c164d5..0000000 --- a/views/stuff.qtpl +++ /dev/null @@ -1,180 +0,0 @@ -{% import "fmt" %} -{% import "path/filepath" %} - -{% import "github.com/bouncepaw/mycorrhiza/cfg" %} -{% import "github.com/bouncepaw/mycorrhiza/hyphae" %} -{% import "github.com/bouncepaw/mycorrhiza/user" %} -{% import "github.com/bouncepaw/mycorrhiza/util" %} -{% import "github.com/bouncepaw/mycorrhiza/l18n" %} - -{% func Base(title, body string, lc *l18n.Localizer, u *user.User, headElements ...string) %} - - - - - - {%s title %} - - - - {% for _, el := range headElements %}{%s= el %}{% endfor %} - - -
    - -
    - {%s= body %} - - {%= commonScripts() %} - - - -{% endfunc %} - -{% func TitleSearch(query string, generator func(string) <-chan string, lc *l18n.Localizer) %} -
    -
    -

    {%s lc.Get("ui.search_results_query", &l18n.Replacements{"query": query})%}

    -

    {%s lc.Get("ui.search_results_desc")%}

    -
    -
    -{% endfunc %} - -{% func Backlinks(hyphaName string, generator func(string) <-chan string, lc *l18n.Localizer) %} -
    -
    -

    {%s= lc.Get( - "ui.backlinks_heading", - &l18n.Replacements{ - "hypha_link": fmt.Sprintf( - `%s`, - hyphaName, - util.BeautifulName(hyphaName), - ), - }, - )%}

    -

    {%s lc.Get("ui.backlinks_desc")%}

    - -
    -
    -{% endfunc %} - -{% func Help(content, lang string, lc *l18n.Localizer) %} -
    -
    -
    - {%s= content %} -
    -
    -{%s= helpTopics(lang, lc) %} -
    -{% endfunc %} - -{% func HelpEmptyError(lc *l18n.Localizer) %} -

    {%s lc.Get("help.empty_error_title") %}

    -

    {%s lc.Get("help.empty_error_line_1") %}

    -

    {%s lc.Get("help.empty_error_line_2a") %} {%s lc.Get("help.empty_error_link") %} {%s lc.Get("help.empty_error_line_2b") %}

    -{% endfunc %} - -{% func HyphaList(lc *l18n.Localizer) %} -
    -
    -

    {%s lc.Get("ui.list_heading") %}

    -

    {%s lc.GetPlural("ui.list_desc", hyphae.Count()) %}

    - -
    -
    -{% endfunc %} - -{% func commonScripts() %} -{% for _, scriptPath := range cfg.CommonScripts %} - -{% endfor %} -{% endfunc %} diff --git a/views/stuff.qtpl.go b/views/stuff.qtpl.go deleted file mode 100644 index ee439b0..0000000 --- a/views/stuff.qtpl.go +++ /dev/null @@ -1,667 +0,0 @@ -// Code generated by qtc from "stuff.qtpl". DO NOT EDIT. -// See https://github.com/valyala/quicktemplate for details. - -//line views/stuff.qtpl:1 -package views - -//line views/stuff.qtpl:1 -import "fmt" - -//line views/stuff.qtpl:2 -import "path/filepath" - -//line views/stuff.qtpl:4 -import "github.com/bouncepaw/mycorrhiza/cfg" - -//line views/stuff.qtpl:5 -import "github.com/bouncepaw/mycorrhiza/hyphae" - -//line views/stuff.qtpl:6 -import "github.com/bouncepaw/mycorrhiza/user" - -//line views/stuff.qtpl:7 -import "github.com/bouncepaw/mycorrhiza/util" - -//line views/stuff.qtpl:8 -import "github.com/bouncepaw/mycorrhiza/l18n" - -//line views/stuff.qtpl:10 -import ( - qtio422016 "io" - - qt422016 "github.com/valyala/quicktemplate" -) - -//line views/stuff.qtpl:10 -var ( - _ = qtio422016.Copy - _ = qt422016.AcquireByteBuffer -) - -//line views/stuff.qtpl:10 -func StreamBase(qw422016 *qt422016.Writer, title, body string, lc *l18n.Localizer, u *user.User, headElements ...string) { -//line views/stuff.qtpl:10 - qw422016.N().S(` - - - - - - `) -//line views/stuff.qtpl:16 - qw422016.E().S(title) -//line views/stuff.qtpl:16 - qw422016.N().S(` - - - - `) -//line views/stuff.qtpl:20 - for _, el := range headElements { -//line views/stuff.qtpl:20 - qw422016.N().S(el) -//line views/stuff.qtpl:20 - } -//line views/stuff.qtpl:20 - qw422016.N().S(` - - -
    - -
    - `) -//line views/stuff.qtpl:68 - qw422016.N().S(body) -//line views/stuff.qtpl:68 - qw422016.N().S(` - - `) -//line views/stuff.qtpl:80 - streamcommonScripts(qw422016) -//line views/stuff.qtpl:80 - qw422016.N().S(` - - - -`) -//line views/stuff.qtpl:84 -} - -//line views/stuff.qtpl:84 -func WriteBase(qq422016 qtio422016.Writer, title, body string, lc *l18n.Localizer, u *user.User, headElements ...string) { -//line views/stuff.qtpl:84 - qw422016 := qt422016.AcquireWriter(qq422016) -//line views/stuff.qtpl:84 - StreamBase(qw422016, title, body, lc, u, headElements...) -//line views/stuff.qtpl:84 - qt422016.ReleaseWriter(qw422016) -//line views/stuff.qtpl:84 -} - -//line views/stuff.qtpl:84 -func Base(title, body string, lc *l18n.Localizer, u *user.User, headElements ...string) string { -//line views/stuff.qtpl:84 - qb422016 := qt422016.AcquireByteBuffer() -//line views/stuff.qtpl:84 - WriteBase(qb422016, title, body, lc, u, headElements...) -//line views/stuff.qtpl:84 - qs422016 := string(qb422016.B) -//line views/stuff.qtpl:84 - qt422016.ReleaseByteBuffer(qb422016) -//line views/stuff.qtpl:84 - return qs422016 -//line views/stuff.qtpl:84 -} - -//line views/stuff.qtpl:86 -func StreamTitleSearch(qw422016 *qt422016.Writer, query string, generator func(string) <-chan string, lc *l18n.Localizer) { -//line views/stuff.qtpl:86 - qw422016.N().S(` -
    -
    -

    `) -//line views/stuff.qtpl:89 - qw422016.E().S(lc.Get("ui.search_results_query", &l18n.Replacements{"query": query})) -//line views/stuff.qtpl:89 - qw422016.N().S(`

    -

    `) -//line views/stuff.qtpl:90 - qw422016.E().S(lc.Get("ui.search_results_desc")) -//line views/stuff.qtpl:90 - qw422016.N().S(`

    -
    -
    -`) -//line views/stuff.qtpl:99 -} - -//line views/stuff.qtpl:99 -func WriteTitleSearch(qq422016 qtio422016.Writer, query string, generator func(string) <-chan string, lc *l18n.Localizer) { -//line views/stuff.qtpl:99 - qw422016 := qt422016.AcquireWriter(qq422016) -//line views/stuff.qtpl:99 - StreamTitleSearch(qw422016, query, generator, lc) -//line views/stuff.qtpl:99 - qt422016.ReleaseWriter(qw422016) -//line views/stuff.qtpl:99 -} - -//line views/stuff.qtpl:99 -func TitleSearch(query string, generator func(string) <-chan string, lc *l18n.Localizer) string { -//line views/stuff.qtpl:99 - qb422016 := qt422016.AcquireByteBuffer() -//line views/stuff.qtpl:99 - WriteTitleSearch(qb422016, query, generator, lc) -//line views/stuff.qtpl:99 - qs422016 := string(qb422016.B) -//line views/stuff.qtpl:99 - qt422016.ReleaseByteBuffer(qb422016) -//line views/stuff.qtpl:99 - return qs422016 -//line views/stuff.qtpl:99 -} - -//line views/stuff.qtpl:101 -func StreamBacklinks(qw422016 *qt422016.Writer, hyphaName string, generator func(string) <-chan string, lc *l18n.Localizer) { -//line views/stuff.qtpl:101 - qw422016.N().S(` -
    -
    -

    `) -//line views/stuff.qtpl:104 - qw422016.N().S(lc.Get( - "ui.backlinks_heading", - &l18n.Replacements{ - "hypha_link": fmt.Sprintf( - `%s`, - hyphaName, - util.BeautifulName(hyphaName), - ), - }, - )) -//line views/stuff.qtpl:113 - qw422016.N().S(`

    -

    `) -//line views/stuff.qtpl:114 - qw422016.E().S(lc.Get("ui.backlinks_desc")) -//line views/stuff.qtpl:114 - qw422016.N().S(`

    - -
    -
    -`) -//line views/stuff.qtpl:124 -} - -//line views/stuff.qtpl:124 -func WriteBacklinks(qq422016 qtio422016.Writer, hyphaName string, generator func(string) <-chan string, lc *l18n.Localizer) { -//line views/stuff.qtpl:124 - qw422016 := qt422016.AcquireWriter(qq422016) -//line views/stuff.qtpl:124 - StreamBacklinks(qw422016, hyphaName, generator, lc) -//line views/stuff.qtpl:124 - qt422016.ReleaseWriter(qw422016) -//line views/stuff.qtpl:124 -} - -//line views/stuff.qtpl:124 -func Backlinks(hyphaName string, generator func(string) <-chan string, lc *l18n.Localizer) string { -//line views/stuff.qtpl:124 - qb422016 := qt422016.AcquireByteBuffer() -//line views/stuff.qtpl:124 - WriteBacklinks(qb422016, hyphaName, generator, lc) -//line views/stuff.qtpl:124 - qs422016 := string(qb422016.B) -//line views/stuff.qtpl:124 - qt422016.ReleaseByteBuffer(qb422016) -//line views/stuff.qtpl:124 - return qs422016 -//line views/stuff.qtpl:124 -} - -//line views/stuff.qtpl:126 -func StreamHelp(qw422016 *qt422016.Writer, content, lang string, lc *l18n.Localizer) { -//line views/stuff.qtpl:126 - qw422016.N().S(` -
    -
    -
    - `) -//line views/stuff.qtpl:130 - qw422016.N().S(content) -//line views/stuff.qtpl:130 - qw422016.N().S(` -
    -
    -`) -//line views/stuff.qtpl:133 - qw422016.N().S(helpTopics(lang, lc)) -//line views/stuff.qtpl:133 - qw422016.N().S(` -
    -`) -//line views/stuff.qtpl:135 -} - -//line views/stuff.qtpl:135 -func WriteHelp(qq422016 qtio422016.Writer, content, lang string, lc *l18n.Localizer) { -//line views/stuff.qtpl:135 - qw422016 := qt422016.AcquireWriter(qq422016) -//line views/stuff.qtpl:135 - StreamHelp(qw422016, content, lang, lc) -//line views/stuff.qtpl:135 - qt422016.ReleaseWriter(qw422016) -//line views/stuff.qtpl:135 -} - -//line views/stuff.qtpl:135 -func Help(content, lang string, lc *l18n.Localizer) string { -//line views/stuff.qtpl:135 - qb422016 := qt422016.AcquireByteBuffer() -//line views/stuff.qtpl:135 - WriteHelp(qb422016, content, lang, lc) -//line views/stuff.qtpl:135 - qs422016 := string(qb422016.B) -//line views/stuff.qtpl:135 - qt422016.ReleaseByteBuffer(qb422016) -//line views/stuff.qtpl:135 - return qs422016 -//line views/stuff.qtpl:135 -} - -//line views/stuff.qtpl:137 -func StreamHelpEmptyError(qw422016 *qt422016.Writer, lc *l18n.Localizer) { -//line views/stuff.qtpl:137 - qw422016.N().S(` -

    `) -//line views/stuff.qtpl:138 - qw422016.E().S(lc.Get("help.empty_error_title")) -//line views/stuff.qtpl:138 - qw422016.N().S(`

    -

    `) -//line views/stuff.qtpl:139 - qw422016.E().S(lc.Get("help.empty_error_line_1")) -//line views/stuff.qtpl:139 - qw422016.N().S(`

    -

    `) -//line views/stuff.qtpl:140 - qw422016.E().S(lc.Get("help.empty_error_line_2a")) -//line views/stuff.qtpl:140 - qw422016.N().S(` `) -//line views/stuff.qtpl:140 - qw422016.E().S(lc.Get("help.empty_error_link")) -//line views/stuff.qtpl:140 - qw422016.N().S(` `) -//line views/stuff.qtpl:140 - qw422016.E().S(lc.Get("help.empty_error_line_2b")) -//line views/stuff.qtpl:140 - qw422016.N().S(`

    -`) -//line views/stuff.qtpl:141 -} - -//line views/stuff.qtpl:141 -func WriteHelpEmptyError(qq422016 qtio422016.Writer, lc *l18n.Localizer) { -//line views/stuff.qtpl:141 - qw422016 := qt422016.AcquireWriter(qq422016) -//line views/stuff.qtpl:141 - StreamHelpEmptyError(qw422016, lc) -//line views/stuff.qtpl:141 - qt422016.ReleaseWriter(qw422016) -//line views/stuff.qtpl:141 -} - -//line views/stuff.qtpl:141 -func HelpEmptyError(lc *l18n.Localizer) string { -//line views/stuff.qtpl:141 - qb422016 := qt422016.AcquireByteBuffer() -//line views/stuff.qtpl:141 - WriteHelpEmptyError(qb422016, lc) -//line views/stuff.qtpl:141 - qs422016 := string(qb422016.B) -//line views/stuff.qtpl:141 - qt422016.ReleaseByteBuffer(qb422016) -//line views/stuff.qtpl:141 - return qs422016 -//line views/stuff.qtpl:141 -} - -//line views/stuff.qtpl:143 -func StreamHyphaList(qw422016 *qt422016.Writer, lc *l18n.Localizer) { -//line views/stuff.qtpl:143 - qw422016.N().S(` -
    -
    -

    `) -//line views/stuff.qtpl:146 - qw422016.E().S(lc.Get("ui.list_heading")) -//line views/stuff.qtpl:146 - qw422016.N().S(`

    -

    `) -//line views/stuff.qtpl:147 - qw422016.E().S(lc.GetPlural("ui.list_desc", hyphae.Count())) -//line views/stuff.qtpl:147 - qw422016.N().S(`

    - -
    -
    -`) -//line views/stuff.qtpl:174 -} - -//line views/stuff.qtpl:174 -func WriteHyphaList(qq422016 qtio422016.Writer, lc *l18n.Localizer) { -//line views/stuff.qtpl:174 - qw422016 := qt422016.AcquireWriter(qq422016) -//line views/stuff.qtpl:174 - StreamHyphaList(qw422016, lc) -//line views/stuff.qtpl:174 - qt422016.ReleaseWriter(qw422016) -//line views/stuff.qtpl:174 -} - -//line views/stuff.qtpl:174 -func HyphaList(lc *l18n.Localizer) string { -//line views/stuff.qtpl:174 - qb422016 := qt422016.AcquireByteBuffer() -//line views/stuff.qtpl:174 - WriteHyphaList(qb422016, lc) -//line views/stuff.qtpl:174 - qs422016 := string(qb422016.B) -//line views/stuff.qtpl:174 - qt422016.ReleaseByteBuffer(qb422016) -//line views/stuff.qtpl:174 - return qs422016 -//line views/stuff.qtpl:174 -} - -//line views/stuff.qtpl:176 -func streamcommonScripts(qw422016 *qt422016.Writer) { -//line views/stuff.qtpl:176 - qw422016.N().S(` -`) -//line views/stuff.qtpl:177 - for _, scriptPath := range cfg.CommonScripts { -//line views/stuff.qtpl:177 - qw422016.N().S(` - -`) -//line views/stuff.qtpl:179 - } -//line views/stuff.qtpl:179 - qw422016.N().S(` -`) -//line views/stuff.qtpl:180 -} - -//line views/stuff.qtpl:180 -func writecommonScripts(qq422016 qtio422016.Writer) { -//line views/stuff.qtpl:180 - qw422016 := qt422016.AcquireWriter(qq422016) -//line views/stuff.qtpl:180 - streamcommonScripts(qw422016) -//line views/stuff.qtpl:180 - qt422016.ReleaseWriter(qw422016) -//line views/stuff.qtpl:180 -} - -//line views/stuff.qtpl:180 -func commonScripts() string { -//line views/stuff.qtpl:180 - qb422016 := qt422016.AcquireByteBuffer() -//line views/stuff.qtpl:180 - writecommonScripts(qb422016) -//line views/stuff.qtpl:180 - qs422016 := string(qb422016.B) -//line views/stuff.qtpl:180 - qt422016.ReleaseByteBuffer(qb422016) -//line views/stuff.qtpl:180 - return qs422016 -//line views/stuff.qtpl:180 -} diff --git a/views/user_list.qtpl b/views/user_list.qtpl deleted file mode 100644 index 2d647d2..0000000 --- a/views/user_list.qtpl +++ /dev/null @@ -1,64 +0,0 @@ -{% import "github.com/bouncepaw/mycorrhiza/cfg" %} -{% import "github.com/bouncepaw/mycorrhiza/l18n" %} -{% import "github.com/bouncepaw/mycorrhiza/user" %} -{% import "sort" %} - -{% code -var hyphaListL10n = map[string]l10nEntry{ - "heading": en("List of users").ru("Список пользователей"), - "administrators": en("Administrators").ru("Администраторы"), - "moderators": en("Moderators").ru("Модераторы"), - "editors": en("Editors").ru("Редакторы"), -} -%} - -{% func UserList(lc *l18n.Localizer) %} -
    -
    -{% code -var get = func(key string) string { - return hyphaListL10n[key].get(lc.Locale) -} - -var ( - admins = make([]string, 0) - moderators = make([]string, 0) - editors = make([]string, 0) -) -for u := range user.YieldUsers() { - switch u.Group { - // What if we place the users into sorted slices? - case "admin": - admins = append(admins, u.Name) - case "moderator": - moderators = append(moderators, u.Name) - case "editor", "trusted": - editors = append(editors, u.Name) - } -} -sort.Strings(admins) -sort.Strings(moderators) -sort.Strings(editors) -%} -

    {%s get("heading") %}

    -
    -

    {%s get("administrators") %}

    -
      {% for _, name := range admins %} -
    1. {%s name %}
    2. - {% endfor %}
    -
    -
    -

    {%s get("moderators") %}

    -
      {% for _, name := range moderators %} -
    1. {%s name %}
    2. - {% endfor %}
    -
    -
    -

    {%s get("editors") %}

    -
      {% for _, name := range editors %} -
    1. {%s name %}
    2. - {% endfor %}
    -
    -
    -
    -{% endfunc %} \ No newline at end of file diff --git a/views/user_list.qtpl.go b/views/user_list.qtpl.go deleted file mode 100644 index 31cdd88..0000000 --- a/views/user_list.qtpl.go +++ /dev/null @@ -1,199 +0,0 @@ -// Code generated by qtc from "user_list.qtpl". DO NOT EDIT. -// See https://github.com/valyala/quicktemplate for details. - -//line views/user_list.qtpl:1 -package views - -//line views/user_list.qtpl:1 -import "github.com/bouncepaw/mycorrhiza/cfg" - -//line views/user_list.qtpl:2 -import "github.com/bouncepaw/mycorrhiza/l18n" - -//line views/user_list.qtpl:3 -import "github.com/bouncepaw/mycorrhiza/user" - -//line views/user_list.qtpl:4 -import "sort" - -//line views/user_list.qtpl:6 -import ( - qtio422016 "io" - - qt422016 "github.com/valyala/quicktemplate" -) - -//line views/user_list.qtpl:6 -var ( - _ = qtio422016.Copy - _ = qt422016.AcquireByteBuffer -) - -//line views/user_list.qtpl:7 -var hyphaListL10n = map[string]l10nEntry{ - "heading": en("List of users").ru("Список пользователей"), - "administrators": en("Administrators").ru("Администраторы"), - "moderators": en("Moderators").ru("Модераторы"), - "editors": en("Editors").ru("Редакторы"), -} - -//line views/user_list.qtpl:15 -func StreamUserList(qw422016 *qt422016.Writer, lc *l18n.Localizer) { -//line views/user_list.qtpl:15 - qw422016.N().S(` -
    -
    -`) -//line views/user_list.qtpl:19 - var get = func(key string) string { - return hyphaListL10n[key].get(lc.Locale) - } - - var ( - admins = make([]string, 0) - moderators = make([]string, 0) - editors = make([]string, 0) - ) - for u := range user.YieldUsers() { - switch u.Group { - // What if we place the users into sorted slices? - case "admin": - admins = append(admins, u.Name) - case "moderator": - moderators = append(moderators, u.Name) - case "editor", "trusted": - editors = append(editors, u.Name) - } - } - sort.Strings(admins) - sort.Strings(moderators) - sort.Strings(editors) - -//line views/user_list.qtpl:42 - qw422016.N().S(` -

    `) -//line views/user_list.qtpl:43 - qw422016.E().S(get("heading")) -//line views/user_list.qtpl:43 - qw422016.N().S(`

    -
    -

    `) -//line views/user_list.qtpl:45 - qw422016.E().S(get("administrators")) -//line views/user_list.qtpl:45 - qw422016.N().S(`

    -
      `) -//line views/user_list.qtpl:46 - for _, name := range admins { -//line views/user_list.qtpl:46 - qw422016.N().S(` -
    1. `) -//line views/user_list.qtpl:47 - qw422016.E().S(name) -//line views/user_list.qtpl:47 - qw422016.N().S(`
    2. - `) -//line views/user_list.qtpl:48 - } -//line views/user_list.qtpl:48 - qw422016.N().S(`
    -
    -
    -

    `) -//line views/user_list.qtpl:51 - qw422016.E().S(get("moderators")) -//line views/user_list.qtpl:51 - qw422016.N().S(`

    -
      `) -//line views/user_list.qtpl:52 - for _, name := range moderators { -//line views/user_list.qtpl:52 - qw422016.N().S(` -
    1. `) -//line views/user_list.qtpl:53 - qw422016.E().S(name) -//line views/user_list.qtpl:53 - qw422016.N().S(`
    2. - `) -//line views/user_list.qtpl:54 - } -//line views/user_list.qtpl:54 - qw422016.N().S(`
    -
    -
    -

    `) -//line views/user_list.qtpl:57 - qw422016.E().S(get("editors")) -//line views/user_list.qtpl:57 - qw422016.N().S(`

    -
      `) -//line views/user_list.qtpl:58 - for _, name := range editors { -//line views/user_list.qtpl:58 - qw422016.N().S(` -
    1. `) -//line views/user_list.qtpl:59 - qw422016.E().S(name) -//line views/user_list.qtpl:59 - qw422016.N().S(`
    2. - `) -//line views/user_list.qtpl:60 - } -//line views/user_list.qtpl:60 - qw422016.N().S(`
    -
    -
    -
    -`) -//line views/user_list.qtpl:64 -} - -//line views/user_list.qtpl:64 -func WriteUserList(qq422016 qtio422016.Writer, lc *l18n.Localizer) { -//line views/user_list.qtpl:64 - qw422016 := qt422016.AcquireWriter(qq422016) -//line views/user_list.qtpl:64 - StreamUserList(qw422016, lc) -//line views/user_list.qtpl:64 - qt422016.ReleaseWriter(qw422016) -//line views/user_list.qtpl:64 -} - -//line views/user_list.qtpl:64 -func UserList(lc *l18n.Localizer) string { -//line views/user_list.qtpl:64 - qb422016 := qt422016.AcquireByteBuffer() -//line views/user_list.qtpl:64 - WriteUserList(qb422016, lc) -//line views/user_list.qtpl:64 - qs422016 := string(qb422016.B) -//line views/user_list.qtpl:64 - qt422016.ReleaseByteBuffer(qb422016) -//line views/user_list.qtpl:64 - return qs422016 -//line views/user_list.qtpl:64 -} diff --git a/viewutil/base.html b/viewutil/base.html new file mode 100644 index 0000000..c604709 --- /dev/null +++ b/viewutil/base.html @@ -0,0 +1,82 @@ +{{define "page"}} + + + + + + {{block "title" .}}{{end}} + + + + {{range .HeadElements}}{{.}}{{end}} + + +
    + +
    +{{block "body" .}}{{end}} + +{{range .CommonScripts}}{{.}}{{end}} + + + +{{end}} \ No newline at end of file diff --git a/viewutil/chain.go b/viewutil/chain.go new file mode 100644 index 0000000..783be46 --- /dev/null +++ b/viewutil/chain.go @@ -0,0 +1,33 @@ +package viewutil + +import "text/template" + +// Chain represents a chain of different language versions of the same template. +type Chain struct { + en *template.Template + ru *template.Template +} + +// En returns a new Chain. This is the only constructor of the type, so every view is forced to have an English representation. +func En(en *template.Template) Chain { + return Chain{ + en: en, + } +} + +// Ru adds a Russian translation to the Chain. +func (c Chain) Ru(ru *template.Template) Chain { + c.ru = ru + return c +} + +// Get returns an appropriate language representation for the given locale in meta. +func (c Chain) Get(meta Meta) *template.Template { + switch meta.Locale() { + case "en": + return c.en + case "ru": + return c.ru + } + panic("unknown language " + meta.Locale()) +} diff --git a/viewutil/err.go b/viewutil/err.go new file mode 100644 index 0000000..c375842 --- /dev/null +++ b/viewutil/err.go @@ -0,0 +1,27 @@ +package viewutil + +import ( + "fmt" + "mime" + "net/http" +) + +// HttpErr is used by many handlers to signal errors in a compact way. +// TODO: get rid of this abomination +func HttpErr(meta Meta, status int, name, errMsg string) { + meta.W.(http.ResponseWriter).Header().Set("Content-Type", mime.TypeByExtension(".html")) + meta.W.(http.ResponseWriter).WriteHeader(status) + fmt.Fprint( + meta.W, + Base( + meta, + "Error", + fmt.Sprintf( + `

    %s. %s

    `, + errMsg, + name, + meta.Lc.Get("ui.error_go_back"), + ), + ), + ) +} diff --git a/viewutil/meta.go b/viewutil/meta.go new file mode 100644 index 0000000..36a4e90 --- /dev/null +++ b/viewutil/meta.go @@ -0,0 +1,28 @@ +package viewutil + +import ( + "github.com/bouncepaw/mycorrhiza/l18n" + "github.com/bouncepaw/mycorrhiza/user" + "io" + "net/http" +) + +// Meta is a bundle of common stuffs used by views, templates. +type Meta struct { + Lc *l18n.Localizer + U *user.User + W io.Writer +} + +// MetaFrom makes a Meta from the given data. You are meant to further modify it. +func MetaFrom(w http.ResponseWriter, rq *http.Request) Meta { + return Meta{ + Lc: l18n.FromRequest(rq), + U: user.FromRequest(rq), + W: w, + } +} + +func (m Meta) Locale() string { + return m.Lc.Locale +} diff --git a/viewutil/viewutil.go b/viewutil/viewutil.go new file mode 100644 index 0000000..6fd71e6 --- /dev/null +++ b/viewutil/viewutil.go @@ -0,0 +1,96 @@ +// Package viewutil provides utilities and common templates for views across all packages. +package viewutil + +import ( + "embed" + "fmt" + "github.com/bouncepaw/mycorrhiza/cfg" + "github.com/bouncepaw/mycorrhiza/util" + "io/fs" + "log" + "strings" + "text/template" // TODO: save the world +) + +var ( + //go:embed *.html + fsys embed.FS + BaseEn *template.Template + BaseRu *template.Template + m = template.Must +) + +const ruText = ` +{{define "search by title"}}Поиск по названию{{end}} +{{define "close this dialog"}}Закрыть этот диалог{{end}} +{{define "login"}}Войти{{end}} +{{define "Register"}}Регистрация{{end}} +` + +func Init() { + dataText := fmt.Sprintf(` +{{define "wiki name"}}%s{{end}} +`, cfg.WikiName) + BaseEn = m(m(template.New(""). + Funcs(template.FuncMap{ + "beautifulName": util.BeautifulName, + }).ParseFS(fsys, "base.html")). + Parse(dataText)) + if !cfg.UseAuth { + m(BaseEn.Parse(`{{define "auth"}}{{end}}`)) + } + if !cfg.AllowRegistration { + m(BaseEn.Parse(`{{define "registration"}}{{end}}`)) + } + BaseRu = m(m(BaseEn.Clone()).Parse(ruText)) +} + +// TODO: get rid of this +func localizedBaseWithWeirdBody(meta Meta) *template.Template { + t := func() *template.Template { + if meta.Locale() == "ru" { + return BaseRu + } + return BaseEn + }() + return m(m(t.Clone()).Parse(` +{{define "body"}}{{.Body}}{{end}} +{{define "title"}}{{.Title}}{{end}} +`)) +} + +type BaseData struct { + Meta Meta + HeadElements []string + HeaderLinks []cfg.HeaderLink + CommonScripts []string + Title string // TODO: remove + Body string // TODO: remove +} + +// Base is a temporary wrapper around BaseEn and BaseRu, meant to facilitate the migration from qtpl. +func Base(meta Meta, title, body string, headElements ...string) string { + var w strings.Builder + meta.W = &w + t := localizedBaseWithWeirdBody(meta) + err := t.ExecuteTemplate(&w, "page", BaseData{ + Meta: meta, + Title: title, + HeadElements: headElements, + HeaderLinks: cfg.HeaderLinks, + CommonScripts: cfg.CommonScripts, + Body: body, + }) + if err != nil { + log.Println(err) + } + return w.String() +} + +func CopyEnWith(fsys fs.FS, f string) *template.Template { + return m(m(BaseEn.Clone()).ParseFS(fsys, f)) +} + +func CopyRuWith(fsys fs.FS, f string) *template.Template { + return m(m(BaseRu.Clone()).ParseFS(fsys, f)) +} diff --git a/web/admin.go b/web/admin.go index a463f9a..5a87bf7 100644 --- a/web/admin.go +++ b/web/admin.go @@ -2,6 +2,7 @@ package web import ( "fmt" + "github.com/bouncepaw/mycorrhiza/viewutil" "io" "log" "mime" @@ -35,7 +36,7 @@ func initAdmin(r *mux.Router) { func handlerAdmin(w http.ResponseWriter, rq *http.Request) { w.Header().Set("Content-Type", "text/html;charset=utf-8") w.WriteHeader(http.StatusOK) - views.AdminPanel(views.MetaFrom(w, rq)) + views.AdminPanel(viewutil.MetaFrom(w, rq)) } // handlerAdminShutdown kills the wiki. @@ -70,7 +71,7 @@ func handlerAdminUsers(w http.ResponseWriter, rq *http.Request) { var lc = l18n.FromRequest(rq) html := views.AdminUsersPanel(userList, lc) - html = views.Base(lc.Get("admin.users_title"), html, lc, user.FromRequest(rq)) + html = views.Base(viewutil.MetaFrom(w, rq), lc.Get("admin.users_title"), html) w.Header().Set("Content-Type", mime.TypeByExtension(".html")) io.WriteString(w, html) @@ -109,7 +110,7 @@ func handlerAdminUserEdit(w http.ResponseWriter, rq *http.Request) { var lc = l18n.FromRequest(rq) html := views.AdminUserEdit(u, f, lc) - html = views.Base(fmt.Sprintf(lc.Get("admin.user_title"), u.Name), html, lc, user.FromRequest(rq)) + html = views.Base(viewutil.MetaFrom(w, rq), fmt.Sprintf(lc.Get("admin.user_title"), u.Name), html) if f.HasError() { w.WriteHeader(http.StatusBadRequest) @@ -139,7 +140,7 @@ func handlerAdminUserDelete(w http.ResponseWriter, rq *http.Request) { var lc = l18n.FromRequest(rq) html := views.AdminUserDelete(u, util.NewFormData(), lc) - html = views.Base(fmt.Sprintf(lc.Get("admin.user_title"), u.Name), html, l18n.FromRequest(rq), user.FromRequest(rq)) + html = views.Base(viewutil.MetaFrom(w, rq), fmt.Sprintf(lc.Get("admin.user_title"), u.Name), html) if f.HasError() { w.WriteHeader(http.StatusBadRequest) @@ -153,7 +154,7 @@ func handlerAdminUserNew(w http.ResponseWriter, rq *http.Request) { if rq.Method == http.MethodGet { // New user form html := views.AdminUserNew(util.NewFormData(), lc) - html = views.Base(lc.Get("admin.newuser_title"), html, lc, user.FromRequest(rq)) + html = views.Base(viewutil.MetaFrom(w, rq), lc.Get("admin.newuser_title"), html) w.Header().Set("Content-Type", mime.TypeByExtension(".html")) io.WriteString(w, html) @@ -165,7 +166,7 @@ func handlerAdminUserNew(w http.ResponseWriter, rq *http.Request) { if err != nil { html := views.AdminUserNew(f.WithError(err), lc) - html = views.Base(lc.Get("admin.newuser_title"), html, lc, user.FromRequest(rq)) + html = views.Base(viewutil.MetaFrom(w, rq), lc.Get("admin.newuser_title"), html) w.WriteHeader(http.StatusBadRequest) w.Header().Set("Content-Type", mime.TypeByExtension(".html")) diff --git a/web/auth.go b/web/auth.go index fc47510..f4ea069 100644 --- a/web/auth.go +++ b/web/auth.go @@ -3,6 +3,7 @@ package web import ( "errors" "fmt" + "github.com/bouncepaw/mycorrhiza/viewutil" "io" "log" "mime" @@ -19,6 +20,7 @@ import ( ) func initAuth(r *mux.Router) { + r.HandleFunc("/user-list", handlerUserList) r.HandleFunc("/lock", handlerLock) // The check below saves a lot of extra checks and lines of codes in other places in this file. if !cfg.UseAuth { @@ -34,6 +36,13 @@ func initAuth(r *mux.Router) { r.HandleFunc("/logout", handlerLogout) } +func handlerUserList(w http.ResponseWriter, rq *http.Request) { + lc := l18n.FromRequest(rq) + w.Header().Set("Content-Type", mime.TypeByExtension(".html")) + w.WriteHeader(http.StatusOK) + w.Write([]byte(views.Base(viewutil.MetaFrom(w, rq), lc.Get("ui.users_title"), views.UserList(lc)))) +} + func handlerLock(w http.ResponseWriter, rq *http.Request) { _, _ = io.WriteString(w, views.Lock(l18n.FromRequest(rq))) } @@ -46,10 +55,9 @@ func handlerRegister(w http.ResponseWriter, rq *http.Request) { _, _ = io.WriteString( w, views.Base( + viewutil.MetaFrom(w, rq), lc.Get("auth.register_title"), views.Register(rq), - lc, - user.FromRequest(rq), ), ) } else if rq.Method == http.MethodPost { @@ -65,14 +73,13 @@ func handlerRegister(w http.ResponseWriter, rq *http.Request) { _, _ = io.WriteString( w, views.Base( + viewutil.MetaFrom(w, rq), lc.Get("auth.register_title"), fmt.Sprintf( `

    %s

    %s

    `, err.Error(), lc.Get("auth.try_again"), ), - lc, - user.FromRequest(rq), ), ) } else { @@ -101,7 +108,7 @@ func handlerLogout(w http.ResponseWriter, rq *http.Request) { } _, _ = io.WriteString( w, - views.Base(lc.Get("auth.logout_title"), views.Logout(can, lc), lc, u), + views.Base(viewutil.MetaFrom(w, rq), lc.Get("auth.logout_title"), views.Logout(can, lc)), ) } else if rq.Method == http.MethodPost { user.LogoutFromRequest(w, rq) @@ -118,10 +125,9 @@ func handlerLogin(w http.ResponseWriter, rq *http.Request) { _, _ = io.WriteString( w, views.Base( + viewutil.MetaFrom(w, rq), lc.Get("auth.login_title"), views.Login(lc), - lc, - user.EmptyUser(), ), ) } else if rq.Method == http.MethodPost { @@ -133,7 +139,7 @@ func handlerLogin(w http.ResponseWriter, rq *http.Request) { if err != "" { w.Header().Set("Content-Type", "text/html;charset=utf-8") w.WriteHeader(http.StatusInternalServerError) - _, _ = io.WriteString(w, views.Base(err, views.LoginError(err, lc), lc, user.EmptyUser())) + _, _ = io.WriteString(w, views.Base(viewutil.MetaFrom(w, rq), err, views.LoginError(err, lc))) return } http.Redirect(w, rq, "/", http.StatusSeeOther) @@ -172,6 +178,7 @@ func handlerTelegramLogin(w http.ResponseWriter, rq *http.Request) { _, _ = io.WriteString( w, views.Base( + viewutil.MetaFrom(w, rq), lc.Get("ui.error"), fmt.Sprintf( `

    %s

    %s

    %s

    `, @@ -179,8 +186,6 @@ func handlerTelegramLogin(w http.ResponseWriter, rq *http.Request) { err.Error(), lc.Get("auth.go_login"), ), - lc, - user.FromRequest(rq), ), ) return @@ -193,6 +198,7 @@ func handlerTelegramLogin(w http.ResponseWriter, rq *http.Request) { _, _ = io.WriteString( w, views.Base( + viewutil.MetaFrom(w, rq), "Error", fmt.Sprintf( `

    %s

    %s

    %s

    `, @@ -200,8 +206,6 @@ func handlerTelegramLogin(w http.ResponseWriter, rq *http.Request) { err.Error(), lc.Get("auth.go_login"), ), - lc, - user.FromRequest(rq), ), ) return diff --git a/web/backlinks.go b/web/backlinks.go deleted file mode 100644 index f7747ae..0000000 --- a/web/backlinks.go +++ /dev/null @@ -1,30 +0,0 @@ -package web - -import ( - "github.com/bouncepaw/mycorrhiza/hyphae/backlinks" - "net/http" - - "github.com/gorilla/mux" - - "github.com/bouncepaw/mycorrhiza/l18n" - "github.com/bouncepaw/mycorrhiza/user" - "github.com/bouncepaw/mycorrhiza/util" - "github.com/bouncepaw/mycorrhiza/views" -) - -func initBacklinks(r *mux.Router) { - r.PathPrefix("/backlinks/").HandlerFunc(handlerBacklinks) -} - -// handlerBacklinks lists all backlinks to a hypha. -func handlerBacklinks(w http.ResponseWriter, rq *http.Request) { - var ( - hyphaName = util.HyphaNameFromRq(rq, "backlinks") - lc = l18n.FromRequest(rq) - ) - util.HTTP200Page(w, views.Base( - lc.Get("ui.backlinks_title", &l18n.Replacements{"query": util.BeautifulName(hyphaName)}), - views.Backlinks(hyphaName, backlinks.YieldHyphaBacklinks, lc), - lc, - user.FromRequest(rq))) -} diff --git a/web/history.go b/web/history.go index 8b3a245..bcac81b 100644 --- a/web/history.go +++ b/web/history.go @@ -2,6 +2,7 @@ package web import ( "fmt" + "github.com/bouncepaw/mycorrhiza/viewutil" "log" "net/http" "strconv" @@ -10,7 +11,6 @@ import ( "github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/l18n" - "github.com/bouncepaw/mycorrhiza/user" "github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/views" ) @@ -42,10 +42,10 @@ func handlerHistory(w http.ResponseWriter, rq *http.Request) { var lc = l18n.FromRequest(rq) util.HTTP200Page(w, views.Base( + viewutil.MetaFrom(w, rq), fmt.Sprintf(lc.Get("ui.history_title"), util.BeautifulName(hyphaName)), views.History(rq, hyphaName, list, lc), - lc, - user.FromRequest(rq))) + )) } // handlerRecentChanges displays the /recent-changes/ page. @@ -57,10 +57,10 @@ func handlerRecentChanges(w http.ResponseWriter, rq *http.Request) { } var lc = l18n.FromRequest(rq) util.HTTP200Page(w, views.Base( + viewutil.MetaFrom(w, rq), lc.GetPlural("ui.recent_title", n), views.RecentChanges(n, lc), - lc, - user.FromRequest(rq))) + )) } // genericHandlerOfFeeds is a helper function for the web feed handlers. diff --git a/web/mutators.go b/web/mutators.go index 2d97c24..8c3ad02 100644 --- a/web/mutators.go +++ b/web/mutators.go @@ -2,11 +2,12 @@ package web import ( "fmt" + "github.com/bouncepaw/mycomarkup/v4" + "github.com/bouncepaw/mycorrhiza/viewutil" "log" "net/http" - "github.com/bouncepaw/mycomarkup/v3" - "github.com/bouncepaw/mycomarkup/v3/mycocontext" + "github.com/bouncepaw/mycomarkup/v4/mycocontext" "github.com/gorilla/mux" @@ -32,31 +33,31 @@ func initMutators(r *mux.Router) { func handlerRemoveMedia(w http.ResponseWriter, rq *http.Request) { util.PrepareRq(rq) var ( - u = user.FromRequest(rq) - lc = l18n.FromRequest(rq) - h = hyphae.ByName(util.HyphaNameFromRq(rq, "delete")) + u = user.FromRequest(rq) + lc = l18n.FromRequest(rq) + h = hyphae.ByName(util.HyphaNameFromRq(rq, "delete")) + meta = viewutil.MetaFrom(w, rq) ) if !u.CanProceed("remove-media") { - httpErr(w, lc, http.StatusForbidden, h.CanonicalName(), "no rights") + viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "no rights") return } if rq.Method == "GET" { util.HTTP200Page( w, views.Base( + meta, fmt.Sprintf(lc.Get("ui.ask_remove_media"), util.BeautifulName(h.CanonicalName())), - views.RemoveMediaAsk(rq, h.CanonicalName()), - lc, - u)) + views.RemoveMediaAsk(rq, h.CanonicalName()))) return } switch h := h.(type) { case *hyphae.EmptyHypha, *hyphae.TextualHypha: - httpErr(w, lc, http.StatusForbidden, h.CanonicalName(), "no media to remove") + viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "no media to remove") return case *hyphae.MediaHypha: if err := shroom.RemoveMedia(u, h); err != nil { - httpErr(w, lc, http.StatusInternalServerError, h.CanonicalName(), err.Error()) + viewutil.HttpErr(meta, http.StatusInternalServerError, h.CanonicalName(), err.Error()) return } } @@ -65,14 +66,15 @@ func handlerRemoveMedia(w http.ResponseWriter, rq *http.Request) { func handlerDelete(w http.ResponseWriter, rq *http.Request) { util.PrepareRq(rq) var ( - u = user.FromRequest(rq) - lc = l18n.FromRequest(rq) - h = hyphae.ByName(util.HyphaNameFromRq(rq, "delete")) + u = user.FromRequest(rq) + lc = l18n.FromRequest(rq) + h = hyphae.ByName(util.HyphaNameFromRq(rq, "delete")) + meta = viewutil.MetaFrom(w, rq) ) if !u.CanProceed("delete") { log.Printf("%s has no rights to delete ‘%s’\n", u.Name, h.CanonicalName()) - httpErr(w, lc, http.StatusForbidden, h.CanonicalName(), "No rights") + viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "No rights") return } @@ -80,7 +82,7 @@ func handlerDelete(w http.ResponseWriter, rq *http.Request) { case *hyphae.EmptyHypha: log.Printf("%s tries to delete empty hypha ‘%s’\n", u.Name, h.CanonicalName()) // TODO: localize - httpErr(w, lc, http.StatusForbidden, h.CanonicalName(), "Cannot delete an empty hypha") + viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "Cannot delete an empty hypha") return } @@ -88,16 +90,15 @@ func handlerDelete(w http.ResponseWriter, rq *http.Request) { util.HTTP200Page( w, views.Base( + meta, fmt.Sprintf(lc.Get("ui.ask_delete"), util.BeautifulName(h.CanonicalName())), - views.DeleteAsk(rq, h.CanonicalName()), - lc, - u)) + views.DeleteAsk(rq, h.CanonicalName()))) return } if err := shroom.Delete(u, h.(hyphae.ExistingHypha)); err != nil { log.Println(err) - httpErr(w, lc, http.StatusInternalServerError, h.CanonicalName(), err.Error()) + viewutil.HttpErr(meta, http.StatusInternalServerError, h.CanonicalName(), err.Error()) return } http.Redirect(w, rq, "/hypha/"+h.CanonicalName(), http.StatusSeeOther) @@ -106,21 +107,22 @@ func handlerDelete(w http.ResponseWriter, rq *http.Request) { func handlerRename(w http.ResponseWriter, rq *http.Request) { util.PrepareRq(rq) var ( - u = user.FromRequest(rq) - lc = l18n.FromRequest(rq) - h = hyphae.ByName(util.HyphaNameFromRq(rq, "rename")) + u = user.FromRequest(rq) + lc = l18n.FromRequest(rq) + h = hyphae.ByName(util.HyphaNameFromRq(rq, "rename")) + meta = viewutil.MetaFrom(w, rq) ) switch h.(type) { case *hyphae.EmptyHypha: log.Printf("%s tries to rename empty hypha ‘%s’", u.Name, h.CanonicalName()) - httpErr(w, lc, http.StatusForbidden, h.CanonicalName(), "Cannot rename an empty hypha") // TODO: localize + viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "Cannot rename an empty hypha") // TODO: localize return } if !u.CanProceed("rename") { log.Printf("%s has no rights to rename ‘%s’\n", u.Name, h.CanonicalName()) - httpErr(w, lc, http.StatusForbidden, h.CanonicalName(), "No rights") + viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "No rights") return } @@ -134,16 +136,15 @@ func handlerRename(w http.ResponseWriter, rq *http.Request) { util.HTTP200Page( w, views.Base( + meta, fmt.Sprintf(lc.Get("ui.ask_rename"), util.BeautifulName(oldHypha.CanonicalName())), - views.RenameAsk(rq, oldHypha.CanonicalName()), - lc, - u)) + views.RenameAsk(rq, oldHypha.CanonicalName()))) return } if err := shroom.Rename(oldHypha, newName, recursive, u); err != nil { log.Printf("%s tries to rename ‘%s’: %s", u.Name, oldHypha.CanonicalName(), err.Error()) - httpErr(w, lc, http.StatusForbidden, oldHypha.CanonicalName(), lc.Get(err.Error())) // TODO: localize + viewutil.HttpErr(meta, http.StatusForbidden, oldHypha.CanonicalName(), lc.Get(err.Error())) // TODO: localize return } http.Redirect(w, rq, "/hypha/"+newName, http.StatusSeeOther) @@ -160,10 +161,10 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) { err error u = user.FromRequest(rq) lc = l18n.FromRequest(rq) + meta = viewutil.MetaFrom(w, rq) ) if err := shroom.CanEdit(u, h, lc); err != nil { - httpErr(w, lc, http.StatusInternalServerError, hyphaName, - err.Error()) + viewutil.HttpErr(meta, http.StatusInternalServerError, hyphaName, err.Error()) return } switch h.(type) { @@ -173,18 +174,16 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) { textAreaFill, err = shroom.FetchTextFile(h) if err != nil { log.Println(err) - httpErr(w, lc, http.StatusInternalServerError, hyphaName, - lc.Get("ui.error_text_fetch")) + viewutil.HttpErr(meta, http.StatusInternalServerError, hyphaName, lc.Get("ui.error_text_fetch")) return } } util.HTTP200Page( w, views.Base( + meta, fmt.Sprintf(lc.Get("edit.title"), util.BeautifulName(hyphaName)), - views.Editor(rq, hyphaName, textAreaFill, warning), - lc, - u)) + views.Editor(rq, hyphaName, textAreaFill, warning))) } // handlerUploadText uploads a new text part for the hypha. @@ -198,21 +197,23 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) { message = rq.PostFormValue("message") u = user.FromRequest(rq) lc = l18n.FromRequest(rq) + meta = viewutil.MetaFrom(w, rq) ) if action != "Preview" { if err := shroom.UploadText(h, []byte(textData), message, u); err != nil { - httpErr(w, lc, http.StatusForbidden, hyphaName, err.Error()) + viewutil.HttpErr(meta, http.StatusForbidden, hyphaName, err.Error()) return } } if action == "Preview" { - ctx, _ := mycocontext.ContextFromStringInput(hyphaName, textData) + ctx, _ := mycocontext.ContextFromStringInput(textData, shroom.MarkupOptions(hyphaName)) util.HTTP200Page( w, views.Base( + meta, fmt.Sprintf(lc.Get("edit.preview_title"), util.BeautifulName(hyphaName)), views.Preview( rq, @@ -220,9 +221,7 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) { textData, message, "", - mycomarkup.BlocksToHTML(ctx, mycomarkup.BlockTree(ctx))), - lc, - u)) + mycomarkup.BlocksToHTML(ctx, mycomarkup.BlockTree(ctx))))) } else { http.Redirect(w, rq, "/hypha/"+hyphaName, http.StatusSeeOther) } @@ -238,14 +237,13 @@ func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) { u = user.FromRequest(rq) lc = l18n.FromRequest(rq) file, handler, err = rq.FormFile("binary") + meta = viewutil.MetaFrom(w, rq) ) if err != nil { - httpErr(w, lc, http.StatusInternalServerError, hyphaName, - err.Error()) + viewutil.HttpErr(meta, http.StatusInternalServerError, hyphaName, err.Error()) } if err := shroom.CanAttach(u, h, lc); err != nil { - httpErr(w, lc, http.StatusInternalServerError, hyphaName, - err.Error()) + viewutil.HttpErr(meta, http.StatusInternalServerError, hyphaName, err.Error()) } // If file is not passed: @@ -263,7 +261,7 @@ func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) { ) if err := shroom.UploadBinary(h, mime, file, u); err != nil { - httpErr(w, lc, http.StatusInternalServerError, hyphaName, err.Error()) + viewutil.HttpErr(meta, http.StatusInternalServerError, hyphaName, err.Error()) return } http.Redirect(w, rq, "/hypha/"+hyphaName, http.StatusSeeOther) diff --git a/web/readers.go b/web/readers.go index 051279d..acccb33 100644 --- a/web/readers.go +++ b/web/readers.go @@ -3,6 +3,9 @@ package web import ( "encoding/hex" "fmt" + "github.com/bouncepaw/mycomarkup/v4" + "github.com/bouncepaw/mycorrhiza/shroom" + "github.com/bouncepaw/mycorrhiza/viewutil" "io" "log" "net/http" @@ -12,7 +15,6 @@ import ( "github.com/gorilla/mux" - "github.com/bouncepaw/mycorrhiza/cfg" "github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/l18n" @@ -21,9 +23,8 @@ import ( "github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/views" - "github.com/bouncepaw/mycomarkup/v3" - "github.com/bouncepaw/mycomarkup/v3/mycocontext" - "github.com/bouncepaw/mycomarkup/v3/tools" + "github.com/bouncepaw/mycomarkup/v4/mycocontext" + "github.com/bouncepaw/mycomarkup/v4/tools" ) func initReaders(r *mux.Router) { @@ -47,10 +48,9 @@ func handlerMedia(w http.ResponseWriter, rq *http.Request) { ) util.HTTP200Page(w, views.Base( + viewutil.MetaFrom(w, rq), lc.Get("ui.media_title", &l18n.Replacements{"name": util.BeautifulName(hyphaName)}), - views.MediaMenu(rq, h, u), - lc, - u)) + views.MediaMenu(rq, h, u))) } func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) { @@ -73,7 +73,7 @@ func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) { hyphaName = util.CanonicalName(slug) h = hyphae.ByName(hyphaName) user = user.FromRequest(rq) - locale = l18n.FromRequest(rq) + lc = l18n.FromRequest(rq) ) switch h := h.(type) { case *hyphae.EmptyHypha: @@ -81,8 +81,9 @@ func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) { io.WriteString(w, "404 not found") case hyphae.ExistingHypha: util.HTTP200Page(w, views.Base( - locale.Get("ui.diff_title", &l18n.Replacements{"name": util.BeautifulName(hyphaName), "rev": revHash}), - views.PrimitiveDiff(rq, h, user, revHash), locale, user)) + viewutil.MetaFrom(w, rq), + lc.Get("ui.diff_title", &l18n.Replacements{"name": util.BeautifulName(hyphaName), "rev": revHash}), + views.PrimitiveDiff(rq, h, user, revHash))) } } @@ -133,14 +134,13 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) { hyphaName = util.CanonicalName(shorterURL[firstSlashIndex+1:]) h = hyphae.ByName(hyphaName) contents = fmt.Sprintf(`

    %s

    `, lc.Get("ui.revision_no_text")) - u = user.FromRequest(rq) ) switch h := h.(type) { case hyphae.ExistingHypha: var textContents, err = history.FileAtRevision(h.TextFilePath(), revHash) if err == nil { - ctx, _ := mycocontext.ContextFromStringInput(hyphaName, textContents) + ctx, _ := mycocontext.ContextFromStringInput(textContents, shroom.MarkupOptions(hyphaName)) contents = mycomarkup.BlocksToHTML(ctx, mycomarkup.BlockTree(ctx)) } } @@ -156,10 +156,9 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) { _, _ = fmt.Fprint( w, views.Base( + viewutil.MetaFrom(w, rq), lc.Get("ui.revision_title", &l18n.Replacements{"name": util.BeautifulName(hyphaName), "rev": revHash}), page, - lc, - u, ), ) } @@ -200,7 +199,6 @@ func handlerHypha(w http.ResponseWriter, rq *http.Request) { h = hyphae.ByName(hyphaName) contents string openGraph string - u = user.FromRequest(rq) lc = l18n.FromRequest(rq) ) @@ -208,16 +206,14 @@ func handlerHypha(w http.ResponseWriter, rq *http.Request) { case *hyphae.EmptyHypha: util.HTTP404Page(w, views.Base( + viewutil.MetaFrom(w, rq), util.BeautifulName(hyphaName), - views.Hypha(views.MetaFrom(w, rq), h, contents), - lc, - u, + views.Hypha(viewutil.MetaFrom(w, rq), h, contents), openGraph)) case hyphae.ExistingHypha: fileContentsT, errT := os.ReadFile(h.TextFilePath()) if errT == nil { - ctx, _ := mycocontext.ContextFromStringInput(hyphaName, string(fileContentsT)) - ctx = mycocontext.WithWebSiteURL(ctx, cfg.URL) + ctx, _ := mycocontext.ContextFromStringInput(string(fileContentsT), shroom.MarkupOptions(hyphaName)) getOpenGraph, descVisitor, imgVisitor := tools.OpenGraphVisitors(ctx) ast := mycomarkup.BlockTree(ctx, descVisitor, imgVisitor) contents = mycomarkup.BlocksToHTML(ctx, ast) @@ -230,10 +226,9 @@ func handlerHypha(w http.ResponseWriter, rq *http.Request) { util.HTTP200Page(w, views.Base( + viewutil.MetaFrom(w, rq), util.BeautifulName(hyphaName), - views.Hypha(views.MetaFrom(w, rq), h, contents), - lc, - u, + views.Hypha(viewutil.MetaFrom(w, rq), h, contents), openGraph)) } } diff --git a/web/search.go b/web/search.go deleted file mode 100644 index 06453a1..0000000 --- a/web/search.go +++ /dev/null @@ -1,38 +0,0 @@ -package web - -import ( - "io" - "net/http" - - "github.com/gorilla/mux" - - "github.com/bouncepaw/mycorrhiza/l18n" - "github.com/bouncepaw/mycorrhiza/shroom" - "github.com/bouncepaw/mycorrhiza/user" - "github.com/bouncepaw/mycorrhiza/util" - "github.com/bouncepaw/mycorrhiza/views" -) - -func initSearch(r *mux.Router) { - r.HandleFunc("/title-search/", handlerTitleSearch) -} - -func handlerTitleSearch(w http.ResponseWriter, rq *http.Request) { - util.PrepareRq(rq) - _ = rq.ParseForm() - var ( - query = rq.FormValue("q") - u = user.FromRequest(rq) - lc = l18n.FromRequest(rq) - ) - w.WriteHeader(http.StatusOK) - _, _ = io.WriteString( - w, - views.Base( - lc.Get("ui.title_search_title", &l18n.Replacements{"query": query}), - views.TitleSearch(query, shroom.YieldHyphaNamesContainingString, lc), - lc, - u, - ), - ) -} diff --git a/web/stuff.go b/web/stuff.go deleted file mode 100644 index ad5866f..0000000 --- a/web/stuff.go +++ /dev/null @@ -1,167 +0,0 @@ -package web - -// stuff.go is used for meta stuff about the wiki or all hyphae at once. -import ( - "github.com/bouncepaw/mycorrhiza/hyphae/backlinks" - "io" - "log" - "math/rand" - "net/http" - "strings" - - "github.com/gorilla/mux" - - "github.com/bouncepaw/mycorrhiza/cfg" - "github.com/bouncepaw/mycorrhiza/files" - "github.com/bouncepaw/mycorrhiza/help" - "github.com/bouncepaw/mycorrhiza/hyphae" - "github.com/bouncepaw/mycorrhiza/l18n" - "github.com/bouncepaw/mycorrhiza/shroom" - "github.com/bouncepaw/mycorrhiza/user" - "github.com/bouncepaw/mycorrhiza/util" - "github.com/bouncepaw/mycorrhiza/views" - - "github.com/bouncepaw/mycomarkup/v3" - "github.com/bouncepaw/mycomarkup/v3/mycocontext" -) - -func initStuff(r *mux.Router) { - r.PathPrefix("/help").HandlerFunc(handlerHelp) - r.HandleFunc("/list", handlerList) - r.HandleFunc("/reindex", handlerReindex) - r.HandleFunc("/update-header-links", handlerUpdateHeaderLinks) - r.HandleFunc("/random", handlerRandom) - r.HandleFunc("/about", handlerAbout) - r.HandleFunc("/favicon.ico", func(w http.ResponseWriter, rq *http.Request) { - http.Redirect(w, rq, "/static/favicon.ico", http.StatusSeeOther) - }) -} - -// handlerHelp gets the appropriate documentation or tells you where you (personally) have failed. -func handlerHelp(w http.ResponseWriter, rq *http.Request) { - lc := l18n.FromRequest(rq) - articlePath := strings.TrimPrefix(strings.TrimPrefix(rq.URL.Path, "/help/"), "/help") - // See the history of this file to resurrect the old algorithm that supported multiple languages - lang := "en" - if articlePath == "" { - articlePath = "en" - } - - if !strings.HasPrefix(articlePath, "en") { - w.WriteHeader(http.StatusNotFound) - _, _ = io.WriteString(w, "404 Not found") - return - } - - content, err := help.Get(articlePath) - if err != nil && strings.HasPrefix(err.Error(), "open") { - w.WriteHeader(http.StatusNotFound) - _, _ = io.WriteString( - w, - views.Base(lc.Get("help.entry_not_found"), - views.Help(views.HelpEmptyError(lc), lang, lc), - lc, - user.FromRequest(rq)), - ) - return - } - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - _, _ = io.WriteString( - w, - views.Base(err.Error(), - views.Help(err.Error(), lang, lc), - lc, - user.FromRequest(rq)), - ) - return - } - - // TODO: change for the function that uses byte array when there is such function in mycomarkup. - ctx, _ := mycocontext.ContextFromStringInput(articlePath, string(content)) - ast := mycomarkup.BlockTree(ctx) - result := mycomarkup.BlocksToHTML(ctx, ast) - w.WriteHeader(http.StatusOK) - _, _ = io.WriteString( - w, - views.Base(lc.Get("help.title"), - views.Help(result, lang, lc), - lc, - user.FromRequest(rq)), - ) -} - -// handlerList shows a list of all hyphae in the wiki in random order. -func handlerList(w http.ResponseWriter, rq *http.Request) { - u := user.FromRequest(rq) - var lc = l18n.FromRequest(rq) - util.PrepareRq(rq) - util.HTTP200Page(w, views.Base(lc.Get("ui.list_title"), views.HyphaList(lc), lc, u)) -} - -// handlerReindex reindexes all hyphae by checking the wiki storage directory anew. -func handlerReindex(w http.ResponseWriter, rq *http.Request) { - util.PrepareRq(rq) - if ok := user.CanProceed(rq, "reindex"); !ok { - var lc = l18n.FromRequest(rq) - httpErr(w, lc, http.StatusForbidden, cfg.HomeHypha, lc.Get("ui.reindex_no_rights")) - log.Println("Rejected", rq.URL) - return - } - hyphae.ResetCount() - log.Println("Reindexing hyphae in", files.HyphaeDir()) - hyphae.Index(files.HyphaeDir()) - backlinks.IndexBacklinks() - http.Redirect(w, rq, "/", http.StatusSeeOther) -} - -// handlerUpdateHeaderLinks updates header links by reading the configured hypha, if there is any, or resorting to default values. -// -// See https://mycorrhiza.wiki/hypha/configuration/header -func handlerUpdateHeaderLinks(w http.ResponseWriter, rq *http.Request) { - util.PrepareRq(rq) - if ok := user.CanProceed(rq, "update-header-links"); !ok { - var lc = l18n.FromRequest(rq) - httpErr(w, lc, http.StatusForbidden, cfg.HomeHypha, lc.Get("ui.header_no_rights")) - log.Println("Rejected", rq.URL) - return - } - shroom.SetHeaderLinks() - http.Redirect(w, rq, "/", http.StatusSeeOther) -} - -// handlerRandom redirects to a random hypha. -func handlerRandom(w http.ResponseWriter, rq *http.Request) { - util.PrepareRq(rq) - var ( - randomHyphaName string - amountOfHyphae = hyphae.Count() - ) - if amountOfHyphae == 0 { - var lc = l18n.FromRequest(rq) - httpErr(w, lc, http.StatusNotFound, cfg.HomeHypha, lc.Get("ui.random_no_hyphae_tip")) - return - } - i := rand.Intn(amountOfHyphae) - for h := range hyphae.YieldExistingHyphae() { - if i == 0 { - randomHyphaName = h.CanonicalName() - } - i-- - } - http.Redirect(w, rq, "/hypha/"+randomHyphaName, http.StatusSeeOther) -} - -// handlerAbout shows a summary of wiki's software. -func handlerAbout(w http.ResponseWriter, rq *http.Request) { - w.Header().Set("Content-Type", "text/html;charset=utf-8") - w.WriteHeader(http.StatusOK) - var ( - lc = l18n.FromRequest(rq) - title = lc.Get("ui.about_title", &l18n.Replacements{"name": cfg.WikiName}) - ) - _, err := io.WriteString(w, views.Base(title, views.AboutHTML(lc), lc, user.FromRequest(rq))) - if err != nil { - log.Println(err) - } -} diff --git a/web/web.go b/web/web.go index c255b5d..2511e6b 100644 --- a/web/web.go +++ b/web/web.go @@ -2,74 +2,21 @@ package web import ( - "fmt" + "github.com/bouncepaw/mycorrhiza/backlinks" + "github.com/bouncepaw/mycorrhiza/categories" + "github.com/bouncepaw/mycorrhiza/help" + "github.com/bouncepaw/mycorrhiza/misc" "io" - "mime" "net/http" "net/url" "github.com/gorilla/mux" "github.com/bouncepaw/mycorrhiza/cfg" - "github.com/bouncepaw/mycorrhiza/l18n" - "github.com/bouncepaw/mycorrhiza/static" "github.com/bouncepaw/mycorrhiza/user" "github.com/bouncepaw/mycorrhiza/util" - "github.com/bouncepaw/mycorrhiza/views" ) -var stylesheets = []string{"default.css", "custom.css"} - -// httpErr is used by many handlers to signal errors in a compact way. -func httpErr(w http.ResponseWriter, lc *l18n.Localizer, status int, name, errMsg string) { - w.Header().Set("Content-Type", mime.TypeByExtension(".html")) - w.WriteHeader(status) - fmt.Fprint( - w, - views.Base( - "Error", - fmt.Sprintf( - `

    %s. %s

    `, - errMsg, - name, - lc.Get("ui.error_go_back"), - ), - lc, - user.EmptyUser(), - ), - ) -} - -func handlerStyle(w http.ResponseWriter, rq *http.Request) { - w.Header().Set("Content-Type", mime.TypeByExtension(".css")) - for _, name := range stylesheets { - file, err := static.FS.Open(name) - if err != nil { - continue - } - io.Copy(w, file) - file.Close() - } -} - -func handlerUserList(w http.ResponseWriter, rq *http.Request) { - lc := l18n.FromRequest(rq) - w.Header().Set("Content-Type", mime.TypeByExtension(".html")) - w.WriteHeader(http.StatusOK) - w.Write([]byte(views.Base(lc.Get("ui.users_title"), views.UserList(lc), lc, user.FromRequest(rq)))) -} - -func handlerRobotsTxt(w http.ResponseWriter, rq *http.Request) { - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - - file, err := static.FS.Open("robots.txt") - if err != nil { - return - } - io.Copy(w, file) - file.Close() -} - // Handler initializes and returns the HTTP router based on the configuration. func Handler() http.Handler { router := mux.NewRouter() @@ -86,10 +33,6 @@ func Handler() http.Handler { // Public routes. They're always accessible regardless of the user status. initAuth(router) - router.HandleFunc("/robots.txt", handlerRobotsTxt) - router.HandleFunc("/static/style.css", handlerStyle) - router.PathPrefix("/static/"). - Handler(http.StripPrefix("/static/", http.FileServer(http.FS(static.FS)))) // Wiki routes. They may be locked or restricted. wikiRouter := router.PathPrefix("").Subrouter() @@ -105,10 +48,10 @@ func Handler() http.Handler { initReaders(wikiRouter) initMutators(wikiRouter) initHistory(wikiRouter) - initStuff(wikiRouter) - initSearch(wikiRouter) - initBacklinks(wikiRouter) - initCategories(wikiRouter) + help.InitHandlers(wikiRouter) + backlinks.InitHandlers(wikiRouter) + categories.InitHandlers(wikiRouter) + misc.InitHandlers(wikiRouter) // Admin routes. if cfg.UseAuth { @@ -117,9 +60,6 @@ func Handler() http.Handler { initAdmin(adminRouter) } - // Miscellaneous - wikiRouter.HandleFunc("/user-list", handlerUserList) - // Index page wikiRouter.HandleFunc("/", func(w http.ResponseWriter, rq *http.Request) { // Let's pray it never fails