10 Commits

15 changed files with 115 additions and 40 deletions

2
DB.go
View File

@@ -92,7 +92,7 @@ func (this *WikiDB) GetRevision(revId int) (*Article, error) {
}
func (this *WikiDB) GetLatestVersion(title string) (*Article, error) {
row := this.db.QueryRow(`SELECT articles.* FROM articles WHERE article = (SELECT id FROM titles WHERE title = ?) ORDER BY modified DESC LIMIT 1`, title)
row := this.db.QueryRow(`SELECT articles.* FROM articles WHERE article = (SELECT id FROM titles WHERE title = ?) ORDER BY modified DESC LIMIT 1`, this.normaliseTitle(title))
return this.parseArticle(row)
}

View File

@@ -2,7 +2,7 @@
# Makefile for YATWiki3
#
VERSION:=3.1.2
VERSION:=3.2.0
SOURCES:=Makefile \
static \

View File

@@ -22,6 +22,7 @@ type ServerOptions struct {
DeclareRSSLanguage string
DeclareRSSEmail string
ContentedServer string
ContentedBBCodeTag string
}
func DefaultOptions() *ServerOptions {
@@ -43,5 +44,6 @@ func DefaultOptions() *ServerOptions {
DeclareRSSLanguage: "en-GB",
DeclareRSSEmail: `nobody@example.com`,
ContentedServer: "",
ContentedBBCodeTag: "",
}
}

View File

@@ -76,6 +76,10 @@ func NewWikiServer(opts *ServerOptions) (*WikiServer, error) {
return &ws, nil
}
func (this *WikiServer) GetBBCodeRenderer() *BBCodeRenderer {
return NewBBCodeRenderer(this.opts.ExpectBaseURL, this.opts.ContentedServer, this.opts.ContentedBBCodeTag)
}
func (this *WikiServer) Close() {
this.db.Close()
}

View File

@@ -36,6 +36,16 @@ This package can be installed via go get: `go get code.ivysaur.me/yatwiki`
=CHANGELOG=
2017-11-18 3.2.0
- Feature: Add new ContentedBBCodeTag option to choose a BBCode tag for mini thumbnails (requires `contented` >= 1.2.0)
- Feature: Replace menu image with SVG, for high-DPI screens
- Feature: Hover over timestamps to display in more detail
- Feature: Link to diff pages directly from the Recent Changes page
- Fix some cosmetic issues with the Recent Changes page
2017-10-29 3.1.3
- Fix one more case of article title normalisation
2017-10-29 3.1.2
- Lock dependency versions
- Enhancement: Advertise build number in Server headers

View File

@@ -2,6 +2,7 @@ package yatwiki
import (
"encoding/json"
"html"
"html/template"
"net/url"
"regexp"
@@ -13,13 +14,17 @@ type BBCodeRenderer struct {
baseUrl string
CodePresent bool
DynamicContentWarning string
ContentedURL string
ContentedTag string
}
func NewBBCodeRenderer(baseUrl string) *BBCodeRenderer {
func NewBBCodeRenderer(baseUrl, ContentedURL, ContentedTag string) *BBCodeRenderer {
return &BBCodeRenderer{
baseUrl: baseUrl,
CodePresent: false,
DynamicContentWarning: `⚠ run dynamic content`,
ContentedURL: ContentedURL,
ContentedTag: ContentedTag,
}
}
@@ -61,6 +66,15 @@ func (this *BBCodeRenderer) bbcode(data string) string {
}},
}
if len(this.ContentedTag) > 0 {
s_to_r = append(s_to_r,
pregReplaceRule{regexp.MustCompile(`(?si)\[` + regexp.QuoteMeta(this.ContentedTag) + `\](.*?)\[/` + regexp.QuoteMeta(this.ContentedTag) + `\]`),
`<a href="` + html.EscapeString(this.ContentedURL) + `p/${1}"><img class="imgur" alt="" src="` + html.EscapeString(this.ContentedURL) + `thumb/s/${1}" ></a>`,
nil,
},
)
}
for _, prr := range s_to_r {
for prr.match.MatchString(data) { // repeat until all recursive replacements are consumed

View File

@@ -87,23 +87,47 @@ function els(e,s){ // no js exec in innerHTML
</head>
<body>
<div class="header">
<a href="{{.BaseURL}}view/{{.DefaultPage | pathcomponent}}" title="Home"><div class="sprite hm"></div></a>
<a href="javascript:;" onclick="tid('spm');tid('tr1');tid('tr2');" title="Menu"><div class="sprite sp"></div></a>
<a href="{{.BaseURL}}modify/{{.NewArticleTitle | pathcomponent}}" title="New Page"><div class="sprite nw"></div></a>
<a href="{{.BaseURL}}view/{{.DefaultPage | pathcomponent}}" title="Home"><div class="sprite">
<svg viewBox="0 0 24 24">
<path d="M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z" />
</svg>
</div></a>
<a href="javascript:;" onclick="tid('spm');tid('tr1');tid('tr2');" title="Menu"><div class="sprite">
<svg viewBox="0 0 24 24">
<path d="M12,17.27L18.18,21L16.54,13.97L22,9.24L14.81,8.62L12,2L9.19,8.62L2,9.24L7.45,13.97L5.82,21L12,17.27Z" />
</svg>
</div></a>
<a href="{{.BaseURL}}modify/{{.NewArticleTitle | pathcomponent}}" title="New Page"><div class="sprite">
<svg viewBox="0 0 24 24">
<path d="M13,9V3.5L18.5,9M6,2C4.89,2 4,2.89 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2H6Z" />
</svg>
</div></a>
{{if .CurrentPageIsArticle }}
<div class="sep"></div>
<a href="{{.BaseURL}}history/{{.CurrentPageName | pathcomponent}}" title="Page History"><div class="sprite hs"></div></a>
<a href="{{.BaseURL}}modify/{{.CurrentPageName | pathcomponent}}" title="Modify Page"><div class="sprite ed"></div></a>
<a href="{{.BaseURL}}history/{{.CurrentPageName | pathcomponent}}" title="Page History"><div class="sprite">
<svg viewBox="0 0 24 24">
<path d="M15,13H16.5V15.82L18.94,17.23L18.19,18.53L15,16.69V13M19,8H5V19H9.67C9.24,18.09 9,17.07 9,16A7,7 0 0,1 16,9C17.07,9 18.09,9.24 19,9.67V8M5,21C3.89,21 3,20.1 3,19V5C3,3.89 3.89,3 5,3H6V1H8V3H16V1H18V3H19A2,2 0 0,1 21,5V11.1C22.24,12.36 23,14.09 23,16A7,7 0 0,1 16,23C14.09,23 12.36,22.24 11.1,21H5M16,11.15A4.85,4.85 0 0,0 11.15,16C11.15,18.68 13.32,20.85 16,20.85A4.85,4.85 0 0,0 20.85,16C20.85,13.32 18.68,11.15 16,11.15Z" />
</svg>
</div></a>
<a href="{{.BaseURL}}modify/{{.CurrentPageName | pathcomponent}}" title="Modify Page"><div class="sprite">
<svg viewBox="0 0 24 24">
<path d="M20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18,2.9 17.35,2.9 16.96,3.29L15.12,5.12L18.87,8.87M3,17.25V21H6.75L17.81,9.93L14.06,6.18L3,17.25Z" />
</svg>
</div></a>
{{end}}
</div>
<div id="tr1" style="display:none;"></div>
<div id="tr2" style="display:none;"></div>
<div class="ddmenu" id="spm" style="display:none;">
<a href="{{.BaseURL}}recent/1"><div class="sprite no"></div> Recent Changes</a>
<a href="{{.BaseURL}}random"><div class="sprite rn"></div> Random Page</a>
<a href="{{.BaseURL}}index"><div class="sprite no"></div> Article Index</a>
<a href="{{.BaseURL}}recent/1"><div class="sprite"></div> Recent Changes</a>
<a href="{{.BaseURL}}random"><div class="sprite">
<svg viewBox="0 0 24 24">
<path d="M12,18A6,6 0 0,1 6,12C6,11 6.25,10.03 6.7,9.2L5.24,7.74C4.46,8.97 4,10.43 4,12A8,8 0 0,0 12,20V23L16,19L12,15M12,4V1L8,5L12,9V6A6,6 0 0,1 18,12C18,13 17.75,13.97 17.3,14.8L18.76,16.26C19.54,15.03 20,13.57 20,12A8,8 0 0,0 12,4Z" />
</svg>
</div> Random Page</a>
<a href="{{.BaseURL}}index"><div class="sprite"></div> Article Index</a>
{{if .AllowDownload}}
<a href="{{.BaseURL}}download-database" download><div class="sprite no"></div> Download DB backup</a>
<a href="{{.BaseURL}}download-database" download><div class="sprite"></div> Download DB backup</a>
{{end}}
</div>
<div class="content">

View File

@@ -25,7 +25,7 @@ func (this *WikiServer) routeArchive(w http.ResponseWriter, r *http.Request, rev
pto.CurrentPageName = a.Title
pto.CurrentPageIsArticle = true
bcr := NewBBCodeRenderer(this.opts.ExpectBaseURL)
bcr := this.GetBBCodeRenderer()
pto.Content = template.HTML(
`<div class="info">`+
`You are viewing specific revision of this page, last modified `+

View File

@@ -47,9 +47,11 @@ func (this *WikiServer) servePageResponse(w http.ResponseWriter, r *http.Request
}
}
func (this *WikiServer) formatTimestamp(m int64) string {
func (this *WikiServer) formatTimestamp(m int64) template.HTML {
// TODO add a more detailed timestamp on hover
return template.HTMLEscapeString(time.Unix(m, 0).In(this.loc).Format(this.opts.DateFormat))
dt := time.Unix(m, 0).In(this.loc)
return template.HTML(`<span title="` + template.HTMLEscapeString(dt.Format(time.RFC3339)) + `">` + template.HTMLEscapeString(dt.Format(this.opts.DateFormat)) + `</span>`)
}
func (this *WikiServer) viewLink(articleTitle string) template.HTML {

View File

@@ -1,13 +1,15 @@
package yatwiki
import (
"html/template"
"net/http"
)
func (this *WikiServer) routeFormatting(w http.ResponseWriter, r *http.Request) {
pto := DefaultPageTemplateOptions(this.opts)
pto.CurrentPageName = "Formatting help"
pto.Content = `
content := `
<h2>Formatting help</h2><br><br>
<ul>
<li>[h]header[/h]</li>
@@ -21,9 +23,20 @@ func (this *WikiServer) routeFormatting(w http.ResponseWriter, r *http.Request)
<li>[article=page name]title[/article] or [rev=id]title[/rev]</li>
<li>[img]image-url[/img]</li>
<li>[imgur]asdf.jpg[/imgur]</li>
`
if len(this.opts.ContentedBBCodeTag) > 0 {
content += `
<li>[` + this.opts.ContentedBBCodeTag + `]abc[/` + this.opts.ContentedBBCodeTag + `]</li>
`
}
content += `
<li>[code]fixed width[/code]</li>
<li>[section=header]content[/section]</li>
<li>[html]raw html[/html]</li>
</ul>`
pto.Content = template.HTML(content)
this.servePageResponse(w, r, pto)
}

View File

@@ -39,7 +39,7 @@ func (this *WikiServer) routeHistory(w http.ResponseWriter, r *http.Request, art
for _, rev := range revs {
revIdStr := fmt.Sprintf("%d", rev.ID)
content += `<tr>` +
`<td><a href="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`archive/`+revIdStr) + `">` + this.formatTimestamp(rev.Modified) + `</a></td>` +
`<td><a href="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`archive/`+revIdStr) + `">` + string(this.formatTimestamp(rev.Modified)) + `</a></td>` +
`<td>` + template.HTMLEscapeString(rev.Author) + `</td>` +
`<td><input type="radio" name="t" value="` + revIdStr + `">&nbsp;<input type="radio" name="f" value="` + revIdStr + `"></td>` +
`</tr>`

View File

@@ -37,25 +37,41 @@ func (this *WikiServer) routeRecentChanges(w http.ResponseWriter, r *http.Reques
pto.CurrentPageName = "Recent Changes"
content := `<h2>Recent Changes</h2><br>` +
`<em>Showing up to ` + fmt.Sprintf("%d", this.opts.RecentChanges) + ` changes.</em><br>` +
`<table>`
`<em>Showing up to ` + fmt.Sprintf("%d", this.opts.RecentChanges) + ` changes.</em><br><br>` +
`<div style="display:inline-block;">` +
`<table class="ti">` +
`<tr><td>Page</td><td>Actions</td><td>Time</td><td>Author</td></tr>`
for _, rev := range recents {
diffHtml := ""
diffRev, err := this.db.GetNextOldestRevision(int(rev.ID))
if err != nil {
diffHtml = `[new]`
} else {
diffHtml = `<a href="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`diff/`+fmt.Sprintf("%d/%d", diffRev, rev.ID)) + `">diff</a>`
}
content += `<tr>` +
`<td><a href="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`view/`+url.PathEscape(rev.Title)) + `">` + template.HTMLEscapeString(rev.Title) + `</a>` +
` [<a href="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`archive/`+fmt.Sprintf("%d", rev.ID)) + `">a</a>]` +
`<td><a href="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`view/`+url.PathEscape(rev.Title)) + `">` + template.HTMLEscapeString(rev.Title) + `</a></td>` +
`<td>` +
`<a href="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`archive/`+fmt.Sprintf("%d", rev.ID)) + `">rev</a> &nbsp; ` +
diffHtml +
`</td>` +
`<td>` + this.formatTimestamp(rev.Modified) + ` by ` + template.HTMLEscapeString(rev.Author) + `</td>` +
`</td>` +
`<td>` + string(this.formatTimestamp(rev.Modified)) + `</td>` +
`<td>` + template.HTMLEscapeString(rev.Author) + `</td>` +
`</tr>`
}
content += `<tr><td>`
content += `</table>`
if pageNum > 1 {
content += `<a href="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`recent/`+fmt.Sprintf("%d", pageNum-1)) + `">&laquo; Newer</a>`
content += `<span style="float:left;"><a href="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`recent/`+fmt.Sprintf("%d", pageNum-1)) + `">&laquo; Newer</a></span>`
}
content += `</td><td></td><td style="text-align:right;">`
if pageNum < maxPage {
content += `<a href="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`recent/`+fmt.Sprintf("%d", pageNum+1)) + `">Older &raquo;</a>`
content += `<span style="float:right;"><a href="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`recent/`+fmt.Sprintf("%d", pageNum+1)) + `">Older &raquo;</a></span>`
}
content += `</td></tr></table>`
content += `</div>`
pto.Content = template.HTML(content)
this.servePageResponse(w, r, pto)

View File

@@ -30,7 +30,7 @@ func (this *WikiServer) routeView(w http.ResponseWriter, r *http.Request, articl
pto.CurrentPageName = articleTitle
pto.CurrentPageIsArticle = true
bcr := NewBBCodeRenderer(this.opts.ExpectBaseURL)
bcr := this.GetBBCodeRenderer()
pto.Content = bcr.RenderHTML(string(a.Body))
pto.LoadCodeResources = bcr.CodePresent

View File

@@ -112,16 +112,6 @@ fieldset legend {
display:inline-block;
width:16px;height:16px;
vertical-align:text-bottom;
background-repeat:no-repeat;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAwCAYAAABwrHhvAAAGS0lEQVRYw52Y32sVVxDHl4JI+2ApwUj7YCmC0ioJIVQI0YpSUwlRoxQlInmQoBEjBpsI/gR/Iv5Co9GYqKiXoCCiL0pBRUViNBA3EBAlBIwvvvsPnJ7P4cwyd3fv7k0XvnfnnvnO7OyPc2bOBMaY4O7duwncunUrvH37tgHIekz+YyvQ/DSg13yB+ykUCkXo7+8PBwYGzOTkpAMyY1euXHH/OcPTjm7cuBHxNS5evBidL1++HKYGcO3atQgXLlwIz507Z8bHx83nz58dkBk7deqUeffunTl27JiBqx1dvXrVfPz4MYF9+/aZ169fOyAfOXIkTARw6dIlh+PHj4eHDh1yZKJGBsiMIXOhnTt3GvjaUU9Pjws0jtbWVhe0gP+JAE6fPh3s2bMnxPGjR4/M+/fv3UVu3rzpgMwYOuQtW7YYbLQjeTpxnDx50ixZsiQCtokAtm7dGra0tLiLYYTMOxsdHXVAZgwdHGRstKPDhw9HjzoL8BIBrFy5MiIg846HhoaKwFicpx3ZJ2iePXuWC3iJANasWRPW1tYasHfvXvPkyZNUoBMeNtqRvL48wEsEsGvXrmD79u3m4cOHEe7cuRPNYWStg4uNdsS7vXfvXi5SvwGcbd682QwODkaQOS/zXuvgxgPYuHFj9NFmAV4iADu9grVr17opJuCdy5eMrHVwsdGOGhsbXaB5gJcIwL7bYPny5W6xEeh3iqx1cLHRjpYtW2bOnDmTC3iJALq7u4PFixcXffXr1q0zIyMjDshaBxcb7YgP89WrV7mAlwhg9+7dwerVq8OqqiojgFhXV+eArHVwsdGOmpqawoULF5o8wEsEYI/5FrUWSy3qLBosGmP40+J3j0XYFDkKgl+9rt6iOQaxrbFYkDoLgD2+taj0mAvZ42c/9gMc4ccC4JhhUWHxo7cj0F+87fden8yGPp9/zcrnsdz+NS23/x9EguTtcgB3OheJFzepAei8nQe407k4AX/58iW1MImI8bydhXhOL4Xr16+HVEr37993QUtdoW+gaC3XeTsL8fU8DX19fWFvb6/58OGDgw0mKk70DUw7n6fl9DjOnz8fsuqNjY0VgYC4gebm5uQriOfz+JGV0zVOnDgRpt0Mj59x9KkfYTyfU2IJOLJyuuDAgQNhV1eXefr0aRF4BYyjLzkNS+VzDorRrJwOOjs7w23btpkHDx4UgcKUcfSZ60BaPuewa35uTpd5/vjx4yKe1Jfx+jE1gLR8ziEyX7EsJkePHi0KQG8+mHKc37x543L/pk2bwrJWwrR8znQRmTqAi7CgIGsn8c0Hd08W1V97bgB5+Vz2Bpx5rNpJ2uaDlD2tXJCXz7kjSnHO8ZzOneqFqpw7TwSg8rnUA20W/1jssPjb1wroq1NqgQVet9RD1xFNFn95v3CqtH3kxNcD3/icP8fn8UU+sJ98np8Fp0QtEHh9pbef523n+foA+++EGNlOTU2VhC3BW0EWJ69HUKovkBuANW6VxQS5FE96CfGeArMFMJYZwKdPn1JhHY5LFYxciie9BDlLX0FKePoNmQHYaBOwi858NiDiEJmxNK70E3TSYYxeQ1mzYGJiIrArXb2tUlpttH0WBYt/efQyr5EZ87o+uNhgG09i/KfXUPY0tA7rbf52yyfVCx8O+78XL15Ed4XMGDo4cLHBVvoKGnnrf1EAZ8+erWZtZ+0eHh7O3d/DgYsNtrpnoKH7B6U6aIwHtlIJDh48OGDTpesB8NGV2tujgwMXG2x1b0FD9w+YCVKUajAejI6OOuzfv7+7o6Njkkddam+PDg5csSvnMUvnTTpuAsaCt2/fRrCO6ykeSu3t0cHRNuV+bLoDJ+B/YIUI7e3tvZRO5H+mHgUGQGYMHRxtU87FdQdOunAyYwKbaiOsX78+pA9UKBTcB0eLDSAzhg6Otsm6sHTf9EyRKknOwcuXLyPYVPpVphvVjL3YEECW6QlH22QFkDZDGNuwYUMo5+D58+cONsPNqqmpcQXnqlWrJtra2jpFh8wYOjhwRZcVQNoMiXfXnBOfMqsrKipWNDQ09Fp5RUp/YIUNomf27NlLfU6f523n+31/vc/5f6TYNvqaoN7XBPQifnPXlb2+rwdkf1/pawDpD8xV/YGZMRs5Znp9paondH9hjqopZojRf/M9B2Tz737/AAAAAElFTkSuQmCC);
}
.sprite.hm { background-position:0px 0px;}
.sprite.hs { background-position:0px -16px;}
.sprite.sp { background-position:0px -32px;}
.sprite.nw { background-position:-16px 0px;}
.sprite.ed { background-position:-16px -16px;}
.sprite.rn { background-position:-16px -32px;}
.sprite.no {
background:none;
}
.sep {

File diff suppressed because one or more lines are too long