29 Commits

Author SHA1 Message Date
db659236bf doc: update readme 2017-11-18 15:37:13 +13:00
817b1690e7 bump all versions to 3.2.0 2017-11-18 15:33:35 +13:00
5b533f7b40 redesign the 'recent changes' page, include diff links, "new" display, hover rows 2017-11-18 15:30:50 +13:00
51aae382b7 hover all timestamp displays to get more detail 2017-11-18 15:30:29 +13:00
cfc0107bef staticResources.go: rebuild 2017-11-18 15:05:12 +13:00
e997e1b08a replace menu icons with inline SVG from materialdesignicons.com 2017-11-18 15:04:39 +13:00
c830c2b4dd add new ContentedBBCodeTag option for contented 1.2.0++ thumbnails 2017-11-18 15:04:19 +13:00
fdb854e6c7 doc: changelog 2017-10-29 15:10:35 +13:00
f934c2917f catch one more case of title normalisation 2017-10-29 15:09:53 +13:00
1bfefdccb3 bump all versions to 3.1.3 2017-10-29 14:11:31 +13:00
9ca58bc16c doc: update readme 2017-10-29 14:08:10 +13:00
122acf6999 rebuild staticResources.go 2017-10-29 14:05:20 +13:00
f627946c0d use 'dep' for dependency management 2017-10-29 14:04:26 +13:00
5b42685956 diff/test: fix package import path 2017-10-29 14:01:16 +13:00
90fedf86d9 serve proper 404 if favicon.ico not configured 2017-10-29 13:40:15 +13:00
a9a6b51a3f show yatwiki version in Server header 2017-10-29 13:40:00 +13:00
9687f90cf5 build: use simpler cleanup target 2017-10-29 13:29:12 +13:00
5cc93387e7 fix a regression with not normalising titles to lowercase/trim 2017-10-29 13:19:24 +13:00
fc57e4d8f3 bump version to 3.1.2 2017-10-15 20:02:20 +13:00
2bc26c5966 bump version to 3.1.1 2017-10-15 20:01:59 +13:00
f5767db840 load contented without jquery, but it's present by the callback 2017-10-15 20:00:04 +13:00
edf88d1f31 doc: changelog update 2017-10-15 19:58:04 +13:00
262c3ba903 contented: update integration to 1.1.0 2017-10-15 19:56:03 +13:00
a260d102ee doc: update readme, bump version to 3.1.0 2017-10-08 17:09:15 +13:00
179617d058 contented integration 2017-10-08 17:08:26 +13:00
5347efb51a bump version to 3.0.3 2017-08-13 18:29:21 +12:00
e3cee5b94c doc: changelog update 2017-08-13 18:27:04 +12:00
e4cf02cde7 restructure error handling to prevent reflected XSS 2017-08-13 18:25:58 +12:00
06e5b4ddf9 bump version to 3.0.1 2017-08-13 18:07:29 +12:00
20 changed files with 266 additions and 67 deletions

3
.gitignore vendored
View File

@@ -8,3 +8,6 @@ cmd/yatwiki-server/yatwiki-server
# Development db files # Development db files
cmd/yatwiki-server/*.db cmd/yatwiki-server/*.db
cmd/yatwiki-server/config.json cmd/yatwiki-server/config.json
# Vendor
vendor/

14
DB.go
View File

@@ -3,6 +3,7 @@ package yatwiki
import ( import (
"database/sql" "database/sql"
"fmt" "fmt"
"strings"
"time" "time"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
@@ -91,7 +92,7 @@ func (this *WikiDB) GetRevision(revId int) (*Article, error) {
} }
func (this *WikiDB) GetLatestVersion(title string) (*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) return this.parseArticle(row)
} }
@@ -103,6 +104,10 @@ func (aae ArticleAlteredError) Error() string {
return fmt.Sprintf("Warning: Your changes were not based on the most recent version of the page (r%d ≠ r%d). No changes were saved.", aae.got, aae.expected) return fmt.Sprintf("Warning: Your changes were not based on the most recent version of the page (r%d ≠ r%d). No changes were saved.", aae.got, aae.expected)
} }
func (this *WikiDB) normaliseTitle(title string) string {
return strings.ToLower(strings.Trim(title, " \r\n\t"))
}
func (this *WikiDB) SaveArticle(title, author, body string, expectBaseRev int64) error { func (this *WikiDB) SaveArticle(title, author, body string, expectBaseRev int64) error {
isNewArticle := false isNewArticle := false
a, err := this.GetLatestVersion(title) a, err := this.GetLatestVersion(title)
@@ -125,7 +130,7 @@ func (this *WikiDB) SaveArticle(title, author, body string, expectBaseRev int64)
var titleId int64 var titleId int64
if isNewArticle { if isNewArticle {
titleInsert, err := this.db.Exec(`INSERT INTO titles (title) VALUES (?)`, title) titleInsert, err := this.db.Exec(`INSERT INTO titles (title) VALUES (?)`, this.normaliseTitle(title))
if err != nil { if err != nil {
return err return err
} }
@@ -147,7 +152,10 @@ func (this *WikiDB) SaveArticle(title, author, body string, expectBaseRev int64)
} }
func (this *WikiDB) GetRevisionHistory(title string) ([]Article, error) { func (this *WikiDB) GetRevisionHistory(title string) ([]Article, error) {
rows, err := this.db.Query(`SELECT articles.id, articles.modified, articles.author FROM articles WHERE article = (SELECT id FROM titles WHERE title = ?) ORDER BY modified DESC`, title) rows, err := this.db.Query(
`SELECT articles.id, articles.modified, articles.author FROM articles WHERE article = (SELECT id FROM titles WHERE title = ?) ORDER BY modified DESC`,
this.normaliseTitle(title),
)
if err != nil { if err != nil {
return nil, err return nil, err
} }

21
Gopkg.lock generated Normal file
View File

@@ -0,0 +1,21 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
name = "github.com/mattn/go-sqlite3"
packages = ["."]
revision = "5160b48509cf5c877bc22c11c373f8c7738cdb38"
version = "v1.3.0"
[[projects]]
branch = "master"
name = "golang.org/x/net"
packages = ["context"]
revision = "c73622c77280266305273cb545f54516ced95b93"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "a1f2d643f8c1770c92ee1759184a0c7004af5672869db579328d05bb7cfd6bef"
solver-name = "gps-cdcl"
solver-version = 1

26
Gopkg.toml Normal file
View File

@@ -0,0 +1,26 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
[[constraint]]
name = "github.com/mattn/go-sqlite3"
version = "1.3.0"

View File

@@ -2,20 +2,24 @@
# Makefile for YATWiki3 # Makefile for YATWiki3
# #
VERSION:=3.0.1 VERSION:=3.2.0
SOURCES:=Makefile \ SOURCES:=Makefile \
static \ static \
cmd $(wildcard cmd/yatwiki-server/*.go) \ cmd $(wildcard cmd/yatwiki-server/*.go) \
Gopkg.lock Gopkg.toml \
$(wildcard *.go) $(wildcard *.go)
GOFLAGS := -ldflags='-s -w' -gcflags='-trimpath=$(GOPATH)' -asmflags='-trimpath=$(GOPATH)' GOFLAGS:=-a \
-ldflags "-s -w -X code.ivysaur.me/yatwiki.SERVER_HEADER=YATWiki/$(VERSION)" \
-gcflags '-trimpath=$(GOPATH)' \
-asmflags '-trimpath=$(GOPATH)'
# #
# Phony targets # Phony targets
# #
.PHONY: all dist clean .PHONY: all dist clean deps
all: build/linux64/yatwiki-server build/win32/yatwiki-server.exe all: build/linux64/yatwiki-server build/win32/yatwiki-server.exe
@@ -25,9 +29,14 @@ dist: \
_dist/yatwiki-$(VERSION)-src.zip _dist/yatwiki-$(VERSION)-src.zip
clean: clean:
if [ -f ./staticResources.go ] ; then rm ./staticResources.go ; fi rm -f ./staticResources.go
if [ -d ./build ] ; then rm -r ./build ; fi rm -fr ./build
if [ -f ./yatwiki ] ; then rm ./yatwiki ; fi rm -f ./yatwiki
deps:
go get -u github.com/jteeuwen/go-bindata/...
go get -u github.com/golang/dep/cmd/dep
dep ensure
# #
# Generated files # Generated files

View File

@@ -21,6 +21,8 @@ type ServerOptions struct {
ExternalBaseURL string ExternalBaseURL string
DeclareRSSLanguage string DeclareRSSLanguage string
DeclareRSSEmail string DeclareRSSEmail string
ContentedServer string
ContentedBBCodeTag string
} }
func DefaultOptions() *ServerOptions { func DefaultOptions() *ServerOptions {
@@ -41,5 +43,7 @@ func DefaultOptions() *ServerOptions {
ExternalBaseURL: "http://127.0.0.1/", ExternalBaseURL: "http://127.0.0.1/",
DeclareRSSLanguage: "en-GB", DeclareRSSLanguage: "en-GB",
DeclareRSSEmail: `nobody@example.com`, DeclareRSSEmail: `nobody@example.com`,
ContentedServer: "",
ContentedBBCodeTag: "",
} }
} }

View File

@@ -14,6 +14,8 @@ import (
"time" "time"
) )
var SERVER_HEADER string = "YATWiki/0.0.0-devel"
type WikiServer struct { type WikiServer struct {
db *WikiDB db *WikiDB
opts *ServerOptions opts *ServerOptions
@@ -74,12 +76,16 @@ func NewWikiServer(opts *ServerOptions) (*WikiServer, error) {
return &ws, nil return &ws, nil
} }
func (this *WikiServer) GetBBCodeRenderer() *BBCodeRenderer {
return NewBBCodeRenderer(this.opts.ExpectBaseURL, this.opts.ContentedServer, this.opts.ContentedBBCodeTag)
}
func (this *WikiServer) Close() { func (this *WikiServer) Close() {
this.db.Close() this.db.Close()
} }
func (this *WikiServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (this *WikiServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Server", "YATWiki3") w.Header().Set("Server", SERVER_HEADER)
if len(this.bans) > 0 { if len(this.bans) > 0 {
remoteIP := RemoteAddrToIPAddress(r.RemoteAddr) remoteIP := RemoteAddrToIPAddress(r.RemoteAddr)
@@ -110,9 +116,13 @@ func (this *WikiServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write(content) w.Write(content)
return return
} else if remainingPath == "favicon.ico" && len(this.opts.FaviconFilePath) > 0 { } else if remainingPath == "favicon.ico" {
if len(this.opts.FaviconFilePath) > 0 {
w.Header().Set("Content-Type", "image/x-icon") w.Header().Set("Content-Type", "image/x-icon")
http.ServeFile(w, r, this.opts.FaviconFilePath) http.ServeFile(w, r, this.opts.FaviconFilePath)
} else {
http.Error(w, "Not found", 404)
}
return return
} else if remainingPath == "download-database" { } else if remainingPath == "download-database" {

View File

@@ -1,6 +1,6 @@
A semi-anonymous wiki for use in trusted environments. A semi-anonymous wiki for use in trusted environments.
As of the 20150901 release, a desktop version is available for Windows (based on PHPDesktop). For the 20150901 release, a desktop version is available for Windows (based on PHPDesktop).
As of the 3.0 release, YATWiki is now a standalone server instead of a PHP script. As of the 3.0 release, YATWiki is now a standalone server instead of a PHP script.
@@ -14,6 +14,7 @@ As of the 3.0 release, YATWiki is now a standalone server instead of a PHP scrip
- IP-based ban system - IP-based ban system
- Article index, random article, download database backup - Article index, random article, download database backup
- Source code highlighting (thanks [url=https://github.com/isagalaev/highlight.js]highlight.js[/url]) - Source code highlighting (thanks [url=https://github.com/isagalaev/highlight.js]highlight.js[/url])
- Optional integration with `contented` for file/image uploads
Written in Golang, PHP Written in Golang, PHP
@@ -35,6 +36,31 @@ This package can be installed via go get: `go get code.ivysaur.me/yatwiki`
=CHANGELOG= =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
- Fix a regression in 3.x series with not normalising article titles
- Fix server response if favicon is not configured
2017-10-15 3.1.1
- Update `contented` integration (requires `contented` >= 1.1.0)
2017-10-08 3.1.0
- Feature: Support content upload to a `contented` server
2017-08-11 3.0.2
- Fix an issue with XSS prevention for web browsers other than Chrome
2017-08-11 3.0.1 2017-08-11 3.0.1
- Feature: New `TrustXForwardedFor` config option for usage behind reverse proxies - Feature: New `TrustXForwardedFor` config option for usage behind reverse proxies
- Fix an issue with article titles containing `+` - Fix an issue with article titles containing `+`

View File

@@ -2,6 +2,7 @@ package yatwiki
import ( import (
"encoding/json" "encoding/json"
"html"
"html/template" "html/template"
"net/url" "net/url"
"regexp" "regexp"
@@ -13,13 +14,17 @@ type BBCodeRenderer struct {
baseUrl string baseUrl string
CodePresent bool CodePresent bool
DynamicContentWarning string DynamicContentWarning string
ContentedURL string
ContentedTag string
} }
func NewBBCodeRenderer(baseUrl string) *BBCodeRenderer { func NewBBCodeRenderer(baseUrl, ContentedURL, ContentedTag string) *BBCodeRenderer {
return &BBCodeRenderer{ return &BBCodeRenderer{
baseUrl: baseUrl, baseUrl: baseUrl,
CodePresent: false, CodePresent: false,
DynamicContentWarning: `⚠ run dynamic content`, 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 := range s_to_r {
for prr.match.MatchString(data) { // repeat until all recursive replacements are consumed for prr.match.MatchString(data) { // repeat until all recursive replacements are consumed

View File

@@ -4,7 +4,7 @@ import (
"reflect" "reflect"
"testing" "testing"
"code.ivysaur.me/yatwiki3/diff" "code.ivysaur.me/yatwiki/diff"
) )
func TestDiff(t *testing.T) { func TestDiff(t *testing.T) {

View File

@@ -17,7 +17,9 @@ type pageTemplateOptions struct {
LoadCodeResources bool LoadCodeResources bool
DefaultPage string DefaultPage string
AllowDownload bool AllowDownload bool
SessionMessage template.HTML SessionMessage string
PageNotExistsError bool
PageNotExistsTarget string
} }
func DefaultPageTemplateOptions(opts *ServerOptions) *pageTemplateOptions { func DefaultPageTemplateOptions(opts *ServerOptions) *pageTemplateOptions {
@@ -85,26 +87,56 @@ function els(e,s){ // no js exec in innerHTML
</head> </head>
<body> <body>
<div class="header"> <div class="header">
<a href="{{.BaseURL}}view/{{.DefaultPage | pathcomponent}}" title="Home"><div class="sprite hm"></div></a> <a href="{{.BaseURL}}view/{{.DefaultPage | pathcomponent}}" title="Home"><div class="sprite">
<a href="javascript:;" onclick="tid('spm');tid('tr1');tid('tr2');" title="Menu"><div class="sprite sp"></div></a> <svg viewBox="0 0 24 24">
<a href="{{.BaseURL}}modify/{{.NewArticleTitle | pathcomponent}}" title="New Page"><div class="sprite nw"></div></a> <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 }} {{if .CurrentPageIsArticle }}
<div class="sep"></div> <div class="sep"></div>
<a href="{{.BaseURL}}history/{{.CurrentPageName | pathcomponent}}" title="Page History"><div class="sprite hs"></div></a> <a href="{{.BaseURL}}history/{{.CurrentPageName | pathcomponent}}" title="Page History"><div class="sprite">
<a href="{{.BaseURL}}modify/{{.CurrentPageName | pathcomponent}}" title="Modify Page"><div class="sprite ed"></div></a> <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}} {{end}}
</div> </div>
<div id="tr1" style="display:none;"></div> <div id="tr1" style="display:none;"></div>
<div id="tr2" style="display:none;"></div> <div id="tr2" style="display:none;"></div>
<div class="ddmenu" id="spm" style="display:none;"> <div class="ddmenu" id="spm" style="display:none;">
<a href="{{.BaseURL}}recent/1"><div class="sprite no"></div> Recent Changes</a> <a href="{{.BaseURL}}recent/1"><div class="sprite"></div> Recent Changes</a>
<a href="{{.BaseURL}}random"><div class="sprite rn"></div> Random Page</a> <a href="{{.BaseURL}}random"><div class="sprite">
<a href="{{.BaseURL}}index"><div class="sprite no"></div> Article Index</a> <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}} {{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}} {{end}}
</div> </div>
<div class="content"> <div class="content">
{{if .PageNotExistsError}}
<div class="info">
No such article exists.
<a href="{{.BaseURL}}modify/{{.PageNotExistsTarget | pathcomponent}}">Click here</a> to create it.
</div>
{{end}}
{{if len .SessionMessage}} {{if len .SessionMessage}}
<div class="info">{{.SessionMessage}}</div> <div class="info">{{.SessionMessage}}</div>
{{end}} {{end}}

View File

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

View File

@@ -8,12 +8,8 @@ import (
"time" "time"
) )
func (this *WikiServer) noSuchArticleError(title string) template.HTML { func (this *WikiServer) serveErrorMessage(w http.ResponseWriter, err error) {
return template.HTML(`No such article exists. <a href="` + this.opts.ExpectBaseURL + `modify/` + template.HTMLEscapeString(url.PathEscape(title)) + `">Click here</a> to create it.`) this.serveErrorText(w, err.Error())
}
func (this *WikiServer) serveErrorMessage(w http.ResponseWriter, message error) {
this.serveErrorHTMLMessage(w, template.HTML(template.HTMLEscapeString(message.Error())))
} }
func (this *WikiServer) serveInternalError(w http.ResponseWriter, r *http.Request, e error) { func (this *WikiServer) serveInternalError(w http.ResponseWriter, r *http.Request, e error) {
@@ -21,8 +17,12 @@ func (this *WikiServer) serveInternalError(w http.ResponseWriter, r *http.Reques
http.Error(w, "An internal error occurred. Please ask an administrator to check the log file.", 500) http.Error(w, "An internal error occurred. Please ask an administrator to check the log file.", 500)
} }
func (this *WikiServer) serveErrorHTMLMessage(w http.ResponseWriter, msg template.HTML) { func (this *WikiServer) serveErrorText(w http.ResponseWriter, msg string) {
this.serveRedirect(w, this.opts.ExpectBaseURL+"view/"+url.PathEscape(this.opts.DefaultPage)+"?error="+url.QueryEscape(string(msg))) this.serveRedirect(w, this.opts.ExpectBaseURL+"view/"+url.PathEscape(this.opts.DefaultPage)+"?error="+url.QueryEscape(msg))
}
func (this *WikiServer) serveNoSuchArticle(w http.ResponseWriter, lookingFor string) {
this.serveRedirect(w, this.opts.ExpectBaseURL+"view/"+url.PathEscape(this.opts.DefaultPage)+"?notfound="+url.QueryEscape(lookingFor))
} }
func (this *WikiServer) serveRedirect(w http.ResponseWriter, location string) { func (this *WikiServer) serveRedirect(w http.ResponseWriter, location string) {
@@ -32,7 +32,14 @@ func (this *WikiServer) serveRedirect(w http.ResponseWriter, location string) {
func (this *WikiServer) servePageResponse(w http.ResponseWriter, r *http.Request, pto *pageTemplateOptions) { func (this *WikiServer) servePageResponse(w http.ResponseWriter, r *http.Request, pto *pageTemplateOptions) {
w.WriteHeader(200) w.WriteHeader(200)
pto.SessionMessage = template.HTML(r.URL.Query().Get("error")) // FIXME reflected XSS (although Chrome automatically blocks it..)
if noSuchArticleTarget, ok := r.URL.Query()["notfound"]; ok {
pto.PageNotExistsError = true
pto.PageNotExistsTarget = noSuchArticleTarget[0]
} else {
pto.SessionMessage = r.URL.Query().Get("error")
}
err := this.pageTmp.Execute(w, pto) err := this.pageTmp.Execute(w, pto)
if err != nil { if err != nil {
@@ -40,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 // 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 { func (this *WikiServer) viewLink(articleTitle string) template.HTML {

View File

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

View File

@@ -12,7 +12,7 @@ func (this *WikiServer) routeHistory(w http.ResponseWriter, r *http.Request, art
revs, err := this.db.GetRevisionHistory(articleTitle) revs, err := this.db.GetRevisionHistory(articleTitle)
if err != nil { if err != nil {
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
this.serveErrorHTMLMessage(w, this.noSuchArticleError(articleTitle)) this.serveNoSuchArticle(w, articleTitle)
return return
} }
@@ -21,7 +21,7 @@ func (this *WikiServer) routeHistory(w http.ResponseWriter, r *http.Request, art
} }
if len(revs) == 0 { if len(revs) == 0 {
this.serveErrorHTMLMessage(w, this.noSuchArticleError(articleTitle)) this.serveNoSuchArticle(w, articleTitle)
return return
} }
@@ -39,7 +39,7 @@ func (this *WikiServer) routeHistory(w http.ResponseWriter, r *http.Request, art
for _, rev := range revs { for _, rev := range revs {
revIdStr := fmt.Sprintf("%d", rev.ID) revIdStr := fmt.Sprintf("%d", rev.ID)
content += `<tr>` + 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>` + template.HTMLEscapeString(rev.Author) + `</td>` +
`<td><input type="radio" name="t" value="` + revIdStr + `">&nbsp;<input type="radio" name="f" value="` + revIdStr + `"></td>` + `<td><input type="radio" name="t" value="` + revIdStr + `">&nbsp;<input type="radio" name="f" value="` + revIdStr + `"></td>` +
`</tr>` `</tr>`

View File

@@ -49,6 +49,24 @@ func (this *WikiServer) routeModify(w http.ResponseWriter, r *http.Request, arti
</label> </label>
<input type="submit" value="Save &raquo;"> <input type="submit" value="Save &raquo;">
| <a href="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`formatting`) + `" target="_blank">formatting&nbsp;help</a> | <a href="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`formatting`) + `" target="_blank">formatting&nbsp;help</a>
`
if len(this.opts.ContentedServer) > 0 {
content += `
<script type="text/javascript" src="` + this.opts.ContentedServer + `sdk.js"></script>
| <a href="javascript:;" id="open-contented-uploader">upload...</a>
<script type="text/javascript">
document.getElementById("open-contented-uploader").addEventListener("click", function() {
contented.init("#contentctr", function(items) {
for (var i = 0; i < items.length; ++i) {
$("#contentctr textarea").append(" " + contented.getPreviewURL(items[i]) + " ");
}
});
});
</script>
`
}
content += `
</div> </div>
<div id="contentctr"><textarea name="content">` + template.HTMLEscapeString(existingBody) + `</textarea></div> <div id="contentctr"><textarea name="content">` + template.HTMLEscapeString(existingBody) + `</textarea></div>
</form> </form>

View File

@@ -37,25 +37,41 @@ func (this *WikiServer) routeRecentChanges(w http.ResponseWriter, r *http.Reques
pto.CurrentPageName = "Recent Changes" pto.CurrentPageName = "Recent Changes"
content := `<h2>Recent Changes</h2><br>` + content := `<h2>Recent Changes</h2><br>` +
`<em>Showing up to ` + fmt.Sprintf("%d", this.opts.RecentChanges) + ` changes.</em><br>` + `<em>Showing up to ` + fmt.Sprintf("%d", this.opts.RecentChanges) + ` changes.</em><br><br>` +
`<table>` `<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 { 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>` + content += `<tr>` +
`<td><a href="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`view/`+url.PathEscape(rev.Title)) + `">` + template.HTMLEscapeString(rev.Title) + `</a>` + `<td><a href="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`view/`+url.PathEscape(rev.Title)) + `">` + template.HTMLEscapeString(rev.Title) + `</a></td>` +
` [<a href="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`archive/`+fmt.Sprintf("%d", rev.ID)) + `">a</a>]` + `<td>` +
`<a href="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`archive/`+fmt.Sprintf("%d", rev.ID)) + `">rev</a> &nbsp; ` +
diffHtml +
`</td>` + `</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>` `</tr>`
} }
content += `<tr><td>` content += `</table>`
if pageNum > 1 { 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 { 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) pto.Content = template.HTML(content)
this.servePageResponse(w, r, pto) this.servePageResponse(w, r, pto)

View File

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

View File

@@ -112,16 +112,6 @@ fieldset legend {
display:inline-block; display:inline-block;
width:16px;height:16px; width:16px;height:16px;
vertical-align:text-bottom; vertical-align:text-bottom;
background-repeat:no-repeat;
background-image: url(data:image/png;base64,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);
}
.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; background:none;
} }
.sep { .sep {

File diff suppressed because one or more lines are too long