forked from phc/website
Pagina utenti caricata da json
This commit is contained in:
@@ -7,4 +7,6 @@ GIT_URL=https://git.phc.dm.unipi.it
|
|||||||
CHAT_URL=https://chat.phc.dm.unipi.it
|
CHAT_URL=https://chat.phc.dm.unipi.it
|
||||||
|
|
||||||
# Other
|
# Other
|
||||||
EMAIL=macchinisti.phc@gmail.com
|
EMAIL=macchinisti@lists.dm.unipi.it
|
||||||
|
|
||||||
|
USER_PAGES_BASE_URL=https://poisson.phc.dm.unipi.it/~
|
||||||
|
|||||||
@@ -3,3 +3,6 @@
|
|||||||
|
|
||||||
# Miscellaneous
|
# Miscellaneous
|
||||||
tags
|
tags
|
||||||
|
|
||||||
|
# Local files
|
||||||
|
*.local*
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ Un comando comodo in fase di development che usa [`entr`](https://github.com/era
|
|||||||
|
|
||||||
```bash shell
|
```bash shell
|
||||||
$ find . -type f -name '*.go' | entr -r go run .
|
$ find . -type f -name '*.go' | entr -r go run .
|
||||||
|
# Or also...
|
||||||
|
$ fd -e go | entr -r go run .
|
||||||
```
|
```
|
||||||
|
|
||||||
### Environment Variables
|
### Environment Variables
|
||||||
|
|||||||
+10
-2
@@ -16,6 +16,8 @@ var GitUrl string
|
|||||||
var ChatUrl string
|
var ChatUrl string
|
||||||
var Email string
|
var Email string
|
||||||
|
|
||||||
|
var UserPagesBaseUrl string
|
||||||
|
|
||||||
func loadEnv(target *string, name, defaultValue string) {
|
func loadEnv(target *string, name, defaultValue string) {
|
||||||
value := os.Getenv(name)
|
value := os.Getenv(name)
|
||||||
if len(strings.TrimSpace(value)) == 0 {
|
if len(strings.TrimSpace(value)) == 0 {
|
||||||
@@ -33,17 +35,23 @@ func Load() {
|
|||||||
|
|
||||||
loadEnv(&Mode, "MODE", "production")
|
loadEnv(&Mode, "MODE", "production")
|
||||||
loadEnv(&Host, "HOST", "localhost:8080")
|
loadEnv(&Host, "HOST", "localhost:8080")
|
||||||
|
|
||||||
loadEnv(&GitUrl, "GIT_URL", "https://git.example.org")
|
loadEnv(&GitUrl, "GIT_URL", "https://git.example.org")
|
||||||
loadEnv(&ChatUrl, "CHAT_URL", "https://chat.example.org")
|
loadEnv(&ChatUrl, "CHAT_URL", "https://chat.example.org")
|
||||||
loadEnv(&Email, "EMAIL", "mail@example.org")
|
loadEnv(&Email, "EMAIL", "mail@example.org")
|
||||||
|
|
||||||
|
loadEnv(&UserPagesBaseUrl, "USER_PAGES_BASE_URL", "https://poisson.phc.dm.unipi.it/~")
|
||||||
}
|
}
|
||||||
|
|
||||||
func Object() util.H {
|
func Object() util.H {
|
||||||
return util.H{
|
return util.H{
|
||||||
"Mode": Mode,
|
"Mode": Mode,
|
||||||
"Host": Host,
|
"Host": Host,
|
||||||
|
|
||||||
"GitUrl": GitUrl,
|
"GitUrl": GitUrl,
|
||||||
"ChatUrl": ChatUrl,
|
"ChatUrl": ChatUrl,
|
||||||
"Email": Email,
|
"Email": Email,
|
||||||
|
|
||||||
|
"UserPagesBaseUrl": UserPagesBaseUrl,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"html/template"
|
"html/template"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -38,10 +39,9 @@ func main() {
|
|||||||
// Routes
|
// Routes
|
||||||
|
|
||||||
actuallyStaticRoutes := map[string]string{
|
actuallyStaticRoutes := map[string]string{
|
||||||
"/": "home.html",
|
"/": "home.html",
|
||||||
"/link": "link.html",
|
"/link": "link.html",
|
||||||
"/utenti": "utenti.html",
|
"/login": "login.html",
|
||||||
"/login": "login.html",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for route, view := range actuallyStaticRoutes {
|
for route, view := range actuallyStaticRoutes {
|
||||||
@@ -55,6 +55,34 @@ func main() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r.Get("/api/utenti", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
utenti, err := GetUtenti()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewEncoder(w).Encode(utenti); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
r.Get("/utenti", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
utenti, err := GetUtenti()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := renderer.Render(w, "utenti.html", util.H{
|
||||||
|
"Utenti": utenti,
|
||||||
|
}); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
r.Get("/appunti", func(w http.ResponseWriter, r *http.Request) {
|
r.Get("/appunti", func(w http.ResponseWriter, r *http.Request) {
|
||||||
searchQuery := ""
|
searchQuery := ""
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
// window.addEventListener('DOMContentLoaded', () => {
|
||||||
|
|
||||||
|
// })
|
||||||
+67
-1
@@ -475,6 +475,31 @@ button.icon {
|
|||||||
width: 2rem;
|
width: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
font-family: var(--font-sf);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
/* gray variant #b3b3b3 */
|
||||||
|
border: 1px solid var(--bg-darker-3);
|
||||||
|
/* gray variant #bfbfbf */
|
||||||
|
background: var(--bg-darker-2);
|
||||||
|
/* gray variant #333333 */
|
||||||
|
color: var(--fg);
|
||||||
|
|
||||||
|
height: 2rem;
|
||||||
|
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
padding: 0 0.25rem;
|
||||||
|
|
||||||
|
transition: all 100ms ease-in-out;
|
||||||
|
|
||||||
|
box-shadow: 0 4px 8px 0 #00000022;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
/* Text Fields */
|
/* Text Fields */
|
||||||
|
|
||||||
input[type=text], input[type=password] {
|
input[type=text], input[type=password] {
|
||||||
@@ -511,9 +536,35 @@ input[type=password] {
|
|||||||
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: 0 0 8px 0 #00000022;
|
box-shadow: 0 0 8px 0 #00000022;
|
||||||
|
|
||||||
|
border: 1px solid var(--bg-darker-3);
|
||||||
|
background: var(--bg-darker-2);
|
||||||
|
color: var(--fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.compound > button, .compound > .button, .compound > input {
|
.compound > .divider {
|
||||||
|
height: 2rem;
|
||||||
|
width: 1px;
|
||||||
|
|
||||||
|
background: var(--bg-darker-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.compound .icon {
|
||||||
|
width: 2.5rem;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.compound > select {
|
||||||
|
background: none;
|
||||||
|
|
||||||
|
margin-right: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.compound > button, .compound > .button, .compound > input, .compound > select {
|
||||||
|
border: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -633,8 +684,23 @@ form .field-set input {
|
|||||||
padding-top: calc(100vh - 6rem);
|
padding-top: calc(100vh - 6rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page-utenti .user-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-utenti .user-item {
|
||||||
|
flex-direction: row;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.search {
|
.search {
|
||||||
margin: 2rem 0;
|
margin: 2rem 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search input[type=text] {
|
.search input[type=text] {
|
||||||
|
|||||||
+25
-16
@@ -3,6 +3,7 @@ package templates
|
|||||||
import (
|
import (
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -10,20 +11,23 @@ import (
|
|||||||
"git.phc.dm.unipi.it/phc/website/util"
|
"git.phc.dm.unipi.it/phc/website/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CachedTemplate holds a reference to the list of patterns to preload and a reference to the already parsed template
|
||||||
type CachedTemplate struct {
|
type CachedTemplate struct {
|
||||||
LoadPatterns []string
|
PreLoadTemplatePatterns []string
|
||||||
cachedTmpl *template.Template
|
cachedTmpl *template.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewCacheTemplate creates a new template from a list of patterns a loads it
|
||||||
func NewCacheTemplate(loadPatterns ...string) *CachedTemplate {
|
func NewCacheTemplate(loadPatterns ...string) *CachedTemplate {
|
||||||
cachedTemplate := &CachedTemplate{
|
cachedTemplate := &CachedTemplate{
|
||||||
LoadPatterns: loadPatterns,
|
PreLoadTemplatePatterns: loadPatterns,
|
||||||
cachedTmpl: nil,
|
cachedTmpl: nil,
|
||||||
}
|
}
|
||||||
template.Must(cachedTemplate.Load(template.New("")))
|
template.Must(cachedTemplate.Load(template.New("")))
|
||||||
return cachedTemplate
|
return cachedTemplate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load returns the cached template or loads it from disk using the list of preload (glob) patterns.
|
||||||
func (ct *CachedTemplate) Load(t *template.Template) (*template.Template, error) {
|
func (ct *CachedTemplate) Load(t *template.Template) (*template.Template, error) {
|
||||||
if ct.cachedTmpl != nil {
|
if ct.cachedTmpl != nil {
|
||||||
return ct.cachedTmpl, nil
|
return ct.cachedTmpl, nil
|
||||||
@@ -31,7 +35,7 @@ func (ct *CachedTemplate) Load(t *template.Template) (*template.Template, error)
|
|||||||
|
|
||||||
ct.cachedTmpl = t
|
ct.cachedTmpl = t
|
||||||
|
|
||||||
for _, pattern := range ct.LoadPatterns {
|
for _, pattern := range ct.PreLoadTemplatePatterns {
|
||||||
var err error
|
var err error
|
||||||
ct.cachedTmpl, err = ct.cachedTmpl.ParseGlob(pattern)
|
ct.cachedTmpl, err = ct.cachedTmpl.ParseGlob(pattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -42,38 +46,43 @@ func (ct *CachedTemplate) Load(t *template.Template) (*template.Template, error)
|
|||||||
return ct.cachedTmpl, nil
|
return ct.cachedTmpl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reload the pattern from disk (used for developing)
|
||||||
func (ct *CachedTemplate) Reload() {
|
func (ct *CachedTemplate) Reload() {
|
||||||
ct.cachedTmpl = nil
|
ct.cachedTmpl = nil
|
||||||
template.Must(ct.Load(nil))
|
template.Must(ct.Load(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Template returns the cached template
|
||||||
func (ct *CachedTemplate) Template() *template.Template {
|
func (ct *CachedTemplate) Template() *template.Template {
|
||||||
|
log.Printf("preloadPatterns: %v", ct.PreLoadTemplatePatterns)
|
||||||
return ct.cachedTmpl
|
return ct.cachedTmpl
|
||||||
}
|
}
|
||||||
|
|
||||||
// Renderer holds cached templates for rendering
|
// TODO: Add a render function to CachedTemplate instead of returning the template reference. In this way the Template Renderer would not have a "direct" dependency on "html/template"...
|
||||||
type Renderer struct {
|
|
||||||
rootPath string
|
// TemplateRenderer holds cached templates for rendering
|
||||||
loadPatterns []string
|
type TemplateRenderer struct {
|
||||||
templateCache map[string]*CachedTemplate
|
viewsDir string
|
||||||
|
preloadTemplatePatterns []string
|
||||||
|
templateCache map[string]*CachedTemplate
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRenderer constructs a template renderer with a base file
|
// NewRenderer constructs a template renderer with a base file
|
||||||
func NewRenderer(rootPath string, loadPatterns ...string) *Renderer {
|
func NewRenderer(rootPath string, loadPatterns ...string) *TemplateRenderer {
|
||||||
return &Renderer{
|
return &TemplateRenderer{
|
||||||
rootPath,
|
rootPath,
|
||||||
loadPatterns,
|
loadPatterns,
|
||||||
map[string]*CachedTemplate{},
|
map[string]*CachedTemplate{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Renderer) Load(name string) *CachedTemplate {
|
func (r *TemplateRenderer) Load(name string) *CachedTemplate {
|
||||||
cachedTemplate, present := r.templateCache[name]
|
cachedTemplate, present := r.templateCache[name]
|
||||||
|
|
||||||
if !present {
|
if !present {
|
||||||
loaders := []string{}
|
loaders := []string{}
|
||||||
loaders = append(loaders, r.loadPatterns...)
|
loaders = append(loaders, r.preloadTemplatePatterns...)
|
||||||
loaders = append(loaders, path.Join(r.rootPath, name))
|
loaders = append(loaders, path.Join(r.viewsDir, name))
|
||||||
cachedTemplate = NewCacheTemplate(loaders...)
|
cachedTemplate = NewCacheTemplate(loaders...)
|
||||||
r.templateCache[name] = cachedTemplate
|
r.templateCache[name] = cachedTemplate
|
||||||
}
|
}
|
||||||
@@ -82,7 +91,7 @@ func (r *Renderer) Load(name string) *CachedTemplate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Render the template, also injects "Page" and "Config" values in the template
|
// Render the template, also injects "Page" and "Config" values in the template
|
||||||
func (r *Renderer) Render(w io.Writer, name string, data util.H) error {
|
func (r *TemplateRenderer) Render(w io.Writer, name string, data util.H) error {
|
||||||
cachedTemplate := r.Load(name)
|
cachedTemplate := r.Load(name)
|
||||||
|
|
||||||
if config.Mode == "development" {
|
if config.Mode == "development" {
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserInfo struct {
|
||||||
|
Uid string `string:"uid"`
|
||||||
|
Nome string `json:"nome"`
|
||||||
|
Cognome string `json:"cognome"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUtenti() ([]UserInfo, error) {
|
||||||
|
var users []UserInfo
|
||||||
|
|
||||||
|
usersJsonData, err := ioutil.ReadFile("./utenti-poisson-2022.local.json")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(usersJsonData, &users); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return users, nil
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
|
// H is a shortcut for creating a json-like object
|
||||||
type H map[string]interface{}
|
type H map[string]interface{}
|
||||||
|
|
||||||
|
// Apply is like Object.apply from JS and merges the given objects on top of target
|
||||||
func (target H) Apply(sources ...H) H {
|
func (target H) Apply(sources ...H) H {
|
||||||
for _, source := range sources {
|
for _, source := range sources {
|
||||||
for k, v := range source {
|
for k, v := range source {
|
||||||
|
|||||||
+23
-4
@@ -12,6 +12,17 @@
|
|||||||
Questa è la lista di tutti gli utenti con un account su Poisson. Scrivi nome, cognome o username di un utente per filtrare la lista in tempo reale. Altrimenti di base in cima compariranno gli utenti con più "follower".
|
Questa è la lista di tutti gli utenti con un account su Poisson. Scrivi nome, cognome o username di un utente per filtrare la lista in tempo reale. Altrimenti di base in cima compariranno gli utenti con più "follower".
|
||||||
</p>
|
</p>
|
||||||
<div class="search">
|
<div class="search">
|
||||||
|
<div class="compound padded">
|
||||||
|
<div class="icon">
|
||||||
|
<i class="fas fa-sort"></i>
|
||||||
|
</div>
|
||||||
|
<div class="divider"></div>
|
||||||
|
<select>
|
||||||
|
<option value="name">Cronologico</option>
|
||||||
|
<option value="name">Nome</option>
|
||||||
|
<option value="name">Cognome</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div class="compound">
|
<div class="compound">
|
||||||
<input type="text" id="search-field" placeholder="Cerca..." autocomplete="off">
|
<input type="text" id="search-field" placeholder="Cerca..." autocomplete="off">
|
||||||
<button class="icon">
|
<button class="icon">
|
||||||
@@ -19,12 +30,20 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="user-list">
|
<div class="user-list card-list">
|
||||||
<code>TODO: Lista work in progress</code>
|
{{ $config := .Config }}
|
||||||
|
{{ range .Utenti }}
|
||||||
|
<div class="user-item card">
|
||||||
|
<a class="full-name" href="{{ $config.UserPagesBaseUrl }}{{ .Uid }}">{{ .Nome }} {{ .Cognome }}</a>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<script>
|
|
||||||
|
|
||||||
</script>
|
<!--
|
||||||
|
<script src="//unpkg.com/alpinejs" defer></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/fuse.js/dist/fuse.js" defer></script>
|
||||||
|
|
||||||
|
<script type="module" src="/public/js/utenti.js"></script>
|
||||||
|
-->
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
Reference in New Issue
Block a user