Add minimal markup

This commit is contained in:
2025-09-13 10:24:08 +09:00
parent 35054e6a0d
commit f621154431
9 changed files with 291 additions and 45 deletions

View File

@@ -11,7 +11,7 @@ import (
"github.com/akikareha/himewiki/internal/format"
)
func render(text string) string {
func render(text string) (string, string) {
if strings.HasPrefix(text, "=") {
return format.Creole(text)
} else if strings.HasPrefix(text, "#") {
@@ -30,7 +30,7 @@ func View(cfg *config.Config, w http.ResponseWriter, r *http.Request, params *Pa
tmpl := template.Must(template.ParseFiles("templates/view.html"))
escaped := url.PathEscape(params.Name)
rendered := render(content)
_, rendered := render(content)
tmpl.Execute(w, struct {
SiteName string
Name string
@@ -47,32 +47,35 @@ func View(cfg *config.Config, w http.ResponseWriter, r *http.Request, params *Pa
func Edit(cfg *config.Config, w http.ResponseWriter, r *http.Request, params *Params) {
var previewed bool
var content string
var preview string
var save string
if r.Method != http.MethodPost {
previewed = false
content, _ = data.Load(params.Name)
preview = ""
save = ""
} else {
previewed = r.FormValue("previewed") == "true"
content = r.FormValue("content")
preview := r.FormValue("preview")
save := r.FormValue("save")
if previewed && save != "" {
if err := data.Save(params.Name, content); err != nil {
http.Error(w, "Failed to save", http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/"+url.PathEscape(params.Name), http.StatusFound)
return
} else if preview != "" {
previewed = true
} else {
http.Error(w, "Invalid state", http.StatusInternalServerError)
preview = r.FormValue("preview")
save = r.FormValue("save")
}
normalized, rendered := render(content)
if previewed && save != "" {
if err := data.Save(params.Name, normalized); err != nil {
http.Error(w, "Failed to save", http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/"+url.PathEscape(params.Name), http.StatusFound)
return
} else if preview != "" {
previewed = true
}
tmpl := template.Must(template.ParseFiles("templates/edit.html"))
escaped := url.PathEscape(params.Name)
rendered := render(content)
tmpl.Execute(w, struct {
SiteName string
Name string
@@ -84,7 +87,7 @@ func Edit(cfg *config.Config, w http.ResponseWriter, r *http.Request, params *Pa
SiteName: cfg.Site.Name,
Name: params.Name,
Escaped: escaped,
Text: content,
Text: normalized,
Rendered: template.HTML(rendered),
Previewed: previewed,
})

View File

@@ -66,7 +66,7 @@ func ViewRevision(cfg *config.Config, w http.ResponseWriter, r *http.Request, pa
tmpl := template.Must(template.ParseFiles("templates/revision.html"))
escaped := url.PathEscape(params.Name)
rendered := render(content)
_, rendered := render(content)
tmpl.Execute(w, struct {
SiteName string
Name string

View File

@@ -1,6 +1,5 @@
package format
func Creole(text string) string {
// TODO
return escapeHTML("(Creole)\n" + text)
func Creole(text string) (string, string) {
return Nomark(text) // TODO
}

View File

@@ -1,6 +1,5 @@
package format
func Markdown(text string) string {
// TODO
return escapeHTML("(Markdown)\n" + text)
func Markdown(text string) (string, string) {
return Nomark(text) // TODO
}

View File

@@ -1,6 +1,260 @@
package format
func Nomark(text string) string {
// TODO
return escapeHTML("(Nomark)\n" + text)
import (
"bytes"
"html/template"
"net/url"
)
type blockMode int
const (
blockNone blockMode = iota
blockParagraph
)
type state struct {
data []byte
index int
text *bytes.Buffer
html *bytes.Buffer
block blockMode
nextLine int
lineEnd int
}
func skip(s *state) {
for s.index < len(s.data) {
c := s.data[s.index]
if c != '\r' && c != '\n' && c != ' ' && c != '\t' {
break
}
s.index += 1
}
}
func skipEnd(s *state) {
i := s.index
for i < len(s.data) {
c := s.data[i]
if c != '\r' && c != '\n' && c != ' ' && c != '\t' {
break
}
i += 1
}
if i >= len(s.data) {
s.index = i
}
}
func nextLine(s *state) {
if s.nextLine < s.index {
s.nextLine = s.index
s.lineEnd = s.index
}
s.index = s.nextLine
if s.nextLine >= len(s.data) {
return
}
i := bytes.IndexByte(s.data[s.nextLine:], '\n')
if i == -1 {
s.nextLine = len(s.data)
s.lineEnd = len(s.data)
} else {
end := s.nextLine + i
s.nextLine = end + 1
if end > 0 && s.data[end - 1] == '\r' {
end -= 1
}
s.lineEnd = end
}
}
func blockEnd(s *state) {
if s.block == blockParagraph {
s.text.WriteString("\n")
s.html.WriteString("\n</p>")
skip(s)
if s.index < len(s.data) {
s.text.WriteString("\n")
s.html.WriteString("\n")
}
}
s.block = blockNone
}
func blockBegin(s *state, block blockMode) {
blockEnd(s)
if block == blockParagraph {
s.html.WriteString("<p>\n")
}
s.block = block
}
func ignore(s *state) bool {
c := s.data[s.index]
if c == '\r' {
s.index += 1
return true
}
return false
}
func wikiLink(s *state) bool {
line := s.data[s.index:s.lineEnd]
if !bytes.HasPrefix(line, []byte("[[")) {
return false
}
ket := bytes.Index(line[2:], []byte("]]"))
if ket == -1 {
return false
}
name := string(line[2:2 + ket])
s.text.WriteString("[[" + name + "]]")
s.html.WriteString("<a href=\"/" + url.PathEscape(name) + "\">" + template.HTMLEscapeString(name) + "</a>")
s.index += 2 + ket + 2
return true
}
func spaceIndex(b []byte) int {
for i := 0; i < len(b) ; i += 1 {
c := b[i]
if c == '\r' || c == '\n' || c == ' ' || c == '\t' {
return i
}
}
return len(b)
}
func link(s *state) bool {
line := s.data[s.index:s.lineEnd]
if !bytes.HasPrefix(line, []byte("https:")) {
return false
}
space := spaceIndex(line[6:])
rawURL := string(line[:6 + space])
u, err := url.Parse(rawURL)
if err != nil || u.Scheme != "https" {
return false
}
checked := u.String()
s.text.WriteString(checked)
htmlURL := template.HTMLEscapeString(checked)
s.html.WriteString("<a href=\"" + htmlURL + "\">" + htmlURL + "</a>")
s.index += len(rawURL)
return true
}
func html(s *state) bool {
c := s.data[s.index]
if c == '&' {
s.index += 1
s.text.WriteString("&")
s.html.WriteString("&amp;")
return true
} else if c == '<' {
s.index += 1
s.text.WriteString("<")
s.html.WriteString("&lt;")
return true
} else if c == '>' {
s.index += 1
s.text.WriteString(">")
s.html.WriteString("&gt;")
return true
} else if c == '"' {
s.index += 1
s.text.WriteString("\"")
s.html.WriteString("&quot;")
return true
}
return false
}
func raw(s *state) bool {
c := s.data[s.index]
s.index += 1
s.text.WriteByte(c)
s.html.WriteByte(c)
return true
}
func isBlank(b []byte) bool {
for _, c := range b {
if c != '\r' && c != '\n' && c != ' ' && c != '\t' {
return false
}
}
return true
}
func nomarkLine(s *state) {
for s.index < s.lineEnd {
if ignore(s) {
continue
} else if wikiLink(s) {
continue
} else if link(s) {
continue
} else if html(s) {
continue
} else if raw(s) {
continue
} else {
panic("program error")
}
}
nextLine(s)
skipEnd(s)
if s.index < len(s.data) {
line := s.data[s.index:s.lineEnd]
if !isBlank(line) {
s.text.WriteString("\n")
s.html.WriteString("<br />\n")
}
}
}
func Nomark(text string) (string, string) {
d := []byte(text)
s := state {
data: d,
index: 0,
text: new(bytes.Buffer),
html: new(bytes.Buffer),
block: blockNone,
nextLine: 0,
lineEnd: 0,
}
skip(&s)
blockBegin(&s, blockParagraph)
nextLine(&s)
for s.index < len(s.data) {
line := s.data[s.index:s.lineEnd]
if isBlank(line) {
blockEnd(&s)
blockBegin(&s, blockParagraph)
for s.index < len(s.data) {
nextLine(&s)
if s.index >= len(s.data) {
break
}
line := s.data[s.index:s.lineEnd]
if !isBlank(line) {
break
}
}
} else {
nomarkLine(&s)
}
}
blockEnd(&s)
return s.text.String(), s.html.String()
}

View File

@@ -1,15 +0,0 @@
package format
import (
"strings"
)
func escapeHTML(s string) string {
r := strings.NewReplacer(
"&", "&amp;",
"<", "&lt;",
">", "&gt;",
"\"", "&quot;",
)
return r.Replace(s)
}

View File

@@ -28,7 +28,9 @@
{{if .Previewed}}
<h1><a href="/?a=search&t=content&w={{.Name | urlquery}}">{{.Name}}</a></h1>
<div>{{.Rendered}}</div>
<div>
{{.Rendered}}
</div>
{{end}}
</body>

View File

@@ -17,7 +17,9 @@
<h1>Old Revision - <a href="/?a=search&t=content&w={{.Name | urlquery}}">{{.Name}}</a></h1>
<div>{{.Rendered}}</div>
<div>
{{.Rendered}}
</div>
<form action="/{{.Escaped}}?a=revert&i={{.ID}}" method="POST">
<input type="submit" name="revert" value="Revert" />

View File

@@ -18,7 +18,9 @@
<h1><a href="/?a=search&t=content&w={{.Name | urlquery}}">{{.Name}}</a></h1>
<div>{{.Rendered}}</div>
<div>
{{.Rendered}}
</div>
</body>
</html>