1
0
mirror of https://github.com/osmarks/mycorrhiza.git synced 2024-12-04 18:19:54 +00:00

Categories: Show pre-populated categories

They are useless now, and cannot be edited. Also, not properly styled. You get the idea though.
This commit is contained in:
Timur Ismagilov 2022-03-19 23:57:33 +03:00
parent 94fa2e5688
commit f5cbd5622d
8 changed files with 285 additions and 87 deletions

View File

@ -71,7 +71,7 @@ func PrepareWikiRoot() error {
paths.userCredentialsJSON = filepath.Join(cfg.WikiDir, "users.json")
paths.tokensJSON = filepath.Join(paths.cacheDir, "tokens.json")
paths.categoriesJSON = filepath.Join(cfg.WikiDir, "tags.json")
paths.categoriesJSON = filepath.Join(cfg.WikiDir, "categories.json")
return nil
}

View File

@ -1,4 +1,4 @@
// Package categories provides category management.
// Package categories provides category management. All operations in this package are mutexed.
//
// As per the long pondering, this is how categories (cats for short)
// work in Mycorrhiza:
@ -14,31 +14,43 @@
// exist. If there are 1 or more hyphae in the cat, cat A exists.
package categories
// For WithHypha and Contents, should the results be sorted?
import "sync"
// WithHypha returns what categories have the given hypha.
// WithHypha returns what categories have the given hypha. The hypha name must be canonical.
func WithHypha(hyphaName string) (categoryList []string) {
panic("todo")
return
mutex.RLock()
defer mutex.RUnlock()
return hyphaToCategories[hyphaName].categoryList
}
// Contents returns what hyphae are in the category. If the returned slice is empty, the category does not exist, and vice versa.
// 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) {
panic("todo")
return
mutex.RLock()
defer mutex.RUnlock()
return categoryToHyphae[catName].hyphaList
}
// AddHyphaToCategory adds the hypha to the category and updates the records on the disk. If the hypha is already in the category, nothing happens. This operation is async-safe.
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.
func AddHyphaToCategory(hyphaName, catName string) {
for _, cat := range WithHypha(hyphaName) {
if cat == catName {
return
}
mutex.Lock()
if node, ok := hyphaToCategories[hyphaName]; ok {
node.storeCategory(catName)
} else {
hyphaToCategories[hyphaName] = &hyphaNode{categoryList: []string{catName}}
}
panic("todo")
if node, ok := categoryToHyphae[catName]; ok {
node.storeHypha(hyphaName)
} else {
categoryToHyphae[catName] = &categoryNode{hyphaList: []string{hyphaName}}
}
mutex.Unlock()
}
// RemoveHyphaFromCategory removes the hypha from the category and updates the records on the disk. If the hypha is not in the category, nothing happens. This operation is async-safe.
// RemoveHyphaFromCategory removes the hypha from the category and updates the records on the disk. If the hypha is not in the category, nothing happens.
func RemoveHyphaFromCategory(hyphaName, catName string) {
panic("todo")
mutex.Lock()
mutex.Unlock()
}

109
hyphae/categories/files.go Normal file
View File

@ -0,0 +1,109 @@
package categories
import (
"encoding/json"
"github.com/bouncepaw/mycorrhiza/files"
"github.com/bouncepaw/mycorrhiza/util"
"log"
"os"
)
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() {
var (
record, err = readCategoriesFromDisk()
)
if err != nil {
log.Fatalln(err)
}
for _, cat := range record.Categories {
if len(cat.Hyphae) == 0 {
continue
}
cat.Name = util.CanonicalName(cat.Name)
for i, hyphaName := range cat.Hyphae {
cat.Hyphae[i] = util.CanonicalName(hyphaName)
}
categoryToHyphae[cat.Name] = &categoryNode{hyphaList: cat.Hyphae}
}
for cat, hyphaeInCat := range categoryToHyphae {
for _, hyphaName := range hyphaeInCat.hyphaList {
if node, ok := hyphaToCategories[hyphaName]; ok {
node.storeCategory(cat)
} else {
hyphaToCategories[hyphaName] = &hyphaNode{categoryList: []string{cat}}
}
}
}
log.Println("Found", len(categoryToHyphae), "categories")
for cat, catNode := range categoryToHyphae { // TODO: remove when not needed
log.Println(cat, "->", catNode.hyphaList)
}
for hyp, hypNode := range hyphaToCategories {
log.Println(hyp, "<-", hypNode.categoryList)
}
}
type categoryNode struct {
// TODO: ensure this is sorted
hyphaList []string
}
func (cn *categoryNode) storeHypha(hypname string) {
for _, hyphaName := range cn.hyphaList {
if hyphaName == hypname {
return
}
}
cn.hyphaList = append(cn.hyphaList, hypname)
}
type hyphaNode struct {
// TODO: ensure this is sorted
categoryList []string
}
func (hn *hyphaNode) storeCategory(cat string) {
for _, category := range hn.categoryList {
if category == cat {
return
}
}
hn.categoryList = append(hn.categoryList, cat)
}
type catFileRecord struct {
Categories []catRecord `json:"categories"`
}
type catRecord struct {
Name string `json:"name"`
Hyphae []string `json:"hyphae"`
}
func readCategoriesFromDisk() (catFileRecord, error) {
var (
record catFileRecord
categoriesFile = files.CategoriesJSON()
fileContents, err = os.ReadFile(categoriesFile)
)
if os.IsNotExist(err) {
return record, nil
}
if err != nil {
return record, err
}
err = json.Unmarshal(fileContents, &record)
if err != nil {
return record, err
}
return record, nil
}

View File

@ -5,6 +5,7 @@
package main
import (
"github.com/bouncepaw/mycorrhiza/hyphae/categories"
"github.com/bouncepaw/mycorrhiza/migration"
"log"
"os"
@ -48,6 +49,7 @@ func main() {
history.InitGitRepo()
migration.MigrateRocketsMaybe()
shroom.SetHeaderLinks()
categories.InitCategories()
// Static files:
static.InitFS(files.StaticFiles())

View File

@ -60,7 +60,7 @@ header { width: 100%; margin-bottom: 1rem; }
.layout { display: grid; grid-template-columns: auto 1fr; column-gap: 1rem; margin: 0 1rem; row-gap: 1rem; }
.main-width { margin: 0; }
main { grid-column: 1 / span 1; grid-row: 1 / span 2; }
.sibling-hyphae, .markup-toolbar, .help-topics { grid-column: 2 / span 1; grid-row: 1 / span 1; }
.sibling-hyphae, .markup-toolbar, .help-topics, .categories-card { grid-column: 2 / span 1; grid-row: 1 / span 1; }
.action-toolbar { grid-column: 2 / span 1; grid-row: 2 / span 1; }
.layout-card { width: 100%; }
.edit-toolbar__buttons {display: grid; }
@ -74,10 +74,10 @@ header { width: 100%; margin-bottom: 1rem; }
.layout { grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); }
.layout-card {max-width: 18rem;}
.main-width { margin: 0 auto; }
main { grid-column: 2 / span 1; grid-row: 1 / span 2; }
.sibling-hyphae, .markup-toolbar, .help-topics { grid-column: 3 / span 1; margin-left: 0; }
main { grid-column: 2 / span 1; grid-row: 1 / span 3; }
.sibling-hyphae, .markup-toolbar, .help-topics { grid-column: 3 / span 1; margin-left: 0; grid-row: 1 / span 2; }
.markup-toolbar { grid-column: 3 / span 1; grid-row: 1 / span 2; }
.action-toolbar { grid-column: 1 / span 1; grid-row: 1 / span 1; }
.action-toolbar, .categories-card { grid-column: 1 / span 1; grid-row: 1 / span 1; }
.edit-toolbar__buttons { grid-template-columns: 1fr; }
}
@ -275,6 +275,16 @@ table { border: 0; background-color: #444444; color: #ddd; }
mark { background: rgba(130, 80, 30, 5); color: inherit; }
}
/*
* Categories
*/
.categories-card__entries {
padding-left: 1rem;
}
.categories-card__remove-form {
float: right;
}
/*
* Shortcuts
*/

59
views/categories.go Normal file
View File

@ -0,0 +1,59 @@
package views
import (
"github.com/bouncepaw/mycorrhiza/hyphae/categories"
"github.com/bouncepaw/mycorrhiza/util"
"html/template"
"log"
"strings"
)
const categoriesCardTmpl = `{{$hyphaName := .HyphaName
}}<aside class="layout-card categories-card">
<h2 class="layout-card__title">Categories</h2>
<ul class="categories-card__entries">
{{range .Categories}}
<li class="categories-card__entry">
<a class="categories-card__link" href="/category/{{.}}">{{beautifulName .}}</a>
<form method="POST" action="/remove-from-category" class="categories-card__remove-form">
<input type="hidden" name="cat" value="{{.}}">
<input type="hidden" name="hypha" value="{{$hyphaName}}">
<input type="submit" value="X">
</form>
</li>
{{end}}
<li class="categories-card__entry categories-card__add-to-cat">
<form method="POST" action="/add-to-category" class="categories-card__add-form">
<label for="_cat-name">
<input type="text">
<input type="submit" value="Add to category">
</form>
</li>
</ul>
</aside>`
var categoriesCardT *template.Template
func init() {
categoriesCardT = template.Must(template.
New("category card").
Funcs(template.FuncMap{
"beautifulName": util.BeautifulName,
}).
Parse(categoriesCardTmpl))
}
func categoryCardHTML(hyphaName string) string {
var buf strings.Builder
err := categoriesCardT.Execute(&buf, struct {
HyphaName string
Categories []string
}{
hyphaName,
categories.WithHypha(hyphaName),
})
if err != nil {
log.Println(err)
}
return buf.String()
}

View File

@ -131,6 +131,7 @@ If you rename .prevnext, change the docs too.
{%= hyphaInfo(rq, h) %}
</section>
</main>
{%s= categoryCardHTML(h.CanonicalName()) %}
{%= siblingHyphaeHTML(siblings, lc) %}
</div>
{%= viewScripts() %}

View File

@ -469,162 +469,167 @@ func StreamHyphaHTML(qw422016 *qt422016.Writer, rq *http.Request, lc *l18n.Local
</main>
`)
//line views/readers.qtpl:134
streamsiblingHyphaeHTML(qw422016, siblings, lc)
qw422016.N().S(categoryCardHTML(h.CanonicalName()))
//line views/readers.qtpl:134
qw422016.N().S(`
`)
//line views/readers.qtpl:135
streamsiblingHyphaeHTML(qw422016, siblings, lc)
//line views/readers.qtpl:135
qw422016.N().S(`
</div>
`)
//line views/readers.qtpl:136
//line views/readers.qtpl:137
streamviewScripts(qw422016)
//line views/readers.qtpl:136
//line views/readers.qtpl:137
qw422016.N().S(`
`)
//line views/readers.qtpl:137
//line views/readers.qtpl:138
}
//line views/readers.qtpl:137
//line views/readers.qtpl:138
func WriteHyphaHTML(qq422016 qtio422016.Writer, rq *http.Request, lc *l18n.Localizer, h hyphae.Hypha, contents string) {
//line views/readers.qtpl:137
//line views/readers.qtpl:138
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/readers.qtpl:137
//line views/readers.qtpl:138
StreamHyphaHTML(qw422016, rq, lc, h, contents)
//line views/readers.qtpl:137
//line views/readers.qtpl:138
qt422016.ReleaseWriter(qw422016)
//line views/readers.qtpl:137
//line views/readers.qtpl:138
}
//line views/readers.qtpl:137
//line views/readers.qtpl:138
func HyphaHTML(rq *http.Request, lc *l18n.Localizer, h hyphae.Hypha, contents string) string {
//line views/readers.qtpl:137
//line views/readers.qtpl:138
qb422016 := qt422016.AcquireByteBuffer()
//line views/readers.qtpl:137
//line views/readers.qtpl:138
WriteHyphaHTML(qb422016, rq, lc, h, contents)
//line views/readers.qtpl:137
//line views/readers.qtpl:138
qs422016 := string(qb422016.B)
//line views/readers.qtpl:137
//line views/readers.qtpl:138
qt422016.ReleaseByteBuffer(qb422016)
//line views/readers.qtpl:137
//line views/readers.qtpl:138
return qs422016
//line views/readers.qtpl:137
//line views/readers.qtpl:138
}
//line views/readers.qtpl:139
//line views/readers.qtpl:140
func StreamRevisionHTML(qw422016 *qt422016.Writer, rq *http.Request, lc *l18n.Localizer, h hyphae.Hypha, contents, revHash string) {
//line views/readers.qtpl:139
//line views/readers.qtpl:140
qw422016.N().S(`
<div class="layout">
<main class="main-width">
<section>
<p>`)
//line views/readers.qtpl:143
//line views/readers.qtpl:144
qw422016.E().S(lc.Get("ui.revision_warning"))
//line views/readers.qtpl:143
//line views/readers.qtpl:144
qw422016.N().S(` <a href="/rev-text/`)
//line views/readers.qtpl:143
//line views/readers.qtpl:144
qw422016.E().S(revHash)
//line views/readers.qtpl:143
//line views/readers.qtpl:144
qw422016.N().S(`/`)
//line views/readers.qtpl:143
//line views/readers.qtpl:144
qw422016.E().S(h.CanonicalName())
//line views/readers.qtpl:143
//line views/readers.qtpl:144
qw422016.N().S(`">`)
//line views/readers.qtpl:143
//line views/readers.qtpl:144
qw422016.E().S(lc.Get("ui.revision_link"))
//line views/readers.qtpl:143
//line views/readers.qtpl:144
qw422016.N().S(`</a></p>
`)
//line views/readers.qtpl:144
//line views/readers.qtpl:145
qw422016.N().S(NaviTitleHTML(h))
//line views/readers.qtpl:144
//line views/readers.qtpl:145
qw422016.N().S(`
`)
//line views/readers.qtpl:145
//line views/readers.qtpl:146
qw422016.N().S(contents)
//line views/readers.qtpl:145
//line views/readers.qtpl:146
qw422016.N().S(`
</section>
</main>
</div>
`)
//line views/readers.qtpl:149
//line views/readers.qtpl:150
streamviewScripts(qw422016)
//line views/readers.qtpl:149
//line views/readers.qtpl:150
qw422016.N().S(`
`)
//line views/readers.qtpl:150
//line views/readers.qtpl:151
}
//line views/readers.qtpl:150
//line views/readers.qtpl:151
func WriteRevisionHTML(qq422016 qtio422016.Writer, rq *http.Request, lc *l18n.Localizer, h hyphae.Hypha, contents, revHash string) {
//line views/readers.qtpl:150
//line views/readers.qtpl:151
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/readers.qtpl:150
//line views/readers.qtpl:151
StreamRevisionHTML(qw422016, rq, lc, h, contents, revHash)
//line views/readers.qtpl:150
//line views/readers.qtpl:151
qt422016.ReleaseWriter(qw422016)
//line views/readers.qtpl:150
//line views/readers.qtpl:151
}
//line views/readers.qtpl:150
//line views/readers.qtpl:151
func RevisionHTML(rq *http.Request, lc *l18n.Localizer, h hyphae.Hypha, contents, revHash string) string {
//line views/readers.qtpl:150
//line views/readers.qtpl:151
qb422016 := qt422016.AcquireByteBuffer()
//line views/readers.qtpl:150
//line views/readers.qtpl:151
WriteRevisionHTML(qb422016, rq, lc, h, contents, revHash)
//line views/readers.qtpl:150
//line views/readers.qtpl:151
qs422016 := string(qb422016.B)
//line views/readers.qtpl:150
//line views/readers.qtpl:151
qt422016.ReleaseByteBuffer(qb422016)
//line views/readers.qtpl:150
//line views/readers.qtpl:151
return qs422016
//line views/readers.qtpl:150
//line views/readers.qtpl:151
}
//line views/readers.qtpl:152
//line views/readers.qtpl:153
func streamviewScripts(qw422016 *qt422016.Writer) {
//line views/readers.qtpl:152
//line views/readers.qtpl:153
qw422016.N().S(`
`)
//line views/readers.qtpl:153
//line views/readers.qtpl:154
for _, scriptPath := range cfg.ViewScripts {
//line views/readers.qtpl:153
//line views/readers.qtpl:154
qw422016.N().S(`
<script src="`)
//line views/readers.qtpl:154
//line views/readers.qtpl:155
qw422016.E().S(scriptPath)
//line views/readers.qtpl:154
//line views/readers.qtpl:155
qw422016.N().S(`"></script>
`)
//line views/readers.qtpl:155
//line views/readers.qtpl:156
}
//line views/readers.qtpl:155
//line views/readers.qtpl:156
qw422016.N().S(`
`)
//line views/readers.qtpl:156
//line views/readers.qtpl:157
}
//line views/readers.qtpl:156
//line views/readers.qtpl:157
func writeviewScripts(qq422016 qtio422016.Writer) {
//line views/readers.qtpl:156
//line views/readers.qtpl:157
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/readers.qtpl:156
//line views/readers.qtpl:157
streamviewScripts(qw422016)
//line views/readers.qtpl:156
//line views/readers.qtpl:157
qt422016.ReleaseWriter(qw422016)
//line views/readers.qtpl:156
//line views/readers.qtpl:157
}
//line views/readers.qtpl:156
//line views/readers.qtpl:157
func viewScripts() string {
//line views/readers.qtpl:156
//line views/readers.qtpl:157
qb422016 := qt422016.AcquireByteBuffer()
//line views/readers.qtpl:156
//line views/readers.qtpl:157
writeviewScripts(qb422016)
//line views/readers.qtpl:156
//line views/readers.qtpl:157
qs422016 := string(qb422016.B)
//line views/readers.qtpl:156
//line views/readers.qtpl:157
qt422016.ReleaseByteBuffer(qb422016)
//line views/readers.qtpl:156
//line views/readers.qtpl:157
return qs422016
//line views/readers.qtpl:156
//line views/readers.qtpl:157
}