Reste à prévoir la pagination et la recherche

This commit is contained in:
Laurent Ulrich
2025-08-20 16:19:55 +02:00
parent 10ebdaad4a
commit 9dd396e95f
8 changed files with 183 additions and 70 deletions

46
blog.go
View File

@@ -6,6 +6,7 @@ import (
"log" "log"
"strings" "strings"
"sync" "sync"
"time"
"github.com/PuerkitoBio/goquery" "github.com/PuerkitoBio/goquery"
) )
@@ -17,8 +18,9 @@ type Post struct {
Title string Title string
Author string Author string
Date string Date string
DateTime time.Time
HTML template.HTML HTML template.HTML
ShortText string ShortText template.HTML
Text string Text string
} }
@@ -44,28 +46,25 @@ func (b *Blog) GetPost(Id string) (Post, bool) {
return ret, false return ret, false
} }
func (p *Post) GenerateExcerpt() { func (p *Post) GenerateExcerpt() {
fmt.Println("----", p.Title)
p.ShortText = ""
if len(p.HTML) > 0 { if len(p.HTML) > 0 {
fmt.Println("Short text is html", p.HTML)
heading, err := p.GetFirstHeading(string(p.HTML)) heading, err := p.GetFirstHeading(string(p.HTML))
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} }
paragraph, err := p.GetFirstParagraph(string(p.HTML)) paragraph, err := p.GetFirstParagraphs(string(p.HTML))
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} }
if len(heading) > 0 { if len(heading) > 0 {
log.Println("heading is:", heading) p.ShortText += template.HTML(heading)
p.ShortText += heading
} }
if len(paragraph) > 0 { if len(paragraph) > 0 {
log.Println("paragraph is:", paragraph) p.ShortText += template.HTML(paragraph)
p.ShortText += paragraph
} }
} else { } else {
fmt.Println("Short text is text", p.Text) p.ShortText = template.HTML(p.Text)
p.ShortText = p.Text
} }
} }
@@ -88,22 +87,31 @@ func (p *Post) GetFirstHeading(html string) (string, error) {
return htmlContent, nil return htmlContent, nil
} }
func (p *Post) GetFirstParagraph(html string) (string, error) { func (p *Post) GetFirstParagraphs(html string) (string, error) {
doc, err := goquery.NewDocumentFromReader(strings.NewReader(html)) doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
if err != nil { if err != nil {
return "", err return "", err
} }
firstParagraph := doc.Find("p").First() var htmlContent string
if firstParagraph.Length() == 0 { var numberOfTextChars int
return "", fmt.Errorf("no paragraph found")
}
htmlContent, err := goquery.OuterHtml(firstParagraph) // Eventuellement trouver un moyen de ne plus examiner le reste du document
if err != nil { // si 200 caractères atteints
return "", err doc.Find("p,div,h1,h2,h3,h4,h5").Each(func(i int, s *goquery.Selection) {
}
if numberOfTextChars < 200 {
numberOfTextChars += len(s.Text())
content, err := goquery.OuterHtml(s)
if err != nil {
log.Println(err)
}
htmlContent += content
}
})
return htmlContent, nil return htmlContent, nil
} }

View File

@@ -14,10 +14,15 @@ type MailBoxConfiguration struct {
SSL string SSL string
InBox string InBox string
} }
type ServiceConfiguration struct {
Address string
Port string
}
type BlogConfiguration struct { type BlogConfiguration struct {
Title string Title string
ShortName string ShortName string
MailBox MailBoxConfiguration MailBox MailBoxConfiguration
Service ServiceConfiguration
} }
func (configuration *BlogConfiguration) Prompt() error { func (configuration *BlogConfiguration) Prompt() error {
@@ -31,7 +36,7 @@ func (configuration *BlogConfiguration) Prompt() error {
return err return err
} }
if !regexp.MustCompile(`^[a-zA-Z0-9]+$`).MatchString(configuration.ShortName) { if !regexp.MustCompile(`^[a-zA-Z0-9]+$`).MatchString(configuration.ShortName) {
return fmt.Errorf("short same invalid") return fmt.Errorf("short name invalid")
} }
/* /*
@@ -73,5 +78,15 @@ func (configuration *BlogConfiguration) Prompt() error {
return err return err
} }
configuration.Service.Address, err = prompt("Listen address", nil, "127.0.0.1", configuration.Service.Address, false)
if err != nil {
return err
}
configuration.Service.Port, err = prompt("Listen port", nil, "8080", configuration.Service.Port, false)
if err != nil {
return err
}
return nil return nil
} }

View File

@@ -3,14 +3,23 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<link rel="stylesheet" href="../styles.css">
<title>{{.BlogTitle}}</title> <title>{{.BlogTitle}}</title>
</head> </head>
<h1><a href="/proxy/8080/">{{.BlogTitle}}</a></h1> <h1 class="blogtitle"><a href="/proxy/8080/">{{.BlogTitle}}</a></h1>
<body> <body>
<h2>{{.Title}}</h2> <article class="post">
<div> <header class="postheader">
<h2 class="posttitle">{{.Title}}</h2>
<p class="postdate"><time datetime="{{.Date}}">{{.Date}}</Time></p>
</header>
<div class="postcontent">
{{ if .HTML }}
{{.HTML}} {{.HTML}}
{{ else }}
{{.Text}}
{{ end }}
</div> </div>
</article>
</body> </body>
</html> </html>

View File

@@ -4,36 +4,19 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<title>{{.Title}}</title> <title>{{.Title}}</title>
<style> <link rel="stylesheet" href="styles.css">
a:link {
color: black;
text-decoration: none;
}
a:visited {
color: black;
text-decoration: none;
}
a:hover {
color: orange;
text-decoration: none;
}
</style>
</head> </head>
<body> <body>
<h1><a href="/proxy/8080/">{{.Title}}</a></h1> <h1 class="blogtitle"><a href="/proxy/8080/">{{.Title}}</a></h1>
{{range .Posts}} {{range .Posts}}
<article> <article class="shortpost">
<h2><a href=/proxy/8080/post/{{ .Id }}>{{.Title}}</a></h2> <header class="shortpostheader">
<address> <h2 class="shortposttitle"><a href=/proxy/8080/post/{{ .Id }}>{{.Title}}</a></h2>
{{.Author}} <p class="shortpostdate"><time datetime="{{.Date}}">{{.Date}}</Time></p>
</address> </header>
<div> <div>
<b>Extrait:</b> <blockquote class="shortpostcontent">{{.ShortText}}</blockquote>
<blockquote>{{.ShortText}}</blockquote>
</div> </div>
<footer>
<p>Publié le:<time datetime="{{.Date}}">{{.Date}}</Time></p>
</footer>
</article> </article>
{{ end }} {{ end }}
</body> </body>

59
html/styles.css Normal file
View File

@@ -0,0 +1,59 @@
a:link {
color: black;
text-decoration: none;
}
a:visited {
color: black;
text-decoration: none;
}
a:hover {
color: orange;
text-decoration: none;
}
.blogtitle {
color: coral;
text-decoration:underline;
}
/*
.shortpost {
}
.shortpostheader {
}
*/
.shortposttitle {
font-weight: bold;
}
.shortpostdate {
font-style: italic;
font-size: smaller;
font-weight: lighter;
}
/*
.shortpostcontent {
}
*/
/*
.post {
}
.postheader {
}
*/
.posttitle {
font-weight: bold;
}
.postdate {
font-style: italic;
font-size: smaller;
font-weight: lighter;
}
/*
.postcontent {
}
*/

View File

@@ -7,6 +7,7 @@ import (
"io" "io"
"log" "log"
"mime" "mime"
"sort"
"strconv" "strconv"
"strings" "strings"
@@ -69,12 +70,11 @@ func (mb *MailBox) Connect() error {
log.Println("Error connnecting to", imapServer, ":", err) log.Println("Error connnecting to", imapServer, ":", err)
return err return err
} }
log.Println("Connected")
err = mb.Client.Login(mb.User, mb.Password).Wait() err = mb.Client.Login(mb.User, mb.Password).Wait()
if err != nil { if err != nil {
log.Fatal("failed to login:", err) log.Fatal("failed to login:", err)
} }
log.Println("Logged in")
return nil return nil
} }
func (mb *MailBox) ListFolders() ([]string, error) { func (mb *MailBox) ListFolders() ([]string, error) {
@@ -122,6 +122,7 @@ func (mb *MailBox) GetMessages() ([]Post, error) {
post.Author = messages[0].Envelope.To[0].Name post.Author = messages[0].Envelope.To[0].Name
post.Date = messages[0].Envelope.Date.Format("2006/01/02 15:04") post.Date = messages[0].Envelope.Date.Format("2006/01/02 15:04")
post.DateTime = messages[0].Envelope.Date
section := messages[0].FindBodySection(bodySection) section := messages[0].FindBodySection(bodySection)
@@ -186,7 +187,6 @@ func (mb *MailBox) GetMessages() ([]Post, error) {
post.HTML = template.HTML(buf.String()) post.HTML = template.HTML(buf.String())
post.GenerateExcerpt() post.GenerateExcerpt()
} }
default: default:
log.Println("Content-Type:", part.Header.Get("Content-Type")) log.Println("Content-Type:", part.Header.Get("Content-Type"))
@@ -198,6 +198,9 @@ func (mb *MailBox) GetMessages() ([]Post, error) {
} }
posts = append(posts, post) posts = append(posts, post)
} }
sort.Slice(posts, func(i, j int) bool {
return posts[i].DateTime.After(posts[j].DateTime)
})
return posts, nil return posts, nil
} }

View File

@@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"log" "log"
"os" "os"
"time"
) )
//go:embed html/index.tmpl //go:embed html/index.tmpl
@@ -14,6 +15,9 @@ var indexTemplate string
//go:embed html/entry.tmpl //go:embed html/entry.tmpl
var entryTemplate string var entryTemplate string
//go:embed html/styles.css
var nativeCSS string
func main() { func main() {
var err error var err error
@@ -26,7 +30,6 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
configurationFileName := fmt.Sprintf("%s/.config/mailblog.json", home) configurationFileName := fmt.Sprintf("%s/.config/mailblog.json", home)
log.Println("Mailblog starting using", configurationFileName)
file, err := os.Open(configurationFileName) file, err := os.Open(configurationFileName)
if err == nil { if err == nil {
@@ -60,24 +63,34 @@ func main() {
blog.Lang = "fr-FR" blog.Lang = "fr-FR"
blog.Title = configuration.Title blog.Title = configuration.Title
go MailboxFetcher(configuration, &blog) go MailboxFetcher(configuration, &blog)
StartServer(&blog) StartServer(configuration, &blog)
} }
func MailboxFetcher(configuration BlogConfiguration, blog *Blog) { func MailboxFetcher(configuration BlogConfiguration, blog *Blog) {
var mb MailBox var mb MailBox
mb.Configure(&configuration.MailBox) mb.Configure(&configuration.MailBox)
err := mb.Connect()
if err != nil {
log.Fatal(err)
}
defer mb.Close()
posts, err := mb.GetMessages() for {
if err != nil { err := mb.Connect()
log.Println(err) if err != nil {
log.Fatal(err)
}
posts, err := mb.GetMessages()
if err != nil {
log.Println(err)
mb.Close()
time.Sleep(10 * time.Second)
continue
}
mb.Close()
blog.mutex.Lock()
blog.Posts = posts
blog.mutex.Unlock()
time.Sleep(10 * time.Second)
} }
blog.mutex.Lock()
defer blog.mutex.Unlock()
blog.Posts = posts
} }

33
web.go
View File

@@ -3,14 +3,17 @@ package main
import ( import (
"html/template" "html/template"
"log" "log"
"net"
"net/http" "net/http"
) )
func StartServer(blog *Blog) { func StartServer(configuration BlogConfiguration, blog *Blog) {
mux := http.NewServeMux()
/* Handle home page (/): list of blog entries */ /* Handle home page (/): list of blog entries */
http.HandleFunc("GET /{$}", mux.HandleFunc("GET /{$}",
func(w http.ResponseWriter, r *http.Request) { func(w http.ResponseWriter, r *http.Request) {
log.Printf("%+v", r.Header)
tpl, err := template.New("index").Parse(indexTemplate) tpl, err := template.New("index").Parse(indexTemplate)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@@ -24,7 +27,7 @@ func StartServer(blog *Blog) {
}) })
/* Handle one post display(/post/{post ID}) */ /* Handle one post display(/post/{post ID}) */
http.HandleFunc("GET /post/{id}", mux.HandleFunc("GET /post/{id}",
func(w http.ResponseWriter, r *http.Request) { func(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id") id := r.PathValue("id")
post, found := blog.GetPost(id) post, found := blog.GetPost(id)
@@ -42,11 +45,31 @@ func StartServer(blog *Blog) {
}) })
http.HandleFunc("GET /favicon.ico", mux.HandleFunc("GET /styles.css",
func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "text/css")
w.Write([]byte(nativeCSS))
})
mux.HandleFunc("GET /favicon.ico",
func(w http.ResponseWriter, r *http.Request) { func(w http.ResponseWriter, r *http.Request) {
log.Println("favicon.ico") log.Println("favicon.ico")
w.WriteHeader(200) w.WriteHeader(200)
}) })
http.ListenAndServe("0.0.0.0:8080", nil) addr := net.JoinHostPort(configuration.Service.Address, configuration.Service.Port)
server := &http.Server{
Addr: addr,
Handler: mux,
}
server.ListenAndServe()
//http.ListenAndServe(addr, server)
} }
/*
func isCodeServerProxy(r *http.Request) bool {
return len(r.Header.Get("code-server-session")) > 0
}
*/