2018-11-24 12:34:51 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2020-06-17 15:41:32 +00:00
|
|
|
"bytes"
|
2019-08-25 10:30:00 +00:00
|
|
|
"encoding/json"
|
2019-02-18 19:47:55 +00:00
|
|
|
"flag"
|
2018-11-24 12:34:51 +00:00
|
|
|
"fmt"
|
2019-08-25 10:56:11 +00:00
|
|
|
"html"
|
2018-11-24 12:34:51 +00:00
|
|
|
"html/template"
|
2020-06-11 20:20:17 +00:00
|
|
|
"io"
|
2018-11-24 12:34:51 +00:00
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
2020-06-17 15:41:32 +00:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2018-11-24 12:34:51 +00:00
|
|
|
"regexp"
|
|
|
|
"strings"
|
2019-02-18 19:47:31 +00:00
|
|
|
"time"
|
2019-08-25 10:30:00 +00:00
|
|
|
|
2020-06-30 20:56:12 +00:00
|
|
|
"github.com/blevesearch/bleve"
|
2019-08-25 10:30:00 +00:00
|
|
|
"p83.nl/go/ekster/pkg/util"
|
|
|
|
"p83.nl/go/indieauth"
|
2018-11-24 12:34:51 +00:00
|
|
|
)
|
|
|
|
|
2020-05-12 14:12:33 +00:00
|
|
|
func init() {
|
|
|
|
log.SetFlags(log.Lshortfile)
|
|
|
|
}
|
|
|
|
|
2018-11-24 12:34:51 +00:00
|
|
|
var (
|
|
|
|
mp PagesRepository
|
|
|
|
|
2020-05-18 19:58:44 +00:00
|
|
|
port = flag.Int("port", 8080, "listen port")
|
|
|
|
baseurl = flag.String("baseurl", "", "baseurl")
|
|
|
|
redirectURI string = ""
|
2018-11-24 12:34:51 +00:00
|
|
|
)
|
|
|
|
|
2020-05-08 15:21:39 +00:00
|
|
|
type Backref struct {
|
2020-07-05 18:34:36 +00:00
|
|
|
Name string
|
|
|
|
Title string
|
|
|
|
LineHTML template.HTML
|
|
|
|
LineEditHTML template.HTML
|
|
|
|
Line string
|
2020-05-08 15:21:39 +00:00
|
|
|
}
|
|
|
|
|
2018-11-24 12:34:51 +00:00
|
|
|
// Page
|
|
|
|
type Page struct {
|
2020-05-07 13:20:36 +00:00
|
|
|
// Name is the filename of the page
|
|
|
|
Name string
|
|
|
|
|
|
|
|
// Title is the human-readable title of the page
|
|
|
|
Title string
|
|
|
|
|
2018-11-24 12:34:51 +00:00
|
|
|
Content string
|
2020-05-07 13:20:36 +00:00
|
|
|
|
2020-10-21 18:49:23 +00:00
|
|
|
Refs map[string][]Backref
|
|
|
|
Blocks BlockResponse
|
|
|
|
Parent string
|
2018-11-24 12:34:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type DiffPage struct {
|
|
|
|
Content string
|
|
|
|
Diff template.HTML
|
|
|
|
}
|
|
|
|
|
|
|
|
type Revision struct {
|
|
|
|
Version string
|
|
|
|
Page DiffPage
|
|
|
|
Summary string
|
|
|
|
}
|
|
|
|
|
2019-02-18 19:47:31 +00:00
|
|
|
type Change struct {
|
2019-03-02 13:38:35 +00:00
|
|
|
Page string
|
|
|
|
Date time.Time
|
|
|
|
EndDate time.Time
|
|
|
|
Body string
|
|
|
|
Count int
|
|
|
|
|
|
|
|
DateSummary string
|
|
|
|
TimeSummary string
|
2019-02-18 19:47:31 +00:00
|
|
|
}
|
|
|
|
|
2018-11-24 12:34:51 +00:00
|
|
|
type PagesRepository interface {
|
|
|
|
Get(p string) Page
|
2019-02-19 06:34:52 +00:00
|
|
|
Save(p string, page Page, summary, author string) error
|
2018-11-24 12:34:51 +00:00
|
|
|
Exist(p string) bool
|
|
|
|
PageHistory(p string) ([]Revision, error)
|
2019-02-18 19:47:31 +00:00
|
|
|
RecentChanges() ([]Change, error)
|
2020-06-30 20:56:12 +00:00
|
|
|
AllPages() ([]Page, error)
|
2018-11-24 12:34:51 +00:00
|
|
|
}
|
|
|
|
|
2020-05-18 19:12:49 +00:00
|
|
|
type pageBaseInfo struct {
|
|
|
|
BaseURL string
|
|
|
|
RedirectURI string
|
|
|
|
}
|
|
|
|
|
2018-11-24 12:34:51 +00:00
|
|
|
type indexPage struct {
|
2020-05-18 19:12:49 +00:00
|
|
|
pageBaseInfo
|
2020-07-19 13:09:15 +00:00
|
|
|
Session *Session
|
|
|
|
Title string
|
|
|
|
Name string
|
|
|
|
Content template.HTML
|
|
|
|
Backrefs map[string][]Backref
|
|
|
|
ShowGraph bool
|
2020-07-19 13:18:03 +00:00
|
|
|
TodayPage string
|
2018-11-24 12:34:51 +00:00
|
|
|
}
|
|
|
|
|
2020-06-17 15:41:32 +00:00
|
|
|
type Node struct {
|
2020-07-05 19:18:42 +00:00
|
|
|
Id int `json:"id"`
|
|
|
|
Label string `json:"label"`
|
|
|
|
Color *string `json:"color"`
|
2020-07-07 22:45:06 +00:00
|
|
|
Opacity float64 `json:"opacity"`
|
2020-06-17 15:41:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type Edge struct {
|
|
|
|
From int `json:"from"`
|
|
|
|
To int `json:"to"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type graphPage struct {
|
|
|
|
pageBaseInfo
|
2020-07-05 18:34:36 +00:00
|
|
|
Session *Session
|
|
|
|
Title string
|
|
|
|
Name string
|
|
|
|
Nodes template.JS
|
|
|
|
Edges template.JS
|
2020-06-17 15:41:32 +00:00
|
|
|
}
|
|
|
|
|
2020-10-21 18:49:23 +00:00
|
|
|
type Parent struct {
|
|
|
|
Text string
|
|
|
|
ID string
|
|
|
|
}
|
|
|
|
|
2018-11-24 12:34:51 +00:00
|
|
|
type editPage struct {
|
2020-05-18 19:12:49 +00:00
|
|
|
pageBaseInfo
|
2020-07-19 13:09:15 +00:00
|
|
|
Session *Session
|
|
|
|
Title string
|
|
|
|
Content string
|
|
|
|
Name string
|
|
|
|
Editor template.HTML
|
|
|
|
Backrefs map[string][]Backref
|
|
|
|
ShowGraph bool
|
2020-07-19 13:18:03 +00:00
|
|
|
TodayPage string
|
2020-10-21 18:49:23 +00:00
|
|
|
Parent Parent
|
|
|
|
Parents []Parent
|
2018-11-24 12:34:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type historyPage struct {
|
2020-05-18 19:12:49 +00:00
|
|
|
pageBaseInfo
|
2018-11-24 12:34:51 +00:00
|
|
|
Session *Session
|
|
|
|
Title string
|
|
|
|
Name string
|
|
|
|
History []Revision
|
|
|
|
}
|
|
|
|
|
2019-02-18 19:47:31 +00:00
|
|
|
type recentPage struct {
|
2020-05-18 19:12:49 +00:00
|
|
|
pageBaseInfo
|
2019-02-18 19:47:31 +00:00
|
|
|
Session *Session
|
|
|
|
Title string
|
|
|
|
Name string
|
2019-02-19 06:34:52 +00:00
|
|
|
Recent []Change
|
2019-02-18 19:47:31 +00:00
|
|
|
}
|
|
|
|
|
2020-08-05 08:03:54 +00:00
|
|
|
var baseTemplate = []string{"templates/layout.html", "templates/sidebar.html"}
|
|
|
|
|
2018-11-24 12:34:51 +00:00
|
|
|
type indexHandler struct{}
|
2020-06-17 15:41:32 +00:00
|
|
|
type graphHandler struct{}
|
2020-07-01 14:40:10 +00:00
|
|
|
type saveHandler struct {
|
2020-06-30 20:56:12 +00:00
|
|
|
SearchIndex bleve.Index
|
|
|
|
}
|
2018-11-24 12:34:51 +00:00
|
|
|
type editHandler struct{}
|
|
|
|
type historyHandler struct{}
|
2019-02-18 19:47:31 +00:00
|
|
|
type recentHandler struct{}
|
2018-11-24 12:34:51 +00:00
|
|
|
type authHandler struct{}
|
|
|
|
|
|
|
|
func (*authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
|
defer r.Body.Close()
|
|
|
|
|
|
|
|
sess, err := NewSession(w, r)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
2019-02-25 18:03:04 +00:00
|
|
|
defer func() {
|
|
|
|
if err := sess.Flush(); err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
}
|
|
|
|
}()
|
2018-11-24 12:34:51 +00:00
|
|
|
|
|
|
|
if r.Method == http.MethodGet {
|
|
|
|
if r.URL.Path == "/auth/login" {
|
2020-08-05 08:03:54 +00:00
|
|
|
templates := baseTemplate
|
|
|
|
templates = append(templates, "templates/login.html")
|
|
|
|
t, err := template.ParseFiles(templates...)
|
2018-11-24 12:34:51 +00:00
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
err = t.Execute(w, nil)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
} else if r.URL.Path == "/auth/callback" {
|
2019-02-25 19:09:43 +00:00
|
|
|
code := r.URL.Query().Get("code")
|
|
|
|
state := r.URL.Query().Get("state")
|
2018-11-24 12:34:51 +00:00
|
|
|
|
|
|
|
if state != sess.State {
|
2019-02-25 19:09:43 +00:00
|
|
|
log.Printf("mismatched state: %s != %s", state, sess.State)
|
2018-11-24 12:34:51 +00:00
|
|
|
http.Error(w, "mismatched state", 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
authURL := sess.AuthorizationEndpoint
|
|
|
|
|
2020-05-18 19:58:44 +00:00
|
|
|
verified, response, err := indieauth.VerifyAuthCode(*baseurl, code, redirectURI, authURL)
|
2018-11-24 12:34:51 +00:00
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if !verified {
|
|
|
|
http.Redirect(w, r, "/auth/login", 302)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
sess.Me = response.Me
|
|
|
|
sess.LoggedIn = true
|
|
|
|
sess.State = ""
|
|
|
|
|
|
|
|
http.Redirect(w, r, sess.NextURI, 302)
|
|
|
|
return
|
|
|
|
} else if r.URL.Path == "/auth/logout" {
|
2020-08-05 08:03:54 +00:00
|
|
|
templates := baseTemplate
|
|
|
|
templates = append(templates, "templates/logout.html")
|
|
|
|
t, err := template.ParseFiles(templates...)
|
2018-11-24 12:34:51 +00:00
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
err = t.Execute(w, nil)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if r.Method == http.MethodPost {
|
|
|
|
if r.URL.Path == "/auth/login" {
|
|
|
|
r.ParseForm()
|
|
|
|
urlString := r.PostFormValue("url")
|
|
|
|
|
|
|
|
u, err := url.Parse(urlString)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 400)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
endpoints, err := indieauth.GetEndpoints(u)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
authURL, err := url.Parse(endpoints.AuthorizationEndpoint)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
state := util.RandStringBytes(32)
|
|
|
|
|
|
|
|
sess.AuthorizationEndpoint = authURL.String()
|
|
|
|
sess.Me = urlString
|
|
|
|
sess.LoggedIn = false
|
2020-05-18 19:58:44 +00:00
|
|
|
sess.RedirectURI = redirectURI
|
2018-11-24 12:34:51 +00:00
|
|
|
sess.NextURI = "/"
|
|
|
|
sess.State = state
|
|
|
|
|
2020-05-18 19:58:44 +00:00
|
|
|
newURL := indieauth.CreateAuthenticationURL(*authURL, urlString, *baseurl, redirectURI, state)
|
2018-11-24 12:34:51 +00:00
|
|
|
|
|
|
|
http.Redirect(w, r, newURL, 302)
|
|
|
|
return
|
|
|
|
} else if r.URL.Path == "/auth/logout" {
|
|
|
|
sess.LoggedIn = false
|
|
|
|
sess.Me = ""
|
|
|
|
sess.AuthorizationEndpoint = ""
|
|
|
|
http.Redirect(w, r, "/", 302)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *historyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
|
defer r.Body.Close()
|
|
|
|
|
|
|
|
sess, err := NewSession(w, r)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
2019-02-25 18:03:04 +00:00
|
|
|
defer func() {
|
|
|
|
if err := sess.Flush(); err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
}
|
|
|
|
}()
|
2018-11-24 12:34:51 +00:00
|
|
|
|
2019-02-05 20:22:16 +00:00
|
|
|
if !sess.LoggedIn {
|
|
|
|
http.Redirect(w, r, "/", http.StatusFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-11-24 12:34:51 +00:00
|
|
|
r.ParseForm()
|
|
|
|
page := r.URL.Path[9:]
|
2020-08-05 08:11:00 +00:00
|
|
|
page = resolvePageName(page)
|
2018-11-24 12:34:51 +00:00
|
|
|
|
|
|
|
history, err := mp.PageHistory(page)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
funcs := template.FuncMap{
|
2019-02-05 20:22:16 +00:00
|
|
|
"historyIndex": func(x int, history []Revision) int { return len(history) - x },
|
2018-11-24 12:34:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
t, err := template.New("layout.html").Funcs(funcs).ParseFiles("templates/layout.html", "templates/history.html")
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-05-18 19:49:57 +00:00
|
|
|
pageBase := getPageBase()
|
|
|
|
pageData := historyPage{
|
|
|
|
pageBaseInfo: pageBase,
|
|
|
|
Session: sess,
|
|
|
|
Title: "History of " + cleanTitle(page),
|
|
|
|
Name: page,
|
|
|
|
History: history,
|
|
|
|
}
|
|
|
|
|
|
|
|
err = t.Execute(w, pageData)
|
2018-11-24 12:34:51 +00:00
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-18 19:49:57 +00:00
|
|
|
func getPageBase() pageBaseInfo {
|
2020-05-18 19:58:44 +00:00
|
|
|
clientID := *baseurl
|
2020-05-18 19:49:57 +00:00
|
|
|
|
|
|
|
pageBase := pageBaseInfo{
|
|
|
|
BaseURL: clientID,
|
|
|
|
RedirectURI: redirectURI,
|
|
|
|
}
|
|
|
|
return pageBase
|
|
|
|
}
|
|
|
|
|
2018-11-24 12:34:51 +00:00
|
|
|
func (h *saveHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
|
defer r.Body.Close()
|
|
|
|
|
|
|
|
sess, err := NewSession(w, r)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
2020-07-01 14:40:10 +00:00
|
|
|
log.Println(err)
|
2018-11-24 12:34:51 +00:00
|
|
|
return
|
|
|
|
}
|
2019-02-25 18:03:04 +00:00
|
|
|
defer func() {
|
|
|
|
if err := sess.Flush(); err != nil {
|
|
|
|
log.Println(err)
|
2020-07-01 14:40:10 +00:00
|
|
|
log.Println(err)
|
2019-02-25 18:03:04 +00:00
|
|
|
}
|
|
|
|
}()
|
2018-11-24 12:34:51 +00:00
|
|
|
|
2019-02-05 20:22:16 +00:00
|
|
|
if !sess.LoggedIn {
|
|
|
|
http.Redirect(w, r, "/", http.StatusFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-08-25 10:30:00 +00:00
|
|
|
err = r.ParseForm()
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
2020-07-01 14:40:10 +00:00
|
|
|
log.Println(err)
|
2019-08-25 10:30:00 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
isJson := r.PostForm.Get("json") == "1"
|
2018-11-24 12:34:51 +00:00
|
|
|
page := r.PostForm.Get("p")
|
|
|
|
summary := r.PostForm.Get("summary")
|
|
|
|
|
2019-02-19 06:34:52 +00:00
|
|
|
pageData := Page{Content: r.PostForm.Get("content")}
|
|
|
|
err = mp.Save(page, pageData, summary, sess.Me)
|
|
|
|
if err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
}
|
2019-08-25 10:30:00 +00:00
|
|
|
|
|
|
|
if isJson {
|
|
|
|
fmt.Print(w, "{}")
|
|
|
|
} else {
|
|
|
|
http.Redirect(w, r, "/"+page, http.StatusFound)
|
|
|
|
}
|
2018-11-24 12:34:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (h *editHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
|
defer r.Body.Close()
|
|
|
|
|
|
|
|
sess, err := NewSession(w, r)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
2019-02-25 18:03:04 +00:00
|
|
|
defer func() {
|
|
|
|
if err := sess.Flush(); err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
}
|
|
|
|
}()
|
2018-11-24 12:34:51 +00:00
|
|
|
|
2019-02-05 20:22:16 +00:00
|
|
|
if !sess.LoggedIn {
|
|
|
|
http.Redirect(w, r, "/", http.StatusFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-11-24 12:34:51 +00:00
|
|
|
page := r.URL.Path[6:]
|
2020-08-05 08:11:00 +00:00
|
|
|
page = resolvePageName(page)
|
2018-11-24 12:34:51 +00:00
|
|
|
|
2020-05-08 15:21:39 +00:00
|
|
|
mpPage := mp.Get(page)
|
|
|
|
pageText := mpPage.Content
|
2018-11-24 12:34:51 +00:00
|
|
|
|
2020-05-12 14:12:33 +00:00
|
|
|
if pageText == "" {
|
|
|
|
pageText = "[]"
|
|
|
|
}
|
|
|
|
|
|
|
|
var rawMsg json.RawMessage
|
|
|
|
err = json.NewDecoder(strings.NewReader(pageText)).Decode(&rawMsg)
|
|
|
|
jsonEditor := pageText != "" && err == nil
|
2019-08-25 10:30:00 +00:00
|
|
|
|
|
|
|
var editor template.HTML
|
|
|
|
if jsonEditor {
|
2020-06-09 18:36:41 +00:00
|
|
|
editor, err = renderEditor(page, pageText, "json", *baseurl)
|
2019-08-25 10:30:00 +00:00
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
} else {
|
2019-08-25 10:56:11 +00:00
|
|
|
editor = template.HTML(fmt.Sprintf(`
|
|
|
|
<textarea name="content" rows="24" style="width:100%%">%s</textarea>
|
2019-08-25 10:30:00 +00:00
|
|
|
<br>
|
|
|
|
|
|
|
|
<div class="field">
|
|
|
|
<label class="label">Summary</label>
|
|
|
|
<div class="control">
|
|
|
|
<input type="text" name="summary" class="input" placeholder="Summary"/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<button class="button is-primary" type="submit">Save</button>
|
2019-08-25 10:56:11 +00:00
|
|
|
`, html.EscapeString(pageText)))
|
2019-08-25 10:30:00 +00:00
|
|
|
}
|
|
|
|
|
2020-05-18 19:49:57 +00:00
|
|
|
pageBase := getPageBase()
|
2020-10-21 18:49:23 +00:00
|
|
|
title := cleanTitle(mpPage.Title)
|
2020-07-19 15:10:59 +00:00
|
|
|
if newTitle, err := PageTitle(pageText); err == nil {
|
|
|
|
title = newTitle
|
|
|
|
}
|
|
|
|
|
2020-10-21 18:49:23 +00:00
|
|
|
var parent Parent
|
|
|
|
parent.ID = mpPage.Parent
|
|
|
|
parent.Text = mpPage.Blocks.Texts[parent.ID]
|
|
|
|
|
|
|
|
var parents []Parent
|
|
|
|
for _, p := range mpPage.Blocks.Parents {
|
|
|
|
var parent Parent
|
|
|
|
parent.ID = p
|
|
|
|
parent.Text = mpPage.Blocks.Texts[p]
|
|
|
|
parents = append(parents, parent)
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, j := 0, len(parents)-1; i < j; i, j = i+1, j-1 {
|
|
|
|
parents[i], parents[j] = parents[j], parents[i]
|
|
|
|
}
|
|
|
|
|
2018-11-24 12:34:51 +00:00
|
|
|
data := editPage{
|
2020-05-18 19:49:57 +00:00
|
|
|
pageBaseInfo: pageBase,
|
2020-05-18 19:58:44 +00:00
|
|
|
Session: sess,
|
2020-07-19 15:10:59 +00:00
|
|
|
Title: title,
|
2020-05-18 19:58:44 +00:00
|
|
|
Content: pageText,
|
|
|
|
Editor: editor,
|
|
|
|
Name: page,
|
|
|
|
Backrefs: mpPage.Refs,
|
2020-08-05 08:11:00 +00:00
|
|
|
TodayPage: "Today",
|
2020-07-19 13:09:15 +00:00
|
|
|
ShowGraph: page != "Daily_Notes",
|
2020-10-21 18:49:23 +00:00
|
|
|
Parent: parent,
|
|
|
|
Parents: parents,
|
2018-11-24 12:34:51 +00:00
|
|
|
}
|
2020-08-05 08:03:54 +00:00
|
|
|
templates := baseTemplate
|
2020-08-31 09:50:03 +00:00
|
|
|
templates = append(templates, "templates/edit.html")
|
2020-08-05 08:03:54 +00:00
|
|
|
t, err := template.ParseFiles(templates...)
|
2018-11-24 12:34:51 +00:00
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
2020-10-21 18:49:23 +00:00
|
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
|
|
w.Header().Set("Cache-Control", "public, max-age=600")
|
2018-11-24 12:34:51 +00:00
|
|
|
err = t.Execute(w, data)
|
|
|
|
if err != nil {
|
2020-07-19 13:18:03 +00:00
|
|
|
log.Println(err)
|
2018-11-24 12:34:51 +00:00
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-17 15:41:32 +00:00
|
|
|
func (h *graphHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
|
defer r.Body.Close()
|
|
|
|
|
|
|
|
sess, err := NewSession(w, r)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
if err := sess.Flush(); err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2020-07-04 13:03:37 +00:00
|
|
|
if !sess.LoggedIn {
|
|
|
|
http.Redirect(w, r, "/", http.StatusFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-07-05 12:12:15 +00:00
|
|
|
gb, err := NewGraphBuilder(mp)
|
2020-06-17 15:41:32 +00:00
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-07-05 18:34:36 +00:00
|
|
|
gb.prepareNodeMap()
|
|
|
|
|
2020-07-05 12:12:15 +00:00
|
|
|
gb.RemoveNode("DONE")
|
|
|
|
gb.RemoveNode("TODO")
|
|
|
|
gb.RemoveNode("Home")
|
|
|
|
gb.RemoveNode("Projects")
|
|
|
|
gb.RemoveNode("Notes")
|
|
|
|
gb.RemoveNode("Books")
|
|
|
|
gb.RemoveNode("Daily_Notes")
|
|
|
|
gb.RemoveNode("NewJsonTest")
|
|
|
|
gb.RemoveNodeWithSuffix("_2020")
|
2020-06-17 15:41:32 +00:00
|
|
|
|
2020-07-05 12:12:15 +00:00
|
|
|
err = gb.prepareGraph()
|
2020-06-17 15:41:32 +00:00
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var nodesBuf bytes.Buffer
|
|
|
|
var edgesBuf bytes.Buffer
|
|
|
|
|
2020-07-05 12:12:15 +00:00
|
|
|
err = json.NewEncoder(&nodesBuf).Encode(&gb.nodes)
|
2020-06-17 15:41:32 +00:00
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-07-05 12:12:15 +00:00
|
|
|
err = json.NewEncoder(&edgesBuf).Encode(&gb.edges)
|
2020-06-17 15:41:32 +00:00
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
pageBase := getPageBase()
|
|
|
|
data := graphPage{
|
|
|
|
pageBaseInfo: pageBase,
|
|
|
|
Session: sess,
|
|
|
|
Title: "Graph",
|
|
|
|
Name: "Graph",
|
|
|
|
Nodes: template.JS(nodesBuf.String()),
|
|
|
|
Edges: template.JS(edgesBuf.String()),
|
|
|
|
}
|
2020-08-05 08:03:54 +00:00
|
|
|
templates := baseTemplate
|
2020-08-31 09:50:03 +00:00
|
|
|
templates = append(templates, "templates/graph.html")
|
2020-08-05 08:03:54 +00:00
|
|
|
t, err := template.ParseFiles(templates...)
|
2020-06-17 15:41:32 +00:00
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
err = t.Execute(w, data)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-24 12:34:51 +00:00
|
|
|
func (h *indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
|
defer r.Body.Close()
|
|
|
|
|
2020-07-19 15:10:59 +00:00
|
|
|
metaKV, err := regexp.Compile(`(\w+)::\s+(.*)`)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2018-11-24 12:34:51 +00:00
|
|
|
sess, err := NewSession(w, r)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
2019-02-25 18:03:04 +00:00
|
|
|
defer func() {
|
|
|
|
if err := sess.Flush(); err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
}
|
|
|
|
}()
|
2018-11-24 12:34:51 +00:00
|
|
|
|
2020-07-04 13:03:37 +00:00
|
|
|
if !sess.LoggedIn {
|
2020-07-04 13:07:32 +00:00
|
|
|
http.Redirect(w, r, "/auth/login", http.StatusFound)
|
2020-07-04 13:03:37 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-06-11 20:20:17 +00:00
|
|
|
format := r.URL.Query().Get("format")
|
|
|
|
if format == "" {
|
|
|
|
format = "html"
|
|
|
|
}
|
|
|
|
|
2020-11-16 21:48:54 +00:00
|
|
|
if !(format == "html" || format == "markdown" || format == "hmarkdown" || format == "json" || format == "metakv") {
|
2020-06-11 20:20:17 +00:00
|
|
|
http.Error(w, "unknown format", http.StatusBadRequest)
|
|
|
|
}
|
|
|
|
|
2018-11-24 12:34:51 +00:00
|
|
|
page := r.URL.Path[1:]
|
2020-08-05 08:11:00 +00:00
|
|
|
page = resolvePageName(page)
|
2018-11-24 12:34:51 +00:00
|
|
|
|
2020-05-07 13:20:36 +00:00
|
|
|
mpPage := mp.Get(page)
|
|
|
|
pageText := mpPage.Content
|
2020-10-21 18:49:23 +00:00
|
|
|
if (format == "" || format == "html") && pageText == "" {
|
2018-11-24 12:34:51 +00:00
|
|
|
http.Redirect(w, r, "/edit/"+page, 302)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-05-12 14:12:33 +00:00
|
|
|
if pageText == "" {
|
|
|
|
pageText = "[]"
|
|
|
|
}
|
2018-11-24 12:34:51 +00:00
|
|
|
|
2020-05-12 14:12:33 +00:00
|
|
|
var rawMsg json.RawMessage
|
|
|
|
err = json.NewDecoder(strings.NewReader(pageText)).Decode(&rawMsg)
|
|
|
|
|
2020-07-19 15:10:59 +00:00
|
|
|
title := cleanTitle(page)
|
|
|
|
|
2020-05-12 14:12:33 +00:00
|
|
|
jsonPage := pageText != "" && err == nil
|
|
|
|
if jsonPage {
|
2020-08-31 09:50:03 +00:00
|
|
|
if format == "json" {
|
2020-10-21 18:49:23 +00:00
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
w.Header().Set("Cache-Control", "no-store")
|
2020-08-31 09:50:03 +00:00
|
|
|
// Shortcut for json output
|
|
|
|
_, err := io.WriteString(w, pageText)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
} else if format == "metakv" {
|
2020-10-21 18:49:23 +00:00
|
|
|
so, err := createStructuredFormat(mpPage)
|
2020-08-31 09:50:03 +00:00
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
2020-10-21 18:49:23 +00:00
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
w.Header().Set("Cache-Control", "no-store")
|
2020-08-31 09:50:03 +00:00
|
|
|
enc := json.NewEncoder(w)
|
|
|
|
enc.SetIndent("", " ")
|
|
|
|
err = enc.Encode(so)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-05-11 21:34:10 +00:00
|
|
|
var listItems []struct {
|
|
|
|
Indented int
|
|
|
|
Text string
|
|
|
|
}
|
|
|
|
|
|
|
|
err = json.NewDecoder(strings.NewReader(pageText)).Decode(&listItems)
|
2019-08-25 10:30:00 +00:00
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
2018-11-24 12:34:51 +00:00
|
|
|
}
|
2020-05-11 21:34:10 +00:00
|
|
|
|
2020-05-31 23:57:23 +00:00
|
|
|
builder := strings.Builder{}
|
2021-01-17 13:54:53 +00:00
|
|
|
if format == "markdown" || format == "html" {
|
2020-11-16 21:48:54 +00:00
|
|
|
for _, item := range listItems {
|
|
|
|
lines := strings.Split(item.Text, "\n")
|
|
|
|
if len(lines) > 1 {
|
|
|
|
first := true
|
|
|
|
for _, line := range lines {
|
|
|
|
if first {
|
|
|
|
builder.WriteString(strings.Repeat(" ", item.Indented))
|
|
|
|
builder.WriteString("* ")
|
|
|
|
builder.WriteString(line)
|
|
|
|
builder.WriteByte('\n')
|
|
|
|
first = false
|
|
|
|
} else {
|
|
|
|
builder.WriteString(strings.Repeat(" ", item.Indented+1))
|
|
|
|
builder.WriteString(line)
|
|
|
|
builder.WriteByte('\n')
|
|
|
|
}
|
2020-05-31 23:57:23 +00:00
|
|
|
}
|
2020-11-16 21:48:54 +00:00
|
|
|
} else {
|
|
|
|
if matches := metaKV.FindStringSubmatch(item.Text); matches != nil {
|
|
|
|
if matches[1] == "Title" {
|
|
|
|
title = matches[2]
|
|
|
|
}
|
2020-07-19 15:10:59 +00:00
|
|
|
}
|
2020-11-16 21:48:54 +00:00
|
|
|
builder.WriteString(strings.Repeat(" ", item.Indented))
|
|
|
|
builder.WriteString("* ")
|
|
|
|
builder.WriteString(item.Text)
|
|
|
|
builder.WriteByte('\n')
|
2020-07-19 15:10:59 +00:00
|
|
|
}
|
2020-11-16 21:48:54 +00:00
|
|
|
}
|
|
|
|
} else if format == "hmarkdown" {
|
|
|
|
for _, item := range listItems {
|
2020-05-31 23:57:23 +00:00
|
|
|
builder.WriteString(item.Text)
|
|
|
|
builder.WriteByte('\n')
|
2020-11-16 21:48:54 +00:00
|
|
|
builder.WriteByte('\n')
|
2020-05-31 23:57:23 +00:00
|
|
|
}
|
2020-05-11 21:34:10 +00:00
|
|
|
}
|
2020-05-31 23:57:23 +00:00
|
|
|
pageText = builder.String()
|
2019-08-25 10:30:00 +00:00
|
|
|
}
|
|
|
|
|
2020-06-11 20:20:17 +00:00
|
|
|
if format == "html" {
|
2020-07-01 15:36:36 +00:00
|
|
|
pageText = metaKV.ReplaceAllString(pageText, "**[[$1]]**: $2")
|
|
|
|
|
2020-07-05 18:34:36 +00:00
|
|
|
pageText = renderLinks(pageText, false)
|
2020-06-11 20:20:17 +00:00
|
|
|
pageText = renderMarkdown2(pageText)
|
2020-05-11 21:34:10 +00:00
|
|
|
|
2020-06-11 20:20:17 +00:00
|
|
|
pageBase := getPageBase()
|
|
|
|
data := indexPage{
|
|
|
|
pageBaseInfo: pageBase,
|
|
|
|
Session: sess,
|
2020-07-19 15:10:59 +00:00
|
|
|
Title: title,
|
2020-06-11 20:20:17 +00:00
|
|
|
Content: template.HTML(pageText),
|
|
|
|
Name: page,
|
|
|
|
Backrefs: mpPage.Refs,
|
2020-07-19 13:09:15 +00:00
|
|
|
ShowGraph: page != "Daily_Notes",
|
2020-08-05 08:11:00 +00:00
|
|
|
TodayPage: "Today",
|
2020-06-11 20:20:17 +00:00
|
|
|
}
|
2020-08-31 09:50:03 +00:00
|
|
|
templates := baseTemplate
|
|
|
|
templates = append(templates, "templates/view.html")
|
2020-08-05 08:03:54 +00:00
|
|
|
t, err := template.ParseFiles(templates...)
|
2020-06-11 20:20:17 +00:00
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
2020-10-21 18:49:23 +00:00
|
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
|
|
w.Header().Set("Cache-Control", "public, max-age=600")
|
2020-06-11 20:20:17 +00:00
|
|
|
err = t.Execute(w, data)
|
|
|
|
if err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
// http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
2020-11-16 21:48:54 +00:00
|
|
|
} else if format == "markdown" || format == "hmarkdown" {
|
2020-10-21 18:49:23 +00:00
|
|
|
w.Header().Set("Content-Type", "text/markdown")
|
|
|
|
w.Header().Set("Cache-Control", "public, max-age=600")
|
2020-06-11 20:20:17 +00:00
|
|
|
_, err = io.WriteString(w, "# ")
|
2020-07-19 15:10:59 +00:00
|
|
|
_, err = io.WriteString(w, title)
|
2020-06-11 20:20:17 +00:00
|
|
|
_, err = io.WriteString(w, "\n\n")
|
|
|
|
_, err = io.WriteString(w, pageText)
|
2018-11-24 12:34:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-05 18:34:36 +00:00
|
|
|
func renderLinks(pageText string, edit bool) string {
|
2020-05-18 20:09:48 +00:00
|
|
|
hrefRE, err := regexp.Compile(`#?\[\[\s*([^\]]+)\s*\]\]`)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
pageText = hrefRE.ReplaceAllStringFunc(pageText, func(s string) string {
|
|
|
|
tag := false
|
|
|
|
if s[0] == '#' {
|
|
|
|
s = strings.TrimPrefix(s, "#[[")
|
|
|
|
tag = true
|
|
|
|
} else {
|
|
|
|
s = strings.TrimPrefix(s, "[[")
|
|
|
|
}
|
|
|
|
s = strings.TrimSuffix(s, "]]")
|
|
|
|
s = strings.TrimSpace(s)
|
|
|
|
if tag {
|
|
|
|
return fmt.Sprintf(`<a href=%q class="tag">%s</a>`, cleanNameURL(s), s)
|
|
|
|
}
|
2020-07-05 18:34:36 +00:00
|
|
|
editPart := ""
|
|
|
|
if edit {
|
|
|
|
editPart = "edit/"
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Sprintf("[%s](/%s%s)", s, editPart, cleanNameURL(s))
|
2020-05-18 20:09:48 +00:00
|
|
|
})
|
|
|
|
return pageText
|
|
|
|
}
|
|
|
|
|
2019-02-18 19:47:31 +00:00
|
|
|
func (h *recentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
|
defer r.Body.Close()
|
|
|
|
|
|
|
|
sess, err := NewSession(w, r)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
2019-02-25 18:03:04 +00:00
|
|
|
defer func() {
|
|
|
|
if err := sess.Flush(); err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
}
|
|
|
|
}()
|
2019-02-18 19:47:31 +00:00
|
|
|
|
|
|
|
if !sess.LoggedIn {
|
|
|
|
http.Redirect(w, r, "/", http.StatusFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
r.ParseForm()
|
|
|
|
|
|
|
|
changes, err := mp.RecentChanges()
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-03-02 13:24:52 +00:00
|
|
|
// group recentchanges on Page
|
|
|
|
if len(changes) > 0 {
|
2019-03-03 07:30:04 +00:00
|
|
|
f := groupRecentChanges(changes)
|
2019-03-02 13:24:52 +00:00
|
|
|
changes = changes[f:]
|
|
|
|
}
|
|
|
|
|
2019-03-02 13:38:35 +00:00
|
|
|
for i, c := range changes {
|
2019-03-03 07:36:22 +00:00
|
|
|
if c.Count <= 1 {
|
2019-03-02 13:38:35 +00:00
|
|
|
changes[i].DateSummary = c.Date.Format("Mon, 2 Jan 2006")
|
|
|
|
changes[i].TimeSummary = c.Date.Format("15:04")
|
|
|
|
} else {
|
|
|
|
changes[i].DateSummary = c.Date.Format("Mon, 2 Jan 2006")
|
|
|
|
changes[i].TimeSummary = fmt.Sprintf("%s - %s", c.Date.Format("15:04"), c.EndDate.Format("15:04"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-18 19:47:31 +00:00
|
|
|
t, err := template.New("layout.html").ParseFiles("templates/layout.html", "templates/recent.html")
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-05-18 19:49:57 +00:00
|
|
|
pageBase := getPageBase()
|
2019-02-18 19:47:31 +00:00
|
|
|
err = t.Execute(w, recentPage{
|
2020-05-18 19:49:57 +00:00
|
|
|
pageBaseInfo: pageBase,
|
2020-05-18 19:58:44 +00:00
|
|
|
Session: sess,
|
|
|
|
Title: "Recent changes",
|
|
|
|
Name: "Recent changes",
|
|
|
|
Recent: changes,
|
2019-02-18 19:47:31 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-25 10:30:00 +00:00
|
|
|
type LinkResponseMetaImage struct {
|
|
|
|
Url string `json:"url"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type LinkResponseMeta struct {
|
|
|
|
Title string `json:"title"`
|
|
|
|
Description string `json:"description"`
|
|
|
|
Image LinkResponseMetaImage `json:"image"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type LinkResponse struct {
|
|
|
|
Success int `json:"success"`
|
|
|
|
Link string `json:"link"`
|
|
|
|
Meta LinkResponseMeta `json:"meta"`
|
|
|
|
}
|
|
|
|
|
2018-11-24 12:34:51 +00:00
|
|
|
func main() {
|
2019-02-18 19:47:55 +00:00
|
|
|
flag.Parse()
|
|
|
|
|
2020-05-18 19:58:44 +00:00
|
|
|
*baseurl = strings.TrimRight(*baseurl, "/") + "/"
|
|
|
|
redirectURI = fmt.Sprintf("%sauth/callback", *baseurl)
|
|
|
|
|
2020-07-01 14:40:10 +00:00
|
|
|
dataDir := "data"
|
|
|
|
searchIndex, err := createSearchIndex(dataDir, "_page-index")
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
2020-06-30 20:56:12 +00:00
|
|
|
}
|
|
|
|
defer searchIndex.Close()
|
|
|
|
|
|
|
|
sh, err := NewSearchHandler(searchIndex)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2020-07-01 14:40:10 +00:00
|
|
|
mp = NewFilePages(dataDir, searchIndex)
|
2018-11-24 12:34:51 +00:00
|
|
|
|
|
|
|
http.Handle("/auth/", &authHandler{})
|
2020-11-03 20:23:33 +00:00
|
|
|
http.HandleFunc("/api/document/append", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
err := r.ParseForm()
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 400)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
id := r.Form.Get("id")
|
|
|
|
if id == "" {
|
|
|
|
http.Error(w, "missing id", 400)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
page := mp.Get(id)
|
|
|
|
log.Println(page.Content)
|
|
|
|
var listItems []ListItem
|
|
|
|
|
|
|
|
err = json.NewDecoder(strings.NewReader(page.Content)).Decode(&listItems)
|
|
|
|
if err != nil && err != io.EOF {
|
|
|
|
http.Error(w, fmt.Sprintf("while decoding: %s", err.Error()), 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
newId := &ID{"1", true}
|
|
|
|
listItems = append(listItems, ListItem{
|
|
|
|
ID: newId.NewID(),
|
|
|
|
Indented: 0,
|
|
|
|
Text: r.Form.Get("text"),
|
|
|
|
Fleeting: false,
|
|
|
|
})
|
|
|
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
|
|
|
|
err = json.NewEncoder(&buf).Encode(&listItems)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, fmt.Sprintf("while encoding: %s", err.Error()), 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
page.Content = buf.String()
|
|
|
|
page.Name = id
|
|
|
|
page.Title = id
|
|
|
|
|
|
|
|
err = mp.Save(id, page, "", "")
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, fmt.Sprintf("while saving: %s", err.Error()), 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return
|
|
|
|
})
|
2020-05-31 20:08:36 +00:00
|
|
|
http.HandleFunc("/links.json", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
2020-07-01 14:40:10 +00:00
|
|
|
http.ServeFile(w, r, filepath.Join(dataDir, LinksFile))
|
2020-05-31 20:08:36 +00:00
|
|
|
})
|
2020-07-05 18:34:36 +00:00
|
|
|
http.HandleFunc("/api/graph", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
gb, err := NewGraphBuilder(mp)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-07-19 15:10:59 +00:00
|
|
|
for _, name := range r.URL.Query()["name"] {
|
|
|
|
err = gb.buildFromCenter(name)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
2020-07-05 18:34:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Keep a copy of the nodes, buildFromCenter appends to the nodeMap
|
|
|
|
var nodes []string
|
|
|
|
for k, _ := range gb.nodeMap {
|
|
|
|
nodes = append(nodes, k)
|
|
|
|
}
|
|
|
|
|
2020-07-07 22:45:06 +00:00
|
|
|
for _, node := range nodes {
|
|
|
|
err = gb.buildFromCenter(node)
|
2020-07-05 18:34:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
err = gb.prepareGraph()
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
type data struct {
|
|
|
|
Nodes []Node `json:"nodes"`
|
|
|
|
Edges []Edge `json:"edges"`
|
|
|
|
}
|
|
|
|
|
|
|
|
err = json.NewEncoder(w).Encode(&data{gb.nodes, gb.edges})
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
})
|
2020-06-30 20:56:12 +00:00
|
|
|
http.Handle("/search/", sh)
|
2019-08-25 10:46:48 +00:00
|
|
|
http.Handle("/public/", http.StripPrefix("/public/", http.FileServer(http.Dir("./dist"))))
|
2018-11-24 12:34:51 +00:00
|
|
|
http.Handle("/save/", &saveHandler{})
|
|
|
|
http.Handle("/edit/", &editHandler{})
|
|
|
|
http.Handle("/history/", &historyHandler{})
|
2019-02-18 19:47:31 +00:00
|
|
|
http.Handle("/recent/", &recentHandler{})
|
2020-06-17 15:41:32 +00:00
|
|
|
http.Handle("/graph/", &graphHandler{})
|
2018-11-24 12:34:51 +00:00
|
|
|
http.Handle("/", &indexHandler{})
|
|
|
|
|
2020-05-18 19:58:44 +00:00
|
|
|
fmt.Printf("Running on port %d\n", *port)
|
|
|
|
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
|
2018-11-24 12:34:51 +00:00
|
|
|
}
|
2020-07-01 14:40:10 +00:00
|
|
|
|
|
|
|
func createSearchIndex(dataDir, indexName string) (bleve.Index, error) {
|
|
|
|
indexDir := filepath.Join(dataDir, indexName)
|
|
|
|
if _, err := os.Stat(indexDir); os.IsNotExist(err) {
|
|
|
|
indexMapping := bleve.NewIndexMapping()
|
2020-10-21 18:49:23 +00:00
|
|
|
documentMapping := bleve.NewDocumentMapping()
|
|
|
|
|
|
|
|
nameFieldMapping := bleve.NewTextFieldMapping()
|
|
|
|
nameFieldMapping.Store = true
|
|
|
|
documentMapping.AddFieldMappingsAt("name", nameFieldMapping)
|
|
|
|
|
|
|
|
titleFieldMapping := bleve.NewTextFieldMapping()
|
|
|
|
titleFieldMapping.Store = true
|
|
|
|
documentMapping.AddFieldMappingsAt("title", titleFieldMapping)
|
|
|
|
|
2020-10-30 23:29:41 +00:00
|
|
|
linkFieldMapping := bleve.NewTextFieldMapping()
|
|
|
|
linkFieldMapping.Store = true
|
|
|
|
documentMapping.AddFieldMappingsAt("link", linkFieldMapping)
|
|
|
|
|
2020-10-21 18:49:23 +00:00
|
|
|
indexMapping.AddDocumentMapping("block", documentMapping)
|
|
|
|
|
2020-07-01 14:40:10 +00:00
|
|
|
searchIndex, err := bleve.New(indexDir, indexMapping)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
fp := NewFilePages(dataDir, nil)
|
|
|
|
pages, err := fp.AllPages()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, page := range pages {
|
2020-10-21 18:49:23 +00:00
|
|
|
searchObjects, err := createSearchObjects(page.Name)
|
2020-07-01 14:40:10 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-10-21 18:49:23 +00:00
|
|
|
for _, so := range searchObjects {
|
|
|
|
err = searchIndex.Index(so.ID, so)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-07-01 14:40:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return searchIndex, nil
|
|
|
|
} else {
|
|
|
|
searchIndex, err := bleve.Open(indexDir)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return searchIndex, nil
|
|
|
|
}
|
|
|
|
}
|
2020-08-05 08:11:00 +00:00
|
|
|
|
|
|
|
func resolvePageName(name string) string {
|
|
|
|
if name == "" {
|
|
|
|
return "Home"
|
|
|
|
}
|
|
|
|
if name == "Today" {
|
|
|
|
return todayPage()
|
|
|
|
}
|
|
|
|
return name
|
|
|
|
}
|